Close
0%
0%

AVR Random Potentially-Obsure Potentially-Usefuls

Random potentially-obscure potentially-useful things related to AVRs

Similar projects worth following
Every so often I come across something that's potentially-obscure and potentially-useful on AVRs... This is a place to throw that info...

(Hey, you got something to throw-in the mix? Throw it in the comments! Or maybe consider becoming a contributor...?)

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...

Read more »

  • AVR and "Fully Static Operation"

    Eric Hertz01/04/2023 at 02:08 0 comments

    Yeah?

    This caught my attention for the first time today:

    Atmega8515, which I've been using for some twenty years...

    "Fully Static Operation"

    https://en.m.wikipedia.org/wiki/Static_core

    I especially like the simplicity of the following:

    https://community.arm.com/support-forums/f/keil-forum/19230/fully-static-operation

    "Stop the clock and it will happely keep waiting until you are so kind to provide a clock pulse again."

    That's how I understood it...

    I guess it caught my attention now because I've been gaining interest in single-stepping CPUs.

    BUT:

    I *distinctly* recall seeing that the clock can't vary too much from cycle to cycle, so had to look it up.

    (Same datasheet)

    Right... So then... What're you supposed to change suddenly to realize this "Fully Static Operation," the internal RC clock? An external two-pin crystal?

    I don't get it.

    Maybe it has to do with sleep/powerdown modes, but I wouldn't exactly call something "fully" if you have to go through hoops to realize it.

    Beware!

  • Multiply instead of shift!

    Eric Hertz04/16/2017 at 08:51 2 comments

    @Mark Sherman has shared a wealth of great ideas... go check out his projects.

    He just pointed out something I hadn't considered...

    Many AVRs have an 8bit multiply instruction which executes in 2 clock-cycles.

    These same AVRs usually only have a *single* shift-instruction, so to shift-left 3 times actually takes *more* clock-cycles than to use a multiply-by-8!

    "The more you know!"

  • multi-byte register-access considerations

    Eric Hertz02/14/2017 at 18:35 0 comments

    4/16/17 Hmmm... I seem to have written this long ago, and completely forgotten that it was sitting in drafts... I haven't reread it, so don't recall *why* it was a draft and not published.

    -----------------

    When accessing multi-byte registers (e.g. the ADC's 10-bit, or Timer1's 16-bit), some things should be considered:

    • Atomicity
    • Temp-Variables
      • and atomicity
      • and reuse!
    • Order of read/write

    Some of this might seem a bit pedantic... But probably wise to consider, especially atomicity, and also if you're concerned about code-portability (e.g. between different versions of avr-gcc, or between different C compilers altogether).

    The gist is: since AVRs are 8-bit processors, that means accessing registers wider than 8-bits requires multiple instructions, and often a specific order-of-operations.

    Here are two examples, I'll go into in more detail:

    1. The 16-bit timer on the ATmega644 has the two registers: TCNT1L and TCNT1H used to read/write the Timer/Counter's count-value, ICR1L and ICR1H to read/write the Input-Capture Register, etc.
    2. The 10-bit timer on the ATtiny861 has TCNT1, ICR1, OCR1A, etc, which are 8-bit registers, and a *shared* TC1H used to write those registers' high-bits.

    Despite the m644's apparently having separate high-bytes for its registers, the fact is BOTH these devices use a "Temporary High-Register" during read/write accesses, which is shared between multiple >8-bit registers.

    For instance, when writing to the full 16-bits of the m644's TCNT1 one must *first* write the high byte to TCNT1H. This value is stored in the temporary high-byte register. Then when the Low Byte TCNT1L is written, both bytes in the *internal* 16-bit TCNT1 register will be updated simultaneously. (This is important! Otherwise your counter will briefly have a glitch! Further, it's not an option, you *have to* write these bytes in this order, otherwise you may inadvertently load garbage into the high-byte).

    Similarly, when writing to the full 10-bits of the t861's 10-bit-wide TCNT1 *internal* register, one must *first* write the high-byte to the shared temporary high-byte register TC1H, then write the low-byte to the TCNT1 register-address. (It's a bit confusing because TCNT1 appears as an 8-bit register, by name, to the programmer, but is 10-bits internal to the device).

    Read-back also has an order-of-operations. But note that the order is *opposite* that of write. First you must read the low-byte (TCNT1, in the case of the t861, TCNT1L, in the case of the m644). Doing-so automatically loads the devices' temporary high-byte register at that same instant. Then you read-back the high-byte from that temporary register. Again, on the m644, you'd read TCNT1H, whereas on the t861 you'd read TC1H.

    These orders-of-operations assure that when you perform a read/write, all >8 bits are read/written at the same instant. But note that you can't reverse these operations and just expect a minor glitch for the couple clock-cycles between accesses. By which I mean: Imagine the reversed order-of-operations used to write TCNT1 on the m644. If you write TCNT1L first, whatever random value was last-stored in the temporary-high-byte register will be loaded at that instant alongside your (correct) low-byte. And writing TCNT1H, thereafter, will have no effect on your counter's TCNT value and serve no other purpose than to store that value until the next time it's indirectly (and most-likely mistakenly) accessed by a write to a 16-bit register's low-byte.

    Note, too, that I specified "*a* 16-bit register's low-byte", rather than TCNT1L.

    Even though the m644 has register-addresses explicitly for TCNT1H and ICR1H, reading/writing these high-byte registers actually accesses the (singular/shared) temporary-high-byte-register.

    Basically, the m644's and t861's systems are the same, with the exception that the m644 has TC1H accessible at various memory-locations with different names.

    In the earlier example, where one mistakenly writes the bytes of TCNT1 out of order, now the temporary-high-register...

    Read more »

  • flash-usage and global/static initialization-routine sizes

    Eric Hertz11/26/2016 at 05:03 0 comments

    It appears that one could save up to 22 bytes of program-space by carefully considering whether you really need *both* initialized *and* "uninitialized" global/static variables... Choose one method for the entirety of your project and you might save a few bytes!

    https://hackaday.io/project/18574-limited-code-hackstips/log/49537-avr-project-doing-nada-58bytes-and-some-experimentsresults

  • AVRs, stdio, printf, etc...

    Eric Hertz11/25/2016 at 12:04 0 comments

    This probably needs some rewriting... But, basically, the gist is: If you're not careful, stdio.h can increase your code-size, even if you're not using it!

    https://hackaday.io/project/18574-limited-code-hacks/log/49498-avrs-stdio-printf-etc

  • printf() + PSTR() + %S And INCREASED code-size???

    Eric Hertz08/27/2016 at 19:12 2 comments

    E.G.

    printf("The variable 'thing' is %s.\n",
                ( thing ) ? "true" : "false");
    And, yahknow, with AVR's it's smart to use PSTR() and printf_P():
    printf_P(PSTR("The variable 'thing' is %s.\n"),
                ( thing ) ? "true" : "false");
    But, then... What about the strings "true" and "false"?

    They're still stored in RAM, right? They're certainly not *explicitly* PSTR()ed, here...

    So some searching, and I figured it had to be possible, but couldn't guess... found it as a side-note on a forum about something else entirely.

    %S (capital S):

    printf_P(PSTR("The variable 'thing' is %S.\n"),
                ( thing ) ? PSTR("true") : PSTR("false"));
    (FYI: coulda just checked 'man printf_P'... It's a bit difficult to find in there, as it's way down there... And, no mention of PSTR, so search instead for "ROM")

    BUT!

    I did something *just like this* and it actually *increased* my .text size by eight bytes, while making Zero impact on .bss and .data!

    Can't exactly explain it... maybe the optimizer was too smart for it's own good?

    So, if you're tight on space, be sure to check your savings when doing something like this...


    If anyone cares to verify my findings, here's the actual code-snippet:

    #if 0
          sprintf_P(stringBuf, PSTR("%s %"PRIu16" <-- %" PRIu8 "\n\r"),
                   (rowNCol) ? "row" : "col",
                   addr, highBit);
    #else
          //No decrease in .data/.bss, increased .text from 8152 to 8160!
          sprintf_P(stringBuf, PSTR("%S %"PRIu16" <-- %" PRIu8 "\n\r"), 
                   (rowNCol) ? PSTR("row") : PSTR("col"),
                   addr, highBit);
    #endif
    
    $ avr-gcc -v
    Using built-in specs.
    COLLECT_GCC=avr-gcc
    COLLECT_LTO_WRAPPER=/usr/lib/gcc/avr/4.8.1/lto-wrapper
    Target: avr
    Configured with: ../src/configure -v --enable-languages=c,c++ --prefix=/usr/lib --infodir=/usr/share/info --mandir=/usr/share/man --bindir=/usr/bin --libexecdir=/usr/lib --libdir=/usr/lib --enable-shared --with-system-zlib --enable-long-long --enable-nls --without-included-gettext --disable-libssp --build=i486-linux-gnu --host=i486-linux-gnu --target=avr
    Thread model: single
    gcc version 4.8.1 (GCC)


  • usb-tiny-isp + avrdude v6.1-6.3 bug(?)

    Eric Hertz08/14/2016 at 01:23 0 comments

    Reading an old project's chip for backup before repurposing the chip...

    READING the flash into an ihex file: AVR-Dude crashes with:

    *** Error in `avrdude': free(): invalid next size (normal): 0x09323798 ***
    Aborted
    • Linux x86-32bit
    • ATmega8515
      • Tried later with ATtiny861, same problem. (also with eeprom-read)
    • avr-dude v6.1
      • Tried later with v6.3, same problem.
    • usb-tiny-isp v1.04 (this is the version that comes from adafruit)
      • Tried later with FT2232H-based programmer, NO PROBLEM.
      • v1.07 has been tried with same problem at second link, below

    The file is written and appears to be correct.

    VERY LITTLE info 'round the web about this error-message...

    I think, mostly, due to the fact that the message isn't coming from avrdude, but from glibc(?)... most OS's probably don't actually print-out an error-message when free() is given an invalid address.

    Here's what I've found:

    http://www.avrfreaks.net/forum/error-avrdude-free-invalid-next-size-normal-0x000000000112c720

    https://bugs.launchpad.net/ubuntu/+source/avrdude/+bug/1400185

    http://www.mikrocontroller.net/attachment/301320/avrdude_crash.txt

    http://savannah.nongnu.org/bugs/?41292 (not a usb-tiny-isp)

    Otherwise, search-fu is failing me...

    Again, from what I can tell, I think this "bug" is probably more-existant than these results, but I don't know what to search for, because, again, most OS's probably don't even report an error, and the file *is* created, so basically it *looks* like the program's exitting normally.

    I've done a tiny bit of debugging (within my skillset) and determined that my encounter with this error appears to occur within avrpart.c:

    "avr_free_mem() -> m->buf = 0x9323798"

    Can't imagine why there'd be a free() call in avrdude to an address it didn't malloc(), but that appears to be what's happening

    (or maybe it was already freed?)

    As it stands, since I have an FT2232H-based programmer, I'll probably be using that, for now.

    There's a new bug-submission, let's see what they've got to say :)

    https://savannah.nongnu.org/bugs/index.php?48776

View all 7 project logs

Enjoy this project?

Share

Discussions

Jens Geisler wrote 04/14/2015 at 13:32 point

According to this page http://wp.josh.com/2014/03/03/the-mystery-of-the-zombie-ram/ the whole SRAM is quite persistent and will easily survive a reset cycle (but beware of the bootloader cleaning it anyway).  And with a tiny current source like a LED turned solar cell, retention will last for inifinity.

To prevent the compiler from zeroing a global variable it has to be declared like this:

"int foo __attribute__ ((section (".noinit")));"

  Are you sure? yes | no

Eric Hertz wrote 04/14/2015 at 13:44 point

Heh, you're too quick for me! Just discovered that noinit thing in <avr/wdt.h>... shoulda checked for an update here before diving into all that.

Good to know about the LED current source. Thanks!

  Are you sure? yes | no

Bruce Land wrote 04/14/2015 at 12:15 point

How to generate a clean reset: (1) wait for the UART to finish sending (2) start the watchdog with minimum time (3) while(1):

  Are you sure? yes | no

Eric Hertz wrote 04/14/2015 at 12:20 point

ah hah! I might just use something like that in this particular situation.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates