为什么要使用TIM搭配UART?
问题:接收超时检测(帧间隔超时)
UART接收不定长数据时,无法预知数据帧的结束位置(例如Modbus RTU协议通过 3.5字符时间 的静默判定帧结束)。若仅依赖接收中断,可能因数据间隔不明确导致帧解析错误。
解决方法:
启用定时器TIM:在每次收到一个字符时重启定时器。
定时器超时中断:若在设定时间内未收到新数据,则认为一帧数据接收完成,触发处理逻辑。这样就可以对接收的数据是否正确以及进行正确和错误信息的处理
1.硬件连接
-
RX(Receive):接收数据线,用于从外部设备读取数据。
-
TX(Transmit):发送数据线,用于向外部设备发送数据。
-
连接规则:两个设备的串口需交叉连接,即 A设备的TX → B设备的RX,反之亦然。
| stm32 | USB TO TTL |
| PB10(TX) | RX |
| PB11(RX) | TX |
| GND | GND |
| 3.3V | VCC |

2.CubeMX配置
串口配置的关键参数
(1) 波特率(Baud Rate)
-
定义:数据传输速率,单位为波特(Baud),即每秒传输的符号数。
-
常见值:9600、19200、38400、57600、115200 等。
-
注意:通信双方必须设置相同的波特率,否则数据无法正确解析。
(2) 数据位(Data Bits)
-
定义:每个数据帧的有效位数。
-
可选值:5、6、7、8 位(常用 8 位,对应一个字节)。
(3) 停止位(Stop Bits)
-
定义:标志数据帧结束的位数,用于同步。
-
可选值:1、1.5、2 位(常用 1 位)。
(4) 校验位(Parity Bit)
-
定义:用于检测传输错误的校验方式。
-
可选值:
-
None:无校验。
-
Even:偶校验(数据位 + 校验位的 1 的总数为偶数)。
-
Odd:奇校验(数据位 + 校验位的 1 的总数为奇数)。
-


TIM参数
(1)PSC:预分频系数
降低计数频率:将定时器的输入时钟源分频,得到更低的计数频率
扩展定时范围: 通过降低计数频率,定时器计数器(Counter)的每个“步进”时间变长,从而允许在有限的计数器位数(如16位)下实现更长的定时周期。
(2)ARR:自动重装载值
定义计数周期:定时器从 0 开始计数,当达到 ARR 值时触发中断或更新事件,随后自动重置为 0。
控制中断频率:ARR 直接决定了定时器中断的间隔时间。较小的 ARR 值会频繁触发中断,较大的 ARR 值则减少中断频率。
(3)TIM的中断时间T
T=1/F
(4)TIM的中断频率
F=时钟源频率/(预分频系数+1)/(自动重装载值+1)=72 000 000/(PSC+1)/(ARR+1)
时钟源频率:stm32f103c8t6一般默认为72KHz,我们配置的时钟树是多少就是多少(如下图)



使能TIM4
(5) 设置定时时间
需要设置100ms
F=时钟源频率/(预分频系数+1)/(自动重装载值+1)=72 000 000/(PSC+1)/(ARR+1)
=72 000 000 / (720-1)/(10000 -1 )
=10Hz
T=1/F=0.1S=100ms
3.函数发送
printf重定向
添加头文件
#include "stdio.h"
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}

使用重定向printf请打开这个
4.函数接收
接收函数封装
//串口封装函数
#define RX_BUF 255 //缓存区的最大容量
uint8_t rxdat[RX_BUF + 1]; //接收数据的存储缓存区
volatile uint16_t rx_count = 0; //记录当前接收到的字节数
uint8_t rxbuff; //临时存储单个接收字节
volatile uint8_t message_processed = 1; //是否接收到正确数据标志位
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM4) {
__HAL_TIM_CLEAR_IT(&htim4, TIM_IT_UPDATE);// 清除中断标志
if (!message_processed) {
//错误信息处理区
printf("错误信息:%s\r\n",(char*)rxdat);
//
message_processed = 1;
rx_count = 0;
memset(rxdat, 0, sizeof(rxdat));
}
HAL_TIM_Base_Stop_IT(&htim4);
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART3) {
// 停止定时器并清除中断标志
HAL_TIM_Base_Stop_IT(&htim4);
__HAL_TIM_CLEAR_IT(&htim4, TIM_IT_UPDATE);
HAL_TIM_Base_Start_IT(&htim4);// 重启定时器
if (message_processed) {
rx_count = 0;
memset(rxdat, 0, sizeof(rxdat));
message_processed = 0;
}
if (rx_count >= RX_BUF) {
rx_count = 0;
memset(rxdat, 0, sizeof(rxdat));
printf("[溢出] 缓冲区已满,数据已清空\r\n");
}
rxdat[rx_count++] = rxbuff;
if (rx_count >= 2) {
if (rxdat[rx_count - 2] == '\r' && rxdat[rx_count - 1] == '\n') {
rxdat[rx_count] = '\0';
//正确信息处理区
printf("完整消息: %s", (char*)rxdat);
//
while (HAL_UART_GetState(&huart3) == HAL_UART_STATE_BUSY_TX);// 等待发送完成
// 停止定时器并清除中断标志
HAL_TIM_Base_Stop_IT(&htim4);
__HAL_TIM_CLEAR_IT(&htim4, TIM_IT_UPDATE);
message_processed = 1;
rx_count = 0;
memset(rxdat, 0, sizeof(rxdat));
HAL_UART_Receive_IT(&huart3, &rxbuff, 1);
return;
}
}
HAL_UART_Receive_IT(&huart3, &rxbuff, 1);
}
}
5.main.c主函数初始化
HAL_UART_Receive_IT(&huart3,&rxbuff,1);
//确保 UART 中断优先级高于定时器中断,避免 UART 回调被定时器中断抢占:
HAL_NVIC_SetPriority(USART3_IRQn, 0, 0);
HAL_NVIC_SetPriority(TIM4_IRQn, 1, 0);
HAL_TIM_Base_Start_IT(&htim4);
通过测试得到正确的接收和发送数据

串口的参数调节如图
实验结果:
发送:1\r\n 返回:完整信息:1
发送:12 返回:错误信息:12
发送:123\r\n 返回:完整信息:123
发送:1234 返回:错误信息:1234
和CubeMX实现UART配合TIM实现不定长数据接收并处理正确和错误的信息&spm=1001.2101.3001.5002&articleId=147531322&d=1&t=3&u=0b7ca7873b4442378983012511dbb8ec)
5283

被折叠的 条评论
为什么被折叠?



