在DSP开发中,ADC模拟电压采样是最常用的外设功能,用于将外部模拟信号(电压、电流、温度、压力等)转换为DSP可识别的数字量。

本文基于 TMS320F28035 内置12位ADC,实现ADCINA6通道电压采集,包含:

  • ADC寄存器配置(稳定版)

  • 软件触发SOC采样

  • 16次平均滤波(抗干扰)

  • 电压值换算(0~3.3V)

  • LCD显示原始值+实际电压

  • 解决:显示残影、采样漂移、底噪干扰

一、F28035 ADC 基础知识点

  1. 分辨率:12位(采样值范围:0 ~ 4095)

  2. 参考电压:默认 0 ~ 3.3V(严禁输入超过3.3V,否则会烧毁芯片)

  3. 通道:本文使用 ADCINA6(可根据需求修改为其他通道)

  4. 转换公式:毫伏电压 = 原始采样值 × 3300 / 4095

  5. 采样方式:软件触发SOC,多次采样求平均(提升稳定性)

二、ADC 采样基本原理

ADC 的全称是 Analog-to-Digital Converter,模拟/数字转换器。它的作用只有一句话:把连续变化的模拟电压,转换成 DSP 能识别的离散数字量。

2.1 模拟信号 vs 数字信号

现实世界里的电压、温度、电流、声音都是连续、平滑变化的,没有断点。比如 0V ~ 3.3V 之间可以有无数个电压值:0.1V、1.253V、2.8888V…… 这种信号叫模拟信号

但DSP是数字芯片,看不懂连续电压,只能看懂0和1组成的数字。就像我们无法直接“读懂”温度的连续变化,只能通过温度计上的数字(比如25℃)来感知,ADC就相当于DSP的“温度计”,负责完成“模拟电压→数字”的翻译。

2.2 12位 ADC 是什么意思?

ADC的“位数”决定了它的转换精度,F28035内置的是12位ADC,具体含义:

  • 1位 = 1个二进制位(0或1)

  • 12位 = 2^12= 4096个离散等级(采样值范围:0 ~ 4095)

  • 分辨率 ≈ 0.8mV:也就是说,ADC能分辨的最小电压变化约为0.8mV,位数越高,分辨率越高,采集越精准。

2.3 ADC 工作三步骤

ADC完成“模拟→数字”转换,核心分为3个步骤,全程由硬件自动完成,我们只需通过寄存器配置即可触发。

① 采样(Sampling)

每隔一小段时间,ADC内部的“开关”闭合,快速抓取当前输入的模拟电压值。这个过程就像给电压波形“拍照”,拍照的频率(采样率)可通过配置寄存器调整。

② 保持(Hold)

“开关”断开,把刚才抓到的电压值固定住,给ADC内部的转换电路留出足够时间,完成后续的量化操作(避免电压实时变化导致转换出错)。

③ 量化与编码(Quantization + Coding)

把固定住的模拟电压,对应到 0~4095 的某个数字(量化),再把这个数字转换成二进制(编码),最终输出给DSP的寄存器,供我们读取和处理。

2.4 电压与数字量的换算公式

F28035 采用 3.3V 参考电压,12位 ADC,核心换算公式如下:

1. 由电压求采样值:

2. 由采样值求实际电压(使用毫伏计算,避免浮点数运算,提升DSP效率):

说明:3300 = 3.3V = 3300mV,4095 = 12位ADC最大值。

2.5 ADC 工作流程总结

  1. 外部模拟电压 → 接入 ADCINA6 引脚

  2. ADC 完成采样、保持、量化 → 得到 0~4095 的原始采样值

  3. DSP 读取 ADC 寄存器中的原始值

  4. 通过换算公式,将原始值转为实际电压(毫伏)

  5. 将原始值和电压值格式化后,在LCD上显示

三、硬件说明

  1. DSP芯片:TMS320F28035

  2. ADC输入通道:ADCINA6(可根据开发板引脚调整)

  3. 输入电压范围:0 ~ 3.3V(严禁超过,建议添加RC滤波和钳位电路)

  4. 显示模块:SPI TFT LCD(ILI9340 驱动,分辨率 320×240)

  5. 指示模块:LED D400(用于指示程序正常运行,闪烁状态)

四、核心代码

4.1 头文件与全局变量

#include "DSP28x_Project.h"
#include "HK_all_include.h"

