PUYA ADC — 软件触发单次转换(多通道)

PUYA PY32F0xx_Firmware V1.5.0 ADC 软件触发单次转换(ADC_MultiChannelSingleConversion_TriggerSW)示例教学

ADC — 软件触发单次转换(多通道)

固件库: PY32F0xx_Firmware V1.5.0
芯片: PY32F030
源文件: PUYA/PY32F0xx_Firmware_V1.5.0/Projects/PY32F030-STK/Example/ADC/ADC_MultiChannelSingleConversion_TriggerSW/Src/main.c


功能简介

ADC(模数转换器)把引脚上的模拟电压转换成数字量,是读取电位器、温湿度、光照等模拟传感器的基础。

本示例演示软件触发 + 单次转换 + 轮询等待的经典用法:

  • 配置 ADC1,12 位分辨率,扫描通道 0、1、4;
  • 上电后先做校准(Calibration),再用 HAL_ADC_Start() 由软件启动一次转换;
  • HAL_ADC_PollForConversion() 轮询等待转换结束,读取结果;
  • 通过串口(USART2,PA2/PA3,115200)把采样值打印出来。

文件名虽带 MultiChannel,但 ContinuousConvMode = DISABLEExternalTrigConv = ADC_SOFTWARE_START,本质就是“软件触发、扫描多个通道、各转换一次”的模式,最贴合“软件触发单次转换”的教学需求。

硬件准备

  • PY32F030 开发板
  • 可调电位器(中间抽头接 PA0 / PA1 / PA4 任一处)或信号源
  • 串口调试工具(USB 转串口接 PA2=TX、PA3=RX,波特率 115200)
  • 板载串口打印由 BSP_USART_Config() 初始化(见 py32f030xx_Start_Kit.c

完整代码

 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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include "main.h"

/* ADC 句柄与通道配置、保存 3 路采样值 */
ADC_HandleTypeDef       AdcHandle;
ADC_ChannelConfTypeDef  sConfig;
uint32_t adc_value[3];

/* 静态函数声明 */
static void APP_AdcInit(void);

int main(void)
{
  /* 1. HAL 初始化(含 SysTick 时基) */
  HAL_Init();

  /* 2. 初始化 ADC(含校准) */
  APP_AdcInit();

  /* 3. 初始化调试串口(printf 重定向到 USART2) */
  DEBUG_USART_Config();

  while (1)
  {
    /* 4. 软件启动一次 ADC 转换 */
    HAL_ADC_Start(&AdcHandle);

    for (uint8_t i = 0; i < 3; i++)
    {
      /* 5. 轮询等待本通道转换完成(超时 10000ms) */
      HAL_ADC_PollForConversion(&AdcHandle, 10000);
      /* 6. 读取转换结果(12 位,右对齐) */
      adc_value[i] = HAL_ADC_GetValue(&AdcHandle);
      printf("adc[%u]:%u\r\n", i, (unsigned int)adc_value[i]);
    }

    HAL_Delay(1000);   /* 每秒采样一轮 */
  }
}

/**
  * @brief  ADC 初始化:先复位与校准,再配时钟/分辨率/通道
  */
static void APP_AdcInit(void)
{
  /* 复位 ADC 外设,保证起始状态干净 */
  __HAL_RCC_ADC_FORCE_RESET();
  __HAL_RCC_ADC_RELEASE_RESET();
  __HAL_RCC_ADC_CLK_ENABLE();                    /* 开启 ADC 时钟 */

  AdcHandle.Instance = ADC1;
  /* 上电校准:必须在使用 ADC 前执行,否则精度受影响 */
  if (HAL_ADCEx_Calibration_Start(&AdcHandle) != HAL_OK)
  {
    APP_ErrorHandler();
  }

  AdcHandle.Instance = ADC1;
  AdcHandle.Init.ClockPrescaler      = ADC_CLOCK_SYNC_PCLK_DIV4;   /* ADC 时钟 = PCLK/4 */
  AdcHandle.Init.Resolution          = ADC_RESOLUTION_12B;         /* 12 位分辨率 */
  AdcHandle.Init.DataAlign           = ADC_DATAALIGN_RIGHT;        /* 数据右对齐 */
  AdcHandle.Init.ScanConvMode        = ADC_SCAN_DIRECTION_FORWARD; /* 按通道号顺序扫描 */
  AdcHandle.Init.EOCSelection        = ADC_EOC_SINGLE_CONV;        /* 每完成一次转换产生 EOC */
  AdcHandle.Init.LowPowerAutoWait    = ENABLE;                     /* 等待转换模式,省功耗 */
  AdcHandle.Init.ContinuousConvMode  = DISABLE;                    /* 单次转换(不连续) */
  AdcHandle.Init.DiscontinuousConvMode = DISABLE;                  /* 关闭间断模式 */
  AdcHandle.Init.ExternalTrigConv    = ADC_SOFTWARE_START;         /* 软件触发 */
  AdcHandle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; /* 无外部触发边沿 */
  AdcHandle.Init.DMAContinuousRequests = DISABLE;                  /* 不使用 DMA */
  AdcHandle.Init.Overrun             = ADC_OVR_DATA_OVERWRITTEN;   /* 溢出时覆盖旧值 */
  AdcHandle.Init.SamplingTimeCommon  = ADC_SAMPLETIME_239CYCLES_5; /* 采样时间 239.5 周期 */
  if (HAL_ADC_Init(&AdcHandle) != HAL_OK)
  {
    APP_ErrorHandler();
  }

  /* 依次把通道 0、1、4 加入扫描序列(Rank 方式排队) */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank    = ADC_RANK_CHANNEL_NUMBER;
  if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK) APP_ErrorHandler();

  sConfig.Channel = ADC_CHANNEL_1;
  if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK) APP_ErrorHandler();

  sConfig.Channel = ADC_CHANNEL_4;
  if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK) APP_ErrorHandler();
}

