STM32-空闲中断+DMA实现串口数据接收

发布时间 2023-11-09 09:43:44作者: (喜欢黑夜的孩子)

接上一个随笔的空闲中断..........

通过江科大老师的图来复习一下关于DMA的知识

 

DMA的作用

DMA作为一个外设,它的作用就是帮CPU处理数据搬运的事情,减少CPU的消耗

如何佩戴STM32的DMA?

1.使能时钟

2.DMA参数配置

3.DMA使能

关于DMA的各个参数

 DMA_PeripheralBaseAddr: 外设寄存器的地址。(stm32官方将外设地址和内存地址相互区分开了)

DMA_PeripheralDataSize:每次操作外设寄存器的数据大小

DMA_PeripheralInc    :外设寄存器地址是否自增

DMA_MemoryBaseAddr,DMA_MemoryDataSize,DMA_MemoryInc同理,只不过是从外设地址变成了内存地址

DMA_BufferSize      :DMA的转运次数,对应它的相关寄存器  CNDTR寄存器(传输计数器)  ,该寄存器记录转运次数并不断递减

DMA_Mode        :分为正常模式和循环模式,循环模式CNDTR寄存器会自动重载

DMA_M2M        :选择触发方式,本例中使用的是硬件触发,即外设触发

DMA_Priority        :DMA转运的优先级,在多个DMA通道转运的时候会发挥作用吧。

DMA_DIR          :DMA转运的方向,由外设充当的角色决定,如这里外设地址作为SRC(source:来源),说明数据是从外设到内存中

 

关于DMA的启动

三个条件:  1.DMA使能  2.传输计数器 != 0  3.有触发源提供触发信号(无论是软件触发还是硬件(外设)触发)。

更详细的学习DMA建议去B站学习江科大老师的视频

 

以下为代码部分

 

serial部分和空闲中断部分差不多,唯一区别就是中断处理函数部分和初始化部分多了串口触发DMA使能USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);

 

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxData[255];
uint8_t Serial_RxFlag;            //数据接收完成标志位
uint8_t Serial_count;           //串口接收到数据的总数

void Serial_Init(void)
{
    //配置串口,端口时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    //配置端口GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    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_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    //配置串口配置
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1, &USART_InitStructure);
    
    //配置串口接收完成中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);                                //串口空闲中断
    
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
    
    
    //开启DMA接收请求,dma硬件触发开启的最后一块拼图
    USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
    
    USART_Cmd(USART1, ENABLE);
    Serial_count = 0;
}

void Serial_SendByte(uint8_t Byte)
{
    USART_SendData(USART1, Byte);
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
    uint16_t i;
    for (i = 0; i < Length; i ++)
    {
        Serial_SendByte(Array[i]);
    }
}

void Serial_SendString(char *String)
{
    uint8_t i;
    for (i = 0; String[i] != '\0'; i ++)
    {
        Serial_SendByte(String[i]);
    }
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
    uint32_t Result = 1;
    while (Y --)
    {
        Result *= X;
    }
    return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
    uint8_t i;
    for (i = 0; i < Length; i ++)
    {
        Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
    }
}

int fputc(int ch, FILE *f)
{
    Serial_SendByte(ch);
    return ch;
}

void Serial_Printf(char *format, ...)
{
    char String[100];
    va_list arg;
    va_start(arg, format);
    vsprintf(String, format, arg);
    va_end(arg);
    Serial_SendString(String);
}

uint8_t Serial_GetRxFlag(void)
{
    if (Serial_RxFlag == 1)
    {
        Serial_RxFlag = 0;
        return 1;
    }
    return 0;
}


void USART1_IRQHandler(void)
{
    uint8_t clear;
//    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
//    {
//        //USART_ReceiveData()函数会返回DR寄存器的1个字节数据回来
//        Serial_RxData[Serial_count++] = USART_ReceiveData(USART1);
//        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
//    }
    
    if (USART_GetITStatus(USART1, USART_IT_IDLE) == SET)
    {

        
        //只是单纯为了清除空闲中断位
        clear= USART1->SR;            //读取状态寄存器
        clear= USART1->DR;          //读取数据寄存器
        
        DMA_Cmd(DMA1_Channel5,DISABLE);
        Serial_count = 255 - DMA_GetCurrDataCounter(DMA1_Channel5);                //得到DMA接收到了多少个数据值,因为配置之后计数寄存器的值会不断自减,减法结果就是接收到的数据
        DMA1_Channel5->CNDTR = 255;                                                //重新赋计数寄存器的值
        DMA_Cmd(DMA1_Channel5,ENABLE);
        
        //此标志位确定所有数据都接收完成
        Serial_RxFlag = 1;
    
    }
}

serial.h代码
#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>


extern uint8_t Serial_count;
extern uint8_t Serial_RxFlag;
extern uint8_t Serial_RxData[255];

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);

#endif

DMA部分代码,要确认好要配置的通道,如这里我使用了串口1的接收PA10引脚,对应DMA通道就是DMA1的通道5,具体只需要去手册DMA章节上查找即可

void My_usart_DMA_Init()
{
    /*
    STM32把外设和存储器区分开了
    DMA转运的条件:1.DMA使能  2:触发源有信号 3:转运数 > 0
    */
    
    /*开启时钟*/
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);                        //开启DMA的时钟
    
    /*接收DMA初始化*/
    DMA_InitTypeDef DMA_InitStructure;                                        //定义结构体变量
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;                        //串口的DR寄存器作为外设地址
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;    //外设数据宽度,选择字节
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;            //外设地址自增,选择不使能(因为要使用串口DR寄存器)
    
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Serial_RxData;            //存储器基地址,给定Serial_RxData,一个自己定义的缓冲区数组
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;            //存储器数据宽度,选择字节
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                    //存储器地址自增,选择使能
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                        //数据传输方向,选择由外设到存储器,外设站点作为源头
    DMA_InitStructure.DMA_BufferSize = 255;                                //转运的数据大小(转运次数)
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                            //模式,选择正常模式
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                                //DMA触发方式,ENABLE:软件触发  DISABLE:硬件触发
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;                    //优先级,选择中等
    DMA_Init(DMA1_Channel5, &DMA_InitStructure);                            //将结构体变量交给DMA_Init,配置DMA1的通道1
    
    
  //上面配置了外设地址是DR寄存器地址,内存地址是缓冲区数组地址,转运最大数量255,外设站点作为源头(数据: 外设 -> 数组 )
    
    /*DMA使能*/
    /*
    我这里要使用串口1的DMA接收,所以开启了DMA1的通道5
    */
    DMA_Cmd(DMA1_Channel5, ENABLE);    //这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
}

main函数部分
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
#include "Serial.h"




int main(void)
{
    /*模块初始化*/
    OLED_Init();                //OLED初始化
    Serial_Init();
    My_usart_DMA_Init();
    
    Serial_Printf("START");

    while (1)
    {
        if(Serial_RxFlag == 1)
        {
            Serial_RxFlag = 0;
            for(uint8_t i=0;i<Serial_count;i++)
            {
                Serial_SendByte(Serial_RxData[i]);
                Serial_Printf("\r\n");
            }
        
        }
        
        
    }
}