ZZZ-18-ADC多通道采集

在“多通道 + DMA”模式下,单片机会像一台自动流水线:ADC 负责把电压“打包”成数字,DMA 负责把包“搬运”到内存。CPU 甚至可以去喝咖啡,只需要在想看数据的时候,去数组里看一眼就行了。

1. 硬件连接 (准备两只“眼睛”)

假设你准备了两个模拟传感器(比如:一个电位器、一个光敏电阻):

  • 传感器 A 的输出 $\rightarrow$ 连接到 PA0 (ADC1IN0)
  • 传感器 B 的输出 $\rightarrow$ 连接到 PA1 (ADC1IN1)
  • 所有 GND $\rightarrow$ 必须全部连在一起(共地)
  • 电源 $\rightarrow$ 传感器接板子上的 3.3V

2. STM32CubeIDE 配置 (.ioc) —— 这是实验的灵魂

这里的配置比单通道稍微复杂一点,请屏住呼吸:

A. ADC1 配置

  1. 勾选通道:勾选 IN0IN1
  2. 基本参数 (Parameter Settings)
    • Scan Conversion ModeEnabled(扫描模式,必须开,否则它只看一个引脚)。
    • Continuous Conversion ModeEnabled(连续转换,自动循环看)。
    • DMA Continuous RequestsEnabled极其重要! 只有开了这个,DMA 才会源源不断地搬运,否则搬一次就停了)。
  3. 规则组参数 (ADC_Regular_ConversionMode)
    • Number of Conversion:改为 2
    • Rank 1:选择 Channel 0 (对应 PA0)。
    • Rank 2:选择 Channel 1 (对应 PA1)。
    • 建议将 Sampling Time 调大一点(比如 55.5 或 71.5 Cycles),这样数据更稳。

(注意上图中采样时间才 1.5 个周期,需要调大一点,太快采不到)

配置项 位置 设置值 作用
DMA Mode DMA Settings Circular 搬运指针自动折返。
Continuous Conversion ADC Parameter Settings Enabled ADC 拍完一张不停,接着拍。
DMA Continuous Requests ADC Parameter Settings Enabled 每次拍完都呼叫快递员搬运。

B. DMA 配置 (关键点)

  1. 点击下方的 DMA Settings 选项卡 -> 点击 Add
  2. 选择 ADC1
  3. DMA 模式设置
    • Mode:选择 Circular (循环模式)。(这样 DMA 搬完两个数后,会自动回到数组开头覆盖旧数据,形成闭环)。
    • Memory Address Increment:勾选 Increment Address。(这样第一个数存数组 [0],第二个数存数组 [1])。
    • Data Width:Peripheral 和 Memory 都选 Half Word (16 位)。(因为 ADC 结果是 12 位,8 位装不下,用 16 位刚好)。

3. 编写代码 (main.c)

你会惊讶地发现,有了 DMA 后,代码反而变得异常简洁。

第一步:准备缓冲区

main.cUSER CODE BEGIN PV 区域:

1
2
3
/* USER CODE BEGIN PV */
uint16_t adc_buffer[2]; // 准备一个容纳 2 个通道数据的“小篮子”
/* USER CODE END PV */

第二步:启动 ADC 搬运流水线

main 函数的 while(1) 之前,启动 DMA 传输:

1
2
3
4
5
6
7
8
/* USER CODE BEGIN 2 */
// 为了精准,建议先进行 ADC 校准(F1系列特有)
HAL_ADCEx_Calibration_Start(&hadc1);

// 核心命令:启动 ADC + DMA
// 参数:ADC句柄,存储的目标地址,要搬运的数据个数
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 2);
/* USER CODE END 2 */

第三步:读取并显示数据

