(Using VSCode with PlatformIO IDE and Arduino UNO Rev. 3, witch have a ATmega328P microprocessor)
Hi, I started learning about embedded systems' firmware development using register manipulation. My first goal is to make a "Hello World" program:
Serial.begin(9600);
Serial.print("Hello world");
But directly setting register values to reproduce the same result as the code above.
Following the USART section of ATmega328P, I develop:
#include <Arduino.h>
// USART initialization parameters
#define FOSC 8000000 // Clock speed
unsigned int UBRRnAsynchronousNormalMode(int desiredBaud){
return (FOSC/(16*desiredBaud))-1;
}
void serialBegin(unsigned int UBRRn){
// Setting baud rate
UBRR0H = (unsigned char) (UBRRn>>8);
UBRR0L = (unsigned char) UBRRn;
// Defining frame format
UCSR0C = (1<<USBS0) | (3<<UCSZ00);
// Enabling transmiter
UCSR0B = (1<<TXEN0) | (1<<RXEN0);
}
void serialPrint(unsigned char data){
while(!(UCSR0A & (1<<UDRE0)));
UDR0 = data;
}
void setup() {
serialBegin(UBRRnAsynchronousNormalMode(9600));
serialPrint('1');
}
The Serial Monitor says "Terminal on COM5 | 9600 8-N-1", but my print results in:
Sounds like a common baud rate disparity problem, but as I understand, I set my rate to 9600 (same as indicated by the Serial Monitor). Have I made an error in the code, or missed some step to reproduce a Serial.print()?
Understanding the numerical limits of types is essential when doing any form of programming, but during old 8 bit MCU programming in particular.
This function is wrong:
unsigned int UBRRnAsynchronousNormalMode(int desiredBaud){
return (FOSC/(16*desiredBaud))-1;
}
FOSC is defined as 8000000
and that number, an "integer constant", has a type like everything else. C first tries to see if it can make it type int
. But since AVR has 16 bit int
, the largest number is 65535. So instead it tries to fit it inside a 32 bit long
, which is ok. So the integer constant 8000000
has type long
.
Meaning that on AVR, your equation is equivalent to ((long)FOSC/(16*desiredBaud))-1
. But here both 16
and desiredBaud
are 16 bit int
. And 16 * 9600
= 153600, so you get an integer overflow.
In comments you describe that FOSC/16/desiredBaud
fixed the problem. This is because the /
operators associate operands from left to right, so this is guaranteed to be equivalent to (FOSC/16)/desiredBaud
. And in the sub expression FOSC/16
, one operand is long
. An implicit type promotion happens before calculation (see Implicit type promotion rules). So it is calculated on long
and the result is long
And since the result is long
, "result"/desiredBaud gets implicitly promoted to long
too. So it worked by luck... don't rely on luck.
So what to actually do to fix this? Follow best practices:
int
or long
. Use the portable types from stdint.h
instead.1 << ...
or 1 >> ...
is always dangerous, since 1
is int
and signed. Always use 1u
.The code could be rewritten as:
#include <stdint.h>
#define FOSC 8000000ul
uint16_t UBRRnAsynchronousNormalMode (uint32_t desiredBaud){
return (FOSC / (16ul * desiredBaud)) - 1ul;
}