STM32 串口协议简明教程

前言

本文旨在介绍STM32单片机串口协议的使用。

主要是为了个人复习,一段时间没用,就容易忘记。因此在文章中也不会出现串口的原理等讲解。
本文的重点是利用CubeMX实现一个最基本的串口模板,从而能够在往后的各个项目中得到运用。

本文使用单片机是STM32F407VET6核心板。因为是第一篇关于STM32的文章,我就浅浅讲一下新工程的创建

你将解决以下问题

  • 创建一个最基本的工程模板
  • 完成串口发送消息(使用HAL_UART_Transmit 和 printf)
  • 完成串口回显(中断回调函数的配置)

创建新工程

打开Cube,选择你对应型号的单片机,正常情况下,你会看到这个

首先配置SystemCore,Debug务必选择SerialWire,否则无法重复烧录,你的单片机成一次性的了

接下来配置RCC,选择HSE并找到Crystal Resonator选项,就是选择外部高速晶振。你选择了这个,就意味着时钟树配置的时候你要修改Input Frequency为这个外部高速晶振的频率。

最后,找到NVIC选项,勾选RCC全局中断

完事之后配置一下GPIO,点亮一个LED啥的,来确保代码是正确的。本代码不包括FreeRTOS,我们直接生成裸机代码。

时钟树关键要调节这几个部分。调节完毕直接回车。这里面有几个比较重要的我说一下.

第一个是输入频率。这个为什么是8MHz?

关于Input Frequency,一方面,你可以直接查阅芯片手册,上面会有高速外部晶振的推荐频率。另一方面,也是最实际的,你可以直接询问你购入芯片的商家。大多数商家会直接把这个晶振标注在外面。就不用你费心思查阅了。
第二个是你
一定要选择HSE和PLLCLK
,也就是选择高速外部时钟和锁相环倍增。HCLK直接和下面推荐的max设定一样,来确保单片机发挥了最大性能。

我这边用的是优信电子的F407VET6核心板,他很贴心地帮我标好了。一般407都是这个外部晶振频率

配置完时钟树后,我们可以开始生成工程代码,并测试生成工程的正确性

后面我把A0的初始化改成A1了,所以在main函数的while里面扔如下代码,只要led闪烁了,就说明工程生成是正确的

c 复制代码
while (1)
{
  /* USER CODE END WHILE */
  HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1);
  HAL_Delay(500);
  /* USER CODE BEGIN 3 */
}

使用CubeMX配置串口

找到Connectivity中的UART5选项,并选择Mode为Async...,也就是异步模式。大多数的串口都是异步串口。在上面图片的右侧,展示了这个串口的具体引脚。分别是D2和C12。

串口的波特率、字长等都保持默认。

一般项目中只会涉及波特率的修改,这里BaudRate是115200。

另外,我们的串口是依赖中断来实现接收的,接收也是本文的重点。因此我们需要配置中断,打勾UART5 global interrupt即可

到此为止,你可以生成代码了

串口收发信息

单单完成一个初始化只能直接使用信息的发送。如果要使用信息的接受,则需要在MX_UART5_Init();函数下面添加一个串口接收中断。

HAL_UART_Receive_IT这个函数的作用是,等待串口收到一个rx_data的数据,只要串口收到了rx_data大小的数据,就会触发一次串口回调的中断函数。这个中断函数,咱们后面再谈。

请注意"等待"这个词。意思是只要代码经过了这个函数,串口就会开启一次等待。等待的时候这个任务是挂起的,不会阻塞,因而不会影响到后续代码的执行。

中断回调函数里面写着我们用户自定义的代码,例如实现回显,或者解析数据流等等操作。

c 复制代码
MX_UART5_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart5, &rx_data, sizeof(rx_data));
/* USER CODE END 2 */

这里rx_data定义在usart.c中,并extern出来。这个头文件是cube自动生成的,可以在User里面直接找到

c 复制代码
/* USER CODE BEGIN Includes */
extern uint8_t rx_data;
/* USER CODE END Includes */

另外,为了使用printf,我们在usart.c里面添加fputc函数。这个函数在stdio.c中,是一个弱定义。因此要记得在usart.c中引用这个头文件"stdio.h"

