MSPM0L1306 从零到入门: 第九章 ADC-电压采集

第九章 ADC-电压采集

本章带你用 MSPM0L1306 的 ADC12 采集单端电压,并把原始计数值换算成电压。重点讲清"时钟/采样时间/参考电压/通道映射/中断"的关系,给出稳健的、可扩展的代码骨架。

1. MSPM0L 系列的 ADC 概览

  • 架构与分辨率:12 位 SAR(逐次逼近)ADC,结果范围 0...4095。
  • 通道资源:最多 10 路外部模拟输入(具体映射见数据手册/引脚手册),每路对应若干可复用管脚;需将目标引脚配置为"模拟功能"。
  • 参考电压 Vref:
    • VDDA(默认,通常近似板载 3.3V,但可能有偏差)
    • 外部参考引脚(如你板上 PA23 可作参考电压,与R24跳线有关)
    • 内部参考(若芯片支持,常用于自校准)
  • 转换模式:单次/重复单次/多通道顺序单次/多通道顺序重复(本文先做"单通道重复")。
  • 触发源:软件触发/定时器触发/序列自动触发等。
  • 采样时间:由"采样时钟 × 采样周期数"决定,必须与信号源阻抗匹配(高阻信号要更长的采样时间,保证采样电容充分充电)。

小提醒:

  • 如果用 VDDA 为参考而你又要算"绝对电压",需知道真实 VDDA(3.30V 只是经验值)。更严谨做法是:用内部参考/外部参考,或用内部通道反推 VDDA。

2. 软件设计

2.1 编程大纲

  1. ADC 相关宏与引脚/通道定义
  2. ADC 时钟与基础配置(重复单次 + 中断)
  3. ADC 中断服务函数(取样就绪标志 + 保存结果)
  4. API:阻塞式读取 + mV 换算(支持整数运算)
  5. 主函数测试(打印计数与电压)

2.2 代码分析

2.2.1 ADC 相关参数与引脚定义
c 复制代码
static volatile bool     g_adcReady = false;
static volatile uint16_t g_adcLast  = 0;

关于参考电压:

  • 本例使用 DL_ADC12_REFERENCE_VOLTAGE_VDDA(与供电相同)。若要提高精度且板上支持外部参考,请改为外部参考并正确配置引脚/电平。
  • 若用 VDDA 作为 Vref,换算电压时请用"实测的 VDDA"而不是固定 3.3V(先用 3300mV 开发,后期可标定)。
2.2.2 ADC 基础配置

思路:

  • ADC 时钟用 SYSOSC,经 8 分频得到 4 MHz(与前文 32MHz 主频一致)。
  • 采样时间设定适中(例如 64 周期 → 16 μs @4MHz),可覆盖大多数 <10kΩ 的信号源阻抗。若源阻更高,请增大采样周期数。
  • 采用"重复单次 + 软件触发一次启动",让 ADC 连续采样;每次结果到 MEM0 触发中断。
c 复制代码
#include <ti_msp_dl_config.h>
#include <stdbool.h>
#include <stdint.h>

/* 采样完成标志与上次结果(ISR更新) */
static volatile bool     g_adcReady = false;
static volatile uint16_t g_adcLast  = 0;

/* ADC 时钟:SYSOSC/8,目标 24~32MHz 频段配置 */
static const DL_ADC12_ClockConfig gADC_ClockCfg = {
    .clockSel    = DL_ADC12_CLOCK_SYSOSC,
    .divideRatio = DL_ADC12_CLOCK_DIVIDE_8,
    .freqRange   = DL_ADC12_CLOCK_FREQ_RANGE_24_TO_32
};