#define SAMPLE_TIMES       16          // ADC采样次数(可调整为32/64,提升稳定性)
#define DELAY_COUNT        600000UL    // LED闪烁延时计数(UL表示无符号长整型,避免溢出)
#define ADC_TIMEOUT        100000UL    // ADC采样超时阈值(防止程序卡死)
#define ADC_REF_VOLTAGE    3300UL      // ADC参考电压(3.3V = 3300mV)
#define ADC_MAX_VALUE      4095UL      // 12位ADC最大采样值
#define NOISE_THRESHOLD    5           // 底噪阈值,小于该值视为0,避免抖动
#define ADC_CHANNEL        6           // ADC采样通道(ADCINA6,可直接修改通道号)

static volatile Uint16 Beepcnt = 0;    // 解决 timer0 链接错误(无需修改)
static int16 tempRaw = 0;              // ADC A6 采样平均值(0~4095)
static Uint32 voltage_mV = 0;          // 换算后的实际电压(毫伏,0~3300mV)
static char strBuf[32] = {0};          // LCD 显示字符串缓冲区,初始化清零,避免乱码

// 外设初始化函数
void InitLED(void);
void InitKEY(void);
void Final_ADC_Init(void);            

// 工具函数
static void Int2Str_4Digit(int16 num, char *str); // 内部函数,static限制作用域
static void Volts_Display_Format(Uint32 mv, char *str);
static void ADC_ext_delay(void);       // 内部延时函数,static限制作用域

4.2 主函数(核心逻辑:采样+滤波+显示)

void main(void)
{
    volatile Uint32 i;            // 循环变量(volatile防止编译器优化)
    long tempSum = 0;             // 采样值累加和(用于求平均),初始化清零,避免脏数据

    // 1. 系统基础初始化
    InitSysCtrl();                // 初始化系统时钟(默认100MHz)
    DINT;                         // 关闭全局中断(初始化期间关闭,避免干扰)
    InitPieCtrl();                // 初始化PIE中断控制器
    IER = 0x0000;                 // 清空中断使能寄存器
    IFR = 0x0000;                 // 清空中断标志寄存器
    InitPieVectTable();           // 初始化中断向量表

    // 2. 外设初始化
    InitLED();                    // LED初始化(D400引脚配置为输出)
    InitKEY();                    // 按键初始化(S100引脚配置为输入,使能上拉)
    Timer0_init();                // 定时器0初始化(避免链接错误)

    // 3. LCD 初始化(SPI通信,ILI9340驱动)
    SPI_IOinit();                 // SPI引脚初始化
    spi_fifo_init();              // SPI FIFO初始化(提升通信效率)
    spi_init();                   // SPI 模式初始化
    LCD_BK_High();                // 开启LCD背光
    HD_reset();                   // LCD复位
    Initial_ILI9340C();           // 初始化ILI9340C驱动

    // 启动界面(蓝色背景,提示系统启动)
    LCD_TEST_SingleColor(Blue); 
    LCD_DisplayString(0, 0, "System Starting...");
    // 启动延时,避免初始化未完成就进入采样,提升稳定性
    for(i=0; i<DELAY_COUNT; i++); 

    // 4. ADC 初始化
    Final_ADC_Init();
    ADC_ext_delay();              // ADC初始化后延时,等待ADC稳定,避免首次采样漂移

    // 开启总中断(ADC转换完成需要中断标志判断)
    PieCtrlRegs.PIECTRL.bit.ENPIE = 1;          // 使能PIE中断
    EINT;                                       // 开启全局中断
    ERTM;                                       // 使能实时模式

    // 监控界面
    LCD_TEST_SingleColor(Black); 
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);

    // 死循环:持续采样、滤波、显示(程序核心逻辑)
    for(;;) 
    {
        // ================== ADC 多次平均采样(滤波核心) ==================
        tempSum = 0;                          // 累加和清零
        Uint8 sampleErr = 0;                  // 采样错误标志,便于异常排查
        for(i = 0; i < SAMPLE_TIMES; i++)               
        {
            AdcRegs.ADCSOCFRC1.all = 0x0003;  // 软件触发 SOC0 和 SOC1 采样
            // 超时判断,避免ADC异常导致程序卡死
            Uint32 adcTimeout = 0;
            while(AdcRegs.ADCINTFLG.bit.ADCINT1 == 0)
            {
                adcTimeout++;
                if(adcTimeout > ADC_TIMEOUT) 
                {
                    sampleErr = 1;            // 标记采样错误
                    tempRaw = 0;              // 异常处理,避免错误值影响显示
                    break;
                }
            }
            if(sampleErr) break;               // 采样错误,退出循环,避免无效累加
            AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1;     // 清除ADC中断标志
            tempSum += AdcResult.ADCRESULT1;  // 累加采样值(取SOC1结果)
        }

        // 采样异常判断,避免除以0(极端情况保护),同时处理采样错误
        if((SAMPLE_TIMES != 0) && (sampleErr == 0))
        {
            tempRaw = (int16)(tempSum / SAMPLE_TIMES); 
        }
        else
        {
            tempRaw = 0;
        }

        // 底噪屏蔽:采样值小于阈值时,视为0(避免底噪导致的微小抖动)
        if(tempRaw < NOISE_THRESHOLD) 
        {
            tempRaw = 0;
        }
        
        // 电压换算,用宏定义替换固定值,增强通用性;强制32位运算,避免溢出
        voltage_mV = (Uint32)(((unsigned long)tempRaw * ADC_REF_VOLTAGE)/ADC_MAX_VALUE);

        // ================== LCD 刷新显示(无残影,优化显示效率) ==================
        // 显示ADC原始值(固定4位,如0045、4095)
        LCD_DisplayString(0, 60, "RAW VALUE : "); 
        Int2Str_4Digit(tempRaw, strBuf);      // 原始值转为4位字符串
        LCD_DisplayString(100, 60, strBuf);   // 显示到LCD指定位置

        // 显示实际电压值(格式:X.XXX V,如3.300 V、0.000 V)
        LCD_DisplayString(0, 90, "VOLTAGE   : "); 
        Volts_Display_Format(voltage_mV, strBuf);   // 电压格式化
        LCD_DisplayString(100, 90, strBuf);        // 显示到LCD指定位置

        // ================== 程序运行指示灯(D400闪烁,优化延时逻辑) ==================
        D400ON();                     // LED点亮
        for(i=0; i<DELAY_COUNT; i++); // 用宏定义统一延时计数,便于调整闪烁频率
        D400LOW();                    // LED熄灭
        for(i=0; i<DELAY_COUNT; i++);
    }
}

4.3 ADC 初始化函数(稳定版配置,解决漂移问题)

// ADC初始化函数,通过宏定义配置通道,无需修改函数体即可切换采样通道
// 经过测试,采样稳定,无漂移,适配F28035内置ADC
void Final_ADC_Init(void)
{
    volatile Uint32 j;  // 延时变量(volatile防止编译器优化)
    EALLOW;             // 解除寄存器写保护(F28035 配置寄存器必须先解锁)
    
    // 1. 开启ADC时钟(确保ADC外设能正常工作)
    SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1; 
    for(j=0; j<10000; j++);  // 延时,等待时钟稳定

    // 2. 初始化ADC控制寄存器1,清空寄存器,避免脏数据
    AdcRegs.ADCCTL1.all = 0x0000;  
    AdcRegs.ADCCTL1.bit.TEMPCONV  = 0; // 禁用温度传感器(避免干扰ADC采样)

    // 3. 开启ADC电源和参考电压(关键步骤,否则ADC无法工作,优化延时逻辑)
    AdcRegs.ADCCTL1.bit.ADCBGPWD  = 1;      // 开启带隙电源
    ADC_ext_delay();                         // 复用延时函数,代码复用,避免冗余
    AdcRegs.ADCCTL1.bit.ADCREFPWD = 1;      // 开启参考电压电源
    AdcRegs.ADCCTL1.bit.ADCPWDN   = 1;      // 开启ADC核心电源
    AdcRegs.ADCCTL1.bit.ADCENABLE = 1;      // 使能ADC
    AdcRegs.ADCCTL1.bit.ADCREFSEL = 0;      // 选择内部参考电压(0=3.3V,与宏定义匹配)
    
    // 4. 配置ADC时钟(降低频率,提升高阻抗输入的稳定性)
    AdcRegs.ADCCTL2.bit.CLKDIV2EN = 1;      // 时钟分频2倍
    AdcRegs.ADCCTL2.bit.ADCNONOVERLAP = 1;  // 禁用重叠采样(提升稳定性)

    // 5. 通过宏定义配置采样通道,通用性强,切换通道无需修改函数体
    AdcRegs.ADCSOC0CTL.bit.CHSEL = ADC_CHANNEL;       // SOC0 对应目标通道
    AdcRegs.ADCSOC1CTL.bit.CHSEL = ADC_CHANNEL;       // SOC1 对应目标通道
    
    // 6. 配置采样窗口(拉到最大63,解决电荷残留导致的采样粘连、漂移)
    AdcRegs.ADCSOC0CTL.bit.ACQPS = 63;      // SOC0 采样窗口:63个ADC时钟周期
    AdcRegs.ADCSOC1CTL.bit.ACQPS = 63;      // SOC1 采样窗口:63个ADC时钟周期
    
    // 7. 配置ADC中断(用于判断采样完成)
    AdcRegs.INTSEL1N2.bit.INT1SEL = 1;      // 中断1 对应 SOC1 采样完成
    AdcRegs.INTSEL1N2.bit.INT1E = 1;        // 使能 ADC 中断1
    EDIS;                                  // 使能寄存器写保护(配置完成后锁定)
}

