ATmega32 Timer not accurate for a sine wave with 10 bit DAC

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

hi,

I try to get a 1KHz sine wave with an ATmega32 and a 10 bit R-2R DAC. 

I put 100 sine value in a table and I use a timer so that each time the timer counter overflow, the duration is equal to the duration of one of the 100 steps from 0 to 2pi.
So an interrupt is executed to change de value of the sine to the next value of the 100 value of the sine table. After 100 times, the period is completed and it starts over from the beginning of the table.

I get a good sine but the problem is that I don't get 1KHz with a timer who should give me 1KHz. I get something like 420Hz.

My caalculation is :

 

TCCR0 |= (1 << CS01); //Prescaler = 8
TCNT0 = 256 - 20; // (1/16MHZ ) * 8 * 20 = (1/1000) / 100
//  1/1000 is the period for 1Khz, but the timer has to overflow every period/100 because we calculte 100 sine value over a period

My question is why it is not accurate ?

 

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <math.h>

/*Global variables declaration*/

	int i = 0;
	float x = 0;
	double const pi = 3.14159265359;
	float sine[100]; //Sine value
	int sineCast[100]; //Sine value converted in integer

int main(void)
{
	/*Direction and PORT initialization */

	DDRD = 0xFF; //PortD as output, 8 LSBits of the 10 bit DAC
	PORTD = 0x00; //PortD = LOW

	DDRB = 0xFF;//PortB as output, 2 MSbits of the 10 bit DAC
	PORTB = 0x00; //PortB = LOW

	/*100 sine value (float) put in a table*/

	for(i = 0; i < 100; i++)
	{
		sine[i] = 512 + (512 * sin(x));
		x += 0.0628; // 2pi / 100 = 0.0628
	}

	/*The sine values converted to int*/

	for(i = 0; i < 100; i++)
	{
		sineCast[i] = (int)sine[i];
	}

	i = 0;

	TCCR0 |= (1 << CS01); //Prescaler = 8
	TCNT0 = 256 - 20; // (1/16MHZ ) * 8 * 20 = (1/1000) / 100
	TIMSK |= (1 << TOIE0);
	sei();

	while (1)
	{

	}
}

 	ISR (TIMER0_OVF_vect)
{
	PORTD = sineCast[i];
	PORTB = sineCast[i] >> 8; //PB1 and PB0 (MSBits) active

	i++;
	if(i > 99) i = 0;

	TCNT0 = 256 - 20;
	TIFR = (1 << TOV0);
} 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1
    TCNT0 = 256-20;

TCNT0 has advanced some time after overflow at the time of executing this instruction. Therefore, the interrupt interval will be greater than 160 CPU cycles.
In my simulation the interrupt cycle was 216 CPU cycles. This gives 740Hz
Perhaps your CPU clock will be internal 8MHz.
Although it should be 370Hz in the calculation, since the internal clock is inaccurate, the result of 420Hz is sufficiently conceivable.

 

Please use the crystal resonator, set the correct fuse and operate at exact 16MHz.
Then use the CTC mode of the timer to set the interrupt interval to exact 160 CPU cycles.

 

It is a modified source.

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <math.h>

/*Global variables declaration*/

int i = 0;
float x = 0;
double const pi = 3.14159265359;
float sine[100]; //Sine value
int sineCast[100]; //Sine value converted in integer

int main(void)
{
	/*Direction and PORT initialization */

	DDRD = 0xFF; //PortD as output, 8 LSBits of the 10 bit DAC
	PORTD = 0x00; //PortD = LOW

	DDRB = 0xFF;//PortB as output, 2 MSbits of the 10 bit DAC
	PORTB = 0x00; //PortB = LOW

	/*100 sine value (float) put in a table*/

	for(i = 0; i < 100; i++)
	{
		sine[i] = 512 + (512 * sin(x));
		x += 0.0628; // 2pi / 100 = 0.0628
	}

	/*The sine values converted to int*/

	for(i = 0; i < 100; i++)
	{
		sineCast[i] = (int)sine[i];
	}

	i = 0;

	OCR0 = 159;
	TCCR0 = (1 << WGM01) | (1 << CS00); // CTC MODE, Prescaler = 1
	TIMSK |= (1 << OCIE0);
	sei();

	while (1){}
}

