ATTiny85 PWM for 4 LEDs - avr

I need to control 4 individual LEDs via PWM on an ATTiny85. I have found lots of info on how to control 3 LEDs. But apparently to control 4 with PWM, you have to really twist the 85 into knots. Is there an easier way to handle 4 LEDs on the 85, or would it be better to step over to the 84? If I went with the 84, would I be likely to run into the same brick walls as with the 85?
I found this code for controlling 4 on the 85, but it's above my skill level. Anyone see any issues with it?
/* Four PWM Outputs */
// ATtiny85 outputs
const int Red = 0;
const int Green = 1;
const int Blue = 4;
const int White = 3;
volatile uint8_t* Port[] = {&OCR0A, &OCR0B, &OCR1A, &OCR1B};
void setup() {
pinMode(Red, OUTPUT);
pinMode(Green, OUTPUT);
pinMode(Blue, OUTPUT);
pinMode(White, OUTPUT);
// Configure counter/timer0 for fast PWM on PB0 and PB1
TCCR0A = 3<<COM0A0 | 3<<COM0B0 | 3<<WGM00;
TCCR0B = 0<<WGM02 | 3<<CS00; // Optional; already set
// Configure counter/timer1 for fast PWM on PB4
TCCR1 = 1<<CTC1 | 1<<PWM1A | 3<<COM1A0 | 7<<CS10;
GTCCR = 1<<PWM1B | 3<<COM1B0;
// Interrupts on OC1A match and overflow
TIMSK = TIMSK | 1<<OCIE1A | 1<<TOIE1;
}
ISR(TIMER1_COMPA_vect) {
if (!bitRead(TIFR,TOV1)) bitSet(PORTB, White);
}
ISR(TIMER1_OVF_vect) {
bitClear(PORTB, White);
}
// Sets colour Red=0 Green=1 Blue=2 White=3
// to specified intensity 0 (off) to 255 (max)
void SetColour (int colour, int intensity) {
*Port[colour] = 255 - intensity;
}
void loop() {
for (int i=-255; i <= 254; i++) {
OCR0A = abs(i);
OCR0B = 255-abs(i);
OCR1A = abs(i);
OCR1B = 255-abs(i);
delay(10);
}
}

If you want to save pins at the expense of a more complicated strategy, you can get away with only 3 pins by connecting the LEDs as two sets of two like this...
Instead of using the built in PWM, you will need to do the PWM manually by setting a timer and then changing the INPUT/OUTPUT and ON/OFF of each of the pins each time the timer expires.
+-----+----------+----------+----------+
| LED | A | B | C |
+-----+----------+----------+----------+
| 1 | OUTPUT 1 | INPUT | OUTPUT 0 |
| 2 | OUTPUT 0 | INPUT | OUTPUT 1 |
| 3 | INPUT | OUTPUT 1 | OUTPUT 0 |
| 4 | INPUT | OUTPUT 0 | OUTPUT 1 |
+-----+----------+----------+----------+
Update or comment if you want more details on this strategy.

An easy strategy is to multiplex the 4 LEDs onto a single PWM pin. This will let independently control the brightness of each LED on the ATTINY using 5 pins total.
So, for example, you could connect all 4 of the cathodes together and connect those to a single PWM pin. Then you connect each of the 4 anodes to a different IO pin.
At any given moment, only one of the anodes is in output mode - the others are left floating. The means that at most 1 single LED is active and its brightness is controlled by the PWM duty cycle.
You can then use the overflow ISR for the PWM timer to activate the next LED in the sequence after each PWM cycle. You also update the PWM match to reflect the brightness of the next LED.
If you rotate though the LEDs quickly (faster than, say, 60 times per second), then visually they all just look like they are on at the desired brightness. PWM, after all, is just blinking an LED too quickly to see, so we are just adding a second dimension on to it.
One downside: Since only a single LED is on at any moment, the maximum total brightness will theoretically be 1/4 of what it would be if you drove all the LEDs independently. In practice this is likely an issue since the ATTINY is limited to how much current it can pass though all if its pins at once if you tried to light all the LEDs at the same time.
One hint: when setting up the PWM timer, make is so that the LED is OFF at the beginning of the cycle and turns on in the middle. This will give the ISR time to step to the next LED while all LEDs are off. This is better becuase it is Easy to see an LED that is on when it should not be, but not so easy to see an LED that is off when it should be on.
One suggestion: I will get flamed for this, but you can leave out any current limiting resistors when doing this since each LED is only on for at most 1/4 of the time. This will give you more brightness and also make it so you can dial down the PWM duty cycle so you have more off time at the beginning of each cycle to step to the next LED.
I have used this technique successfully many times, and even have been able to multiplex 6 RGB LEDs (three channels each) onto one chip and it works great.
Update the question if you have any questions about the details!

Related

Pulse sensor not showing correct data on ESP32 (Micropython)

