STM32F407 HAL库定时器触发ADC采集与DMA数据传输(定时器TIM+ADC+DMA)

在STM32F407系列微控制器的开发中,结合定时器、ADC(模数转换器)与DMA(直接存储器访问)控制器,能够显著提升数据采集与传输的效率。本文将指导你如何使用STM32 HAL库,通过定时器触发ADC1的单通道采集,利用DMA传输数据,最终通过串口将电压值打印出来。具体实现中,我们将读取ADC1的通道5(对应引脚PA5),并将转换得到的电压值发送到串口助手上进行显示。

 一、开发环境

硬件:正点原子探索者 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增加代码:main.c的第1行添加printf的头文件#include<stdio.h>。​​​​​ADC电压换算时用到公式:待测电压=(ADC的返回值/4095​)∗3.3V​.

    /* USER CODE BEGIN Header */
    #include<stdio.h>
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * Copyright (c) 2024 STMicroelectronics.
      * All rights reserved.
      *
      * This software is licensed under terms that can be found in the LICENSE file
      * in the root directory of this software component.
      * If no LICENSE file comes with this software, it is provided AS-IS.
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    #define ADC_DMA_BUF_SIZE        200          /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍 */
    
    uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];   /* ADC DMA BUF */
    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); //数据打印,查看结果
    	}
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    	}
      /* USER CODE END 3 */
    }
  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,当输入正弦波得到如下结果。

0.073
0.137
0.361
0.735
1.248
1.724
2.242
2.721
3.09
3.3
3.3
3.3
3.158
2.783
2.326
1.809
1.256
0.798
0.417
0.164
0.068
0.128
0.356
0.723
1.189
1.71
2.235
2.713
3.111
3.3
3.3
3.3
3.169
2.832
2.337
1.809
1.3
0.764
0.418
0.175
0.073
0.134
0.358
0.713
1.183
1.702
2.223
2.702
3.095
3.3
3.3
3.3
3.173
2.807
2.346
1.824
1.31
0.817
0.429
0.176
0.064
0.094
0.347
0.7
1.164
1.684
2.216
2.699
2.978
3.3
3.3
3.3
3.173
2.782
2.345
1.838
1.319
0.824
0.438
0.172
0.071
0.121
0.343
0.697
1.171
1.678
2.22
2.692
3.076
3.273
3.3
3.3
3.184
2.822
2.361
1.86
1.328
0.836
0.434
0.201
0.057
0.116
0.34
0.679
1.065
1.655
2.186
2.66
3.063
3.3
3.3
3.3
3.197
2.869
2.379
1.884
1.348
0.80.103
0.069
0.216
0.507
0.939
1.415
1.947
2.454
2.899
3.219
3.3
3.3
3.3
3.016
2.596
2.102
1.579
1.065
0.618
0.288
0.098
0.069
0.212
0.506
0.915
1.401
1.939
2.466
2.899
3.222
3.3
3.3
3.3
3.016
2.609
2.107
1.596
1.065
0.625
0.294
0.108
0.07
0.202
0.499
0.907
1.405
1.926
2.451
2.887
3.219
3.3
3.3
3.3
3.025
2.609
2.119
1.593
1.083
0.629
0.297
0.106
0.068
0.2
0.494
0.901
1.396
1.929
2.437
2.888
3.219
3.3
3.3
3.3
3.025
2.619
2.122
1.598
1.086
0.637
0.303
0.106
0.067
0.21
0.486
0.902
1.386
1.914
2.43
2.879
3.212
3.3
3.3
3.3
3.036
2.634
2.14
1.603
1.09
0.635
0.297
0.113
0.068
0.203
0.482
0.89
1.381
1.907
2.414
2.872
3.214
3.3
3.3
3.3
3.049
2.633
2.144
1.617
1.102
0.65
0.308
0.119
0.068
0.195
0.484
0.872
1.376
1.901
2.417
2.862
3.208
3.3
3.3
3.3
3.007
2.646
2.154
1.633
1.115
0.663
0.312
0.108
0.065
0.172
0.462
0.873
1.398
1.888
2.401
2.866
3.197
3.3
3.3
3.3
3.049
2.649
2.152
1.629
1.11
0.704
0.319
0.104
0.066
0.191
0.473
0.857
1.351
1.877
2.4
2.854
3.201
3.3
3.3
3.3
3.069
2.664
2.173
1.647
1.131
0.674
0.326
0.084
0.068
0.189
0.46
0.855
1.347
1.87
2.387
2.837
3.198
3.3
3.3
3.3
3.063
2.67
2.181
1.66
1.145
0.687
0.332

