在STM32F407系列微控制器的开发中,结合定时器、ADC(模数转换器)与DMA(直接存储器访问)控制器,能够显著提升数据采集与传输的效率。本文将指导你如何使用STM32 HAL库,通过定时器触发ADC1的单通道采集,利用DMA传输数据,累加的方式计算正弦波的平均值、均方根、最大值、最小值、峰峰值,最终通过串口将电压值打印出来,仅显示简单演示运算原理,精度不高。具体实现中,我们将读取ADC1的通道5(对应引脚PA5),并将转换得到的电压值发送到串口助手上进行显示。无需DSP库。

 一、开发环境

硬件:正点原子探索者 V3 STM32F407开发板

单片机:STM32F407ZGT6

Keil版本:5.32

STM32CubeMX版本:6.9.2

STM32Cube MCU Packges版本:STM32F4xx_DFP.2.14.0

  二、配置STM32CubeMX

  1. 启动STM32CubeMX,新建STM32CubeMX项目
  2. 选择MCU:在软件中选择你的STM32型号-STM32F407ZGT6。
  3. 选择时钟源:

  4. 配置时钟:
  5. 使能Debug功能:Serial Wire
  6. HAL库时基选择:SysTick
  7. USART1配置:选择异步模式。
  8. 开启外部时钟:配置系统时钟,确保ADC和串口的外部时钟已开启。

  9. 配置ADC

1)选择ADC1作为采集模块。

2)设置ADC1的通道5(对应引脚PA5)为采集通道。

3)配置采样时间和分辨率。通常,采样时间越长,ADC的转换精度越高,但也会增加转换时间。

4)启用连续禁用模式。

5)禁用扫描模式,因为我们只采集一个通道。

10.配置定时器3

  • 时钟源频率:84 MHz
  • 预分频器(Prescaler)的值:0
  • 周期(Period)的值:840 - 1 = 839(因为是从0开始计数,所以实际周期是840个时钟周期,但寄存器中存储的是839)
  • 定时器时钟频率 = 时钟源频率 / (预分频器值 + 1) = 84 MHz / (1 + 1) = 84 MHz / 1 = 84 MHz
  • 定时器输出频率 = 定时器时钟频率 / (周期值 + 1) = 假设的定时器时钟频率 84 MHz / 840 = 100 kHz

11.配置工程参数:在Project标签页中,配置项目名称和位置,选择工具链MDK-ARM。​ 12.生成代码:在Code Generator标签页中,配置工程外设文件与HAL库,勾选头文件.c和.h文件分开,然后点击Project > Generate Code生成代码。 