I connected a pulse sensor (This one) to my ESP32(WROOM 32 model and micropython) using 3 wires(3.3v, GND, Analog) and I expected to read pulse data on my terminal but at the first time the data I received is 6, after that the data remains 0. When I take my finger off the sensor and put it back, the data that is received still makes no sense (12, 18, ...).
the Onboard LED also blinked just when I put my finger on the sensor.
the tutorial I use for this interface:
here
the code:
from machine import Pin, Signal, ADC, Timer
adc = ADC(Pin(36))
# On my board on = off, need to reverse.
led = Signal(Pin(2, Pin.OUT), invert=True)
MAX_HISTORY = 250
# Maintain a log of previous values to
# determine min, max, and threshold.
history = []
beat = False
beats = 0
def calculate_bpm(t):
global beats
print('BPM:', beats * 6) # Triggered every 10 seconds, * 6 = bpm
beats = 0
timer = Timer(1)
timer.init(period=10000, mode=Timer.PERIODIC, callback=calculate_bpm)
while True:
v = adc.read()
history.append(v)
# Get the tail, up to MAX_HISTORY length
history = history[-MAX_HISTORY:]
minima, maxima = min(history), max(history)
threshold_on = (minima + maxima * 3) // 4 # 3/4
threshold_off = (minima + maxima) // 2 # 1/2
if not beat and v > threshold_on:
beat = True
beats += 1
led.on()
if beat and v < threshold_off:
beat = False
led.off()

Problems getting the current time in microseconds with a STM32 device