ISR (TIMER0_COMP_vect)
{
	PORTD = sineCast[i];
	PORTB = sineCast[i] >> 8; //PB1 and PB0 (MSBits) active

	i++;
	if(i > 99) i = 0;
}

 

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

Why would you use the lowly AVR to calculate the sine values then waste RAM to hold them? Get something exceedingly accurate like Excel to calculate the values then put them in a table in flash.

 

(BTW you only need 1/4 of the circle - the rest can be derived from that)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
ISR (TIMER0_COMP_vect)
{
    // do as fast an update as possible using
    // values already computed

    PORTD = pre_computed_lo_byte;
    PORTB = pre_computed_hi_byte;

    // determine the output values for the next
    // timer match here

    pre_computed_lo_byte = .......;
    pre_computed_hi_byte = .......;
}

 

--Mike

 

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

1.  Use CTC for the Timer.   The hardware will produce perfect timing.

2.  Precompute 90 degrees worth of sine table and store in Flash.

3.  Update the DAC in the COMPA interrupt.

4.  A crystal will give better accuracy but the regular 1MHz RC will be good enough.

 

The whole program will be about 20 lines of code + about 16 lines of sine table.

 

David.

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

david.prentice wrote:
4. A crystal will give better accuracy but the regular 1MHz RC will be good enough.

1MHz enough?  Even if OP wants to update every 10us, as in the posted code?  I think not.

 

clawson wrote:
calculate the sine values then waste RAM to hold them?

C'mon.  OP can use a venerable Mega32 in any fashion desired.  A small app; an extra few hundred bytes of SRAM usage -- so what?  SRAM vs. flash?  Yeah, in a real app most/all of us would pre-compute and use flash.  But let me riddle you this:  Which is faster in operation, accessing the flash table or accessing an SRAM table, even if just a copy of the flash table?  Generating the table?  Again, so what?  It is straightforward for the OP apparently.  A few hundred words of flash?  So what.  A couple milliseconds at startup?  So what.

 

That said--

-- yes, for an efficient repetitive fast timer tick, use CTC.

-- if one >>really<< wants to get much faster, then some register variables and pointers can shave down the ISR.  Then you can use some ASM; then you can skip the interrupt servicing and check timer flag; then you can cycle count.

-- which brings us to Jesper miniDDS; could be adapted to 10 bit.

-- Or use a chip with DMA such as Xmega

-- Or use a real DAC

-- Or use a DDS chip

 

 

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

Point taken. I had not done the maths. 100 values is only 200 bytes of SRAM. 100 samples per 1ms cycle only allows 10 instructions with a 1Mhz clock. 8Mhz should be ok with Flash or SRAM.
.
You have given me a challenge. First, how many lines. Second what is min speed of AVR. Yes, computing table at runtime is not hard.
.
David.

Last Edited: Fri. Oct 19, 2018 - 06:41 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
You have given me a challenge. First, how many lines. Second what is min speed of AVR.

Didn't Jesper do all that with miniDDS?

 

Again, most of us would do it a bit differently if only part of a full app.  So, for your contest, it is the whole AVR?

 

I don't get why "lines" is important.  If one wants to get best speed then one unrolls the loop, has mod 256 table entries, dedicates pointer registers, and uses all of flash.  Avoiding glitch at rollover is left as an exercise for the reader.  Tell why 1MHz is important, anyway?  OP has 16MHz.  Why not 128kHz?  Why not 32kHz?

 

If one wants to use the same approach as OP then I think it is a couple cycles faster to use one index register and one table array versus two  Table on mod 256 boundary; table has 128 2-byte entries.  Set Z to table base.

 

