(软件03)单片机串口处理思路,超时接收的方法

本文目录

  •     软件学习前言
  •     代码思路
  •     实操练习

软件学习前言

        最近写了两篇硬件分享文章,要做的一个通过485串口接收指令,从而控制电机转速的内容。里面涉及到了串口的处理,于是便想写一下关于串口处理的相关经验分享,串口也是非常重要的,不管是printf打印log信息,还是涉及到协议通信部分,都是嵌入式里面必不可少的知识点。

        相关配套的硬件思路请参考我之前的硬件篇文章:

        (硬件02)按键+电位器+485控制的电机调速电路实战,上篇icon-default.png?t=N7T8https://blog.csdn.net/BEXZJ/article/details/134784629

        (硬件03)按键+电位器+485控制的电机调速电路实战,下篇icon-default.png?t=N7T8https://blog.csdn.net/BEXZJ/article/details/134791467

        接下来就开始正式地介绍串口的收发处理了,本篇我们要讲的是,串口的超时接收处理。

        老规矩,一张封面图进入今天的分享。

代码思路

        1.串口理解

        说起刚学串口那会,听到的一个顺口溜叫“9681N”,让我印象很深刻,学习完串口知识之后,就理解它的意思了。

        96:代表9600波特率,英文bps,bit per second,就是每秒钟可以发9600位数据,我们知道一个字节是8位的,那么就是9600/8=1200个字节。常见的波特率有9600、115200。

        8:代表数据是8位为一个字节的,其中也可以选择7位的标准ASCII码(0-127)数据,8位的是拓展的ASCII码(0-255)数据。

        1:代表1个停止位,这个与通讯双方进行约定好即可,可选1.5、2位。

        N:代表None,是不进行校验的意思,这个与通讯双方进行约定好即可,可选奇校验、偶检验等。

        2.串口的初始化

        使用单片机对应的库函数,将串口硬件初始化为上述我们理解后的串口格式配置。再配置串口中断,以及在中断服务函数的相关处理。

        3.串口的超时接收

        串口中断收到一个字节的数据,将其存入BUFF数组,并且将数组的位置往后一位以用来存储下一个字节数据,重置一下接收超时的时间,标记我们已经开始接收了,最后清中断标记位。

        在接收完最后一字节数据后。接收时间便不会重置,于是我们可以想到,根据开始接收的标记,结合重置时间的非0自减,当重置时间减少到0时,我们就认为这一帧数据我们已经接收完了,可以对其进行处理了,并且让接收数据的位置回到第一位,其他的标记也重置一下。

        4.数据处理

        根据接收完的标记,我们可以进行数据处理,处理完,再将接收完的标记取消了,便可以开始下一轮的串口接收和处理了。

        5.思路总结

        串口有接收字节时,给它一个等待时间,如20ms(刚刚我们算出9600波特率是1秒钟传1200个字节,那么1ms可以传1.2个字节,我们只需要等1ms就可以了,我们之前设置的是1ms的中断时基处理,为了保险,我们取20ms作为等待时间,再说了,两条报文间只间隔20ms是很少见的,基本不会出现连包情况),最后一个字节接收完了后,再等20ms时间,就判读为接收完成,标记数据帧有效,可以处理数据。

实操练习

        1.头文件的宏定义、结构体、函数申明。

        头文件zj_public.h

#include <at32f4xx.h>
#include <string.h>

#include "at32f4xx_usart.h"


//串口
#define BSP_UART1_TX_GPIO_PIN      		GPIO_Pins_6
#define BSP_UART1_TX_GPIO_AF1_Source	GPIO_PinsSource6
#define BSP_UART1_TX_GPIO_PORT   	  	GPIOB
#define BSP_UART1_RX_GPIO_PIN      		GPIO_Pins_7
#define BSP_UART1_RX_GPIO_AF1_Source	GPIO_PinsSource7
#define BSP_UART1_RX_GPIO_PORT   	  	GPIOB

#define BSP_485_USART   	  			USART1
#define BSP_485_USART_IRQHandler 		USART1_IRQHandler
#define BSP_485_USART_BANDRATE 			115200

#define BSP_485_USART_IRQN  		    USART1_IRQn
#define BSP_485_USART_IRQN_LEVEL  		2
#define BSP_485_USART_IRQN_HANDLER  	USART1_IRQHandler


//时基定时器
#define BSP_TIME_BASE_TIMER  			TMR6
#define BSP_TIME_BASE_TIMER_ARR  		10-1
#define BSP_TIME_BASE_TIMER_PSC  		7200-1
#define BSP_TIME_BASE_IRQN  			TMR6_GLOBAL_IRQn
#define BSP_TIME_BASE_IRQN_LEVEL  		0
#define BSP_TIME_BASE_IRQN_HANDLER  	TMR6_GLOBAL_IRQHandler


