STM32 HAL 库实现 ADC 采样
XiaoMa 博士生

例程 1:ADC 单次数据采样与电压换算

要求:在 STM32F429IGTx 中进行 STM32 应用开发,完成以下的功能。

  • 将 ADC_IN0 设置为 12 位 ADC,右对齐,启用中断。
  • 分别用查询和中断这 2 种方式,每隔 0.5 秒采样一次 ADC 数据。
  • 将每次读取到的 ADC 采样值转换为对应的电压值,发送到上位机(需用到串口)。
  • LED1 作为采样指示灯,在 ADC 转换过程中点亮,其余时间熄灭。LED0 作为系统检测灯常亮。

【注】配置 GPIO、ADC1、USART1。

  1. GPIO 比较好配置,就 LED0、LED1 的引脚设为 Output。
  2. 配置 ADC1,PA0 设为 ADC1_IN0,然后勾选 IN0。参数设置模块的 Data Alignment 默认 Right alignment(右对齐)即可,同样其他都默认,NVIC 中断使能勾选。
  3. 配置 USART1,Mode 选 Asynchronous(异步),参数设置模块波特率改为 9600 Bits/s,其他默认。由于串口没有用到中断,所以 NVIC 模块不需要使能。

步骤

  1. 打开 STM32CubeMX 软件,按照 STM32CubeMX 通用配置[1]配置完成后。将 PB0、PB1 引脚分别设置为 GPIO_Out。如图所示:
image-20230411162944353
图 1.1 LED 灯引脚配置
  1. 配置 ADC。将 PA0 引脚设置为 ADC1_IN0,然后勾选 ADC1IN0。如图所示:
image-20230411163909279
图 1.2 ADC1 引脚、参数配置

image-20230411164125549

图 1.3 ADC1 中断使能
  1. 配置串口 USART1。选择异步通信,波特率设置为 9600。配置完成后,PA9、PA10 引脚会自动变成绿色。由于本要求中只写了将每次读取到的 ADC 采样值转换为对应的电压值,发送到上位机,且在代码中使用阻塞式发送,因此就没有必要开启中断了。

image-20230411164502352

图 1.4 USART1 配置
  1. 进行 STM32CubeMX 通用配置 [1]的第 5 步:生成代码。

  2. 打开工程文件。按照 Keil5 MDK 通用配置 [1]完成后开始编写代码。

    打开 main.c 文件。

    方式 1:查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    -----------------------
    ↓↓↓注:main() 函数外↓↓↓
    -----------------------
    /* USER CODE BEGIN 0 */

    /* 第一步:引入头文件,以使用 sprintf() 函数 */
    #include "stdio.h"

    /* 第二步:宏定义 LED 灯开关 */
    #define LED0_ON() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
    #define LED0_OFF() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
    #define LED1_ON() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
    #define LED1_OFF() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);

    /* 第三步-1:定义变量。采样到数据存储在 ADC_Value 中,ADC_Volt 用于运算 */
    /* 第三步-2:定义 str_buff[64],用于向上位机发送数据,用在 sprintf() 函数中 */
    uint16_t ADC_Value = 0, ADC_Volt = 0;
    uint8_t str_buff[64];

    /* 第五步:写发送数据到上位机的功能函数 */
    void UR1_Send_Info()
    {
    /* 将电压值取余,分别放到个位和小数位上 */
    sprintf((char *)str_buff, "采样值:%d,电压值:%d.%d%d%dv\r\n", ADC_Value, ADC_Volt/1000, (ADC_Volt%1000)/100, (ADC_Volt%100)/10, ADC_Volt%10);
    /* 阻塞式串口发送函数,将缓冲数组 str_buff 发送到上位机,显示在串口调试助手中 */
    HAL_UART_Transmit(&huart1, str_buff, sizeof(str_buff), 10000);
    }

    /* 第四步-1:查询方式 */
    /* 功能函数:采样数据(写完后放在 main() 函数中运行 */
    void ADC1_Get_Value()
    {
    HAL_ADC_Start(&hadc1); //启动 ADC1
    LED1_OFF(); //关闭 LED1,表示 ADC 开始转换

    /* 判断查询转换函数,如果确实是在转换(HAL_OK),则读 ADC1 的值 */
    if(HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
    {
    ADC_Value = HAL_ADC_GetValue(&hadc1); //得到 ADC1 的值
    ADC_Volt = ADC_Value * 3300 / 4096; //计算电压值的转换公式,3300mv = 3.3v,4096 是 2 的 12 次方,因为是 12 位的 ADC
    }

    UR1_Send_Info(); //在上面写此功能函数,一定要写在上面。或者写在下面,但是一定要在上面声明
    LED1_ON(); //开启 LED1,表示 ADC 转换完成

    HAL_ADC_Stop(&hadc1); //关闭 ADC1
    }
    /* USER CODE END 0 */

    -----------------------
    ↓↓↓注:main() 函数内↓↓↓
    -----------------------
    /* USER CODE BEGIN WHILE */
    while (1)
    {
    /*第四步-2,运行该采样功能函数*/
    ADC1_Get_Value();
    HAL_Delay(500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */

    方式 2:中断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    -----------------------
    ↓↓↓注:main() 函数外↓↓↓
    -----------------------
    /* USER CODE BEGIN 0 */

    /* 第一步:引入头文件,以使用 sprintf() 函数 */
    #include "stdio.h"

    /* 第二步:宏定义 LED 灯开关 */
    #define LED0_ON() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
    #define LED0_OFF() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
    #define LED1_ON() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
    #define LED1_OFF() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);

    /* 第三步-1:定义变量。采样到数据存储在 ADC_Value 中,ADC_Volt 用于运算 */
    /* 第三步-2:定义 str_buff[64],用于向上位机发送数据,用在 sprintf() 函数中 */
    uint16_t ADC_Value = 0, ADC_Volt = 0;
    uint8_t str_buff[64];

    /* 第五步:写发送数据到上位机的功能函数 */
    void UR1_Send_Info()
    {
    /* 将电压值取余,分别放到个位和小数位上 */
    sprintf((char *)str_buff, "采样值:%d,电压值:%d.%d%d%dv\r\n", ADC_Value, ADC_Volt/1000, (ADC_Volt%1000)/100, (ADC_Volt%100)/10, ADC_Volt%10);
    /* 阻塞式串口发送函数,将缓冲数组 str_buff 发送到上位机,显示在串口调试助手中 */
    HAL_UART_Transmit(&huart1, str_buff, sizeof(str_buff), 10000);
    }

    /* 第四步-1:重写 ADC 中断回调函数 */
    void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
    {
    if(hadc->Instance == ADC1) //如果有多个 ADC,就要先用 if 来判断是不是 ADC1
    {
    ADC_Value = HAL_ADC_GetValue(&hadc1); //得到 ADC1 的采样值
    ADC_Volt = ADC_Value * 3300 / 4096; //转换为电压,3300mv = 3.3v,4096 是 2 的 12 次方,因为是 12 位的 ADC
    UR1_Send_Info(); //发送函数,写在了上面
    LED1_ON(); //因为在主函数中,LED1 先灭掉了,才启动的中断服务函数
    }
    }
    /* USER CODE END 0 */

    -----------------------
    ↓↓↓注:main() 函数内↓↓↓
    -----------------------
    /* USER CODE BEGIN WHILE */
    while (1)
    {
    /*第四步-2,运行该采样功能函数*/
    LED1_OFF(); //中断启动前关掉 LED1
    HAL_ADC_Start_IT(&hadc1); //以中断方式启动 ADC1
    HAL_Delay(500); //延时 0.5s,然后运行中断回调函数里面的内容(最后一句:LED1 开启)
    HAL_ADC_Stop_IT(&hadc1); //运行完后关闭 ADC1,然后继续循环
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
image-20230411174232128
图 1.5 串口调试助手接收到的采样数值