STM32串口发送数据和接收数据方式总结

发布者:HarmonyInLife最新更新时间:2020-07-09 来源: eefocus关键字:STM32  串口发送数据  接收数据方式 手机看文章 扫描二维码
随时随地手机看文章

之前写了篇关于ESP8266使用AT指令进行互相通讯的实验,在写STM32串口接发数据的程序中,觉得有必要将之前学的有关于串口方面的使用经历加以总结。


串口发送数据:


1. 串口发送数据最直接的方式就是标准调用库函数 。 void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

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


void Send_data(u8 *s)

{

while(*s!='')

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!='')

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("%srn",Uart2_Buffer);        //这里我做打印数据处理

Uart2_Rx=0;                                   

else

{

Uart2_Rx=0;                                   //不是我们需要的数据或者达到最大接收数则开始重新接收

}

}

}

}


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


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


2.可以接受指定的数据


3.防止接收的数据使数组越界


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


以上的接收形式,是中断一次就接收一个字符,这在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中断和DMA需要朋友们先行了解。


    参考链接:


    https://blog.csdn.net/jdh99/article/details/8444474


    https://blog.csdn.net/phker/article/details/51925668   


   最后在讲下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("%srn",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);

[1] [2]
关键字:STM32  串口发送数据  接收数据方式 引用地址:STM32串口发送数据和接收数据方式总结

上一篇:MPU6500驱动调试笔记(STM32F407+SPI)
下一篇:STM32F1--串口通讯实验

推荐阅读最新更新时间:2024-11-10 22:38

