Check the code below:
#include <avr/io.h>
const uint16_t baudrate = 9600;
void setupUART( void ) {
uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 );
UBRRH = ubrr >> 8;
UBRRL = ubrr & 0xff;
}
int main( void ) {
setupUART();
}
This is the command used to compile the code:
avr-gcc -g -DF_CPU=4000000 -Wall -Os -Werror -Wextra -mmcu=attiny2313 -Wa,-ahlmns=project.lst -c -o project.o project.cpp
ubrr
is calculated by the compiler as 25, so far so good. However, to check what the compiler calculated, I have peek into the disassembly listing.
000000ae <setupUART()>:
ae: 12 b8 out UBRRH, r1 ; 0x02
b0: 89 e1 ldi r24, 0x19 ; 25
b2: 89 b9 out UBRRL, r24 ; 0x09
b4: 08 95 ret
Is it possible to make avr-gcc
print out the intermediate result at compile time (or pull the info from the .o file), so when I compile the code it prints a line like (uint16_t) ubbr = 25
or similar? That way I can do a quick sanity check on the calculation and settings.
GCC has command line options to request that it dump out its intermediate representation after any stage of compilation. The "tree" dumps are in pseudo-C syntax and contain the information you want. For what you're trying to do, the -fdump-tree-original
and -fdump-tree-optimized
dumps happen at useful points in the optimization pipeline. I don't have an AVR compiler to hand, so I modified your test case to be self-contained and compilable with the compiler I do have:
typedef unsigned short uint16_t;
const int F_CPU = 4000000;
const uint16_t baudrate = 9600;
extern uint16_t UBRRH, UBRRL;
void
setupUART(void)
{
uint16_t ubrr = ((F_CPU / (16 * (float) baudrate)) - 1 + .5);
UBRRH = ubrr >> 8;
UBRRL = ubrr & 0xff;
}
and then
$ gcc -O2 -S -fdump-tree-original -fdump-tree-optimized test.c
$ cat test.c.003t.original
;; Function setupUART (null)
;; enabled by -tree-original
{
uint16_t ubrr = 25;
uint16_t ubrr = 25;
UBRRH = (uint16_t) ((short unsigned int) ubrr >> 8);
UBRRL = ubrr & 255;
}
$ cat test.c.149t.optimized
;; Function setupUART (setupUART, funcdef_no=0, decl_uid=1728, cgraph_uid=0)
setupUART ()
{
<bb 2>:
UBRRH = 0;
UBRRL = 25;
return;
}
You can see that constant-expression folding is done so early that it's already happened in the "original" dump (which is the earliest comprehensible dump you can have), and that optimization has further folded the shift and mask operations into the statements writing to UBRRH and UBRRL.
The numbers in the filenames (003t and 149t) will probably be different for you. If you want to see all the "tree" dumps, use -fdump-tree-all
. There are also "RTL" dumps, which don't look anything like C and are probably not useful to you. If you're curious, though, -fdump-rtl-all
will turn 'em on. In total there are about 100 tree and 60 RTL dumps, so it's a good idea to do this in a scratch directory.
(Psssst: Every time you put spaces on the inside of your parentheses, God kills a kitten.)
There might be a solution for printing intermediate results, but it will take you some time to be implemented. So it is worthwhile only for a quite large source code base.
You could customize your GCC compiler; either thru a plugin (painfully coded in C or C++) or thru a MELT extension. MELT is a high-level, Lisp-like, domain specific language to extend GCC. (It is implemented as a [meta-]plugin for GCC and is translated to C++ code suitable for GCC).
However such an approach requires you to understand GCC internals, then to add your own "optimization" pass to do the aspect oriented programming (e.g. using MELT) to print the relevant intermediate results.
You could also look not only the generated assembly (and use -fverbose-asm -S
as options to GCC) but also perhaps in the generated Gimple representations (perhaps with -fdump-tree-gimple
). For some interactive tool, consider the graphical MELT probe.
Perhaps adding your own builtin (with a MELT extension) like __builtin_display_compile_time_constant
might be relevant.
I doubt there is an easy way to determine what the compiler does. There may be some tools in gcc specifically to dump the intermediate form of the language, but it will definitely not be easy to read, and unless you REALLY suspect that the compiler is doing something wrong (and have a VERY small example to show it), it's unlikely you can use it for anything meaningful - simply because it is too much work to follow what is going on.
A better approach is to add temporary variables (and perhaps prints) to your code, if you worry about it being correct:
uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 );
uint8_t ubrr_high = ubrr >> 8
uint8_t ubrr_low = ubrr & 0xff;
UBRRH = ubrr_high;
UBRRL = ubrr_low;
Now, if you have a non-optimized build and step through it in GDB, you should be able to see what it does. Otherwise, adding printouts of some sort to the code to show what the values are...
If you can't print it on the target system because you are in the process of setting up the uart that you will be using to print with, then replicate the code on your local host system and debug it there. Unless the compiler is very buggy, you should get the same values from the same compilation.
Here's a hack: simply automate what you are doing by hand now.
-ahlms=
output.lst). Alternatively, use your own dissassembly method as a post-compile step in your makefile.out UBRRH
and out UBRRL
lines. These are going to be loaded from registers, so your script can pull out the immediately preceeding assignments to registers that will be loaded into UBRRH
and UBRRL
. The script can then reassemble the UBRR
value from the value loaded into the general-purpose registers which whhich are used to set UBRRH
and UBRRL
.This sounds to be easier than Basile Starynkevich's very useful suggestion of MELT extension. Now, granted that this solution seems fragile, at first blush, so let's consider this issue:
out UBRR_, r__
will appear in the disassembly listing: there is simply no other way to set the registers/write data to port. One thing that might change is the spacing in/around these lines but this can be easily handled by your scriptout
instructions can only take place from general-purpose registers, so we know there will be a general-purpose register as the second argument to the out
instruction line, so that should not be a problem.out
instruction. Here we must allow for some variability: instead of LDI
(load immediate), avr-gcc might produce some other set of instructions to set the register value. I think as a first pass the script should be able to parse immediate loading, and otherwise dump whatever last instruction it finds involving the register that will be written to UBRR_
ports.The script may have to change if you change platforms (some processors have UBRRH1
/2
registers instea of UBRRH
, however in that case you baud code will have to change. If the script complains that it can't parse the disassembly then you'll at least know that your check has not been performed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With