单片机串口通用收发代码

本篇博客大部分是自己收集和整理,借鉴了很多大佬的图片和知识点整理,如有侵权请联系我删除。本博客内容原创,创作不易,转载请注明

串口接收最后应有一定的协议,如发送一帧数据应该有头标志或尾标志,也可两个标志都有。这样在处理数据时既能保证数据的正确接收,也有利于接收完后我们处理数据。串口的配置在这里就不在赘述,这里我以串口2接收中断服务程序函数且接收的数据包含头尾标识为例。

#define Max_BUFF_Len 18
unsigned char Uart2_Buffer[Max_BUFF_Len];
unsigned int Uart2_Rx=0;
void USART2_IRQHandler() 
{if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET) //中断产生 {USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中断标志Uart2_Buffer[Uart2_Rx] = USART_ReceiveData(USART2);     //接收串口1数据到buff缓冲区Uart2_Rx++; if(Uart2_Buffer[Uart2_Rx-1] == 0x0a || Uart2_Rx == Max_BUFF_Len)    //如果接收到尾标识是换行符(或者等于最大接受数就清空重新接收){if(Uart2_Buffer[0] == '+')                      //检测到头标识是我们需要的 {printf("%s\r\n",Uart2_Buffer);        //这里我做打印数据处理Uart2_Rx=0;                                   } else{Uart2_Rx=0;                                   //不是我们需要的数据或者达到最大接收数则开始重新接收}}}
}

数据的头标识为“\n”,即换行符,尾标识为“+”。该函数将串口接收的数据存放在USART_Buffer数组中,然后先判断当前字符是不是尾标识,如果是说明接收完毕,然后再来判断头标识是不是“+”号,如果还是那么就是我们想要的数据,接下来就可以进行相应数据的处理了。但如果不是那么就让Usart2_Rx=0重新接收数据。这样做的有以下好处:

  • 可以接受不定长度的数据,最大接收长度可以通过Max_BUFF_Len来更改

  • 可以接受指定的数据

  • 防止接收的数据使数组越界

这里我的把接受正确数据直接打印出来,也可以通过设置标识位,然后在主函数里面轮询再操作。

以上的接收形式,是中断一次就接收一个字符,这在UCOS等实时内核系统中频繁的中断,非常消耗CPU资源,在有些时候我们需要接收大量数据时且波特率很高的情况下,长时间中断会带来一些额外的问题。所以以DMA形式配合串口的IDLE(空闲中断)来接受数据将会大大的提高CPU的利用率,减少系统资源的消耗。首先还是先看代码。

#define DMA_USART1_RECEIVE_LEN 18
void USART1_IRQHandler(void)                                 
{     u32 temp = 0;  uint16_t i = 0;  if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)  {  USART1->SR;  USART1->DR; //这里我们通过先读SR(状态寄存器)和DR(数据寄存器)来清USART_IT_IDLE标志    DMA_Cmd(DMA1_Channel5,DISABLE);  temp = DMA_USART1_RECEIVE_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); //接收的字符串长度=设置的接收长度-剩余DMA缓存大小 for (i = 0;i < temp;i++)  {  Uart2_Buffer[i] = USART1_RECEIVE_DMABuffer[i];  }  //设置传输数据长度  DMA_SetCurrDataCounter(DMA1_Channel5,DMA_USART1_RECEIVE_LEN);  //打开DMA  DMA_Cmd(DMA1_Channel5,ENABLE);  }        
} 

之前的串口中断是一个一个字符的接收,现在改为串口空闲中断,就是一帧数据过来才中断进入一次。而且接收的数据时候是DMA来搬运到我们指定的缓冲区(也就是程序中的USART1_RECEIVE_DMABuffer数组),是不占用CPU时间资源的。

最后在讲下DMA的发送:

#define DMA_USART1_SEND_LEN 64
void DMA_SEND_EN(void)
{DMA_Cmd(DMA1_Channel4, DISABLE);      DMA_SetCurrDataCounter(DMA1_Channel4,DMA_USART1_SEND_LEN);   DMA_Cmd(DMA1_Channel4, ENABLE);
}

