当前位置:首页 > 经验 >

串口通信的接收与发送(串口通信发送数据应该在哪发送)

来源:原点资讯(m.360kss.com)时间:2023-03-16 18:58:16作者:YD166手机阅读>>

文章下方附学习资源,自助领取

串口发送数据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等的时候,需考虑函数重入的问题,相关推荐:使用STM32CubeMx工具,写FreeRTOS的demo程序。

嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!

无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。

点击这里找小助理0元领取:

串口通信的接收与发送,串口通信发送数据应该在哪发送(1)

串口通信的接收与发送,串口通信发送数据应该在哪发送(2)

当然也可以简单的实现把该函数复制一下,然后修改串口号也可以避免该问题。然而这个函数不能像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不行的。需要加上以下的重映射函数:

串口通信的接收与发送,串口通信发送数据应该在哪发送(3)

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

串口通信的接收与发送,串口通信发送数据应该在哪发送(4)

串口接收数据

串口接收最后应有一定的协议,如发送一帧数据应该有头标志或尾标志,也可两个标志都有,串口其他相关文章:学习STM32单片机,绕不开的串口。

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

本文转载自“STM32嵌入式开发”,如有侵权,请联系删除
原文链接:

版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。

栏目热文

串口通信的四种方式及特点(串口通信的三种方式)

串口通信的四种方式及特点(串口通信的三种方式)

单片机原理及应用期末考试试题汇总1、单片机是将微处理器、一定容量的 RAM 和ROM以及 I/O 口、定时器等电路集成在...

2023-03-16 19:04:00查看全文 >>

串口通信协议解析过程(uart串口通信协议)

串口通信协议解析过程(uart串口通信协议)

串口是计算机上一种非常通用的设备通信协议。---------------------------------串口的引脚定...

2023-03-16 19:00:38查看全文 >>

串口通信接口图解(CC2530有多少个串口通信接口)

串口通信接口图解(CC2530有多少个串口通信接口)

说到S7-200SMART PLC通常都采用以太网通信及交换数据,比如威纶通触摸屏现都支持网口与200SMART通信控制...

2023-03-16 19:11:55查看全文 >>

串口通信四种工作方式

串口通信四种工作方式

1、串口通讯串口通讯(Serial Communication),是指外设和计算机间,通过数据信号线、地线等,按位进行传...

2023-03-16 18:41:35查看全文 >>

485通讯技巧口诀(485通讯故障检测方法)

485通讯技巧口诀(485通讯故障检测方法)

做工程很多时候会提到RS485控制线,它到底是什么呢?今天我聊聊RS485相关的应用,深入了解RS485,你会发现里面的...

2023-03-16 19:25:21查看全文 >>

串口通信结构图解(串口通信原理动画图解)

串口通信结构图解(串口通信原理动画图解)

通信接口背景知识设备之间通信的方式一般情况下,设备之间的通信方式可以分成并行通信和串行通信两种。并行与串行通信的区别如下...

2023-03-16 18:47:32查看全文 >>

串口与并口的区别(串口和光驱并口有什么区别)

串口与并口的区别(串口和光驱并口有什么区别)

  并行接口,简称并口。工业电脑的并口采用25针D形接头。所谓“并行”,是指8位数据同时通过并行线进行传送,这样数据传送...

2023-03-16 19:02:56查看全文 >>

com口与串口的区别(com口接线图解)

com口与串口的区别(com口接线图解)

并行接口,称为并口。并行端口使用25针D型连接头。所谓“并行”是指通过并行线路同时传输8位数据,从而大大提高了数据传输速...

2023-03-16 18:58:04查看全文 >>

串口通信架构图(串口通信仿真图)

串口通信架构图(串口通信仿真图)

1、串行接口是指数据一位一位地顺序传送。其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传...

2023-03-16 18:47:57查看全文 >>

485通讯原理(485通讯故障检测方法)

485通讯原理(485通讯故障检测方法)

在现代工业控制系统中,常常需要实现分布式控制,而分布式控制需要实现不同设备之间的通信。其中,485通信协议是一种被广泛使...

2023-03-16 19:02:53查看全文 >>

文档排行