资讯详情

资讯详情

STM8定时器触发ADC采样:原理、寄存器配置与工程实践

简介基于STM8S003单片机演示定时器触发ADC周期性采样的嵌入式工程面向单片机学习者与嵌入式硬件开发者解决单纯依靠软件循环控制采样间隔不够精准的问题。压缩包共41个文件约76KB以C源文件、头文件、xcl链接脚本、ewp/ewd工程文件及编译输出为主包含main.c、HardWare目录下的定时器与ADC驱动、LED及串口等模块以及hex、map、日志等生成物便于直接编译烧录验证。已有3708人学习下载。工程完整展示了定时器时基配置预分频与自动重载、ADC工作模式及触发源选择、中断服务程序中启动转换并读取结果的流程适合需要稳定周期采样的环境监测、电机控制、电源管理等场合为理解STM8定时器与ADC协同工作机制提供了可直接运行的参考代码。 最近弄一个小项目需要对电池电压做周期性检测同时还想在电机运行时抓一下电流变化。手里正好有STM8S103F3P6这颗料8KB Flash、1KB RAM带一个10位ADC和几个定时器组合起来做“定时器触发ADC采样”非常顺手。STM8这个系列价格便宜、货源稳定开发环境也不像老51那样全靠手工很适合做这种中等复杂度的采样任务。我把整个实现过程整理成一篇实操笔记从为什么用定时器触发、底层时序怎么算到寄存器配置、中断处理、问题排查尽量把你可能踩的坑也提前说出来。无论你是刚转到STM8还是之前一直在裸机循环里轮询ADC这篇文章应该都能帮你少走几步弯路。1. 项目思路与整体架构1.1 需求拆解为什么采样必须等间隔刚开始做电压采样时我第一反应是主循环里直接调ADC读回来显示。功能确实能跑但仔细看数据就发现一个问题采样的时间间隔完全不由自己控制。主循环里只要多执行一个按键扫描、显示刷新或者串口打印下一个采样点就会往后拖导致波形还原出来是歪的。尤其做电机电流监测这种场景电流信号本身叠加了PWM斩波噪声如果采样点抖动软件滤波也不好处理。所以必须有一个稳定的节拍来决定“什么时候采”这个节拍最好由硬件定时器产生而不是靠软件延时。定时器触发ADC的本质就是让ADC的启动时刻和主循环的繁忙程度彻底解耦。1.2 方案取舍中断启动转换与硬件直接触发实现定时器触发ADC常见有两种思路。第一种是定时器更新中断里启动ADC然后等EOC标志或直接在ADC中断里读取结果。这种方案逻辑直观STM8所有带ADC的型号都能用不需要翻太多参考手册也是我这次采用的方式。第二种是定时器事件直接硬件触发ADC转换CPU不参与“启动”动作。这种方案延迟更小采样时刻更精确但STM8不同子系列对ADC外部触发源的支持和配置方式有差异需要仔细查对应型号的数据手册。如果刚开始接触STM8我建议先按第一种跑通整个采样链路确认没问题之后再考虑切到硬件触发优化时序。2. 定时器与ADC工作的底层原理2.1 STM8的ADC转换过程STM8S系列的ADC是逐次逼近型SAR不是单片机里的一个神秘黑盒。它的核心工作方式是让内部DAC先猜一个中间值然后和输入电压比较根据比较结果缩小猜测范围逐位逼近一直到10位比较完得到最终的数字量。我们软件要关注的就三件事选择哪个通道、什么时候启动转换、什么时候读取结果。启动转换的操作在很多型号上只要把ADC控制寄存器的ADON位写1转换完成后硬件置EOC标志这时再从数据寄存器高字节ADC_DRH和低字节ADC_DRL里拼出完整10位数据。我用的右对齐方式下转换值计算公式是uint16_t adc_value ((uint16_t)ADC_DRH 2) | (ADC_DRL 0x03);这个过程看起来简单但有个容易被忽略的细节STM8S的ADON位第一次写1是给ADC上电第二次写1才真正启动一次转换。如果上电后立刻启动可能得不到稳定结果。我一般会在初始化里写两次ADON第一次激活ADC第二次触发一次“空转换”让内部采样结构稳定下来。2.2 采样周期与时间预算计算定时器触发频率不是随便定的要同时考虑信号带宽、ADC转换时间和CPU负载。以我的STM8S103F3为例主时钟用内部HSI的16MHz。TIM2的预分频寄存器PSCR是3位实际分频系数是2的幂次方。我设置PSCR3即8分频计数时钟变成2MHz。如果自动重装载值ARR设为999那么一次循环就是1000个计数脉冲更新时间频率是2MHz / (999 1) 2kHz也就是说每500us进入一次定时器中断。这个频率做电池电压监测非常合适做电机电流采样配合软件滤波也够用。但要注意定时器中断频率不能超过ADC的物理上限。STM8S的ADC转换时间大约是14个ADC时钟周期。如果设置ADC时钟为8MHz一次转换约1.75us看起来2kHz完全没问题。真正容易翻车的是采样保持时间。如果信号源内阻偏高采样保持电容还没充满转换结果就会偏低且跳动。这种时候要么降低信号源阻抗要么把ADC的采样时钟分频调大给采样保持留出足够时间。3. 开发环境与寄存器配置3.1 开发环境准备我在Windows下用IAR for STM8配ST-Link调试器。IAR的工程模板很成熟向量表、启动文件都帮你处理好了可以专心写业务代码。老牌的STVD加Cosmic编译器也有人用但STVD在新系统上兼容性一般如果你手头资料是STVD的建议直接换IAR会少很多环境问题。创建工程时选好具体型号我是STM8S103F3然后在options里把Debugger设为ST-LINK。下载调试用SWIM接口线不要太长SWIM对地线比较敏感连接不稳定时先检查接地。需要说明的是以下寄存器代码用的是寄存器直接操作方式不依赖官方标准库。这么做的好处是寄存器行为透明出了问题容易定位。如果你习惯标准库也可以对应改成库函数调用逻辑是一样的。3.2 定时器初始化TIM2是16位定时器预分频、重装载值都可以配得比较灵活。我的初始化代码如下void TIM2_Config(void) { CLK_PCKENR1 | 0x02; // 使能TIM2时钟 TIM2_PSCR 0x03; // 8分频计数时钟16MHz/82MHz TIM2_ARRH 0x03; // 高8位0x03 TIM2_ARRL 0xE7; // 低8位0xE7ARR0x03E7999 TIM2_CR1 0x01; // 计数器使能 TIM2_IER 0x01; // 更新中断使能 }这里有几个容易犯的错误需要提醒。首先是时钟门控很多人忘了使能外设时钟写半天寄存器一点反应都没有。其次是写ARR时要注意顺序16位定时器一般要求先写高字节ARRH再写低字节ARRL。还有一点如果使用了标准库中断函数名必须和启动文件里的向量表严格对应否则编译可能通过但中断就是进不去。3.3 ADC初始化ADC部分同样按寄存器操作写void ADC_Config(void) { CLK_PCKENR2 | 0x02; // 使能ADC1时钟 ADC_CR1 0x00; // ADC时钟分频默认值 ADC_CR2 0x00; // 数据右对齐 ADC_CSR 0x00; // 选择通道AIN0具体引脚查数据手册 ADC_CR1 | 0x01; // 第一次写ADONADC上电 ADC_CR1 | 0x01; // 第二次写ADON启动一次转换 while((ADC_CSR 0x20) 0); // 等待EOC }初始化完成后后面每次需要转换时再写一次ADC_CR1 | 0x01即可启动。EOC标志是ADC_CSR的第5位。读取ADC_DRH和ADC_DRL后有些型号会自动清除EOC如果发现不清除就在读取后手动加一句ADC_CSR ~0x20;。4. 核心代码实现与处理逻辑4.1 定时器中断里启动ADC并读取我采用的方式很直接定时器更新中断触发后在中断里启动ADC转换等待转换完成后读取结果存到全局变量里。示例代码volatile uint16_t adc_value 0; volatile uint8_t adc_ready 0; // 中断函数名以当前工程的启动文件为准IAR下常见写法如下 #pragma vector TIM2_UPD_OVF_BRK_VECTOR __interrupt void TIM2_UPD_OVF_BRK_IRQHandler(void) { ADC_CR1 | 0x01; // 启动一次ADC转换 while((ADC_CSR 0x20) 0); // 等待转换完成 adc_value ((uint16_t)ADC_DRH 2) | (ADC_DRL 0x03); adc_ready 1; }这段代码在2kHz中断频率下可行因为ADC转换时间相对500us来说很短中断里死等这几十微秒不会堵塞主循环。但如果采样频率继续提高死等就会浪费CPU时间。更推荐的进阶写法是启用ADC转换完成中断定时器中断里只写一次ADC_CR1 | 0x01启动转换然后立刻退出等ADC转换完成后硬件会进ADC中断在ADC中断里读取数据并置标志。这样CPU等待时间几乎为零代价是要多写一个中断服务函数并额外使能ADC_CSR里的EOCIE位。我实际测试中两种方式的数据质量没有差别主要是看你的定时中断频率高不高。4.2 主循环消费采样数据主流程就非常简单了不断检查adc_ready标志void main(void) { CLK_Config(); TIM2_Config(); ADC_Config(); rim; // 开全局中断 while (1) { if (adc_ready) { adc_ready 0; ProcessADC(adc_value); } // 其他低优先级任务放这里 } }两个全局变量必须用volatile修饰否则编译器可能把adc_ready优化到寄存器里导致主循环一直判断不到变化。这里还有个容易忽略的点主循环里消费数据的时间不能超过采样周期。如果你在ProcessADC里做了耗时的浮点运算或LCD刷新会导致adc_ready一直为1主循环追不上中断实际采样频率会掉下来。5. 常见问题与排查技巧5.1 ADC数据乱跳怎么办我遇到过最普遍的问题是ADC读数不稳定。排查思路按以下顺序来检查输入引脚是不是悬空。悬空状态下ADC采集到的就是周围环境的不确定电压值跳动非常正常。检查电源退耦。STM8S的参考电压通常就是VCCVCC纹波大采样值就会跟着抖。我一般会在VCC和GND之间放一个10uF电解电容加一个0.1uF陶瓷电容。检查采样保持时间。信号源内阻高时需要调慢ADC时钟或降低采样频率给采样电容更多充电时间。软件滤波。简单的滑动平均就能显著改善数据质量比如取最近8次采样的平均值。// 简易滑动平均 static uint16_t history[8]; static uint8_t idx 0; uint32_t sum 0; history[idx] adc_value; if (idx 8) idx 0; for (uint8_t i 0; i 8; i) sum history[i]; uint16_t filtered (uint16_t)(sum / 8);5.2 采样频率对不上理论值这个问题多半出在时钟配置上。STM8S默认可能是HSI的16MHz但如果你初始化过程序分频器主时钟可能已经不是16MHz了采样频率自然跟着变。TIM2的预分频和ARR计算也要核对。我习惯把计算过程写进注释避免过几个月回来看代码还要重新翻手册。另一个容易被忽略的是如果中断服务函数里处理时间太长比如调试时加了断点或串口打印都会让实际采样节奏被打乱。调试采样频率建议直接用示波器测一个IO翻转波形比单看寄存器值直观。5.3 定时器中断进不去中断不进通常是三个原因。一是没有执行rim指令开启全局中断。二是TIM2_IER的更新中断位没使能。三是中断服务函数名和启动文件的向量表对不上这个最隐蔽因为编译不报错程序跑了就是没反应。如果用了IAR可以在工程里查看启动文件或官方例程里同一个向量用的是哪个函数名。对照着改你的ISR名称基本就能解决。6. 扩展思路与个人的一点体会6.1 从单点采样到连续波形采集这个项目跑到后面我不满足于只读一个电压值想把一段波形连续记录下来分析。思路很简单在定时器中断里把每次的adc_value存进一个数组存满一帧后置一个frame_ready标志主循环再把这一帧数据发送出去。但STM8S103F3只有1KB RAM数组不能无限大。如果存10位完整值一个uint16_t占2字节最多只能存500个点左右。我后来的做法是只存高8位ADC_DRH精度从10位降到8位但能存到接近1K的数组。对很多波动信号分析场景8位也够用。6.2 调试节奏很重要最后分享一个我反复强调的经验不要一上来就追求高采样率。先把定时器频率设置在1kHz左右确认每个中断间隔正常、数据稳定再逐步调高。我早期调试直接在10kHz采样率下查数据跳动的毛病排查了大半天才发现是信号源内阻太高导致采样保持时间不足。如果当时先降频率跑通链路至少能省一半时间。定时器触发ADC采样这个功能在STM8上其实不难但它是很多工程问题的交汇点定时器、中断、模拟采样、信号处理。把这套流程理清楚后续再做更复杂的采集系统就有了可以依赖的地基。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →