-
AVR and "Fully Static Operation"
01/04/2023 at 02:08 • 0 commentsYeah?
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!
04/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
02/14/2017 at 18:35 • 0 comments4/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:
- 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.
- 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 stores your intended-value. Now say you use the same mistaken order-of-operations to write your OCR1A register's 16-bits. Now OCR1A contains the high-byte you previously-intended for TCNT1. Whoops!
-----------
So, make sure your order-of-operations is correct!
For the t861 a 10-bit write to TCNT1 might appear as follows:
uint16_t count = 0xffff; TC1H = count>>8; TCNT1 = count&0xff;
and a 10-bit read:uint8_t low_byte, high_byte; low_byte = TCNT1; high_byte = TC1H; uint16_t count = low_byte | (high_byte<<8);
(The TCNT1 and TC1H reads likely *could* be placed directly in the assignment of count, rather than using temporary 8-bit variables... But... do-so carefully, and even that may not be safe! And, realistically, this isn't even guaranteed, after the optimizer has its way. For things like these, inline-assembly might be the best way to go. Write those macros once, and use them thereafter.)For the m644 we're kind-of lucky you can simply write:
uint16_t count = 0xffff; TCNT1 = count;
or
uint16_t count = TCNT1;
But note these come with a bit of risk. This is only possible because the m644's 16-bit TCNT1 *internal* register is exposed to the user as both TCNT1Land the fake/temporary/buffered TCNT1H, which exist at sequential addresses.
AND your version of C, be it avr-gcc, or another, must explicitly support the proper order-of-operations for reads and writes to 16-bit I/O registers! (Note that some older versions of avr-gcc *don't*! Further, that it did once, then "regressed". And, in fact, there was a lot of debate as to whether to include that feature, at all... suggesting that it's *not* de-facto, and likely *not* in other C compilers.)
Further, imagine a case where you need very short duty-cycle pulses, using the PWM outputs. One might at one point attempt the following:
TCNT1 = 0x7ff7; ......... OCR1AL = 0xff;
See the problem, here...?The assignment to TCNT1 implicitly sets TCNT1H = 0x7f. But at the programmer-side of things writes to TCNT1H are stored to the temporary high-byte register. The internal TCNT1 register will be written 0x7ff7, as intended. But the temporary register still contains 0x7f. Now writing OCR1AL = 0xff will cause the internal OCR1A register to contain 0x7fff, NOT 0x00ff, as likely-intended.
Here's another case:
uint16_t thisCount = TCNT1; ........... OCR1AL = 0xff;
Now we have no idea what OCR1A will contain, as its high-byte depends *entirely* on what was read in the previous read-instruction.The t861, while a bit more difficult to code-for, forces the user to see some of these potential difficulties... So, if developing new habits, even aiming toward more-sophisticated microcontrollers (like the m644, in this case), it might be wise to consider using the method required for the t861: Read each byte explicitly, and in the proper order.
(Note, again, @K.C. Lee's experience, discovering that some C compilers don't actually respect the inherent order-of-operations! So it might be even wiser-still to write each byte-access on its own line, assigned to its own variable, then merge those variables in a final operation. Still, smart optimizers might optimize-that-out... Inline-assembly macros may be the best way to go).
-------------
ALLEGEDLY: one can use many 16-bit peripherals in 8-bit mode, by only ever accessing the Low-bytes. I've done it, on occasion, mostly from laziness. But looking into these details it seems very apparent to me that that could be risky. E.G. Imagine a case where the operating-mode of the Timer/Counter is changed, or another case where a 16-bit value is read/written before starting the system in 8-bit mode... e.g. the most-recent code-example could cause trouble even if it were only written to access 8-bits at a time!
uint8_t thisCount = TCNT1L; ..... Maybe hours later... ..... OCR1AL = 0xff;
Because, again, the action of reading TCNT1L causes the temporary-high-byte register to be loaded with the high-byte of TCNT1, whatever it may be, and now the high-byte of OCR1A is random.
---------------
http://www.atmel.com/images/doc1493.pdf
644 datasheet:
(OCR1A, etc. also use the temp-high register during *writes* but not during reads, and is not shown in drawings).
t861http://www.atmel.com/Images/doc8027.pdf
------------Atomicity!
You've already seen the earlier examples where mistakenly-written code could cause troubles... Even when accessing two *different* internal registers like ICR1 and TCNT1, the temp-register may contain unexpected data, etc.
And, you may already be familiar with the risks of accessing a multi-byte variable both inside and outside interrupts.
E.G. Briefly:
uint16_t globalVar = 0; main() { ... while(1) { printf("0x%x, ", globalVar); } } InterruptHandler() { globalVar++; }
Keep in mind, each access to globalVar is actually *two* instructions... one byte apiece.So there may be a case where, say, globalVar = 0x00ff. globalVar's low-byte=0xff is read in main(), an interrupt occurs, now globalVar = 0x0100. The interrupt returns to main(), where globalVar's high-byte is read as 0x01.
Now we get the printout: "0xfe, 0x1ff, 0x101" Whoops!
So, the solution is to "make it atomic", or, essentially, disable interrupts while globalVar is being read, in main:
main() { ... while(1) { uint16_t temp; cli(); temp = globalVar; sei(); printf("0x%x, ", temp); } }
Imagine how much worse it could be in the case of the many 16-bit registers which *share* a temporary-high-byte register!Basically, if you access *any* 16-bit register both in main() *and* in an interrupt, then you'd better make them *all* atomic. All of them!
(So, now we're at: even if you're planning to use a 16-bit timer in 8-bit mode, each access to each register will amount to *numerous* instructions: Read the current status of Interrupt-Enable. Disable Interrupts. Access BOTH registers, in order. Restore Interrupt-Enable-Status.)
-
flash-usage and global/static initialization-routine sizes
11/26/2016 at 05:03 • 0 commentsIt 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!
-
AVRs, stdio, printf, etc...
11/25/2016 at 12:04 • 0 commentsThis 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???
08/27/2016 at 19:12 • 2 commentsE.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(?)
08/14/2016 at 01:23 • 0 commentsReading 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 :)