此时,数据已经在后台自动刷写到 adc_buffer 数组里了。在 while(1) 中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* USER CODE BEGIN WHILE */
while (1)
{
    // 直接从内存数组里拿数据,不需要调用任何 HAL_ADC 函数!
    uint16_t sensor_A = adc_buffer[0];
    uint16_t sensor_B = adc_buffer[1];

    // 转换成电压显示
    float volt_A = (float)sensor_A * 3.3 / 4096;
    float volt_B = (float)sensor_B * 3.3 / 4096;

    printf("PA0(A): %.2fV | PA1(B): %.2fV\r\n", volt_A, volt_B);

    HAL_Delay(500); 
    /* USER CODE END WHILE */
}

运行后为什么看不到数字:

因为在代码中使用 %f 格式化字符串,但编译器没有开启浮点数支持时,printf 就会直接忽略这部分内容,导致你看到的输出跳过了数字,直接显示了后面的 V

解决方法 A:开启 IDE 设置(最简单)

  1. 在左侧 Project Explorer 中,右键点击你的项目名称,选择 Properties
  2. 进入 C/C++ Build -> Settings
  3. 在右边的选项卡中选择 Tool Settings
  4. 找到 MCU Settings
  5. 勾选底部的 Runtime Library: Use float with printf from newlib-nano (-u _printf_float)
  6. 点击 Apply and Close
  7. 重新编译(小锤子)并下载。

4. 实验原理图解 (Mermaid)

graph TD subgraph "硬件自动流转 (无需 CPU 参与)" SensorA["传感器 A (PA0)"] -- 采样 --> CH0["ADC 通道 0"] SensorB["传感器 B (PA1)"] -- 采样 --> CH1["ADC 通道 1"] CH0 -- DMA 搬运 --> Buf0["adc_buffer[0]"] CH1 -- DMA 搬运 --> Buf1["adc_buffer[1]"] Buf1 -- 循环模式重置 --> Buf0 end Buf0 -.-> Read["CPU 直接读取数组内容"] Buf1 -.-> Read style SensorA fill:#f9f,stroke:#333 style SensorB fill:#f9f,stroke:#333 style Read fill:#bbf,stroke:#333

1. 为什么这个结构很强大?

在传统的做法中,如果你想测两个通道,你需要:

  1. 切到通道 0 $\to$ 启动 $\to$ 等待 $\to$ 读数。
  2. 切到通道 1 $\to$ 启动 $\to$ 等待 $\to$ 读数。

而在你现在配置的 扫描模式 (Scan) + 循环模式 (Circular) + DMA 下:

  • ADC 会自动在 PA0 和 PA1 之间来回切换采样。
  • DMA 会自动把 PA0 的结果放进 adc_buffer[0],把 PA1 的结果放进 adc_buffer[1]
  • 完全同步:这个过程是硬件级锁定的,你永远不用担心 PA1 的数据被误存到了 adc_buffer[0] 里。

2. DMA 循环模式

结构图当中,循环模式重置是什么意思:

你可以把 adc_buffer[2] 想象成一个只有两个格子的环形跑道

  • 第 1 步:ADC 转换完第一个通道(PA0),DMA 把结果送进 Buf0(第一个格子)。
  • 第 2 步:ADC 转换完第二个通道(PA1),DMA 把结果送进 Buf1(第二个格子)。

此时,DMA 的搬运任务“名义上”完成了(因为你告诉它只搬 2 个数)。

  • 如果是普通模式 (Normal Mode):搬运工 DMA 就地解散,去睡觉了。如果你想再看数据,必须由 CPU 再次下令 HAL_ADC_Start_DMA
  • 如果是循环模式 (Circular Mode):DMA 发现自己到了终点,会瞬间自动回到起点,把指针重新指向 Buf0

5. 这个实验的“专业感”在哪里?

  1. 零等待:CPU 不需要调用 PollForConversion 去死等转换完成。无论何时 CPU 去读 adc_buffer,拿到的都是最新的电压值
  2. 严格同步:PA0 和 PA1 的数据几乎是同时采集并按顺序排列的,绝对不会出现通道对错位。
  3. 自由扩展:如果你有 10 个传感器,只需要在配置里把 Number 改成 10,代码里定义长度为 10 的数组即可,代码量完全不增加。
Licensed under CC BY-NC-SA 4.0