ADC(Analog-to-Digital Converter,模数转换器)是一种将模拟信号(电压)转换为数字信号的硬件模块。通过 ADC,可以读取传感器的模拟输出、测量电压、监测电池电量等。
本教程适合新手小白,从零开始学习 ADC 的使用。
通过本教程,你将学会:
常见的模拟信号应用:
| 应用场景 | 传感器类型 | 输出信号 |
|---|---|---|
| 电压监测 | 分压电阻 | 0-3.3V 电压 |
| 温度检测 | NTC 热敏电阻 | 电压随温度变化 |
| 光照检测 | 光敏电阻 | 电压随光照变化 |
| 电池电量 | 电池电压 | 0-4.2V 电压 |
| 按键检测 | 电位器 | 电压随旋转角度变化 |
问题: 这些传感器输出的是模拟电压信号,MCU 无法直接读取。
解决方案: 使用 ADC 将模拟电压转换为数字值。
模拟电压输入 → 采样保持 → 量化编码 → 数字值输出
(0-3.3V) (0-1023)
分辨率 决定了 ADC 能区分的最小电压变化。
Mars SDK 的 ADC: 10 位分辨率
示例:
参考电压 决定了 ADC 的测量范围。
Mars SDK 支持多种参考电压:
| 参考电压源 | 宏定义 | 说明 |
|---|---|---|
| 内部 1.2V | CSK_GPADC_VREF_SEL_VBG1_2 |
精度高,测量范围 0-1.2V ⭐ |
| VDD_IO | CSK_GPADC_VREF_SEL_VDDIO |
测量范围 0-3.3V |
| 外部参考 | CSK_GPADC_VREF_SEL_EXT |
外部输入参考电压 |
推荐: 使用内部 1.2V 参考电压,精度更高。
采样时间 决定了 ADC 采样一个值需要的时间。
Mars SDK 支持的采样时间:
| 采样时间 | 宏定义 | 说明 |
|---|---|---|
| 4 时钟 | CSK_GPADC_SAMPLETIME_4 |
最快 |
| 8 时钟 | CSK_GPADC_SAMPLETIME_8 |
|
| 16 时钟 | CSK_GPADC_SAMPLETIME_16 |
|
| 32 时钟 | CSK_GPADC_SAMPLETIME_32 |
|
| 64 时钟 | CSK_GPADC_SAMPLETIME_64 |
推荐 ⭐ |
| 128 时钟 | CSK_GPADC_SAMPLETIME_128 |
最稳定 |
推荐: 使用 64 时钟,平衡速度和稳定性。
| 通道 | 宏定义 | 说明 | 用途 |
|---|---|---|---|
| VCC/5 | CSK_GPADC_CHANNEL_SEL_1_5_VCC |
VCC 分压 1/5 | 测量供电电压 ⭐⭐⭐ |
| VDDIO/3 | CSK_GPADC_CHANNEL_SEL_1_3_VDDIO |
VDDIO 分压 1/3 | 测量 IO 电压 |
| Temp | CSK_GPADC_CHANNEL_SEL_TEMP |
温度传感器 | 测量芯片温度 |
重要: 内部通道不需要配置引脚,直接测量芯片内部电压!
| 通道 | 宏定义 | 说明 |
|---|---|---|
| Channel 0 | CSK_GPADC_CHANNEL_SEL_0 |
外部引脚(需确认引脚映射) |
| Channel 1 | CSK_GPADC_CHANNEL_SEL_1 |
外部引脚 |
| Channel 2 | CSK_GPADC_CHANNEL_SEL_2 |
外部引脚 |
| Channel 3 | CSK_GPADC_CHANNEL_SEL_3 |
外部引脚 |
注意: 外部通道需要配置引脚和时钟,具体引脚映射请参考芯片手册。
使用 ADC 需要包含以下头文件:
#include "Driver_GPADC.h" // ADC 驱动
#include "PowerManager.h" // 电源管理(时钟使能)
#include "ClockManager.h" // 时钟管理
#include "lisa_log.h" // 日志输出
#include "xutils.h" // 工具函数
场景: 测量芯片供电电压,监测电池电量或系统电压
优点:
完整代码:
#include "Driver_GPADC.h"
#include "PowerManager.h"
#include "ClockManager.h"
#include "lisa_log.h"
#include "xutils.h"
// 测量 VCC 电压(单位:mV)
int32_t get_vcc_power() {
int adc_value;
// ⭐ 步骤1:使能 ADC 时钟和复位(必须!)
__HAL_PMU_GPADC_RST_ENABLE();
__HAL_CRM_GPADC_CLK_ENABLE();
// ⭐ 步骤2:初始化 ADC
HAL_GPADC_Initialize(GPADC());
// ⭐ 步骤3:配置 ADC 参数
// - 采样时间:64 时钟
// - 通道:VCC/5(内部通道)
// - 输入模式:单端
// - 参考电压:内部 1.2V
// - DMA:禁用
HAL_GPADC_Control(GPADC(),
CSK_GPADC_SAMPLETIME_64 |
CSK_GPADC_CHANNEL_SEL_1_5_VCC |
CSK_GPADC_INPUT_MODE_Single |
CSK_GPADC_VREF_SEL_VBG1_2 |
CSK_GPADC_DMA_DISABLE);
// ⭐ 步骤4:设置触发次数(采样5次)
HAL_GPADC_SetTriggerNum(GPADC(), 5);
// ⭐ 步骤5:���动 ADC 转换
HAL_GPADC_Start(GPADC());
// ⭐ 步骤6:等待转换完成
HAL_GPADC_PollForConversion(GPADC(), 0);
// ⭐ 步骤7:读取 ADC 值
adc_value = HAL_GPADC_GetValue(GPADC(), CSK_GPADC_CHANNEL_SEL_1_5_VCC);
// ⭐ 步骤8:转换为实际电压(mV)
// VCC = (ADC_Value / 1024) * VRef * 5
// 这里 VRef = 1.2V,所以乘以 6000
adc_value = (uint16_t)(adc_value * 1000 * 6 / 1024);
return adc_value;
}
// 使用示例
void example_measure_vcc() {
printk("\n");
printk("========================================\n");
printk("VCC Voltage Monitor\n");
printk("========================================\n\n");
while (1) {
int vcc_mv = get_vcc_power();
printk("[VCC] Supply voltage: %d mV\n", vcc_mv);
// 电池电量判断
if (vcc_mv < 3300) {
printk("⚠️ Low battery warning!\n");
} else if (vcc_mv < 3500) {
printk("🔋 Battery level: Low\n");
} else {
printk("✅ Battery level: Good\n");
}
printk("\n");
x_task_sleep(5000); // 每5秒测量一次
}
}
关键要点:
⭐ 必须使能时钟和复位:
__HAL_PMU_GPADC_RST_ENABLE();
__HAL_CRM_GPADC_CLK_ENABLE();
忘记这一步会导致 ADC 无法工作!
⭐ VCC/5 通道计算:
VCC(mV) = (ADC * 1000 * 6) / 1024⭐ 多次采样提高精度:
HAL_GPADC_SetTriggerNum(GPADC(), 5); // 采样5次取平均
场景: 测量外部引脚的电压(如 B11)
⚠️ 重要提示: 外部引脚 ADC 需要注意:
ANA_IOMuxManager_PinConfigure() ⭐⭐⭐已验证代码示例:
#include "Driver_GPADC.h"
#include "IOMuxManager.h"
#include "PowerManager.h"
#include "lisa_log.h"
#include "xutils.h"
// ⚠️ 需要根据芯片手册确认 B11 对应的 ADC 通道
// 示例:假设 B11 对应通道 2(需确认!)
#define ADC_CHANNEL_B11 CSK_GPADC_CHANNEL_SEL_2
// ADC 初始化(使用 B11 引脚)
int adc_b11_init() {
printk("\n========================================\n");
printk("ADC Init (Pin: B11)\n");
printk("========================================\n\n");
// ⭐ 步骤1:使能 ADC 时钟和复位(必须!)
__HAL_PMU_GPADC_RST_ENABLE();
__HAL_CRM_GPADC_CLK_ENABLE();
// ⭐ 步骤2:配置引脚为模拟 ADC 功能
// ⚠️ 必须使用 ANA_IOMuxManager_PinConfigure,而不是 IOMuxManager_PinConfigure!
printk("[1] Configure B11 as ADC input (Analog function)\n");
printk(" - ⚠️ Must use ANA_IOMuxManager_PinConfigure for ADC!\n");
int ret = -1;
int successful_alter = -1;
// 尝试模拟 ALTER 功能 1-5,找到可用的配置
for (int alter = 1; alter <= 5; alter++) {
printk(" - Trying ANA ALTER%d: ", alter);
ret = ANA_IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 11, alter); // ⭐ 模拟引脚配置
if (ret == 0) {
printk("✅ Success!\n");
successful_alter = alter;
break;
} else {
printk("❌ Failed (ret=%d)\n", ret);
}
}
if (successful_alter == -1) {
printk(" - ⚠️ All ALTER functions failed! Pin may be locked.\n");
return -1;
}
printk(" - Using ANA ALTER%d for B11\n", successful_alter);
// ⭐ 步骤3:初始化 ADC
printk("\n[2] Initialize ADC\n");
ret = HAL_GPADC_Initialize(GPADC());
printk(" - HAL_GPADC_Initialize: ret=%d\n", ret);
// ⭐ 步骤4:配置 ADC 参数
printk("\n[3] Configure ADC parameters\n");
uint32_t control = CSK_GPADC_SAMPLETIME_64 | // 采样时间 64 时钟
ADC_CHANNEL_B11 | // 选择通道(B11)
CSK_GPADC_INPUT_MODE_Single | // 单端输入模式
CSK_GPADC_VREF_SEL_VBG1_2 | // 内部参考电压 1.2V ⭐
CSK_GPADC_DMA_DISABLE; // 禁用 DMA
ret = HAL_GPADC_Control(GPADC(), control);
printk(" - HAL_GPADC_Control: ret=%d\n", ret);
printk(" - Channel: 0x%X\n", ADC_CHANNEL_B11);
printk(" - VRef: Internal 1.2V\n");
// ⭐ 步骤5:设置触发次数(多次采样)
printk("\n[4] Set trigger number\n");
ret = HAL_GPADC_SetTriggerNum(GPADC(), 5); // 采样5次
printk(" - Trigger number: 5 samples\n");
printk("\n========================================\n");
printk("ADC initialized successfully (Pin: B11)\n");
printk("========================================\n\n");
return 0;
}
// 读取 B11 的 ADC 值(原始值)
uint16_t adc_b11_read_raw() {
// 启动 ADC 转换
HAL_GPADC_Start(GPADC());
// 等待转换完成
HAL_GPADC_PollForConversion(GPADC(), 0);
// 读取 ADC 值
uint16_t adc_value = HAL_GPADC_GetValue(GPADC(), ADC_CHANNEL_B11);
return adc_value;
}
// 读取 B11 的电压值(单位:mV)
int adc_b11_read_voltage() {
// 读取原始 ADC 值
uint16_t adc_raw = adc_b11_read_raw();
// 转换为电压值(mV)
// 公式:Voltage = (ADC_Value / 1024) * VRef
// VRef = 1.2V
int voltage_mv = (adc_raw * 1200) / 1024;
return voltage_mv;
}
// 使用示例
void example_adc_b11() {
// 初始化 ADC
adc_b11_init();
printk("\n========================================\n");
printk("ADC Example Running (Pin: B11)\n");
printk("========================================\n");
printk("Reading ADC value every 2 seconds...\n\n");
int read_count = 0;
while (1) {
// 读取 ADC 值
uint16_t adc_raw = adc_b11_read_raw();
int voltage_mv = adc_b11_read_voltage();
read_count++;
printk("[ADC] Count: %d\n", read_count);
printk(" - Raw value: %d (0x%03X)\n", adc_raw, adc_raw);
printk(" - Voltage: %d mV\n", voltage_mv);
printk(" - Percentage: %d%%\n", (adc_raw * 100) / 1024);
printk("\n");
// 每 2 秒读取一次
x_task_sleep(2000);
}
}
关键要点:
⭐⭐⭐ 必须使用模拟引脚配置函数:
ANA_IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 11, alter); // ⭐ 正确!
错误用法:
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 11, alter); // ❌ 错误!这是数字功能
⭐ 参考电压选择:
CSK_GPADC_VREF_SEL_VBG1_2(内部 1.2V)- 推荐,精度高voltage_mv = (adc_raw * 1200) / 1024⭐ 必须使能时钟和复位:
__HAL_PMU_GPADC_RST_ENABLE();
__HAL_CRM_GPADC_CLK_ENABLE();
⭐ 自动尝试 ALTER 功能:
⭐ 多次采样提高精度:
HAL_GPADC_SetTriggerNum(GPADC(), 5); // 采样5次
⚠️ 注意事项:
ANA_IOMuxManager_PinConfigure() 而不是 IOMuxManager_PinConfigure()(模拟引脚)外部引脚 ADC 必须使用模拟引脚配置函数:
// ⭐ 正确:使用模拟引脚配置函数
ANA_IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 11, alter); // 模拟功能
// ❌ 错误:使用数字引脚配置函数(会导致 ADC 读数错误)
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 11, alter); // 数字功能
原因:
IOMuxManager_PinConfigure() 配置的是数字功能(GPIO、UART、SPI等)ANA_IOMuxManager_PinConfigure() 配置的是模拟功能(ADC、DAC等)CSK_GPADC_VREF_SEL_VBG1_2,精度高 ⭐CSK_GPADC_VREF_SEL_VDDIO电压计算公式:
// 内部 1.2V 参考
int voltage_mv = (adc_raw * 1200) / 1024;
// VDDIO 参考(3.3V)
int voltage_mv = (adc_raw * 3300) / 1024;
分压电路示例:
Vin (0-5V) ──┬── R1 (10kΩ) ──┬── ADC 输入
│ │
└────────────────┴── R2 (10kΩ) ── GND
ADC 输入电压 = Vin * R2 / (R1 + R2) = Vin / 2
测量范围:0-5V
超量程后果:
超过参考电压,但不超过引脚最大电压(1.2V < Vin < 3.3V):
超过引脚最大电压(Vin > 3.3V)⚠️:
多次采样取平均
HAL_GPADC_SetTriggerNum(GPADC(), 5); // 设置触发次数为 5
// 或手动多次采样
int samples = 10;
uint32_t sum = 0;
for (int i = 0; i < samples; i++) {
sum += adc_b11_read_raw();
x_task_sleep(10);
}
int avg_voltage = (sum / samples) * 1200 / 1024;
增加采样时间
CSK_GPADC_SAMPLETIME_128 // 使用最长采样时间
使用内部参考电压
CSK_GPADC_VREF_SEL_VBG1_2 // 精度更高
外部引脚 ADC 必须使能时钟和复位:
__HAL_PMU_GPADC_RST_ENABLE(); // 复位 ADC 模块
__HAL_CRM_GPADC_CLK_ENABLE(); // 使能 ADC 时钟
忘记这一步会导致 ADC 无法工作!
问题现象:
原因: 使用了错误的引脚配置函数!
错误代码:
// ❌ 错误:使用数字引脚配置函数
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 11, CSK_ANA_IOMUX_FUNC_ALTER1);
正确代码:
// ⭐ 正确:使用模拟引脚配置函数
ANA_IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 11, alter);
可能原因:
解决方法:
ANA_IOMuxManager_PinConfigure() 配置引脚__HAL_PMU_GPADC_RST_ENABLE();
__HAL_CRM_GPADC_CLK_ENABLE();
解决方法: 使用分压电阻
// 分压比例 2:1(两个 10kΩ 电阻)
int real_voltage_mv = adc_voltage_mv * 2;
⚠️ 注意: 超过 3.6V 可能永久损坏引脚!
A: 需要查阅芯片数据手册的引脚功能表,确认 B11 对应的 ADC 通道号。
常见情况:
CSK_GPADC_CHANNEL_SEL_2)