Go Down

Topic: Arduino - Coding without delays using program Ticks (Read 9930 times) previous topic - next topic

mixania

Hi Arduino Forum, I've created a helpful article on my blog that explains how to add delays to your program without actually delaying your program.

http://bakingtechnology.blogspot.com/2013/11/arduino-coding-without-delays-using.html

PaulS

Quote
I've created a helpful article on my blog that explains how to add delays to your program without actually delaying your program.

Why would you want to add delays to your program? Using millis() as in the blink without delay example, WILL allow you to achieve accurate timing. At least more accurate than guessing how many times loop needs to iterate to achieve the desired intervals.
The art of getting good answers lies in asking good questions.

Vaclav


Quote
I've created a helpful article on my blog that explains how to add delays to your program without actually delaying your program.

Why would you want to add delays to your program? Using millis() as in the blink without delay example, WILL allow you to achieve accurate timing. At least more accurate than guessing how many times loop needs to iterate to achieve the desired intervals.

Long time ago such delay was done in assembly using NOP and other magic words.  In these days I prefer the machine to do the work and figure out how many times the "wait code" needs to be executed to accomplish the desired delay.
Vaclav

westfw

millis() is already a "program tick", except that it's one that continues to tick away, at rate meaningful in human terms, regardless of how much code is running in your loop and how much other stuff is going on.  You can uses millis() in a sketch pretty much exactly the way the OP's example uses "programTick", giving you approximately the same thing that happens in the Blink_without_delay sketch, without have your code actually "delay" anywhere.

lardconcepts

You might also be interested in http://playground.arduino.cc/Code/Timer1

Quote
attachInterrupt(function, period)
Calls a function at the specified interval in microseconds. Be careful about trying to execute too complicated of an interrupt at too high of a frequency, or the CPU may never enter the main loop and your program will 'lock up'. Note that you can optionally set the period with this function if you include a value in microseconds as the last parameter when you call it.


Completely removes the need for anything in the loop.

Give it a go:

Code: [Select]
#include <TimerOne.h>

void setup() {
  DDRB |= _BV (5); //pinMode(13, OUTPUT);
  Timer1.initialize();
  Timer1.attachInterrupt(blink, 500000);
}

void loop() {
}

void blink() {
  PINB |= _BV (5);  // digitalWrite(13, !digitalRead(13));
}


Also has the advantage of operating completely independently of "the loop", leaving it free for other things.

Not saying it's a better way, but another different way that might serve a different purpose.

Incidentally, I used this opportunity to "big up" that nice Mr Nick Gammon's post listing the direct port numbers. I have it pinned on my wall for permanent reference!

Simply replacing:

Code: [Select]
pinMode(13, OUTPUT);
with
Code: [Select]
DDRB |= _BV (5);

and
Code: [Select]
digitalWrite(13, !digitalRead(13));
with
Code: [Select]
PINB |= _BV (5); 

takes it from 1,478 to 904 bytes saving 574 bytes. Reasonably significant.  (Your example is 980 bytes, so actually smaller code without "fiddling"!).

Coding Badly

Code: [Select]
void blink() {
  PINB |= _BV (5);  // digitalWrite(13, !digitalRead(13));
}


Almost works as expected.  You're off by one character.

lardconcepts

#6
Dec 07, 2013, 03:02 am Last Edit: Dec 07, 2013, 03:10 am by lardconcepts Reason: 1

Code: [Select]
void blink() {
 PINB |= _BV (5);  // digitalWrite(13, !digitalRead(13));
}


Almost works as expected.  You're off by one character.
Huh??!? I'm looking at the board flashing away right in front of me - been running like that with that code since I posted it.

What am I missing? Which character? What happens when your run it? What do you mean by "almost" as expected?

EDIT: Just copied that code directly from the forum into  a new sketch, flashed it again just in case there was a forum character encoding glitch, and the LED blinks on and of once per second. This is what I expected. Clearly you're not getting the same result. What's happening to your LED? Which board are you using? I'm on the Atmega328-based Uno.

Coding Badly


westfw

(also, consider putting some LEDs on the other PORTB pins and watch what they do. )

lardconcepts

#9
Dec 07, 2013, 11:02 am Last Edit: Dec 07, 2013, 11:16 am by lardconcepts Reason: 1

Try expanding the expression.


I'm not sure I follow. I'm "OR"-ing bit 5 of PORTB with whatever bit 5 of PORTB currently is.

This will cause it to flip. Well, it will according to CrossRoads and Nick Gammon....


