[STM8] 把 STM8S 的 ADC 玩明白:一个连续采集的ADC项目

发布时间:2026/6/1 5:26:14

[STM8] 把 STM8S 的 ADC 玩明白:一个连续采集的ADC项目 把 STM8S 的 ADC 玩明白一个连续采集的ADC项目作者: 404是NotFound呀日期: 2026/05/31基于参考手册 RM0016 Rev 14, 第 24 章项目背景: 一个用 STM8A 控制的项目需要同时采样多路模拟信号写在前面最近在做一个xxx驱动项目需要用 STM8S 同时采集多路模拟信号。翻开 RM0016 参考手册第 24 章对着那几十页的寄存器描述和时序图啃了半小时。说实话ST 这份手册写得不算挺好。所以我决定把自己踩过的坑、搞明白的东西整理成这篇笔记。如果你也在用 STM8S 的 ADC希望这篇能帮你少走些弯路。先说结论五个模式怎么选STM8S 的 ADC 有五种转换模式。只需要记住三个控制位CONT连续、SCAN扫描、DBUF缓冲它们的组合就决定了模式你想干嘛CONTSCANDBUF简单一句话偶尔读一个通道000转一次就停不停地读一个通道100转完又转但数据会被覆盖不停地读一个通道还要存历史101结果存进缓冲区可以做平均滤波一次性扫多个通道01xAIN0 到 AINn 轮一遍不停地扫多个通道11x一轮扫完自动下一轮我的项目选的是单次扫描模式CONT0, SCAN1因为我需要同时采集 6 路反馈信号但不需要连续不停地采——主循环里按需触发一次就够了。1. 先搞清楚你手上有什么STM8S 系列一般有两个 ADC 模块ADC1 和 ADC2。都是 10 位 SAR逐次逼近型最多 16 路复用输入。但这里有个关键区别ADC1 是完全体ADC2 是青春版。ADC2 只有基本的单次和连续转换没有扫描模式、没有数据缓冲、没有模拟看门狗。所以如果你的应用涉及多通道采样基本都得用 ADC1。我项目里也是只用了 ADC1。小贴士: 具体有多少通道取决于你的芯片型号和封装不是所有 16 个通道都引出来了。一定要查你那颗芯片的 datasheet pin description 表。ADC 的引脚这里容易被忽略的是参考电压引脚小封装48脚及以下: V_REF 和 V_REF- 在芯片内部直接连到了 V_DDA 和 V_SSA没有外部引脚。也就是说参考电压就是供电电压没法调。大封装64脚以上: 有独立的 V_REF 和 V_REF- 引脚可以外接精密参考电压源提高采样精度。如果你用小封装采样精度就取决于你的电源质量。供电纹波大ADC 读数就抖。这是硬件设计时就要考虑的事情。2. ADON 位一个位干两件事这是我刚开始觉得最反直觉的地方。ADC_CR1 寄存器的 bit 0 叫 ADON它的行为是这样的第一次写 ADON1 → 唤醒 ADC从掉电模式 第二次写 ADON1 → 启动一次转换 之后每次写 ADON1 → 再启动一次转换我猜是为了省电。ADC 上电需要稳定时间大约一个转换周期所以唤醒和启动转换被分成了两步。你需要先唤醒它等它稳定然后才能真正开始采样。实际使用中要注意两点上电前先选好通道。因为 ADON1 之后选中通道的数字 I/O 会被自动禁用。如果你先上电再选通道可能会有一个短暂的窗口期引脚状态不确定。不要顺手在写 ADON 的同时改 CR1 的其他位。手册明确说了如果同一次写操作同时修改了 ADON 和其他位转换不会被触发。这是为了防止你改分频系数的时候误触发一次转换但如果你不知道这个设计就会很纳闷为什么我的 ADC 启动不了。3. 时钟和预分频ADC 时钟由主时钟 f_MASTER 分频而来分频系数通过 CR1 的 SPSEL[2:0] 配置从 /2 到 /18。SPSEL000 → fMASTER/2 SPSEL001 → fMASTER/3 ... SPSEL111 → fMASTER/18一个实际建议如果你要改分频系数最好在 ADC 掉电时改ADON0 时。否则时钟切换瞬间可能产生毛刺导致第一次转换结果不准。如果你非要在 ADC 运行时改那就忽略改完后的第一个转换结果。每次 ADC 转换需要 14 个时钟周期。假设 f_MASTER 16MHzSPSEL100 (/8)则 ADC 时钟 2MHz一次转换需要 14/2MHz 7us。这在大多数应用场景下足够快了。4. 五种转换模式逐一拆解4.1 单次转换模式 — 最简单的一个配置: CONT0, SCAN0这是最基础的模式。你选好一个通道写 ADON1它就转一次转完后 EOC 标志置位然后歇着等你下一次吩咐。选通道 → ADON1 → 等 EOC → 读数据 → 完事适合场景偶尔读一下电池电压、芯片温度这种不需要高频采样的东西。4.2 连续转换模式 — 停不下来配置: CONT1, SCAN0设了 CONT1 之后ADC 就像上了发条一样转完一次马上转下一次中间不停。但问题来了——数据只存在 ADC_DR 这一对寄存器里新结果会直接覆盖旧结果。ADON1 → 转 → 存数据 → 转 → 覆盖数据 → 转 → 又覆盖 → ...如果你 CPU 处理得不够快数据就丢了。所以这个模式适合的场景比较窄你只关心当前值的情况比如实时监测一个电源电压。要停止的话清 CONT 位或者直接 ADON0 关掉 ADC。我的忠告如果你发现用连续模式数据总是丢换缓冲连续模式。4.3 缓冲连续转换模式 — 连续模式的正确打开方式配置: CONT1, SCAN0, DBUF1 | 仅 ADC1这是连续模式的增强版。区别在于结果不再只存一个 ADC_DR而是依次存入一组缓冲寄存器8 个或 10 个取决于型号。ADON1 → 转→存DB0R → 转→存DB1R → ... → 转→存DB(N-1)R → 缓冲区满EOC1 → 转→覆盖DB0R → 转→覆盖DB1R → ... → 又满了EOC1 → ...这个模式最实用的场景是多次采样求平均。你可以等 EOC 置位后把缓冲区里的 8 或 10 个值全部读出来做软件滤波然后再等下一轮。要注意OVROverrun标志如果你没来得及读完缓冲区新的一轮就开始覆盖了OVR 就会被置 1。这时候之前读到的数据可能已经不完整了建议直接重来。设置 ADON1 可以自动清除 OVR。4.4 单次扫描模式 — 多通道采样的主力配置: CONT0, SCAN1 | 仅 ADC1这是我项目里用的模式也是我觉得最值得详细讲的一个。扫描模式的核心思想是你告诉 ADC “从 AIN0 扫到 AINn”它就自动把 AIN0、AIN1、…、AINn 依次转换一遍每个通道的结果存入对应编号的缓冲寄存器。设 CH[3:0] 5 → ADON1 → AIN0→DB0R, AIN1→DB1R, AIN2→DB2R, AIN3→DB3R, AIN4→DB4R, AIN5→DB5R → 全部完成EOC1停止几个容易踩的坑CH[3:0] 设的是终点不是起点。扫描永远从 AIN0 开始CH[3:0] 告诉硬件最后一个通道是几号。我一开始以为是选择起始通道结果白调试了半小时。不要在扫描过程中清 SCAN 位。想停清 ADON。扫描模式下清 EOC 有坑。这是手册里用了一整段 “Caution” 来警告的事。你不能用ADC1-CSR ~ADC1_CSR_EOC这种位操作来清 EOC因为这个操作会先读 CSR把当前硬件正在扫描的通道号读回来然后写回去——这就把你的扫描终点给改了正确做法直接把一个预先准备好的值写入 CSR// 不要这样ADC1-CSR~ADC1_CSR_EOC;// 应该这样ADC1-CSRlast_channel_number;// EOC0, CH你想要的终点通道AIN12 在扫描模式下不能选。这是硬件限制具体看 datasheet。扫描模式下每个被选中的通道的 GPIO 输出会被禁用。所以别想着在扫描 AIN0-AIN5 的同时用 PB3 做 PWM 输出——它会被关掉。4.5 连续扫描模式 — 不停地扫配置: CONT1, SCAN1 | 仅 ADC1就是单次扫描的停不下来版。一轮扫完 AIN0 到 AINn 之后自动从头再来一轮如此循环。停止方法ADON0立即停可能数据不完整CONT0等当前这轮扫完再停优雅停机清 EOC 的注意事项和单次扫描一样——别用位操作直接赋值。5. 数据对齐左还是右通过 CR2 的 ALIGN 位选择。这个看似简单但如果你读数据的顺序搞错了数据一致性就没法保证。右对齐 (ALIGN1) — 我的选择ADC_DRH: [ - - - - - - D9 D8 ] ADC_DRL: [ D7 D6 D5 D4 D3 D2 D1 D0 ]读取顺序先读 DRL再读 DRH。这看起来反直觉但 STM8 是小端序用 LDW 指令可以一次性读出 16 位。右对齐的好处是直接拿到的就是 0~1023 的线性值方便计算。左对齐 (ALIGN0)ADC_DRH: [ D9 D8 D7 D6 D5 D4 D3 D2 ] ADC_DRL: [ D1 D0 - - - - - - ]读取顺序先读 DRH再读 DRL。左对齐的好处是如果你只需要 8 位精度直接读 DRH 就完事了DRL 可以不看。注意: ALIGN 位只影响 ADC_DRH/ADC_DRL 的读取顺序不影响缓冲寄存器 ADC_DBxRH/ADC_DBxRL 的行为。6. 模拟看门狗 — ADC1 的隐藏技能说实话这个功能我用得不多但了解一下没坏处。模拟看门狗的原理很简单你设一个高阈值和一个低阈值如果 ADC 转换结果跑到了阈值外面就触发 AWD 标志如果开了中断还会触发中断。高阈值 (ADC_HTR) ┌──────────────────┐ 超限 │ 危险区域 (高) │ ← AWD 报警 ├──────────────────┤ 正常 │ 安全区 │ ← 一切正常 ├──────────────────┤ 超限 │ 危险区域 (低) │ ← AWD 报警 └──────────────────┘ 低阈值 (ADC_LTR)在不同模式下看门狗的行为不同单次/非缓冲连续模式默认使能直接监测 ADC_DR扫描模式通过 AWENx 位选择性使能某些通道的看门狗缓冲连续模式通过 AWENx 位选择性使能某些缓冲区的看门狗7. 外部触发 — 让定时器来控制采样节奏如果你的采样需要精确的定时可以用外部触发。有两种触发源TIM1 的 TRGO 事件内部定时器ADC_ETR 引脚的外部信号通过 CR2 的 EXTSEL[1:0] 选择EXTTRIG 位使能。重要提醒: 执行 HALT 指令前必须先禁用外部触发EXTTRIG0否则从 Halt 唤醒后 ADC 的行为不可预测。8. 寄存器速查手册这部分我把所有 ADC 寄存器整理在了一起方便开发时快速查阅。ADC_CSR — 控制/状态寄存器 (0x20, 复位值 0x00)位7 位6 位5 位4 位3 位2 位1 位0 EOC AWD EOCIE AWDIE CH3 CH2 CH1 CH0 rc_w0 rc_w0 rw rw rw rw rw rw位名称说明7EOC转换结束。硬件置1软件写0清。这是你最常打交道的标志位6AWD看门狗越限标志仅ADC15EOCIEEOC 中断使能。开了之后每次转换完都会进中断4AWDIE看门狗中断使能仅ADC13:0CH[3:0]通道选择。扫描模式下设的是最后一个通道号ADC_CR1 — 配置寄存器1 (0x21, 复位值 0x00)位7 位6 位5 位4 位3 位2 位1 位0 Res SP2 SP1 SP0 Res Res CONT ADON r rw rw rw r r rw rw位名称说明6:4SPSEL[2:0]预分频。/2, /3, /4, /6, /8, /10, /12, /181CONT0单次, 1连续。扫描模式下配合 SCAN 使用0ADONADC 开关和启动触发。首次写唤醒再次写启动ADC_CR2 — 配置寄存器2 (0x22, 复位值 0x00)位7 位6 位5 位4 位3 位2 位1 位0 Res EXTTRIG EXTSEL1 EXTSEL0 ALIGN Res SCAN Res r rw rw rw rw r rw r位名称说明6EXTTRIG外部触发使能5:4EXTSEL[1:0]00TIM1 TRGO, 01ADC_ETR3ALIGN0左对齐, 1右对齐1SCAN扫描模式使能仅ADC1有ADC_CR3 — 配置寄存器3 (0x23, 复位值 0x00, 仅ADC1)位7 位6 位5~位0 DBUF OVR Reserved rw rc_w0 r位名称说明7DBUF数据缓冲使能。开了之后结果存 DBxR 而不是 DR6OVR溢出标志。缓冲区数据被覆盖前没读就走这个位数据寄存器寄存器偏移说明ADC_DRH0x24数据高字节复位值不定ADC_DRL0x25数据低字节复位值不定ADC_DBxRH0x002n缓冲区高字节n0…9仅ADC1ADC_DBxRL0x012n缓冲区低字节其他功能寄存器偏移寄存器说明0x26ADC_TDRHSchmitt 触发器禁用 [15:8]设1禁用省电0x27ADC_TDRLSchmitt 触发器禁用 [7:0]0x28ADC_HTRH看门狗高阈值 MSB [9:2]复位值 0xFF0x29ADC_HTRL看门狗高阈值 LSB [1:0]复位值 0x030x2AADC_LTRH看门狗低阈值 MSB [9:2]0x2BADC_LTRL看门狗低阈值 LSB [1:0]0x2CADC_AWSRH看门狗状态 [9:8]仅ADC10x2DADC_AWSRL看门狗状态 [7:0]仅ADC10x2EADC_AWCRH看门狗使能 [9:8]仅ADC10x2FADC_AWCRL看门狗使能 [7:0]仅ADC19. 寄存器映射总图ADC1 完整映射偏移寄存器位7位6位5位4位3位2位1位0复位值0x00DB0RH------D9D80x000x01DB0RLD7D6D5D4D3D2D1D00x000x02~0x0DReserved0x0EDB7RH------D9D80x000x0FDB7RLD7D6D5D4D3D2D1D00x000x10DB8RH*------D9D80x000x11DB8RL*D7D6D5D4D3D2D1D00x000x12DB9RH*------D9D80x000x13DB9RL*D7D6D5D4D3D2D1D00x000x14~0x1FReserved0x20CSREOCAWDEOCIEAWDIECH3CH2CH1CH00x000x21CR1-SP2SP1SP0--CONTADON0x000x22CR2-EXTTRIGEXTSEL1EXTSEL0ALIGN-SCAN-0x000x23CR3DBUFOVR------0x000x24DRH------D9D80xXX0x25DRLD7D6D5D4D3D2D1D00xXX0x26TDRHTD15TD14TD13TD12TD11TD10TD9TD80x000x27TDRLTD7TD6TD5TD4TD3TD2TD1TD00x000x28HTRHHT9HT8HT7HT6HT5HT4HT3HT20xFF0x29HTRL------HT1HT00x030x2ALTRHLT9LT8LT7LT6LT5LT4LT3LT20x000x2BLTRL------LT1LT00x000x2CAWSRH------AWS9AWS80x000x2DAWSRLAWS7AWS6AWS5AWS4AWS3AWS2AWS1AWS00x000x2EAWCRH------AWEN9AWEN80x000x2FAWCRLAWEN7AWEN6AWEN5AWEN4AWEN3AWEN2AWEN1AWEN00x00*仅缓冲区大小为 10 的型号8 缓冲区型号为保留ADC2 映射ADC2 比较精简只有 CSR、CR1无 SCAN、CR2无 SCAN、CR3、DRH/DRL 和 TDRH/TDRL。没有数据缓冲寄存器没有看门狗。10. 我项目里的代码是怎么写的项目路径:bsp/src/bsp_ADC.c用 ADC1 的单次扫描模式采集 PB0-PB5AIN0-AIN5共 6 路反馈。初始化voidADC_Config(void){ADC1-CR10x00;// CONT0, SPSEL000 (fMASTER/2)ADC1-CR2ADC1_CR2_ALIGN|ADC1_CR2_SCAN;// 右对齐 扫描ADC1-CR3ADC1_CR3_DBUF;// 使能数据缓冲// 禁用 Schmitt 触发器省电 降噪声ADC1-TDRH0x3F;// ch8-ch13ADC1-TDRL0x00;// 扫描范围: AIN0 到 AIN5ADC1-CSR~0x0F;ADC1-CSR|adc_channels[ADC_CH_COUNT-1];// CH[3:0] 5// 首次 ADON1: 唤醒 ADCADC1-CR1|ADC1_CR1_ADON;}这里我初始化时把 TDRH 设了 0x3F禁用 ch8-ch13 的 Schmitt 触发器虽然我实际用的是 ch0-ch5。这是因为项目早期规划过用 ch8-ch13后来改了但这段代码留着也无害。如果你只用 ch0-ch5应该设 TDRL 0x3F禁用 ch0-ch5才对能更有效地降低噪声。启动扫描voidADC_Start_Scan(void){ADC1-CSR~ADC1_CSR_EOC;// 清 EOCADC1-CR1|ADC1_CR1_ADON;// 第二次 ADON1 触发扫描}两行代码很清晰。不过要注意这里清 EOC 用了位操作——在单次扫描模式下这是安全的因为扫描已经完成了EOC1 时才调用CH[3:0] 的值就是终点通道号读回来写回去没影响。但如果你在连续扫描模式的中断服务函数里清 EOC就不能这么写了。读取结果// 通道 0-9: 从缓冲寄存器读右对齐先低后高drlADC1-DB0RL;drhADC1-DB0RH;result(drh8)|drl;// 拼成 10 位对通道号 10 的回退处理缓冲寄存器只有 8~10 个DB0R ~ DB7R 或 DB9R所以如果通道号 ≥ 10 就没有对应的缓冲寄存器。代码里做了个回退临时关掉扫描模式用单次转换模式单独读这个通道然后再恢复扫描模式。staticuint16_tADC_Read_Channel_Single(uint8_tchannel){ADC1-CR2~ADC1_CR2_SCAN;// 退出扫描ADC1-CSR~0x0F;ADC1-CSR|(channel0x0F);ADC1-CSR~ADC1_CSR_EOC;ADC1-CR1|ADC1_CR1_ADON;// 单次转换while(!(ADC1-CSRADC1_CSR_EOC));ADC1-CSR~ADC1_CSR_EOC;ADC1-CR2|ADC1_CR2_SCAN;// 恢复扫描drlADC1-DRL;drhADC1-DRH;return(drh8)|drl;}这个回退方案虽然有点暴力但对于偶尔需要读一个高通道号的场景够用了。11. 一张表搞定所有操作最后我把日常开发中最常用的操作整理成一张速查表想干嘛怎么做单通道偶尔读一次CONT0, SCAN0 → ADON 唤醒 → ADON 启动 → 等 EOC → 读 DR单通道不停读CONT1, SCAN0 → ADON 启动 → 随时读 DR会被覆盖单通道不停读存历史CONT1, SCAN0, DBUF1 → 等 EOC → 读 DBxR 全部多通道扫一遍CONT0, SCAN1 → CH[3:0]末通道 → ADON 启动 → 等 EOC → 读 DBxR多通道不停扫CONT1, SCAN1 → CH[3:0]末通道 → ADON 启动 → 周期性读 DBxR停止连续/连续扫描清 CONT 或 ADON0停止单次扫描清 ADON立即停读数据右对齐先读 DRL再读 DRH读数据左对齐先读 DRH再读 DRL清 EOC扫描模式不要用位操作直接赋值 CSR写在最后STM8S 的 ADC 设计其实挺有意思的——五种模式看起来多但它们之间的关系是层层递进的单次→连续→缓冲连续单次扫描→连续扫描。理解了这个递进关系选择模式就不是一件困难的事。如果让我给刚上手的人一个建议那就是先搞清楚你要采样几个通道、采样频率是多少然后对照那张模式选择表基本上一眼就能确定用哪个模式。至于寄存器常用的其实就那几个CSR通道和状态、CR1ADON/CONT/分频、CR2ALIGN/SCAN、CR3DBUF/OVR。其他都是看门狗和外部触发这种锦上添花的功能用到的时候再翻手册也来得及。希望这篇笔记对你有帮助。如果发现有错误或者有补充的欢迎交流。Winston Qu2026/05/31 于浙江工业大学

相关新闻