//LED灯
#define BSP_LED_RUN_GPIO_PIN      		GPIO_Pins_10
#define BSP_LED_RUN_GPIO_PORT     		GPIOA
#define BSP_LED_RUN_ON      			GPIO_ResetBits(BSP_LED_RUN_GPIO_PORT, BSP_LED_RUN_GPIO_PIN)
#define BSP_LED_RUN_OFF      			GPIO_SetBits(BSP_LED_RUN_GPIO_PORT, BSP_LED_RUN_GPIO_PIN)




//串口数据缓存长度
#define	UART_DATA_BUFF_LEN 				512

extern volatile uint64_t gTimeBase;

typedef struct
{
	uint8_t rx_buff[UART_DATA_BUFF_LEN];
	uint16_t rx_index;
	uint8_t rx_finish_flag;
	uint8_t rx_overtime_flag;
	uint16_t rx_overtime_ms;
	uint16_t rx_len;
	
	uint8_t tx_buff[UART_DATA_BUFF_LEN];
	uint16_t tx_len;
	
}UART_INFO;
extern UART_INFO zj_485_uart;

void zj_app_timebase_process(void);
void zj_app_timebase_1ms_process(void);

void zj_app_uart_process(void);
void zj_app_uart_10ms_process(void);
void zj_app_uart_send(uint8_t *pBuf,uint8_t mLen);


硬件配置函数zj_bsp.c,配置时钟、GPIO复用、串口配置、串口中断、时基配置(软件01篇有介绍)

#include "zj_public.h"

volatile uint64_t gTimeBase = 0;


void RCC_Configuration(void)
{   
    RCC_AHBPeriphClockCmd(RCC_AHBPERIPH_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPERIPH_GPIOB, ENABLE);
	
    RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_USART1, ENABLE);//串口 
	RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_TMR6, ENABLE);//时基
	
	RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_SYSCFGCOMP, ENABLE);//IO复用
}

