ATMEGA3208 (megaAVR) Timer PWM setup

Go To Last Post
8 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

 

I'm programming an ATMEGA3208 (megaAVR) for the first time. I'm trying to port a PWM/Timer code from an ATTINY84, it's outputting a 1khz PWM with modulation (LFO) on PINA6:

volatile uint16_t msturns = 0;
volatile uint16_t ledturns = 0;
volatile uint8_t inc = 0;
volatile uint16_t pwm;
volatile uint16_t speed;

void Timerinit(void)
{
  DDRA |= (1<<PINA6);	//PWM pin as output
  
  TCNT0 = 0;				//timer0, fast PWM, OCRA as TOP, enable compare A interrupt, 1024 prescaler
  OCR0A = 100;
  TCCR0A |= (1<<WGM00) | (1<<WGM01);
  TIMSK0 |= (1<<OCIE0A);
  TCCR0B |= (1<<WGM02) | (1<<CS02) | (1<<CS00);
  
  ICR1 = 999;	//timer1, fast PWM, ICR1 as TOP, enable overflow interrupt and compare A interrupt, no prescaler
  TCNT1 = 0;
  
  OCR1A = 300;
  TCCR1A |= (1<<COM1A1);
  TIMSK1 |= (1<<OCIE1A);
  
  TCCR1A |= (1<<WGM11);
  TIMSK1 |= (1<<TOIE1);
  TCCR1B |= (1<<WGM12) | (1<<WGM13) | (1<<CS11);
}

ISR(TIM1_OVF_vect)
{
	msturns++;	//ms increment if timer1 overflow
	ledturns++;
}

ISR(TIM1_COMPA_vect)
{
	cli();		//updates mod pwm duty cycle
	OCR1A = pwm;
	sei();
}

ISR(TIM0_COMPA_vect)
{
	inc++;		//increment position in wavetable
	cli();
	OCR0A = speed;	//update mod speed
	sei();
}

The timer configuration on the ATMEGA3208 is quite different, any tips on which direction I should go to implement it would be awesome!

 

Thank you!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


Refer to the correct section of the data sheet.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Kabasan, I made some progress, I managed to get something close to the original but I think there is something wrong with the ISR for the Compare 0, it doesn't seem to work:

 

void TCA0_init(void)
{
    /* set waveform output on PORT A */
    PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTA_gc;
    
    TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm; // Overflow Interrupt
    
    TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm            /* enable compare channel 0 */
                      | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;    /* set single slope PWM mode */
    
    /* disable event counting */
    TCA0.SINGLE.EVCTRL &= ~(TCA_SINGLE_CNTEI_bm); 
    
    TCA0.SINGLE.PER  = 999;  // Periodic value
    TCA0.SINGLE.CMP0 = 300;  // Duty cycle
    
    TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV16_gc        /* set clock source (sys_clk/16) */
                      | TCA_SINGLE_ENABLE_bm;           /* start timer */
}

void PORT_init(void)
{    
    /* set pin 0 of PORT A as output */    
    PORTA.DIR |= PIN0_bm;
}

ISR(TCA0_OVF_vect)
{
    msturns++;	//ms increment if TCA0 overflow
    ledturns++;
    TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm;
}

ISR(TCA0_CMP0_vect)
{
    cli();		//updates mod pwm duty cycle
    TCA0.SINGLE.CMP0 = pwm;
    sei();
}

Also I need to implement another very slow timer (8-bit) for the LFO modulation, can I use TCA0 in split mode (but it needs a much higher prescaler like 1024) or can we do it with TCB0?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You have only overflow interrupts enabled.

    TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm; // Overflow Interrupt

Do this if you also want to enable compare.

    TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm | TCA_SINGLE_CMP0_bm // Overflow  & Compare Interrupt

Don't forget to clear the interrupt flag in CMP0_vect.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

octavez wrote:
can I use TCA0 in split mode (but it needs a much higher prescaler like 1024) or can we do it with TCB0?

 

Neither of those will work. Split mode doesn't allow different prescalers for each half. TCB uses the same clock source as TCA but has a max 2x prescaler so it can't give a much lower frequency either.

Probably your only option to get an independent, lower frequency, is to use the RTC.

 

Another option is to generate the PWM from TCB but with 8 bit maximum resolution, and the low frequency from TCA or RTC.

 

edit: BTW, are you doing some kind of siren or alarm?

