DSP实战TMS320F28035:ADC 电压采样
在DSP开发中,ADC模拟电压采样是最常用的外设功能,用于将外部模拟信号(电压、电流、温度、压力等)转换为DSP可识别的数字量。
本文基于 TMS320F28035 内置12位ADC,实现ADCINA6通道电压采集,包含:
-
ADC寄存器配置(稳定版)
-
软件触发SOC采样
-
16次平均滤波(抗干扰)
-
电压值换算(0~3.3V)
-
LCD显示原始值+实际电压
-
解决:显示残影、采样漂移、底噪干扰
一、F28035 ADC 基础知识点
-
分辨率:12位(采样值范围:0 ~ 4095)
-
参考电压:默认 0 ~ 3.3V(严禁输入超过3.3V,否则会烧毁芯片)
-
通道:本文使用 ADCINA6(可根据需求修改为其他通道)
-
转换公式:毫伏电压 = 原始采样值 × 3300 / 4095
-
采样方式:软件触发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 工作流程总结
-
外部模拟电压 → 接入 ADCINA6 引脚
-
ADC 完成采样、保持、量化 → 得到 0~4095 的原始采样值
-
DSP 读取 ADC 寄存器中的原始值
-
通过换算公式,将原始值转为实际电压(毫伏)
-
将原始值和电压值格式化后,在LCD上显示
三、硬件说明
-
DSP芯片:TMS320F28035
-
ADC输入通道:ADCINA6(可根据开发板引脚调整)
-
输入电压范围:0 ~ 3.3V(严禁超过,建议添加RC滤波和钳位电路)
-
显示模块:SPI TFT LCD(ILI9340 驱动,分辨率 320×240)
-
指示模块: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 用于重新使能写保护,配置寄存器时必须成对使用。
更多推荐
所有评论(0)