Author Topic: A more efficient blink for AVR processors  (Read 15780 times)

0 Members and 1 Guest are viewing this topic.

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21688
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: A more efficient blink for AVR processors
« Reply #25 on: March 25, 2014, 01:28:30 am »
Efficient Blink

Advantages:
- 6 WORDs
- No memory required (not counting IO registers), only uses four registers
- Runs on any memory size
- Can be used stand-alone or with other programs

Disadvantages:
- Other programs cannot have loops (screws with timing -- or, the loops should have consistent timing); several interrupts are unavailable (for best timing)
- Fixed blink frequency only

Warning: erase before programming.

Code: [Select]
.cseg
.org 0

inc r0
brne overloop

in r16, DDRB
ldi r17, 1 << PB0
eor r16, r17
out DDRB, r16
overloop:

I'm disappointed that the port write is so classically RISC.  I'm sure it can be slimmed down.

Note: this is running in an ATmega32 on an Olimex board, so the LED is PB0, active low.  If it were PB7, I could clean it up a lot more.

Tim
« Last Edit: March 25, 2014, 01:34:20 am by T3sl4co1l »
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #26 on: March 25, 2014, 02:17:27 am »
Quote
2) I gather, that you refer to me reloading Z twice? I know it is rather anal (1 lousy ms), but I wanted to try my hand at cycle-counting, which is more of an academic exercise.
But the load takes the same cycles whether it happens before or after the jump.  I'm pretty sure my fix has exactly the same timing as your original code, with 2 fewer instructions.

Quote
- 6 WORDs
I don't think it's fair not to count the thousands of instructions at "overloop."
 

Offline GiantGnomeTopic starter

  • Contributor
  • Posts: 25
  • Country: dk
Re: A more efficient blink for AVR processors
« Reply #27 on: March 25, 2014, 05:34:54 am »
Quote
2) I gather, that you refer to me reloading Z twice? I know it is rather anal (1 lousy ms), but I wanted to try my hand at cycle-counting, which is more of an academic exercise.
But the load takes the same cycles whether it happens before or after the jump.  I'm pretty sure my fix has exactly the same timing as your original code, with 2 fewer instructions.

Not sure if I understand it correctly, but it seems that if I use your code, the first round of the inner loop would be done (clockCyclesPerMilliSecond-16-8)/4 = 3994 times, when all subsequent would run 65536 times, because the Z registers will start at zero. This makes the delay timing way off.

My intention was to have the inner loop to run 3994 times the first time, then 3998 all other times. The 3998 compensates for the cycles used in the outer loop, the 3994 compensates for the cycles in the main loop, the calls, setup and return from the subroutine.

You could argue that the last 16 cycles (1 microsecond) is not worth it for the extra instructions it costs... And you would probably be right.

(Also it seems to be stuck because you decrement Y in the end of the outer loop and reset it to delayMilliseconds at the start of the outerloop. This, I ascribe to typo)

- o when I do SBI to PINB, it actually toggles HIGH/LOW, rather than just setting HIGH?-

You could benefit from reading the datasheet - it is all documented there clearly.


