HAL库学习笔记- 8 串口通信之使用

16 篇文章 47 订阅
16 篇文章 18 订阅
本文详细介绍了STM32的串口通信,包括USART初始化结构体的配置,轮询方式下串口通信的发送和接收函数及其示例,以及中断方式下的串口通信处理。通过实例展示了如何使用MDK进行串口初始化、数据收发及串口重定向。同时,还讲解了如何实现简单的帧格式通信,确保数据的正确传输。
摘要由CSDN通过智能技术生成


前言

上一篇文章主要是讲了一些概念上的东西,有点多,所以单独写一笔记记录STM32串口通信的使用。


一、USART初始化

串口的数据类型定义

1.初始化结构体

/**
  * @brief UART Init Structure definition
  */
typedef struct
{
  uint32_t BaudRate;                  /*!< This member configures the UART communication baud rate.
                                           The baud rate is computed using the following formula:
                                           - IntegerDivider = ((PCLKx) / (8 * (OVR8+1) * (huart->Init.BaudRate)))
                                           - FractionalDivider = ((IntegerDivider - ((uint32_t) IntegerDivider)) * 8 * (OVR8+1)) + 0.5
                                           Where OVR8 is the "oversampling by 8 mode" configuration bit in the CR1 register. */

  uint32_t WordLength;                /*!< Specifies the number of data bits transmitted or received in a frame.
                                           This parameter can be a value of @ref UART_Word_Length */

  uint32_t StopBits;                  /*!< Specifies the number of stop bits transmitted.
                                           This parameter can be a value of @ref UART_Stop_Bits */

  uint32_t Parity;                    /*!< Specifies the parity mode.
                                           This parameter can be a value of @ref UART_Parity
                                           @note When parity is enabled, the computed parity is inserted
                                                 at the MSB position of the transmitted data (9th bit when
                                                 the word length is set to 9 data bits; 8th bit when the
                                                 word length is set to 8 data bits). */

  uint32_t Mode;                      /*!< Specifies whether the Receive or Transmit mode is enabled or disabled.
                                           This parameter can be a value of @ref UART_Mode */

  uint32_t HwFlowCtl;                 /*!< Specifies whether the hardware flow control mode is enabled or disabled.
                                           This parameter can be a value of @ref UART_Hardware_Flow_Control */

  uint32_t OverSampling;              /*!< Specifies whether the Over sampling 8 is enabled or disabled, to achieve higher speed (up to fPCLK/8).
                                           This parameter can be a value of @ref UART_Over_Sampling */
} UART_InitTypeDef;
  1. BaudRate:波特率设置。一般设置为2400、9600、19200、115200。HAL 库函数会根据设定值计算得到UARTDIV 值,见公式。
  2. WordLength:数据帧字长,可选8 位或9 位。如果没有使能奇偶校验控制,一般使用8位;如果使能了奇偶校验则一般设置为9位。
  3. StopBits:停止位设置,可选0.5 个、1 个、1.5 个和2 个停止位,一般我们选择1 个停止位。
  4. Parity : 奇偶校验控制选择, 可选UART_PARITY_NONE ( 无校验) 、UART_PARITY_EVEN (偶校验)以及UART_PARITY_ODD (奇校验)。
  5. Mode:UART 模式选择,有UART_MODE_RX 、UART_MODE_TX 和UART_MODE_TX_RX。
  6. HwFlowCtl:硬件流控可以控制数据传输的进程,防止数据丢失,该功能主要在收发双方传输速度不匹配的时候使用。一般不使能。
  7. OverSampling:设置8倍过采样是否使能

串口初始化过程

在这里插入图片描述

二、轮询方式串口通信

1.接口函数

代码如下(示例):

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • Transmit函数:该函数连续发送数据,发送过程中通过判断TXE标志来发送下一个数据,通过判断TC标志来结束数据的发送;如果在等待时间内没有完成发送,则不再发送,返回超时标志
  • Receive函数:该函数连续接收数据,接收过程中通过判断RXNE标志来接收新的数据;如果在超时时间内没有完成接收,则不再接收数据,返回超时标志

2.示例-固定长度收发

从PC上发送固定数量字符到开发板,开发板收到后原样发回PC,通过串口助手观测发送。

1)使用MX完成串口外设初始化配置

  1. 选择异步模式,无硬件流控
  2. 在参数设置中,设置初始化结构体中的通信参数:使用115200-8-n-1模式,即波特率设置为115200,采用8位数据位,无校验,1位停止位;使能接收和发送,采用16倍过采样。
  3. 在GPIO选项卡中,可使用默认的引脚,也可选择其他引脚
  4. 配置完成后生成工程代码

