STM32CubeMX5.1.0使用教程,以STM32L431为例(三):串口通信

写在前面

    串口,说简单其实是个很简单的东西,但架不住涉及的东西比较多,STM32串口功能很强大,同步的,异步的,还有红外都集成了,还涉及很多概念,中断、DMA、DMA还有DMA中断。。。这里讲的东西说实在,还是很基础,而且受限于篇幅,这里写不说DMA,后面打算更一篇单独说DMA的

1 串口简介

    串行通讯接口,简称串口。即数据在通信线上一次传输一位,按先后一定顺序传输。我们通常所说的单片机串口准确来说应该是串行异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART),使用TTL电平。常用两根数据线,即TXD和RXD,配合GND至少需要三根线即可进行通信。以下除特殊说明,串口均指代单片机UART串口

    串口信息以数据线上的高低电平表示,高电平为1,低电平为0

图1 串口时序图

    图1为数据线上典型时序图,数据线空闲时为高电平,以一位低电平最为起始(除特殊说明,串口大部分都是一位起始位),而后以先传输低位,再传输高位的方式发送数据位,最后以高电平为数据停止位(常用1位停止位,也有2位和1.5位等)。数据位长度此处为8位,但不一定是8位,也会有7位、9位等,有时也会包含有奇偶校验位(校验位实际上就是数据的最后一位,但通常不算在数据位里)。上图所示即我们常用的8位数据位,无校验,一位停止位的串口格式

    最为异步通信接口,串口不具有时钟线,所以需要在通信双方设置数据位长度,即电平持续时间,其倒数为电平转换的频率,这一参数即为波特率(但注意,波特的概念并不是位的概念,此处按下不表)。例9600波特率即为电平改变频率为9600Hz,电平持续时间1/9600秒,则一秒最多传输9600/(1+8+1)=960个字节(不严谨,但差不多)

    因串口发送数据线与接收数据线分离,其为全双工接口,串口最少使用三根线即可完成通讯,但除这三根线外还可以有硬件流控RTS、CTS等,我没用到过,具体我也不是很清楚

区分UART、TTL、RS-232、RS-422、RS-485

    UART即使用TTL电平的串口,TTL是电平形式,通常低于0.8V为低电平,高于2.4V为高电平,不是协议格式,但我们有时也常说TTL串口,以区分RS-232串口

    RS-232串口数据格式与上文介绍的UART串口基本一致,但是电平不一样,通常以-3V至-15V为逻辑1,3V-15V为逻辑0,与UART不可混接,有烧板子的风险

    RS-422常用4线制和2线制,4线制发送和接收均有两根线,两根线上为差分信号,增加传输距离,全双工。2线制的两根线上为差分信号,同时只能做为发或者收,半双工

    RS-485为422的改进版本,最少可用两根线通信,因为使用差分信号,不使用地线也可正常通信,但不建议,半双工

2 设置STM32串口

    STM32有多种类型串口UART、USART、LPUART等,这些串口功能强大,我们现在仅讨论UART功能。在这里,我们选用USART的异步收发功能即UART功能

    新建工程、时钟配置等操作请参考博客STM32CubeMX5.1.0使用教程,以STM32L431为例(二):新建工程、时钟、gpio

    如下图所示,点击左侧1处,在2处选择Asynchronous(异步),即出现3处,在3处我们可以设置数据位、波特率等参数,这里我们使用默认的,也是最常用的8位数据位,无校验,1位停止位,115200波特率。在2中选择完后,4处也会自动设置引脚功能。除PA9、PA10外,USART还可以设置为PB6和PB7,若有需要,仅需在响应引脚上左键并选择功能即可,系统会自动切换。

图2 Cube串口设置

    设置好后,我们点击GENERATE CODE按钮,生成keil工程并打开

3 串口发送与printf函数

3.1 串口发送函数

    Cube库的串口发送函数有三个,函数原型分别如下所示:

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

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size)

    三个函数的返回值均为硬件状态,通过对硬件状态的判断,可以得知硬件是否已准备好发送下一帧数据。第一个参数为串口结构体,用于指向指定串口号,第二个参数为需要发送数据的首地址,第三个参数为发送字节数。第一个原型还具有第四个参数,因其未使用中断或DMA,需要指定超时时间,单位为毫秒,经过超时时间发送未完成则终止发送。

    当前,我们还未使用中断和DMA,则使用第一个函数原型进行串口发送测试。

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
    HAL_UART_Transmit(&huart1, "hello\r\n", 7, 1);
    HAL_Delay(1000);
    /* USER CODE END WHILE */

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

    如上所示,在生成的main.c指定位置编写代码(包含有自动生成的代码,便于读者定位,以下所有代码均如此),这样在Cube重新生成工程后,自己编写的代码也不会被覆盖。编译,下载,打开串口助手即可收到约一秒一次的hello\r\n(\r\n为换行)数据。

3.2 printf函数

    使用printf函数能够更加方便的通过串口发送数据。

    首先,如图3所示,在项目选项中勾选Use Micro LIB选项

图3 勾选Use Micro LIB选项

    printf函数会调用fputs函数,fputs函数为弱定义函数,即使用__weak修饰符修饰的函数,可通过自己编写函数定义覆盖原定义,我们在此处重写fputs函数。在usart.c中添加如下代码,同样,注意添加位置,避免重新生成工程后被覆盖

