第23章 DMA实验

第二十三章 DMA实验

1. 硬件设计

本章用到的硬件资源有:

  • 指示灯DS0

  • KEY0按键

  • 串口

  • TFTLCD模块

  • DMA

本章我们将利用外部按键 KEY0 来控制 DMA 的传送,每按一次 KEY0, DMA 就传送一次数据到USART1,然后在 TFTLCD 模块上显示进度等信息。 DS0 还是用来做为程序运行的指示灯。

2. 软件设计

2.1 DMA初始化及配置

DMA_HandleTypeDef  UART1TxDMA_Handler; // DMA句柄

// DMAx的各通道配置
// 这里的传输形式是固定的,这点要根据不同的情况来修改
// 从存储器->外设模式/8位数据宽度/存储器增量模式
// DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
// chx:DMA通道选择,@ref DMA_channel DMA_CHANNEL_0~DMA_CHANNEL_7
void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx)
{ 
    if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
    {
        __HAL_RCC_DMA2_CLK_ENABLE();//DMA2时钟使能    
    }else 
    {
        __HAL_RCC_DMA1_CLK_ENABLE();//DMA1时钟使能 
    }

    __HAL_LINKDMA(&UART1_Handler,hdmatx,UART1TxDMA_Handler);    //将DMA与USART1联系起来(发送DMA)

    //Tx DMA配置
    UART1TxDMA_Handler.Instance=DMA_Streamx;                        // 数据流选择
    UART1TxDMA_Handler.Init.Channel=chx;                            // 通道选择
    UART1TxDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH;         // 存储器到外设
    UART1TxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE;             // 外设非增量模式
    UART1TxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE;                 // 存储器增量模式
    UART1TxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;// 外设数据长度:8位
    UART1TxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE;   // 存储器数据长度:8位
    UART1TxDMA_Handler.Init.Mode=DMA_NORMAL;                        // 外设普通模式
    UART1TxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM;           // 中等优先级
    UART1TxDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE;              
    UART1TxDMA_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL;      
    UART1TxDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE;             // 存储器突发单次传输
    UART1TxDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE;          // 外设突发单次传输
    HAL_DMA_DeInit(&UART1TxDMA_Handler);   
    HAL_DMA_Init(&UART1TxDMA_Handler);
} 

2.2 DMA串口传输

// 开启一次DMA传输
// huart:串口句柄
// pData:传输的数据指针
// Size:传输的数据量
void MYDMA_USART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) // 函数参数:串口句柄,数据指针,数据长度
{
    HAL_DMA_Start(huart->hdmatx, (u32)pData, (uint32_t)&huart->Instance->DR, Size); // 开启DMA传输 
    huart->Instance->CR3 |= USART_CR3_DMAT;//使能串口DMA发送
}

2.3 主函数

#define SEND_BUF_SIZE 8200    //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.

u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区
const u8 TEXT_TO_SEND[]={"ALIENTEK Explorer STM32F4 DMA 串口实验"}; 

int main(void)
{
    u16 i;
    u8 t=0;
    u8 j,mask=0;
    float pro=0; 

    HAL_Init();                 // 初始化HAL库    
    Stm32_Clock_Init(336,8,2,7);// 设置时钟,168Mhz
    delay_init(168);            // 初始化延时函数
    uart_init(115200);          // 初始化USART
    usmart_dev.init(84);         // 初始化USMART
    LED_Init();                    // 初始化LED    
    KEY_Init();                    // 初始化KEY
     LCD_Init();                   // 初始化LCD
    MYDMA_Config(DMA2_Stream7,DMA_CHANNEL_4); // 初始化DMA,使用DMA2_Stream7通道4
    POINT_COLOR=RED; 
    LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");    
    LCD_ShowString(30,70,200,16,16,"DMA TEST");     
    LCD_ShowString(30,130,200,16,16,"KEY0:Start"); 
    POINT_COLOR=BLUE;//设置字体为蓝色 
    //显示提示信息    
    j=sizeof(TEXT_TO_SEND);       
    for(i=0;i<SEND_BUF_SIZE;i++)  // 填充ASCII字符集数据
    {
        if(t>=j)//加入换行符
        {
            if(mask)
            {
                SendBuff[i]=0x0a;
                t=0;
            }else 
            {
                SendBuff[i]=0x0d;
                mask++;
            }    
        }else//复制TEXT_TO_SEND语句
        {
            mask=0;
            SendBuff[i]=TEXT_TO_SEND[t];
            t++;
        }          
    }   
    POINT_COLOR=BLUE;//设置字体为蓝色      
    i=0;
       while(1)
    {
        t=KEY_Scan(0);
        if(t==KEY0_PRES)  //KEY0按下
        {
            printf("\r\nDMA DATA:\r\n");         
            LCD_ShowString(30,150,200,16,16,"Start Transimit....");
            LCD_ShowString(30,170,200,16,16,"   %") ;     //显示百分号      
            HAL_UART_Transmit_DMA(&UART1_Handler,SendBuff,SEND_BUF_SIZE);//启动传输
            //使能串口1的DMA发送 //等待DMA传输完成,此时我们来做另外一些事,点灯
            //实际应用中,传输数据期间,可以执行另外的任务
            while(1)
            {
                if(__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7))//等待DMA2_Steam7传输完成
                {
                    __HAL_DMA_CLEAR_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7);//清除DMA2_Steam7传输完成标志
                    HAL_UART_DMAStop(&UART1_Handler);      //传输完成以后关闭串口DMA
                    break; 
                }
                pro=__HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler);//得到当前还剩余多少个数据
                pro=1-pro/SEND_BUF_SIZE;    //得到百分比      
                pro*=100;                      //扩大100倍
                LCD_ShowNum(30,170,pro,3,16);        
            }
            LCD_ShowNum(30,170,100,3,16);//显示100%      
            LCD_ShowString(30,150,200,16,16,"Transimit Finished!");//提示传送完成
        }
        i++;
        delay_ms(10);
        if(i==20)
        {
            LED0=!LED0;//提示系统正在运行    
            i=0;
        }           
    }
}