LPM Rx, Z+  ; LD saves a cycle

OUT PORTa, Rx

LPM Rx, Z+  ; LD saves a cycle

OUT PORTb, Rx

LDI ZH, page of table which takes care of wrap

RJMP

 

11 cycles.  Table in SRAM 9 cycles.  miniDDS gives many more frequency options.

 

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Fri. Oct 19, 2018 - 07:08 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok.

/*
* DAC_sinetable.c
*
* Created: 19/10/2018 19:44:55
* Author : David
*/

#define F_CPU 8000000
#include <avr/io.h>
#include <avr/interrupt.h>
#include <math.h>

uint16_t sinetable[100];

ISR(TIMER1_COMPA_vect)
{
    static uint8_t deg;
    if (deg >= 100) deg = 0;
    uint16_t val = sinetable[deg++];
    PORTC = val >> 8;
    PORTB = val;
}

int main(void)
{
    for (int i = 0; i < 100; i++) sinetable[i] = sin(2 * 3.141592654 / 100 * i) * 512 + 512;
    OCR1A = (F_CPU / 1000) / 100 - 1;
    TCCR1B= (1<<WGM12) | (1<<CS10);  //CTC mode div1
    TIMSK= (1<<OCIE1A);
    DDRB = 0xFF;   //10 port bits
    DDRC = 0x03;
    sei();
    while (1)
    {
    }
}

The Simulator says that the ISR() takes about 60 machine cycles.  

So yes,  you need to use F_CPU > 6MHz.   And my prog is 36 lines.

If you use the 8MHz RC you need to load OSCCAL with te 8MHz calibration.

 

I suspect that Codevision might do a quicker ISR().

 

David.

Last Edited: Fri. Oct 19, 2018 - 07:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
I suspect that Codevision might do a quicker ISR().