You are absolutely right. It is very clear on page 51 of the Attiny13a datasheet, section 10.2.2.  :-[

Lesson learned (again)! Do not rely on tutorials only - ALWAYS READ THE DATASHEET.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #28 on: March 25, 2014, 07:52:01 am »
oops.  It looks like I did it wrong.
You had
Code: [Select]
    ldi     ZH,HIGH((clockCyclesPerMilliSecond-8-16)/4)
    ldi     ZL,LOW((clockCyclesPerMilliSecond-8-16)/4)
    ; A lot of nops and grief could be saved by only supporting a
    ; maximum of 255 millisecond delay.
    ldi     YL,LOW(delayMilliseconds)
    ldi     YH,HIGH(delayMilliseconds)
   
    delayloop:
            sbiw    ZL, 1       ; 2 cycles
            brne    delayloop   ; 2 cycles
       
        sbiw    YL,1                                     ; 2 cycles
        ldi     ZH,HIGH((clockCyclesPerMilliSecond-8)/4) ; 1 cycle
        ldi     ZL,LOW((clockCyclesPerMilliSecond-8)/4)  ; 1 cycle
        nop ; added to make a number of cycles divisible by 4 1 cycle 
        nop ; added to make a number of cycles divisble by 4  1 cycle
        brne    delayloop                                ; 2 cycles
And I should have re-arranged as well as changing the jump:
Code: [Select]
Delay:
    nop   
    ldi     YL,LOW(delayMilliseconds)
    ldi     YH,HIGH(delayMilliseconds)
DelayOuter:
    ldi     ZH,HIGH((clockCyclesPerMilliSecond-8-16)/4)
    ldi     ZL,LOW((clockCyclesPerMilliSecond-8-16)/4)
   
    delayloop:
            sbiw    ZL, 1       ; 2 cycles
            brne    delayloop   ; 2 cycles     
        sbiw    YL,1                                     ; 2 cycles
rjmp delay2 ;;; Two cycle delay
delay2: brne    DelayOuter ; reload and restart inner loop
Z is loaded at DelayOuter for both the initial loop, and also subsequent reloads.

 

Offline dannyf

  • Super Contributor
  • ***
  • Posts: 8221
  • Country: 00
Re: A more efficient blink for AVR processors
« Reply #29 on: March 25, 2014, 01:12:53 pm »
Quote
But that doesn't keep any time at all

Check on SysTick for Cortex-M processors.

Quote
for controlling the blinky, just a delta won't help the OP much.

That's easily implemented. Here is one example:

Code: [Select]
  time0=systick_get(); //obtain current time
  while (systick_get() - time0 < desired_duration) continue; //wait for desired duration to pass
  //do your things.

You can implement it, I am sure, a gazillion different ways too. But the basic idea is to have one free-running timer at all times.
================================
https://dannyelectronics.wordpress.com/
 

Offline miguelvp

  • Super Contributor
  • ***
  • Posts: 5550
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #30 on: March 25, 2014, 02:05:08 pm »
Check on SysTick for Cortex-M processors.

Yes, it's the same thing I mentioned but for ARM processors, it returns the clock ticks elapsed. But you have to know the frequency of your CPU and sample a well known oscillator that can give you a second worth of ticks that you can rely on for the rest of the program.

If you don't care about actually keeping time, then that is fine. But if you are going to use it to interface with other devices or humans, I would recommend you can accurately determine at least milliseconds without drift (so not discarding ticks and keeping them around for the next loop)

Quote
That's easily implemented. Here is one example:

Code: [Select]
  time0=systick_get(); //obtain current time
  while (systick_get() - time0 < desired_duration) continue; //wait for desired duration to pass
  //do your things.

You can implement it, I am sure, a gazillion different ways too. But the basic idea is to have one free-running timer at all times.

But that doesn't keep a constant time, just delta times. Actually just delta ticks.

Having a master clock that doesn't drift  and you can determine milliseconds since start at any given time would be a better approach.

say systick_get() - time0 < desired_duration gives you 3 extra ticks over desired_duration, you have drifted then 4 ticks.

On your approach you are really just delaying for the next frame, which is fine for applications that need to keep up a desired frame time say 120Hz per program loop then eat up extra cycles for the next frame. But you have to keep the running clock to prevent the tick drift even if it's probably no more than 4 ticks per loop.

Edit: but if it was for keeping up with a constant frame time, I would do the work first then find out how many ticks I have to delay by, instead of delay first then do work.

And I'm all for having one free-running timer at all times, that's why I suggested it in the first place
« Last Edit: March 25, 2014, 02:09:46 pm by miguelvp »
 

Offline dannyf

  • Super Contributor
  • ***
  • Posts: 8221
  • Country: 00
Re: A more efficient blink for AVR processors
« Reply #31 on: March 25, 2014, 02:11:07 pm »
Quote
But you have to know the frequency of your CPU

SysTick is driven by HCLK. You can read it fairly easily.

Quote
without drift

Maybe you can articulate what "drift" you are talking about before I respond further.
================================
https://dannyelectronics.wordpress.com/
 

Offline miguelvp

  • Super Contributor
  • ***
  • Posts: 5550
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #32 on: March 25, 2014, 03:26:55 pm »
Quote
without drift

Maybe you can articulate what "drift" you are talking about before I respond further.

say you do:
  time0=systick_get(); //obtain current time
and get 100000
and say your desired_duration is 1ms (1000 on a 1 MHz system)

Now say your loop takes 7 cycles per iteration (or any other value that 1000 is not divisible by):
while (systick_get() - time0 < desired_duration) continue; //wait for desired duration to pass

after 142 loops systick_get() - time0 will be 6 ticks short so it has to loop once more and 143*7 is 1001 ticks, so you drifted one microsecond.

If your loop took 9 cycles, then the drift will be 8us.

so that's why you must adjust your begin time with the desire_duration on the next loop so that it compensates from those extra cycles here and there.
 

Offline dannyf

  • Super Contributor
  • ***
  • Posts: 8221
  • Country: 00
Re: A more efficient blink for AVR processors
« Reply #33 on: March 25, 2014, 08:35:32 pm »
That kind of "drift" is inevitable in a real application (where the mcu is doing more than just blinking). Take your code for example, in the middle of it, an interrupt could have arrived thus lengthening the task to finish a job.

Putting aside the practical usefulness of being accurate to 1us here, you can improve the "drift" in the code that I posted. One approach may look like this:

Code: [Select]
  while (systick_get() < time_target) continue; //wait for target time to arrive
  time_target += desired_duration; //update time_target
  do_my_thing();  //execute user task

So everytime desired_duration or its multiples are happening, your task is executed.

You can also institute a user isr for systick counter so do_my_thing is done there directly, or in the main() via a flag.

The goal isn't to eliminate drifting; but to come up with a compromise.
================================
https://dannyelectronics.wordpress.com/
 

Offline miguelvp

  • Super Contributor
  • ***
  • Posts: 5550
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #34 on: March 26, 2014, 12:50:16 am »
Agreed, that's why I took the same approach to add the desired time to the begin time.
Adding it to the target time like you did works as well.

Furthermore if your new target time is lower than the last time tick, then it means your processes are taking longer than the desired time and during development you could adjust for that or rethink some processes. Having a timing system like this is pretty powerful.

It not only helps on the final product, but during development you can do very precise performance timings that might bite you if you didn't have such a system.

Too bad only higher end MCU's have dedicated instructions in the core fabric, but using an external oscillator driving an interrupt will work as well, many ways to skin that cat.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #35 on: March 26, 2014, 04:49:31 am »
Quote
Too bad only higher end MCU's have dedicated instructions in the core fabric
Lots of "moderate" level MCUs have a "systick timer" that that is pretty close.  (standard on all the ARM Cortex processors, and it's high time we stopped thinking about a CM0 as "high end")
Although I guess they're not quite as valuable when you need to implement an ISR to handle times more than about 1s.
(But then, the built-in timers pretty much have to be 64bits (like the RDTSC instruction on x86) to do that, and I'm not sure I'd want a 64bit counter on my 8bit CPU anyway.)

This relatively tiny (32bytes) timer-based code is based on the code from optiboot:

Code: [Select]
int main() {
  DDRB |=  1<<LED;
  // Set up Timer 1 for timeout counter
  TCCR1B = _BV(CS12) | _BV(CS10); // div 1024
  do {
    TCNT1 = -(F_CPU/(1024*16));
    TIFR1 = 1<<TOV1;
    while(!(TIFR1 & _BV(TOV1)))
        ;
    LED_PIN |= 1<<LED;
  } while (1);
}
« Last Edit: March 26, 2014, 05:06:26 am by westfw »
 

Offline miguelvp

  • Super Contributor
  • ***
  • Posts: 5550
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #36 on: March 26, 2014, 05:12:23 am »
Quote
Too bad only higher end MCU's have dedicated instructions in the core fabric
Lots of "moderate" level MCUs have a "systick timer" that that is pretty close.  (standard on all the ARM Cortex processors, and it's high time we stopped thinking about a CM0 as "high end")

I guess I mean too bad that only 32bit processors....

And thanks for your implementation btw.

Only question I have about your code is what do you use to drive the ICP (input capture pin)? or is that built in on the AVR with an oscillator?

Edit: also I noticed that if you use the 16 bit timer and you had an accurate timer you could use the 16 bit timer as a single channel logic analyzer on either falling or rising edge on certain modes :)

Edit2: nevermind, it would only work on protocols that can be determined by looking only at rising edges or falling edges but not both :(
« Last Edit: March 26, 2014, 05:38:52 am by miguelvp »
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #37 on: March 26, 2014, 06:21:26 am »
Quote
what do you use to drive the ICP
It's not using the input capture feature at all.  Do you mean the clock in general?  The AVR (and for that matter, most microcontroller timers) has a variety of possible clock sources, many of which are internal.  In this case, the
  TCCR1B = _BV(CS12) | _BV(CS10); // div 1024
statement sets the clock source (CS means Clock Select, I think) as the system clock after it's passed though a /1024 prescaler...
Except for the "systick" and "performance" counters, microcontroller timers tend to be pretty complicated beasts with lots of different operational modes.  (Putting the 'simple' systick counter inside the CPU definition of the Cortex ARM processors, instead of leaving the function "outside" in a vendor-defined timer module is relatively brilliant...)
 

Offline miguelvp

  • Super Contributor
  • ***
  • Posts: 5550
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #38 on: March 26, 2014, 07:07:38 am »
Thanks for the clarification, I thought ICP1 drove the Timer/Counter1 that you were referring to.

from the datasheet:
Quote
• ICP1 – Port D, Bit 6
ICP1 – Input Capture Pin: The PD6 pin can act as an Input Capture pin for Timer/Counter1.

But I missed the CS11 from the Clock Select Bit that uses the external T1 Pin (Port B, Bit 1) but that's a different pin as ICP1 (Port B, Bit 6)

(page 113 of the data sheet)

For whatever reason while reading the datasheet it seemed the 16 bit timer you were referring to was driven by that chip pin (ICP1) and overridable via T1.

http://www.atmel.com/Images/doc2466.pdf

I guess I'm not sure if the prescaler is driven by Port D, bit 6? or Port B, bit 1?

Sorry I'm a bit confused. but if the prescaler is driven by the ICP1 pin (PD6), maybe your board has it pre-configured to some external oscillator. Or is it internal to the chip? I guess I could read through the whole thing, might be educational anyways.

Thanks.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #39 on: March 26, 2014, 09:53:33 am »
The prescaler is driven by clkio, which is an internal signal:
 

Offline miguelvp

  • Super Contributor
  • ***
  • Posts: 5550
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #40 on: March 26, 2014, 01:25:44 pm »
Awesome, that picture makes it more clear.
T0 and T1 are used to sync clocks but not needed.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: A more efficient blink for AVR processors
« Reply #41 on: March 26, 2014, 05:26:29 pm »
the synchronizers for T0/T1 come before the clock selector.
I read it as "timers clock source is one of (nothing), (sync'ed T0), (sync'ed T1), (Clkio), or one of four prescaled Clkio values.  8 possible sources, total.)"
T1/T0 are not needed, and CLKio is always present an internally generated.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf