[Arduino][寄存器操作] 如何用寄存器制作自己的Serial.print()?

问题描述 投票:0回答:1

(使用带有 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() 的某些步骤?

embedded arduino-uno cpu-registers atmega
1个回答
0
投票

在进行任何形式的编程时,了解类型的数值限制至关重要,尤其是在旧的 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
  • 永远不要编写依赖于隐式类型提升的程序。显式强制转换作为自记录代码的一种方式并没有错,使用额外的括号来清除运算符优先级也没有错。
  • (如果可能,请避免在 8 位微控制器上使用 32 位算术,因为它非常慢。但在这种特定情况下不起作用。)

代码可以重写为:


#include <stdint.h>

#define FOSC 8000000ul

uint16_t UBRRnAsynchronousNormalMode (uint32_t desiredBaud){
  return (FOSC / (16ul * desiredBaud)) - 1ul;
}
© www.soinside.com 2019 - 2024. All rights reserved.