Updating PWM Duty Cycle Using a Millisecond Timer
In the previous lesson, we were able to get basic PWM working. In order to do something interesting with PWM, like gradually fading in or out the brightness of an LED or increasing the speed of a motor, we need to regularly update our duty cycle. For this, we are going to add a second timer. We will use the 8-bit timer/counter0 which we learned in a previous lesson that it can count up to about 30ms if we use the maximum prescaler of 1024.
In This Video
- Setting up a millisecond timer to update the duty cycle
- Using the 8-bit timer-counter 0 as a millisecond timer.
- Incrementing the duty cycle every 2 ms, resetting the duty cycle to 0 when it equals the period.
Procedure
TC0 - 8-bit Timer/Counter0
- Review the Register Description for TC0 - 8-bit Timer/Counter0
Look for the Waveform Generation Modes table in the datasheet. We will be using CTC mode which means that we need to set the WGM01 bit in the TCCR0A register.
Prescaler Selector Bits
Determine the 1024 Prescaler selector bits
From this table, we see that we need to set CS02 and CS00 in the TCCR0B register.
CTC Mode
- Select CTC mode and set the Prescaler in our Code
Here is the start of the new function for our millisecond timer
{
TCCR0A |= (1 << WGM01); //set to CTC mode
TCCR0B |= (1 << CS02) |= (1 << CS00); //set prescaler to 1024
}
OCR0A Value
Get a value for the OCR0A.
This register is associated with the CTC mode from the table shown in Step 2. This value will determine how long our timer will run.
Recall the formula...
We will rearrange it to solve for OCRnX (OCR0A). include our milliS variable, and add it to our function.
OCR0A = milliS * 7.8125 - 1
{
TCCR0A |= (1 << WGM01); //set to CTC mode
TCCR0B |= (1 << CS02) | (1 << CS00); //set prescaler to 1024
OCR0A = milliS * 7.8125 - 1;
}
Enable the interrupt
In the register description section for the 8-bit Timer/Counter0 there is the register for TC0 Interrupt Mask Register.
Bit 1 needs to be set in our milliS_timer function which is OCIE0A
{
TCCR0A |= (1 << WGM01); //set to CTC mode
TCCR0B |= (1 << CS02) | (1 << CS00); //set prescaler to 1024
OCR0A = milliS * 7.8125 - 1;
TIMSK0 |= (1 << OCIE0A); //enable the interrupt
}
Call the millS_timer function
- Recall that our period resolution was set at 800 so if we wait 2ms before we increment each we should see about 1.6 seconds before we reach the top. Our function call will look like this...
Increase the Duty Cycle
- Finally, we will write the Counter/Timer interrupt routine with some logic to increase our duty cycle and if the duty value has reached the period value, the duty value is reset.
{
uint16_t period = OCR1A;
uint16_t duty = OCR1B;
if (duty < period)
{
duty++;
}
else
{
duty = 0;
}
OCR1B = duty;
}
Program the device
The full code is here:
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define LED_ON PORTB |= (1<<PORTB5)
#define LED_OFF PORTB &= ~(1<<PORTB5)
#define LED_TOGGLE PINB |= (1<<PINB5)
ISR(TIMER1_COMPA_vect)
{
LED_ON;
}
ISR(TIMER1_COMPB_vect)
{
LED_OFF;
}
ISR(TIMER0_COMPA_vect)
{
uint16_t period = OCR1A;
uint16_t duty = OCR1B;
if (duty < period)
{
duty++;
}
else
{
duty = 0;
}
OCR1B = duty;
}
void Timer_Frequency(uint8_t freq)
{
TCCR1B |= (1 << CS12) | (1 << WGM12); //Set clock source & set mode to CTC
TIMSK1 |= (1 << OCIE1A); //Enable the CTC interrupt
OCR1A = (F_CPU/(freq*2*256)-1);
}
void PWM_Init(void)
{
TCCR1B |= (1 << CS10) | (1 << WGM12); //No Prescaler & set mode to CTC
TIMSK1 |= (1 << OCIE1A) | (1 << OCIE1B); //Enable the CTC interrupt
OCR1A = 800;
OCR1B = 400;
}
void milliS_timer(uint8_t milliS)
{
TCCR0A |= (1 << WGM01); //set to CTC mode
TCCR0B |= (1 << CS02) | (1 << CS00); //set prescaler to 1024
OCR0A = milliS * 7.8125 - 1;
TIMSK0 |= (1 << OCIE0A); //enable the interrupt
}
int main(void) {
DDRB |= (1 << PB5); // set PB5 as output pin
DDRB &= ~(1<<DDB7); //set PB7 as an input pin
milliS_timer(2);
PWM_Init();
sei(); //Enable global interrupts
while (1) {
}
}
Select the Make and Program Target Main Project Button at the top of the MPLAB X GUI.
You should see the LED increasing in brightness and resetting.