基于CUBEMX的HAL库串口通信实操代码

1、摘要

  • 芯片采用为RoboMaster开发板C型,型号STM32F407IGHx。

  • 使用了串口+DMA+中断,CUBEMX中展示通信配置以及部分注释

  • 内容都是互通的,代码大体框架可以用于其他芯片上,弄清框架即可

  • 列举例子:获取遥控器数据;获取处理视觉传输数据

2、概念理论

2.1 串口

        串口(Serial Port)是一种通过串行传输数据的通信接口。常用于在计算机系统和外部设备之间进行数据传输,例如连接计算机与打印机、鼠标、调制解调器、传感器、单片机等。串口传输以比特流的形式进行,其中每个数据位依次传输。串口通信有两根线,一根用于发送(TX,传输),另一根用于接收(RX,接收)。用于调试串口的比较常用的是USB转TTL,上面有四根线,分别对应了VCC、TX、RX、GND。

2.2 中断

        中断是一种计算机硬件或软件机制,用于在发生特定事件时中断当前正在执行的程序,转而执行与该事件相关的处理程序。中断可以分为

  1. 硬中断:由处理器外部的硬件设备触发,例如硬件故障、定时器溢出、外部设备请求等。串口中断通常属于硬中断,因为它与外部设备(串口)的状态变化有关。

  2. 软中断:由软件生成的中断,通常是由程序中的特定条件或系统调用触发。软中断可以用于实现一些特殊功能,例如操作系统的系统调用。

2.3 DMA

        DMA代表Direct Memory Access(直接内存访问),计算机系统中用于在主处理器不直接参与数据传输的情况下,将数据从一个地方传输到另一个地方。通过使用DMA,数据传输可以在不占用主处理器时间的情况下完成,提高了系统的效率。

2.4 串口DMA中断

        在串口通信中,中断是一种机制,通过该机制,处理器可以及时响应串口的状态变化或数据接收事件,而无需不断地轮询串口状态。这样可以提高系统的效率,使处理器能够在等待串口事件发生的过程中执行其他任务。其中可以触发串口中断的条件可能是:

  1. 数据接收中断:当串口接收缓冲区中有新的数据可用时,会触发数据接收中断,处理器会中断当前执行的任务,转而执行数据接收的中断服务程序,以处理接收到的数据。

  2. 数据发送中断:当串口发送缓冲区为空时,表示可以发送新的数据,触发数据发送中断,处理器中断当前执行的任务,执行数据发送的中断服务程序,将需要发送的数据放入发送缓冲区。

  3. 错误中断:串口发生错误时,如奇偶校验错误、帧错误等,可能会触发错误中断,处理器中断当前执行的任务,执行错误处理的中断服务程序。

        串口DMA中断指的是在串口通信中,使用DMA进行数据传输时可能发生的中断。串口通信是通过串行传输数据的一种方式,而DMA可以用于提高串口通信的效率。当使用串口DMA时,DMA控制器负责管理数据的传输,而不需要主处理器的直接干预。但是,当某个数据传输完成或者发生某些特定事件时,系统可能会触发一个中断,以便主处理器能够及时处理相关的事务。因此,串口DMA中断是指在串口通信中,使用DMA进行数据传输时,当需要主处理器介入时触发的中断。这样的中断可以用于处理数据传输完成后的收尾工作或处理其他需要主处理器干预的情况。

        比如想要串口一直接收/发送数据,需求是数据实时更新获取,但又不希望占用资源或者需要等待其他任务结束,所以需要开启中断功能。

3、CUBEMX配置介绍

3.1 模式Mode选择:

基于CUBEMX的HAL库串口通信实操代码_第1张图片

  1. Disable(禁用):

    1. 意义: 表示禁用串口功能,即不启用串口通信。

    2. 用途: 在某些情况下,可能需要禁用串口以节省电力或资源。

  2. Asynchronous(异步):

    1. 意义: 异步串口是一种常见的串口模式,其中数据的传输不依赖于定时时钟。每个数据字节都包含起始位、数据位、可选的奇偶校验位和停止位。

    2. 用途: 适用于大多数通用串口通信,例如连接计算机和外设。

  3. Synchronous(同步):

    1. 意义: 同步串口使用时钟信号同步数据传输。通常,数据字节之间没有间隔,而是通过时钟信号同步传输。

    2. 用途: 适用于高速数据传输,其中需要更精确的时序。

  4. Single Wire (Half-Duplex)(单线半双工):

    1. 意义: 单线半双工模式允许在同一根线上进行数据传输和接收,但不能同时进行。

    2. 用途: 适用于资源受限或只需半双工通信的场景,例如简单的串口通信。

  5. Multiprocessor Communication(多处理器通信):

    1. 意义: 用于与多个处理器进行通信,每个处理器有唯一的地址。

    2. 用途: 适用于多处理器系统,其中需要通过串口与不同处理器进行通信。

  6. IrDA(红外数据协会):

    1. 意义: IrDA模式允许使用红外线进行无线通信。

    2. 用途: 适用于需要通过红外线进行通信的场景,例如红外线通信设备之间的连接。

  7. LIN(局域网互联):

    1. 意义: 用于局域网互联(LIN)总线通信,主要用于在汽车电子系统中进行通信。

    2. 用途: 适用于汽车电子系统中的通信,例如连接汽车内部的各种控制模块。

  8. SmartCard(智能卡):

    1. 意义: 用于与智能卡进行通信,智能卡通常包含存储和处理能力,可与其他设备进行安全通信。

    2. 用途: 适用于需要与智能卡进行通信的场景,例如金融交易或身份验证系统。

Mode这里选择常用的异步通信(Asynchronous)模式

3.2 串口配置参数

基于CUBEMX的HAL库串口通信实操代码_第2张图片

 

  1. Baud Rate(波特率):

    • 波特率是指在一秒钟内传输的比特数,它决定了数据传输的速度。常见的波特率包括9600、115200等。

    • 波特率必须在发送和接收设备之间保持一致,以确保正确的数据传输。

    • 串口发送和接收的波特率必须一样。波特率是指串口通信中每秒钟传输的比特数,它用来确定信号的速度。如果发送端和接收端的波特率不一致,就会导致通信错误,因为接收端无法正确地解析发送端的数据。

  2. Word Length(数据位长度):

    1. 表示每个数据字节中的比特数,通常为8位。

    2. 数据位长度定义了每个数据字节的大小,包括有效数据位和可能的奇偶校验位。

  3. Parity(奇偶校验):

    1. 用于检测数据传输中的错误,可以设置为

    2. 无奇偶校验(None):在数据传输中不使用奇偶校验位,接收端不检查数据位中的奇偶性

    3. 奇/偶校验(Odd/Even):设置奇(偶)校验时,系统会确保每个数据字节中的位数为奇(偶)数,以便在传输中检测单个比特的错误。

  4. Stop Bits(停止位):

    1. 表示在每个数据字节之后的停止位的数量,通常为1或2。

    2. 停止位用于告知接收器数据字节的结束,以便接收器可以准确地将各个字节分隔开。

  5. Data Direction(数据方向):

    1. 指定串口通信的数据方向。数据方向决定了串口是用于单向传输还是双向通信。

    2. Receive and Transmit(接收和发送):串口通信支持双向数据传输,即既可以接收数据又可以发送数据。

    3. Receive Only(仅接收):串口通信只能接收数据而不能发送数据。需要从另一设备接收数据的场景,例如从传感器或测量设备中读取数据。

    4. Transmit Only(仅发送):串口通信只能发送数据而不能接收数据。需要将数据传输到另一设备的场景

  6. Over Sampling(过采样):

    1. 指在每个位时间内对信号进行采样的次数,通常为16倍过采样。

    2. 过采样可以提高系统对输入信号的灵敏度,减少误差。

3.3 中断配置

基于CUBEMX的HAL库串口通信实操代码_第3张图片

  1. 中断使能,Enabled勾选上才可以生效

  2. Preemption Priority(抢占优先级):在操作系统中用来确定哪个任务或线程能够抢占(中断)当前正在执行的任务或线程的优先级。当一个高优先级的任务需要执行时,它可以抢占当前正在执行的低优先级任务,以确保及时响应紧急任务。

  3. 这里因为我开启了DMA,所以显示也DMA1

3.4 DNA配置