main 函数的流程大致是:先初始化内存 SendBuff 的值,然后通过 KEY0 开启串口 DMA 发送,在发送过程中,通过__HAL_DMA_GET_COUNTER()函数获取当前还剩余的数据量来计算传输百分比,最后在传输结束之后清除相应标志位,提示已经传输完成。这里还有一点要注意,因为是使用的串口 1 DMA 发送,所以代码中使用 HAL_UART_Transmit_DMA 函数开启串口的 DMA 发送。

3. 小结

DMA不光可以用在串口上,比如下面我们可以利用DMA来采集ADC数据

实验目标

  • 理解 DMA 的工作原理
  • 学习如何配置 DMA 进行 ADC 数据采集
  • 验证 DMA 数据传输的正确性

代码实现

#include "main.h"

#define BUFFER_SIZE 100 // 定义 ADC 数据缓冲区大小
uint32_t adc_buffer[BUFFER_SIZE]; // ADC 数据缓冲区
UART_HandleTypeDef huart2; // UART 句柄

// ADC 和 DMA 相关句柄
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

// 初始化函数声明
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(void);

// 主函数
int main(void) {
    HAL_Init(); // 初始化 HAL 库
    SystemClock_Config(); // 配置系统时钟
    MX_GPIO_Init(); // 初始化 GPIO
    MX_DMA_Init(); // 初始化 DMA
    MX_ADC1_Init(); // 初始化 ADC
    MX_USART2_UART_Init(); // 初始化 UART

    // 启动 ADC DMA
    HAL_ADC_Start_DMA(&hadc1, adc_buffer, BUFFER_SIZE);

    while (1) {
        // 计算 ADC 数据的平均值
        uint32_t sum = 0;
        for (int i = 0; i < BUFFER_SIZE; i++) {
            sum += adc_buffer[i];
        }
        uint32_t average = sum / BUFFER_SIZE;

        // 将平均值发送到串口
        char buffer[50];
        int len = snprintf(buffer, sizeof(buffer), "Average ADC Value: %lu\r\n", average);
        HAL_UART_Transmit(&huart2, (uint8_t*)buffer, len, HAL_MAX_DELAY); // 通过 UART 发送数据

        HAL_Delay(1000); // 每秒发送一次
    }
}

// ADC 初始化
static void MX_ADC1_Init(void) {
    ADC_ChannelConfTypeDef sConfig = {0};

    // 配置 ADC
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // 设置 ADC 时钟
    hadc1.Init.Resolution = ADC_RESOLUTION12b; // 设置分辨率
    hadc1.Init.ScanConvMode = DISABLE; // 禁用扫描模式
    hadc1.Init.ContinuousConvMode = ENABLE; // 启用连续转换模式
    hadc1.Init.DiscontinuousConvMode = DISABLE; // 禁用不连续转换模式
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 数据对齐
    hadc1.Init.NbrOfConversion = 1; // 只进行一次转换
    HAL_ADC_Init(&hadc1); // 初始化 ADC

    // 配置 ADC 通道
    sConfig.Channel = ADC_CHANNEL_0; // 使用通道 0(PA0)
    sConfig.Rank = 1; // 优先级
    sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES; // 采样时间
    HAL_ADC_ConfigChannel(&hadc1, &sConfig); // 配置 ADC 通道
}

// DMA 初始化
static void MX_DMA_Init(void) {
    __HAL_RCC_DMA1_CLK_ENABLE(); // 启用 DMA1 时钟

    // 配置 DMA
    hdma_adc1.Instance = DMA1_Stream1; // DMA 流
    hdma_adc1.Init.Channel = DMA_CHANNEL_0; // DMA 通道
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; // 从外设到内存
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不增
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; // 内存地址增
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; // 外设数据对齐
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; // 内存数据对齐
    hdma_adc1.Init.Mode = DMA_CIRCULAR; // 循环模式
    hdma_adc1.Init.Priority = DMA_PRIORITY_LOW; // 优先级
    hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // 禁用 FIFO
    HAL_DMA_Init(&hdma_adc1); // 初始化 DMA

    // 连接 ADC 和 DMA
    __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
}

// UART 初始化
static void MX_USART2_UART_Init(void) {
    huart2.Instance = USART2; // UART 实例
    huart2.Init.BaudRate = 115200; // 波特率
    huart2.Init.WordLength = UART_WORDLENGTH_8B; // 字长
    huart2.Init.StopBits = UART_STOPBITS_1; // 停止位
    huart2.Init.Parity = UART_PARITY_NONE; // 奇偶校验
    huart2.Init.Mode = UART_MODE_TX_RX; // 模式
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 硬件流控制
    huart2.Init.OverSampling = UART_OVERSAMPLING_16; // 超采样
    HAL_UART_Init(&huart2); // 初始化 UART
}

// 系统时钟配置
void SystemClock_Config(void) {
    // 配置系统时钟(这里使用默认设置)
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    // 配置振荡器
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
    RCC_OscInitStruct.PLL.PLLM = 16;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 7;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    // 配置时钟
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
}

// GPIO 初始化
static void MX_GPIO_Init(void) {
    // 此处可添加 GPIO 初始化代码(如 LED 等)
}

// DMA 完成回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    // 可以在此处添加数据处理逻辑
}
posted @ 2024-10-18 11:21  hazy1k  阅读(0)  评论(0编辑  收藏  举报