【STM32】基于cubemx+HAL库的串口实验(一)

本文档详述了基于STM32CubeMX和HAL库的串口通信实验,涵盖轮询、中断、重定向及帧格式通信。通过具体项目代码,深入解析uart串口通信原理,适合初学者系统学习。
该文章已生成可运行项目,

一些基础串口通信实验(通过串口调试助手与PC机通信)和相关知识点汇总,主要参考MOOC课程《基于STM32CubeMX和HAL驱动库的嵌入式系统设计-漆强》和部分文章,方便新手系统性学习uart串口通信知识

一、实验前准备

  • 完成过基于cubemx和MDK-ARM的点灯实验【STM32】STM32CubeMX教程二–基本使用(新建工程点亮LED灯)
  • 相应MCU的原理图(查询TX,RX口,硬件连接用)
  • 串口调试助手
  • 串口转换器
    需要按说明书设置为对应模式,此处设置为USB转TTL模式,如:
    在这里插入图片描述
    (开始没注意转换器还有好几种模式,MCU一直没有接收到信号,找了一堆教程看,后来看说明书才发现这个问题)

USB转TTL

USB转TTL、USB转串口、USB转232的区别

二、具体项目代码

根据自己MCU型号在CubeMX中进行相应配置,可参考
【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解
STM32CubeMX串口通信调试避坑(胎教级教程)

项目1:轮询方式通信

轮询方式: 可直接检测标志位(不用中断就算轮询)
中断方式: 在中断服务程序中通过检测不同的中断标志位来判断中断类型
实验内容:发送测试数据,接收指定长度数据并发送给PC(用来显示)
使用的HAL库函数:
//为了直观,省略了参数类型
HAL_UART_Transmit(*huart, *pData, Size, Timeout):该函数连续发送数据,发送过程中判断TXE标志来发送下一个数据,通过TC标志来结束数据的发送
HAL_UART_Receive(*huart, *pData, Size, Timeout):该函数连续接收数据,接收过程中判断RXNE标志来接收新的数据

  1. 在main.c中申明变量
/* Private variables ----------------------------*/
/* USER CODE BEGIN PV */
#define RXBUFFERSIZE  5
uint8_t RxBuffer[RXBUFFERSIZE];   //接收数据
/* USER CODE END PV */
  1. 在while循环中添加发送接收语句
 /* USER CODE BEGIN 3 */
  if(HAL_UART_Receive(&huart2,RxBuffer,RXBUFFERSIZE,100)==HAL_OK)
	  {
		HAL_UART_Transmit(&huart2,RxBuffer,RXBUFFERSIZE,100); //把接收的数据发回来
		}
  /* USER CODE END 3 */

项目2:printf重定向实验

目标:利用串口实现printf函数和scanf函数
内容:利用串口调试助手,发送数据到MCU,MCU调用scanf函数读取数据,然后用printf函数发送对应消息到PC

P.S.内容跟轮询模式差不多,只是用printf和scanf去输入输出,为了熟悉重定向这个概念

重定向

  • 在c语言中,printf函数是将数据格式化输出到屏幕,内部调用fputc函数;scanf函数是从键盘格式化输入数据,内部调用fgetc函数
  • 在嵌入式系统中,数据的输入输出一般采用串口
  • 如果直接调用printf和scanf函数,MCU不知道输出到哪或从哪里读,需要用户改写c语言的库函数,用对应的串口函数实现重定向

具体步骤:

  1. 添加头文件:在main.h中添加标准输入输出头文件"stdio.h"
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
  1. 在main.c中的USER CODE BEGIN 4(主函数之后)里添加重定向函数
/* USER CODE BEGIN 4 */
/**
  *@brief:	重定向printf函数到USART
  *采用轮询方式发送1字节数据,请参考HAL_UART_Transmit用法
  *HAL_MAX_DELAY为无限等待
 */
