UART通过DMA接收和发送,使用环形缓冲区,状态机的使用

前言

为了实现UART通过DMA接收和发送不定长度的数据,可以使用环形缓冲区(环形缓冲区)和插入相关的状态机来解析接收到的数据。

步骤

1.启用时钟,配置引脚,配置USART,配置DMA,这些不用说了吧,官方教程有一堆

2.DMA 接收处理,DMA 接收完成中断,这个没什么用,接收到的数据长度不一,DMA只要负责把UART的数据寄存器的数据搬到内存中就行了。

3.UART的接收处理和状态机解析,在UART接收完成中断中,需要处理内存的数据和和状态机解析。

4.UART的发送函数中再初始化txBuffer的DMA,因为每次发送的length不一样。

具体代码

1.初始化 DMA 和 UART:
#include 复制代码
#define RX_BUFFER_SIZE 1024  // 环形缓冲区大小
#define TX_BUFFER_SIZE 150   // 发送缓冲区大小

uint8_t rxBuffer[RX_BUFFER_SIZE];
uint8_t txBuffer[TX_BUFFER_SIZE];

volatile uint16_t rxWriteIndex = 0;

void UART_DMA_Init(void) {
    USART_InitTypeDef USART_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    // 启用时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    // 配置UART引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // TX
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // RX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置USART
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_Init(USART1, &USART_InitStructure);

    // 配置DMA RX
    DMA_DeInit(DMA1_Channel5);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rxBuffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = RX_BUFFER_SIZE;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel5, &DMA_InitStructure);

    // 启用DMA
    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
    DMA_Cmd(DMA1_Channel5, ENABLE);

    // 启用USART
    USART_Cmd(USART1, ENABLE);
}
2.DMA 接收处理和状态机解析:
复制代码
void DMA1_Channel5_IRQHandler(void) {
    if (DMA_GetITStatus(DMA1_IT_TC5)) {
        DMA_ClearITPendingBit(DMA1_IT_TC5);
        // 传输完成
    }
}

void USART1_IRQHandler(void) {
    if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) {
        volatile uint32_t temp;
        temp = USART1->SR;  // 读取USART SR寄存器
        temp = USART1->DR;  // 读取USART DR寄存器以清除IDLE中断标志

        uint16_t rxIndex = RX_BUFFER_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5);

        if (rxIndex != rxWriteIndex) {
            if (rxIndex > rxWriteIndex) {
                ProcessReceivedData(&rxBuffer[rxWriteIndex], rxIndex - rxWriteIndex);
            } else {
                ProcessReceivedData(&rxBuffer[rxWriteIndex], RX_BUFFER_SIZE - rxWriteIndex);
                ProcessReceivedData(&rxBuffer[0], rxIndex);
            }
            rxWriteIndex = rxIndex;
        }
    }
}

void ProcessReceivedData(uint8_t *data, uint16_t length) {
    // 将接收到的数据传入状态机进行解析
    for (uint16_t i = 0; i < length; i++) {
        ParseStateMachine(data[i]);
    }
}

void ParseStateMachine(uint8_t byte) {
    // 根据实际应用编写状态机解析代码
}
3.DMA 发送 UART 数据的代码及发送完处理逻辑
复制代码
void UART_SendDataDMA(uint8_t *data, uint16_t length) {
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 配置 DMA
    DMA_DeInit(DMA1_Channel4);  // 假设使用 DMA1 Channel4
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)data;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = length;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    // 初始化 DMA
    DMA_Init(DMA1_Channel4, &DMA_InitStructure);

    // 清除传输完成标志
    DMA_ClearFlag(DMA1_FLAG_TC4);

    // 配置 NVIC
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // 使能 DMA传输完成中断
    DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);

    // 使能 DMA 通道
    DMA_Cmd(DMA1_Channel4, ENABLE);

    // 使能 USART 的 DMA 传输请求
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
}
// DMA1 Channel4 中断处理
void DMA1_Channel4_IRQHandler(void)
{
    // 检查 DMA 传输完成中断标志
    if (DMA_GetITStatus(DMA1_IT_TC4))
    {
        // 清除传输完成中断标志
        DMA_ClearITPendingBit(DMA1_IT_TC4);

        // 禁用 DMA 通道
        DMA_Cmd(DMA1_Channel4, DISABLE);

        // 其他传输完成后的处理代码...
    }
}
4.主函数调用:
复制代码
int main(void) {
    UART_DMA_Init();

    // 使能IDLE中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
    NVIC_EnableIRQ(USART1_IRQn);
    NVIC_EnableIRQ(DMA1_Channel4_IRQn);
    NVIC_EnableIRQ(DMA1_Channel5_IRQn);

    uint8_t dataToSend[] = "Hello World!";
    UART_SendDataDMA(dataToSend, sizeof(dataToSend) - 1);

    while (1) {
        // 主循环,中断处理和DMA完成发送接收
    }
}
相关推荐
国科安芯2 小时前
ASP4644芯片低功耗设计思路解析
网络·单片机·嵌入式硬件·安全
充哥单片机设计2 小时前
【STM32项目开源】基于STM32的智能厨房火灾燃气监控
stm32·单片机·嵌入式硬件
CiLerLinux9 小时前
第四十九章 ESP32S3 WiFi 路由实验
网络·人工智能·单片机·嵌入式硬件
时光の尘9 小时前
【PCB电路设计】常见元器件简介(电阻、电容、电感、二极管、三极管以及场效应管)
单片机·嵌入式硬件·pcb·二极管·电感·三极管·场效应管
Lu Zelin9 小时前
单片机为什么不能跑Linux
linux·单片机·嵌入式硬件
宁静致远202110 小时前
stm32 freertos下基于hal库的模拟I2C驱动实现
stm32·嵌入式硬件·freertos
Wave84515 小时前
STM32--智能小车
stm32·单片机·嵌入式硬件
wdfk_prog17 小时前
[Linux]学习笔记系列 -- lib/timerqueue.c Timer Queue Management 高精度定时器的有序数据结构
linux·c语言·数据结构·笔记·单片机·学习·安全
helesheng20 小时前
用低成本FPGA实现FSMC接口的多串口(UART)控制器
stm32·fsmc·fpga·uart控制器
充哥单片机设计20 小时前
【STM32项目开源】基于STM32的智能家居环境(空气质量)检测系统
stm32·单片机·嵌入式硬件