STM32 CubeMX学习:8. 串口收发

前期的准备

点亮 LED

闪烁 LED

定时器闪烁LED

PWM控制LED亮度

  1. 常见的PWM设备
  2. 按键的外部中断
  3. ADC模数转换
  4. 串口收发
  5. 串口打印遥控器数据
  6. 未完待续…

文章目录

  • STM32 CubeMX学习:8. 串口收发
  • 0 前言
  • 1. 基础学习
  • 1.1 串口接收中断和空闲中断
  • 2. 程序学习
  • 2.1 串口在CubeMX的详细配置
  • 2.2 串口接收中断与空闲中断
  • 2.3 串口发送函数与中断函数
  • 2.4 整体程序介绍
  • 3. 效果展示
  • 4. 进阶学习
  • 4.1 APB时钟计算串口波特率
  • 总结


0 前言

这次的博客我们将在单片机中完成常用的串口功能。串口是一种在单片机,传感器,执行模块等诸多设备上常用的通讯接口,我们可以通过串口读取遥控器发送来的数据,也可以通过串口读取超声波等传感器的数据。 今天,我们将掌握如何通过APB时钟计算串口的波特率,完成串口在cubeMX中的配置方法,实现串口的接收中断与空闲中断功能,了解串口的发送函数与发送中断。

1. 基础学习

1.1 串口接收中断和空闲中断

  1. 串口全称为通用串行通信接口,是一种非常常用的通信接口。串行即以高低电平表示1和0,将数据一位一位顺序发送给接收方。由此看来,通用串行通信接口有着协议简单,易于控制的优点。
  2. 串口的通讯协议由 开始位,数据位,校验位,结束位 构成。一般以一个低电平作为一帧数据的起始,接着跟随8位或者9位数据位,之后为校验位。分为奇校验,偶校验和无校验,最后以一个先高后低的脉冲表示结束位,长度可以设置为0.5,1,1.5或2位长度。
  3. 奇偶校验位的原理是统计发送数据中高电平即’1’的奇偶,将结果记录在奇偶校验位中发送给接收方,接收方收到奇偶校验位后和自己收到的数据进行对比,如果奇偶性一致就接受这帧数据,否则认为这帧数据出错。 下图是一个8位数据位,1位奇偶校验位,1位结束位的串口数据帧。

数据帧内容

长度

功能

起始位

1位

标志帧的起始

数据位

8位或9位

传输数据

奇偶校验位

1位奇校验/偶校验,或无校验位

校验本帧数据是否正确

停止位

0.5,1,1.5或2位

标志帧的结束

  1. 一般进行串口通讯时,收发双方要保证遵守同样的协议才能正确的完成收发,除了协议要一致之外,还有一个非常重要的要素要保持一致,那就是通讯的速率,即波特率。 波特率是指发送数据的速率,单位为波特每秒,一般串口常用的波特率有115200,38400,9600等。串口的波特率和总线时钟周期(clock)成倒数关系,即总线时钟周期越短,单位时间内发送的码元数量越多,串口波特率就越高。

2. 程序学习

2.1 串口在CubeMX的详细配置

  1. 首先在Connectivity标签页下将USART1打开,将其Mode设置为Asynchronous异步通讯方式。异步通讯即发送方和接收方间不依靠同步时钟信号的通讯方式。
  2. 接着将其波特率设置为115200,数据帧设置为8位数据位,无校验位,1位停止位。
  3. 为什么cubemx里没有串口 cubemx 串口接收_为什么cubemx里没有串口

  4. 同样地,打开USART6,将其以和USART1同样的方式进行设置。
  5. 为什么cubemx里没有串口 cubemx 串口接收_stm32_02

  6. 接着前往NVIC标签页下,开启USART1和USART6的中断。
  7. 为什么cubemx里没有串口 cubemx 串口接收_为什么cubemx里没有串口_03

  8. 点击Generate Code生成工程。

2.2 串口接收中断与空闲中断

接收中断

空闲中断

处理函数

USARTx_IRQHandler

USARTx_IRQHandler

回调函数

HAL_UART_RxCpltCallback

HAL库没有提供

USART状态寄存器中的位

UART_FLAG_RXNE

UART_FLAG_IDLE

触发条件

完成一帧数据的接收之后触发一次中断

串口接收完一帧数据后又过了一个字节的时间没有接收到任何数据

串口接收中断即每当串口完成一次接收之后触发一次中断。在STM32中相应的中断处理函数为USARTx_IRQHandler,中断回调函数为HAL_UART_RxCpltCallback。可以通过USART状态寄存器中的UART_FLAG_RXNE位判断USART是否发生了接收中断。

串口空闲中断即每当串口接收完一帧数据后又过了一个字节的时间没有接收到任何数据则触发一次中断,中断处理函数同样为USARTx_IRQHandler,可以通过USART状态寄存器中的UART_FLAG_IDLE判断是否发生了空闲中断。

2.3 串口发送函数与中断函数

HAL库提供了串口发送函数HAL_UART_Transmit,通过这个函数可以从指定的串口发送数据。

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

函数作用

从指定的串口发送一段数据

返回值

