Some of these are pretty briefly-explained here, but may be better-explained elsewhere (especially in my own code)... if you've questions, feel free to ask!

Table Of Contents:

  1. Storing data across resets without using EEPROM/FLASH (use EEAR(L/H)... or just use ".noinit")
  2. PWM/Compare-Match precision/accuracy quirks
  3. Using subi/sbci to *increment*
  4. ... (more to come, I'm sure)
  5. Code-size reduction... check out #limited-code hacks/tips (added 11-26-16)
  6. I HAVE NOT been keeping this ToC up-to-date... check out the project-logs!

See Also (in Comments, below):

  1. Generating a clean reset - UART Cleared + WDT-Reset (Thanks @Bruce Land!)
  2. LED memory-retention + .noinit - (Thanks @Jens Geisler!)
  3. ... (more to come?)

1. Where to store data across resets, without eating-up your EEPROM write-cycles?

EEARL/H (the EEPROM Address Registers) appear to go un-initialized...

Test:

  1. print-out the values of the EEAR registers
  2. store a value to them
  3. reset
  4. repeat
    1. (Upon power-up their values will be random. Subsequent resets will printout the value written)

(Tested on: ATmega8515)

Notes: "static" variables, in C, are automatically initialized to 0, even if you do not explicitly state to... Apparently globals are, as well. Locals, of course, are often stored to registers, which are initialized upon reset. An alternative, possibly, would be to define an uninitialized array in the SRAM? C might recognize use of an uninitialized value as an error, or might not...

Or, maybe there's something in this... from <avr/wdt.h>... "noinit" would probably do it.

uint8_t mcusr_mirror __attribute__ ((section (".noinit")));

2. PWM/Compare-Match precision/accuracy quirks

The basic jist: There are (at least) TWO methods for compare-matching, depending on the AVR device and/or its timer. (verified in FastPWM mode).

Some(?) AVR Timers guarantee that setting the OCR value to 0 will result in a PWM output that's ALWAYS low, and setting the OCR value to "TOP" will result in a PWM output that's ALWAYS high.

So, imagine a case where TOP = 3... (255 is more common) The TCNT register will increment 0, 1, 2, 3, then reset to 0 and repeat...(See note)

One Cycle:    |<------------->|
       ___ ___|___ ___ ___ ___|___
TCNT:   2 X 3 X 0 X 1 X 2 X 3 X 0 
       ¯¯¯ ¯¯¯|¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯|¯¯¯

PWM OUTPUTS
@ OCR=

       _______|               |
0:     \\\\\\\\               |   (0%)
       ¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯
       _______|_______________|___
TOP=3: ////////               |   (100%)
       ¯¯¯¯¯¯¯|

Perfect, right? Two cases...
OCR=0 = 0% PWM, OCR=TOP = 100% PWM...
BUT WAIT! There're *THREE* cases not shown above...
       ___ ___|___ ___ ___ ___|___ 
TCNT:   2 X 3 X 0 X 1 X 2 X 3 X 0 
       ¯¯¯ ¯¯¯|¯¯¯.¯¯¯.¯¯¯.¯¯¯|¯¯¯ 
              |___.   .   .   |
A:            /   \   .   .   /   (25%)
              |    ¯¯¯¯¯¯¯¯¯¯¯|
              |_______.   .   |
B:            /       \   .   /   (50%)
              |        ¯¯¯¯¯¯¯|
              |___________.   |
C:            /           \   /   (75%)
              |            ¯¯¯|

See the problem?

There are 4 OCR values which are relevent in this case... 0-3

Yet, there are 5 possible PWM duty-cycles.

In my experience, the data-sheets are not particularly clear about *how* this is implemented; I had to find it experimentally.

In fact, two devices--with *nearly identical* sections in the data-sheets regarding FastPWM--actually handle this particular scenario differently... (Was it the ATtiny85 vs the ATtiny861?).

Basically, the end-result of my experiments is that as I recall:

ATtiny861: Option A above is just flat-out not possible. "B" results from OCR=1, "C" from OCR=2. Again, a single-count pulse-width is *not possible* on this device, and quite plausibly many other devices.

ATtiny85: This device seems to handle "compare match" differently... I can't recall the exact details, but as I recall, the case in the timing-diagrams is possible with TOP=4 (not 3). Thus, every PWM-duty-cycle possibility shown is usable. But it is a bit confusing... In this case, it means that the compare-match results in a change *immediately* when the counter reaches the value... It counts 0 1 2 3 (4->0) 1 2 3... The 4->0 transition happens within a single timer-count (not two).

Again, it's a bit confusing... it means some devices actually consider a compare-match to occur essentially at the *beginning* of the timer-count, and other devices consider the match to occur at the *end* of the timer-count. Another way I've thought about it is that some devices respond to a match upon entry of a TCNT value, and others respond to a match upon exit...

(I haven't *fully* wrapped my head around how this works-out... I think there're several possible cases between matching an OCR value for PWM generation vs matching an OCR "TOP" value, and more... It's entirely plausible there are quite a few undiscovered quirks within this realm).

In most cases this sort of precision/accuracy is probably not necessary, but there are certainly some where it is. (e.g. "pseudo-FPD-Link" implemented for avr-lvds-lcd requires *exactly* 7 "bits") Again, as well, these experiments were with FastPWM, but these differing compare-match schemes may very-well affect other modes (e.g. waveform generation, timer-interrupts, etc). Again, in most cases, a difference of 1 timer-count is easily unnoticeable and can be ignored.

3. Using subi/sbci to *increment*

This one's probably not as obscure as it seems to me... but it took me a bit to figure out *how* it works...

for(uint32_t i=0; i<MAX; i++){}
avr-gcc is compiling this to SUBTRACTION instructions:
subi r20, -1
sbci r21, -1
sbci r22, -1
sbci r23, -1
The first instruction makes sense... we're talking 8-bit unsigned numbers here, so we have

r20 = r20 - (-1) = r20 - 255 = r20+1

But how does that Carry work...?

The instruction-set summary says the Carry Flag is:

"Set if the absolute value of K is larger than the absolute value of Rd; cleared otherwise."

And, K=255 is *always* larger than r20 *except* in the case where r20=255

r20 = 255 - 255 = 0, C = 0...

OK... This seems totally backwards (as well it should, subtraction is the reverse of addition...)

So, in *every* case, EXCEPT r20=255, the carry-flag is TRUE (1)...

sbci r21, -1:

r21 = r21 - (-1) - C = r21 + (1 - C)

Again, C is almost always 1... so r21 = r21 + (1 - 1) = r21 + 0 = r21

And, of course, when r20 = 255, then r20 = 255 - 255 = 0, C=0:

r21 = r21 + (1 - 0) = r21 + 1.

I dunno, *I* found it a bit mind-boggling...

...