STM32CUBEMX配置教程(八)STM32串口轮询发送中断接收+重定义+优化

基于STM32H743VI
使用STM32CUBEMX两年了,始终觉得这个工具非常的方便,但因为不是经常使用,导致有些要点总是会有些遗忘,因此写下这一系列教程以供记忆,顺便让我这个大萌新给广大小萌新提供一些学习帮助。

此次工程效果:串口115200波特率,接收串口助手XCOM发送的数据并发送回XCOM

本次配置的工程链接在最下方,有需要自取。
0基础可以从第一个教程开始阅读
STM32CUBEMX配置教程(一)基础配置
STM32CUBEMX配置教程(二)时钟等内部参数配置
STM32CUBEMX配置教程(三)通用GPIO配置
STM32CUBEMX配置教程(四)定时器中断配置
STM32CUBEMX配置教程(五)高级定时器输出两路PWM波
STM32CUBEMX配置教程(六)高级定时器单通道输出互补PWM波(带死区和刹车)
STM32CUBEMX配置教程(七)定时器DMA产生占空比可调方波

1 新建工程

参考STM32CUBEMX配置教程(一)基础配置

2 修改时钟树

参考STM32CUBEMX配置教程(二)时钟等内部参数配置

3 串口基本原理

串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。串行接口 (Serial Interface)是指数据一位一位地顺序传送。其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。

上述来自百度,串口在调试时有很大作用

4 CUBEMX配置

此处使用串口1作为测试工具,具体来说使用PA9和PA10作为串口发送引脚。
在新建的工程里面配置PA9和PA10分别为串口的发送与接收引脚,如下图所示,此时引脚显示为黄色,表示暂未激活。
下一步需要对串口模式进行选择以激活,找到Connectivity下的USART1选项并点击,界面如下:

在右上角配置界面的第一个配置为异步通信模式即可,即为Asynchronous。除了通讯模式的配置外,此界面还有一些常用工业接口(RS232\RS585)的硬件流控,在此无需使用。配置图如下:

在配置为异步通信模式后,引脚变为绿色,表示已经配置:

配置界面右下角也会出现具体的配置框,如下图,我这边默认波特率为115200,如果不是这个数值则修改为这个数值即可,其他参数不用修改:

到此配置基本结束,点击生成代码。

5 使用库自带函数发送数据

库所自带的发送函数为:

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

此函数的输入为句柄、要发送的数据的指针、数据的大小、超时的时间

下面以此函数发送HELLO WORLD到串口助手XCOM:

int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();unsigned char s_buf[]="hello world\r\n";while (1){HAL_UART_Transmit(&huart1,s_buf,sizeof(s_buf),0xff);HAL_Delay(1000);}
}

看看结果,测试成功:

6 使用重定义后的printf发送数据

大多数人喜欢使用重定义后的printf发送数据,但是这种方式的效率较为低下,因为在重定义时数据的发送是一个字节一个字节发送的,也就是每发送一个字节就会调用一次HAL_UART_Transmit函数。

先在左侧找到usart.c这个c文件并打开:

在这个文件中的最后的用户代码区添加代码,用户代码区如下(不在这里添加在CUBEMX重新生成代码的时候会被覆盖):

添加后如下,如果报错FILE没有定义需要添加头文件#include “stdio.h”

/* USER CODE BEGIN 1 *///加入以下代码,支持printf函数,而不需要选择use MicroLIB
//#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#if 1
//#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{ int handle;
}; FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{ x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{   HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0x0001);  return ch;
}
#endif /* USER CODE END 1 */

那么重定义就已经完成。下一步进行printf的调用,修改while(1)中的代码如下则完成。

  /* Infinite loop *//* USER CODE BEGIN WHILE */while (1){printf("hello");HAL_Delay(1000);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}

效果图片如下:

6 使用优化后的(类)printf发送数据

大家使用Printf只是因为这个函数比较方便。
但是如果一个和printf一样方便更加高效的函数岂不是可以完全取代printf。
但是大家显然更关注简单而忽略高效,简单对刚刚入门的人来说很重要,但对有经验的人来说高效才是更应该追求的。

找到usart.c这个c文件并打开:
先在这个文件里面添加头文件:#include <stdarg.h>和#include <stdio.h>
然后再用户代码区添加:

unsigned char UartTxBuf[128];
void Usart1Printf(const char *format,...)
{uint16_t len;va_list args; va_start(args,format);len = vsnprintf((char*)UartTxBuf,sizeof(UartTxBuf)+1,(char*)format,args);va_end(args);HAL_UART_Transmit(&huart1, UartTxBuf, len,0xff);
}

这个函数与重定义的区别就是此函数可以将多个字符一下子全部发送,而非一个一个发送:
这个函数可以完全当成printf使用。
例如:

  while (1){Usart1Printf("hello");Usart1Printf("hello%d",10);HAL_Delay(1000);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}

效果如下:

7 串口接收

