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_init 里 GPIO_MODE_ANALOG |
| 电压换算偏差大 |
VREF 设置错误 |
把 VREF_ADC_VDDA_VOLTAGE 改成真实 VDDA |
| 值跳动剧烈 |
信号源阻抗高/有干扰 |
增大采样时间、做更多次平均、加滤波电容 |
| 第一次采样明显偏 |
未做校准 |
复位后先调用 bsp_adc_software_calibrate() |
七、小结
软件触发 + 轮询是 ADC 入门最友好的方式:
开时钟 → 配引脚为模拟 → 设触发/模式/通道 → 使能 → 校准 → 触发 → 等 EOC → 读数 → 换算。
记住 12 位满量程是 4095,电压换算离不开真实的 VREF。
八、拓展应用
- 改用定时器触发 + DMA,实现无 CPU 干预的连续高速采集;
- 开启模拟看门狗(AWD),当电压越限时产生中断(见
ADC_SingleConversion_TriggerSW_AWD);
- 多通道轮询采集:在序列里使能多个通道,依次读取。