(使用带有 PlatformIO IDE 和 Arduino UNO Rev. 3 的 VSCode,拥有 ATmega328P 微处理器)
嗨,我开始学习使用寄存器操作的嵌入式系统固件开发。 我的第一个目标是制作一个“Hello World”程序:
Serial.begin(9600);
Serial.print("Hello world");
但是直接设置寄存器值可以重现与上面代码相同的结果。
按照ATmega328P的USART部分,我开发:
#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');
}
串行监视器显示“Terminal on COM5 | 9600 8-N-1”,但我的打印结果是:
听起来像是一个常见的波特率差异问题,但据我了解,我将速率设置为 9600(与串行监视器指示的相同)。 我是否在代码中犯了错误,或者错过了重现 Serial.print() 的某些步骤?
在进行任何形式的编程时,了解类型的数值限制至关重要,尤其是在旧的 8 位 MCU 编程期间。
这个函数是错误的:
unsigned int UBRRnAsynchronousNormalMode(int desiredBaud){
return (FOSC/(16*desiredBaud))-1;
}
FOSC 定义为
8000000
,该数字是一个“整数常量”,具有与其他所有数字一样的类型。 C 首先尝试看看是否可以使其输入 int
。但由于 AVR 有 16 位 int
,所以最大的数字是 65535。所以它尝试将其放入 32 位 long
,这是可以的。因此整数常量 8000000
的类型为 long
。
这意味着在 AVR 上,您的方程相当于
((long)FOSC/(16*desiredBaud))-1
。但这里 16
和 desiredBaud
都是 16 位 int
。并且 16 * 9600
= 153600,因此会出现整数溢出。
在评论中您描述
FOSC/16/desiredBaud
解决了问题。这是因为 /
运算符从左到右关联操作数,因此保证等同于 (FOSC/16)/desiredBaud
。在子表达式FOSC/16
中,一个操作数是long
。隐式类型提升发生在计算之前(请参阅隐式类型提升规则)。因此,它是在 long
上计算的,结果是 long
并且由于结果是 long
,“结果”/desiredBaud 也隐式提升为 long
。
那么实际要做什么来解决这个问题呢?遵循最佳实践:
int
或 long
。请使用 stdint.h
中的便携式类型。1 << ...
或 1 >> ...
总是危险的,因为 1
是 int
并且有符号。始终使用1u
。代码可以重写为:
#include <stdint.h>
#define FOSC 8000000ul
uint16_t UBRRnAsynchronousNormalMode (uint32_t desiredBaud){
return (FOSC / (16ul * desiredBaud)) - 1ul;
}