18:(标准库)DMA二:DMA+串口收发数据

DMA+串口收发数据

1、DMA+串口发送数据


当串口的波特率大于115200时,可以通过DMA1进行数据搬运,以防止数据的丢失。如上图所示:UART1的Tx发送请求使用DMA1的通道4,UART1的Rx接收数据请求使用DMA1的通道5。
①串口发送时 :当UART1的发送数据寄存器TDR中没有数据时,就会向DMA1的通道4申请数据搬运,DMA1将缓冲区的数据搬运到TDR数据寄存器中,然后串口将数据发送出去。

②串口接收时 :当UART1的接收数据寄存器RDR中有数据时,就会向DMA1的通道5申请数据搬运,DMA1将数据从RDR寄存器中搬运到缓冲区中。

【注意】数据的搬运和数据的发送的过程都不需要CPU参与,CPU只参与串口UART1和DMA1通道1的配置。

①UART.c文件的代码如下:

c 复制代码
#include "UART.h"

uint8_t Buff[Buffer_Size];//定义数据缓冲区
/**
 * 串口1的初始化函数
 */
void UART1_Init(void)
{
    /* 开启串口的UART1的时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

    /* 开启串口的GPIO的时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* 配置串口1的引脚 */
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;// 复用推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;// 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* 配置串口1的模式 */
    USART_InitTypeDef USART_InitStruct;
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 不使用硬件流控制
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 收发模式
    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);
    
    /* 使能串口DMA发送请求 */
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
    
    /* 使能串口1 */
    USART_Cmd(USART1, ENABLE);
}

②UART.h文件的代码如下:

c 复制代码
#ifndef __UART_H
#define __UART_H
#include "stm32f10x.h"
#include "stdio.h"

#define Buffer_Size 256
extern uint8_t Buff[Buffer_Size];//定义数据缓冲区

void UART1_Init(void);

#endif

③MyDMA.c文件的代码如下:

c 复制代码
#include "MyDMA.h"
#include "UART.h"
/**
 * DMA1的通道4的初始化 
 */
void DMA1_Init(void)
{
    /* 1、使能DMA1的时钟 */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* 2、配置DMA1的通道1 */
    DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR);    //"外设站点"的起始地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度,8位
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;       //外设站点地址是否自增,这里选择不自增,因为搬运到数据寄存器TDR中
	
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Buff;                 //"内存站点"起始地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;        //数据宽度,8位
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;                //目的站点地址是否自增,自增
	
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;                     //搬运方向的选择(目的地选择),这里选择内存站点--->外设站点:DMA_DIR_PeripheralDST
	DMA_InitStruct.DMA_BufferSize = 0;                                  //传输计数器的大小,代表搬运数据的个数,先置为0
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                          //是否自动重装,这里选择不自动重装
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;                           //是否软件触发,这里选择不是,由硬件触发
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;                  //优先级,这里选择中等
	DMA_Init(DMA1_Channel4,&DMA_InitStruct);                            //配置DMA1的通道4
	
//	DMA_Cmd(DMA1_Channel4,ENABLE);                                      //使能DMA1的通道4
	DMA_Cmd(DMA1_Channel4,DISABLE);                                     //先失能DMA1的通道4
}
   
/**
 * DMA1开启搬运函数
 */
void UART1_DMA1_Transport(uint16_t DataNumber)
{
    /* 1、失能DMA1 */
    DMA_Cmd(DMA1_Channel4,DISABLE); 
    
    /* 2、先设置传输计数器的计数值 */
    DMA_SetCurrDataCounter(DMA1_Channel4, DataNumber);
    
    /* 3、使能DMA1 */
    DMA_Cmd(DMA1_Channel4,ENABLE);   
    
    /* 4、等待搬运完成 */
    while(!DMA_GetFlagStatus(DMA1_FLAG_TC4));   //等待DMA1通道4全部搬运完成
	DMA_ClearFlag(DMA1_FLAG_TC4);               //手动清除标志位 
}

④MyDMA.h文件的代码如下:

c 复制代码
#ifndef __MyDMA_H
#define __MyDMA_H
#include "stm32f10x.h"

void DMA1_Init(void);
void UART1_DMA1_Transport(uint16_t DataNumber);

#endif

⑤主函数main.c文件的代码如下:

c 复制代码
#include "stm32f10x.h"                 
#include "Delay.h"
#include "UART.h"
#include "MyDMA.h"

#define DataNumber 10 //定义需要发送的数据个数

int main(void)
{
    for(uint8_t i = 0; i<DataNumber; i++)//先向缓冲区里面填入数据
    {
        Buff[i] = i;
    }
    
    UART1_Init();
    DMA1_Init();
    UART1_DMA1_Transport(DataNumber);     //开始搬运数据 
    
	while(1)
	{ 
        
	}
}

2、DMA中断+串口接收定长数据包

①UART.c文件的代码如下:

c 复制代码
#include "UART.h"

uint8_t Buff[Buffer_Size];  //定义数据缓冲区
uint16_t Length = 10;       //定义定长数据包长度
uint8_t Flag = 0;           //传输完成标志位
/**
 * 串口1的初始化函数
 */