void APP_ErrorHandler(void)
{
  while (1) {}
}

代码分段讲解

1. 校准与时钟

PY32F0xx 的 SAR ADC 上电后必须执行 HAL_ADCEx_Calibration_Start() 消除内部失调,否则读数偏差很大。ADC 时钟通过 ADC_CLOCK_SYNC_PCLK_DIV4 分频得到,需满足数据手册规定的最大 ADC 时钟(通常 ≤ 16MHz)。

2. 软件触发 vs 硬件触发

ExternalTrigConv = ADC_SOFTWARE_START 表示转换由 HAL_ADC_Start() 启动;若用定时器触发,则设为 ADC_EXTERNALTRIGCONV_Tx_TRGO 等并配置边沿。教学阶段用软件触发最简单直观。

3. 单次 / 连续 / 扫描

  • ContinuousConvMode = DISABLE:每调用一次 HAL_ADC_Start() 只转换一轮(这里 3 个通道各一次)。
  • ScanConvMode = FORWARD:多通道时按 Rank 顺序自动切换通道。
  • 每个通道转换完都会置 EOC,HAL_ADC_PollForConversion() 就是在等这个标志。

4. 结果换算

12 位右对齐时,读数与电压关系为:

1
电压(V) = 读数 / 4095 × VREF+

PY32F030 内部参考电压 VREF+ 一般等于 VDDA(多为 3.3V)。

实验现象

  • 串口助手(115200-8-N-1)每秒打印一次:

    1
    2
    3
    
    adc[0]:1234
    adc[1]:2047
    adc[2]:888
    
  • 转动接在 PA0/PA1/PA4 的电位器,对应通道数值随之变化(0~4095)。

常见问题

  1. 读数一直是 0 或 4095(满偏)?

    • 检查引脚是否真的接到模拟信号;悬空输入会被上/下拉拉到极端值。
    • 确认 VREF+/VDDA 供电正常。
  2. 读数跳动很大?

    • 增大 SamplingTimeCommon(采样时间),给内部采样电容更长的充电时间;信号源内阻大时尤其需要。
    • 软件上可做多次采样取平均。
  3. 想用 DMA 自动搬运多路数据?

    • 参考同目录 ADC_ContinuousConversion_DMA,把 DMAContinuousRequests 打开并用 HAL_ADC_Start_DMA()
  4. 想固定单通道采样?

    • ConfigChannel 一个通道即可,for 循环删掉多余通道。

小结

  • ADC 标准流程:开时钟 → 校准 → 配参数 → 配通道 → 启动 → 等待 EOC → 读值
  • 软件触发 + 轮询是最易理解的入门方式;对实时性要求高时用定时器触发 + DMA。
  • 12 位分辨率下满量程 4095,注意参考电压与采样时间对精度的影响。

拓展应用

  • 接 NTC 热敏电阻做温度计(查表/公式换算温度)。
  • 接光敏电阻做光照检测,配合 PWM 自动调光。
  • 用 DMA + 定时器触发做“示波器”式连续采样,再用串口绘图。
  • 读取内部 ADC_CHANNEL_VREFINT(参考电压)与 ADC_CHANNEL_TEMPSENSOR(温度)做自校准/测温。
世界是你们
使用 Hugo 构建
主题 StackJimmy 设计