此处使用串口中断进行数据的接收,这也是串口接收的最常用的方式之一。
首先进行CUBEMX的配置,打开串口的全局中断,如下图:
然后点击生成代码即可。在usart.c文件中的串口初始化的最后添加开启串口中断接收的函数,修改后如下:

void MX_USART1_UART_Init(void)
{huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;if (HAL_UART_Init(&huart1) != HAL_OK){Error_Handler();}if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK){Error_Handler();}if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK){Error_Handler();}if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN USART1_Init 2 */HAL_UART_Receive_IT(&huart1, (unsigned char *)aRxBuffer, RXBUFFERSIZE);//此处为添加的/* USER CODE END USART1_Init 2 */
}

重点理解这个函数,非常重要:

 HAL_UART_Receive_IT(&huart1, (unsigned char *)aRxBuffer,,RXBUFFERSIZE);

&huart1为句柄,表示此处使用的是串口1

aRxBuffer为接收缓冲区,串口接收到的原始数据会被存储到这个数组之中

RXBUFFERSIZE为接收缓存区的大小,在接收完RXBUFFERSIZE个数据后接收会被关闭,需要再次开启

下面介绍以下本次教程的接收策略:RXBUFFERSIZE设置为1,每次接收1个数据进入一次中断,并在串口接收中断中重新开启一次接收,以此不断接收数据。

上述策略在大多数基础教程中十分常见,但我也有一些疑问:效率问题,接收一次就需要重新打开一次接收函数这样的策略效率可能较低。

之后在stm32h7xx_it.c文件中找到Usart的中断处理函数,修改成如下代码增加串口回调函数,在串口回调函数中判断是否收到回车以判断一条数据是否发送完毕(很原子的方法):

#define USART_REC_LEN            200     //定义最大接收字节数 200
#define RXBUFFERSIZE   1 //缓存大小
unsigned short USART_RX_STA=0;       //接收状态标记
unsigned char aRxBuffer[RXBUFFERSIZE];//HAL库使用的串口接收缓冲
unsigned char USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
void USART1_IRQHandler(void)
{/* USER CODE BEGIN USART1_IRQn 0 */unsigned int timeout=0;unsigned int maxDelay=0x1FFFF;/* USER CODE END USART1_IRQn 0 */HAL_UART_IRQHandler(&huart1);/* USER CODE BEGIN USART1_IRQn 1 */timeout=0;while (HAL_UART_GetState(&huart1)!=HAL_UART_STATE_READY)//等待就绪{timeout++;超时处理if(timeout>maxDelay) break;       }timeout=0;while(HAL_UART_Receive_IT(&huart1,(unsigned char *)aRxBuffer, RXBUFFERSIZE)!=HAL_OK)//一次处理完成之后,重新开启中断并设置RxXferCount为1{timeout++; //超时处理if(timeout>maxDelay) break; }/* USER CODE END USART1_IRQn 1 */
}
/* USER CODE BEGIN 1 */void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if(huart->Instance==USART1)//如果是串口1{if((USART_RX_STA&0x8000)==0)//接收未完成{if(USART_RX_STA&0x4000)//接收到了0x0d{if(aRxBuffer[0]!=0x0a)USART_RX_STA=0;//接收错误,重新开始else USART_RX_STA|=0x8000; //接收完成了 }else //还没收到0X0D{   if(aRxBuffer[0]==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=aRxBuffer[0] ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收     }      }}}
}
/* USER CODE END 1 */

回调函数中的判断是否收到0X0D只不过是在判断是否收到回车而已,对应阿斯克码表相应数据(阿斯克码中0x0d就是代表回车),这在串口通信中非必要,只不过自定义的协议以收到回车作为一条完整消息的结束符而已。

下面是接收测试:使用XCOM串口工具发送,配置如下,注意勾选发送新行:

串口助手发送hello,单片机仿真调试观察数据,接收成功。


值得注意,此处接收到的数据并不会手动清空,一般在main函数中调用USART_RX_STA进行相关操作,例如将收到的消息原路发送回去:

