ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CH32V307 RISC-V MCU开源示波器设计:从ADC采集到屏幕显示

CH32V307 RISC-V MCU开源示波器设计:从ADC采集到屏幕显示 CH32V307 开源示波器是一个在嵌入式开发圈里反复出现过的项目方向用一颗国产 RISC-V 内核 MCU 作为采集核心配合模拟前端、DMA、定时器和屏幕组成一台能够观察方波、正弦波甚至简单总线波形的便携示波器。CH32V307 自带较完整的外设资源尤其 ADC、DMA 和丰富定时器的组合使它很适合用来做“从模拟输入到数字显示”的完整练习。真正动手前容易忽略的一点是示波器并不是“屏幕能画出线”就完成了。输入信号能不能安全进入 ADC、能不能被稳定采样、触发点选在哪一帧、时基变化时怎么抽取数据点这些环节拆开之后才是项目的核心工作量。下面按从模拟到数字、从硬件到固件、从学习到生产的顺序把 CH32V307 开源示波器的实现链路完整梳理一遍。1. CH32V307 示波器项目要解决的三个核心问题1.1 示波器本质是一条信息处理链路示波器项目表面上是一台仪表实际上是连续处理一条数据链路被测信号经过前端衰减或放大进入 MCU 的 ADC 引脚ADC 按固定节奏采样DMA 把结果搬运到内存固件再从缓冲区里寻找触发点、完成电压到屏幕坐标的换算最后由屏幕把点阵画出来。如果把整条链路拆开可以表示为被测信号 - 前端保护与阻抗变换 - 幅度调节或钳位 - CH32V307 ADC 引脚 - ADC 采样数据 - DMA 内存缓冲 - 触发判断与坐标换算 - LCD/OLED 屏幕显示这个链条里真正影响结果的因素有很多前端电阻分压比例是不是准确、运放带宽够不够、ADC 采样时钟是不是稳定、DMA 缓冲区长度够不够、触发阈值和边沿选择是否正确、屏幕刷新时会不会中断采样。任何一个环节出问题最终显示出来的波形都会失真。1.2 CH32V307 在这些环节里能承担什么CH32V307 属于 RISC-V 内核的 MCU 产品线开发时既能使用官方 EVT 示例包也可以用常见 IDE 配合 WCH-Link 直接下载调试。相比单独使用外置高速 ADC 芯片的方案它把转换器、DMA、定时器、SPI 屏幕接口放在一颗芯片里项目入门门槛明显更低。对示波器场景来说需要重点确认的 MCU 资源可以简化为这张表MCU 资源在示波器项目里的作用项目落地前要确认的内容ADC对模拟信号做量化分辨率、采样率上限、输入电压范围、参考电压选择DMA在不占用 CPU 的情况下持续搬运 ADC 结果是否存在循环模式、目标缓冲区能否覆盖需要的点数定时器产生 ADC 触发节奏也用于计算波形频率或脉冲宽度能否输出与 ADC 匹配的触发源SRAM保存采样缓冲区和 UI 缓冲缓冲区长度与点阵显存的折中SPI/LCD 控制器显示波形和参数屏幕刷新速度是否拖累采集任务GPIO 中断读取按键和编码器去抖和中断优先级是否合理不同 EVT 版本、不同评估板上某些外设引脚有差异所以实际开发时不要只看型号要以开发板原理图和实验包里的头文件为准。1.3 开源示波器的硬件与软件模块拆分一个可维护的开源示波器项目通常不会把所有电路堆在一张原理图里而是分成几个边界清晰的部分电源部分负责将 USB 或电池电压转换为 MCU 需要的 3.3V并给模拟前端提供低噪声电源。模拟输入部分处理探头进来的信号包含分压、跟随、幅度切换、直流偏置和过压保护。数字采集部分CH32V307 的 ADC、DMA 和触发逻辑。人机交互部分屏幕、按键、旋转编码器。可选的上位机链路通过串口或 USB 把采样数据发给上位机显示也可以保留为调试通道。对于学习项目建议先按固定量程、固定触发阈值跑通“输入正弦波 - 屏幕出现一条正弦波”然后再逐步加入档位切换、触发设置和频率显示。不要一开始就追求完整示波器菜单否则很容易卡在人机交互而忽略了采样链路本身的正确性。2. 先研究模拟前端设计再谈采样率2.1 ADC 无法接受任意电压信号很多人在入门阶段会直接把信号发生器输出接到 CH32V307 的 ADC 引脚上。信号源内部如果只输出 0 到 3.3V确实可以工作但真实测量场景中被测信号可能高于 3.3V也可能带负半周。若外部信号直接灌进引脚轻则采样结果被钳位重则损坏芯片。设计模拟前端时至少要考虑三点过压保护。在被测信号超过量程时不能让过高电压进入 ADC 引脚。常见做法是在输入端串联保护电阻并用二极管或肖特基二极管把电压钳位在电源和地之间。分压和放大。ADC 的满量程对应芯片供电或 VREF。假如供电为 3.3V当输入信号是 0 到 10V 时必须先按比例衰减到 3.3V 以内。参考电压的稳定性。ADC 的转换结果是与参考电压比较得到的如果参考电压本身抖动波形数据就会上下波动。噪声明显时要给参考引脚加足够旁路电容并避免和数字大电流走线共用电源。用 12 位 ADC 且参考电压为 3.3V 时理想换算关系为code round(Vin / Vref * 4095)反过来计算实际输入电压Vin_mV code * 3300 / 4095如果前端带放大或衰减还要再乘回前端的增益或衰减系数。示例代码只是为了说明换算思路实际项目中的 VREF 电压值应以万用表测量为准。2.2 前端必须做阻抗变换示波器测量要求对被测电路的影响尽量小通常希望输入阻抗足够高。MCU 的 ADC 内部是采样电容结构每次转换都会从外部吸收电荷。如果外部信号源内阻很大采样电容没有被充分充电转换结果就会偏低或不稳定。因此模拟前端不只是一串电阻还应该在分压之后加一级运放缓冲。最简单的结构是运放电压跟随器输入信号经过分压后进入运放正输入端输出端直接反馈到反相输入端得到一个低阻输出再去驱动 ADC 引脚。运放选型时要关注几个参数参数为什么重要供电电压范围是否支持 3.3V 单电源输出电压摆幅是否能接近轨到轨否则量程两端出现死区单位增益带宽决定示波器能做多高的模拟带宽输入偏置电流过大偏置电流会在高阻分压网络上产生额外误差失调电压失调会导致屏幕上的波形相对于零线整体偏移如果只需要观察低频方波和正弦波对带宽要求较低如果要观察 MHz 级以上信号普通通用运放带宽可能不够需要额外考虑增益带宽积和摆率。2.3 量程切换和直流偏置如何设计入门方案先做一档固定量程即可例如测量 0 到 3.3V 的信号。更接近真实示波器的项目通常会设计多档量程通过机械开关或模拟开关切换分压网络。量程切换需要注意的问题比较多分压网络使用高阻值大电阻时电阻本身的寄生电容和运放输入电容会构成低通滤波让高频方波边沿变圆。模拟开关也有导通电阻切换时可能引入非线性。每档量程都应在 PCB 上预留补偿电容位置用实际方波校准过冲和上升沿。示波器还需要处理正负信号或交流耦合。简单方法是在前端加入直流偏置把信号抬到 ADC 量程中间。加入隔直电容可以实现交流耦合但隔直电容和输入网络的组合会形成高通滤波器最低只能观察到某个频率以上的信号低频方波会出现平顶跌落。2.4 前端最容易出现的问题下表列出了前端设计里最常见的几类问题问题现象可能原因处理建议输入电压较高时芯片发热或引脚损坏缺少限流电阻或钳位电路增加串阻并把信号钳位在电源与地之间方波边沿变圆高频幅度下降分压电阻与寄生电容形成 RC 低通降低分压电阻值预留补偿电容波形始终在零线附近波动运放不满足轨到轨输出或输入没有偏置到量程中段检查供电电压与输出摆幅加入合适直流偏置垂直方向噪声大ADC 参考电压噪声、地环路或示波器共地问题用稳压源给参考供电缩短接地线并保证良好接地切换量程后幅度跳变明显分压电阻实际阻值误差偏大使用 0.1% 精度电阻并在固件里做分段校准这里要特别提醒DIY 示波器的测量安全边界依赖前端隔离和保护设计。如果被测对象是强电或市电普通 MCU 示波器没有隔离设计不建议在没有完整隔离方案和足够经验的情况下直接测量安全风险很高。3. 采样、DMA 和触发把连续模拟信号装进数组3.1 为什么要用 DMA 循环搬运采集功能的核心目标是“不漏点”。如果在中断里逐次读取 ADC 寄存器每次读数和存储之间都存在等待高采样率时很容易出现时间参数漂移。更合理的做法是让 ADC 转换完成后自动触发 DMA把结果写到固定数组再由 DMA 中断或标志通知应用层处理。DMA 工作模式建议设置为循环模式。这样 ADC 会连续采样DMA 不断向缓冲写数据。缓冲写满后自动回到起始位置整个过程一般不占用 CPU。应用层读取数据时要注意DMA 写入和应用层读取共享同一块内存必须在缓冲区前半段和后半段完整写入后分别处理避免读到“写了一半”的脏数据。3.2 一个 ADC 加 DMA 的初始化示例下面代码以 CH32V307 常见的标准外设库接口风格为例用于说明 ADC 和 DMA 的初始化结构。不同 EVT 版本中函数名称、宏定义和 DMA 通道号可能存在差异拿到代码后要对照本机实际安装的头文件进行修正。#include ch32v30x.h #define SCOPE_BUF_LEN 1024 static uint16_t s_adc_buf[SCOPE_BUF_LEN]; static volatile uint8_t s_tc_flag 0; static volatile uint8_t s_ht_flag 0; static void Scope_Adc_Gpio_Init(void) { GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin GPIO_Pin_0; /* PA0 作为模拟输入 */ gpio.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, gpio); } static void Scope_Adc_Init(void) { ADC_InitTypeDef adc; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); adc.ADC_Mode ADC_Mode_Independent; adc.ADC_ScanConvMode DISABLE; /* 单通道采样 */ adc.ADC_ContinuousConvMode ENABLE; /* 配合 DMA 循环 */ adc.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; adc.ADC_DataAlign ADC_DataAlign_Right; adc.ADC_Channel ADC_Channel_0; ADC_Init(ADC1, adc); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }static void Scope_Dma_Init(void) { DMA_InitTypeDef dma; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); dma.DMA_PeripheralBaseAddr (uint32_t)ADC1-RDATAR; dma.DMA_MemoryBaseAddr (uint32_t)s_adc_buf; dma.DMA_DIR DMA_DIR_PeripheralSRC; dma.DMA_BufferSize SCOPE_BUF_LEN; dma.DMA_PeripheralInc DMA_PeripheralInc_Disable; dma.DMA_MemoryInc DMA_MemoryInc_Enable; dma.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; dma.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; dma.DMA_Mode DMA_Mode_Circular; dma.DMA_Priority DMA_Priority_High; dma.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel1, dma); DMA_ITConfig(DMA1_Channel1, DMA_IT_TC | DMA_IT_HT, ENABLE); DMA_Cmd(DMA1_Channel1, ENABLE); }初始化完成后中断里只需要把标志位置位不要在 DMA 中断中做屏幕刷新和大型计算void DMA1_Channel1_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TC1)) { DMA_ClearITPendingBit(DMA1_IT_TC1); s_tc_flag 1; } if (DMA_GetITStatus(DMA1_IT_HT1)) { DMA_ClearITPendingBit(DMA1_IT_HT1); s_ht_flag 1; } }TC表示整个缓冲写满HT表示写完一半。应用层可以先处理前一半数据再处理后一半数据这样缓冲区总长度不高时也能保证数据连续性。实际使用中要把DMA1_Channel1替换为当前工程实际使用的通道触发中断名也以库文件为准。3.3 触发到底在做什么如果只是把采样数据连续堆到缓冲区然后从数组第一个点开始显示波形会在屏幕上左右滚动难以形成稳定的观察效果。示波器的触发就是让每次刷新都从同一类信号特征开始绘制例如检测到“从低到高跨越阈值”的位置再从该位置前后截取固定点数显示。最简单可用的触发策略是软件边沿触发在缓冲区里寻找相邻点从小于阈值变成大于阈值的位置。该位置就是触发点把这个点作为显示窗口的中心或起点。int Scope_FindEdge(uint16_t *buf, uint32_t len, uint16_t threshold, int rising) { for (uint32_t i 1; i len; i) { if (rising buf[i - 1] threshold buf[i] threshold) { return (int)i; } if (!rising buf[i - 1] threshold buf[i] threshold) { return (int)i; } } return -1; }上升沿触发用于观察边沿突变下降沿触发适合观察负脉冲如果信号频率较低或占空比特殊还要考虑重新触发超时机制。软件触发实现简单但当采样率很高、缓冲区很大时CPU 要消耗不少时间扫描数组所以适合中等档位使用。要做几百 MHz 以上或高重复频率信号的稳定触发就需要在前端增加比较器或外部触发电路。理解触发时要注意一个常见误区触发不是“只保留触发点之后的波形”。为了看清信号触发前的状态缓冲区设计常常预留不少前置点数。需要显示触发前和触发后各多少点由触发点位置和显示窗口长度共同决定。3.4 缓冲区长度、采样率和时基的关系示波器屏幕的横轴代表时间。屏幕上每一格像素都要对应真实的采样间隔所以设置时基时需要明确当前要显示的完整时间窗口。假设采样率为Fs缓冲区长度为N一次采集覆盖的总时间约为T_total N / Fs假设屏幕上横向显示W个点每个像素对应的时间就是T_total / W。如果希望一次刷新看到 10ms 的信号并且横向 1000 个点则采样率可以设置为Fs 1000 / 10ms 100kHz用常见点数和窗口反推采样率大致可以参考下表显示时间窗口采样点数推荐采样率典型用途250us1024约 4Msps观察音频以上或较高频信号1ms1024约 1Msps观察几十 kHz 到百 kHz 信号10ms1024约 100ksps观察 kHz 级信号100ms1024约 10ksps观察慢变化信号1s1024约 1ksps观察低频或传感器输出上表中的采样率只是保证“屏幕点数有数据”不等于信号能够被真实还原。根据采样定理采样率至少要大于信号最高频率的两倍否则会出现混叠。更稳妥的做法是在前端加入抗混叠低通滤波或者在降采样前先做数字平均或抽取而不是每隔几个点简单跳采。真实采样率能否跑到目标值取决于芯片 ADC 时钟配置、采样时间参数和前端模拟带宽应该以实际测试为准。4. 显示、量程和时基之间如何换算4.1 屏幕不是直接画 ADC 原始值如果直接把s_adc_buf中的数值作为屏幕纵坐标虽然能显示波形趋势但量程变大或变小时波形幅度不会同步变化。正确做法是把 ADC 原始值先换算成电压再根据当前垂直档位换算成纵坐标。假设 ADC 是 12 位参考电压为 3.3V原始码值范围是
返回列表