这里需要注意下DMA_Cmd(DMA1_Channel4,

DISABLE)函数需要在设置传输大小之前调用一下,否则不会重新启动DMA发送。

有了以上的接收方式,对一般的串口数据处理是没有问题的了。下面再讲一下,在ucosiii中我使用信号量+消息队列+储存管理的形式来处理我们的串口数据。先来说一下这种方式对比其他方式的一些优缺点。

一般对串口的处理形式是"生产者"和"消费者"的模式,即本次接收的数据要马上处理,否则当数据大量涌进的时候,就来不及"消费"掉生产者(串口接收中断)的数据,那么就会丢失本次的数据处理。所以使用队列就能够很方便的解决这个问题。

在下面的程序中,对数据的处理是先接受,在处理,如果在处理的过程中,有串口中断接受数据,那么就把它依次放在队列中,队列的特征是先进先出,在串口中就是先处理先接受的数据,所以根据生产和消费的速度,定义不同大小的消息队列缓冲区就可以了。缺点就是太占用系统资源,一般51单片机是没可能了。下面是从我做的项目中截取过来的程序

OS_MSG_SIZE  Usart1_Rx_cnt;          //字节大小计数值
unsigned char Usart1_data;           //每次中断接收的数据
unsigned char* Usart1_Rx_Ptr;        //储存管理分配内存的首地址的指针
unsigned char* Usart1_Rx_Ptr1;       //储存首地址的指针void USART1_IRQHandler() 
{OS_ERR err;OSIntEnter();if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET) //中断产生 {   USART_ClearFlag(USART1, USART_FLAG_RXNE);     //清除中断标志Usart1_data = USART_ReceiveData(USART1);     //接收串口1数据到buff缓冲区if(Usart1_data =='+')                     //接收到数据头标识{
//   OSSemPend((OS_SEM*  )&SEM_IAR_UART,  //这里请求信号量是为了保证分配的存储区,但一般来说不允许
//   (OS_TICK  )0,                   //在终端服务函数中调用信号量请求但因为
//   (OS_OPT   )OS_OPT_PEND_NON_BLOCKING,//我OPT参数设置为非阻塞,所以可以这么写
//   (CPU_TS*  )0,
//   (OS_ERR*  )&err); 
//   if(err==OS_ERR_PEND_WOULD_BLOCK)     //检测到当前信号量不可用
//   {
//     printf("error");
//   }    Usart1_Rx_Ptr=(unsigned char*) OSMemGet((OS_MEM*)&UART1_MemPool,&err);//分配存储区Usart1_Rx_Ptr1=Usart1_Rx_Ptr;          //储存存储区的首地址}if(Usart1_data == 0x0a )       //接收到尾标志{                    *Usart1_Rx_Ptr++=Usart1_data;Usart1_Rx_cnt++;                         //字节大小增加OSTaskQPost((OS_TCB    *  )&Task1_TaskTCB,(void      *  )Usart1_Rx_Ptr1,    //发送存储区首地址到消息队列(OS_MSG_SIZE  )Usart1_Rx_cnt,(OS_OPT       )OS_OPT_POST_FIFO,  //先进先出,也可设置为后进先出,再有地方很有用(OS_ERR    *  )&err);Usart1_Rx_Ptr=NULL;          //将指针指向为空,防止修改Usart1_Rx_cnt=0;      //字节大小计数清零}else{*Usart1_Rx_Ptr=Usart1_data; //储存接收到的数据Usart1_Rx_Ptr++;Usart1_Rx_cnt++;} }    OSIntExit();
}

上面被注释掉的代码为我是为了防止当分区中没有空闲的存储块时加入信号量,打印出报警信息。当然我们也可以将存储块直接设置大一点,但是还是无法避免当没有可有存储块时会程序会崩溃现象。希望懂的朋友能告知下~。

下面是串口数据处理任务,这里删去了其他代码,只把他打印出来了而已。