基于CUBEMX的HAL库串口通信实操代码_第4张图片

  1. DMA Request : DMA传输的对应外设。

  2. Stream:DMA通常由多个通道(Channel)组成,而每个通道则有一个或多个Stream。每个Stream可以看作是DMA通道中的一个工作单元,负责处理特定的数据传输。Stream配置包括设置源地址、目标地址、数据传输方向、数据大小、传输模式等。

  3. Direction:DMA传输方向,一共有下面四个选项,这里选“外设到内存 Peripheral To Memory”

    1. 外设到内存 Peripheral To Memory

          数据从外设(通常是一些外部设备或外围设备,比如传感器、ADC、UART等)传输到内存。

    2. 内存到外设 Memory To Peripheral

          数据从内存传输到外设。通常在将大量数据传输到外设缓冲区时使用,例如将内存中的数据写入到SD卡、发送到UART等。

    3. 内存到内存 Memory To Memory

          数据在两个内存区域之间进行传输,而不涉及外设。这种方向通常用于内存块之间的数据复制,例如在内存中进行数据处理后,将结果存储到另一个内存区域。

    4. 外设到外设 Peripheral To Peripheral

          数据直接从一个外设传输到另一个外设,而不涉及中间的内存。这种情况下,DMA充当中间的数据传输引擎,将数据从一个外设移动到另一个外设,而不需要经过主处理器的介入。

  4. Priority:DMA的传输速度

  5. Mode:DMA传输模式

    1. Normal:正常模式

          当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次

    2. Circular: 循环模式

          传输完成后又重新开始继续传输,不断循环永不停止

  6. Peripheral和Memory:用于确定数据传输的方向

    1. Peripheral(外设):

      1. 外设是指连接到微控制器的附加设备或模块,如传感器、通信接口(UART、SPI、I2C等)、定时器、ADC(模数转换器)等。外设负责与微控制器进行通信或执行特定的任务。在DMA中,"Peripheral"通常指的是从这些外设读取或向其写入数据的数据寄存器。

    2. Memory(内存):

      1. 内存是存储程序和数据的区域。在微控制器中,内存通常分为不同的类型,包括Flash存储器(用于存储程序代码)、RAM(随机存取存储器,用于存储变量和运行时数据)等。在DMA中,"Memory"通常指的是RAM,即将数据从一个RAM区域传输到另一个RAM区域。

    3. 这里所选要依据Direction中选择的DMA传输方向末端,比如我选择了“Peripheral To Memory(外设到内存)”,所以数据传输方向选择Memory。

  7. Increment Address:用于指定DMA在进行连续数据传输时是否应该自动增加源地址和/或目标地址。

    1. 在DMA传输过程中,如果启用了地址自增,那么在每次数据传输完成后,DMA将自动递增源地址和/或目标地址,以使下一次传输能够操作新的数据。这对于处理连续存储器块或外设寄存器组是非常有用的。

  8. Data Width:数据宽度,即每次传输的数据大小,DMA传输期间要读取或写入的数据字节的数量

    1. Byte(字节):

          表示每次传输一个字节的数据。如果你选择了字节宽度,DMA将每次传输一个字节的数据。

    2. Half Word(半字):

          表示每次传输两个字节的数据。这通常对应于16位的数据类型。如果选择了半字宽度,DMA将每次传输两个字节的数据。

    3. Word(字):

          表示每次传输四个字节的数据。在32位系统中,一个字通常对应于32位的数据类型。如果你选择了字宽度,DMA将每次传输四个字节的数据。

    4. 如果处理的是8位数据,例如字符或字节,那么选择Byte字节宽度更合适。如果处理的是16位数据,例如整数或半字数据类型,那么选择半字宽度更适合。如果处理的是32位数据,例如长整数或单精度浮点数,那么选择字宽度是合适的。这里配置的数据宽度会影响到你通过DMA获取数据的数据类型,要多注意这里。

4、代码介绍

4.1 HAL库结构体相关内容注释

4.1.1 USART_TypeDef

该结构体表示了一个 USART(通用异步收发传输器)寄存器的布局,这个结构体的成员对应了 USART 寄存器的各个字段,每个字段有特定的地址偏移量。这些成员描述了 USART 的状态、数据传输、波特率设置以及控制寄存器等。以下是注释

typedef struct
{
  __IO uint32_t SR;         /*!< USART Status register,                   Address offset: 0x00 */
  __IO uint32_t DR;         /*!< USART Data register,                     Address offset: 0x04 */
  __IO uint32_t BRR;        /*!< USART Baud rate register,                Address offset: 0x08 */
  __IO uint32_t CR1;        /*!< USART Control register 1,                Address offset: 0x0C */
  __IO uint32_t CR2;        /*!< USART Control register 2,                Address offset: 0x10 */
  __IO uint32_t CR3;        /*!< USART Control register 3,                Address offset: 0x14 */
  __IO uint32_t GTPR;       /*!< USART Guard time and prescaler register, Address offset: 0x18 */
} USART_TypeDef;
  • SR(Status Register): USART 状态寄存器,包含有关 USART 状态的信息,如接收寄存器非空、发送寄存器为空等。

  • DR(Data Register): USART 数据寄存器,用于读写 USART 数据。

  • BRR(Baud Rate Register): USART 波特率寄存器,用于设置 USART 的波特率。

  • CR1(Control Register 1): USART 控制寄存器 1,包含一些 USART 的基本配置,如启用 USART、配置数据位数、使能接收和发送等。

  • CR2(Control Register 2): USART 控制寄存器 2,包含更多的配置选项,如配置停止位数、使能硬件流控制等。

  • CR3(Control Register 3): USART 控制寄存器 3,包含一些其他的配置选项,如配置奇偶校验、使能 DMA 传输等。

  • GTPR(Guard Time and Prescaler Register): USART 保护时间和分频器寄存器,用于配置 USART 的保护时间和分频器。