int fputc(int ch, FILE *f)
{
	HAL_UART_Transmit(&huart2, (uint8_t *)&ch,1,HAL_MAX_DELAY);//
	return ch;
}
/**
  *@brief:	重定向scanf函数到USART
 */
int fgetc(FILE *f)
{
	uint8_t ch;
	HAL_UART_Receive(&huart2,(uint8_t *)&ch,1,HAL_MAX_DELAY);
	return ch;
}
/* USER CODE END 4 */
  1. main.c主函数前,除了基本的初始化代码外,添加申明变量和提示信息
/* Private variables -------------------------*/
/* USER CODE BEGIN PV */
uint8_t aRxBuffer;			//接收数据缓冲
/* USER CODE END PV */
 /* USER CODE BEGIN 2 */
 printf("UART Retarget:\r\n");//用户提示信息
 /* USER CODE END 2 */
  1. while循环中添加接收和发送语句:当接收到y时发送Received y!,当接收到其他消息时发送Received others!
   /* USER CODE BEGIN 3 */
     scanf("%c",&aRxBuffer);
     if(aRxBuffer == 'y')
     {
   	  printf("Received y!\r\n");
     }
     else
     {
   	  printf("Received others!\r\n");
     }
 /* USER CODE END 3 */
  1. 实验结果:
    在这里插入图片描述

项目3:中断方式的串口通信

串口中断方式特点:

  • 在每字节数据收发完成后,由中断标志位触发中断
  • 在传输数据量较大,且通信波特率较高(大于38400)时,如果采用中断方式,每收发一字节的数据CPU都会被打断,造成CPU无法处理其他事物。因此在批量数据传输、波特率较高时,建议采用DMA方式

相关中断函数

P.S.为了直观,省略了这些函数的参数类型,具体信息请直接看对应函数的官方代码和注释。

串口中断方式发送函数HAL_UART_Transmit_IT (*huart,*pData,Size)
功能描述在中断方式下发送一定量数据
发送过程每发送一个数据进入一次中断,在中断中根据发送数据的个数判断数据是否发送完成
返回值HAL状态值:HAL_OK表示发送成功;HAL_ERROR表示参数错误;HAL_BUSY表示串口被占用

注意事项:

  1. 函数将使能串口发送中断
  2. 函数将置位TXEIE和TCIE,使能发送数据寄存器空中断和发送完成中断。完成指定数量的数据发送后,将会关闭发送中断(清零YXEIE和TCIE),因此用户采用中断方式连续发送数据时,需要重复调用该函数,以便重新开启发送中断
  3. 当指定数量的数据发送完成后,将调用发送中断回调函数HAL_UART_TxCpltCallback进行后续处理。
串口中断方式接收函数HAL_UART_Receive_IT (*huart,*pData,Size)
功能描述在中断方式下接收一定数量的数据
发送过程每接收一个数据进入一次中断,在中断中根据接收数据的个数判断数据是否接收完成
返回值HAL状态值:HAL_OK表示接收成功;HAL_ERROR表示参数错误;HAL_BUSY表示串口被占用

注意事项:

  1. 函数将使能串口接收中断
  2. 函数将置位RXNEIE,使能接收数据寄存器非空中断。完成指定数量的数据接收后,将会关闭接收中断(清零RXNEIE),因此用户采用中断方式连续接收数据时,需要重复调用该函数,以便重新开启接收中断
  3. 当指定数量的数据接收完成后,将调用接收中断回调函数HAL_UART_RxCpltCallback进行后续处理。
接口函数HAL_UART_IRQHhandler(*huart)
功能描述作为所有串口中断发生后的通用处理函数
返回值

注意事项:

  1. 函数内部先判断中断类型,并清除对应的中断标志,最后调用回调函数完成对应的中断处理
串口发送中断回调函数HAL_UART_TxCpltCallback (*huart)
功能描述回调函数,用于处理所有串口的发送中断,用户在该函数内编写实际的任务处理函数
返回值