c 复制代码
/* USER CODE BEGIN 0 */
#include "stdio.h"
uint8_t rx_data;
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart5, (uint8_t *)&ch, 1, 0xFFFF);
  return ch;
}
/* USER CODE END 0 */

值得注意的是,如果你要使用printf,你就必须要勾选这个UseMicroLIB的选项,否则printf将无法正常使用。

接下来,我们分别使用HAL_UART_Transmit和printf来实现串口消息打印。

这里值得注意的是,HAL_UART_Transmit在HAL库中有两种,它们的函数原型如下

c 复制代码
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size);

它们的区别在于阻塞和非阻塞。换言之,就是没有IT的会有一个Timeout变量,只要经过了这么长时间,还是没有发送成功,那么单片机就不会因为这个任务毁了整个流程,过了Timeout时长,就不会继续卡住了。

HAL_UART_Transmit_IT则不然,只要信息发送成功,他就会来一个串口中断。但是没啥卵用。我们一般将串口中断用在接收中。

因此我们会喜欢去使用阻塞发送,非阻塞接收

我们用如下代码实现串口消息发送

c 复制代码
int main(void)
{
  /* USER CODE BEGIN 1 */
  const uint8_t test_word[20]="hello!";
  /* 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_UART5_Init();
  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart5, &rx_data, sizeof(rx_data));

  HAL_UART_Transmit(&huart5, test_word, sizeof(test_word), 1000);
  printf("good!");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
    HAL_Delay(500);
  }
  /* USER CODE END 3 */
}

测试结果毫无问题

接着测试串口接收。

找到stm32f4xx_it.c,划到最后面,添加这一段函数。不要忘记在这个c文件的前面引用#include "usart.h"

c 复制代码
/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){  
  if(huart==&huart5){
    printf("%c\n",rx_data);
    HAL_UART_Receive_IT(&huart5, &rx_data, sizeof(rx_data));
  }
}
/* USER CODE END 1 */

为啥这样做就好了?HAL_UART_RxCpltCallback在HAL库中是一个弱定义,这里需要用户去具体实现。串口最终会调用这个中断服务函数。那么要怎么知道具体是哪个串口的中断服务函数?只需要加一个if作为判断即可,写法就类似于if(huart==&huart5)

其中rx_data就是接收到的数据。注意,接受完数据后,如果要持续接收数据,那就必须要继续使用HAL_UART_Receive_IT(&huart5, &rx_data, sizeof(rx_data));

这样,即使你在串口助手里面输入一堆字符串,它也会一个一个处理,一个一个显示出来。因为你的每一个字符都会触发一次中断回调函数。

至此,一个串口的收发基本模板已经形成,可以开始放进你的具体工程实践了。

相关推荐
BreezeJuvenile9 分钟前
外设模块学习(5)——DS18B20温度传感器(STM32)
stm32·嵌入式硬件·学习·温度传感器·ds18b20
hollq3 小时前
STM32F103RCT6+STM32CubeMX+keil5(MDK-ARM)+Flymcu实现串口重定向
arm开发·stm32·嵌入式硬件
小鱼儿电子4 小时前
17-基于STM32的宠物饲养系统设计与实现
stm32·嵌入式硬件·物联网·宠物·宠物饲养系统
小莞尔6 小时前
【51单片机】【protues仿真】基于51单片机四层电梯系统
单片机·嵌入式硬件
CFZPL6 小时前
使用江科大串口发送函数发送freertos的vTaskList出现跑飞
单片机
F133168929576 小时前
WD5030A,24V降5V,15A 大电流,应用于手机、平板、笔记本充电器
stm32·单片机·嵌入式硬件·51单片机·硬件工程·pcb工艺
易享电子8 小时前
基于单片机电器断路器保护器系统Proteus仿真(含全部资料)
单片机·嵌入式硬件·fpga开发·51单片机·proteus
爱倒腾的老唐10 小时前
01、如何学习单片机
单片机·嵌入式硬件·学习
点灯小铭10 小时前
基于单片机的夹具压力控制系统设计
单片机·嵌入式硬件·mongodb·毕业设计·课程设计
雾削木16 小时前
stm32解锁芯片
javascript·stm32·单片机·嵌入式硬件·gitee