4.1.2 DMA_Stream_TypeDef

DMA(Direct Memory Access)控制器的一个数据流(stream)的寄存器布局。

typedef struct
{
  __IO uint32_t CR;     /*!< DMA stream x configuration register      */
  __IO uint32_t NDTR;   /*!< DMA stream x number of data register     */
  __IO uint32_t PAR;    /*!< DMA stream x peripheral address register */
  __IO uint32_t M0AR;   /*!< DMA stream x memory 0 address register   */
  __IO uint32_t M1AR;   /*!< DMA stream x memory 1 address register   */
  __IO uint32_t FCR;    /*!< DMA stream x FIFO control register       */
} DMA_Stream_TypeDef;
  1. CR(Configuration Register): DMA 数据流配置寄存器,用于配置 DMA 数据流的工作模式、数据宽度、传输方向等。

  2. NDTR(Number of Data Register): DMA 数据流数据数量寄存器,指定要传输的数据的数量。

  3. PAR(Peripheral Address Register): DMA 数据流外设地址寄存器,指定外设的地址,从该地址传输数据。

  4. M0AR(Memory 0 Address Register): DMA 数据流内存 0 地址寄存器,指定内存的地址,数据将从外设传输到这个地址。

  5. M1AR(Memory 1 Address Register): DMA 数据流内存 1 地址寄存器,用于双缓冲区模式,指定内存的另一个地址。

  6. FCR(FIFO Control Register): DMA 数据流 FIFO(First In, First Out)控制寄存器,用于配置和控制 DMA 数据流的 FIFO。

4.2 开启函数框架

这里将一些开启函数封装在一个函数中,使能DMA串口接收、使能串口中断以及配置双缓冲Memory1和Memory0等等

DMA_HandleTypeDef hdma_usart3_rx;
UART_HandleTypeDef huart3;
static uint8_t sbus_rx_buf[2][SBUS_RX_BUF_NUM];
void UR3_Init(uint8_t *rx1_buf, uint8_t *rx2_buf, uint16_t dma_buf_num)
{
    //使能DMA串口接收
    SET_BIT(huart3.Instance->CR3, USART_CR3_DMAR);

    //使能空闲中断
    __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);

    //失效DMA
    __HAL_DMA_DISABLE(&hdma_usart3_rx);
    while(hdma_usart3_rx.Instance->CR & DMA_SxCR_EN)
    {
        __HAL_DMA_DISABLE(&hdma_usart3_rx);
    }

    hdma_usart3_rx.Instance->PAR = (uint32_t) & (USART3->DR);

    //内存缓冲区1 Memory0
    hdma_usart3_rx.Instance->M0AR = (uint32_t)(rx1_buf);

    //内存缓冲区2 Memory1
    hdma_usart3_rx.Instance->M1AR = (uint32_t)(rx2_buf);

    //数据长度
    hdma_usart3_rx.Instance->NDTR = dma_buf_num;

    //使能双缓冲区
    SET_BIT(hdma_usart3_rx.Instance->CR, DMA_SxCR_DBM);

    //使能DMA
    __HAL_DMA_ENABLE(&hdma_usart3_rx);
}

4.3 中断服务程序内容框架

4.3.1 流程

  1. 检查是否有数据接收(UART_FLAG_RXNE)以及是否接收到空闲线状态(UART_FLAG_IDLE)。

  2. 如果有数据接收,清除奇偶校验错误标志。

  3. 根据当前使用的内存缓冲区是 Memory 0 还是 Memory 1,切换 DMA 的内存缓冲区,使得数据传输可以连续进行。

  4. 失效DMA,获取接收数据的长度,重新设定数据长度,设定成功后,再重启DMA

  5. 根据长度判断是否接收到完整的遥控器数据帧。

  6. 如果接收到完整的遥控器数据帧,进行相应的数据处理和记录。

  7. 最后,将数据发送到 USART1。