void ADC_Init_CH0_Repeat(void)
{
    /* 若非用 SysConfig 配置引脚为模拟功能,需要手动设置为模拟输入:
       DL_GPIO_initAnalogFunction(GPIO_ADC_CH0_IOMUX);  // IOMUX 常量以芯片包为准
       建议:用 SysConfig 勾选 ADC 通道并生成引脚配置,避免手写出错。 */

    /* 1) 时钟配置 */
    DL_ADC12_setClockConfig(ADC_INST, (DL_ADC12_ClockConfig *)&gADC_ClockCfg);

    /* 2) 单通道"重复单次"+ 自动采样源 + 软件触发启动 */
    DL_ADC12_initSingleSample(
        ADC_INST,
        DL_ADC12_REPEAT_MODE_ENABLED,                 /* 重复单次 */
        DL_ADC12_SAMPLING_SOURCE_AUTO,                /* 自动采样 */
        DL_ADC12_TRIG_SRC_SOFTWARE,                   /* 软件触发 */
        DL_ADC12_SAMP_CONV_RES_12_BIT,                /* 12bit */
        DL_ADC12_SAMP_CONV_DATA_FORMAT_UNSIGNED       /* 无符号 */
    );

    /* 3) 配置 MEM0 采样 CH0,参考电压选 VDDA,使用 Sample Timer0,单次结果,不加平均 */
    DL_ADC12_configConversionMem(
        ADC_INST,
        ADC_MEM_IDX,
        ADC_INPUT_CH,
        DL_ADC12_REFERENCE_VOLTAGE_VDDA,
        DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP0,
        DL_ADC12_AVERAGING_MODE_DISABLED,
        DL_ADC12_BURN_OUT_SOURCE_DISABLED,
        DL_ADC12_TRIGGER_MODE_AUTO_NEXT,              /* 自动进入下一次 */
        DL_ADC12_WINDOWS_COMP_MODE_DISABLED
    );

    /* 4) 采样时间:以采样时钟 4MHz 计,64 周期 ≈ 16us。
          高阻源(>10kΩ)或 RC 滤波较大时,可适当增大,如 128/256 周期。 */
    DL_ADC12_setSampleTime0(ADC_INST, 64U);

    /* 5) 中断:清标志→使能 MEM0 结果就绪中断 */
    DL_ADC12_clearInterruptStatus(ADC_INST, DL_ADC12_INTERRUPT_MEM0_RESULT_LOADED);
    DL_ADC12_enableInterrupt(ADC_INST,       DL_ADC12_INTERRUPT_MEM0_RESULT_LOADED);

    /* 6) 使能转换并启动"重复单次" */
    DL_ADC12_enableConversions(ADC_INST);
    DL_ADC12_startConversion(ADC_INST);
}

要点:

  • 若仅需"按需采样一次",可将 REPEAT_MODE_ENABLED 改为 DISABLED,并在读取函数中"开始→等待→读取→停止"。
  • 建议用 SysConfig 配置通道与引脚;手写 IOMUX 容易出错。
2.2.3 ADC 中断服务函数
c 复制代码
void ADC_INST_IRQHandler(void)
{
    switch (DL_ADC12_getPendingInterrupt(ADC_INST)) {
        case DL_ADC12_IIDX_MEM0_RESULT_LOADED:
            /* 读取会清掉本次 MEM0 标志,为下一次转换做准备 */
            g_adcLast  = (uint16_t)DL_ADC12_getMemResult(ADC_INST, ADC_MEM_IDX);
            g_adcReady = true;
            break;
        default:
            break;
    }
}

说明:

  • ISR 要尽量短小;若要滤波/均值,建议只做累计并把计算放到主循环或低优先级任务中。
  • 读取 MEM0 后,下一次转换会继续进行(重复单次)。
2.2.4 采集 API 与电压换算

提供"阻塞读取一帧 + mV 换算"的简洁接口。开发期可先假设 VREF_MV=3300,后期可改为"测得 VDDA"或"使用外部/内部参考"。

c 复制代码
/* 假设参考电压为 3.300V(开发阶段可用;量产请改为实测或稳定参考) */
#define VREF_MV  (3300U)

/* 阻塞式获取一次最新样点(由重复模式持续产生,ISR 更新 g_adcLast) */
uint16_t ADC_ReadBlocking(void)
{
    g_adcReady = false;
    /* 如果刚进来时已经有新样点,可直接返回;否则等待一次中断 */
    while (!g_adcReady) {
        __WFI(); /* 低功耗等待中断 */
    }
    return (uint16_t)g_adcLast;
}

/* 计数→毫伏(整数运算,避免浮点) */
static inline uint32_t adcCounts_to_mV(uint16_t counts, uint32_t vref_mV)
{
    /* 12bit 最大值 4095 */
    return ((uint32_t)counts * vref_mV) / 4095U;
}

可选:做一个简易滑动平均(抗抖/降噪),如下为 8 次均值示例(在主循环里调用,非 ISR)。

c 复制代码
/* 8 点滑动平均(示例) */
uint16_t ADC_ReadAvg8(void)
{
    uint32_t sum = 0;
    for (int i = 0; i < 8; ++i) {
        sum += ADC_ReadBlocking();
    }
    return (uint16_t)(sum / 8U);
}
2.2.5 主函数测试
c 复制代码
#include "ti_msp_dl_config.h"
#include <stdio.h>

