NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Help with delay_us using a 9.216mhz crystal

September 10, 2012
by akschu
akschu's Avatar

Hello Fellow Nerds,

I have a project that I want to run at 9.216 because I want it to be solid at the various serial baud rates, and it needs to run on 3.3v (which the atmel can't do at 14.7mhz)

The issue is that my code does some things with timing, but of course my delay_us doesn't work correctly anymore.

I've changed it to something like this (sorry don't have the code directly in front of me):

delay_us( us ){

int32 foo = us * .742

  for( i = 0; i < foo; i++){
    nop;
  }

}

The idea is that I can figure out how much time a nop takes then convert that into us and do some rounding to come reasonably close.

The issue is that I need to use a 32-bit int because when I use a 16 bit the conversion is greater than 1 and then I have the possibility of foo being greater than 16-bits. When I convert to 32-bit foo is less than 1 because it takes longer for the micro to convert everything.

The issue is that this isn't all that accurate because of the overhead.

So what can I do to get a more accurate delay? Can you Nerd Kit Dudes post something on timing and how to run at different speeds and get accurate delay_us?

thanks, schu

September 10, 2012
by pcbolt
pcbolt's Avatar

@ akschu

In the NK "delay.c" file you'll see that they set the number of NOP statements to 9 for 1 uSec using a 14 Mhz oscillator. For a 9 Mhz clock, you'd have to use 6 NOPs. You could just copy the NK file, alter it to 6 NOPs and save it as some other name ("delay9.c"). You'd have to do the same for "delay.h" but without any alterations.

September 10, 2012
by akschu
akschu's Avatar

That doesn't work for some reason. Here is what I have for delay.c:

inline void delay_us(uint16_t us) {
  //_delay_us(us);
  uint16_t i;
  for(i=0; i<us; i++) {
    NOP;
    NOP;
    NOP;
  }
}

And using the blinking led code:

  // loop keeps looking forever
  while(1) {
    // turn on LED
    PORTC |= (1<<PC4);

    //delay for 500 milliseconds to let the light stay on
    delay_ms( 500 );

    // turn off LED
    PORTC &= ~(1<<PC4);

    //delay for 500 milliseconds to let the light stay off
    delay_ms( 500 );

    // 
  }

That should give me 500ms on and 500ms off, but it doesn't. My saleae logic analyzer is showing me that it's on then off for 542.7ms which is almost 10% off. And this is with 3 NOPs.

Any other ideas?

schu

September 11, 2012
by Noter
Noter's Avatar

Maybe one of the timers will work for you, interrupt or pwm. Here's an interrupt example. For 1ms pulse I would use a smaller scaler to improve accuracy.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <stdbool.h>

#include "../libnerdkits/lcd.h"

FILE lcd_stream;

// pin I/O macros
//
#define INPUT2(port,pin) DDR ## port &= ~_BV(pin) 
#define OUTPUT2(port,pin) DDR ## port |= _BV(pin) 
#define CLEAR2(port,pin) PORT ## port &= ~_BV(pin) 
#define SET2(port,pin) PORT ## port |= _BV(pin) 
#define TOGGLE2(port,pin) PORT ## port ^= _BV(pin) 
#define READ2(port,pin) ((PIN ## port & _BV(pin))?1:0)
#define STATE2(port,pin) ((DDR ## port & _BV(pin))?1:0)
//
#define INPUT(x) INPUT2(x) 
#define OUTPUT(x) OUTPUT2(x)
#define CLEAR(x) CLEAR2(x)
#define SET(x) SET2(x)
#define TOGGLE(x) TOGGLE2(x)
#define READ(x) READ2(x)
#define STATE(x) STATE2(x)
#define PULLUP_ON(x) INPUT2(x); SET2(x)
#define PULLUP_OFF(x) INPUT2(x); CLEAR2(x)

// define pin for output
#define PIN_OUT B,1