writing a logic one to a bit in the PINx Register, will result in a toggle in the corresponding bit in the Data Register


And it seems to do so. The only thing I could do differently is to put
PINB = _BV (5);
instead of
PINB |= _BV (5);

but while the end result is the same, it uses 2 more bytes. So it wouldn't be that, either.
I also just tried & and ^ in case there was some big byte-saving technique I wasn't aware of, but, as I expected, that doesn't work either.


(also, consider putting some LEDs on the other PORTB pins and watch what they do. )


OK, a couple glow very dimly, the rest are all off. As I'd expect, as I've not set them to be or do anything. Not sure why I would for this exercise, but if I make them output and low, then all go out and stay out.
If I make them outputs and high, they stay bright while pin 13 flashes.
As I expect, as I'm only addressing pin13. Should they be doing anything different?

OK, I give up. Hands in the air - I know your point is meant to make me think it out for myself, but I've spent an hour puzzling over this since you told me my code "almost works", but was "off by one character". I'm missing the knowledge gap that's allowing me to fill in the blanks here.

This (now thoroughly confused!) newbie is throwing in the towel and shamelessly begging for a clear explanation :)

robtillaart


Hi Arduino Forum, I've created a helpful article on my blog that explains how to add delays to your program without actually delaying your program.

http://bakingtechnology.blogspot.com/2013/11/arduino-coding-without-delays-using.html


I do not think the concept you present is usable for accurate timing, on contrary it heavily depends on the flow of loop().
On the other hand, it can be powerful to know how often loop has been called (from main).

But there are some problems you need to tackle. e.g. how to handle  continue;  return;  and    break;
Code: [Select]

void loop()
{
  ...(A)
  if (..) continue;
  ..(B)
  if (..) break;
  ..(C)
  if (..) return;
  ..(D)
}

It really depends where you increase/reset the counter  - A,B,C,D  - to have the number still correct.

I would put an uint32_t counter into main that would be incremented every time loop is called.
main can be found near the core libs,  have to look for exact location
Code: [Select]

main()
{
  uint32_t loopCounter= 0;
  setup();
  for(;;)
  {
    loop();
    loopCounter++;
  }
}
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Coding Badly

This (now thoroughly confused!) newbie is throwing in the towel and shamelessly begging for a clear explanation :)


You have done an excellent job investigating.  You have even identified the symptom.  Try slowly carefully rereading your last post.  Compare what happened with what you expected to happen.  If the light bulb doesn't come on I will put you out of your misery and explain.

lardconcepts

#12
Dec 09, 2013, 05:01 pm Last Edit: Dec 09, 2013, 06:30 pm by lardconcepts Reason: 1
You have done an excellent job investigating.  You have even identified the symptom.  Try slowly carefully rereading your last post.  Compare what happened with what you expected to happen.  If the light bulb doesn't come on I will put you out of your misery and explain.


OK, I give up! Step by step, here's each stage:

The OP posted a technique for "how to add delays to your program without actually delaying your program".

As others noted, this might be susceptible to other things going on in the loop.
I thought it might also consume a few cycles checking for a count every time the loop runs.

So, having followed some of Nick Gammon's tutorials on on both PWM timers and direct port manipulation, the Crossroad's post about bit-flipping (which also led me to a tip about saving a couple of bytes by "OR-ing") and then being guided toward the TimerOne library, I put all three together and offered an alternative in Timer1.attachInterrupt() function.
I'd been using this library and function for a few days with great success as I needed to fire off an SPI byte to another 328 not only quickly, but regularly too.

What I expected the code to do was to switch the LED on and off once a second. What it did was switch the LED on and off once a second.

You then wrote:


Code: [Select]
void blink() {
 PINB |= _BV (5);  // digitalWrite(13, !digitalRead(13));
}

Almost works as expected.  You're off by one character.


And westfw wrote:


(also, consider putting some LEDs on the other PORTB pins and watch what they do. )


I explained that that also behaved in the way I expected.

In fact, to put it all together, this will pulse the LED on and off completely unaffected by the delay in blinking an LED on pin 12, and I've even made the PIN12 LED flash at a different rate so you have a sort of "3/2 time beat" so you can see that neither LED is affected when the other changes state.

Code: [Select]
#include <TimerOne.h>

void setup() {
 DDRB |= _BV (5); // pinMode (13, OUTPUT);
 pinMode(12, OUTPUT);
 Timer1.initialize();
 Timer1.attachInterrupt(blink, 500000);
}

void loop() {
 digitalWrite(12, HIGH);
 delay(1250);
 digitalWrite(12, LOW);
 delay(1250);
}