void UART1_Init(void)
{
    /* 开启串口的UART1的时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

    /* 开启串口的GPIO的时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* 配置串口1的引脚 */
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;        // 复用推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;  // 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* 配置串口1的模式 */
    USART_InitTypeDef USART_InitStruct;
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 不使用硬件流控制
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                // 收发模式
    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);
    
    /* 使能串口DMARx接收请求 */
    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
    
    /* 使能串口1 */
    USART_Cmd(USART1, ENABLE);
}

/**
 * 串口发送多个字节的数据
 */
void USART_SendArray(uint8_t *array, uint16_t len)
{
    /* 发送一组数据 */
    for (uint16_t i = 0; i < len; i++)
    {
        USART_SendChar(array[i]);
    }
}

②UART.h文件的代码如下:

c 复制代码
#ifndef __UART_H
#define __UART_H
#include "stm32f10x.h"
#include "stdio.h"

#define Buffer_Size 256
extern uint8_t Buff[Buffer_Size];//定义数据缓冲区
extern uint16_t Length;
extern uint8_t Flag;

void UART1_Init(void);
void USART_SendArray(uint8_t *array, uint16_t len);

#endif

③MyDMA.c文件的代码如下:

c 复制代码
#include "MyDMA.h"
#include "UART.h"
/**
 * DMA1通道5的初始化
 */

void DMA1_Init(void)
{
    /* 1、使能DMA1的时钟 */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* 2、配置DMA1的通道1 */
    DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR);    //外设站点的起始地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度,8位
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;       //外设站点地址是否自增,这里选择不自增,因为搬运到数据寄存器TDR中
	
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Buff;                 //内存站点起始地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;        //数据宽度,8位
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;                //目的站点地址是否自增,自增
	
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;                     //搬运方向的选择(目的地选择),这里选择外设站点--->内存站点:DMA_DIR_PeripheralSRC
	DMA_InitStruct.DMA_BufferSize = Length;                             //传输计数器的大小,代表搬运数据的个数
	DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;                        //是否自动重装,这里选择自动重装
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;                           //是否软件触发,这里选择不是,由硬件触发
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;                  //优先级,这里选择中等
	DMA_Init(DMA1_Channel5,&DMA_InitStruct);                            //配置DMA1的通道5
    
    /* 3、使能DMA1通道5搬运完成中断和NVIC */
	DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel5_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
    
	DMA_Cmd(DMA1_Channel5,ENABLE);                                      //使能DMA1的通道5
}
   
/**
 * DMA1开启搬运函数
 */
//void UART1_DMA1_Transport(uint16_t DataNumber)
//{
//    /* 1、失能DMA1 */
//    DMA_Cmd(DMA1_Channel4,DISABLE); 
//    
//    /* 2、先设置传输计数器的计数值 */
//    DMA_SetCurrDataCounter(DMA1_Channel4, DataNumber);
//    
//    /* 3、使能DMA1 */
//    DMA_Cmd(DMA1_Channel4,ENABLE);   
//    
//    /* 4、等待搬运完成 */
//    while(!DMA_GetFlagStatus(DMA1_FLAG_TC4));   //等待通道4搬运完成
//	  DMA_ClearFlag(DMA1_FLAG_TC4);               //手动清除标志位 
//}

/**
 * DMA1通道5传输完成的中断服务函数
 */
void DMA1_Channel5_IRQHandler(void)
{
    if(DMA_GetFlagStatus(DMA1_FLAG_TC5))
    {
        DMA_ClearFlag(DMA1_FLAG_TC5);   //清除通道5的标志位
        Flag = 1;
    }      
}

④主函数main.c文件的代码如下:

c 复制代码
#include "stm32f10x.h"                 
#include "Delay.h"
#include "UART.h"
#include "MyDMA.h"

int main(void)
{
    UART1_Init();
    DMA1_Init();
 
	while(1)
	{ 
        if(Flag)
        {  
            Flag = 0;
            USART_SendArray(Buff, Length); 
        }       
	}
}

3、串口空闲中断+DMA接收不定长数据包

①UART.c文件的代码如下:

c 复制代码
#include "UART.h"

uint8_t Buff[Buffer_Size];  //定义数据缓冲区
uint8_t Flag = 0;           //传输完成标志位
uint16_t Index = 0;         //定义接收到的数据个数
/**
 * 串口1的初始化函数
 */
void UART1_Init(void)
{
    /* 开启串口的UART1的时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

    /* 开启串口的GPIO的时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* 配置串口1的引脚 */
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;        // 复用推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;  // 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* 配置串口1的模式 */
    USART_InitTypeDef USART_InitStruct;
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 不使用硬件流控制
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                // 收发模式
    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);
    
    /* 使能串口DMARx接收请求 */
    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
    
    /* 使能串口IDLE空闲中断和NVIC */
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
    
    /* 使能串口1 */
    USART_Cmd(USART1, ENABLE);
}

/**
 * 串口发送一个字节的数据
 */
