USART通过DMA实现外设到内存数据

DMA理解:

直接内存存取(Direct Memory Access):在数据传输过程中不需要通过CPU的干预,控制总线并直接与外设和内存进行通信,给CPU减轻负担,以及提升数据的传输速度。

两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道用来管理外设对存储器访问的请求,也可以理解为,通道是数据的来源以及去向。

仲裁器: 

咋听起来很高级,其实就是协调各个DMA请求的优先级

软件: 每个通道优先权可以再DMA_CCRx寄存器中设置,有4个等级

     最高优先级

     高优先级

     中等优先级

     低优先级

硬件:如果有两个请求相同的软件优先级,则较低编号通道优于较高编号优先权,2通道优于4通道。

 DMA特性:

1:12个独立可配置通道:DMA1有7个通道,DMA2有5个通道

2:每个通道直接连接专用硬件DMA请求,每个通道同样支持软件触发,这些功能通过软件配置

3:独立数据源和目标数据区的传输宽度(字节,半字,全字),模拟打包和拆包过程,源目标地址必须按数据传输宽度对齐。

4:支持循环的缓冲区管理

5:每个通道都有3个事件标志(DMA半传输,DMA传输完成,DMA传输出错),每个事件标志逻辑成为一个单独的中断请求

6:闪存,SRAM,外设的SRAM,APB1,APB2外设均可作为访问源和目标

 数据的大小单位

在计算机科学和嵌入式系统中,字、半字、全字 是指数据的大小单位

字(Word):字指得是处理器能够一次处理的数据大小,取决于处理器架构和位数,在32位处理器中,一个字通常是32位(4字节)大小,64位(8字节)大小。字是处理能够处理的最大数据单元。

半字(Half - Word):半字是字的一半大小,半字用来表示处理器能够读取或写入最小数据单元

全字(Double Word):全字是字的两倍大小,全字用于表示比一个字更大的数据单元

 事件标志

DMA控制器中,每个通道都会有多个事件标志位,其中包括以下三个主要的事件标志:

1:传输完成(Transfer Complete):当DMA通道数据传输完成时,会触发完成事件,表示DMA已经成功完成一次数据传输操作

2:半传输(Half Transfer):这个标志表示DMA通道已经传输数据缓冲区的一半,在双缓冲区模式下,当DMA传输一半的数据时,会产生这个中间状态的事件

3:传输错误(Transfer Error):如果发生DMA传输错误,比如传输过程中出现了总线错误或外设错误等,会触发传输错误事件,这标志表示DMA传输过程中发生了错误。

DMA通道数量

 最大达到65535

DMA优点: 

1:提高效率:

        CPU不需要介入数据传输过程,通过DMA可以并行处理其他任务,提高系统效率。

2:减少CPU负载:

         对于大容量数据传输,使用DMA可以减少CPU的负载,让CPU处理更重要的任务

3:快速数据传输(仲裁器)可选择:

        3.1:最高优先级

        3.2:高优先级

        3.3:中等优先级

        3.4:低优先级

4:传输模式:

        4.1:单次传输模式:

                   单次传输模式下,DMA只进行一次数据传输操作,传输完成后立即停止

        4.2:循环传输模式:

                    循环模式下,DMAke已反复执行数据传输,不断重复数据传输操作

        4.3:自动请求传输模式:

                   外设可以通过某种方式自动触发DMA数据传输,不需要CPU干预

        4.4:多块传输模式:

                   在多块传输模式下,DMA将数据分成多个块进行传输

5:灵活性 :

        5.1:外设到内存

        5.2:内存到外设

        5.3:内存到内存

        5.4:外设到外设

我们今天的代码是外设(上位机)通过USART用DMA传输数据到Falsh中

以下图片可以看到是不经过CPU数据传输

USART1实现发送接收,这里需要记住DMA通道,等会得用到DMA的通道实现USART1接收和发数据的中断

1:DMA初始化

void DMA_Config(void)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    DMA_InitTypeDef  DMA_InitStructure;    
    DMA_DeInit(DMA1_Channel5); 
    DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;   
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; 
    DMA_InitStructure.DMA_PeripheralInc =  DMA_PeripheralInc_Disable; 
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt]; 
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_Init(DMA1_Channel5,&DMA_InitStructure);

}

2:理解 

#define    SECTORSIZE       (1024*2)
#define     RX_ARRAY          2
#define     RXBUFFERSIZE  (1024+128)
#define     TXBUFFERSIZE  1024
uint32_t     rx_array_cnt;       //缓冲区
uint8_t       RxBuffer [RX_ARRAY][RXBUFFERSIZE];//数据大小

//设置DMA数据传输的缓冲区大小为 RXBUFFERSIZE,即需要传输的数据量

1:DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;

//禁止存储器到存储器的传输模式

2:DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

//设置DMA工作模式作为普通模式,即非循环模式,普通模式下,DMA只传输一次数据后就停止。

3:DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  

//设置数据传输方向,为外设到寄存器,(外设是源地址。存储器是目的地址)

4:DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;   

//设置DMA传输的优先级为高优先级,决定了在有多个DMA请求时DMA的处理顺序。

5:DMA_InitStructure.DMA_Priority = DMA_Priority_High;

//允许存储器地址自增功能,每次传输完成之后,存储器地址会自动增加,编译连续存储数据

6:DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 

//配置DMA数据传输的外设基地址为USART1寄存器DR,USART1是外设,DR是数据寄存器,作为数据传输的源地址

7:DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; 

//禁止外设地址自增,因为USART1的DR寄存器地址是固定的,不需要自增

8:DMA_InitStructure.DMA_PeripheralInc =  DMA_PeripheralInc_Disable; 

//设置存储器数据的大小为字节

9:DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 

//设置DMA数据传输的存储器基地址为RxBuffer数据,这里的RxBuffer是存储数据的缓冲器。

10:DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt]; 

//设置外设数据的大小为字节

11:DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

//使用上述配置初始化DMA1的第五个通道

12:DMA_Init(DMA1_Channel5,&DMA_InitStructure);

//将DMA1的第5个通道所有配置重置为默认值

13:DMA_DeInit(DMA1_Channel5); 
 

//清除 DMA 通道 x 中断待处理标志位    
14:DMA_ClearITPendingBit(DMA1_IT_TC5);  

//使能或者失能指定的通道 x 中断    
15:DMA_ITConfig(DMA1_Channel5,DMA_IT_TC,ENABLE); 

 3:初始化USART1

void USART_Config(void)
{

    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_USART1,ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA,&GPIO_InitStructure);


    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_Rx | USART_Mode_Tx;

    USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
    USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
    USART_Init(USART1,&USART_InitStructure);

    USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);  
    USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
    USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); 
    USART_Cmd(USART1, ENABLE);
    USART_ClearITPendingBit(USART1, USART_IT_IDLE);
}

4:普通io口理解: 

1:打开普通IO脚时钟,复用时钟,串口时钟,因为普通io脚复用成USART模式所以需要打开三个时钟。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO|RCC_APB2Periph_USART1, ENABLE);

2:复用推挽输出 GPIO_Mode_AF_PP

USART一般都是配置复用功能,这样引脚就能兼容USART功能,推挽输出模式允许引脚既能提供高电平输出,也能提供低电平输出,这是为了适应USART的输出模式,以正确的传输数据

3:浮空输入 GPIO_Mode_IN_FLOATING

 对于USART的接收线(RX),设置为浮空输入模式是因为UASRT接收到的数据是外部设备发送的,这种模式引脚不会被内部上拉或下拉,而是接收外部设备发送的数据信号

以上配置引脚是为了确保引脚正确和USART相连工作,推挽输出适用于USART输出数据,浮空输入适用于USART接收数据。

5:串口理解:

   //波特率

    USART_InitStructure.USART_BaudRate = 115200; 

   //设置数据长度为8位
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;

    //设置停止位为1位
    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_Rx | USART_Mode_Tx;  

   

    //发送DMA请求

    USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);

     

    //接收DMA请求
    USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);


    USART_Init(USART1, &USART_InitStructure);

    //空闲总线中断

    USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);  

    //接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);

7:NVIC优先级

void NVIC_Config(void)
{

    NVIC_InitTypeDef NVIC_InitStruct; 

    NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;  
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x2;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
    NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel5_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x2;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

} 

 8:DMA中断

void DMA1_Channel5_IRQHandler(void)
{

    if(DMA_GetITStatus(DMA1_IT_TC5) != RESET)
    {

        DMA_ClearITPendingBit(DMA1_IT_TC5);
        DMA_Cmd(DMA1_Channel5, DISABLE);
        rx_array_cnt++;
        rx_array_cnt = rx_array_cnt%RX_ARRAY;
        memset(RxBuffer[rx_array_cnt],0,RXBUFFERSIZE);
        DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt];
        DMA_Init(DMA1_Channel5,&DMA_InitStructure);
        DMA_Cmd(DMA1_Channel5, ENABLE);
    }
    if(DMA_GetITStatus(DMA1_IT_TC4) != RESET)
    {
    
        DMA_ClearITPendingBit(DMA1_IT_TC4);
    }
}