void GPIO_Configuration(void)
{
    GPIO_InitType GPIO_InitStructure;
    GPIO_StructInit(&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;

	//UART1 管脚配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OutType = GPIO_OutType_PP;
	GPIO_InitStructure.GPIO_Pins = BSP_UART1_TX_GPIO_PIN;
    GPIO_Init(BSP_UART1_TX_GPIO_PORT, &GPIO_InitStructure); 
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Pull = GPIO_Pull_PU;
	GPIO_InitStructure.GPIO_Pins = BSP_UART1_RX_GPIO_PIN;
    GPIO_Init(BSP_UART1_RX_GPIO_PORT, &GPIO_InitStructure); 
	
	//UART1 管脚复用
    GPIO_PinAFConfig(BSP_UART1_TX_GPIO_PORT, BSP_UART1_TX_GPIO_AF1_Source, GPIO_AF_0);
    GPIO_PinAFConfig(BSP_UART1_RX_GPIO_PORT, BSP_UART1_RX_GPIO_AF1_Source, GPIO_AF_0);
	
	//灯
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OutType = GPIO_OutType_PP;
	GPIO_InitStructure.GPIO_Pins = BSP_LED_RUN_GPIO_PIN;
    GPIO_Init(BSP_LED_RUN_GPIO_PORT, &GPIO_InitStructure); 
    BSP_LED_RUN_ON;
}

void NVIC_Configuration(void)
{
    NVIC_InitType NVIC_InitStructure;

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

	// 0 TIMER6_TIMEBASE 
    NVIC_InitStructure.NVIC_IRQChannel = BSP_TIME_BASE_IRQN;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = BSP_TIME_BASE_IRQN_LEVEL;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

	// 1 USART1_485 
    NVIC_InitStructure.NVIC_IRQChannel = BSP_485_USART_IRQN;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = BSP_485_USART_IRQN_LEVEL;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

void EXTI_Configuration(void)
{

}

void TIMER_BASE_Configuration(TMR_Type * mTimer ,u16 mArr, u16 mPsc)
{
    TMR_TimerBaseInitType TIM_TimeBaseStructure;

    //定时器初始化
    TMR_Reset(mTimer);
    TIM_TimeBaseStructure.TMR_Period = mArr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 
    TIM_TimeBaseStructure.TMR_DIV = mPsc; //设置用来作为TIMx时钟频率除数的预分频值
    TIM_TimeBaseStructure.TMR_ClockDivision = TMR_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TMR_CounterMode = TMR_CounterDIR_Up; //TIM向上计数模式
    TMR_TimeBaseInit(mTimer, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
	
    TMR_ClearITPendingBit(mTimer, TMR_INT_Overflow);
    TMR_INTConfig(mTimer, TMR_INT_Overflow, ENABLE);
    TMR_Cmd(mTimer, ENABLE); //使能TIMx 
}

void BSP_TIME_BASE_IRQN_HANDLER(void)
{
  if (TMR_GetINTStatus(BSP_TIME_BASE_TIMER, TMR_INT_Overflow) != RESET)//是更新中断
  {
    TMR_ClearITPendingBit(BSP_TIME_BASE_TIMER, TMR_INT_Overflow); //清除TIM更新中断标志 
		
	zj_app_timebase_1ms_process();
  }
}

void USART_Configuration(USART_Type *USARTx, uint32_t nBandRate)
{
    USART_InitType USART_InitStructure;
 
    USART_InitStructure.USART_BaudRate = nBandRate; //波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位
    USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位
    USART_InitStructure.USART_Parity = USART_Parity_No; //检验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //模式
    USART_Init(USARTx, &USART_InitStructure);
	
	 //使能串口1接收中断
    USART_INTConfig(USARTx, USART_INT_RDNE, ENABLE);	
    //使能串口
    USART_Cmd(USARTx, ENABLE);
}

void BSP_485_USART_IRQN_HANDLER(void)
{
  if (USART_GetITStatus(BSP_485_USART, USART_INT_RDNE) != RESET)
  {
    USART_ClearFlag(BSP_485_USART, USART_FLAG_RDNE);
		
	zj_485_uart.rx_overtime_flag = TRUE;
	zj_485_uart.rx_overtime_ms = 20;
		
	zj_485_uart.rx_buff[zj_485_uart.rx_index++] = USART_ReceiveData(BSP_485_USART);
		
	if(zj_485_uart.rx_index >= UART_DATA_BUFF_LEN)
	zj_485_uart.rx_index = 0;
  }
}


void zj_bsp_config(void)
{
  SystemCoreClockUpdate();

  RCC_Configuration();      
  NVIC_Configuration();
  GPIO_Configuration();
  EXTI_Configuration();

  USART_Configuration(BSP_485_USART,BSP_485_USART_BANDRATE);
  TIMER_BASE_Configuration(BSP_TIME_BASE_TIMER,BSP_TIME_BASE_TIMER_ARR,BSP_TIME_BASE_TIMER_PSC);
}

时基函数处理

#include "zj_public.h"
 
 
uint64_t TimeBaseMs=0,TimeBase10ms=0,TimeBase50ms=0,TimeBase100ms=0,TimeBase500ms=0,TimeBase1000ms=0,TimeBase5000ms=0,TimeBase6000ms=0,TimeBase10000ms=0,TimeBase60000ms=0;
 
static uint64_t Time_GetTimeMs(void)
{
    return gTimeBase;
}
 
static void Process(void)
{
    zj_app_uart_process(); //串口接收处理
}
 
void zj_app_timebase_1ms_process(void)
{
	gTimeBase++;

	if(zj_485_uart.rx_overtime_ms)//串口接收超时时间处理,非零自减
	  zj_485_uart.rx_overtime_ms--; 
}
	
static void TimeProcess_10MS(void)
{
    zj_app_uart_10ms_process();//串口数据帧10ms处理
}
static void TimeProcess_50MS(void)
{
 
}
static void TimeProcess_100MS(void)
{
 
}
static void TimeProcess_500MS(void)
{
}                  
static void TimeProcess_1000MS(void)
{
}
static void TimeProcess_5000MS(void)
{   
	static uint8_t sToggleFlag = 0;
 
	if(sToggleFlag)
	{
		sToggleFlag = FALSE;
		
		BSP_LED_RUN_ON;
	}
	else
	{
		sToggleFlag = TRUE;
	
		BSP_LED_RUN_OFF;
	}
}
static void TimeProcess_10000MS(void)
{   
}
static void TimeProcess_60000MS(void)
{   
}
 

void zj_app_timebase_process(void)
{
	
  Process();
  TimeBaseMs=Time_GetTimeMs();
 
  if(((TimeBaseMs-TimeBase10ms))>9)//10ms
  {
    TimeBase10ms+=10;
    TimeProcess_10MS();
  }
  if(((TimeBaseMs-TimeBase50ms))>49)//50ms
  {
    TimeBase50ms+=50;
    TimeProcess_50MS();
  }
  if(((TimeBaseMs-TimeBase100ms))>99)//100ms
  {
    TimeBase100ms+=100;
    TimeProcess_100MS();
  }
  if(((TimeBaseMs-TimeBase500ms))>499)//500ms
  {
    TimeBase500ms+=500;
    TimeProcess_500MS();
  }
  if(((TimeBaseMs-TimeBase1000ms))>999)//1s
  {
    TimeBase1000ms+=1000;
    TimeProcess_1000MS();
  }
  if(((TimeBaseMs-TimeBase5000ms))>4999)//5s
  {
    TimeBase5000ms+=5000;
    TimeProcess_5000MS();
  }
  if(((TimeBaseMs-TimeBase10000ms))>9999)//10s
  {
    TimeBase10000ms+=10000;
		TimeProcess_10000MS();
  }
  if(((TimeBaseMs-TimeBase60000ms))>59999)//60s
  {
    TimeBase60000ms+=60000;
		TimeProcess_60000MS();
  }
}

应用函数zj_app_uart.c

#include "zj_public.h"


void zj_app_uart_send(uint8_t *pBuf,uint8_t mLen)
{
	while(mLen--)
	{
		USART_SendData(BSP_485_USART,*pBuf++); //库函数,串口发送单个字节数据
		while(USART_GetFlagStatus(BSP_485_USART, USART_FLAG_TRAC) == RESET); 
	}
}

void zj_app_uart_process(void)
{
	if((zj_485_uart.rx_overtime_flag == TRUE) && (zj_485_uart.rx_overtime_ms==0))
	{
		//rx_overtime_flag=TRUE :有超时接收,rx_overtime_ms=0 :最后一个字节接收完成

		zj_485_uart.rx_len =  zj_485_uart.rx_index; //接收到的长度
		zj_485_uart.rx_index = 0;                   //接收BUFF回到首位,为下次接收做准备
		zj_485_uart.rx_overtime_flag = FALSE;       //重置超时接收标记,为下次接收做准备
		
		zj_485_uart.rx_finish_flag = TRUE;          //标记接收完成
	}
}


void zj_app_uart_10ms_process(void)
{
	if(zj_485_uart.rx_finish_flag) //根据接收完成标记处理数据
	{
	  zj_485_uart.rx_finish_flag = FALSE;//重置开始接收标记,为下次处理数据做准备

      zj_app_uart_send(zj_485_uart.rx_buff,zj_485_uart.rx_len); //示例用,收到什么数据发什么数据回去
	}
}

主函数main.c

#include "zj_public.h"
     
int main(void)
{  
  zj_bsp_config();
     
  while(1)
  {
   zj_app_timebase_process();
  } 	
}

        如此,串口的超时接收处理已经介绍完成了,具体的串口应用肯定不是接收什么发送什么回去,比如说一些私有协议解析,要解析报文头、报文尾、数据长度、校验方式等,这些都是比较复杂的,但都是在zj_app_uart_10ms_process(void);中处理就行,通过状态机的方式处理,先判断接收数组中的第一个字节,再跳转到接收数组中的第二个字节....最后判断完最后的数据,对整个接收数字进行memset(&buff,0,buff_len)重置处理。

        预告一下,下一篇软件篇将带大家实操练习一下485 Modbus的项目,包括0x03读多个寄存器指令、0x06写单个寄存器指令、0x10写多个寄存器指令,以及遇到一些关键数据怎么保存到flash中去,如从机地址。。。


小弟感谢大家的关注!

      (利他之心,原创分享)
 

  • 27
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Qt串口接收超时是指在使用Qt库进行串口通信时,当接收数据的时间超过了设定的超时时间,系统无法获取到有效的数据。 在Qt中使用串口通信,可以通过使用QSerialPort类来实现。在接收数据时,可以设置一个超时时间,当超过这个时间还未接收到数据时,系统会触发接收超时信号,开发者可以根据需要进行相关处理。 导致Qt串口接收超时的原因可能有以下几种情况: 1. 串口通信速率设置过慢:如果串口通信的速率设置得过低,数据传输速度会变慢,导致在设置超时时间内无法接收到足够的数据,从而触发接收超时。可以适当调整串口通信的速率,确保数据传输速度与设置超时时间相匹配。 2. 接收数据缓冲区未及时读取:当缓冲区中存在大量未读取的数据时,系统无法继续接收新的数据,导致接收超时。可以使用QSerialPort的readAll函数来读取所有的数据,或者使用QSerialPort的waitForReadyRead函数来等待缓冲区有足够的数据可读取。 3. 设备故障或通信异常:如果串口设备或通信线路存在故障或异常,可能导致数据接收超时。可以检查串口设备、通信线路以及连接的外部设备是否正常工作,并确保数据的正确传输。 总结起来,解决Qt串口接收超时问题的方法是:适当调整串口通信的速率、及时读取数据缓冲区中的数据以及检查设备和通信线路的正常工作情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BEXZJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值