硬件编程:STM32串口发送数据和接收数据方式总结!

串口发送数据

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不行的。需要加上以下的重映射函数:

如果不想添加以上代码,也可以勾选以下的Use MicroLI选项来支持printf函数使用:

相关笔记:串口打印知多少?

串口接收数据

串口接收最后应有一定的协议,如发送一帧数据应该有头标志或尾标志,也可两个标志都有。

这样在处理数据时既能能保证数据的正确接收,也有利于接收完后我们处理数据。串口的配置在这里就不再赘述,这里以串口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时间资源的。

关于IDLE中断可查看:STM32串口空闲中断接收不定长数据(DMA方式)

最后在讲下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);     }
}

END

作者:可以吃的鱼

原文:https://blog.csdn.net/qq_35281599


版权归原作者所有,如有侵权,请联系删除。

IT技术分享社区

个人博客网站:https://programmerblog.xyz

文章推荐程序员效率:画流程图常用的工具程序员效率:整理常用的在线笔记软件远程办公:常用的远程协助软件,你都知道吗?51单片机程序下载、ISP及串口基础知识硬件:断路器、接触器、继电器基础知识

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

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

相关文章

Java编程题修院子,了解java虚拟机—JVM相关参数设置(2)

1. JVM相关参数设置JVM相关配置-XX:PrintGC两次次YoungGC&#xff0c;两次FullGC。-XX:PrintGCDetails打印GC时的内存&#xff0c;并且在程序结束时打印堆内存使用情况-XX:PrintHeapAtGC每次GC时会分别打印回收前与回收后堆信息-XX:PrintGCTimeStamps选择打印GC的方式后&…

此异常最初是在此调用堆栈中引发的:_【8】进大厂必须掌握的面试题Java面试异常和线程...

点击上方“全栈程序员社区”&#xff0c;星标公众号重磅干货&#xff0c;第一时间送达Q1。错误和异常有什么区别&#xff1f;错误是在运行时发生的不可恢复的情况。如OutOfMemory错误。这些JVM错误无法在运行时修复。尽管可以在catch块中捕获错误&#xff0c;但是应用程序的执行…

高仿带感魔性病毒源码+成品(最近很火的)

高仿带感魔性病毒源码成品&#xff08;最近很火的&#xff09;娱乐使用。没破坏性 会改壁纸和打乱桌面图标顺序 自己改回来就好 演示地址&#xff1a; 下载地址&#xff1a;链接: http://pan.baidu.com/s/1dF2ZlU5 密码: m95p转载于:https://www.cnblogs.com/blogwy/p/5804711.…

数据库设计:数据库设计的基本步骤介绍

数据库设计主要包括用户需求分析、概念结构设计、逻辑结构设计、物理结构设计、数据库实施阶段、数据库运行和维护阶段等六个阶段。1、用户需求分析 数据库设计人员采用相应的辅助工具对应用对象的功能、性能、限制等要求进行科学实际的分析。2、概念结构设计 概念结构设计主要…

坎蒂雷赋权法 matlab,干货 | 利用MATLAB实现FMCW雷达中的常用角度估计方法

其中在介绍角度估计中&#xff0c;通过对接收差频信号在快慢时间维度的扩展&#xff0c;增加了空域的信息。扩展后的接收差频信号可以表示为其中k表示接收天线的个数&#xff0c;d为天线间距。在“干货|利用MATLAB实现FMCW雷达的角度估计”中&#xff0c;已经介绍了如何理解目标…

vscode 使用笔记

https://code.visualstudio.com/docs/setup/setup-overview#_proxy-server-support 如果使用代理上网时&#xff0c;需要配置&#xff1a; 在 settings.json 中这样设定&#xff1a; // 将设置放入此文件中以覆盖默认设置{"http.proxy": "http://用户名:密码IP:…

数据库设计基础:需求分析相关知识笔记

系统需求分析是用户和相关设计人员对数据库应用系统所涉及的内容和功能描述&#xff0c;主要是以用户角度来了解系统&#xff0c;是数据库逻辑设计和物理设计以及应用程序的涉及都根据系统分析的内容作为基础。该阶段是非常重要的环节&#xff0c;如果该阶段设计的不好&#xf…

matlab 康托尔集,康托尔集的性质特点

康托尔集的性质特点康托三分集中有无穷多个点&#xff0c;所有的点处于非均匀分布状态。此点集具有自相似性&#xff0c;其局部与整体是相似的&#xff0c;所以是一个分形系统。康托三分集具有(1)自相似性&#xff1b;(2)精细结构&#xff1b;(3)无穷操作或迭代过程&#xff1b…

String、StringBuuffer、StringBuilder三者的区别

