首页 > 其他分享 >HAL库教程:串口+定时器接收不定长数据适用Modbus

HAL库教程:串口+定时器接收不定长数据适用Modbus

时间:2024-12-27 11:55:14浏览次数:6  
标签:TIM2 CODE HAL Modbus TIM Init USER 串口

串口接收到的两组数据之间,通常会有一定的时间间隔。我们可以通过判断这个间隔来实现无结束符和无固定长度的串口数据接收功能。当串口在设定的时间内没有接收到新的数据时,认为一组数据已经接收完毕。


在一些通信协议中,可能会指定数据之间的间隔时间。例如,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

相关文章

  • WatchAlert - 一款轻量级云原生多数据源监控告警引擎(运维研发必备能力)
    概述在现代IT环境中,监控和告警是确保系统稳定性和可靠性的关键环节。然而,随着业务规模的扩大和数据源的多样化,传统的单一数据源告警系统已经无法满足复杂的需求。为了解决这一问题,我开发了一个开源的多数据源告警引擎——WatchAlert,它能够集成多种数据源,提供灵活的告警策略配置......
  • 扩展您的串口设备 EU104数据转发芯片可独立设置通讯速率和参数 将1个UART接口扩展为4
    扩展您的串口设备EU104数据转发芯片可独立设置通讯速率和参数将1个UART接口扩展为4个EU104是一款数据转发芯片,具有5个UART接口。它可以将1个UART接口扩展为4个UART接口,主接口的通讯速率可以达到460800bps,子接口的通讯速率最高可达到38400bps。每个接口的通讯速率可以由软件独立......
  • 入门秘籍来啦!串口设计如何避免踩坑?看这篇!
    无论是设计简单的串口通信系统,还是复杂的串口屏应用,掌握这些基础知识都将助你一臂之力。快来动手实践吧,探索串口设计的无限可能!本文将以Air700ECQ/EAQ/EMQ为例,带你从硬件设计的角度,一起来了解串口设计中的一些关键注意点;软件开发或者AT设置方面不做深入探讨。一、串口相关管脚A......
  • Hal库串口中断接收
    当RXbuff的大小为1024字节,并且使用HAL_UART_Receive_IT进行中断接收时,STM32HAL库会根据配置在接收到一半数据(512字节)和全部数据(1024字节)时调用相应的回调函数。具体来说,以下是如何工作的:工作原理启动接收:调用 HAL_UART_Receive_IT(&huartx,RXbuff,sizeof(RXbuff))......
  • Halcon 0 识别图像的步骤
     Halcon识别图像的步骤①基本步骤:图像预处理------》区域分割------》区域筛选------》特征分析、有用信息提取等②图像预处理:图像去噪、图像基本变化、图像增强、图像位置变化等。③区域分割:将感兴趣的区域从原图像中分割出来。包括基于阈值的分割、聚类的分割等等。......
  • 嵌入式单片机中串口通信实现详解
    串口通信的概念通信的概念通信指的是CPU和外部设备之间或者计算机与计算机之间的数据交互。                  通信的种类处理器与外部设备之间的通信方式有两种:   串行通信            并行通信      ......
  • C#Halcon联合编程动态生成显示窗口
    UI编辑界面.exe显示界面代码usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows.Forms;usingHalco......
  • 【总结(三)】单片机重点知识总结记录(串口重定向+按键消抖+延时)
    一.串口重定向串口重定向代码如下注意:要添加头文件include"stdio.h"要勾选微库,即UseMicroLIB/**********重定向************///串口1intfputc(intch,FILE*f){HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,0xffff);returnch;}intfgetc(FILE*f){......
  • 如何用SSCOM测试串口
    本文参考《工业计算机硬件技术支持手册》第2章内容编写。一)准备:先按要求将待测试的串口和工装机上的串口连接起来。连线及其他具体操作内容有点多,请参阅《工业计算机硬件技术支持手册》第2章。二)测试:1,打开SSCOM串口测试软件,选择需要测试的串口。2,点击“打开串口”。注:......
  • Datawhale冬令营第二期!Task 1
    Datawhale冬令营第二期-Task1:动手体验AI辅助编程......