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);   
        }       
	}
}
相关推荐
Shaun_青璇1 小时前
STM32的AFIO讲解
stm32·单片机·嵌入式硬件·mcu·物联网
Qingniu012 小时前
D2761 适合在个人电脑、便携式音响等系统中作音频限幅用。
科技·嵌入式硬件·音视频·摇头机·风扇·水泵·音频限幅
LightningJie2 小时前
STM32中I2C总线中,允许从机控制SCL总线吗?
stm32·单片机·嵌入式硬件
風清掦2 小时前
C/C++ 每日一练:在矩阵中查找特定值
c语言·c++·算法
夜间看花2 小时前
28 基于51单片机的两路电压检测(ADC0808)
嵌入式硬件·51单片机·proteus
FreeLikeTheWind.3 小时前
C语言实例之9斐波那契数列实现
c语言·开发语言·算法
嗯? 嗯。3 小时前
电烙铁焊接STM32芯片、贴片元器件、手册指南!!!
stm32·单片机·嵌入式硬件
打你个大西瓜️3 小时前
单片机知识总结(完整)
数据库·单片机·mongodb
爱编程— 的小李3 小时前
文件的处理(c语言)
c语言·开发语言