void task1_task(void *p_arg)
{OS_ERR err;OS_MSG_SIZE Usart1_Data_size;u8 *p;while(1){p=(u8*)OSTaskQPend((OS_TICK  )0, //请求消息队列,获得储存区首地址(OS_OPT    )OS_OPT_PEND_BLOCKING,(OS_MSG_SIZE* )&Usart1_Data_size,(CPU_TS*   )0,(OS_ERR*   )&err);printf("%s\r\n",p);        //打印数据delay_ms(100);OSMemPut((OS_MEM* )&UART1_MemPool,    //释放储存区(void*   )p,(OS_ERR*  )&err);OSSemPost((OS_SEM* )&SEM_IAR_UART,    //释放信号量(OS_OPT  )OS_OPT_POST_NO_SCHED,(OS_ERR* )&err);OSTimeDlyHMSM(0,0,1,500,OS_OPT_TIME_PERIODIC,&err);     }
}

串口发送数据

1、串口发送数据最直接的方式就是标准调用库函数 。

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

第一个参数是发送的串口号,第二个参数是要发送的数据了。但是用过的朋友应该觉得不好用,一次只能发送单个字符,所以我们有必要根据这个函数加以扩展:

void Send_data(u8 *s)
{while(*s!='\0'){ while(USART_GetFlagStatus(USART1,USART_FLAG_TC )==RESET); USART_SendData(USART1,*s);s++;}
}

以上程序的形参就是我们调用该函数时要发送的字符串,这里通过循环调用USART_SendData来一 一发送我们的字符串。

while(USART_GetFlagStatus(USART1,USART_FLAG_TC )==RESET);

这句话有必要加,他是用于检查串口是否发送完成的标志,如果不加这句话会发生数据丢失的情况。这个函数只能用于串口1发送。有些时候根据需要,要用到多个串口发送那么就还需要改进这个程序。如下:

void Send_data(USART_TypeDef * USARTx,u8 *s)
{while(*s!='\0'){ while(USART_GetFlagStatus(USARTx,USART_FLAG_TC )==RESET); USART_SendData(USARTx,*s);s++;}
}

这样就可实现任意的串口发送。但有一点,我在使用实时操作系统的时候(如UCOS,Freertos等),需考虑函数重入的问题。当然也可以简单的实现把该函数复制一下,然后修改串口号也可以避免该问题。然而这个函数不能像printf那样传递多个参数,所以还可以在改进,最终程序如下:

void USART_printf ( USART_TypeDef * USARTx, char * Data, ... )
{const char *s;int d;   char buf[16];va_list ap;va_start(ap, Data);while ( * Data != 0 )     // 判断是否到达字符串结束符{                              if ( * Data == 0x5c )  //'\'{           switch ( *++Data ){case 'r':                 //回车符USART_SendData(USARTx, 0x0d);Data ++;break;case 'n':                 //换行符USART_SendData(USARTx, 0x0a); Data ++;break;default:Data ++;break;}    }else if ( * Data == '%'){           //switch ( *++Data ){    case 's':            //字符串s = va_arg(ap, const char *);for ( ; *s; s++) {USART_SendData(USARTx,*s);while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );}Data++;break;case 'd':   //十进制d = va_arg(ap, int);itoa(d, buf, 10);for (s = buf; *s; s++) {USART_SendData(USARTx,*s);while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );}Data++;break;default:Data++;break;}   }else USART_SendData(USARTx, *Data++);while ( USART_GetFlagStatus ( USARTx, USART_FLAG_TXE ) == RESET );}
}

该函数就可以像printf使用可变参数,方便很多。通过观察函数但这个函数只支持了%d,%s的参数,想要支持更多,可以仿照printf的函数写法加以补充。

2、 直接使用printf函数

很多朋友都知道想要STM32要直接使用printf不行的。需要加上以下的重映射函数:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/213484.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Pytorch从零开始构建GRU

门控循环单元 (GRU) 是 LSTM 的更新版本。让我们揭开这个网络的面纱并探索这两个兄弟姐妹之间的差异。 您听说过 GRU 吗&#xff1f;门控循环单元&#xff08;GRU&#xff09;是更流行的长短期记忆&#xff08;LSTM&#xff09;网络的弟弟&#xff0c;也是循环神经网络&#x…

【开源】基于Vue.js的用户画像活动推荐系统

项目编号&#xff1a; S 061 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S061&#xff0c;文末获取源码。} 项目编号&#xff1a;S061&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 兴趣标签模块2.3 活…

练习七-在Verilog中使用任务task

在Verilog中使用任务task 1&#xff0c;任务目的2&#xff0c;RTL代码&#xff0c;交换3&#xff0c;测试代码4&#xff0c;波形显示 1&#xff0c;任务目的 &#xff08;1&#xff09;掌握任务在verilog模块设计中的应用&#xff1b; &#xff08;2&#xff09;学会在电平敏感…

今日祝福语道一声早安,惟愿你时时安好,天天幸福!

1、托太阳公公轻轻的叩醒你沉睡的心灵&#xff0c;将暖暖的阳光洒满你全身&#xff0c;愿你精神抖擞信心百倍的迎接新的一天&#xff0c;朋友&#xff0c;早安&#xff01; 2、一天中&#xff0c;第一个叫醒你的是闹钟&#xff0c;第一眼看到的是阳光&#xff0c;第一件要做的事…

目标检测框存在内嵌情况分析与解决

这里写目录标题 问题描述原因分析与解决方法&#xff1a;后续及思考参考文档 问题描述 目标检测模型输出的检测框存在内嵌情况。 原因分析与解决方法&#xff1a; 根据经验&#xff0c;第一感觉是后处理nms部分出了问题。来看下对应的代码&#xff1a; static float CalcIou…

牛客 HJ106 字符逆序 golang实现

牛客题目算法连接 题目 golang 实现 package mainimport ("fmt""bufio""os" )func main() {str, _ : bufio.NewReader(os.Stdin).ReadString(\n)if len(str) 0 {return } else {newstr:""strLen:len(str)-1for i:strLen;i>0;i-…

Java零基础——Linux篇

1.【熟悉】认识Linux 1.1 什么是操作系统 1.2 现实生活中的操作系统 1.2.1 Win10 1.2.2 Mac 1.2.3 Android(Linux) 1.2.4 iOS(Unix) 1.3 操作系统的发展史 1.3.1 Unix 1965年之前的时候&#xff0c;电脑并不像现在一样普遍&#xff0c;它可不是一般人能碰的起的&#xff0c…

AI“胡说八道”?怎么解?

原创 | 文 BFT机器人 01 引言 近年来&#xff0c;人工智能产业迅猛发展&#xff0c;大型语言模型GPT-4发展势头强劲&#xff0c;OpenAI推出ChatGPT、微软推出Bing、马斯克推出“最好的聊天机器人Grok”……科技巨头纷纷入局AI领域&#xff0c;引入人工智能作为办公工具的行业…

LeetCode.88合并两个有序数组

LeetCode.88合并两个有序数组 1.问题描述2.解题思路3.代码 1.问题描述 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同…

rocketMQ5.0顺序消息golang接入

本人理解&#xff0c;顺序消息如果不分消息组&#xff0c;那么会影响并行处理速度&#xff0c;所以尽量消息组分的散一些 首先上要求&#xff0c;官方文档如下&#xff1a; 总结&#xff1a; 1.必须同一个消息组&#xff0c;消息组和消费组不是一个概念&#xff0c;不要混 2.必…

Javaweb之前后台分离开发介绍的详细解析

2.1 前后台分离开发介绍 在之前的课程中&#xff0c;我们介绍过&#xff0c;前端开发有2种方式&#xff1a;前后台混合开发和前后台分离开发。 前后台混合开发&#xff0c;顾名思义就是前台后台代码混在一起开发&#xff0c;如下图所示&#xff1a; 这种开发模式有如下缺点&a…

java 手机商城免费搭建+电商源码+小程序+三级分销+SAAS云平台

【SAAS云平台】打造全行业全渠道全场景的SaaS产品&#xff0c;为店铺经营场景提供一体化解决方案&#xff1b;门店经营区域化、网店经营一体化&#xff0c;本地化、全方位、一站式服务&#xff0c;为多门店提供统一运营解决方案&#xff1b;提供丰富多样的营销玩法覆盖所有经营…