STM32F103 serial port + DMA interrupt for data transmission and reception

    There are two articles in the serial port DMA series, one is to use DMA + idle interrupt to send and receive data of variable length, the other is to use DMA interrupt to send and receive fixed-length data, the article link is as follows:
    01 STM32F103 Serial DMA + idle interrupt to realize variable length Data transceiver
    02 STM32F103 serial port + DMA interrupt to achieve data transceiver

    Last article Serial port DMA + idle interrupt to achieve variable length data transmission and reception
     talked about the function of serial port + DMA idle interrupt to achieve variable length data transmission and reception. In addition to using idle interrupts to achieve data transmission and reception, you can also use DMA interrupts to achieve data transmission and reception. The difference It is the latter that cannot realize the reception of indefinite length data. This article explains the way of DMA interrupt to realize the sending and receiving of data.

1. Code Explanation

1.1 uart_dma.c

    When using the DMA interrupt, there is no need to configure the serial port interrupt

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_dma.h"
#include "misc.h"

#include "systick.h"
#include "uart_dma.h"

uint8_t uart1RecvData[32] = {
    
    0};    // 接收数据缓冲区
uint8_t uart1RecvFlag = 0;          // 接收完成标志位
uint8_t uart1RecvLen = 0;           // 接收的数据长度

uint8_t uart1SendData[32] = {
    
    0};    // 发送数据缓冲区
uint8_t uart1SendFlag = 0;          // 发送完成标志位


/* 串口1 GPIO引脚初始化 */
void Uart1GpioInit(void)
{
    
    
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   // 使能GPIOA时钟
	
	/************ ↓ RS485 相关 ↓ ************/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);   // 使能GPIOD时钟
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;    // 输入输出使能引脚 推挽输出
    GPIO_InitStruct.GPIO_Pin = UART1_EN_PIN;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(UART1_EN_PORT, &GPIO_InitStruct);     // PD1
    Uart1RxEnable();    // 初始化接收模式
    /************ ↑ RS485 相关 ↑ ************/
    
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;    // TX 推挽输出
    GPIO_InitStruct.GPIO_Pin = UART1_TX_PIN;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(UART1_TX_PORT, &GPIO_InitStruct);     // PA9
    
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;      // RX上拉输入
    GPIO_InitStruct.GPIO_Pin = UART1_RX_PIN;
    GPIO_Init(UART1_RX_PORT, &GPIO_InitStruct);     // PA10
    

}

/************ ↓ RS485 相关 ↓ ************/
/* 使能485发送 */
void Uart1TxEnable(void)
{
    
    
    GPIO_WriteBit(UART1_EN_PORT, UART1_EN_PIN, Bit_SET);    // 485的使能引脚,高电平为使能发送
    Delay_ms(5);
}

/* 使能485接收 */
void Uart1RxEnable(void)
{
    
    
    GPIO_WriteBit(UART1_EN_PORT, UART1_EN_PIN, Bit_RESET);  // 485的使能引脚,低电平为使能发送
    Delay_ms(5);
}
/************ ↑ RS485 相关 ↑ ************/