void blink() {
 PINB |= _BV (5); // digitalRead (13);
}


All is behaving exactly as I'd expect. So going back to your quote:

Compare what happened with what you expected to happen.  If the light bulb doesn't come on I will put you out of your misery and explain.


OK, the light bulb is coming on, and in the way I expect. As it also did in response to westfw's request.

I did wonder, for a short moment, if it was because I was using "blink()", even though I was redefining it, but changing it to anything else worked just the same, so it wasn't that either.

So, "put me out of my misery" and explain how it "almost works as expected" and which character I am "off by one of"?

I'm genuinely intrigued!

Coding Badly


Let's start with the symptom...


(also, consider putting some LEDs on the other PORTB pins and watch what they do. )


OK, a couple glow very dimly, the rest are all off.


Why would "a couple glow very dimly"?  There is nothing in your code that would power any more than one LED.  That seems suspicious.  (Important lesson: in software development, when something suspicious happens it is always a bug.)


The hints...


Code: [Select]
void blink() {
  PINB |= _BV (5);  // digitalWrite(13, !digitalRead(13));
}

Almost works as expected.  You're off by one character.


As far as I can tell, there is only one modification you can make and still have something that compiles and works.  You had considered making that change but dismissed it...

Quote
The only thing I could do differently is to put
PINB = _BV (5);
instead of
PINB |= _BV (5);

but while the end result is the same


Is the result the same?  What's different between the two expressions?




Try expanding the expression.

I'm not sure I follow. I'm "OR"-ing bit 5 of PORTB with whatever bit 5 of PORTB currently is.


The expanded expression is...
Code: [Select]
PINB = PINB | _BV (5);

Read PINB, set bit 5 high, write that value to PINB.  Reading PINB reads the current state of the eight inputs and outputs on I/O port B.  What if input zero is already high?  We end up with this...

[font=Courier New]PINB = PINB | _BV (5);
PINB = 0x01 | 0x20;
PINB = 0x21;[/font]

...which toggles two pins!  That is definitely not what was expected!  In your case, the symptom did not cause any serious problems.  What if I/O pin zero was configured as an output that was turned on?  That line of code, meant to toggle an LED on I/O pin 5, would also turn off the output on I/O pin zero!  Not good.

Does that help?

lardconcepts

#14
Dec 09, 2013, 08:54 pm Last Edit: Dec 09, 2013, 09:00 pm by lardconcepts Reason: 1
Let's start with the symptom...
Why would "a couple glow very dimly"?

Ah, slight red herring there. The two (very, very faintly) glowing pins are on 11 and 12, MOSI/MISO, breadboarded to an Adafruit level shifter for the SPI. Remove that link and they go out.

What if I/O pin zero was configured as an output that was turned on?  That line of code, meant to toggle an LED on I/O pin 5, would also turn off the output on I/O pin zero!  Not good.
Does that help?


Sort of. Well, yes. And no. In an explanation theory way, yes. In practice, well....

Taking what you said, 0x21 = 0b00100001, with the 1's representing pins 8(PB0) and 13(PB5) ?

So do you mean something along the lines of adding

Code: [Select]
pinMode(8, OUTPUT);
digitalWrite(8, HIGH);


to the top of my sketch?

If I understand what you're saying, pin8(PB0) should be affected - I couldn't replicate that. The light on pin8 (PB0) just stays on.

In fact, just for good measure, I even changed it to    
DDRB = 0XFF;
PORTB = 0xFF;

wired them all up, all came on and stayed on except pins 13 and 12 which happily blinked away in their 3/2 time.

Possible I might have misunderstood you there. All I can say is - I most definitely didn't come up with the 2-byte-saving technique myself! I just noted it under "top tips".

I've been searching my history for where I got that "  PINB |= _BV (5) is better than PINB = _BV (5) " technique from. All I can say is it wasn't a Nick Gammon tip (he can breath a sigh of relief!) but that around the same time on that evening, I visited various sites including http://www.billporter.info/2010/08/18/ready-set-oscillate-the-fastest-way-to-change-arduino-pins/ and http://hackaday.com/2011/07/09/hardware-xor-for-output-pins-on-avr-microcontrollers/ and  and http://hackaday.com/2010/08/19/todays-arduino-moment/

And fortunately, a copy-and-paste of "the secret lies in using PINB instead of PORTB" led me to http://letsmakerobots.com/node/22154 too which is similar, but not the same.

If I DO find the exact link I got it from, I'll post back here.

Go Up