Last Edited: Mon. Apr 11, 2022 - 02:26 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Use a different timer for the slow time, say every 10ms or whatever.  Then every 5 irqs (50ms), turn on/off the fast timer (pwm), and set the output to 0 if turning timer off.  Or you could instead use the slow timer to set the fast timer so it does/doesn't control the pin.  

So the slow timer is controlling how long the fast timer is on, or whether it controls the pin (your choice).

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kabasan wrote:

You have only overflow interrupts enabled.

 

Thank you it works now!

 

El Tangas wrote:

Another option is to generate the PWM from TCB but with 8 bit maximum resolution, and the low frequency from TCA or RTC.

 

edit: BTW, are you doing some kind of siren or alarm?

 

This is to control an audio delay chip. The clock defines the delay time, and you can modulate that clock (PWM) with an LFO to affect the sound. As you can see in my first post I need a slow timer (8-bit is fine) to simulate an LFO from ~0.12Hz to ~10Hz with a maximum modulation depth of about 30ms. This is how the PMW is changed in the main function:

 

 pwm = (((100 + currentinc) * depthvalue)/255); // Depth value is from a potentiometer (8bit ADC) 

//update mod waveform value
currentinc = (int)((sin(((2 * PI) / 255) * inc)*200) + 200);  //Sine

You might be right having to use TCB (TCB seems very limited in prescaling) or RTC instead for the PWM and modulate it with TCA.

Last Edited: Mon. Apr 11, 2022 - 05:50 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Alright I think I got it to work with the RTC for LFO modulation! Let me know if you see something that needs to be improved:

 

void TCA0_init(void)
{
    /* set pin 0 of PORT A as output */    
    PORTA.DIR |= PIN0_bm;
    
    /* set waveform output on PORT A */
    PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTA_gc;
    
    TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm | TCA_SINGLE_CMP0_bm; // Overflow Interrupt
    
    TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm            /* enable compare channel 0 */
                      | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;    /* set single PWM mode */
    
    /* disable event counting */
    TCA0.SINGLE.EVCTRL &= ~(TCA_SINGLE_CNTEI_bm); 
    
    TCA0.SINGLE.PER  = 999;  // Periodic value
    TCA0.SINGLE.CMP0 = 300;  // Duty cycle
    
    TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV16_gc        /* set clock source (sys_clk/16) */
                      | TCA_SINGLE_ENABLE_bm;           /* start timer */
}

void RTC_init(void)
{
    
    while(RTC.STATUS > 0);  /* Wait for all registers to be synchronized */
	RTC.CTRLA = RTC_PRESCALER_DIV4_gc | RTC_RTCEN_bm; /* Enable RTC, 4 prescaler */
	RTC.CLKSEL = RTC_CLKSEL_INT32K_gc; /* Select 32.768 kHz internal RC oscillator */
	RTC.PER = 100;
	while(RTC.STATUS > 0);  /* Wait for all registers to be synchronized */
    RTC.INTCTRL |= RTC_OVF_bm;
}


ISR(TCA0_OVF_vect)
{
    msturns++;	//ms increment if TCA0 overflow
	ledturns++;
    TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm;
}

ISR(TCA0_CMP0_vect)
{
    cli();		//updates mod pwm duty cycle
	TCA0.SINGLE.CMP0 = pwm;
    TCA0.SINGLE.INTFLAGS = TCA_SINGLE_CMP0_bm;
	sei();
}

ISR(RTC_CNT_vect)
{
    inc++;		//increment position in wavetable
	cli();
	RTC.PER = speed;	//update mod speed
	sei();
    RTC.INTFLAGS = RTC_OVF_bm;
}

int main(void)
{
    CCP = CCP_IOREG_gc; // No clock division (full 16Mhz)
    CLKCTRL.MCLKCTRLB = 0; // No clock division (full 16Mhz)
    TCA0_init();
    RTC_init();
    sei();
    
    uint16_t offset;
    
    while (1)
    {
            
        //---------PWM OUTPUT

            
        if (abs(depthvalue) >= 13)
        {
             offset = (300 - ((depthvalue * 300)/255));  //updating mod pwm value
        }
        
        pwm = (((100 + currentinc) * depthvalue)/255) + offset;
        
        if (abs(speedvalue) >= 13)
        {
            speed = 255 - speedvalue; //updating mod speed
        }
        
        //update mod waveform value
        currentinc = (int)((sin(((2 * PI) / 255) * inc)*200) + 200);  //Sine
        
    }