​导入excel看波形,采样点数 = 采样频率 / 信号频率 = 100kHz / 10kHz = 10。

为了验证采样率是否是100k,ADC去采集信号发生器产生的5k信号,excel看波形,可以看到一个周期20个点。采样点数 = 采样频率 / 信号频率 = 100kHz / 5kHz = 20,说明采样率为100k。

再进一步验证,ADC去采集信号发生器产生的10k信号,excel看波形,可以看到一个周期10个点。采样点数 = 采样频率 / 信号频率 = 100kHz / 10kHz = 10。说明采样率为100k。

​五、注意事项

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

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

通过上述步骤,你就可以在STM32F407上实现基于定时器触发的单通道ADC采集与DMA数据传输,并通过串口打印电压值的功能了。

以下是基于HAL库stm32f407定时器触发adc dma多通道采样代码: 首先,需要初始化定时器ADC: ``` TIM_HandleTypeDef htim; ADC_HandleTypeDef hadc; void MX_TIM_Init(void) { TIM_MasterConfigTypeDef sMasterConfig = {0}; htim.Instance = TIMx; htim.Init.Prescaler = TIMx_PRESCALER; htim.Init.CounterMode = TIM_COUNTERMODE_UP; htim.Init.Period = TIMx_PERIOD; htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim.Init.RepetitionCounter = 0; HAL_TIM_Base_Init(&htim); sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim, &sMasterConfig); } void MX_ADC_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc.Instance = ADCx; hadc.Init.ClockPrescaler = ADC_CLOCKPRESCALER_PCLK_DIV2; hadc.Init.Resolution = ADC_RESOLUTION_12B; hadc.Init.ScanConvMode = ENABLE; hadc.Init.ContinuousConvMode = DISABLE; hadc.Init.DiscontinuousConvMode = DISABLE; hadc.Init.NbrOfDiscConversion = 0; hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; hadc.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_Tx_TRGO; hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc.Init.NbrOfConversion = ADC_CHANNEL_NUM; hadc.Init.DMAContinuousRequests = ENABLE; hadc.Init.EOCSelection = ADC_EOC_SEQ_CONV; HAL_ADC_Init(&hadc); for (int i = 0; i < ADC_CHANNEL_NUM; ++i) { sConfig.Channel = ADC_CHANNEL_x[i]; sConfig.Rank = i + 1; sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES; HAL_ADC_ConfigChannel(&hadc, &sConfig); } } ``` 然后,需要初始化DMA和中断: ``` DMA_HandleTypeDef hdma_adc; void MX_DMA_Init(void) { hdma_adc.Instance = DMAx_STREAMx; hdma_adc.Init.Channel = DMA_CHANNELx; hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc.Init.MemInc = DMA_MINC_ENABLE; hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc.Init.Mode = DMA_CIRCULAR; hdma_adc.Init.Priority = DMA_PRIORITY_HIGH; hdma_adc.Init.FIFOMode = DMA_FIFOMODE_DISABLE; HAL_DMA_Init(&hdma_adc); __HAL_LINKDMA(&hadc, DMA_Handle, hdma_adc); } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc->Instance == ADCx) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } } ``` 最后,在main函数中启动定时器DMA: ``` int main(void) { HAL_Init(); MX_TIM_Init(); MX_ADC_Init(); MX_DMA_Init(); HAL_TIM_Base_Start(&htim); HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc_buf, ADC_CHANNEL_NUM); while (1) { HAL_Delay(1000); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值