找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3091|回复: 1
收起左侧

Stm32串口的无阻塞收发调试

[复制链接]
ID:344395 发表于 2019-9-6 18:09 | 显示全部楼层 |阅读模式
Stm32 芯片目前具有一定的用户,例程也很多。大多数仅仅是“ demo”的作用,往往只注重于功能的实现,意思就是说,看,这个usart 功能这样就可以实现了。但应用于一个运行稳定的产品还需要很多技巧。

串口应用大概可以说在mcu 应用来说是最广的,其主要作用就是收发,它在应用中又表现出两种类型,客户端( client )和服务端( server)。通信就是人与人聊天,人与人聊天的表现把人分为为问者和答者,或者说采访者和受访者。也可以说主从结构。先说客户端,通常有两者做法,一是无阻塞式,一种是有阻塞式。前者是发出命令后,做一全局变量标识,记下我前一次发的什么命令,然后等待收数据的消息,在消息处理过程中根据命令标识来对收到的数据进行相应处理。后者有阻塞式做法就是一个函数,发出命令后等待收到正确数据并解析。在等待的过程中可以用TickCount 做timeout,超过一定时间还没收到数据可以退出返回false,解决死等的问题。因为操作系统有多线程,前者可以在主线程中写出稳定的通信,后者在线程中应用比较多。
服务端的做法思想和客户端不一样,是受访者,要做到有求必答。服务端重中之重的还是稳定第一。当然,收到正确的请求后到应答可以允许有一定的“思考”时间, 。我通常是这样做,做一全局变量用于放接收到的数据,接收的地方只管往接收容器里装,发现满了就全部倒掉再装,其它不用管。在主循环中解析收到的帧数据做出应答,解析过程中对不符合协议的数据可以根据协议做出错误回应或不做任何反应。完成后把收容器里的数据清空。下面的代码实现了在stm32 的dma 的usart 的收发,体现了上述的一些思想。我用了systick 实现了超时的概念,有说uart 的idle (总线空闲)本身就是一个超时,我又想例如和一个口吃者“聊天“用它做超时是不是不太友善? 这一点我没做深究。
代码中含有485 方向的控制,一开始是在dma 的中断控制的,但总是少两个字节,我就在发送的时候多发2 个字节,也可以用。请读者选择吧。

下面是.h 文件输出函数
void USART1_GPIOConfig(void);
void USART1_Config(void);
void USART1_NVICConfig(void);
void USART1_SendString( char* data); // 无阻塞发送字符串函数
void USART1_SendData(u8* data,u8 len);// 无阻塞发送函数
下面是.c 文件
#define DMA_USART1_BUFLEN 32 // 保存DMA 每次数据传送的长度
u8 USART1_SendBuff[DMA_USART1_BUFLEN];
u8 USART1_TX_Finish=Co_TRUE; // USART 发送完成标志量
u8 USART1_ReceivBuff[DMA_USART1_BUFLEN];
void USART1_GPIOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; /* 复用推挽输出模式*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; /* 输出最大频率为50MHz */
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 配置USART1 Rx (PA10) */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; /* 浮空输入模式*/

GPIO_Init(GPIOA, &GPIO_InitStruct);
/// 485 dir
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init( GPIOA, &GPIO_InitStruct);
GPIO_ResetBits( GPIOA, GPIO_Pin_8);// dir
}
void USART1_NVICConfig(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel4_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
NVIC_InitStruct.NVIC_IRQChannelCmd =ENABLE;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel=DMA1_Channel5_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
void USART1_Config(void)
{
USART_InitTypeDef USART_InitStruct;
DMA_InitTypeDef DMA_InitStructure;
/* USART and USART2 configured as follow:
- BaudRate = 9600 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled */
USART_InitStruct.USART_BaudRate = 9600;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStruct);
USART_ITConfig(USART1,USART_IT_IDLE, ENABLE);// 空闲中断
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
USART_Cmd(USART1, ENABLE);
USART_ClearFlag(USART1,USART_FLAG_TC);///*CPU 的小缺陷:串口配置好,如果直接Send,则
第1 个字节发送不出去?如下语句解决第1 个字节无法正确发送出去的问题*/
////////////////////////////////// 发送dma////////////////////////////////////////////////
DMA_DeInit(DMA1_Channel4); //将DMA 的通道1 寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&((USART_TypeDef*)USART1)->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART1_SendBuff; //DMA 内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,
DMA_InitStructure.DMA_BufferSize = DMA_USART1_BUFLEN; //DMA 通道的DMA 缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8 位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 数据宽度为8 位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA 通道x 拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA 通道x 没有设置为内存到内存传输
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel4,DISABLE);
DMA_ITConfig(DMA1_Channel4,DMA_IT_TE,ENABLE);// 错误中断
DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);// 完成中断
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
//接收dma///////////////////////////////////////////////
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)(&((USART_TypeDef*)USART1)->DR);
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)USART1_ReceivBuff;
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize=DMA_USART1_BUFLEN;
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_Circular;
DMA_InitStructure.DMA_Priority=DMA_Priority_High;
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;
DMA_Init(DMA1_Channel5,&DMA_InitStructure);
DMA_ITConfig(DMA1_Channel5,DMA_IT_TC,ENABLE);

