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

一些基础串口通信实验(通过串口调试助手与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
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值