HAL_StatusTypeDef,HAL库定义的几种状态,如果成功发送本次数据,则返回HAL_OK

参数1

UART_HandleTypeDef *huart 要进行发送的串口的句柄指针,如串口1就输入&huart1,串口6就输入&huart6

参数2

uint8_t *pData 要发送的数据的首地址,比如要发送buf[]=”Helloword”则输入buf,也可以直接输入要发送的字符串

参数3

uint16_t Size 要发送的数据的大小,即输入的字符串的长度,也可以通过sizeof关键字获取数据大小

参数4

uint32_t Timeout 发送超时时间,如果发送时间超出该时间则取消本次发送

使能发送完成中断后,每当完成一次串口发送,串口会触发一次发送完成中断,对应的中断回调函数为HAL_UART_TxCpltCallback。

2.4 整体程序介绍

为了方便,这次我们没有在中断回调函数中编写代码,而是直接修改了中断处理函数USARTx_IRQHandler。因此发生中断后会直接进入中断处理函数执行用户代码。

我们首先在初始化时初始化串口1和串口6的接收中断和空闲中断,在主循环中通过HAL_UART_Transmit函数完成串口1和串口的6的数据发送,发送内容为“RoboMaster\r\n”这一字符串。

当串口发生接收中断或者空闲中断时,会进入USARTx_IRQHandler中断处理函数。在中断处理函数中通过串口的状态寄存器来判断产生中断的是接收中断还是空闲中断,如果是接收中断则翻转红色LED的电平将其点亮,如果是空闲中断则将其熄灭。

为什么cubemx里没有串口 cubemx 串口接收_串口_04

其中我们的main.c文件如下所示

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>© Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void USART1_IRQHandler(void)  
{
    volatile uint8_t receive;
    //receive interrupt 接收中断
    if(huart1.Instance->SR & UART_FLAG_RXNE)
    {
        receive = huart1.Instance->DR;
        HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);

    }
    //idle interrupt 空闲中断
    else if(huart1.Instance->SR & UART_FLAG_IDLE)
    {
        receive = huart1.Instance->DR;
        HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
    }

}

void USART6_IRQHandler(void)  
{

    volatile uint8_t receive;
    //receive interrupt 接收中断
    if(huart6.Instance->SR & UART_FLAG_RXNE)
    {
        receive = huart6.Instance->DR;
        HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);

    }
    //idle interrupt 空闲中断
    else if(huart6.Instance->SR & UART_FLAG_IDLE)
    {
        receive = huart6.Instance->DR;
        HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
    }


}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* 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();
  MX_USART6_UART_Init();
  /* USER CODE BEGIN 2 */
	HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
  HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		HAL_UART_Transmit(&huart1, "RoboMaster\r\n", 12, 100);
    HAL_Delay(100);
    HAL_UART_Transmit(&huart6, "RoboMaster\r\n", 12, 100);
    HAL_Delay(100);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage 
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 6;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{ 
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

3. 效果展示

当串口1接收到数据的接收中断,LED红灯会点亮,当串口1进入空闲中断,会将LED红灯熄灭;同样的当串口6接收到数据的接收中断,LED绿灯会点亮,当串口1进入空闲中断,会将LED绿灯熄灭

4. 进阶学习

4.1 APB时钟计算串口波特率

在这里我们将学习如何通过APB时钟计算串口的波特率。

计算串口的波特率时首先要通过在定时器章节介绍的方法,通过datasheet查找到使用的串口对应的总线是APB1还是APB2,然后再在CubeMX中找到对应总线的速率。

在获取总线速率后,可以通过USART_BRR寄存器中的USARTDIV值来计算串口的通讯速率。在该寄存器中,前12位用来表示USARTDIV的整数部分,后4位用来表示小数部分,小数部分乘以16后取整,然后存储在后4位中。

使用USARTDIV计算波特率的公式为: Tx / Rx 波特率 = fPCLKx/(16*USARTDIV)

在数据手册中查出USART1连接在APB1总线上, APB1速率PCLK1为42MHz。而期望的USART1的串口通讯速率为115200。

  1. 通过上述的波特率计算公式,计算出USARTDIV值为42MHz/115200/16=22.786;
  2. 将整数22转化为16进制0x16,小数0.786*16=12.576(取整12)用16进制表示为0x0C,所以存储在USART_BRR中的值最终为0x16C。

由于这里采取了一些取整和舍弃尾数的近似手段,所以通过寄存器产生的波特率和最终期望的波特率之间是存在误差的,一般较小的误差不会影响最终的串口通讯,但是如果通讯时出现问题的话,要记得检查是否与通讯速率的实际值与理想值相差较大有关。

为什么cubemx里没有串口 cubemx 串口接收_stm32_05

代码我已经放到了我的GitHub仓库,如有需要可以下载使用:

CubeMX学习


总结

这次的博客,我们学习了STM32的串口功能,串口是一种在诸多设备上使用的通讯接口,通过串口通讯,我们才能够在这些设备中传递信息。在比赛中,可以通过串口读取遥控器发送来的数据,也可以通过串口读取超声波等传感器的数据,也可以使用串口在单片机和运算自瞄程序的电脑之间进行通讯。