可变性 String 类中使用 final 关键字字符数组保存字符串&#xff0c; private final char value[] &#xff0c;所以 String 对象是不可变的。 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类&#xff0c;在 AbstractStringBuilder 中也是使用字符数组保存…

运算符和类型转换

1.类型转换&#xff1a; 分为自动转换和强制转换&#xff0c;一般用强制转换。 其他类型转换为整数&#xff1a;parseInt&#xff08;&#xff09;&#xff1b; 其他类型转换为小数&#xff1a;parseFloat&#xff08;&#xff09;&#xff1b; 判断是否是一个合法的数字类型&a…

数据库设计基础:数据字典相关知识笔记

1、数据字典的定义 数据字典&#xff08;Data Dictionary ,DD&#xff09;是各类数据描述的集合&#xff0c;它是关于数据库中数据的描述&#xff0c;即元数据&#xff0c;而不是数据本身。2、数据字典的组成数据字典主要包括数据项、数据结构、数据流、数据存储、处理过程等内…

用符号方法求下列极限或导数matlab,matlab实验

3&#xff0c;设有矩阵A 和B 1234530166789101769A ,111213141502341617181920970212223242541311B ????????-????????-????????????????1、求它们的乘积C &#xff1b;2、将矩阵C 的右下角3*2子矩阵赋给D &#xff1b;3、察看matlab 工作空间…

事务管理:事务的基本概念笔记

1、事务的意义事务管理是对于一系列数据库操作进行操作。针对多个事务并发执行的数据库当中&#xff0c;如果对共享的数据进行更新操作不进行控制&#xff0c;很有可能会产生数据的不一致性&#xff0c;造成数据库存储无效甚至错误的数据。数据库在运行过程中会受到很多方面的因…

从零开始攻略PHP(8)——面向对象(下)

8.编写代码类 每个分离的函数可以执行一个明确的任务。任务越简单&#xff0c;编写与测试这个函数就越简单&#xff0c;当然也不要将这个函数分得太小——若将程序分成太多的小个体&#xff0c;读起来就会很困难。 使用继承可以重载操作。我们可以替换成一个大的Display()函数&…

vb treeview 展开子节点_C# / VB.NET 在PPT中创建、编辑PPT SmartArt图形

本文介绍通过C#和http://VB.NET程序代码来创建和编辑PPT文档中的SmartArt图形。文中将分两个操作示例来演示创建和编辑结果。使用工具&#xff1a;Spire.Presentation for .NET hotfix 5.9.5dll文件引用&#xff1a;方式1&#xff1a;下载包&#xff1b;Spire.Presentation for…

mysql安装教程8.0.21安装,Windows系统下MySQL8.0.21安装教程(图文详解)

安装建议&#xff1a;尽量不要用.exe进行安装&#xff0c;用压缩包安装&#xff0c;对日后的卸载/版本升级更为方便下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/1、点击上面的下载地址得到zip压缩包2、解压到要安装的目录我这里是E:\database\mysql8\mysql-8.0…

Java中 a+=b和a=a+b有什么区别?

一&#xff1a;性能方面 aab是加法运算 需要两次寻找地址而ab是增量运算有寄存器优先时 只有一次地址查找。效率方面后者略高于前者 基于现在计算机的发展可忽略不计。 二&#xff1a;对于不同类型的a,b来说 1:不同类型的两个变量在进行运算的时候&#xff0c;我们经常说到的…

事务管理:事务的状态相关知识笔记

1、事务的几个概念中止事务&#xff1a;事务在执行过程中发生故障&#xff0c;不能执行完成的事务。可以进行事务回滚&#xff0c;保持数据库的一致性。事务回滚&#xff1a;将中止事务对数据库的更新操作撤销称为事务回滚。已提交事务&#xff1a;成功执行完成的事务称为已提交…

mysql符合安可要求吗,安可是什么意思?演唱太过精彩,粉丝要求返场(再唱一个)...

解答&#xff1a;安可是指再唱一个的意思&#xff0c;最早是源自英语“Encore”&#xff0c;常被用在演唱会上&#xff0c;当最后一轮表演或演唱结束之后&#xff0c;粉丝就会开始大喊“安可”&#xff0c;就是想要再听一场&#xff0c;这时歌手也会应粉丝的要求返场。安可是什…

事务管理基础:数据库的并发控制相关知识笔记

1、并发操作的概念介绍并发操作主要是指在多用户共享的系统当中&#xff0c;可能存在很多用户同时对同一个数据进行操作。并发操作会造成丢失更新、不可重复读、读脏数据。主要原因是事务的并发操作破坏了事务的隔离性。2、事务调度相关知识事务调度主要有串行调度、并发调度、…