本文目录

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

软件学习前言

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

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

(硬件02)按键+电位器+485控制的电机调速电路实战,上篇https://blog.csdn.net/BEXZJ/article/details/134784629

(硬件03)按键+电位器+485控制的电机调速电路实战,下篇https://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

#include

#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中去,如从机地址。。。

小弟感谢大家的关注!

(利他之心,原创分享)