串口接收到的两组数据之间,通常会有一定的时间间隔。我们可以通过判断这个间隔来实现无结束符和无固定长度的串口数据接收功能。当串口在设定的时间内没有接收到新的数据时,认为一组数据已经接收完毕。
在一些通信协议中,可能会指定数据之间的间隔时间。例如,Modbus协议要求两组数据之间有3.5个字符的间隔。实际上,间隔时间通常与通信波特率有关。在9600波特率下,一个字节的数据包括起始位、8个数据位和结束位,总共为10位,每一位持续104微秒,因此一个字节的数据传输时间为1.04毫秒。3.5个字节的数据间隔大约为3.5 × 1.04ms = 3.64ms,通常为了安全起见,设置为4ms,甚至可以稍微增加到5ms,以便考虑可能的校验位。如果使用115200波特率,则5ms的间隔显得更为宽裕。
接下来开始程序的实现
使用CubeMx来生成初始化代码
Usart初始化配置这里配置的是串口3,波特率9600。
本文使用定时器2,具体CubeMx配置如下,我的主频是80M的,这里溢出时间为4ms
也就是说,从启动定时器到定时器溢出中断需要4ms,对应我们的超时时间。
NVIC配置,需要勾上USART3和TIM2就行了,我这里有其他的外设。
生成代码,这里是生成的定时器2初始化代码
TIM_HandleTypeDef htim2;
/* TIM2 init function */
void MX_TIM2_Init(void)
{
/* USER CODE BEGIN TIM2_Init 0 */
/* USER CODE END TIM2_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM2_Init 1 */
/* USER CODE END TIM2_Init 1 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 79;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 3999;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM2_Init 2 */
/* USER CODE END TIM2_Init 2 */
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspInit 0 */
/* USER CODE END TIM2_MspInit 0 */
/* TIM2 clock enable */
__HAL_RCC_TIM2_CLK_ENABLE();
/* TIM2 interrupt Init */
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
/* USER CODE BEGIN TIM2_MspInit 1 */
/* USER CODE END TIM2_MspInit 1 */
}
}
这里是USART3的初始化代码,注意要先使能一次接收中断,不然收不到数据
HAL_UART_Receive_IT(&huart3, &Usart3_rxtemp, 1); //接收1个字节到变量Usart3_rxtemp
void MX_USART3_UART_Init(void)
{
/* USER CODE BEGIN USART3_Init 0 */
/* USER CODE END USART3_Init 0 */
/* USER CODE BEGIN USART3_Init 1 */
/* USER CODE END USART3_Init 1 */
huart3.Instance = USART3;
huart3.Init.BaudRate = 9600;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
huart3.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart3.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart3) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART3_Init 2 */
HAL_UART_Receive_IT(&huart3, &Usart3_rxtemp, 1);
/* USER CODE END USART3_Init 2 */
}
接下来在usrat.c定义一个结构体方便使用;
typedef struct _UART_FLAG_STRUCT
{
uint8_t UARTFlag;//数据接受完成标志
uint8_t UART_Rxdata[256];//最大长度,自定义
uint8_t UARTCounter;//收到的数据长度,计数作用
uint8_t usart_start;//接收开始,定时器计时启动
uint8_t usart_counter;//定时器计时次数
} UartFlagSt;
USART3中断代码,发生无论发生什么中断,都会进入这里,然后在细分是什么中断。
void USART3_IRQHandler(void) //中断入口函数
{
/* USER CODE BEGIN USART3_IRQn 0 */
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3);
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
}
这里重定义接收完成回调函数
在这段代码中,具体逻辑如下,如果是收到的是第1个数据,那么开始启动定时器,将数据赋值到结构体接收缓存中,数据长度+1,如果接收下一个字节的数据小于定时器2的溢出时间,那么在串口接收完成回调中会把计数器清0,所以只要数据是连续的,那么就会持续清空计数器,就不会产生溢出中断,数据会持续接收。
/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART3)
{
__HAL_TIM_SET_COUNTER(&htim2,0);
Uart3FlagStC.usart_counter = 0;//清空定时器计数
if(0 == Uart3FlagStC.UARTCounter)//如果是第一个字符,则开启定时器
{
Uart3FlagStC.usart_start = 1;//定时器开始工作
__HAL_TIM_CLEAR_FLAG(&htim2,TIM_FLAG_UPDATE);
HAL_TIM_Base_Start_IT(&htim2);
}
if(Uart3FlagStC.UARTCounter<256)//设定的数组最大为160,要小于这个数,防止溢出
{
Uart3FlagStC.UART_Rxdata[Uart3FlagStC.UARTCounter] = Usart3_rxtemp;//将数据存到数组里面
Uart3FlagStC.UARTCounter++;//收到的数据个数+1
}
else Uart3FlagStC.UARTCounter = 0;//如果接受的数据超过设定值,则清空接收值,防止数据溢出
HAL_UART_Receive_IT(&huart3, &Usart3_rxtemp, 1); //重新开始接收
}
}
接下来我们写定时器溢出中断,当定时器溢出的时候,说明已经超过4ms没有清空计数器了,也就是4ms没有接收到数据了,所以代表我们已经接收完一帧数据了, 我们在中断中置标志位,同时关闭定时器,在下一次接收的时候重新启用。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //定时器溢出中断
{
if(htim==(&htim2))
{
Uart3FlagStC.UARTFlag = 1;
HAL_TIM_Base_Stop_IT(&htim2);//关闭定时器
}
}
欧克,接下来就可以处理我们接收到的数据了
在主函数中编写
while(1)
{
if(Uart3FlagStC.UARTFlag == 1) //如果串口3接收完一帧数据
{
HAL_GPIO_TogglePin(GPIOA, LED3_Pin); //翻转LED
Usart2_SendData(Uart3FlagStC.UART_Rxdata,Uart3FlagStC.UARTCounter);//将数据从串口2发送
Uart3FlagStC.UARTCounter = 0; //清空接收计数
Uart3FlagStC.UARTFlag = 0; //接收标志置0
}
}
ok,具体代码写完了
运行看下效果。
我这边串口3连接的是com7,波特率9600,串口2连接的是端口9,波特率115200
发送一帧数据,没有问题
我们再多发点试试10万个字节
可以看到发送和接收的字节数是一样的说明没有丢包
发送:
接收:
欧克,教程就结束了,觉得OK的 收藏点赞加关注谢谢咯
标签:TIM2,CODE,HAL,Modbus,TIM,Init,USER,串口 From: https://blog.csdn.net/qq_54656363/article/details/144748338