I am using a stm32f103c8 and I need a function that will return the correct time in microseconds when called from within an interrupt handler. I found the following bit of code online which proports to do that:
uint32_t microsISR()
{
uint32_t ret;
uint32_t st = SysTick->VAL;
uint32_t pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
uint32_t ms = UptimeMillis;
if (pending == 0)
ms++;
return ms * 1000 - st / ((SysTick->LOAD + 1) / 1000);
}
My understanding of how this works is uses the system clock counter which repeatedly counts down from 8000 (LOAD+1) and when it reaches zero, an interrupt is generated which increments the variable UptimeMills. This gives the time in milliseconds. To get microseconds we get the current value of the system clock counter and divide it by 8000/1000 to give the offset in microseconds. Since the counter is counting down we subtract it from the current time in milliseconds * 1000. (Actually to be correct I believe one should have be added to the # milliseconds in this calculation).
This is all fine and good unless, when this function is called (in an interrupt handler), the system clock counter has already wrapped but the system clock interrupt has not yet been called, then UptimeMillis count will be off by one. This is the purpose of the following lines:
if (pending == 0)
ms++;
Looking at this does not make sense, however. It is incrementing the # ms if there is NO pending interrupt. Indeed if I use this code, I get a large number of glitches in the returned time at the points at which the counter rolls over. So I changed the lines to:
if (pending != 0)
ms++;
This produced much better results but I still get the occasional glitch (about 1 in every 2000 interrupts) which always occurs at a time when the counter is rolling over.
During the interrupt, I log the current value of milliseconds, microseconds and counter value. I find there are two situations where I get an error:
Milli Micros DT Counter Pending
1 1661 1660550 826 3602 0
2 1662 1661374 824 5010 0
3 1663 1662196 822 6436 0
4 1663 1662022 -174 7826 0
5 1664 1663847 1825 1228 0
6 1665 1664674 827 2614 0
7 1666 1665501 827 3993 0
The interrupts are comming in at a regular rate of about 820us. In this case what seems to be happening between interrupt 3 and 4 is that the counter has wrapped but the pending flag is NOT set. So I need to be adding 1000 to the value and since I fail to do so I get a negative elapsed time.
The second situation is as follows:
Milli Micros DT Counter Pending
1 1814 1813535 818 3721 0
2 1815 1814357 822 5151 0
3 1816 1815181 824 6554 0
4 1817 1817000 1819 2 1
5 1817 1816817 -183 1466 0
6 1818 1817637 820 2906 0
This is a very similar situation except in this case the counter has NOT yet wrapped and yet I am already getting the pending interrupt flag which causes me to erronously add 1000.
Clearly there is some kind of race condition between the two competing interrupts. I have tried setting the clock interrupt priority both above and below that of the external interrupt but the problem persists.
Does anyone have any suggestions how to deal with this problem or a suggestion for a different approach to get the time is microseconds within an interrupt handler.
Read UptimeMillis before and after SysTick->VAL to ensure a rollover has not occurred.
uint32_t microsISR()
{
uint32_t ms = UptimeMillis;
uint32_t st = SysTick->VAL;
// Did UptimeMillis rollover while reading SysTick->VAL?
if (ms != UptimeMillis)
{
// Rollover occurred so read both again.
// Must read both because we don't know whether the
// rollover occurred before or after reading SysTick->VAL.
// No need to check for another rollover because there is
// no chance of another rollover occurring so quickly.
ms = UptimeMillis;
st = SysTick->VAL;
}
return ms * 1000 - st / ((SysTick->LOAD + 1) / 1000);
}
Or here is the same idea in a do-while loop.
uint32_t microsISR()
{
uint32_t ms;
uint32_t st;
// Read UptimeMillis and SysTick->VAL until
// UptimeMillis doesn't rollover.
do
{
ms = UptimeMillis;
st = SysTick->VAL;
} while (ms != UptimeMillis);
return ms * 1000 - st / ((SysTick->LOAD + 1) / 1000);
}

Mikrocontroller (PIC16F1827) ADC scrambled output with MCC in MPLAB

Im trying to construct an AD-converter from a potentiometer to an Arduino. I´m trying to learn MCC in MPLAB at the same time. So far I have generated a code that fits my PIC (I think...). My problem is now that my bit represented output is incorrect. This is hoe my PIC16F1827 is configured (se picture)
RA0 = input, RB1 and RB2 = EUSART and RB0,RB3,RA7,RA6,RB7,RB6,RB5,RB4 = output.
My main file look like this (se code). I get an output but its represented wrong and i can´t figure ut why...
char ADC_temp_in;
while (1) //Infinite Loop
{
// Add your application code
printf("pot_value =%d\r\n", ADC_GetConversion(channel_AN0_ADC));
ADC_temp_in = ADC_GetConversion(channel_AN0_ADC); // temp
PORTB = ADC_temp_in; //Write Lower bits to PORTB
PORTA = ADC_temp_in>>6; //Write Higher 2 bits to PORTA
__delay_ms(100); //Delay
}
VREF+ = 5V and is connected directly to VDD.
My goal is to have RB0 as LSB and RA7 as MSB with the voltage difference 0-5 V with the potentiometer.
Two things:
ADC_temp_in had to by a 16 Bit value to hold a value greater than 8 Bit.
So try: uint16_t ADC_temp_in;
Of course your function ADC_GetConversion had to return a uint_16 value.
Another thing is, to get the MSB you had to shift your value 8 times right.
PORTA = ADC_temp_in>>8;

Implement serial in to parallel out shift register with AVR microcontroller

On the internet there are quite a number of tutorials of how to control a shift register with a microcontroller, but is it actually possible to implement the shift register function with only the microcontroller?
If you have enough pins, I don't see why the naive way wouldn't work...
For an n-bit shift in register, you need n+2 pins:
One clock-in
One data-in
n data-out
The pseudocode of the implementation is:
var byte r := 0 // Assuming n=8, so 8 bits fit into a single byte
var byte i := 0
forever:
wait for clock-in = low
wait for clock-in = high
r := r << 0 | data-in
i := i + 1
if i = n:
data-out<1..n> := r
i = 0
If you want to make sure that data-out is updated synchronously, make sure you use pins of a single port: then the data-out<1..n> := r statement can literally be a single port register assignment.
If you want to run this concurrently with other code, you should be able to use a pin for clock-in that can trigger an interrupt.

Real time serial data acquisition

I wish to take the samples from pic16f877a,4Mhz HS( if it is given a sine wave input) and plot the same.I am giving 230vp sine wave 50hz as input and transformed it into 5v range and level shifted it into positive voltages.
This is my code
// Loop variable
float val,val1;//Declare the adcvalue stored variables
char uart_rd[50],uart_rd1[50];
void rmsv();
void adc_uart();//adc read and uart write
void interrupt(){
if (PIR1.ADIF) {
PIR1.ADIF=0; // clear AD interrupt flag
val= (ADRESH << 8) | ADRESL; // read dadta from channel 0
{
val1=(val*325.0)/1023.0;
FloatToStr(val1,uart_rd1);
strncpy(uart_rd,uart_rd1,3);
UART1_Write_Text(uart_rd1);
//UART1_Write(10);
UART1_Write(13);
delay_us(1);
}
Delay_Cyc(3); //wait acquisition time
ADCON0.F2=1; //start conversion again
}
}
void main()
{
TRISA=0XFF;//porta as input
ADCON1 = 0x82; // AN0->AN4 selected as analog input
ADCON0 = 0b11000001; // Configue analog mode
INTCON.GIE = 1; //Enable global interrupt
INTCON.PEIE = 1; //Enable peripheral interrupt
PIE1.ADIE = 1; //Enable ADC interrupt
Delay_us(20); //wait for acquisition time
ADCON0.F2 = 1; //start conversion
// ADCON1=0X81;
UART1_Init(9600); // Initialize UART module at 9600 bps
//ADC_Init();
while(1);
}
I have checked the values in hyperterminal and see that the controller not even sample the peak voltage.I wish to have the samples correctly so that I can able to plot the waveform correctly
Probably it is not a software problem. You say "controller not even sample the peak voltage" and that the signal rage is between 5V and 0V. Well, if you have configured your PIC ADC using the PIC power voltage (Vp) as reference voltage and if the power voltage is less than 5V ( Vp<5V), then your max adc Voltage range is Vp. It could cause that the peak of the sin signal after ADC acquisition looks cut.
Vp ________ _________ _________
\ / \ /
\ / \ /
\ / \ /
\ / \ /
0 \_/ \_/
Measure the power voltage on the PIC pin and if it is the reason then adjust the voltage source or transform the signal into Vp range .

Resources