9. ADC 采集
📝本节您将了解 ADC(模数转换)的工作原理和关键参数,学习如何配置 MSPM0G3519 的 ADC 外设,实现模拟电压的数字化采集与换算。
🏆本章目标
1️⃣ 理解 ADC 的采样原理和分辨率概念
2️⃣ 了解 MSPM0G3519 的 ADC 外设资源
3️⃣ 通过 SysConfig 配置单通道 ADC 采集
4️⃣ 实现电压采集并通过串口输出换算结果
9.1 ADC 基本概念
ADC(Analog-to-Digital Converter,模数转换器)将连续的模拟电压信号转换为离散的数字值,供 MCU 进行处理和计算。
现实世界的物理量(温度、光照、压力等)通常是模拟信号,传感器将其转换为模拟电压后,需要 ADC 将电压值数字化才能被程序处理。
核心参数
| 参数 | 说明 |
|---|---|
| 分辨率 | ADC 输出的数字位数。12-bit ADC 输出范围 0~4095 |
| 参考电压(Vref) | ADC 量程的上限电压。通常为 VDD(3.3V) |
| 采样率 | 每秒完成转换的次数(SPS,Samples Per Second) |
| 量化精度 | 最小可分辨电压 = Vref / 2^n |
电压换算公式
实际电压 = ADC原始值 × Vref / (2^分辨率 - 1)对于 12-bit ADC,Vref = 3.3V:
实际电压 = ADC值 × 3.3 / 4095| ADC 原始值 | 对应电压 |
|---|---|
| 0 | 0V |
| 2048 | ≈ 1.65V |
| 4095 | 3.3V |
量化精度 = 3.3V / 4095 ≈ 0.806mV(即最小可分辨约 0.8mV 的电压变化)。
9.2 MSPM0G3519 ADC 资源
MSPM0G3519 内置两个 12-bit SAR ADC 模块:
| ADC 模块 | 分辨率 | 通道数 | 说明 |
|---|---|---|---|
| ADC0 | 12-bit | 多通道 | 主 ADC 模块 |
| ADC1 | 12-bit | 多通道 | 辅助 ADC 模块 |
支持特性:
- 12-bit 分辨率(0~4095)
- 多种触发源:软件触发、定时器触发、事件触发
- 单次转换和连续转换模式
- 多通道自动扫描
- 内置温度传感器通道
- 支持 DMA 自动数据搬运
参考电压
| 参考源 | 电压 | 说明 |
|---|---|---|
| VDDA | 3.3V | 芯片模拟供电电压,默认参考 |
| 内部参考 | 1.4V / 2.5V | 内部精密参考,精度更高 |
| 外部参考 | 用户提供 | 通过 VREF 引脚输入 |
本教程使用 VDDA(3.3V)作为参考电压。
9.3 SysConfig 配置 ADC
9.3.1 添加 ADC 外设
- 打开
.syscfg文件 - 左侧面板找到 ADC12 选项卡
- 点击 ADD 添加 ADC 实例
9.3.2 配置参数
| 参数 | 设置值 | 说明 |
|---|---|---|
| Name | ADC12_0 | 实例名称 |
| ADC Module | ADC0 | 选择 ADC0 |
| Resolution | 12 Bits | 12 位分辨率 |
| Trigger Source | Software | 软件触发转换 |
| Conversion Mode | Single | 单次转换模式 |
| Sample Timer Source | Timer | 采样时间源 |
| Sampling Timer | 根据需要设置 | 采样保持时间 |
| Voltage Reference | VDDA | 参考电压 3.3V |
9.3.3 配置通道
| 参数 | 设置值 | 说明 |
|---|---|---|
| Channel | 选择目标通道 | 对应的 ADC 输入引脚 |
| Input Pin | 选择空闲引脚 | 如 PA25(确认未被板载外设占用) |
9.3.4 配置中断(可选)
| 参数 | 设置值 |
|---|---|
| Interrupt Enable | Enable |
| Conversion Complete Interrupt | Enable |
保存并编译。
引脚注意
ADC 输入引脚必须是具有模拟功能的引脚。不是所有 GPIO 都支持 ADC 功能,请参考 MSPM0G3519 数据手册中的引脚复用表。SysConfig 会自动过滤不支持的引脚。
9.4 ADC 相关函数
常用 ADC DriverLib 函数
启动 ADC 转换:
void DL_ADC12_startConversion(ADC12_Regs *adc);触发一次 ADC 转换(软件触发模式下)。
检查转换是否完成:
bool DL_ADC12_isSampleReady(ADC12_Regs *adc, DL_ADC12_MEM_IDX idx);返回 true 表示指定通道的转换已完成,结果可读取。
读取转换结果:
uint16_t DL_ADC12_getMemResult(ADC12_Regs *adc, DL_ADC12_MEM_IDX idx);返回 12-bit 原始 ADC 值(0~4095)。
使能/禁止 ADC 转换:
void DL_ADC12_enableConversions(ADC12_Regs *adc);
void DL_ADC12_disableConversions(ADC12_Regs *adc);2
9.5 实验一:单通道电压采集
9.5.1 软件触发 + 轮询方式
最基础的 ADC 使用方式:软件触发转换,轮询等待完成,读取结果。
#include "ti_msp_dl_config.h"
#include <stdio.h>
void uart_send_string(const char *str)
{
while (*str) {
while (DL_UART_Main_isBusy(UART_0_INST));
DL_UART_Main_transmitData(UART_0_INST, *str++);
}
}
int main(void)
{
SYSCFG_DL_init();
char buf[64];
while (1) {
// 使能并启动 ADC 转换
DL_ADC12_enableConversions(ADC12_0_INST);
DL_ADC12_startConversion(ADC12_0_INST);
// 等待转换完成
while (!DL_ADC12_isSampleReady(ADC12_0_INST, DL_ADC12_MEM_IDX_0));
// 读取原始值
uint16_t adc_raw = DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);
// 换算为电压(mV)
uint32_t voltage_mv = (uint32_t)adc_raw * 3300 / 4095;
// 通过串口输出
sprintf(buf, "ADC: %u, Voltage: %lu.%03lu V\r\n",
adc_raw, voltage_mv / 1000, voltage_mv % 1000);
uart_send_string(buf);
// 停止转换,为下次做准备
DL_ADC12_disableConversions(ADC12_0_INST);
delay_cycles(16000000); // 500ms 采集一次
}
}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
电压换算说明
使用整数运算避免浮点数:
adc_raw * 3300 / 4095得到毫伏值- 输出时将毫伏值拆分为整数部分和小数部分
避免浮点的原因:Cortex-M0+ 没有硬件浮点单元(FPU),浮点运算由软件模拟,效率低且代码体积大。
9.5.2 中断方式
更高效的方式:启动转换后 CPU 不阻塞,转换完成后由中断通知。
#include "ti_msp_dl_config.h"
#include <stdio.h>
volatile uint16_t adc_result = 0;
volatile uint8_t adc_done = 0;
void uart_send_string(const char *str)
{
while (*str) {
while (DL_UART_Main_isBusy(UART_0_INST));
DL_UART_Main_transmitData(UART_0_INST, *str++);
}
}
int main(void)
{
SYSCFG_DL_init();
NVIC_EnableIRQ(ADC12_0_INST_INT_IRQN);
char buf[64];
while (1) {
// 启动转换
DL_ADC12_enableConversions(ADC12_0_INST);
DL_ADC12_startConversion(ADC12_0_INST);
// 等待中断完成(也可以做其他事)
while (!adc_done);
adc_done = 0;
// 处理结果
uint32_t voltage_mv = (uint32_t)adc_result * 3300 / 4095;
sprintf(buf, "ADC: %u, Voltage: %lu.%03lu V\r\n",
adc_result, voltage_mv / 1000, voltage_mv % 1000);
uart_send_string(buf);
DL_ADC12_disableConversions(ADC12_0_INST);
delay_cycles(16000000);
}
}
// ADC0 中断服务函数
void ADC0_IRQHandler(void)
{
switch (DL_ADC12_getPendingInterrupt(ADC12_0_INST)) {
case DL_ADC12_IIDX_MEM0_RESULT_LOADED:
adc_result = DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);
adc_done = 1;
break;
default:
break;
}
}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
9.6 实验二:简易电压表
结合 ADC 采集和串口输出,实现一个简易电压表:持续采集并以格式化方式显示电压值。
#include "ti_msp_dl_config.h"
#include <stdio.h>
void uart_send_string(const char *str)
{
while (*str) {
while (DL_UART_Main_isBusy(UART_0_INST));
DL_UART_Main_transmitData(UART_0_INST, *str++);
}
}
// 简单均值滤波(采集 N 次取平均)
uint16_t adc_read_average(uint8_t samples)
{
uint32_t sum = 0;
for (uint8_t i = 0; i < samples; i++) {
DL_ADC12_enableConversions(ADC12_0_INST);
DL_ADC12_startConversion(ADC12_0_INST);
while (!DL_ADC12_isSampleReady(ADC12_0_INST, DL_ADC12_MEM_IDX_0));
sum += DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);
DL_ADC12_disableConversions(ADC12_0_INST);
}
return (uint16_t)(sum / samples);
}
int main(void)
{
SYSCFG_DL_init();
char buf[64];
uart_send_string("=== Tianqiaoxing Voltmeter ===\r\n");
while (1) {
// 采集 16 次取平均,降低噪声
uint16_t adc_avg = adc_read_average(16);
uint32_t voltage_mv = (uint32_t)adc_avg * 3300 / 4095;
sprintf(buf, " Voltage: %lu.%03lu V (ADC raw: %u)\r\n",
voltage_mv / 1000, voltage_mv % 1000, adc_avg);
uart_send_string(buf);
delay_cycles(16000000); // 500ms 更新一次
}
}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
软件滤波
ADC 原始值通常存在一定噪声波动。简单的均值滤波(多次采样取平均)可以有效降低随机噪声。采样次数越多滤波效果越好,但响应速度越慢。
常用滤波方式:
- 均值滤波:N 次采样取平均,简单有效
- 中值滤波:N 次采样取中间值,抗脉冲干扰
- 滑动平均:保持最近 N 次采样的滑动窗口平均值
9.7 ADC 使用注意事项
注意
输入电压不能超过 Vref(3.3V):超压可能损坏 ADC 引脚。如需测量高于 3.3V 的电压,需外接分压电阻。
模拟输入阻抗:ADC 采样电容需要足够时间充电。如果外部信号源阻抗过高(>10kΩ),需增加采样保持时间或加缓冲运放。
噪声隔离:模拟引脚尽量远离数字信号走线。PCB 布局时注意模拟地和数字地的处理。
单次 vs 连续模式:
- 单次模式:每次需手动触发,适合低频采样
- 连续模式:自动重复采样,适合高频采样或配合 DMA
分压电阻计算(测量高于 3.3V 的电压):
V_adc = V_in × R2 / (R1 + R2) 例如:测量 0~12V,选 R1=27kΩ, R2=10kΩ V_adc_max = 12 × 10 / (27+10) = 3.24V < 3.3V ✓1
2
3
4
9.8 知识总结
| 内容 | 说明 |
|---|---|
| ADC 功能 | 模拟电压 → 数字值 |
| 分辨率 | 12-bit(0~4095) |
| 参考电压 | VDDA = 3.3V |
| 量化精度 | 约 0.8mV |
| 电压换算 | V = ADC × 3300 / 4095(mV) |
| 触发方式 | 软件触发 / 定时器触发 / 事件触发 |
| 降噪 | 多次采样取平均 |
掌握 ADC 后,你可以测量各种模拟传感器的输出(温度、光照、电位器、电池电压等)。下一章将学习 DAC 输出——ADC 的逆操作,将数字值转换为模拟电压。