void timer1_init() {
    // crystal is 18.432 mhz
    // prescaler is 1024
    // giving one tick = 1024/18432000 = 55.5 us.
    // thus 1.0s = 18000 ticks, 0.5s = 9000 ticks, 1ms = 18 ticks
    OCR1A = 9000;   // top, overflow (this is a 16 bit integer)
    TCCR1A = (1<<WGM10) | (1<<WGM11);
    TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS12) | (1<<CS10);
    // enable interrupt on overflow 
    TIMSK1 |= (1<<TOIE1);
}

ISR(TIMER1_OVF_vect, ISR_BLOCK) {
    // when Timer0 interrupts,
    TOGGLE(PIN_OUT);
}

int main() {

    // initialize LCD and identify
    fdev_setup_stream(&lcd_stream, lcd_putchar, 0, _FDEV_SETUP_WRITE); 
    stdin = stdout = &lcd_stream;
    lcd_init();
    lcd_clear_and_home();
    printf_P(PSTR("Demo_Timer1 -"));
    lcd_line_two();
    printf_P(PSTR("Output on PB1."));

    // set pin for output
    OUTPUT(PIN_OUT);

    // enable interrupts
    sei();

    // start the timer
    timer1_init();

    while (true)
    {
    }
}
September 11, 2012
by bretm
bretm's Avatar

If you're on Windows, Nerdkits is based on WinAVR. With that library you can use _delay_us instead of Nerdkit's delay_us. Other host platforms probably support the same or similar. The _delay_us function is more accurate than the Nerdkit's version.

The reason Nerdkits have delay_us is that it supports variable delays. Since you just want a constant delay, _delay_us will work just fine. Just be sure to #define FPU with the correct value. _delay_us also supports floating-point values, and the math is done at compile-time so it doesn't pull in floating-point support libraries which would take a lot of program space.

See this library article for more information.

September 11, 2012
by akschu
akschu's Avatar

Actually, I do need it to be variable, it's just not variable right now because I haven't implemented the part of the code that will change the pwm output.

I don't need it to be dead accurate, but I do want it within 2-3% which I should be able to do.

schu

September 11, 2012
by pcbolt
pcbolt's Avatar

schu -

With 9.216 Mhz, the execution time per machine code step should be 108.5 ns. By your code above that would mean the machine code to jump to the "delay" function, set up the stack, start the "for" loop and execute 3 NOPs must be 10 machine code operations. That would give you a clock of 1.085 uSec for each uSec you want. That 8.5% error is exactly what you are seeing with 500 mSec vs. 542.7 mSec. Using 2 NOP's should be closer 0.9766 uSec per uSec or 2.34%.

If you test this, please let me know how it went.

September 11, 2012
by akschu
akschu's Avatar

I get 488.4ms with two nops. I need to read a lot more about machine operations. Do you have any books or links for me to read through. I really need to understand this stuff so that my project can be reliable.

schu

September 11, 2012
by pcbolt
pcbolt's Avatar

schu -

Looks like you are within the 3% margin. That's pretty good. You can always modify your time variable to take into account the slight difference. As far as books/links go, I would actually start with the datasheet for the ATmega 168 in the section on the instruction set. Then write some simple code in C, compile it and check out the *.ass file output. To do this type...

make filename.ass

It will show you the machine code instructions used to generate a .hex file (before final linking I think). Here "filename" should be replaced by the name of the program you are interested in. The problem with books and website in regards to machine code is that it's hard to generalize machine-specific instruction sets and what works for some does not work for others. The ATmega is a whole different animal than what you might encounter.

September 11, 2012
by Rick_S
Rick_S's Avatar

Noter!! Are you back??? !! BigGrin_Green BigGrin_Green Cheers

September 11, 2012
by Noter
Noter's Avatar

Hi Rick! Yep, looks like I'll be around a while longer!

September 11, 2012
by Ralphxyz
Ralphxyz's Avatar

Wow Paul, great to see you.

Ralph

September 11, 2012
by sask55
sask55's Avatar

I agree! It is great to see a post from you Noter. You where such a big help to me with my fridge control project. You are one of major contributors to this forum. I hope to see many more of your informative and interesting posts from you in the future.

Darryl

Post a Reply

Please log in to post a reply.

Did you know that LEDs (light emitting diodes) only conduct current in one direction, like normal diodes? Learn more...