2)使用MDK完成用户代码编写

代码如下(示例):

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
	uint8_t Rdata[10];  //接收缓冲区
/* USER CODE END PV */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
  /* USER CODE BEGIN 3 */
  //接收5个字符并发送回去
		if(HAL_UART_Receive(&huart1, Rdata, 5, 0)==HAL_OK)
		{
		HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
		HAL_UART_Transmit(&huart1, Rdata, 5, 0);	
		}

3)使用串口助手调试

  1. 串口参数设置与开发板一致
  2. 发送任意5个字符,观察串口助手的接收区和发送区

3.示例-串口重定向

利用串口实现printf函数向串口发送数据,scanf从串口读取数据。

设计思路

  1. 在C语言中,printf函数是将数据格式化输出到屏幕,scanf函数是从键盘格式化输入数据;
  2. 在嵌入式系统中,一般采用串口进行数据的输入和输出;
  3. 重定向是指用户改写 C语言的库函数,当链接器检查到用户编写了与 C库函数同名的函数时,将优先使用户编写的函数,从而实现对库函数的修改;
  4. printf函数内部通过调用 fputc 函数来实现数据输出,scanf函数内部通过调用 fgetc 函数来实现数据输入,因此用户需要改写这两个函数实现串口重定向。

函数实现

需要在函数实现文件中添加标准输入输出头文件"stdio.h"
代码如下(示例):

/* USER CODE BEGIN 0 */
#include "stdio.h"
/* USER CODE END 0 */
//以下两个函数采用轮询方式发送和接收1字节数据,超时时间设置为无限等待
/* USER CODE BEGIN 1 */
int fputc(int ch,FILE* f)
{
  HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,HAL_MAX_DELAY);
  return ch;
}
int fgetc(FILE* f)
{
  uint8_t ch;
  HAL_UART_Receive(&huart1,(uint8_t *)&ch,1,HAL_MAX_DELAY);
  return ch;
}
/* USER CODE END 1 */

三、中断方式串口通信

中断方式不必等待数据的传输过程,只需要在每字节数据收发完成后,由中断标志位触发中断,在中断服务程序中放入新的一字节数据或者读取接收到的一字节数据。
在传输数据量较大,且通信波特率较高(大于38400)时,如果采用中断方式,每收发一个字节的数据,CPU都会被打断,造成CPU无法处理其他事务。因此在批量数据传输,通信波特率较高时,可采用DMA方式。

串口中断处理过程

在这里插入图片描述

1.接口函数

  • 发送过程:每发送一个数据进入一次中断,在中断中根据发送数据的个数来判断数据是否发送完成
  • 接收过程:每接收一个数据进入一次中断,在中断中根据接收数据的个数来判断数据是否接收完成
    代码如下(示例):
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
  • Transmit函数:函数开始将使能串口发送中断,完成指定数量的数据发送后,将会关闭发送中断;因此用户采用中断方式连续发送数据时,需要重复调用此函数,以便重新开启发送中断;当指定数量的数据发送完成后,将调用发送中断回调函数HAL_UART_TxCpltCallback进行后续处理,如连续发送,可在最后再次调用发送函数
  • Receive函数:函数开始将使能串口接收中断,完成指定数量的数据接收后,将会关闭接收中断;因此用户采用中断方式连续接收数据时,需要重复调用此函数以便重新开启接收中断;当指定数量的数据接收完成后,将调用接收中断回调函数HAL_UART_RxCpltCallback进行后续处理,如连续接收,可在最后再次调用接收函数
    代码如下(示例):
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)  
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) 
#define __HAL_UART_CLEAR_FLAG(__HANDLE__, __FLAG__)  
#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
  • 串口通用句柄函数:函数内部首先判断中断类型,并清除对应的中断标志,最后调用回调函数完成对应的中断处理
  • 回调函数:函数内部根据串口句柄实例判断是哪一个串口产生的中断,由用户根据具体的处理任务进行代码实现
  • 宏函数:根据名字很清晰的就能看出功能,使能中断,查询标志,清除标志

2.示例-固定长度收发

采用前后台编程模式。
前台程序为中断服务程序,一旦数据接收完成,则设置一个标志位为1;
后台程序为while(1)的死循环,在循环中不断检测标志位是否为1.如果为1,表明数据接收完成,并存放在接收缓冲区中。然后进行后续处理:先清除标志位,再把接收的数据原样发回。

