本文目录
- 软件学习前言
- 代码思路
- 实操练习
软件学习前言
最近写了两篇硬件分享文章,要做的一个通过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 <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中去,如从机地址。。。文章来源:https://www.toymoban.com/news/detail-789819.html
小弟感谢大家的关注!
(利他之心,原创分享)
文章来源地址https://www.toymoban.com/news/detail-789819.html
到了这里,关于(软件03)单片机串口处理思路,超时接收的方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!