Solved: ATtiny 412 & Setting up TCB Timer

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

Hello,

 

I'm going to apologize off the bat. I'm fairly new to AVR & C programming (started about 3-4 months ago). I'm trying to migrate some of the techniques I've learnt over the years from the Arduino language and apply it to AVR programming. More specifically, the ATtiny 412. 

 

I now want to use a timing function so I can gather sample data points over a period of time. More specifically, I'd like to use TCB to do so since I'm already using TCA and TCD. I'm having a hard time getting it setup. I'm not even sure if I'm close. Reading some of the other posts, I think the best way is to utilize ISR(TCB0_INT_vect)? 

 

I provided the code I'm using below. Hopefully it makes sense. I'm trying to gather analog data coming over period of time into PA7 so I can get smooth data vs. spikes up and down (Peak-to-Peak data).

 

If you have need any additional information, feel free to let me know.

 

Thanks,

Ankit

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/atomic.h>
#include <inttypes.h>
#include <avr/interrupt.h>
#include <time.h>
#include <util/delay.h>

unsigned int peakToPeak;
unsigned int signalMax;
unsigned int signalMin;

const int sampleWindow = 50; // Sample window width in mS (50 mS = 20Hz)
unsigned int sample;

uint8_t _millis = 0;
uint8_t _1ms = 0;
uint8_t old_millis = 0;

ISR(TCB0_INT_vect)
{
	_1ms += 10;     // 10 ----> 160
		while (_1ms > 1000)
		{
			_millis++;
			_1ms -=1000;
		}

}

uint8_t millis()
{
	uint8_t m;
	cli();
	m = _millis;
	sei();
	return m;
}

void initADC()
{

    ADC0.CTRLC = ADC_REFSEL_INTREF_gc | ADC_PRESC_DIV8_gc;
    ADC0.MUXPOS = ADC_MUXPOS_AIN7_gc;
    ADC0.CTRLA = ADC_RESSEL_8BIT_gc | ADC_ENABLE_bm;

}
FUSES = {
    .WDTCFG     = FUSE_WDTCFG_DEFAULT,
    .BODCFG     = FUSE_BODCFG_DEFAULT,
    .OSCCFG     = FREQSEL_16MHZ_gc,     //Select 16MHz OSC
    .TCD0CFG    = FUSE_TCD0CFG_DEFAULT,
    .SYSCFG0    = FUSE_SYSCFG0_DEFAULT,
    .SYSCFG1    = FUSE_SYSCFG1_DEFAULT,
    .APPEND     = FUSE_APPEND_DEFAULT,
    .BOOTEND    = FUSE_BOOTEND_DEFAULT,
};

int main(void)
{
     	/* Set pins as i/o */
    PORTA.OUTCLR = PORTA.DIRCLR = PIN1_bm; // input.
	PORTA.OUTCLR = PORTA.DIRSET = PIN2_bm; // output.
//  PORTA.OUTCLR = PORTA.DIRCLR = PIN3_bm; // input. not used
	PORTA.OUTCLR = PORTA.DIRSET = PIN6_bm; // output.
	PORTA.OUTCLR = PORTA.DIRCLR = PIN7_bm; // input.

while(1)
 {	 unsigned int peakToPeak = 0;   // peak-to-peak level
     unsigned long startMillis= millis();
	 unsigned int signalMax = 0;
	 unsigned int signalMin = 600;

     while (millis() - startMillis < sampleWindow)
       {
		ADC0.MUXPOS|= 0x07;                   // ADC input in pin 7.
	    ADC0.COMMAND |= 1; // start running ADC
        if (ADC0.INTFLAGS)                    // if an ADC result is ready
	    {sample = ADC0.RES;}//input reading

        if (sample < 600)  // toss out spurious readings
		 {
		  if (sample > signalMax)
		  { signalMax = sample;}  // save just the max levels

		   else if (sample < signalMin)
		   { signalMin = sample;}// save just the min levels

         }}

peakToPeak = signalMax - signalMin;  // max - min = peak-peak amplitude
}}

 

This topic has a solution.
Last Edited: Tue. Dec 10, 2019 - 02:34 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So, where is the initialization code for the timer?

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