4.4 工具函数(显示格式化,解决残影/乱码)

// 电压格式化函数:将毫伏值转为 X.XXX V 格式(如3300mV → "3.300 V")
// 参数合法性判断,增强鲁棒性
static void Volts_Display_Format(Uint32 mv, char *str)
{
    // 异常处理:避免传入空指针,防止程序崩溃
    if(str == NULL)
    {
        return;
    }
    // 异常处理:超过参考电压或负数,视为异常,显示0.000 V
    if(mv > ADC_REF_VOLTAGE || mv < 0)
    {
        mv = 0;
    }

    Uint32 v = mv / 1000;    // 整数部分(如3300mV → 3)
    Uint32 rem = mv % 1000;  // 小数部分(如3300mV → 300)

    // 拼接字符串,格式:X.XXX V,代码排版整齐,可读性强
    str[0] = (char)(v % 10 + '0');        // 整数位
    str[1] = '.';                         // 小数点
    str[2] = (char)((rem / 100) % 10 + '0');  // 小数第一位
    str[3] = (char)(((rem / 10) % 10) + '0'); // 小数第二位
    str[4] = (char)(rem % 10 + '0');      // 小数第三位
    str[5] = ' '; 
    str[6] = 'V'; 
    str[7] = '\0'; // 字符串结束符,避免乱码
}

// 4位固定整数转字符串:不足4位前面补0(如45 → "0045"),消除LCD残影
// 参数合法性判断,增强鲁棒性
static void Int2Str_4Digit(int16 num, char *str)
{
    // 异常处理:避免传入空指针,防止程序崩溃
    if(str == NULL)
    {
        return;
    }
    // 异常处理:避免负数和超出4位的数值,防止显示异常
    if(num < 0) 
    {
        num = 0;
    }
    if(num > 9999) 
    {
        num = 9999;
    }

    // 拼接4位字符串(千位、百位、十位、个位),逻辑清晰
    str[0] = (num / 1000) % 10 + '0';
    str[1] = (num / 100) % 10 + '0';
    str[2] = (num / 10) % 10 + '0';
    str[3] = num % 10 + '0';
    str[4] = '\0'; // 字符串结束符,避免乱码
}

// LED初始化:D400引脚配置为输出(根据开发板引脚定义调整)
void InitLED(void) 
{ 
    EALLOW; 
    D400MUX = 0;  // 配置为GPIO功能
    D400DIR = 1;  // 配置为输出模式
    EDIS; 
}

// 按键初始化:S100引脚配置为输入,使能上拉(避免按键误触发)
void InitKEY(void) 
{ 
    EALLOW; 
    S100MUX = 0;  // 配置为GPIO功能
    S100DIR = 0;  // 配置为输入模式
    S100PUD = 1;  // 使能上拉电阻,避免误触发
    EDIS; 
}

// ADC延时函数(内部使用,复用性强)
static void ADC_ext_delay(void) 
{ 
    volatile Uint32 k; 
    for(k=0; k<10000; k++); 
}

五、代码关键细节

5.1 16次平均滤波的作用

单次ADC采样容易受电源噪声、外部干扰影响,导致采样值抖动(比如实际1V,采样值在960~970之间波动)。通过16次采样累加求平均,可大幅降低抖动,让显示数值更稳定。

5.2 采样窗口 ACQPS=63 的原因

ACQPS 是ADC采样窗口的时钟周期数,数值越大,采样时间越长,采样越稳定。本文设为最大值63,可避免高阻抗信号(如传感器输出)采集不准、电荷残留导致的采样粘连问题。

5.3 电压换算为什么用毫伏?

本文使用毫伏(mV)计算,而非直接计算伏特(V),核心原因是:避免浮点数运算,提升DSP运行效率。F28035的定点运算能力较强,浮点数运算会占用更多资源,用整数(毫伏)运算可加快程序运行速度。

5.4 解决LCD残影的关键

LCD显示残影的核心原因是:新显示的字符串长度小于旧字符串,旧字符未被完全覆盖(比如旧值是9999,新值是123,会残留“9”)。

本文通过 Int2Str_4Digit 函数,将原始值转为固定4位字符串(不足4位补0),每次刷新都会用4位字符完整覆盖旧内容,彻底解决残影问题。

5.5 EALLOW 和 EDIS 的作用

F28035 的部分寄存器(如系统控制寄存器、ADC控制寄存器)有写保护功能,防止误操作。EALLOW 用于解除写保护,EDIS 用于重新使能写保护,配置寄存器时必须成对使用。

更多推荐