• 三、Clion和STM32CubeMx---USART串口通讯(重定向、接发通信、控制LED亮灭)


    准备资料

    1. Clion

    2. STM32CubeMx开发环境

    3. UART串口协议

    4. stm32中断概念

    实验程序已经发布到百度网盘,本文末有链接可以自取

    串口协议查看这篇博客USART串口协议

    stm32中断概念STM32中断应用概括

    工具Clion和STM32CubeMx

    STM32串口通讯

    1.STM32的USART 简介

    通用同步异步收发器 (Universal Synchronous Asynchronous Receiver and Transmitter) 是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。有别于 USART还有一个UART(Universal
    Asynchronous Receiver and Transmitter) ,它是在 USART 基础上裁剪掉了同步通信功能,只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART (异步通信)。
    串行通信一般是以格式传输数据,即是一帧一帧的传输,每帧包含有起始信号、数据信息、停止信息,可能还有校验信息。 USART 就是对这些传输参数有具体规定,当然也不是只有唯一一个参数值,很多参数值都可以自定义设置,只是增强它的兼容性。

    USART 满足外部设备对工业标准 NRZ 异步串行数据格式的要求,并且使用了小数波特率发生
    器,可以提供多种波特率,使得它的应用更加广泛。 USART 支持同步单向通信和半双工单线通信;还支持局域互连网络 LIN、智能卡(SmartCard) 协议与 lrDA(红外线数据协会) SIR ENDEC 规范。USART 支持使用 DMA,可实现高速数据通信。

    USART 在 STM32 应用最多莫过于“打印”程序信息,一般在硬件设计时都会预留一个 USART通信接口连接电脑,用于在调试程序是可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、如果出错哪具体哪里出错等等

    2. USART 功能框图

    USART 的功能框图包含了 USART 最核心内容,掌握了功能框图,对 USART 就有一个整体的把握,在编程时就思路就非常清晰。 USART 功能框图见图 USART 功能框图 。

    在这里插入图片描述
    功能引脚

    • TX:发送数据输出引脚。
    • RX:接收数据输入引脚。
    • SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。
    • nRTS:请求以发送 (Request To Send), n 表示低电平有效。如果使能 RTS 流控制,当 USART 接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时, nRTS 将被设置为高电平。该引脚只适用于硬件流控制。
    • nCTS:清除以发送 (Clear To Send), n 表示低电平有效。如果使能 CTS 流控制,发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。
    • SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    STM32F103C8T6 系统控制器有三个 USART,其中 USART1 和时钟来源于 APB2 总线时钟,其最大频率为 72MHz,其他两个的时钟来源于 APB1 总线时钟,其最大频率为 36MHz。

    2.1 数据寄存器

    USART 数据寄存器 (USART_DR) 只有低 9 位有效,并且第 9 位数据是否有效要取决于 USART控制寄存器 1(USART_CR1) 的 M 位设置,当 M 位为 0 时表示 8 位数据字长,当 M 位为 1 表示 9位数据字长,我们一般使用 8 位数据字长。
    USART_DR 包含了已发送的数据或者接收到的数据。 USART_DR 实际是包含了两个寄存器,一个专门用于发送的可写 TDR,一个专门用于接收的可读 RDR。当进行发送操作时,往 USART_DR写入数据会自动存储在 TDR 内;当进行读取操作时,向 USART_DR 读取数据会自动提取 RDR
    数据。
    TDR 和 RDR 都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的,发送时把TDR 内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到 RDR。
    USART 支持 DMA 传输,可以实现高速数据传输,具体 DMA 使用将在 DMA 章节讲解。

    2.2 控制器

    USART 有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。使用USART 之前需要向USART_CR1 寄存器的 UE 位置 1 使能 USART, UE 位用来开启供给给串口的时钟。
    发送或者接收数据字长可选 8 位或 9 位,由 USART_CR1的 M 位控制。

    2.3 发送器

    当 USART_CR1 寄存器的发送使能位 TE 置 1 时,启动数据发送,发送移位寄存器的数据会在 TX引脚输出,低位在前,高位在后。如果是同步模式 SCLK 也输出时钟信号。
    一个字符帧发送需要三个部分:起始位 + 数据帧 + 停止位。起始位是一个位周期的低电平,位周期就是每一位占用的时间;数据帧就是我们要发送的 8 位或 9 位数据,数据是从最低位开始传输的;停止位是一定时间周期的高电平。
    停止位时间长短是可以通过 USART 控制寄存器 2(USART_CR2) 的 STOP[1:0] 位控制,可选 0.5个1 个1.5 个2 个停止位。默认使用 1 个停止位。 2 个停止位适用于正常 USART 模式、单线模式和调制解调器模式。 0.5 个和 1.5 个停止位用于智能卡模式。
    当选择 8 位字长,使用 1 个停止位时,具体发送字符时序图见图字符发送时序图 。
    在这里插入图片描述

    当发送使能位 TE 置 1 之后,发送器开始会先发送一个空闲帧 (一个数据帧长度的高电平),接下来就可以往 USART_DR 寄存器写入要发送的数据。在写入最后一个数据后,需要等待 USART 状态寄存器 (USART_SR) 的 TC 位为 1,表示数据传输完成,如果 USART_CR1 寄存器的 TCIE 位置为1,将产生中断。
    在发送数据时,编程的时候有几个比较重要的标志位我们来总结下。
    在这里插入图片描述

    2.4 接收器

    如果将 USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在 RX 线开始搜索起始位。在确定到起始位后就根据 RX 线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到 RDR 内,并把 USART_SR 寄存器的 RXNE 位 置1,同时如果USART_CR2 寄存器的 RXNEIE 置 1 的话可以产生中断。
    在接收数据时,编程的时候有几个比较重要的标志位我们来总结下。

    名称描述
    RE接收使能
    RXNE读数据寄存器非空
    RXNEIE接收完成中断使能

    2.5 小数波特率生成

    波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示,单位为波特。比特率指单位时间内传输的比特数,单位bit/s(bps)。对于 USART 波特率与比特率相等,以后不区分这两个概念。波特率越大,传输速率越快。
    USART 的发送器和接收器使用相同的波特率。计算公式如下:
    在这里插入图片描述
    其中, fPLCK 为 USART 时钟, USARTDIV 是一个存放在波特率寄存器 (USART_BRR) 的一个无符号定点数。其中 DIV_Mantissa[11:0] 位定义 USARTDIV 的整数部分,DIV_Fraction[3:0] 位定义USARTDIV 的小数部分。

    例如: DIV_Mantissa=24(0x18), DIV_Fraction=10(0x0A),此时 USART_BRR 值为 0x18A;那么 USARTDIV 的小数位 10/16=0.625;整数位 24,最终 USARTDIV 的值为 24.625。如果知道 USARTDIV 值为 27.68,那么 DIV_Fraction=16*0.68=10.88,最接近的正整数为 11,所以DIV_Fraction[3:0] 为 0xB; DIV_Mantissa= 整数 (27.68)=27,即为 0x1B。波特率的常用值有 2400、 9600、 19200、 115200。下面以实例讲解如何设定寄存器值得到波特率的值。

    我们知道 USART1 使用 APB2 总线时钟,最高可达 72MHz,其他 USART 的最高频率为 36MHz。我们选取 USART1 作为实例讲解,即fPLCK=72MHz。为得到 115200bps 的波特率,此时:

    115200 =72000000/(16 ∗ USARTDIV)

    解得 USARTDIV=39.0625,可算得 DIV_Fraction=0.0625*16=1=0x01, DIV_Mantissa=39=0x27,即应该设置 USART_BRR 的值为 0x271。

    3 校验控制

    STM32F103 系列控制器 USART 支持奇偶校验。当使用校验位时,串口传输的长度将是 8 位的数
    据帧加上 1 位的校验位总共 9 位,此时 USART_CR1 寄存器的 M 位需要设置为 1,即 9 数据位。将 USART_CR1 寄存器的 PCE 位置 1 就可以启动奇偶校验控制,奇偶校验由硬件自动完成。启动了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收数据时自动验证校验位。接收数据时如果出现奇偶校验位验证失败,会见 USART_SR 寄存器的 PE 位置 1,并可以产生奇偶校验中断。
    使能了奇偶校验控制后,每个字符帧的格式将变成:起始位 + 数据帧 + 校验位 + 停止位。

    3.1 中断控制

    USART 有多个中断请求事件,具体见表 USART 中断请求 。
    在这里插入图片描述

    USART1 接发通信实验

    USART 只需两根信号线即可完成双向通信,对硬件要求低,使得很多模块都预留 USART 接口来实现与其他模块或者控制器进行数据传输,比如 GSM 模块, WIFI 模块、蓝牙模块等,以后都会在实验中提及,在硬件设计时,注意还需要一根“共地线”。
    我们经常使用 USART 来实现控制器与电脑之间的数据传输。这使得我们调试程序非常方便,比如我们可以把一些变量的值、函数的返回值、寄存器标志位等等通过 USART 发送到串口调试助手,这样我们可以非常清楚程序的运行状态,当我们正式发布程序时再把这些调试信息去除即可。
    我们不仅仅可以将数据发送到串口调试助手,我们还可以在串口调试助手发送数据给控制器,控制器程序根据接收到的数据进行下一步工作。

    硬件设计

    为利用 USART 实现开发板与电脑通信,需要用到一个 USB 转 USART 的 IC,我们选择CH340G芯片来实现这个功能, CH340G 是一个 USB 总线的转接芯片,实现 USB 转 USART、 USB 转 lrDA红外或者 USB 转打印机接口,我们使用其 USB 转 USART 功能。

    连接引脚
    CH340的TXD-----USART1的RX引脚相连(c8t6 的PA10)
    CH340的RXD-----USART1的TX引脚相连(c8t6 的PA9)

    1. 双击打开STM32CubeMx软件

    • 新建项目在这里插入图片描述

    • 找到自己对应的芯片型号[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MYXiNB7s-1656512609971)(stm32f103.assets/image-20220629200105438.png)]

    出现芯片型号选择 一般我们直接搜索自己芯片的型号即可
    表示警告,对应配置出现问题 点击该选项即可外设配置界面查看

    2. GPIO设置

    因为我们这里用LED,所以还要配置GPIO引脚
    在这里插入图片描述

    • 起一个别名,宏定义
      在这里插入图片描述

    3. 设置RCC

    设置高速外部时钟HSE 选择外部时钟源

    4. 配置调试模式


    ST-Link 就是 Serial Wire 调试模式,一定要设置!!!
    如果不配置 Serial Wire 模式,程序一旦通过 ST-Link 烧录到芯片中,芯片就再也不能被ST-Link 识别了。
    ST-Link V2烧录问题(已解决)

    5. 设置时钟

    外部晶振为8MHz

    1. 选择外部时钟HSE 8MHz
    2. PLL锁相环倍频72倍
    3. 系统时钟来源选择为PLL
    4. 设置APB1分频器为 /2
    5. 这时候定时器的时钟频率为72Mhz

    32的时钟树框图 《【STM32】系统时钟RCC详解(超详细,超全面)》

    6. 配置串口

    在这里插入图片描述
    在这里插入图片描述

    1. 点击USATR1
    2. 设置MODE为异步通信(Asynchronous)
    3. 基础参数:波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1 接收和发送都使能
    4. GPIO引脚设置 USART1_RX/USART_TX
    5. NVIC Settings 一栏使能接收中断

    7.工程管理

    设置完MCU的各个配置之后,第三个就是工程文件的设置了

    • 设置项目名称(USART)
    • 设置存储路径
    • 选择所用IDE(这里用的是Clion,所以用STM32CubeIDE或者SW4STM32都可以)
      在这里插入图片描述
      然后点击上方GENERATE CODE 创建工程

    8. 使用Clion软件打开刚刚生成代码的文件夹

    在这里插入图片描述

    选择自己的配置文件
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gmXAxNny-1656512609999)(stm32f103.assets/image-20220629221932932.png)]

    我这里是自定义配置文件,自己新建一个cfg文件:st_my_F1_stlink-v2.cfg

    source [find interface/stlink-v2.cfg]
    
    transport select hla_swd
    
    source [find target/stm32f1x.cfg]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (7条消息) 基于Clion IDE + STM32CubeMX搭建STM32开发环境(详细介绍搭建过程)_Ch_champion的博客-CSDN博客_clion搭建stm32开发环境

    9. HAL库UART函数库介绍

    UART结构体定义

    UART_HandleTypeDef huart1;
    
    • 1

    UART的名称定义,这个结构体中存放了UART所有用到的功能,后面的别名就是我们所用的uart串口的别名,默认为huart1
    可以自行修改

    在这里插入图片描述

    1、串口发送/接收函数

    • HAL_UART_Transmit();串口发送数据,使用超时管理机制
    • HAL_UART_Receive();串口接收数据,使用超时管理机制
    • HAL_UART_Transmit_IT();串口中断模式发送
    • HAL_UART_Receive_IT();串口中断模式接收
    • HAL_UART_Transmit_DMA();串口DMA模式发送
    • HAL_UART_Transmit_DMA();串口DMA模式接收
      这几个函数的参数基本都是一样的,我们挑两个讲解一下

    串口发送数据:

    HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
    功能:串口发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)。

    参数:

    • UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
    • *pData 需要发送的数据
    • Size 发送的字节数
    • Timeout 最大发送时间,发送数据超过该时间退出发送
    举例:   HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff);   //串口发送三个字节数据,最大传输时间0xffff
    
    • 1

    中断接收数据:

    HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
    
    • 1

    功能:串口中断接收,以中断方式接收指定长度数据。
    大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。接收到数据时,会触发串口中断。
    再然后,串口中断函数处理,直到接收到指定长度数据,而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)

    参数:

    • UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
    • *pData 接收到的数据存放地址
    • Size 接收的字节数
    举例:    HAL_UART_Receive_IT(&huart1,(uint8_t *)&value,1);   //中断接收一个字符,存储到value中
    
    • 1

    2、串口中断函数

    • HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数
    • HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //串口发送中断回调函数
    • HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //串口发送一半中断回调函数(用的较少)
    • HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断回调函数
    • HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//串口接收一半回调函数(用的较少)
    • HAL_UART_ErrorCallback();串口接收错误函数

    串口接收中断回调函数:

    HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);  
    
    • 1

    功能:HAL库的中断进行完之后,并不会直接退出,而是会进入中断回调函数中,用户可以在其中设置代码,

    串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改,
    参数:

    • UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
    举例:   HAL_UART_RxCpltCallback(&huart1){           //用户设定的代码               }
    
    • 1

    串口中断处理函数

    HAL_UART_IRQHandler(UART_HandleTypeDef *huart);  
    
    • 1

    功能:对接收到的数据进行判断和处理 判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用

    如果接收数据,则会进行接收中断处理函数

     /* UART in mode Receiver ---------------------------------------------------*/
      if((tmp_flag != RESET) && (tmp_it_source != RESET))
      { 
        UART_Receive_IT(huart);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果发送数据,则会进行发送中断处理函数

      /* UART in mode Transmitter ------------------------------------------------*/
      if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
      {
        UART_Transmit_IT(huart);
        return;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3. 串口查询函数

    HAL_UART_GetState(); 判断UART的接收是否结束,或者发送数据是否忙碌

    举例:

    while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX)   //检测UART发送结束
    
    • 1

    USART接收与发送

    重定向

    新建retarget.h

    #ifndef _RETARGET_H__
    
    #define _RETARGET_H__
    
    #include "stm32f1xx_hal.h"
    
    #include 
    
    #include 
    
    void RetargetInit(UART_HandleTypeDef *huart);
    
    int _isatty(int fd);
    
    int _write(int fd, char *ptr, int len);
    
    int _close(int fd);
    
    int _lseek(int fd, int ptr, int dir);
    
    int _read(int fd, char *ptr, int len);
    
    int _fstat(int fd, struct stat *st);
    
    #endif //#ifndef _RETARGET_H__
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    新建retarget.c

    #include <_ansi.h>
    
    #include <_syslist.h>
    
    #include 
    
    #include 
    
    #include 
    
    #include 
    
    #include 
    
    #if !defined(OS_USE_SEMIHOSTING)
    
    #define STDIN_FILENO     0
    
    #define STDOUT_FILENO 1
    
    #define STDERR_FILENO 2
    
    UART_HandleTypeDef *gHuart;
    
    void RetargetInit(UART_HandleTypeDef *huart) {
        gHuart = huart;
        /* Disable I/O buffering for STDOUT stream, so that
    
        * chars are sent out as soon as they are printed. */
        setvbuf(stdout, NULL, _IONBF, 0);
    }
    
    
    int _isatty(int fd) {
        if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
            return 1;
        errno = EBADF;
        return 0;
    }
    
    
    int _write(int fd, char *ptr, int len) {
    
        HAL_StatusTypeDef hstatus;
        if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
            hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
            if (hstatus == HAL_OK)
                return len;
            else
                return EIO;
        }
        errno = EBADF;
        return -1;
    }
    
    
    int _close(int fd) {
        if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
            return 0;
        errno = EBADF;
        return -1;
    }
    
    
    int _lseek(int fd, int ptr, int dir) {
        (void) fd;
        (void) ptr;
        (void) dir;
        errno = EBADF;
        return -1;
    }
    
    
    int _read(int fd, char *ptr, int len) {
    
        HAL_StatusTypeDef hstatus;
    
        if (fd == STDIN_FILENO) {
            hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY);
            if (hstatus == HAL_OK)
                return 1;
            else
                return EIO;
        }
        errno = EBADF;
        return -1;
    }
    
    
    int _fstat(int fd, struct stat *st) {
    
        if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) {
            st->st_mode = S_IFCHR;
            return 0;
        }
        errno = EBADF;
        return 0;
    }
    
    #endif //#if !defined(OS_USE_SEMIHOSTING)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    syscalls.c

    添加这两个文件到工程,更新CMake,编译之后会发现,有几个系统函数重复定义了,被重复定义的函数位于Src目录的syscalls.c文件中,我们把里面重复的几个函数删掉即可。

    /**
     ******************************************************************************
     * @file      syscalls.c
     * @author    Auto-generated by STM32CubeIDE
     * @brief     STM32CubeIDE Minimal System calls file
     *
     *            For more information about which c-functions
     *            need which of these lowlevel functions
     *            please consult the Newlib libc-manual
     ******************************************************************************
     * @attention
     *
     * Copyright (c) 2021 STMicroelectronics.
     * All rights reserved.
     *
     * This software is licensed under terms that can be found in the LICENSE file
     * in the root directory of this software component.
     * If no LICENSE file comes with this software, it is provided AS-IS.
     *
     ******************************************************************************
     */
    
    /* Includes */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    /* Variables */
    extern int __io_putchar(int ch) __attribute__((weak));
    extern int __io_getchar(void) __attribute__((weak));
    
    
    char *__env[1] = { 0 };
    char **environ = __env;
    
    
    /* Functions */
    void initialise_monitor_handles()
    {
    }
    
    int _getpid(void)
    {
    	return 1;
    }
    
    int _kill(int pid, int sig)
    {
    	errno = EINVAL;
    	return -1;
    }
    
    void _exit (int status)
    {
    	_kill(status, -1);
    	while (1) {}		/* Make sure we hang here */
    }
    
    int _open(char *path, int flags, ...)
    {
    	/* Pretend like we always fail */
    	return -1;
    }
    
    int _wait(int *status)
    {
    	errno = ECHILD;
    	return -1;
    }
    
    int _unlink(char *name)
    {
    	errno = ENOENT;
    	return -1;
    }
    
    int _times(struct tms *buf)
    {
    	return -1;
    }
    
    int _stat(char *file, struct stat *st)
    {
    	st->st_mode = S_IFCHR;
    	return 0;
    }
    
    int _link(char *old, char *new)
    {
    	errno = EMLINK;
    	return -1;
    }
    
    int _fork(void)
    {
    	errno = EAGAIN;
    	return -1;
    }
    
    int _execve(char *name, char **argv, char **env)
    {
    	errno = ENOMEM;
    	return -1;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111

    在main函数的初始化代码中添加对头文件的引用并注册重定向的串口号:

    #include "retarget.h"
    
    RetargetInit(&huart1); 
    
    • 1
    • 2
    • 3

    main.c

    /* USER CODE BEGIN Includes */
    #include "retarget.h"
    /* USER CODE END Includes */
    
    void SystemClock_Config(void);
    int main(void) {
        HAL_Init();
    
        SystemClock_Config();
    
        /* Initialize all configured peripherals */
        MX_GPIO_Init();
        MX_USART1_UART_Init();
        /* USER CODE BEGIN 2 */
        RetargetInit(&huart1);
        /* USER CODE END 2 */
    
        /* Infinite loop */
        /* USER CODE BEGIN WHILE */
        printf("欢迎来到洛尘的CSDN");
        while (1) {
            /* USER CODE END WHILE */
    
            /* USER CODE BEGIN 3 */
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    UART接收中断

    因为中断接收函数只能触发一次接收中断,所以我们需要在中断回调函数中再调用一次中断接收函数

    具体流程:

    1、初始化串口

    2、在main中第一次调用接收中断函数

    3、进入接收中断,接收完数据 进入中断回调函数

    4、修改HAL_UART_RxCpltCallback中断回调函数,处理接收的数据,

    5、回调函数中要调用一次HAL_UART_Receive_IT函数,使得程序可以重新触发接收中断

    函数流程图:

    HAL_UART_Receive_IT(中断接收函数) -> USART2_IRQHandler(void)(中断服务函数) -> HAL_UART_IRQHandler(UART_HandleTypeDef *huart)(中断处理函数) -> UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数) -> HAL_UART_RxCpltCallback(huart); (中断回调函数)

    HAL_UART_RxCpltCallback函数就是用户要重写在main.c里的回调函数。

    代码实现:

    在main.c中添加下列定义:

    #include 
     
    #define RXBUFFERSIZE  256     //最大接收字节数
    char RxBuffer[RXBUFFERSIZE];   //接收数据
    uint8_t aRxBuffer;			//接收中断缓冲
    uint8_t Uart1_Rx_Cnt = 0;		//接收缓冲计数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在main()主函数中,调用一次接收中断函数

    /* USER CODE BEGIN 2 */
    	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
    /* USER CODE END 2 */
    
    • 1
    • 2
    • 3

    在main.c下方添加中断回调函数

    /* USER CODE BEGIN 4 */
     
    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(Uart1_Rx_Cnt >= 255)  //溢出判断
    	{
    		Uart1_Rx_Cnt = 0;
    		memset(RxBuffer,0x00,sizeof(RxBuffer));
    		HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF); 	
            
    	}
    	else
    	{
    		RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer;   //接收数据转存
    	
    		if((RxBuffer[Uart1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
    		{
    			HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去
                while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
    			Uart1_Rx_Cnt = 0;
    			memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
    		}
    	}
    	
    	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);   //再开启接收中断
    }
    /* USER CODE END 4 */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    发送数据被正常返回
    在这里插入图片描述

    控制LED亮灭

    我们只用将usrt1的HAL_UART_RxCpltCallback函数改一下就行。

    /* USER CODE BEGIN 4 */
    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 (Uart1_Rx_Cnt >= 255)  //溢出判断
        {
            Uart1_Rx_Cnt = 0;
            memset(RxBuffer, 0x00, sizeof(RxBuffer));
            HAL_UART_Transmit(&huart1, (uint8_t *) "数据溢出", 10, 0xFFFF);
        } else {
            RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer;   //接收数据转存
    
            if ((RxBuffer[Uart1_Rx_Cnt - 1] == 0x0A) && (RxBuffer[Uart1_Rx_Cnt - 2] == 0x0D)) //判断结束位
            {
                HAL_UART_Transmit(&huart1, (uint8_t *) &RxBuffer, Uart1_Rx_Cnt, 0xFFFF); //将收到的信息发送出去
                while (HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
                if (strstr(RxBuffer, "LED=0")) {
                    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, 0); //PC口13引脚输出,高电平
                    printf("LED亮\r\n");
                } else if (strstr(RxBuffer, "LED=1")) {
                    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, 1); //PC口13引脚输出,低电平
                    printf("LED灭\r\n");
                }
                Uart1_Rx_Cnt = 0;
                memset(RxBuffer, 0x00, sizeof(RxBuffer)); //清空数组
            }
        }
        HAL_UART_Receive_IT(&huart1, (uint8_t *) &aRxBuffer, 1);   //再开启接收中断
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在这里插入图片描述

    乱码问题

    查看USART串口printf重定向中文乱码这篇博客

    1、重定向

    在这里插入图片描述

    2、接发通信

    在这里插入图片描述

    3、控制LED灯

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    工程链接

    链接:https://pan.baidu.com/s/1BLdJxgdwOHnf209x4AmIKg 提取码:0000

  • 相关阅读:
    Java中使用java.awt.geom.Point2D进行坐标相关的计算(距离、平方等)
    盘点AI的认证
    持续交付-Jenkinsfile 语法
    python类内置隐式方法全解
    Xshell连接Docker版本CentOS7
    基于SpringBoot大学生心理健康咨询管理系统的分析与设计
    FFmpeg 多图片合成视频带字幕和音乐+特效(淡入淡出,圆圈黑色淡出)
    等级保护测评—Linux(CentOs)身份鉴别
    从 0 到 1 打造企业数字化运营闭环
    mosaic实现
  • 原文地址:https://blog.csdn.net/weixin_55999942/article/details/126470659