三、代码实现与部署

  1.  main.c增加代码:​​​​​ADC电压换算时用到公式:待测电压=(ADC的返回值/4095​)∗3.3V​.

    #include "math.h"
    #include <stdio.h>  
    #include <string.h>  
    #define ADC_DMA_BUF_SIZE        4096     /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍 */
    uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];   /* ADC DMA BUF */
    //测量类型
    float *g_RMSBUF;
    /* 波形通道相关的数据结构 */
    typedef struct
    {
    	float   WaveMean;              /* 平均值 */
    	float   WavePkPk;              /* 峰峰值 */
    	float   WaveFreq;              /* 频率值 */
    	float   WaveMax;               /* 最大值 */
    	float   WaveMin;               /* 峰峰值 */
    	float   WaveRMS;               /* 均方根 */
    	
    }DATA_T;
    DATA_T data;
    
    void WaveProcess(void);//WaveProcess 功能说明: 波形通道最大值,最小值,平均值,峰峰值和RMS的计算
    extern __IO uint8_t g_adc_dma_sta;               /* DMA传输状态标志, 0,未完成; 1, 已完成 */
    // 当前ADC值的索引  
    volatile uint16_t adc_index = 0;  
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_DMA_Init();
      MX_USART1_UART_Init();
      MX_ADC1_Init();
      MX_TIM3_Init();
      /* USER CODE BEGIN 2 */
    	HAL_TIM_Base_Start(&htim3);                           //开启定时器3
        // 启动ADC采集并通过DMA传输数据到缓冲区  
       HAL_ADC_Start_DMA(&hadc1, (uint32_t*)g_adc_dma_buf, ADC_DMA_BUF_SIZE);
    	 while (!g_adc_dma_sta);                                   //等待转换完毕
        
    	for (uint16_t i = 0; i < ADC_DMA_BUF_SIZE; i++)
    	{
        printf("%.3f\r\n", g_adc_dma_buf[i] * 3.3 / 4095); //数据打印,查看结果
    	}
    	WaveProcess();
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    	}
      /* USER CODE END 3 */
    }
    /* USER CODE BEGIN 4 */
    /*
    *********************************************************************************************************
    *	函 数 名: DSO1_WaveProcess
    *	功能说明: 波形通道1的最大值,最小值,平均值,峰峰值,RMS的计算
    *	形    参: 无。
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void WaveProcess(void)
    {
    	
    	
    	unsigned short    i; 
    	unsigned           int   uiCycle, uiCount = 0;
      data.WaveMin = 4095;
    	/* 自动触发模式才计算FFT */
    	
    		
    	/* 求4096个数值的最大值和最小值 */
    	for (i = 0; i < ADC_DMA_BUF_SIZE; i++) 
    	{
    		data.WaveMean += g_adc_dma_buf[i];
    	
    		data.WaveRMS  += g_adc_dma_buf[i]*g_adc_dma_buf[i];
    		
    		if(g_adc_dma_buf[i] < data.WaveMin)
    		{
    			data.WaveMin = g_adc_dma_buf[i];
    		}
    		
    		if(g_adc_dma_buf[i] > data.WaveMax)
    		{
    			data.WaveMax = g_adc_dma_buf[i];
    		}
    	}	
    	/* 求RMS */
    	data.WaveRMS = sqrt(data.WaveRMS/ADC_DMA_BUF_SIZE) * 3.3f / 4095;
    	
    	/* 求平均值 */
    	data.WaveMean = data.WaveMean / ADC_DMA_BUF_SIZE *3.3f / 4095;
    	
    	/* 求最大值 */
    	data.WaveMax =  data.WaveMax * 3.3f / 4095;
    	
    	/* 求最小值 */
    	data.WaveMin = data.WaveMin *3.3f / 4095;
    	
    	/* 求峰峰值 */
    	data.WavePkPk = data.WaveMax - data.WaveMin;		
    	
    	printf("WaveRMS:%.3f\r\n", data.WaveRMS); //数据打印,查看RMS结果
    	printf("WaveMean:%.3f\r\n",data.WaveMean);//数据打印,查看平均值
    	printf("WaveMax:%.3f\r\n", data.WaveMax);//数据打印,查看最大值
    	printf("WaveMin:%.3f\r\n", data.WaveMin);//数据打印,查看最小值 
    	printf("WavePkPk:%.3f\r\n", data.WavePkPk);//数据打印,查看峰峰值
    }
    /* USER CODE END 4 */
  2. stm32f4xx_it.c增加代码

    ADC(模数转换器)负责进行数据采集,而DMA(直接存储器访问)则负责将这些数据高效地搬运到指定的内存区域。当DMA完成数据的搬运任务后,整个ADC的采集过程也随之宣告结束。此时,DMA会触发一个中断信号,通知CPU搬运操作已经完成。

    响应这个DMA中断的是DMA2_Stream0_IRQHandler函数。一旦此函数被调用,它便会执行一系列操作来告知CPU:ADC的采集工作已经全部完成。在程序层面,我们可以通过检查一个名为g_adc_dma_sta的标志位的状态变化,来确认ADC的采集过程是否已经结束。

    /* USER CODE BEGIN 0 */
    __IO uint8_t g_adc_dma_sta =0;  
    /* USER CODE END 0 */
    /**
      * @brief This function handles DMA2 stream0 global interrupt.
      */
    void DMA2_Stream0_IRQHandler(void)
    {
      /* USER CODE BEGIN DMA2_Stream0_IRQn 0 */
    
      /* USER CODE END DMA2_Stream0_IRQn 0 */
      HAL_DMA_IRQHandler(&hdma_adc1);
      /* USER CODE BEGIN DMA2_Stream0_IRQn 1 */
       g_adc_dma_sta = 1;    
      /* USER CODE END DMA2_Stream0_IRQn 1 */
    }
    
  3.  usart.c增加代码:usart.c的第1行添加头文件#include <stdio.h>
    #include <string.h>,在末尾用户代码区增加如下代码。printf调用“fputc()”,“fgetc()”,该函数会使用HAL_UART_Transmit发送数据。
    /*
    * 添加如下代码,可不在工程设置中勾选Use MicroLIB
    */
    #pragma import(__use_no_semihosting)
    
    struct __FILE
    {
    	int a;
    };
    
    FILE __stdout;
    FILE __stdin;
    
    void _sys_exit(int x)
    {
    }
    
    
    /*****************************************************
    *function: 写字符文件函数
    *param1: 输出的字符
    *param2: 文件指针
    *return: 输出字符的ASCII码
    ******************************************************/
    int fputc(int ch, FILE *f)
    {
    	HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 10);
    	return ch;
    }
    /*****************************************************
    *function: 读字符文件函数
    *param1: 文件指针
    *return: 读取字符的ASCII码
    ******************************************************/
    int fgetc(FILE *f)
    {
    	uint8_t ch = 0;
    	HAL_UART_Receive(&huart1, (uint8_t*)&ch, 1, 10);
    	return (int)ch;
    }
  4. 连接USART1:用USB转TTL工具连接当前硬件USART1的PA9、PA10,GND。​​
  5. 打开串口助手:​​
  6. 编译代码:Keil编译生成的代码。
  7. 烧录程序:将编译好的程序用ST-LINK烧录到STM32微控制器中。

四、运行结果

观察结果:一旦程序烧录完成并运行,串口助手打印出PA5的电压,当接地的时候得出的电压值是0,当输入50hz,幅度1v,偏置0.5V正弦波得到如下结果。

当输入50hz,幅度1v,偏置0.5V正弦波得到如下结果。

​导入excel看波形

​五、注意事项

1.确保你的开发环境和工具已经正确安装和配置。

2.如果没有打印电压值,按一下复位键,检查连接和电源是否正确,注意根据你所用的硬件来接线,不要接错线。
3.在串口打印数据时,要确保波特率等参数与串口助手设置一致。

通过上述步骤,你就可以在STM32F407上实现基于定时器触发的单通道ADC采集与DMA数据传输,并通过累加的方式计算正弦波的平均值、均方根、最大值、最小值、峰峰值,最终通过串口将电压值打印出来,代码尚有不完善的地方,仅显示简单演示运算原理,精度不高,仅供参考。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