AVR 0-Series TCB0

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

I am currently building a motor controller using an attiny 404 as the microelectronic that will take in RC PWM (50hz with a high pulse of between 1000 to 2000us) and send appropriate signals to my motor driver. 

I am using TCB0 to try and measure the input pulse width. I think I am pretty close with my code but am having trouble getting my interrupt to trigger. Please help!

 

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define F_CPU 20000000UL

int temp = 0;
char IN1, IN2 = 0;
int PWMinput = 0;

void clock_init(void){
    _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, 0x00);
}

void IOinit(void){

    PORTA.DIRCLR = PIN4_bm | PIN5_bm;        //Set PA4 and PA5 as inputs
    PORTB.DIRSET = PIN0_bm | PIN1_bm;        //Set PB0 and PB1 as outputs

    PORTA.PIN4CTRL = PORT_PULLUPEN_bm;        //Enable Pullup Resistor for PA4
    PORTA.PIN5CTRL = PORT_PULLUPEN_bm;        //Enable Pullup Resistor for PA5
    
    //PORTA.PIN4CTRL|= PORT_ISC_BOTHEDGES_gc;
    PORTA.PIN5CTRL |= PORT_ISC_BOTHEDGES_gc;

    
}

ISR(TCB0_INT_vect){
    
    PORTB.OUTSET = PIN1_bm;                                           //This should indicate the interrupt is triggering.
    PWMinput = (TCB0_CCMPH << 8) | TCB0_CCMPL;
    
    
}

void EVENT_SYSTEM_init(void){
    
    EVSYS.ASYNCUSER0 = EVSYS_ASYNCUSER0_ASYNCCH0_gc;    
    EVSYS.ASYNCCH0 = EVSYS_ASYNCCH0_PORTA_PIN5_gc;
 
    
}
void TCB_init(void){

    TCB0.CTRLB = TCB_CNTMODE_PW_gc | TCB_ASYNC_bp;                //set timer to pulse width measurement mode      // Enable Async Mode
    TCB0.EVCTRL = TCB_CAPTEI_bm;                                //Enable the timer capture interrupt
    TCB0.INTCTRL = TCB_CAPT_bm;                                    //Enable the Capture Interrupt
    TCB0.CTRLA |= TCB_CLKSEL_CLKDIV2_gc;            //Select Pre scaler
    TCB0.CTRLA |= TCB_ENABLE_bm;                    //Start Timer
    
}

int main(void)
{
    
    clock_init();            //Set Clock to 20MHz
    IOinit();                //set all IO
    EVENT_SYSTEM_init();    //Set Event controller
    TCB_init();                //Init timer counter B

    sei();            // enable all interrupts
    
    
    
    while (1){
        
        
    }
}

 

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

I don't know why it doesn't trigger, but you do have an error in the ISR. The interrupt flags do not clear automatically on these chips, you need to clear them before exiting the ISR or the interrupt will trigger again immediately.

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

I know in this mode that the flag will be cleared when you read the High byte of the CCMP register. I have also tested this with a line clearing it and it did not help. I appreciate the suggestion!

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

Ah ok, you are right, sorry.

 

Well, I can try to test your code, I don't have a tiny404 but can use some similar chip.

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

The event system for the pins does not use the pin irq, but the pin is directly routed to the user/peripheral.

 

Simple example for a tiny416-

#include <stdint.h>
#include "Clkctrl.hpp"
#include "Evsys.hpp"
#include "Tcb.hpp"
#include "Cpuint.hpp"

uint16_t capture;

Tcb<Tcb0_Default> tcb0;

int main()
{
    //see clock vs voltage in datasheet
    //10MHz in safe area down to 2.7v
    Clkctrl::clksel( 10000000 );

    //pa2 input (default)
    Port<Pins::PA2> pa2;

    //usart0 alt rx pin PA2 used for capture (capture serial)
    Async async;
    async.gen_ch0( Async::GENCH0::PA2 );
    async.user( Async::USER::TCB0, Async::USERCHSEL::ASYNCCH0 );

    tcb0.mode( tcb0.IN_PW );    //pw measure
    tcb0.capt_irq( true );      //capture irq on
    tcb0.event_on( true );      //enable event
    tcb0.on( true );            //tcb0 on
                                //edge default, clock default

    Cpuint::set_func( Cpuint::TCB0,
        [](){
            capture = tcb0.ccmp(); //read clears capt flag in capture mode
            asm("nop"); //breakpoint here
            //tcb0.capt_irq( false );
        }
    );

    Cpuint::on( true );

    for(;;){}

}

This is just capturing the rx serial signal since I already had that hooked up.

The pa2 is init via constructor because I disable all pins early in startup code (before any constructors), so it is back to normal input.