1)使用MX完成串口外设初始化配置

  1. 选择异步模式,无硬件流控
  2. 在参数设置中,设置初始化结构体中的通信参数:使用115200-8-n-1模式,即波特率设置为115200,采用8位数据位,无校验,1位停止位;使能接收和发送,采用16倍过采样。
  3. 在GPIO选项卡中,可使用默认的引脚,也可选择其他引脚
  4. 使能使用的串口的全局中断,中断优先级使用默认值
  5. 配置完成后生成工程代码

2)使用MDK完成用户代码编写

代码如下(示例):

/* USER CODE BEGIN PD */
/* Private variables ---------------------------------------------------------*/
	#define LENGTH 10  //接收缓冲区的大小
/* USER CODE END PD */
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
	uint8_t RxBuffer[10];  //接收缓冲区
	uint8_t RxFlag = 0;    //接收完成标志:0表示未完成,1表示接收完成
/* USER CODE END PV */
  /* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart1,(uint8_t*)RxBuffer,LENGTH);//触发中断接收,开启接收中断	
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
  /* USER CODE BEGIN 3 */
  //接收5个字符并发送回去
		if(RxFlag == 1)
		{
		   RxFlag = 0;    //清除标志位
		   HAL_UART_Transmit_IT(&huart1,(uint8_t*)RxBuffer,LENGTH);//触发中断发送
		}
  }

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart ->Instance == UART1)
  {
     RxFlag = 1;       //置位接收完成标志
     HAL_UART_Receive_IT(&huart1,(uint8_t*)RxBuffer,LENGTH);  //使能接收中断,连续接收
  }
}
/* USER CODE END 4 */

3.示例-实现简单的帧格式通信(可变长数据)

帧(Frame)是数据传输的一种单位。一帧数据由多个字符组合而成,不同字段的字符代表不同的含义,执行不同的功能;
在实际的工程应用中,数据传输常以帧为单位来进行,如工控领域中最常用Modbus通信协议中的消息帧;发送方按照规定的帧格式发送一帧数据,接收方接收下这一帧数据后,再按照帧格式进行解析,最后完成后续的处理。

Modbus消息帧格式

在这里插入图片描述

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

自定义的帧格式设定

在这里插入图片描述
帧头:0xaa表示一帧数据的开始
设备码:0x01表示LED灯
功能码:0x00表示关灯,0x01表示开灯
帧尾:0x55表示一帧数据的结束

代码实现

代码如下(示例):

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
	uint8_t RxBuffer[4];  //接收缓冲区
	uint8_t RxFlag  = 0;  //接收完成标志:0表示未完成,1表示接收完成
	uint8_t ErrFlag = 0;  //指令错误标志:0表示指令正确,1表示指令错误 
/* USER CODE END PV */
  /* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart1,(uint8_t*)RxBuffer,4);//触发中断接收,开启接收中断	
  /* USER CODE END 2 */
  
    /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
  /* USER CODE BEGIN 3 */
  //接收5个字符并发送回去
		if(RxFlag == 1)
		{
		   RxFlag = 0;    //清除标志位
		   //解析帧格式,处理数据包
		   if(RxBuffer[0] == 0xaa && RxBuffer[3] == 0x55)//判断帧头帧尾
		   {
		      if(RxBuffer[1] == 0x01)                   //判断设备码是否为LED灯
		      {
		        if(RxBuffer[2] == 0x00)                 //读取功能码,这里就两个指令,所以用if语句二选一
		        {
		          HAL_GPIO_WritePin(LED_Port,LED_Pin,GPIO_PIN_RESET);
		        }
		        else if(RxBuffer[2] == 0x01)
		        {
		          HAL_GPIO_WritePin(LED_Port,LED_Pin,GPIO_PIN_SET);
		        }
		        else
		        {
		          ErrFlag =1;  //功能码错误,置位错误标志
		        }
		      }
		      else
		      {
		        ErrFlag = 1; //设备码错误,置位错误标志
		      }
		   }
		   else
		   {
		     ErrFlag = 1;//帧头帧尾不匹配,置位错误标志
		   } 
		if(ErrFlag == 1)
		{
		  printf("ERROR");
		}
		//清除接收缓冲区和错误标志,准备下一次接收
		ErrFlag =0;
		RxBuffer[0] = 0;
		RxBuffer[1] = 0;
		RxBuffer[2] = 0;
		RxBuffer[3] = 0;
		}
  }


/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart ->Instance == UART1)
  {
     RxFlag = 1;       //置位接收完成标志
     HAL_UART_Receive_IT(&huart1,(uint8_t*)RxBuffer,4);  //使能接收中断,连续接收
  }
}
/* USER CODE END 4 */

调试

在串口助手中调试,发送时注意选择十六进制发送,此时发送的即为我们输入的字符,这样就通过自定义的帧完成了灯开关的控制。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值