CIU32 ADC — 单次转换·软件触发·查询

CIU32 CIU32F003_STDLib V1.0.3 ADC 单次转换软件触发查询示例教学

ADC — 单次转换·软件触发·查询(ADC_SingleConversion_TriggerSW_Poll)

固件库: CIU32F003_STDLib V1.0.3 芯片: CIU32F003(Cortex-M0) 源文件: CIU32/CIU32F003_STDLib_V1.0.3/Projects/ADC/ADC_SingleConversion_TriggerSW_Poll/Source/


一、功能简介

本示例演示 ADC 单次转换 + 软件触发 + 查询(轮询) 的最基础用法: 仅配置 1 个通道(PA7 / ADC_IN4),每隔 0.5 秒由软件启动一次转换, 连续采样 10 次取平均以提升精度,最后把平均值换算成电压值(单位 mV)保存。

“查询方式”指 CPU 主动 while 等待转换完成标志,代码简单、易于理解, 适合对实时性要求不高的场景。

二、硬件准备

  • CIU32F003 开发板
  • 一个可调电位器(或任意 0~3.3V 模拟信号源)
  • 把电位器中间抽头接到 PA7(ADC_IN4)
  • PA7 与 GND 之间接好参考地

参考电压 VREF = VDDA = 3.3V。若你的板子 VDDA 不是 3.3V,需修改 adc_bsp.c 中的 VREF_ADC_VDDA_VOLTAGE

三、完整代码

3.1 主流程 main.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include "main.h"

__IO uint32_t g_voltage;   /* 保存换算后的电压值(mV) */

int main(void)
{
    system_clock_config();     /* 配置系统时钟 RCH 48MHz */
    std_delay_init();          /* 初始化延时函数 */
    gpio_init();               /* 配置 PA7 为模拟输入 */
    adc_init();                /* 配置并使能 ADC */

    /* 提升 ADC 校准系数的精度,系统复位后只需执行一次 */
    bsp_adc_software_calibrate();

    while (1)
    {
        /* 采样并换算为电压值(mV),结果存入 g_voltage */
        g_voltage = bsp_adc_get_channel_voltage();
        std_delayms(500);      /* 间隔 500ms 再采下一次 */
    }
}

3.2 ADC 与引脚初始化 common.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
/* 系统时钟:RCH 48MHz(同 GPIO 示例,此处略) */

/* ADC 初始化 */
void adc_init(void)
{
    std_rcc_apb2_clk_enable(RCC_PERIPH_CLK_ADC);  /* ① 使能 ADC 时钟 */

    std_adc_clock_config(ADC_CK_DIV3);             /* ② ADC_CK = PCLK/3 = 16MHz */
    std_adc_trig_sw();                             /* ③ 软件触发 */
    std_adc_conversion_mode_config(ADC_SINGLE_CONVER_MODE); /* ④ 单次转换模式 */
    std_adc_sampt_time_config(ADC_SAMPTIME_119CYCLES);      /* ⑤ 采样时间 119 周期 */
    std_adc_fix_sequence_channel_enable(ADC_CHANNEL_4);     /* ⑥ 固定序列通道 4(PA7) */
    std_adc_wait_mode_enable();                    /* ⑦ 等待模式:防数据未及时读而溢出 */
    std_adc_enable();                              /* ⑧ 使能 ADC */
    std_delayus(ADC_EN_DELAY);                     /* ⑨ 等待 ADC 稳定(1us) */
}

/* GPIO 初始化:PA7 配置为模拟输入 */
void gpio_init(void)
{
    std_gpio_init_t adc_gpio_config = {0};
    std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOA);     /* 使能 GPIOA 时钟 */
    adc_gpio_config.pin   = GPIO_PIN_7;
    adc_gpio_config.mode  = GPIO_MODE_ANALOG;          /* 模拟模式 */
    adc_gpio_config.pull  = GPIO_NOPULL;               /* 无上下拉 */
    std_gpio_init(GPIOA, &adc_gpio_config);
}

3.3 采样与换算 adc_bsp.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
#define VREF_ADC_VDDA_VOLTAGE   (3300U)   /* 参考电压 VDDA = 3.3V */
#define ADC_SAMPLE_NUM          (10U)     /* 采样次数,越多越平滑 */
#define ADC_CONVER_SCALE        (4095U)   /* 12 位 ADC 满量程 */
#define ADC_COMPENSATION_VALUE  (*(int32_t *)(0x1FFF03CC))  /* 出厂校准补偿值 */