DMA_ITConfig(DMA1_Channel5,DMA_IT_TE,ENABLE);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
DMA_Cmd(DMA1_Channel5,ENABLE);
}
void DMA1_Channel4_IRQHandler(void)
{
DMA_ClearITPendingBit(DMA1_IT_TC4);
DMA_ClearITPendingBit(DMA1_IT_TE4);
DMA_Cmd(DMA1_Channel4,DISABLE);
USART1_TX_Finish=Co_TRUE;
}
void DMA1_Channel5_IRQHandler(void){
DMA_ClearITPendingBit(DMA1_IT_TC5);
DMA_ClearITPendingBit(DMA1_IT_TE5);
DMA_Cmd(DMA1_Channel5,DISABLE);
pc_AppendData(USART1_ReceivBuff,DMA_USART1_BUFLEN);
DMA1_Channel5->CNDTR=DMA_USART1_BUFLEN;// 重装填
DMA_Cmd(DMA1_Channel5,ENABLE);
}
void USART1_IRQHandler(void)
{
u16 alen;
if(USART_GetITStatus(USART1,USART_IT_IDLE)!=RESET){// 如果为空闲总线中断?
USART_ClearITPendingBit(USART1,USART_IT_IDLE);
DMA_Cmd(DMA1_Channel5,DISABLE);// 关闭DMA, 防止处理其间有数据
alen=DMA_USART1_BUFLEN-DMA_GetCurrDataCounter(DMA1_Channel5);
if(alen>0){
pc_AppendData(USART1_ReceivBuff,alen);
}
DMA_ClearFlag(DMA1_FLAG_GL5|DMA1_FLAG_TC5|DMA1_FLAG_TE5|DMA1_FLAG_HT5);// 清标
志?
DMA1_Channel5->CNDTR=DMA_USART1_BUFLEN;// 重装填?
DMA_Cmd(DMA1_Channel5,ENABLE);// 处理完,重开DMA
//读SR 后读DR 清除Idle
alen=USART1->SR;
alen=USART1->DR;
}
if(USART_GetITStatus(USART1,USART_IT_PE|USART_IT_FE|USART_IT_NE)!=RESET){// 出错
USART_ClearITPendingBit(USART1,USART_IT_PE|USART_IT_FE|USART_IT_NE);
}

//发送中断处理
if(USART_GetITStatus(USART1, USART_IT_TC) != RESET){
USART_ClearITPendingBit(USART1, USART_IT_TC); //清除中断标志
GPIO_ResetBits( GPIOA, GPIO_Pin_8);
}
}
void USART1_SendString(char* data)
{
USART1_SendData((u8*)data,strlen(data));
}
void USART1_SendData(u8* data,u8 len)
{
memcpy(USART1_SendBuff,data,len);
while(USART1_TX_Finish==0);
DMA_Cmd(DMA1_Channel4,DISABLE);
GPIO_SetBits( GPIOA, GPIO_Pin_8);
DMA1_Channel4->CNDTR=len;
USART1_TX_Finish=0;
DMA_Cmd(DMA1_Channel4,ENABLE);
}
上述代码实现了stm32 的串口1 的收发功能。应用说明如下:
实现windows 操作中的TickCount 方法:
U32 sysTickCount;
void SysTick_Handler(void)
{
sysTickCount++;
}
实现接收容器:
#define PC_LEN 48
u8 pc_receivdata[IDCPC_LEN]; // 用来乘放接收数据
u8 pc_count=0; // 接收字节数
U32 pc_lastrec; // 最后一次接收的tickcount 值
void pc_AppendData(u8* value,u8 alen){
if((pc_count+alen)>PC_LEN)
pc_count=0;
if(alen<=PC_LEN){
memcpy(&pc_receivdata[pc_count],value,alen);
pc_count+=alen;
pc_lastrec = sysTickCount;
}
}
然后在主循环中检测接收数据:

u32 tvalue;
if(pc_count>0){
tvalue = sysTickCount - pc_lastrec;
if(tvalue>3){ // 这里3 可以根据systickcount 的频率修改适当,可以取20ms
⋯⋯⋯ ..//分析帧数据
USART4_SendData(..); 调用无阻塞发送应答
idcpc_count=0; // 清空容器
}
}

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:731155 发表于 2020-5-12 23:28 | 显示全部楼层
感谢分享,但好像没有看到应答,超时请求重发
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表