You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于寄存器实现Arduino Serial.print()时的波特率异常问题排查

ATmega328P寄存器级USART实现乱码问题排查

我正在用VSCode+PlatformIO IDE,基于搭载ATmega328P的Arduino UNO Rev.3学习寄存器级固件开发,目标是用直接操作寄存器的方式实现和以下Arduino代码相同的功能:

Serial.begin(9600);
Serial.print("Hello world");

参考ATmega328P的USART章节后,我写了如下代码:

#include <Arduino.h>

// USART初始化参数
#define FOSC 8000000 // 时钟频率

unsigned int UBRRnAsynchronousNormalMode(int desiredBaud){
  return (FOSC/(16*desiredBaud))-1;
}

void serialBegin(unsigned int UBRRn){
  // 设置波特率
  UBRR0H = (unsigned char) (UBRRn>>8);
  UBRR0L = (unsigned char) UBRRn;

  // 定义帧格式
  UCSR0C = (1<<USBS0) | (3<<UCSZ00);

  // 启用发送器和接收器
  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()的某些实现步骤?


问题排查与修复

1. 时钟频率错误是核心问题

Arduino UNO Rev.3的ATmega328P默认使用16MHz外部晶振,但你代码里定义的FOSC是8000000(8MHz),这会导致计算出的UBRR值完全错误,直接引发波特率不匹配,出现乱码。

修正:把FOSC改成16000000:

#define FOSC 16000000 // Arduino UNO默认时钟频率

2. 帧格式不匹配

串口监视器设置的是8-N-1(8位数据位、无校验、1位停止位),但你代码里设置了(1<<USBS0),这会把停止位设为2位,同样会导致通信格式不匹配。

修正:去掉USBS0的设置,让UCSR0C对应8-N-1:

// 定义帧格式:8位数据位,无校验,1位停止位
UCSR0C = (3<<UCSZ00);

3. serialPrint函数的局限性

你当前的serialPrint只能发送单个字符,而Arduino的Serial.print("Hello world")是发送字符串。如果要实现相同功能,需要扩展函数来遍历字符串的每个字符发送:

void serialPrint(const char* str){
  while(*str != '\0'){
    while(!(UCSR0A & (1<<UDRE0)));
    UDR0 = *str++;
  }
}

修正后的完整代码

#include <Arduino.h>

// USART初始化参数
#define FOSC 16000000 // Arduino UNO默认时钟频率

unsigned int UBRRnAsynchronousNormalMode(int desiredBaud){
  return (FOSC/(16*desiredBaud))-1;
}

void serialBegin(unsigned int UBRRn){
  // 设置波特率
  UBRR0H = (unsigned char) (UBRRn>>8);
  UBRR0L = (unsigned char) UBRRn;

  // 定义帧格式:8位数据位,无校验,1位停止位
  UCSR0C = (3<<UCSZ00);

  // 启用发送器和接收器
  UCSR0B = (1<<TXEN0) | (1<<RXEN0);
}

void serialPrint(unsigned char data){
  while(!(UCSR0A & (1<<UDRE0)));
  UDR0 = data;
}

// 新增字符串发送函数
void serialPrint(const char* str){
  while(*str != '\0'){
    while(!(UCSR0A & (1<<UDRE0)));
    UDR0 = *str++;
  }
}

void setup() {
  serialBegin(UBRRnAsynchronousNormalMode(9600));
  serialPrint("Hello world"); // 现在可以发送完整字符串
}

void loop() {
  // 空循环
}

验证步骤

  1. 替换代码后重新编译上传
  2. 确认串口监视器保持9600 8-N-1设置
  3. 此时应该能正常显示"Hello world"

内容的提问来源于stack exchange,提问作者JoaoV

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:16:17