串口の二三事
串口の二三事
本教程基于HAL库
这两天去夏令营,又捣鼓了一下好久没搞过的单片机。在用串口的时候,感觉熟悉又陌生,故今天整理一下串口在开发中的一些常见疑问,以供日后查阅。
串口收发的原理
UART帧格式,也称UART协议,其内容如下(每一位的时长与波特率有关):
- 起始位:发送1位逻辑0(低电平),开始传输数据。
- 数据位:可以是5~8位的数据,先发低位,再发高位,一般常见的就是8位(1个字节),其他的如7位的ASCII码。
- 校验位:奇偶校验,将数据位加上校验位,1的位数为偶数(偶校验),1的位数4为奇数(奇校验)。
- 停止位:停止位是数据传输结束的标志,可以是1/1.5/2位的逻辑1(高电平)。
- 空闲位:空闲时数据线为高电平状态,代表无数据传输。
这里解答了一个疑问:串口是怎么知道自己收到了数据呢?答案就是通过判断有无收到满足协议的电平变化来判断。
UART与USART
-
UART:通用异步收发器(Universal Asynchronous Receiver/Transmitter);
-
USART:通用同步/异步串行收发器(Universal Synchronous/Asynchronous Receiver/Transmitter)。若使用异步方式,那将和UART无区别,如果是同步,则需要多一根时钟线(USART_CK).
数据类型转换
HAL函数要求的传递参数是unsigned char *
即uint_8 *
,故在实际开发中经常需要处理数据类型转化的问题。
unsigned char <=> char
- 在内存中都占据8位(1字节);
unsigned char
范围是0~255,char
范围是-128~127;- 英文字符的ASCII码小于127,故均能表示一个英文字符
// char -> unsigned char
char a[32];
b = (unsigned char *) a; // 只要a中不包含负数都没问题
// unsigned char -> char
unsigned char a[32];
b = (char *) a; // 只要a[i]≤127都没问题
unsigned char <=> 数字
short
= 16bit,int
= 32bit,long
= 64bit;float
= 32bit,double
= 64bit;
// 数字 -> unsigned char* (会有warning)
# include <stdio.h>
sprintf(unsigned char *, "%d %u %o %x %f...", int, unsigned int, int_8, int_16, float);
// unsigned char* -> 数字
# include <stdlib.h>
unsigned char string[32];
int tmp = atoi(string);
double tmp = atof(string);
double tmp = strtod(string, (char **)NULL);
long tmp = strtol(string, (char **)NULL, intbase); // intbase为进制
其中,strtod
的用法可参考C 库函数 – strtod() | 菜鸟教程 (runoob.com).
*浮点数的存储方式
查阅过程中顺便看了下float类型的存储方式,详见float类型的存储方式-CSDN博客:
以 8.25 为例进行分析:
8:二进制位1000
0.25:为2^-2,即为0.01
因此,8.25转换成二进制位1000.01
用科学记数法表示为:2^3*(1.00001)
因此:
符号位:0(表示正数)
指数位:3+127=130=100000010
尾数:00001-000000-000000-000000
注意,因为用科学记数法,所以最前面肯定是1,故不记录了。
因此,整体数据为 0-10000010-00001-000000-000000-000000
接收不定长数据
以下所用函数的简介,请见STM32 非阻塞HAL_UART_Receive_IT解析与实际应用 - 知乎 (zhihu.com)
需在CubeMX中使能串口全局中断
在实际开发中,经常遇到需要接收不定长字符串的问题,这里参考HAL库教程6:串口数据接收-CSDN博客,整理两种常用方法。
方法一 通过特定结束符判断
通信双方约定,用特定的字符作为结束,比如把0xff作为结束符,则收到0xff就把数据截断。对于ASCII码,正常情况下是不会发送0x0D与0x0A(回车与换行)的,所以可以用作结束符。
结构体定义
// usart.h
#define RX_LENGTH 1
#define RX_LENGTH_MAX 64
typedef struct {
unsigned char rx_buf_[RX_LENGTH_MAX];
unsigned char rx_flag_;
unsigned int rx_count_;
unsigned char rx_temp_[RX_LENGTH];
} UART_UserHandleTypeDef;
// usart.c
UART_UserHandleTypeDef user_huart1 = {
.rx_buf_ = {0},
.rx_count_ = 0,
.rx_flag_ = 0,
.rx_temp_ = 0
};
重载回调函数
// usart.c
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
user_huart1.rx_buf_[user_huart1.rx_count_] = user_huart1.rx_temp_[0];
user_huart1.rx_count_++;
if (0x0a == user_huart1.rx_temp_[0]) { // special character - "0x0a"
user_huart1.rx_flag_ = 1;
}
HAL_UART_Receive_IT(&huart1, (uint8_t *) user_huart1.rx_temp_, RX_LENGTH);
}
}
主循环判断
// main.c
HAL_UART_Receive_IT(&huart1, (uint8_t *) user_huart1.rx_temp_, RX_LENGTH);
while (1) {
if (user_huart1.rx_flag_) {
HAL_UART_Transmit(&huart1, user_huart1.rx_buf_, user_huart1.rx_count_, 0x10); //发送接收到的数据
for (int i = 0; i < user_huart1.rx_count_; i++)
user_huart1.rx_buf_[i] = 0;
user_huart1.rx_count_ = 0;
user_huart1.rx_flag_ = 0;
}
方法二 通过超时判断
需在CubeMX中同时使能串口与定时器的全局中断
如果串口在一定的时间内没有收到新的数据,可以认为一组数据已经接收完毕了。
结构体定义
// usart.h
#define RX_LENGTH 1
#define RX_LENGTH_MAX 64
typedef struct {
unsigned char rx_buf_[RX_LENGTH_MAX];
unsigned char rx_flag_;
unsigned int rx_count_;
unsigned char rx_temp_[RX_LENGTH];
} UART_UserHandleTypeDef;
// usart.c
UART_UserHandleTypeDef user_huart1 = {
.rx_buf_ = {0},
.rx_count_ = 0,
.rx_flag_ = 0,
.rx_temp_ = 0
};
重载回调函数
// usart.c
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
__HAL_TIM_SET_COUNTER(&htim3, 0);
if (0 == user_huart1.rx_count_) {
__HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);
HAL_TIM_Base_Start_IT(&htim3);
}
user_huart1.rx_buf_[user_huart1.rx_count_] = user_huart1.rx_temp_[0];
user_huart1.rx_count_++;
HAL_UART_Receive_IT(&huart1, (uint8_t *) user_huart1.rx_temp_, RX_LENGTH);
}
}
// tim.c
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim == (&htim3)) {
user_huart1.rx_flag_ = 1;
HAL_TIM_Base_Stop_IT(&htim3); // 关闭定时
}
}
主循环判断
// main.c
HAL_UART_Receive_IT(&huart1, (uint8_t *) user_huart1.rx_temp_, RX_LENGTH);
while (1) {
if (user_huart1.rx_flag_) {
HAL_UART_Transmit(&huart1, user_huart1.rx_buf_, user_huart1.rx_count_, 0x10); //发送接收到的数据
for (int i = 0; i < user_huart1.rx_count_; i++)
user_huart1.rx_buf_[i] = 0;
user_huart1.rx_count_ = 0;
user_huart1.rx_flag_ = 0;
}