/* 若已在其他章节完成 UART printf 重定向,这里直接用 printf 输出 */
int main(void)
{
    SYSCFG_DL_init();            /* 时钟=32MHz、GPIO等基础配置(建议用 SysConfig 生成) */
    __enable_irq();

    /* 串口、SysTick 如需可初始化(用于打印与延时) */
    // SYSCFG_DL_UART_0_init();
    // SysTick_init();

    /* ADC 初始化 + 使能中断 */
    ADC_Init_CH0_Repeat();
    NVIC_ClearPendingIRQ(ADC_INST_INT_IRQN);
    NVIC_EnableIRQ(ADC_INST_INT_IRQN);

    printf("\r\n--- MSPM0 ADC Single-Channel Repeat Demo ---\r\n");

    while (1)
    {
        /* 读取一次(或均值) */
        uint16_t adc_raw = ADC_ReadBlocking();
        uint32_t mv      = adcCounts_to_mV(adc_raw, VREF_MV);

        printf("ADC raw: %u, Voltage: %lu.%03lu V\r\n",
               adc_raw, mv / 1000UL, mv % 1000UL);

        /* 如果有 SysTick,可做一个简易 1s 周期 */
        // delay_ms(1000);
        for (volatile uint32_t d = 0; d < 3200000; ++d) { __NOP(); } /* 约 ~100ms 粗延时(32MHz) */
    }
}

运行现象:

  • 串口周期打印"12位计数 + 近似电压值"。
  • 用可调电源或电位器给 PA27(ADC CH0)输入 0...Vref 的电压,观察数值随之变化。

3. 关键细节与常见问题

  • 引脚必须为"模拟模式":数字输入/输出/上拉下拉会影响精度,务必由 SysConfig 或 DL_GPIO_initAnalogFunction(...) 设置为模拟。
  • 采样时间与源阻抗:若信号源阻抗较高(>10kΩ)或串入 RC 滤波,需增大采样周期数(如 128/256/512),保证采样电容充分充电。
  • 参考电压的选择:
    • 用 VDDA 作为参考时,若板上 3.3V 有波动,你换算的"绝对电压"会跟着飘。可改用外部精密参考,或使用内部参考并做一次"测量内部参考→反推 VDDA"的校准。
    • 若切换参考或通道,请确保采样时间/稳定时间足够(有些参考需要上电稳定时间)。
  • 中断处理要短小:复杂滤波应放在主循环或低优先级任务,或用 DMA 搬运到缓冲区后再处理。
  • 触发与速率:若要固定采样频率,建议用定时器触发 ADC,而不是"重复单次+全速跑";配合 DMA 做等间隔采样更稳健。
  • 输入保护:超过 0...Vref 的电压会导致结果钳位甚至损伤芯片;外接电压请加分压、钳位与限流。

4. 小结与进阶

  • 你已掌握:单通道重复采样 + 中断取数 + 整数电压换算。
  • 进阶方向:
    1. 多通道顺序采样(序列 + DMA 环形缓冲),批量采集多路传感器
    2. 定时器触发的等间隔采样(精准采样率)
    3. 标定与温漂:做零点/满量程校准,或读取工厂校准参数修正
    4. 使用外部/内部精密参考,提升绝对精度

相关推荐
net3m3320 小时前
24位INMP441的相关配置,原本是16位mic数据,麦克风音质不高
esp32·i2s
时空自由民.21 小时前
蓝牙协议栈介绍
linux·网络·单片机
怎么就重名了21 小时前
mosquitto在windows上的安装和测试
物联网
蓝天居士21 小时前
M24C64芯片资料与程序代码(2)
嵌入式硬件·芯片资料
吃米饭1 天前
HC32L021C8UB 移植 FreeRTOS
stm32·嵌入式·freertos·rtos
asjodnobfy1 天前
开关电源尖峰电压计算
嵌入式硬件·硬件工程
振南的单片机世界1 天前
开漏输出:只能拉低,不能拉高,高电平靠“外部”帮忙
stm32·单片机·嵌入式硬件
FFF团团员9091 天前
CCS快速使用4(tim,pwm)
单片机·嵌入式硬件
某先森不吃鱼1 天前
工程日志——离轴编码器矫正与磁场串扰解决
嵌入式硬件
黑白园1 天前
STM32 通过I2C 读写EEPR0M AT24C02
stm32·单片机·嵌入式硬件