/* 软件校准:让采样结果更准 */
void bsp_adc_software_calibrate(void)
{
    int32_t get_calfact = 0;
    std_adc_calibration_enable();                 /* 启动校准 */
    while (std_adc_get_flag(ADC_FLAG_EOCAL) == 0U);  /* 等待校准完成 */
    std_adc_clear_flag(ADC_FLAG_ALL);            /* 清除旧状态 */
    get_calfact = std_adc_get_calibration_factor();
    if (get_calfact & ADC_CALFACT_CALFACT_5)     /* 处理符号位 */
        get_calfact |= 0xFFFFFFE0;
    get_calfact -= ADC_COMPENSATION_VALUE;       /* 减去出厂补偿值 */
    if (get_calfact > 31)  get_calfact = 31;    /* 限幅到 [-31, 31] */
    else if (get_calfact < -31) get_calfact = -31;
    std_adc_calibration_factor_config(get_calfact);  /* 写回校准系数 */
}

/* 触发一次转换并读取结果 */
void bsp_adc_get_single_channel_sample_value(uint32_t *conversion_result)
{
    std_adc_start_conversion();                  /* 软件启动一次转换 */
    while (std_adc_get_flag(ADC_FLAG_EOC) == 0U);   /* 轮询等待转换结束 */
    std_adc_clear_flag(ADC_FLAG_EOC);            /* 清 EOC 标志 */
    *conversion_result = std_adc_get_conversion_value(); /* 读 12 位结果 */
}

/* 多次采样取平均,并换算成电压(mV) */
uint32_t bsp_adc_get_channel_voltage(void)
{
    uint32_t sum = 0, single = 0;
    for (uint8_t i = 0; i < ADC_SAMPLE_NUM; i++)
    {
        bsp_adc_get_single_channel_sample_value(&single);
        sum += single;
    }
    single = sum / ADC_SAMPLE_NUM;                       /* 平均采样值 */
    return single * VREF_ADC_VDDA_VOLTAGE / ADC_CONVER_SCALE; /* 换算成 mV */
}

四、代码分段讲解

① ADC 时钟 ADC 挂在 APB2 上,本例把 ADC_CK 设为 PCLK 的 3 分频(48MHz/3 = 16MHz)。 采样时钟过快会增加噪声,过慢会限制采样率,需折中。

② 触发与转换模式

  • std_adc_trig_sw():软件写寄存器触发,最易控制;
  • 单次转换模式:每触发一次只转换一个序列,转换完自动停止,适合低频采集。

③ 通道与采样时间 固定序列只使能通道 4(PA7)。采样时间 119 周期越长,对高阻抗信号源越友好、精度越高,但单次转换更慢。

④ 等待模式(wait mode) 开启后,若上次结果未被读取,新的转换会“等待”而不是覆盖,避免数据丢失/溢出。

⑤ 校准(重点) CIU32F003 的 ADC 带出厂补偿值(固化在 0x1FFF03CC),bsp_adc_software_calibrate() 先做一次内部校准,再减去出厂补偿并限幅到 ±31,最后写回校准系数——这一步能明显减小零偏与增益误差。

⑥ 多次平均 ADC_SAMPLE_NUM = 10 次采样求和取平均,抑制随机噪声;结果再按 采样值 × 3300 / 4095 换算成毫伏。

五、实验现象

  • 旋转电位器,全局变量 g_voltage(mV)在 0~3300 之间平滑变化;
  • 用调试器在线观察 g_voltage,或配合串口打印(参考 UART 示例)即可看到电压值。

六、常见问题

现象 可能原因 解决办法
读到的值一直是 0 或满量程 引脚未设为模拟模式 确认 gpio_initGPIO_MODE_ANALOG
电压换算偏差大 VREF 设置错误 VREF_ADC_VDDA_VOLTAGE 改成真实 VDDA
值跳动剧烈 信号源阻抗高/有干扰 增大采样时间、做更多次平均、加滤波电容
第一次采样明显偏 未做校准 复位后先调用 bsp_adc_software_calibrate()

七、小结

软件触发 + 轮询是 ADC 入门最友好的方式: 开时钟 → 配引脚为模拟 → 设触发/模式/通道 → 使能 → 校准 → 触发 → 等 EOC → 读数 → 换算。 记住 12 位满量程是 4095,电压换算离不开真实的 VREF。

八、拓展应用

  • 改用定时器触发 + DMA,实现无 CPU 干预的连续高速采集;
  • 开启模拟看门狗(AWD),当电压越限时产生中断(见 ADC_SingleConversion_TriggerSW_AWD);
  • 多通道轮询采集:在序列里使能多个通道,依次读取。
世界是你们
使用 Hugo 构建
主题 StackJimmy 设计