Go Down

Topic: Difference in delay() and delayMicroseconds() for a Standalone ATmega2560 (Read 100 times) previous topic - next topic

winsmen

Hi. I am currently working on a project that involves the actuation of a stepper motor coupled to a Linear Motion Guide, and have a reached a problem to which I cannot figure out a solution. I will try and explain the problem as elaborately as possible so that you can get a clear picture of what I am stuck at.

I was planning on working the stepper motor using acceleration/deceleration profiles as I learned that it wouldn't be good to subject the motor to a sudden jump to high speeds, and because it looks more appealing in operation to see uniform transition in speeds. A formula was derived which would vary the delay between each step while actuating the stepper motor and thus obtain the required speed profile. The code to actuate the Stepper motor was such that it would first calculate the delay between the present and next step using the formula, actuate the present phase of the stepper motor, provide the calculated delay using the delayMicroseconds() function (to be more specific, the delay would range from 2500 microseconds at slow speed to 700 microseconds at the desired top speed), de-actuate the phase and then move on to actuating the next phase in the same manner (as would be the operation of any regular stepper motor code, to my knowledge).

The project started out with testing on an Arduino Mega ATmega2650 (running with a 16MHz crystal oscillator) and the results obtained after quite a bit of trial and error were in line with what I had expected. But due the requirements of the project, I had to move on to making my own custom Mega using a standalone ATmega2560 IC. I purchased one that was soldered onto an SMD to DIP breakout board, but this one had a 14.7456 MHz crystal soldered onto it. I figured that this wouldn't be much of an issue as the Arduino libraries are built with functions which handle the required calculations for delays. From my testing with the new standalone IC, it turns out I was probably not entirely right. The same program that worked with the Arduino Mega, didn't seem to work on the standalone.

I tried to work around this and figure out what the problem could be by tweaking the delay provided between steps from which I made two observations:

1. The Arduino Mega would give me the required functionality using either the delay() or the delayMicroseconds() function for delays between 3000 microseconds to 600 microseconds. The standalone would give me the required functionality using the delay() function for delays between 3 milliseconds and 1 millisecond.

2. On the standalone the stepper motor would work the right way with a delay(x) function call, but would not work with a delayMicroseconds(x*1000) function call. For eg.: delay(1) would work fine, but delayMicroseconds(1000) would not work fine. Technically both of them should mean the same, but I do not know why this difference prevailed.



Now that my problem has been described, my questions are:

1. What could be causing the difference in operation between the Mega and the standalone?? Does the different clock actually cause it??

2. Could there be a workaround to this problem in terms of the logic used?? Can I find a solution that does not involve changing the hardware, i.e., the oscillator??

DrAzzy

Check out the implementation of delayMicroseconds()