/* USER CODE BEGIN 0 */
#include <stdio.h>
#ifdef __GNUC__
	#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
	#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
		HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);
		return ch;
}
/* USER CODE END 0 */

    __GNUC__宏定义标志是否实用的是GCC编译器,因函数原型与类型定义在stdio.h中声明,所以需包含该文件,这样,我们就能使用printf函数向串口发送数据了

    添加完后,在main.c中添加如下编写如下两段代码

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
    printf("hello world\r\n");
    HAL_Delay(1000);
    /* USER CODE END WHILE */

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

    编译,下载即可在串口助手收到熟悉的hello world

    通常,我们接收串口数据会使用中断方式,所以这里不展开讲如何接受数据。下面在串口中断中将解如何使用中断方式接收串口数据。

4 串口中断

图4 串口中断设置

    如图4所示设置串口中断,选中NVIC选项,而后勾选串口中断使能,3处可以更改串口中断的抢占优先级和子优先级。设置好后,再次点击GENERATE CODE按钮,重新生成keil工程并打开。此前自己编写的代码如果都是放在了指定位置上,那么重新生成代码并不会覆盖我们自己编写的代码

4.1 串口中断发送

    将重定向的puts函数作如下更改,主函数不变

PUTCHAR_PROTOTYPE
{
    HAL_UART_Transmit_IT(&huart1 , (uint8_t *)&ch, 1);
    return ch;
}

    此处使用使用中断方式发送数据,编译,下载,观察串口助手输出,发现本应输出hello world结果,但仅输出了单个字符h,这是因为printf函数不断调用puts函数,第一个字符h正在发送中,HAL_UART_Transmit_IT便又一次被调用,该函数便将该字符丢弃并返回HAL_BUSY串口状态。通过这一返回值,我们对函数进行如下修改

PUTCHAR_PROTOTYPE
{
		while(HAL_UART_Transmit_IT(&huart1 , (uint8_t *)&ch, 1)==HAL_BUSY);
		return ch;
}

    此处while循环判断若返回值为正忙,则不断重新调用,保障数据均能正常发送。此时编译,下载即可观察到正确结果

4.2串口中断接收

    串口接收函数同样有三个,函数原型分别如下所示

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

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size)

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size)

    三个函数分别为不使用中断和DMA接收,使用中断方式接收,使用DMA方式接收。我们这里只讲中断方式,函数参数与发送一致,就不说了

    当调用HAL_UART_Receive_IT后,函数便会使能串口接收中断,并等待接收Size个字节的数据,使用这种方式,函数并不会一直占用CPU时间,我们还可以继续进行其他操作。

    那我们如何知道是否已经接收完成了呢?当Size个字节接收完后,中断将自动调用HAL_UART_RxCpltCallback函数,该函数也为若定义函数,我们可重写该函数。注意,当Size字节接收完后,串口接收中断会被自动关闭,需要再次调用HAL_UART_Receive_IT函数进行接收。

    在main.c文件特定位置添加如下几段代码

/* USER CODE BEGIN PV */
uint8_t RxBuffer[20];
uint8_t isRxDone = 0;
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, RxBuffer, 5);
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
    if (isRxDone == 1)
    {
        HAL_UART_Transmit_IT(&huart1, RxBuffer, 5);
        printf("hello world");
        isRxDone = 0;
        HAL_UART_Receive_IT(&huart1, RxBuffer, 5);
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)    //判断是否为串口1
    {
    isRxDone = 1;
    }
}
/* USER CODE END 4 */

    添加完后,编译,下载,通过串口助手发送5字节数据,便会收到同样的5字节数据。

写在后面

    串口的中断接收方式可能在实际项目开发中存在一定的局限性,不够灵活,当然也可以选择使用__HAL_UART_ENABLE_IT函数使能中断,并在stm32l4xx_it.c文件中的USART1_IRQHandler函数中编写自己的中断逻辑,这样具有更好地灵活性与定制功能。

    最近确实比较忙,有的项目要结题,有的项目催进度,有的项目改需求。。。看到有人评论催更,看到有人点赞,还有许多人看这篇教程,感谢大家,就赶紧更了一篇。后面我也会尽快更新的。

  • 19
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
引用\[1\]:ST官网下不到我们熟悉的标准外设库(StdPeriph_Lib)了,取而代之的是HAL库和LL库,以及STM32CubeMX软件。使用cube软件进行stm32开发具有以下优缺点:优点是开发速度快,无须关心硬件底层操作,使开发者注意力集中于上层应用逻辑,便于不同型号stm32单片机间的项目迁移。缺点是运行效率较标准库及寄存器操作慢,不利于对STM32硬件系统的了解。\[1\] 引用\[2\]:如果你想安装stm32标准库,你可以打开ST官网链接,然后选择对应的系列,点击下载相应的版本。在下载之前,你需要接受一份协议并验证你的邮箱。安装固件库后,你就可以开始使用stm32标准库进行开发了。\[2\] #### 引用[.reference_title] - *1* [STM32CubeMX5.1.0使用教程,以STM32L431为(一):简介、下载、安装](https://blog.csdn.net/u013229188/article/details/88252723)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [STM32CubeMX——固件库下载以及安装](https://blog.csdn.net/qq_54784198/article/details/126732377)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值