/* 串口1配置 9600 8n1 */
void Uart1Config(void)
{
    
    
    USART_InitTypeDef USART_InitStruct;
    NVIC_InitTypeDef NVIC_InitStructure;
    DMA_InitTypeDef DMA_InitStruct;
    
    USART_DeInit(USART1);   // 寄存器恢复默认值
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);  // 使能串口时钟
    
    /* 串口参数配置 */
    USART_InitStruct.USART_BaudRate = BAUD_RATE;            // 波特率:9600
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;    // 无流控
    USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;    // 收发
    USART_InitStruct.USART_Parity = USART_Parity_No;                // 无校验位 
    USART_InitStruct.USART_StopBits = USART_StopBits_1;             // 1个停止位
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;        // 8个数据位
    USART_Init(USART1, &USART_InitStruct);
    USART_Cmd(USART1, ENABLE);  // 使能串口
      
    /* 串口DMA配置 */
    DMA_DeInit(DMA1_Channel4);  // DMA1 通道4,寄存器复位
    DMA_DeInit(DMA1_Channel5);  // DMA1 通道5,寄存器复位
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  // 使能 DMA1 时钟
    
    // RX DMA1 通道5
    DMA_InitStruct.DMA_BufferSize = sizeof(uart1RecvData);      // 定义了接收的最大长度
    DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;             // 串口接收,方向是外设->内存
    DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;                   // 本次是外设到内存,所以关闭内存到内存
    DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)uart1RecvData;// 内存的基地址,要存储在哪里
    DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存数据宽度,按照字节存储
    DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;        // 内存递增,每次串口收到数据存在内存中,下次收到自动存储在内存的下一个位置
    DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                  // 正常模式
    DMA_InitStruct.DMA_PeripheralBaseAddr = USART1_BASE + 0x04; // 外设的基地址,串口的数据寄存器
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;    // 外设的数据宽度,按照字节存储,与内存的数据宽度一致
    DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;   // 接收只有一个数据寄存器 RDR,所以外设地址不递增
    DMA_InitStruct.DMA_Priority = DMA_Priority_High;            // 优先级
    DMA_Init(DMA1_Channel5, &DMA_InitStruct);
    
    // TX DMA1 通道4  
    DMA_InitStruct.DMA_BufferSize = 0;                          // 发送缓冲区的大小,初始化为0不发送
    DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;             // 发送是方向是外设到内存,外设作为目的地
    DMA_InitStruct.DMA_MemoryBaseAddr =(uint32_t)uart1SendData; // 发送内存地址,从哪里发送
    DMA_Init(DMA1_Channel4, &DMA_InitStruct);
     
    USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE);// 使能DMA串口发送和接受请求
  
    DMA_Cmd(DMA1_Channel5, ENABLE);     // 使能接收
    DMA_Cmd(DMA1_Channel4, DISABLE);    // 禁止发送
    
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                             // 使能
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  // 抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                // 子优先级
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;
    NVIC_Init(&NVIC_InitStructure);     // 嵌套向量中断控制器初始化
    
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;
    NVIC_Init(&NVIC_InitStructure);     // 嵌套向量中断控制器初始化
    
    DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);
    DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);
}

/* 清除DMA的传输数量寄存器 */
void uart1DmaClear(void)
{
    
    
    DMA_Cmd(DMA1_Channel5, DISABLE);    // 关闭 DMA1_Channel5 通道
    DMA_SetCurrDataCounter(DMA1_Channel5, sizeof(uart1RecvData));   // 重新写入要传输的数据数量
    DMA_Cmd(DMA1_Channel5, ENABLE);     // 使能 DMA1_Channel5 通道
}

/* 串口1发送数组 */
void uart1SendArray(uint8_t *arr, uint8_t len)
{
    
    
    if(len == 0)
      return;
    
    uint8_t sendLen = len>sizeof(uart1SendData) ? sizeof(uart1SendData) : len;
    
    /************ ↓ RS485 相关 ↓ ************/
    Uart1TxEnable();    // 使能发送
    /************ ↑ RS485 相关 ↑ ************/
    
    while (DMA_GetCurrDataCounter(DMA1_Channel4));  // 检查DMA发送通道内是否还有数据
    if(arr) 
      memcpy(uart1SendData, arr, sendLen);
    
    // DMA发送数据-要先关 设置发送长度 开启DMA
    DMA_Cmd(DMA1_Channel4, DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel4, sendLen);   // 重新写入要传输的数据数量
    DMA_Cmd(DMA1_Channel4, ENABLE);     // 启动DMA发送    
}

1.2 uart_dma.h

#ifndef _UART_DAM_H_
#define _UART_DMA_H_

#include <stdint.h>