void DMA1_Channel5_IRQHandler(void)
{
    // 检查 DMA1 通道 5 传输完成中断标志位
    if(DMA_GetITStatus(DMA1_IT_TC5) != RESET)
    {
        // 清除 DMA1 传输完成中断标志位
        DMA_ClearITPendingBit(DMA1_IT_TC5);
        
        // 关闭 DMA1 通道 5
        DMA_Cmd(DMA1_Channel5, DISABLE);
        
        // 增加接收缓冲区计数器并取模,确保在接收缓冲区数组范围内
        rx_array_cnt++;
        rx_array_cnt = rx_array_cnt % RX_ARRAY;
        
        // 将当前接收缓冲区清零
        memset(RxBuffer[rx_array_cnt], 0, RXBUFFERSIZE);
        
        // 重新配置 DMA1 通道 5 的 DMA 参数,包括缓冲区大小、内存基地址等
        DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt];
        DMA_Init(DMA1_Channel5, &DMA_InitStructure);
        
        // 再次使能 DMA1 通道 5,准备下一次传输。
        DMA_Cmd(DMA1_Channel5, ENABLE);
    }
    
    // 检查 DMA1_IT_TC4 传输完成中断标志位
    if(DMA_GetITStatus(DMA1_IT_TC4) != RESET)
    {
        // 清除 DMA1_IT_TC4 传输完成中断标志位
        DMA_ClearITPendingBit(DMA1_IT_TC4);
    }
}
 

9:USART中断

void USART1_IRQHandler(void)
{
    uint32_t id = 0;
    uint8_t FIFO_Level = 0,i = 0;
    uint16_t cnt=0;

    if(USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
    {
        USART_ClearFlag(USART1, USART_FLAG_ORE);
    }

    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
    {


        USART_ClearITPendingBit(USART1, USART_IT_IDLE);
        DMA_Cmd(DMA1_Channel5, DISABLE);
        cnt = RXBUFFERSIZE - DMA_GetCurrDataCounter(DMA1_Channel5);
        rx_array_cnt++;
        rx_array_cnt = rx_array_cnt%RX_ARRAY;
        memset(RxBuffer[rx_array_cnt],0,RXBUFFERSIZE);
        DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt];
        DMA_Init(DMA1_Channel5,&DMA_InitStructure);
        DMA_Cmd(DMA1_Channel5, ENABLE);

        uart_com_rcv((uint8_t *)RxBuffer[(rx_array_cnt-1)%RX_ARRAY],cnt);
    }

}

 

void USART1_IRQHandler(void)
{
    // 定义变量
    uint32_t id = 0;
    uint8_t FIFO_Level = 0, i = 0;
    uint16_t cnt = 0;

    // 检查 USART1 接收寄存器溢出标志
    if(USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
    {
        // 如果溢出标志位为非零,则清除溢出标志位
        USART_ClearFlag(USART1, USART_FLAG_ORE);
    }

    // 检查 USART1 空闲中断标志位
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
    {
        // 读取 USART1 数据寄存器,但没有使用返回值
        USART1->DR;
        
        // 清除 USART1 空闲中断标志位
        USART_ClearITPendingBit(USART1, USART_IT_IDLE);
        
        // 禁止 DMA1 通道 5
        DMA_Cmd(DMA1_Channel5, DISABLE);
        
        // 计算接收数据的数量
        cnt = RXBUFFERSIZE - DMA_GetCurrDataCounter(DMA1_Channel5);
        
        // 增加接收缓冲区计数器并取模,确保在接收缓冲区数组范围内
        rx_array_cnt++;
        rx_array_cnt = rx_array_cnt % RX_ARRAY;
        
        // 将接收缓冲区 RxBuffer[rx_array_cnt] 清零
        memset(RxBuffer[rx_array_cnt], 0, RXBUFFERSIZE);
        
        // 重新配置 DMA1 通道 5 的参数,包括缓冲区大小和内存基地址
        DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt];
        DMA_Init(DMA1_Channel5, &DMA_InitStructure);
        
        // 重新使能 DMA1 通道 5,准备下一次传输
        DMA_Cmd(DMA1_Channel5, ENABLE);

        // 执行 uart_com_rcv 函数,处理接收到的数据
        uart_com_rcv((uint8_t *)RxBuffer[(rx_array_cnt - 1) % RX_ARRAY], cnt);
    }
}
 

10:USART发送

void usart_send(uint8_t *data,uint16_t len)
{
    int i = 0;
    uint8_t *p = NULL;
    memset(TxBuffer,0,TXBUFFERSIZE);
    memcpy(TxBuffer,data,len);
    p = TxBuffer;
    for(i=0; i<len;i++,p++)
    {
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); 
        USART1->DR = ((*p) & 0xFF);
    }
}

以下是完整代码连接 

DMA代码链接-CSDN博客

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值