一、前言
学习了UART的相关理论知识,我就让ai给我出题目了,然后自己写的肯定第一遍都是有问题的,还得改正。
二、题目

三、我的解答
第一版答案
cs
void reg_serial_init(void)
{
//tx A9 Rx A10
RCC->APB2ENR |= (1ul<<2) | (1ul<<14); //打开GPIOA,UART1时钟
GPIOA->CRH &= ~(0xF<<4*1 | 0xF<<4*2); //清零PA9,PA10的原始配置
GPIOA->CRH |= (0xB<<4*1); //PA9为Tx引脚,配置为复用推挽输出,50MHz
GPIOA->CRH |= (0x8<<4*2); //PA10为Rx引脚,配置为上拉输入
GPIOA->BSRR |= (1ul<<10); //上拉PA10
USART1->BRR = 0x0271; //对应二进制0000 0010 0111.0001,是分频系数39.0625
USART1->CR1 |= (1ul<<2) | (1ul<<3) | (1ul<<5); //开启接收、发送使能,开启接收中断
//SR2寄存器不需要配置,00默认为1位停止位,CR3也不需要配置,默认其他功能都关闭
USART1->CR1 |= (1ul<<13); //最后开启UART
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置优先级位数
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn; //选择USART1中断
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //使能中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1; //设置抢占优先级和响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
}
void reg_serial_send_arr(uint8_t *data)
{
//通过DR寄存器发送数据
int data_len = sizeof(data)/sizeof(uint8_t);
for(int i=0;i<data_len;i++)
{
USART1->DR = data[i];
}
}
void reg_serial_send_byte(uint8_t data)
{
USART1->DR = data; //数据直接写入DR寄存器
}
uint8_t reg_serial_receive_byte(void)
{
uint8_t data;
data = USART1->DR; //读取DR寄存器的值,然后返回
return data;
}
void serial_send_byte_wait(uint8_t data) //在while循环里调用,查询方式发送 1 字节数据的流程
{
if(USART1->SR & (1ul<<6)) //先检查有没有发送完成
{
if(USART1->SR & (1ul<<7)) //再检查发送移位寄存器有没有发送完数据
{
reg_serial_send_byte(data); //发送一字节数据
}
}
}
void USART1_IRQHandler(void)
{
if(USART1->SR & (1ul<<5)) //判断是否是接收中断
{
uint8_t data = reg_serial_receive_byte(); //接收一字节数据,对DR寄存器进行读操作可以自动清零该中断请求
reg_serial_send_byte(data); //读出后发送回去
}
//下面接其他类型的中断请求,比如这里是判断是否发送完数据的中断请求,当然得确定在CR1寄存器中开启TC中断
if(USART1->SR & (1ul<<6))
{}
}
AI给的错误提示
reg_serial_send_arr里用sizeof(data)/sizeof(uint8_t)获取长度错误,形参是指针,sizeof 拿到的是指针字节数,不是数组长度。reg_serial_send_byte、reg_serial_send_arr直接写 DR,没有先查询 TXE 标志,上一字节没移入移位寄存器时写入会覆盖 TDR 数据,造成丢字节。serial_send_byte_wait逻辑问题:发送下一字节只需要判断 TXE,不需要额外判断 TC;使用 if 而不是 while,标志不满足时函数直接退出,数据发不出去。- 中断服务函数内调用
reg_serial_send_byte(data),同样缺少 TXE 判断,存在数据覆盖丢失风险。 serial_send_byte_wait里面同时判断 TC 和 TXE 是多余的,TC 代表整帧(含停止位)发送完成,不是载入下一个数据的判断条件。
第二版答案
尝试改了sizeof的参数为*data,但是其实这只是一个数据的字节长度,所以是错的,
然后在发送相关的函数添加了判断发送数据寄存器状态的判断:if(USART1->SR & (1ul<<7)),防止数据被覆盖,防止数据覆盖的问题
把发送时判断TC的语句删了,我以为要先判断发送是否完成才能判断要不要继续发送
cs
void reg_serial_init(void)
{
//tx A9 Rx A10
RCC->APB2ENR |= (1ul<<2) | (1ul<<14); //打开GPIOA,UART1时钟
GPIOA->CRH &= ~(0xF<<4*1 | 0xF<<4*2); //清零PA9,PA10的原始配置
GPIOA->CRH |= (0xB<<4*1); //PA9为Tx引脚,配置为复用推挽输出,50MHz
GPIOA->CRH |= (0x8<<4*2); //PA10为Rx引脚,配置为上拉输入
GPIOA->BSRR |= (1ul<<10); //上拉PA10
USART1->BRR = 0x0271; //对应二进制0000 0010 0111.0001,是分频系数39.0625
USART1->CR1 |= (1ul<<2) | (1ul<<3) | (1ul<<5); //开启接收、发送使能,开启接收中断
//SR2寄存器不需要配置,00默认为1位停止位,CR3也不需要配置,默认其他功能都关闭
USART1->CR1 |= (1ul<<13); //最后开启UART
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置优先级位数
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn; //选择USART1中断
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //使能中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1; //设置抢占优先级和响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
}
void reg_serial_send_arr(uint8_t *data)
{
//通过DR寄存器发送数据
int data_len = sizeof(*data)/sizeof(uint8_t);
for(int i=0;i<data_len;i++)
{
if(USART1->SR & (1ul<<7)) //判断发送数据寄存器是否为空,防止数据被覆盖
USART1->DR = data[i];
}
}
void reg_serial_send_byte(uint8_t data)
{
if(USART1->SR & (1ul<<7)) //判断发送数据寄存器是否为空,防止数据被覆盖
USART1->DR = data; //数据直接写入DR寄存器
}
uint8_t reg_serial_receive_byte(void)
{
uint8_t data;
data = USART1->DR; //读取DR寄存器的值,然后返回
return data;
}
void serial_send_byte_wait(uint8_t data) //在while循环里调用,查询方式发送 1 字节数据的流程
{
if(USART1->SR & (1ul<<7)) //再检查发送移位寄存器有没有发送完数据
{
reg_serial_send_byte(data); //发送一字节数据
}
}
void USART1_IRQHandler(void)
{
if(USART1->SR & (1ul<<5)) //判断是否是接收中断
{
uint8_t data = reg_serial_receive_byte(); //接收一字节数据,对DR寄存器进行读操作可以自动清零该中断请求
reg_serial_send_byte(data); //读出后发送回去
}
//下面接其他类型的中断请求,比如这里是判断是否发送完数据的中断请求,当然得确定在CR1寄存器中开启TC中断
if(USART1->SR & (1ul<<6))
{}
}
AI给的问题:
reg_serial_send_arr里int data_len = sizeof(*data)/sizeof(uint8_t);错误:*data是单个 uint8_t,sizeof 结果永远是 1,无法得到数组长度;函数形参是指针,不能这样获取数组长度。reg_serial_send_arr、reg_serial_send_byte、serial_send_byte_wait使用 if 判断 TXE,属于非阻塞判断,如果此时 TXE=0,数据直接丢弃,不会等待发送条件满足,数据发不出去。- 中断服务函数里面调用
reg_serial_send_byte(data),同样是 if 判断 TXE,若此时 TXE=0,收到的数据直接丢失,回显失败。 reg_serial_send_arr的 for 循环:如果某次 TXE=0,这一轮 i 对应的字节直接跳过,循环继续下一个,会出现乱序丢字节。- 注释笔误:代码里写的
SR2寄存器,实际是 USART_CR2。
第三版答案
主要还是这2个函数的问题
我修改了判断TXE的方式,但是逻辑错误了
我修改发送整包数据的参数为数组,但是在sizeof函数里面,数组会退化成指针,结果还是1
AI给的提示:
reg_serial_send_byte的 while 条件写反:while(USART1->SR & (1ul<<7))代表 TXE=1 的时候进入循环、反复写 DR;TXE 一旦变成 0,循环立刻退出。正确应该是等待 TXE 不等于 1(TXE=0)时空循环卡住 ,等 TXE 变为 1 之后跳出循环,再写 DR,也就是while(!(USART1->SR & (1ul<<7)));,循环体内不写 DR。你现在的写法会疯狂重复写入同一个字节。reg_serial_send_arr里int data_len = sizeof(data)/sizeof(uint8_t);仍然错误,形参uint8_t data[]等价指针,sizeof 获取的是指针大小,得不到数组长度。
cs
void reg_serial_send_byte(uint8_t data)
{
while(USART1->SR & (1ul<<7)) //判断发送数据寄存器是否为空,防止数据被覆盖
USART1->DR = data; //数据直接写入DR寄存器
}
void reg_serial_send_arr(uint8_t data[])
{
//通过DR寄存器发送数据
int data_len = sizeof(data)/sizeof(uint8_t);
for(int i=0;i<data_len;i++)
{
reg_serial_send_byte(data[i]);
}
}
第四版答案
解决数组长度的问题,就是直接从外部获取数组长度,因为外部获取时,不受退化影响,如果是内部计算,数组会退化成指针
uint8_t buf[] = {0x11,0x00,0x22,0x33};
reg_serial_send_arr(buf, sizeof(buf));
这里在调用方 用 sizeof(buf),只有在调用处、buf 还是真实数组的时候,sizeof 才能算出真实数组大小;一旦传到函数内部变成指针,就不行了。
后面单独修改发送整包数据的函数了,如下
cs
void reg_serial_send_arr(uint8_t data[],int len)
{
//通过DR寄存器发送数据
for(int i=0;i<len;i++)
{
reg_serial_send_byte(data[i]);
}
}