Code: [Select]
/* Delay for the given number of microseconds.  Assumes a 1, 8, 12, 16, 20 or 24 MHz clock. */
void delayMicroseconds(unsigned int us)
{
// call = 4 cycles + 2 to 4 cycles to init us(2 for constant delay, 4 for variable)

// calling avrlib's delay_us() function with low values (e.g. 1 or
// 2 microseconds) gives delays longer than desired.
//delay_us(us);
#if F_CPU >= 24000000L
// for the 24 MHz clock for the aventurous ones, trying to overclock

// zero delay fix
if (!us) return; //  = 3 cycles, (4 when true)

// the following loop takes a 1/6 of a microsecond (4 cycles)
// per iteration, so execute it six times for each microsecond of
// delay requested.
us *= 6; // x6 us, = 7 cycles

// account for the time taken in the preceeding commands.
// we just burned 22 (24) cycles above, remove 5, (5*4=20)
// us is at least 6 so we can substract 5
us -= 5; //=2 cycles

#elif F_CPU >= 20000000L
// for the 20 MHz clock on rare Arduino boards

// for a one-microsecond delay, simply return.  the overhead
// of the function call takes 18 (20) cycles, which is 1us
__asm__ __volatile__ (
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop"); //just waiting 4 cycles
if (us <= 1) return; //  = 3 cycles, (4 when true)

// the following loop takes a 1/5 of a microsecond (4 cycles)
// per iteration, so execute it five times for each microsecond of
// delay requested.
us = (us << 2) + us; // x5 us, = 7 cycles

// account for the time taken in the preceeding commands.
// we just burned 26 (28) cycles above, remove 7, (7*4=28)
// us is at least 10 so we can substract 7
us -= 7; // 2 cycles

#elif F_CPU >= 16000000L
// for the 16 MHz clock on most Arduino boards

// for a one-microsecond delay, simply return.  the overhead
// of the function call takes 14 (16) cycles, which is 1us
if (us <= 1) return; //  = 3 cycles, (4 when true)

// the following loop takes 1/4 of a microsecond (4 cycles)
// per iteration, so execute it four times for each microsecond of
// delay requested.
us <<= 2; // x4 us, = 4 cycles

// account for the time taken in the preceeding commands.
// we just burned 19 (21) cycles above, remove 5, (5*4=20)
// us is at least 8 so we can substract 5
us -= 5; // = 2 cycles,

#elif F_CPU >= 12000000L
// for the 12 MHz clock if somebody is working with USB

// for a 1 microsecond delay, simply return.  the overhead
// of the function call takes 14 (16) cycles, which is 1.5us
if (us <= 1) return; //  = 3 cycles, (4 when true)

// the following loop takes 1/3 of a microsecond (4 cycles)
// per iteration, so execute it three times for each microsecond of
// delay requested.
us = (us << 1) + us; // x3 us, = 5 cycles

// account for the time taken in the preceeding commands.
// we just burned 20 (22) cycles above, remove 5, (5*4=20)
// us is at least 6 so we can substract 5
us -= 5; //2 cycles

#elif F_CPU >= 8000000L
// for the 8 MHz internal clock

// for a 1 and 2 microsecond delay, simply return.  the overhead
// of the function call takes 14 (16) cycles, which is 2us
if (us <= 2) return; //  = 3 cycles, (4 when true)

// the following loop takes 1/2 of a microsecond (4 cycles)
// per iteration, so execute it twice for each microsecond of
// delay requested.
us <<= 1; //x2 us, = 2 cycles

// account for the time taken in the preceeding commands.
// we just burned 17 (19) cycles above, remove 4, (4*4=16)
// us is at least 6 so we can substract 4
us -= 4; // = 2 cycles

#else
// for the 1 MHz internal clock (default settings for common Atmega microcontrollers)

// the overhead of the function calls is 14 (16) cycles
if (us <= 16) return; //= 3 cycles, (4 when true)
if (us <= 25) return; //= 3 cycles, (4 when true), (must be at least 25 if we want to substract 22)

// compensate for the time taken by the preceeding and next commands (about 22 cycles)
us -= 22; // = 2 cycles
// the following loop takes 4 microseconds (4 cycles)
// per iteration, so execute it us/4 times
// us is at least 4, divided by 4 gives us 1 (no zero delay bug)
us >>= 2; // us div 4, = 4 cycles


#endif

// busy wait
__asm__ __volatile__ (
"1: sbiw %0,1" "\n\t" // 2 cycles
"brne 1b" : "=w" (us) : "0" (us) // 2 cycles
);
// return = 4 cycles
}



As you can see, because it relies on counting clock cycles to get the specified delays, unlike delay(), you will only get correct results with one of the supported clock speeds.

I suggest replacing the crystal with one that runs at a supported speed.
ATtiny core for 841+1634+828  http://goo.gl/6fRf8e
ATtiny core for x4/x5/x61/x7/x8 series http://goo.gl/O5Wtyu
ATtiny 841/1634 breakouts, mosfets, touch sensors in my store http://goo.gl/xyUN2v
(forum no longer allows clickable links in sigs :-( )

westfw

You could also consider using the more primitive delay functions like the _delay_ms() function in the avr-libc delay functions
Note at least two things:
1) _delay_ms(n) uses a floating-point value for n, so to delay 100us, you would use "_delay_ms(0.1);"
2) the value n MUST be a compile-time constant, so to achieve 700 to 2500us delay with 10us resolution, you would have to do something like:

Code: [Select]
void my_delay_us(unsigned int n) {  
   for (int i=0; i < n; i += 10) {  // use increment of 10 for 10us steps
     _delay_ms(0.009925);  // delay for 10us, minus loop overhead (determine experimentally)
   }
}



DrAzzy

Just realized, there's another really ugly solution - just multiply all your delayMicrosecond() delays by 12/14.75. This is objectionable on ideological grounds, but should work fine as long as you remember that the microsecond delays are funky.
ATtiny core for 841+1634+828  http://goo.gl/6fRf8e
ATtiny core for x4/x5/x61/x7/x8 series http://goo.gl/O5Wtyu
ATtiny 841/1634 breakouts, mosfets, touch sensors in my store http://goo.gl/xyUN2v
(forum no longer allows clickable links in sigs :-( )

Go Up