hmmm, that would probably help...It has been a steep learning curve. I wasn't entirely sure how to set the initialization for the timer, but here's what I got based on other forums/discussions (new code that I added in): 

void initTimerB()
{
    TCB0.CCMP = 8200;
    TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc | TCA_SINGLE_ENABLE_bp;
    TCB0.INTCTRL = 1 << TCB_CAPT_bp;

}

int main(void)
{ initTimerB;}

 

 

Thanks,

Ankit 

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

Ankit.Patel wrote:

TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc | TCA_SINGLE_ENABLE_bp;

 

Here you would need to use TCA_SINGLE_ENABLE_bm (bit mask) instead of _bp (bit position). At this point I think I should direct you to the AVR 0/1 tutorial thread:

https://www.avrfreaks.net/forum/...

 

Read in particular TB3214 - Getting Started with TCB and AVR1000: Getting Started with Writing C-Code for XMEGA.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I interpreted the purpose of making a 1ms tick by TCB.
I would write initialization and interrupts like this:

 

volatile uint8_t _millis;           // It must be volatile to share with interrupt handling.

void initTimerB(){
    TCB0.CCMP = 15999;              // Premise of 16MHz operation
    TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm;
    TCB0.INTCTRL = TCB_CAPT_bm;     // Understand the difference between _bp and _bm.
}

ISR(TCB0_INT_vect){                 // 1ms interval interrupt
    _millis++;
    TCB0.INTFLAGS = TCB_CAPT_bm;    // Clear interrupt flag
}

I hope I can help you understand.

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

Here is a simple led blinker on a tiny416 nano board using tcb periodic mode and interrupt-

(FYI- if you do not clear the flag in the interrupt, you will be stuck there)

 

#include <xc.h>
#include <stdint.h>
#include <avr/interrupt.h>

volatile uint32_t systick_ms;

ISR(TCB0_INT_vect){
    systick_ms++;
    TCB0.INTFLAGS = TCB_CAPT_bm;
}

uint32_t systime(){
    uint32_t t1;
    volatile uint8_t* t2 = (volatile uint8_t*)&systick_ms;
    for(;;){
        t1 = systick_ms; //get time
        if( (uint8_t)t1 == *t2 ) break; //if byte0 same, good
    }
    return t1;
}

int main(){

    VPORTB.OUT |= PIN5_bm;  //high is off
    VPORTB.DIR |= PIN5_bm;  //led on PB5

    CCP = CCP_IOREG_gc;     //unlock
    CLKCTRL.MCLKCTRLB = 0;  //no prescale

    //tcb periodic, div1, irq on ccmp, on
    //using 20Mhz in this case
    //TCB0.CCMP = 16000-1;        //16000/16000000 = 1ms
    TCB0.CCMP = 20000-1;        //20000/20000000 = 1ms
    TCB0.INTCTRL = TCB_CAPT_bm; //irq enable
    TCB0.CTRLA = TCB_ENABLE_bm; //enable, div1
    sei();

    for(;;){
        uint32_t start_time = systime();
        uint32_t delay = 500; //ms
        while( systime() - start_time < delay );
        VPORTB.IN = PIN5_bm; //toggle pin (via IN when using vport)
    }
}

 

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

As "_millis" is shared between the ISR and foreground code it MUST be volatile.

 

Also do not use symbols that start with either "_" or "__". The C library reserves names starting with "_" for it's own use (like _delay_ms()) and the C compiler reserves names that start with two "__" (like __builtin_avr_delay_cycles()). This is done to prevent name pollution so that all of the compiler, C library and your code could all (if they want) have something called __counter, _counter or counter.

 

If you do want to use some mark to say "internal variable" or "interrupt variable" or something (such as _millis and _1ms) then you could use it as a suffix so millis_, ms1_ etc - but clearly you could not have 1ms_ as that breaks the rules of C symbol naming (can't start with a digit).

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

Much appreciated guys. The info above is a great start. I'm going to go through all of this today and over the weekend. It's starting to make more and more sense as I go over it. 

 

I'll keep you updated once I get a good foothold.

 

Cheers,

Ankit

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

Got the timer to work as intended, big thanks again for all the help. I've been reading through the tutorial's link provided by El Tangas - these have been a life saver. All the info above helped tremendously.

 

 

Cheers,

Ankit