Tcb0 is setup to use event in pwm capture mode, capture irq enabled (and edge was left as-is, I didn't care which edge was the start).

An isr function is used to read the capture value, with a breakpoint added to see the values.

 

I didn't hang around trying to figure out if the values were valid, but they were changing with serial data incoming.

 

 

Last Edited: Wed. Aug 21, 2019 - 07:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ahh using the PORT IRQ was the issue, the interrupt now is triggering and i am able to manipulate some leds to show that that is working. However now when I try to read the CCMPH and CCMPL registers they seem to either not be reading into my variable or are empty.

 

ISR(TCB0_INT_vect){

    TEMP1 = TCB0_CCMPL;                    //grab the low byte that was captured
    TEMP2 = TCB0_CCMPH;                    //grab the high byte that was captured
}

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

My issue was my variable type. i was using char and in, and should have used a uint16_t, rookie  mistake on my part.  Finished working code that will turn on an led above 1500us is a sfollows

 

#define F_CPU 20000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

char IN1, IN2 = 0;
volatile uint16_t PWMin = 0;
volatile uint16_t PWMus = 0;

void clock_init(void){
    _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, 0x00);
}

void IOinit(void){

    PORTA.DIRCLR = PIN4_bm | PIN5_bm;        //Set PA4 and PA5 as inputs
    PORTB.DIRSET = PIN0_bm | PIN1_bm;        //Set PB0 and PB1 as outputs

    PORTA.PIN4CTRL = PORT_PULLUPEN_bm;        //Enable Pullup Resistor for PA4
    PORTA.PIN5CTRL = PORT_PULLUPEN_bm;        //Enable Pullup Resistor for PA5
    
}

ISR(TCB0_INT_vect){

    PWMin = (TCB0.CCMPH << 8 ) | TCB0.CCMPL;
}

void EVENT_SYSTEM_init(void){

    EVSYS.ASYNCUSER0 = EVSYS_ASYNCUSER0_ASYNCCH0_gc;    
    EVSYS.ASYNCCH0 = EVSYS_ASYNCCH0_PORTA_PIN5_gc;
    
}

void TCB_init(void){

    TCB0_CCMP = 0x05;
    TCB0.CCMPL = 0x05;
    TCB0.CTRLB = TCB_CNTMODE_PW_gc;                                //set timer to pulse width measurement mode    
    TCB0.EVCTRL = TCB_CAPTEI_bm ;                                //Enable the timer capture interrupt    
    TCB0.INTCTRL = TCB_CAPT_bm;                                    //Enable the Capture Interrupt
    TCB0.CTRLA = TCB_ENABLE_bm;                                    //Select Pre scaler //Start Timmer

}

int main(void)
{
    
    clock_init();            //Set Clock to 20MHz
    IOinit();                //set all IO
    EVENT_SYSTEM_init();    //Set Event controller
    TCB_init();                //Init timer counter B

    sei();            // enable all interrupts
    

    
    while (1){

        PWMus = PWMin / 20;
        
        if(PWMus > 1500){
            
            PORTB.OUTSET = PIN1_bm;
            
        }else{
            
            PORTB.OUTCLR = PIN1_bm;    
            
        }    
    }
}

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

Try to make the global variable volatile, because the ISR can change it outside normal program flow.

 

edit: oh, right, you just did while I was typing...

Last Edited: Wed. Aug 21, 2019 - 09:41 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>PWMin = (TCB0.CCMPH << 8 ) | TCB0.CCMPL;

 

PWMin = TCB0.CCMP;

 

Let the compiler deal with getting 16bit values in the correct order, even when order may not matter. When it does matter, you will already have it handled because you were using the 16bit names, plus its less code and easier to read.

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

Good advice, Thanks!

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

Would it be possible to measure two different pulse width signals (provided they don't overlap) on two separate pins by toggling the event system back and forth between the two pins? How quickly can the event system be changed. is there any configuring that is required by the timer to accept the change of the event system?

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

>Would it be possible to measure two different pulse width signals (provided they don't overlap) on two separate pins by toggling the event system back and forth between the two pins?

 

No reason it would not be possible.

 

>How quickly can the event system be changed. is there any configuring that is required by the timer to accept the change of the event system?

 

Simply change the event channel source if the other pin is in the same channel group, or setup the channel which has the other pin and switch the tcb user to use the new channel. In either case the tcb timer doesn't care, it just sees the events of whatever is connected. The time it takes to switch would be the time it takes to write to a register, in other words its like most everything else- one clock your event is one pin, the next clock its another.

 

> (provided they don't overlap)

 

You could check/wait for the new pin to be in the 'off' state (whatever that may be) before switching, or just discard the first reading after a switch.