int main(void)
{/* USER CODE BEGIN 1 */unsigned int times,len;/* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 */unsigned char s_buf[]="hello world\r\n";Usart1Printf("hello");Usart1Printf("hello%d",10);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){ if(USART_RX_STA&0x8000){                      len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度printf("\r\n您发送的消息为:\r\n");HAL_UART_Transmit(&huart1,(uint8_t*)USART_RX_BUF,len,1000);  //发送接收到的数据while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET);       //等待发送结束printf("\r\n\r\n");//插入换行USART_RX_STA=0;}else{times++;if(times%500000==0){printf("\r\n STM32H7开发板 串口通信 \r\n");}if(times%20000==0)printf("\r\n 输入数据,以回车键结束\r\n");  if(times%300==0)//闪烁LED,提示系统正在运行.HAL_Delay(10);   }/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

工程链接:https://download.csdn.net/download/weixin_44584198/20686877

STM32CUBEMX配置教程(八)STM32串口轮询发送中断接收+重定义+优化相关推荐

  1. CubeMX STM32串口1DMA使用IDLE中断接收、串口2DMA接收DMX512信号(标准)

    CubeMX STM32串口1DMA使用IDLE中断接收.串口2DMA收发DMX512信号(标准) DMX512协议 CubeMX 代码部分 串口1 串口2 外部中断 定时器1 总结 DMX512协议 ...

  2. STM32CubeMX配置ADC采样(轮询、中断、DMA)

    选择DMA循环采集DMA_CIRCULAR uint16_t ADCRes[ADC_DMA_BUF_SIZE]; //变量易变,编译器不能随便优化 #define ADC_Ech_Channel 3 ...

  3. STM32CUBEMX配置教程(一)基础配置

    STM32CUBEMX配置教程(一)基础配置 基于STM32H743VI 使用STM32CUBEMX两年了,始终觉得这个工具非常的方便,但因为不是经常使用,导致有些要点总是会有些遗忘,因此写下这一系列 ...

  4. STM32F10x_硬件I2C主从通信(轮询发送,中断接收)

    推荐 分享一个大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到人工智能的队伍中来!http://www.captainbed.net/strongerhuang Ⅰ.写在前面 ...

  5. 关于STM32串口3的使用,接收并解析一帧数据

    关于STM32串口3的使用,接收并解析一帧数据 当stm32的串口1被使用时,我们可以使用其他串口来使用. 步骤: 串口3定义.初始化: 串口3中断服务函数(接收的一帧数据并判断是否正确): 主函数使 ...

  6. 基于HAL库STM32串口驱动不定长数据接收

    STM32串口驱动不定长数据接收带环形缓冲区 最新框架代码 使用方法 源码 串口接口文件 环形缓冲区接口文件 移植图示 使用涉及4个文件, UART_Port.c UART_Port.h Circul ...

  7. java 异步事件_Java编程入门——异步事件:轮询与中断

    CPU几乎把所有的时间都花费在从内存获取指令并运行它们的过程中.然而,CPU和主存仅仅只是计算机硬件系统中众多组件的其中两个.一个完整的系统还包含其他的设备,比如: 硬盘或者固态硬盘,用来存储程序和数 ...

  8. java 硬件中断_Java异步事件:轮询与中断

    CPU几乎把所有的时间都花费在从内存获取指令并运行它们的过程中.然而,CPU和主存仅仅只是计算机硬件系统中众多组件的其中两个. 一个完整的系统还包含其他的设备,比如: 硬盘或者固态硬盘,用来存储程序和 ...

  9. 关于串口数据的发送和接收(调试必备)

    前言 对于串口的数据发送和接收,大多是都是利用串口中断来进行的,但是这样对于编程方面有一定要求,并且程序也不太好写,比如说,如果让你随意接收一段数据,然后利用串口将它发送出来,第一个需要考虑的问题就是 ...

最新文章

  1. Meta AI 新研究,统一模态的自监督新里程碑
  2. android base layout - top middle bottom
  3. Expression: _CrtIsValidHeapPointer(pUserData)
  4. 自定义图框_Smart3D自定义图纸属性及其应用
  5. 【ValueError: Target is multiclass but average=‘binary‘. Please choose another average setting, one 】
  6. C#学习笔记_12_枚举结构体
  7. 备份数据库的expdp语句_银行业Oracle RAC数据库迁移经验分享
  8. 傲游浏览器linux傲游源,Ubuntu下安装遨游浏览器
  9. 验证集准确率上不去_Python机器学习之“模型验证”
  10. 英文字符串过长,QLabel显示不全的问题
  11. Pyinstaller将外部数据文件打包到可执行文件中(onefolder or onefile)教程
  12. 管理Active Directory的工具
  13. Golang 之协程详解
  14. 惠普台式电脑引导不了系统_惠普电脑进入bios设置引导模式操作步骤图文
  15. 牛顿吼 苹果把老子头砸了 于是 爱翁发现 谭
  16. 计算机手机远程控制,手机怎么远程控制电脑?手机远程控制电脑步骤详解
  17. elasticsearch控制match执行过程的低级查询处理规则
  18. 使用RT-Thread Studio DIY 迷你桌面时钟(一)| 基于STM32芯片创建HelloWorld工程
  19. 【利用Altium Designer2018设计元器件原理图库】
  20. 红轴和青轴哪个手感好 红轴和青轴哪个玩游戏好

热门文章

  1. P2P分布式资源存储项目设计收获
  2. 2012年七月GBin1月刊
  3. Unity第三人称上帝视角控制
  4. 如何备考软考-信息系统项目管理师(高级)
  5. 4444445555
  6. 计算机考研和leetcode难度,2021计算机考研真题难度解析
  7. Android截屏和录屏Demo
  8. 使用Mac的浏览器调试ios设备上的cordova app
  9. 谷歌增强现实技术ARCore
  10. 用 AI 生成简历是怎样的体验? #Rezi AI