0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

电子设计 来源:电子设计 作者:电子设计 2022-02-10 13:52 次阅读

串口发送数据

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++;

}

}

这样就可实现任意的串口发送。但有一点,我在使用实时操作系统的时候(如UCOSFreertos等),需考虑函数重入的问题。

当然也可以简单的实现把该函数复制一下,然后修改串口号也可以避免该问题。然而这个函数不能像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时间资源的。

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

}

审核编辑:何安

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • STM32
    +关注

    关注

    2274

    文章

    10929

    浏览量

    358017
  • 串口
    +关注

    关注

    14

    文章

    1562

    浏览量

    77241
收藏 人收藏

    评论

    相关推荐

    RS232串口连接方式及注意事项

    交换。 RS-232串口简介 RS-232标准最初由电子工业联盟(EIA)在1960年制定,用于定义串行通信的电气特性、信号定时和数据格式。RS-232串口使用25针的D型连接器(DB-25),但后来更常用的是9针的D型连接器(
    的头像 发表于 12-10 16:23 1872次阅读

    LS10串口数据库模块外扩SD卡功能

    LS10串口数据库模块外扩SD卡功能
    的头像 发表于 11-23 09:42 275次阅读
    LS10<b class='flag-5'>串口</b><b class='flag-5'>数据</b>库模块外扩SD卡功能

    ESP32串口接收发送数据,会有延迟是怎么回事?

    测试环境:串口波特率600bps,串口接收256字节数据。 在调试串口时,发现发送多于120字节
    发表于 06-26 08:07

    用esp32的串口Stm32F407串口通讯,通过TJA转CAN,结果esp32串口读到的一直是自己发的数据,为什么?

    我用esp32的串口Stm32F407串口通讯,通过TJA转CAN。结果esp32串口读到的一直是自己发的数据,各位大神给我看看哪里有问题
    发表于 06-19 07:00

    单片机在串口发送数据时可以接收串口助手发的数据,为什么只能接收两字节?

    最近做串口通信,单片机在串口发送数据时可以接收串口助手发的
    发表于 05-08 07:52

    STM8L051F3串口发送接收数据丢失怎么解决?

    (USART1); //复位UART1 /* * 将UART1配置为: * 波特率 = 38400 * 数据位 = 8 * 1位停止位 * 无校验位 * 使能接收发送 */ USART_Init
    发表于 05-08 06:21

    stm8串口通信接收数据,如何判断是否接收完毕?

    stm8串口通信接收数据。已有的协议是只有帧头,没有帧尾,最后一个字节是校验和,一帧数据的长度是不确定的,但可以根据第三个字节来判断本次数据
    发表于 05-06 07:24

    STM32 hal库无法接收串口数据是怎么回事?

    型号:STM32L432KC官方开发板 串口使用的是板载的USB下载以及串口 用法: 在串口初始化后,while(1)之前调用
    发表于 04-29 06:11

    Python怎么读取STM32串口数据

    =ser.readlines()print(s) 可是什么都读取不了。如果用买的STM32开发板送的串口助手,能接收数据。板子上烧录的printf输出程序。 请问各位大神,Pytho
    发表于 04-24 07:30

    STM8S105C6T6 UART2串口接收数据,不能发送数据是为什么?

    STM8S105C6T6 UART2串口,能接收数据,不能发送数据,哪位大神能帮忙看看。现在单片机自发自收没有问题,
    发表于 04-10 08:05

    STM32F030F4串口空闲中断接收不定长数据,发生中断后不知道如何计算接收到的字节数?

    我用STM32F030F4串口空闲中断接收不定长数据,发生中断后不知道怎样计算接收到的字节数。
    发表于 04-03 07:12

    stm32g070怎么可以让usart3串口可以自动接收自己发出的数据

    让usart3串口可以自动接收自己发出的数据(即可全双工工作)? usart4:目前仅可以正常接收数据
    发表于 03-28 06:53

    STM32串口发送起始数据丢失的原因?怎么处理?

    今天遇到个有意思的情况,就是采用串口发送的时候,最开始开机第一次发送的时候,第一个数据会丢失,比如我要发送41 46 3A......,但程
    发表于 03-20 06:48

    使用CubeMX配置STM32010C6T6的LPUART1外设,调用串口发送接收函数均不能收发数据怎么解决?

    如题,STM32010C6T6串口1配置完成后,Cube界面接收引脚模式默认为推挽输出,并且没有其它选项可以更改,(其它系列MCU默认是输入模式)生成的工程调用串口
    发表于 03-19 07:48

    为什么stm32h750VBT6串口通信接收不到自己发送过去的数据数据溢出会有影响吗?

    请问有没有知道stm32h750VBT6为什么串口通信接收不到自己发送过去的数据数据溢出会有影
    发表于 03-15 07:09