前言
工作中经常会遇到需要进行串口进行数据收发的环节。在此总结下串口在接收数据时,常见的一些处理方法,框架、逻辑,目的是以后在做类似的串口数据收发时,能够有所启发。
1、普通的发送和接收
1.1 普通串口初始化
串口初始化函数代码如下:
oid UART_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2|RCC_APB1Periph_USART3|RCC_APB1Periph_UART4|RCC_APB1Periph_UART5, ENABLE); // 多个串口初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
USART_InitStructure.USART_BaudRate = OTA_BoundRate;
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(OTA_USARTx, &USART_InitStructure);
USART_Cmd(OTA_USARTx, ENABLE); // 使能 USART1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO |RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOF|RCC_APB2Periph_GPIOG, ENABLE); // GPIO时钟初始化
GPIO_InitStructure.GPIO_Pin = OTA_USART_TX_Pinx; // 配置 USART1 Tx (PA.02) 作为功能引脚并复用推挽输出模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(OTA_USART_TX_GPIOx, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = OTA_USART_RX_Pinx; //配置 USART1 Rx (PA.3) 作为功能引脚并是浮空输入模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(OTA_USART_RX_GPIOx, &GPIO_InitStructure);
}
/* 关于GPIO的四种输出模式在此总结下用法:
1、普通推挽输出(GPIO_Mode_Out_PP):
使用场合:一般用在0V和3.3V的场合。线路经过两个P_MOS 和N_MOS 管,负责上拉和下拉电流。
使用方法:直接使用
输出电平:推挽输出的低电平是0V,高电平是3.3V
2、普通开漏输出(GPIO_Mode_Out_OD):
使用场合:一般用在电平不匹配的场合,如需要输出5V的高电平。
使用方法:就需要再外部接一个上拉电阻,电源为5V,把GPIO设置为开漏模式, 当输出高组态时,由上拉电阻和电源向外输出5V的电压。
输出电平:在开漏输出模式时,如果输出为0,低电平,则使N_MOS 导通,使输 出接地。若控制输出为1(无法直接输出高电平),则既不输出高电平 也不输出低电平,为高组态。为正常使用,必须在外部接一个上拉电 阻。
特性: 它具“线与”特性,即很多个开漏模式 引脚连接到一起时,只有当所有 引脚都输出高阻态,才由上拉电阻提供高电平,此高电平的电压为外部 上拉电阻所接的电源的电压。若其中一个引脚为低电平,那线路就相当 于短路接地,使得整条线路都为低电平,0 伏。
3、复用推挽输出(GPIO_Mode_AF_PP):用作串口的输出。
4、复用开漏输出(GPIO_Mode_AF_OD):用在IIC。
*/
1.2 普通串口接收
串口数据接收逻辑
代码如下:
/*******************************************************************************
函数名称:SerialKeyPressed
函数说明:测试超级终端是否有按键按下
输入参数:key:按键
输出参数:无
返回参数:1:正确
0:错误
*******************************************************************************/
uint32_t SerialKeyPressed(uint8_t *key)
{
if ( USART_GetFlagStatus(OTA_USARTx, USART_FLAG_RXNE) != RESET)
{
*key = (uint8_t)OTA_USARTx->DR;
return 1;
}
else
{
return 0;
}
}
// 串口接收数据超时判断
/*******************************************************************************
函数名称:Receive_Byte
函数说明:从发送端接收一个字节
输入参数:c: 接收字符
timeout: 超时时间
输出参数:无
返回参数:接收的结果
0:成功接收
1:时间超时
*******************************************************************************/
static int32_t Receive_Byte (uint8_t *c, uint32_t timeout)
{
while (timeout-- > 0)
{
if (SerialKeyPressed(c) == 1)
{
return 0;
}
}
return -1;
}
// 根据协议进行一帧数据包的逻辑接收
```java
/*******************************************************************************
函数名称:Receive_Packet
函数说明:从发送端接收一个数据包
输入参数:data :数据指针
length:数据区的长度,128或者1024个字节
timeout :超时时间
输出参数:无
返回参数:接收的结果
0: 正常返回
-1: 超时
1: 用户取消
-3:包号或者CRC校验错误
*******************************************************************************/
static int32_t Receive_Packet (uint8_t *data, int32_t *length, uint32_t timeout)
{
uint16_t i, packet_size,computedcrc;
uint8_t c;
*length = 0;
if (Receive_Byte(&c, timeout) != 0) // 接收超时
{
return -1;
}
switch (c) // switch语句是对OTA数据帧帧头内容进行解析处理。
{
case OTA_Frame_header_1:
case OTA_Frame_header_2:
{
*length = -2;
return 0;
}
case SOH: // 接收到发送端头帧数据,则执行break,准备执行数据内容接收
{
packet_size = PACKET_SIZE;
break;
}
case STX: // 接收到发送端头帧数据,则执行break,准备执行数据内容接收
{
packet_size = PACKET_1K_SIZE;
break;
}
case EOT: // 发送端发送结束传输指令
{
return 0;
}
case CA:
{
if ((Receive_Byte(&c, timeout) == 0) && (c == CA))
{
*length = -1;
return 0;
}
else
{
return -1;
}
}
case ABORT1:
case ABORT2:
{
return 1;
}
default:
{
return -1;
}
}
*data = c;
for (i = 1; i < (packet_size + PACKET_OVERHEAD); i ++)
{
if (Receive_Byte(data + i, timeout) != 0) // 开始数据帧中的数据区数据。
{
return -1;
}
}
if (data[PACKET_SEQNO_INDEX] != ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff)) // 判断数据块儿编号和编号反码
{
return -3;
}
computedcrc = Cal_CRC16(&data[PACKET_HEADER], (uint32_t)packet_size);//计算CRC16码
if (computedcrc != (uint16_t)((data[packet_size+3]<<8) | data[packet_size+4]))//检测CRC16校验码
{
return -3;
}
*length = packet_size;
return 0;
}
1.2 普通串口发送
/*******************************************************************************
函数名称:SerialPutChar
函数说明:串口发送一个字符串
输入参数:*s:需发送的字符串
输出参数:无
返回参数:无
*******************************************************************************/
extern unsigned char Send_Buff[10];
void Serial_PutString(uint8_t *s)
{
while (*s != '\0')
{
OTA_USARTx->DR= *s++;
while((OTA_USARTx->SR&0X40)==0);
}
}
// 调用方法
void Yomdem_Response(char c)
{
unsigned char Send_Buff_0[10];
Send_Buff_0[0] = 0xfa;
Send_Buff_0[1] = 0xfb;
Send_Buff_0[2] = c;
Send_Buff_0[3] = '\0';
SerialPutString(Send_Buff_0);
}
// 第二种封装方式
void USART3_Send_str(unsigned char data[],unsigned char length)
{
char d;
for(d=0;d<length;d++)
{
USART_ClearFlag(USART3,USART_FLAG_TC);
USART_SendData(USART3,data[d]);
while(USART_GetFlagStatus(USART3,USART_FLAG_TC) != SET);
}
}
2、串口空闲中断+DMA
2.1 初始化
void USART3_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 下拉输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
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(USART3, &USART_InitStructure);
USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
}
//dma初始化
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA时钟
DMA_DeInit(DMA_CHx);
DMA_InitStructure.DMA_PeripheralBaseAddr = cpar;//外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向外设到内存
DMA_InitStructure.DMA_BufferSize = cndtr; //传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA_CHx, &DMA_InitStructure);
}
void USART3_Init(void)
{
USART3_Config();
MYDMA_Config(DMA1_Channel3,(u32)&USART3->DR,(u32)APP_ComBuf,2000);//传输方向设置为USART1->DR到USART_RX_BUF 传输大小为2000字节
USART_DMACmd(USART3,USART_DMAReq_Rx,ENABLE);//允许DMA请求
DMA_Cmd(DMA1_Channel3,ENABLE);
USART_Cmd(USART3, ENABLE);
}
2.2 串口接收
串口空闲中断接收处理过程分析:(经典)
void USART3_IRQHandler(void)
{
volatile unsigned char temp;
unsigned int i;
//--- 通知ucosii,中断服务子程序开始了 ------------
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL();
OSIntNesting++;
OS_EXIT_CRITICAL();
//-------------------------------------------------
//
// OSIntEnter(); //进入中断
if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)
{
DMA_Cmd(DMA1_Channel3,DISABLE);
APP_iConter=USART3->DR;//软件清空空闲中断标志位
APP_iConter=USART3->SR;
APP_iConter=2000-DMA_GetCurrDataCounter(DMA1_Channel3);//获取当前接收的数据量
APP_Transfer_Num += APP_iConter; // 数据量计算做加法判断处理
if (((APP_ComBuf[0] == OTA_Frame_header_1) && (APP_ComBuf[1] == OTA_Frame_header_2)) || ((APP_ComBuf[0] == Pack_Head) && (APP_iConter>=5))) // 根据帧头、数据帧长度综合起来进行第一层判断是否为完整的数据帧。
{
DMA1_Channel3->CNDTR=2000;//重新设置传输量为2000 ,时机处理要得当,这个处理会导致数据从缓冲区的起始位置去存储数据。
if (APP_Transfer_Num < 1100) // 进行第二层判断,数据帧的长度最大不可能超过1100
{
for (i = 0; i < APP_Transfer_Num; i++)
{
APP_TemporaryReceiveData[i] = APP_ComBuf[i]; // 用新的缓冲区去接收数据,最好用volatile修饰
}
Pack_Num_APP = APP_Transfer_Num;
memset(APP_ComBuf,0,2000);
OSQPost(AppReceiveMessage,APP_TemporaryReceiveData);
}
APP_Transfer_Num = 0;
}
else if ((APP_ComBuf[0] != OTA_Frame_header_1) && (APP_ComBuf[0] != Pack_Head))
{
DMA1_Channel3->CNDTR=2000;//重新设置传输量为2000
APP_Transfer_Num=0;
}
else if((APP_ComBuf[0] == OTA_Frame_header_1) && (APP_ComBuf[1] != OTA_Frame_header_2))
{
DMA1_Channel3->CNDTR=2000;//重新设置传输量为2000
APP_Transfer_Num=0;
}
DMA_Cmd(DMA1_Channel3,ENABLE); // 使能下次的DMA
}
NVIC_Clear(USART3);
OSIntExit(); //通知ucosii,中断服务程序执行完毕
}
3、其他比较好的博客转载分享
以下链接中,个人感觉比较好的一些思路和逻辑,有必要贴出来分享。
串口接收完整一帧数据包的3种方法
串口中怎样接收一个完整数据包的解析