As I mentioned, even staying in more-or-less straight C, one would make the table a power of 2 to help with rollover.  And neither CV nor GCC (don't know about the others) will allow you to dedicate one or two register variables to pointer register pairs.

 

It depends on all of the criteria and goals.  At these cycle counts, a main loop with no ISR servicing will be faster.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

I presume that the OP wants a sinewave in the background.    The application probably does other things in the foreground.     With 60 cycles used by the ISR() in every 80 cycle period,   the AVR is only going to perform with "2MHz throughput"

 

Yes,   it is possible to tweak the ISR() to be more efficient.    I doubt if it really matters.    Using a 16MHz crystal would make a dramatic difference e.g. an effective 10MHz throughput.

 

Yes,   polling in the main loop uses less instructions than using interrupts.    But you end up without any "foreground application".

 

David.

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

david.prentice wrote:

The Simulator says that the ISR() takes about 60 machine cycles.  

So yes,  you need to use F_CPU > 6MHz.   And my prog is 36 lines.

If you use the 8MHz RC you need to load OSCCAL with te 8MHz calibration.

 

Nice example.

A useful variant of that would be to make the Freq and Table sizes and INT ticks user defined, to make the code more general.

Fine tune of interrupt ticks, and samples per cycle, gives two whole numbers that combine to give final frequency, and you can wiggle smaller step sizes out of this - can be useful for testing. 

eg with a 20MHz SysCLK, nominal step is 5Hz at 1kHz, but you can do (eg)  1/(98*(10u+200n)) = 1000.40016 to get a value more slightly offset from 1kHz

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

Shouldn't the index in the ISR ("i") be declared volatile?

 

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

If you are referring to #1 and #2,   I don't think that it matters.    The ISR() uses the global variable "i" but the foreground code does not read the value from ISR().

 

The ISR() will just treat "i" like my static "i".

 

David.

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

theusch wrote:

 

-- yes, for an efficient repetitive fast timer tick, use CTC.

 

 

Yes now it works fine with a CTC. But why the CTC is more efficient than a normal timer ?

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

You can use the regular Timer overflow if you want but you are limited to the overflow frequency e.g. 256 ticks on 8-bit Timer.
CTC mode means you can change the number of ticks e.g. 1 - 256 ticks for 8-bit or 1 - 65536 ticks for 16-bit Timer 1.
.
The CTC mode resets the timer in hardware. There is no need to alter the timer in software.
Your original strategy looks like an 8051 program. However even the 8051 can use a form of CTC mode.
.
There is a similar situation with PWM. You normally have PWM period at 256 or 1024 ticks but the AVR can be configured to use a different number of ticks. Most PWM apps only worry about duty cycle and not period.
.
David.

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

david.prentice wrote:

You can use the regular Timer overflow if you want but you are limited to the overflow frequency e.g. 256 ticks on 8-bit Timer.
CTC mode means you can change the number of ticks e.g. 1 - 256 ticks for 8-bit or 1 - 65536 ticks for 16-bit Timer 1.

 

But it is possible to use Timer1 (16 bit) in normal mode > 65536 ticks. No ? So we are not limited to 256 ticks with regular Timer overflow.

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

No,  a 16-bit timer can not count past 65535.   It rolls over to 0.   Of course the hardware overflow flag is set.   You can either poll the OVF  flag in software or with a hardware interrupt.

 

Your project for a 1kHz sinewave should work fine.    However you can see that 2kHz requires faster AVR ...

Instead of stepping through all the samples in one cycle,  you can sample at a fixed rate but with a different "step size" when accessing your table.    Google DDS.

 

There are several DDS projects.   They can generate different waveforms at a range of frequencies.

 

David.

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

jowell88 wrote:
Yes now it works fine with a CTC. But why the CTC is more efficient than a normal timer ?

You were told about that in the very first reply text, in the first paragraph of #2.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
ISR (TIMER0_OVF_vect)
{
	PORTD = sineCast[i];
	PORTB = sineCast[i] >> 8; //PB1 and PB0 (MSBits) active

	i++;
	if(i > 99) i = 0;

	TCNT0 = 256 - 20;
	TIFR = (1 << TOV0);
} 

This is problematic for two reasons.

 

First, since I don't see any latch signal being sent, I gather the DAC is just free-running.  In that case, you will get a glitch between the PORTD write and the PORTB write, since the DAC will be receiving an invalid value during that time.

 

Second, if you did have a DAC that required a latch signal, you could use the CTC output pulse to latch the DAC value (which you will have put on PORTD&B earlier), then put a new value on the DAC inputs in the ISR.  By having that DAC latch tied to the timer you will get the most accurate timing possible, with no issues from interrupt latency.

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

The OP just has a resistor ladder.   Yes,  there will be a glitch   when you go from 0x0FF to 0x100 because you will have 0x0FF, 0x000, 0x100.

If you write to the high bits first,  you get 0x0FF, 0x1FF, 0x100.

 

The compiler (or you) can do the array access once.   Then the PORT writes are likely to be just an OUT i.e. 125 nanoseconds @ 8MHz.    I doubt that you will notice 125ns in a 1000000ns period.    A low pass filter will remove most of it.

 

I don't see how you can avoid this behaviour in an 8-bit machine.   Live with it.

 

David.

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

david.prentice wrote:

The OP just has a resistor ladder....

 

They OP mentioned "a 10 bit R-2R DAC", which sounds optimistic/expensive - it could be

a) discrete resistors ? - in this case good luck hitting that claimed 10 bits without costly resistors and careful port-resistance consideration.

b) A parallel DAC using R-2R - yup, digikey actually lists 93, and those will meet 10 bit specs, but the prices are very high. ( over $3) 

 

 Search MCU with 12b DAC for example, and you find them from 89c ! (EFM8BB3)

 

 

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

I'm not sure why you think you need to write out DAC samples every 10µs. In the early days of compact disk most models (not Philips) only used to drive the DAC at 44.1kHz which is 22.67µs.