void USART_SendChar(uint8_t ch)
{
    /* 发送一个字节的数据 */
    USART_SendData(USART1, ch);

    /* 等待发送数据寄存器为空 */
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

/**
 * 串口发送一个字符串的数据
 */
void USART_SendString(uint8_t *str)
{
    /* 发送多个字节的数据 */
    while (*str!= '\0')
    {
        USART_SendChar(*str++);
    }
}

/**
 * 串口发送多个字节的数据
 */
void USART_SendArray(uint8_t *array, uint16_t len)
{
    /* 发送一组数据 */
    for (uint16_t i = 0; i < len; i++)
    {
        USART_SendChar(array[i]);
    }
}

/**
 * 对printf函数进行重定向
 */
int fputc(int ch, FILE *f)
{
    /* 等待发送数据寄存器为空 */
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    
    /* 发送一个字节的数据 */
    USART_SendData(USART1, (uint8_t)ch);
    return ch;
}

/**
 * 串口1的空闲中断服务函数
 */
void USART1_IRQHandler(void)
{
    uint8_t Receive_Data;
    if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE))
    {   
        Receive_Data = USART1->SR;
        Receive_Data = USART1->DR; //清除中断标志位IDLE
        
//      Index = Buffer_Size - DMA_GetCurrDataCounter(DMA1_Channel5);//获取接收到的数据个数
        Index = Buffer_Size -(DMA1_Channel5->CNDTR);                //获取接收到的数据个数
        Flag = 1;
        
        /* 重新给DMA传输计数器设置值:让第二个数据包从缓冲区第一位开始存储 */
        DMA_Cmd(DMA1_Channel5,DISABLE);                             
        DMA_SetCurrDataCounter(DMA1_Channel5, Buffer_Size); 
        DMA_Cmd(DMA1_Channel5,ENABLE);                              //使能DMA1的通道5
    }
}

②UART.h文件的代码如下:

c 复制代码
#ifndef __UART_H
#define __UART_H
#include "stm32f10x.h"
#include "stdio.h"

#define Buffer_Size 256
extern uint8_t Buff[Buffer_Size];//定义数据缓冲区
extern uint8_t Flag;
extern uint16_t Index;

void UART1_Init(void);
void USART_SendChar(uint8_t ch);
void USART_SendString(uint8_t *str);
void USART_SendArray(uint8_t *array, uint16_t len);

#endif

③MyDMA.c文件的代码如下:

c 复制代码
#include "MyDMA.h"
#include "UART.h"
/**
 * DMA1通道5的初始化
 */

void DMA1_Init(void)
{
    /* 1、使能DMA1的时钟 */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* 2、配置DMA1的通道1 */
    DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR);    //外设站点的起始地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度,8位
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;       //外设站点地址是否自增,这里选择不自增,因为搬运到数据寄存器TDR中
	
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Buff;                 //内存站点起始地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;        //数据宽度,8位
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;                //目的站点地址是否自增,自增
	
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;                     //搬运方向的选择(目的地选择),这里选择外设站点--->内存站点:DMA_DIR_PeripheralDST
	DMA_InitStruct.DMA_BufferSize = Buffer_Size;                        //传输计数器的大小,代表搬运数据的个数
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                          //是否自动重装,这里选择不自动重装,接收一个数据包,在空闲中断里面重装
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;                           //是否软件触发,这里选择不是,由硬件触发
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;                  //优先级,这里选择中等
	DMA_Init(DMA1_Channel5,&DMA_InitStruct);                            //配置DMA1的通道5
    
	DMA_Cmd(DMA1_Channel5,ENABLE);                                      //使能DMA1的通道5
}

④主函数main.c文件的代码如下:

c 复制代码
#include "stm32f10x.h"                 
#include "Delay.h"
#include "UART.h"
#include "MyDMA.h"

int main(void)
{
    UART1_Init();
    DMA1_Init();
 
	while(1)
	{ 
        if(Flag)
        {  
            Flag = 0;
            USART_SendArray(Buff, Index);   
        }       
	}
}
相关推荐
序属秋秋秋6 小时前
《Linux系统编程之进程环境》【环境变量】
linux·运维·服务器·c语言·c++·操作系统·系统编程
雲烟6 小时前
嵌入式设备EMC安规检测参考
网络·单片机·嵌入式硬件
泽虞6 小时前
《STM32单片机开发》p7
笔记·stm32·单片机·嵌入式硬件
Yue丶越6 小时前
【C语言】数据在内存中的存储
c语言·开发语言·网络
田甲7 小时前
【STM32】 数码管驱动
stm32·单片机·嵌入式硬件
up向上up7 小时前
基于51单片机垃圾箱自动分类加料机快递物流分拣器系统设计
单片机·嵌入式硬件·51单片机
Yue丶越15 小时前
【C语言】字符函数和字符串函数
c语言·开发语言·算法
纳祥科技16 小时前
Switch快充方案,内置GaN,集成了多个独立芯片
单片机
蓝牙先生17 小时前
简易TCP C/S通信
c语言·tcp/ip·算法
单片机日志17 小时前
【单片机毕业设计】【mcugc-mcu826】基于单片机的智能风扇系统设计
stm32·单片机·嵌入式硬件·毕业设计·智能家居·课程设计·电子信息