为何省略unsigned int中间变量会导致UART通信程序失效?
为什么直接用宏给UBRR寄存器赋值会失效,改用中间变量就正常?
我想要通过UART接口发送消息,最初编写了如下程序:
#ifndef F_CPU #error "F_CPU has to be defined" #endif #ifndef CONSOLE_BAUD #pragma message "CONSOLE_BAUD was not defined, using 9600" #define CONSOLE_BAUD 9600 #endif #define UBRR_VALUE F_CPU/16/CONSOLE_BAUD-1 // init UBRRH = (unsigned char)(UBRR_VALUE >> 8); UBRRL = (unsigned char)UBRR_VALUE; UCSRB = (1 << TXEN); UCSRC = (1 << URSEL) | (1 << USBS) | (3 << UCSZ0); // send while (!(UCSRA & (1 << UDRE))); UDR = 'A';
该程序无法正常工作,发送的十六进制数据为00。
随后我将程序修改为如下形式:
#ifndef F_CPU #error "F_CPU has to be defined" #endif #ifndef CONSOLE_BAUD #pragma message "CONSOLE_BAUD was not defined, using 9600" #define CONSOLE_BAUD 9600 #endif #define UBRR_VALUE F_CPU/16/CONSOLE_BAUD-1 // init unsigned int ubrr = UBRR_VALUE; // changed UBRRH = (unsigned char)(ubrr >> 8); // changed UBRRL = (unsigned char)ubrr; // changed UCSRB = (1 << TXEN); UCSRC = (1 << URSEL) | (1 << USBS) | (3 << UCSZ0); // send while (!(UCSRA & (1 << UDRE))); UDR = 'A';
修改后程序恢复正常。请问这是为什么?直接将预处理器宏赋值给UBRR寄存器与使用中间变量有何区别?
编辑补充:我的问题被标记为重复问题,但我一开始并未发现括号缺失的问题,根本找不到关联的重复问题,我认为该标记并不合理。
问题原因分析
核心问题出在预处理器宏的文本替换特性和运算符优先级上:
宏是纯文本替换,不会像变量那样先计算完整表达式结果。你的宏
UBRR_VALUE展开后,当你写(UBRR_VALUE >> 8)时,实际会变成:(F_CPU/16/CONSOLE_BAUD - 1 >> 8)由于减法
-的优先级高于右移>>,编译器会先计算1 >> 8(结果为0),再用除法结果减去0,最终得到的是F_CPU/16/CONSOLE_BAUD右移8位,而不是正确的(F_CPU/16/CONSOLE_BAUD - 1)右移8位。这导致UBRRH和UBRRL的赋值完全错误,UART波特率配置失效,自然发不出正确数据。改用中间变量
unsigned int ubrr = UBRR_VALUE;时,编译器会先完整计算UBRR_VALUE的结果(先执行除法再减1),得到正确的16位波特率寄存器值,再分别提取高8位和低8位赋值给UBRRH、UBRRL,这就保证了寄存器配置的正确性。
宏与中间变量的核心区别
- 宏:预处理器阶段直接做文本替换,完全遵循代码的运算符优先级,不会自动给表达式加括号保证计算顺序。
- 中间变量:编译器先计算表达式的完整结果,再将结果拆解使用,相当于给整个表达式套了隐形括号,确保计算逻辑符合预期。
解决方案
除了用中间变量,最直接的修复是给宏表达式加上足够的括号,避免优先级问题:
#define UBRR_VALUE (((F_CPU) / (16 * CONSOLE_BAUD)) - 1)
内容的提问来源于stack exchange,提问作者mnj
相关产品推荐
相关产品推荐