#define UART1_TX_PORT   GPIOA
#define UART1_TX_PIN    GPIO_Pin_9
#define UART1_RX_PORT   GPIOA
#define UART1_RX_PIN    GPIO_Pin_10
#define UART1_EN_PORT   GPIOD
#define UART1_EN_PIN    GPIO_Pin_1
#define BAUD_RATE       (9600)

extern uint8_t uart1RecvData[32];
extern uint8_t uart1RecvFlag;
extern uint8_t uart1RecvLen;
extern uint8_t uart1SendFlag;

void Uart1GpioInit(void);
void Uart1Config(void);
void uart1DmaClear(void);
void uart1SendArray(uint8_t *arr, uint8_t len);

/************ ↓ RS485 相关 ↓ ************/
void Uart1RxEnable(void);
void Uart1TxEnable(void);
/************ ↑ RS485 相关 ↑ ************/

#endif  /* uart_dma.h */

1.3 main.c

#include "uart_dma.h"
#include "misc.h"

int main()
{
    
     
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  // 设置中断优先级分组
    
    /************ ↓ RS485 相关 ↓ ************/ 
    SysTickInit();          // 嘀嗒计时器初始化,没用485可以省去
    /************ ↑ RS485 相关 ↑ ************/
    
    Uart1GpioInit();	// 串口GPIO初始化
    Uart1Config();		// 串口和DMA配置

    while(1)
    {
    
         
        if(uart1RecvFlag == 1)	// 接收到数据
        {
    
    
            uart1RecvFlag = 0;  // 接收标志清空
            uart1DmaClear();    // 清空DMA接收通道
            uart1SendArray(uart1RecvData, uart1RecvLen);        // 使用DMA发送数据
            memset(uart1RecvData, '\0', sizeof(uart1RecvData)); // 清空接收缓冲区
        }
        
        if(uart1SendFlag == 1)
        {
    
    
            uart1SendFlag = 0;  // 清空发送标志   
            Uart1RxEnable();    // 发送完成打开接收
        }
    }
}

1.4 stm32f10x_it.c

#include "stm32f10x_it.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_dma.h"

#include "uart_dma.h"

void DMA1_Channel4_IRQHandler(void)		// 串口1 DMA发送中断处理函数
{
    
    
    if(DMA_GetITStatus(DMA1_IT_TC4) != RESET)
    {
    
    
        DMA_ClearITPendingBit(DMA1_IT_TC4);    // 清除传输完成中断标志位	
        DMA_Cmd(DMA1_Channel4, DISABLE);       // 关闭DMA
        DMA1_Channel4->CNDTR=0;                // 清除数据长度
        uart1SendFlag = 1;                     // 设置发送完成事件
    }
}

void DMA1_Channel5_IRQHandler(void)  // 串口1 DMA接收中断处理函数
{
    
    
    if(DMA_GetITStatus(DMA1_IT_TC5) != RESET)
    {
    
    
        DMA_ClearITPendingBit(DMA1_IT_TC5);   // 清除传输完成中断标志位	
        uart1RecvFlag = 1;				      // 置接收完成标志位
        uart1RecvLen = sizeof(uart1RecvData) - DMA_GetCurrDataCounter(DMA1_Channel5);// 总的buf长度减去剩余buf长度,得到接收到数据的长度
    }
}

1.5 Effect demonstration

    Use the serial debugging assistant as the host computer to view the execution result of the code as shown in the figure below. The size of the receiving and sending buffers defined in this article are both 32 bytes. It can be seen from the figure that when the number of transmitted bytes is less than 32 , And will not generate the transmission completion interrupt immediately. The reception completion interrupt will only be generated when the number of bytes transmitted is 32 [> 32 and the following is discarded]. Therefore, if the DMA interrupt is used, it is necessary to meet the data of each reply. Fixed length, no idle interruption flexibility.
Insert picture description here
    If you have any questions during the DMA learning process, welcome to exchange and learn in the comment area.

Guess you like

Origin blog.csdn.net/qq_36310253/article/details/109645134