4.3.2 代码及注释

DMA_HandleTypeDef hdma_usart3_rx;
UART_HandleTypeDef huart3;
static uint8_t sbus_rx_buf[2][SBUS_RX_BUF_NUM];
void USART3_IRQHandler(void)
{
    //检查 USART3 的状态寄存器中是否设置了接收缓冲区非空(RXNE)标志。如果结果非零,表示接收缓冲区非空,即有可读取的数据。
    if (huart3.Instance->SR & UART_FLAG_RXNE)
    {   //如果表达式的结果为非零,说明 USART3 的接收缓冲区非空,可以读取数据。如果结果为零,说明接收缓冲区为空,没有可读取的数据。
       
        __HAL_UART_CLEAR_PEFLAG(&huart3); // 清除奇偶校验错误标志
    }
    
    // 检查是否接收到空闲线状态(通常表示一帧数据接收完毕)
    else if (USART3->SR & UART_FLAG_IDLE)//UART_FLAG_IDLE 是 USART 空闲线状态标志,表示检测到了空闲线状态,通常表示一帧数据的接收完成。
    {
        static uint16_t this_time_rx_len = 0;
        __HAL_UART_CLEAR_PEFLAG(&huart3);// 清除奇偶校验错误标志
        
        // 判断当前使用的内存缓冲区是 Memory 0 还是 Memory 1
        if ((hdma_usart3_rx.Instance->CR & DMA_SxCR_CT) == RESET)//DMA_SxCR_CT 是一个宏,表示 DMA 控制寄存器中的 CT 位。检查 DMA 控制寄存器(CR)中的 CT 位是否被清零
        {    //确定当前 DMA 传输正在使用哪个内存缓冲区.
            //如果条件为真,表示 CT 位为零,即当前内存缓冲区是 Memory 0。如果条件为假,表示 CT 位不为零,即当前内存缓冲区是 Memory 1。
            // 当前使用的内存缓冲区是 Memory 0
           
            __HAL_DMA_DISABLE(&hdma_usart3_rx); // 失效 DMA,暂停数据传输,为了重新设定接收数据长度,下面会再开启DMA
            
            this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
            //在 DMA 接收数据时,NDTR 的值表示还有多少字节的数据需要被传输
            //获取接收数据的长度,长度 = 设置的数据长度 - 剩余长度;
            
            hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;// 重新设定数据长度
            
            hdma_usart3_rx.Instance->CR |= DMA_SxCR_CT;// 切换为 Memory 1
            //通过按位或操作 (|=) 将 DMA 控制寄存器 (CR 寄存器) 中的 DMA_SxCR_CT 位置 1,将 DMA 通道切换为 Memory 1
            
            __HAL_DMA_ENABLE(&hdma_usart3_rx); // 使能 DMA,继续数据传输
            
            if (this_time_rx_len == RC_FRAME_LENGTH)// 如果接收到完整的遥控器数据帧:接收到的数据长度=预期的遥控器数据帧长度
            {
                
                RC_Data(sbus_rx_buf[0], &RC_ctrl);// 处理遥控器数据,这里是自己写的处理数据的函数
               
                detect_hook(DBUS_TOE); // 记录数据接收时间
               
                sbus_to_usart1(sbus_rx_buf[0]); // 将数据发送到 USART1
            }
        }//下面的代码同理,使用Memory1
        else
        {
            // 当前使用的内存缓冲区是 Memory 1
            // 失效 DMA,暂停数据传输
            __HAL_DMA_DISABLE(&hdma_usart3_rx);
            // 获取接收数据的长度,长度 = 设置的数据长度 - 剩余长度
            this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
            // 重新设定数据长度
            hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
            // 切换为 Memory 0
            DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
            // 使能 DMA,继续数据传输
            __HAL_DMA_ENABLE(&hdma_usart3_rx);
            // 如果接收到完整的遥控器数据帧
            if (this_time_rx_len == RC_FRAME_LENGTH)
            {
                // 处理遥控器数据
                sbus_to_rc(sbus_rx_buf[1], &rc_ctrl);
                // 记录数据接收时间
                detect_hook(DBUS_TOE);
                // 将数据发送到 USART1
                sbus_to_usart1(sbus_rx_buf[1]);
            }
        }
    }
}

 5.总结

以上就是全部内容,有问题可以评论或者私聊我,找到错误也可以说出来我改正。谢谢大家~希望能对大家有所帮助

你可能感兴趣的:(STM32,单片机,嵌入式硬件,stm32)