注意事项:

  1. (一般情况)函数由串口中断通用处理函数HAL_UART_IRQHandler调用
  2. 函数内部需要根据串口句柄来判断由哪个串口产生的发送中断。
  3. 函数由用户根据具体的处理任务。
  4. 串口接收中断回调函数 HAL_UART_RxCpltCallback (*huart)类似

补充:weak关键词

在HAL库中stm32xx_hal_uart.c初始定义了
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart),(RxCpltCallback也类似)关于weak关键词:C语言之强化,弱化符号weak

串口中断使能函数__HAL_UART_ENABLE_IT ( _ _HANDLE _ _ , _ _ INTERRUP _ _)
功能描述使能对应的串口中断类型
参数1_ _HANDLE _ _ :串口句柄的地址
参数2_ _INTERRUPT _ _ :串口中断类型
返回值

注意事项:

  1. 该函数是宏函数,进行宏替换,不发生函数调用
  2. 函数需要由用户调用,用于使能对应的串口中断类型
  3. 串口中断类型常用取值如下:
  • UART_IT_TXE:发送数据寄存器空中断
  • UART_IT_TC:发送完成中断
  • UART_IT_RXNE:接收数据寄存器非空中断
  • UART_IT_IDLE:线路空闲中断
串口中断标志查询函数__HAL_UART_GET_FLAG ( _ _HANDLE _ _ , _ _ INTERRUP _ _)
功能描述查询串口中断标志
返回值

具体步骤:
参照【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解
实现任意长度字符接收

/* USER CODE BEGIN PD */
#define RXBUFFERSIZE  256     //最大接收字节数
/* USER CODE END PD */
/* USER CODE BEGIN PV */
uint8_t RxBuffer[RXBUFFERSIZE];   //接收数据缓冲
uint8_t aRxBuffer;			//接收数据
uint8_t Uart2_Rx_Cnt = 0;		//接收缓冲计数
/* USER CODE END PV */
  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart2, (uint8_t *)&aRxBuffer, 1);
  //调用串口中断方式接收函数
  /* USER CODE END 2 */

在int main()后补充完整void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

 /* USER CODE BEGIN 4 */
/**
  * @brief  Rx Transfer completed callback.
  * @param  huart UART handle.
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
	if(Uart2_Rx_Cnt >= 255)  //溢出判断
	{
		Uart2_Rx_Cnt = 0;
		memset(RxBuffer,0x00,sizeof(RxBuffer));
		HAL_UART_Transmit(&huart2, (uint8_t *)"数据溢出", 10,0xFFFF); 	
        
	}
	else
	{
		RxBuffer[Uart2_Rx_Cnt++] = aRxBuffer;   //接收数据转存
	
		if((RxBuffer[Uart2_Rx_Cnt-1] == 0x0A)&&(RxBuffer[Uart2_Rx_Cnt-2] == 0x0D)) //判断结束位
		{
			HAL_UART_Transmit(&huart2, (uint8_t *)&RxBuffer, Uart2_Rx_Cnt,0xFFFF); //将收到的信息发送出去
            while(HAL_UART_GetState(&huart2) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
			Uart2_Rx_Cnt = 0;
			memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
		}
	}	
	HAL_UART_Receive_IT(&huart2, (uint8_t *)&aRxBuffer, 1);   //再开启接收中断
}
/* USER CODE END 4 */

实验结果:
在这里插入图片描述

项目4:简单的帧格式通信

Modbus消息帧格式:

起始符设备地址功能代码数据校验结束符
1个字符2个字符1个字符n个字符2个字符1个字符
起始符表示一帧数据的开始
设备地址用于指定需要进行信息传递的设备
功能代码用于指定需要完成的操作
数据表示需要传输的数据
校验用于通信中的错误校验
结束符表示一帧数据的结束

模仿Modbus,自定义帧格式设定:

帧头设备码功能码帧尾
0xaa1个字符(8bit)1个字符(8bit)0x55
帧头0xaa表示一帧数据的开始
设备码0x01表示指示灯
功能码0x00表示关闭指示灯,0x01表示打开指示灯
帧尾0x55表示一帧数据的结束

目标:实现简单的帧格式通信
内容:PC按照自定义的帧格式发送指令开启或关闭开发板上的LD2

代码:

/* USER CODE BEGIN PV */
#define LENGTH  4
uint8_t RxBuffer[LENGTH];   //接收缓冲区
uint8_t RxFlag =0;//接收完成标志:为0表示接收未完成,为1表示接收完成
uint8_t ErrFlag =0;//指令错误标志:为0表示指令正确,为1表示错误
/* USER CODE END PV */
 /* USER CODE BEGIN 2 */
  //发送提示信息
  printf("***** Communication Frame *****\r\n");
  printf("Please enter instruction:\r\n");
  printf("Head->0xaa Device->0x01 Operation->0x00/0x02 Tail->0x55.\r\n");
  HAL_UART_Receive_IT(&huart2, (uint8_t *)RxBuffer, LENGTH);//使能接收中断
  /* USER CODE END 2 */

main函数中的while(1)循环:

  while (1)
{
 if(RxFlag == 1) //如果接收完成
 {
	 //1、清除接收完成标志位
	RxFlag = 0; 
	 //2、判断帧格式是否符合规范
	 if(RxBuffer[0]==0xaa && RxBuffer[3]==0x55) //判断帧头帧尾
	 {
		 if(RxBuffer[1]==0x01)//判断设备码
		 {
	 //3、判断功能码,若为0x00则关闭指示灯
			 if(RxBuffer[2]==0x00)
			 {
				 HAL_GPIO_WritePin(LD2_GPIO_Port,LD2_Pin,GPIO_PIN_RESET);
				 printf("LD2 is close!\r\n");
			 }
			 else if(RxBuffer[2]==0x01)
			{
				 HAL_GPIO_WritePin(LD2_GPIO_Port,LD2_Pin,GPIO_PIN_SET);
				 printf("LD2 is open!\r\n");
			 }
			else
			{
				 ErrFlag = 1;//如果功能码错误
			 }
		 }
		 else
		 {
			 ErrFlag = 1;//如果帧头帧尾错误
		 }
	 }
	else
	{
		 ErrFlag = 1;//如果设备码错误
	}
	if(ErrFlag == 1)
	{
		printf("Communication Error! Please send again");
	}		
	//4、清除接收缓冲区和错误标志,准备下一次接收
		ErrFlag =0;
		memset(RxBuffer,0x00,sizeof(RxBuffer));
 }  
}
  /* USER CODE END 3 */
/* USER CODE BEGIN 4 */
  /**
  *@brief:	重定向printf函数到USART
  *@param1:	输出的字符
  *@param2:	文件指针
  *@retval:	输出的字符
  *请参考HAL_UART_Transmit用法,HAL_MAX_DELAY为无限等待
 */
int fputc(int ch, FILE *f)
{
	HAL_UART_Transmit(&huart2, (uint8_t *)&ch,1,HAL_MAX_DELAY);//
	return ch;
}
/**
  * @brief  Rx Transfer completed callback.
  * @param  huart UART handle.
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance ==USART2)//判断发生接收中断的串口
	{
		RxFlag = 1;//置位接收完成标志
		HAL_UART_Receive_IT(&huart2, (uint8_t *)RxBuffer, LENGTH);//重新使能接收中断,准备下一次数据接收
	} 
}
/* USER CODE END 4 */

实验结果:
串口调试助手发送时选择16进制发送

  1. 发送 aa 01 01 55,LD2打开(图中蓝灯)
    在这里插入图片描述
    在这里插入图片描述
  2. 发送 aa 01 00 55,LD2关闭
    在这里插入图片描述
    在这里插入图片描述
  3. 发送 aa 01 02 55,报错
    在这里插入图片描述
本文章已经生成可运行项目
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值