stm32 encoder
首先打开timer2的encoder模式: 配置系统时钟。 配置具体定时器的参数: 选择的encoderMode是 TI1和TI2模式。这种模式下,AB两相的上升沿和下降沿都会计数,所以计数值是实际值的4倍,需要做分频。也就是第一个参数,分频值设为3,实际上是3+1=4分频。 还有个地方需要解释一下,我刚开始的时候就是把这里的设置没搞清楚,看Polarity参数设置的是Rising Edge。这个参数的意思是在检测到上升沿的时候就触发encoder捕获AB相的值,而并不是这里设置的是上升沿就只检测AB相的上升沿,下降沿还是同样会计数的。 Input Filter滤波值是从1-15,看情况设定,是用来滤除一些杂波的。
[单片机]
<font color='red'>stm32</font> encoder
STM32学习之DMAM-M模式
由于是M-M模式,所以只能选择DMA2 一、首先应该初始化DMA结构体 1.选择通道DMA_Channel,根据数据流选择对应的通道, 2.选择源数据地址DMA_PeripheralBaseAddr 3.目标地址DMA_Memory0BaseAddr 3.选择传输方向DMA_DIR,根据实际情况有这三种DMA_DIR_PeripheralToMemory DMA_DIR_MemoryToPeripheral DMA_DIR_MemoryToMemory 4.设定待传输数据数目DMA_BufferSize,源数据大小 5.使能源数据地址,目标地址递增, 6.源数据和目标宽度DMA_PeripheralDataSize和D
[单片机]
STM32之SPI_CR1寄存器的SSM, SSI位理解
近日调试STM32的SPI程序,现在记录下自己的一点小理解。 STM32之SPI_CR1寄存器的SSM, SSI位理解 SSM位,启用或禁止软件从设备选择。SSM置位时,NSS输入引脚的电平将被SSI的值代替。 SSI位,在SSM=1时有意义,决定NSS引脚上的电平,NSS引脚上的IO值将忽略。 NSS输入分为硬件输入和软件控制输入两种模式。NSS有内部和外部引脚。当NSS是软件控制输入模式时,NSS的内部引脚和外部引脚断开。内部引脚通过SPI_CR1寄存器的SSI位来驱动,外部引脚留作他用(可以作为GPIO驱动从设备的片选信号)。 当SSM位置位使能时,启用软件从设备选择,也就是软件控制输入模式。外
[单片机]
采用STM32 单片机的太阳能LED街灯解决方案
随着化石类能源的日益减少,以及温室气体的过度排放导致全球变暖问题越来越受到重视,人们一方面在积极开发各类可再生新能源,另一方面也在倡导节能减排的绿色环保技术。太阳能作为取之不尽、用之不竭的清洁能源,成为众多可再生能源的重要代表;而在照明领域,寿命长、节能、安全、绿色环保、色彩丰富、微型化的LED固态照明也已被公认为世界一种节能环保的重要途径。太阳能-LED街灯同时整合了这两者的优势,利用清洁能源以及高效率的LED实现绿色照明。 本文介绍的太阳能-LED街灯方案,能自动检测环境光以控制路灯的工作状态,最大功率点追踪(MPPT)保证最大太阳能电池板效率,恒电流控制LED,并带有蓄电池状态输出以及用户可设定LED工作时间等功能。 系统结
[电源管理]
采用<font color='red'>STM32</font> 单片机的太阳能LED街灯解决方案
嵌入式LWIP网络客户端设计
引言   嵌入式技术的兴起使得传统的基于PC机的互联网技术优势不再,嵌入式网络客户端与服务端技术成为热点,而该技术需要移植性高、占用资源小的协议栈,轻量级TCP/IP协议栈LWIP (light weight Internet protocol)比较适合嵌入式设备中存储容量有限的情况,而且能实现TCP/IP协议栈的基木功能,不影响设备的网络互联与传输服务 。轻量级网络协议LWIP依附的硬件操作系统有RT_Thread 和uC/OS-II ,鉴于uC/OS-II是一款应用较为广泛、技术较成熟的操作系统,本文采用uC/OS-II操作系统设计网络客户端。实验结果表明:ping 32字节的数据,其收发正确,错误率为0%。 1 系统硬件
[单片机]
嵌入式LWIP网络客户端设计
stm32 UCGUI 完美移植
UCGUI是一种嵌入式应用中的图形支持系统。它设计用于为任何使用LCD图形显示的应用提供高效的独立于处理器及LCD控制器的图形用户接口,它适用单任务或是多任务系统环境, 并适用于任意LCD控制器和CPU下任何尺寸的真实显示或虚拟显示。 它的设计架构是模块化的,由不同的模块中的不同层组成,由一个LCD驱动层来包含所有对LCD的具体图形操作。UCGUI可以在任何的CPU上运行,因为它是100%的标准C代码编写的。 类似程序还有国产的一个MINIGUI ( http://www.minigui.com/zhcn/ ),MiniGUI 是一个自由软件项目。其目标是提供一个快速、稳定、跨操作系统的图形用户界面
[单片机]
stm32篇--系统初始化
跟着程序执行流程走,main是程序入口,在里面进行了各种初始化。 1.时钟配置 首先是RCC_Configuration(),里面进行了时钟基本的初始化,stm32有多个时钟源:(1)HSI上电默认启动,精度不高(2)HSE外部高速时钟,系统时钟一般采用它,经过PLL倍频;(3)LSE外部低速时钟,一般专门用于RTC;(4)LSI内部低速时钟,精度不高,一般用于IWDGCLK; void RCC_Configuration(void) { RCC_DeInit();//复位RCC成缺省值 RCC_HSEConfig(RCC_HSE_ON);//开启HSE的时钟作为PLL的时钟源 HSEStartUpStatu
[单片机]
STM32 FreeRTOS Keil环境搭建
由于FreeRTOS的官方已经支持STM32F1X系列的Cortex-M3的移植,所以只需要在Keil IDE中设置相关即可了; 在Keil中新建一工程,在工程中新建3个组,分别对应3个目录用来存放:user、rtos、stmlib user中添加用户自己的代码和头文件; rtos中添加rots的文件主要有:list.c、task.c、queue.c、head_2.c、port.c stmlib中添加STM32官方提供的STM32操作的lib库(注意stm32f10x_md.s中的内容和替换为FreeRTOS Demo 中的STM32F10X.s否则系统调度不能正常工作) 另外需要把FreeRTOS/source/inc
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved