这是选自Hackaday上的一个项目:RPScope:100 MSps 2ch oscilloscope based on Raspberry Pi Pico
这个项目是一个基于RP2040控制器的双通道示波器,采用了一个100Msps的ADC,一个480*320 IPS TFT显示屏以及一个双通道100MHz带宽的前端。
软件使用MicroPython以及LVGL编程,并使用了DMA和PIO以获得最高的吞吐率。
RP2040是一个双核Arm Cortex M0+的微控制器,并有被称之为PIO的可编程输入输出端口。
方案框图
从这个框图中可以看出构成示波器的所有主要元素:
- 电源由USB端口供电,数字和模拟部分分别稳压。
- 每级模拟电路由3个运放、一个增益选择以及一个用于直流偏移的DAC。交直流的切换是通过一个跳线手动操作的(在产品中一般都使用继电器)
- 数据采集部分由复用器、ADC以及触发电路构成,都由PIO来控制
- 3颗DAC都是通过I2C总线来配置,LCD的触摸控制器以及IO扩展器也都挂在这个I2C总线上
- LCD也是通过PIO控制的
原理图
电源部分的电路
电源部分从USB的5V供电中得到3组电压:
- 3V3用于所有的数字电路.
- 对称的3V3A用于所有的模拟电路
- LM27762是一颗带有LDO的双路电荷泵变换器,给模拟电路部分提供+、-供电电压.
控制器部分的电路
这一块包含了ADC,CPU,以及LCD.
所有的MCU的管脚都用上了:
- 8个管脚用于ADC总线
- 3个管脚用于MUX, ADC和TRIG控制.
- 8个管脚用于LCD总线
- 5个管脚用于LCD控制.
- 2个管脚用于I2C总线.
- 1个管脚用于LCD和TSC的复位.
模拟前端电路
模拟输入级由一些无源器件、三个运放、一个模拟开关以及一个DAC构成
输入阻抗为电阻R24+R26, 值为780KOhm.
AC滤波器(由C27设置)的截止频率为20 Hz.
这级的增益为(100/(680+100)) = 0.12.运放由±3.3V供电, 最大输入电压为±27V.
使用TI的仿真软件进行的仿真,带宽超过100MHz,在整个频率范围内增益基本为常数。
其它模拟部分的电路
基准电压为2.048V的稳压二极管接到一个运放上配置为射极输出。
触发是通过一个8位的DAChe一个比较器来实现,TLV3501是一个高速比较器,延迟时间为4nS.
模拟开关由MCU控制。
PCB的元器件布局
相同功能的元器件组在一起,从左到右分布,左侧为模拟部分,右侧为数字部分,电源和其它模拟电路在电路板顶层的上面部分。每一个部分的电路都是在顶层走线,通过过孔连接到电源平面,并通过底层走线连接到其它部分的电路。
布线
模拟供电部分要尽可能干净,所有的网络上的连线要尽可能短,尤其是反馈网络,走线要尽可能宽,尤其是电源走线。从USB 5V来的电源在电路板的上面部分,±3V3走向下面的部分。
模拟电源部分的布线
处于模拟和数值之间的ADC,它的左侧为模拟部分,右侧为数字部分。模拟部分由电源管脚、ADC输入以及基准构成,数字部分由数据及控制管脚构成。在模拟部分要优先对电源管脚进行布线,其次是信号管脚 - 通过底层进行连接。.
ADC部分的布线
模拟前端有5个器件组成,信号从左到右,电源树状分布,模拟开关的控制线以及DAC的I2C线通过底层连接
模拟前端部分的布线
由于每一个区域都是在顶层走线,电源管脚非常容易通过过孔连接到电源平面,不同区域之间的走线也比较容易。.
内部各个区域的布线
电源平面
电源平面也需要仔细分割,主要的原则 - 尽可能将所有的过孔通过比较大的铜区域覆盖。
地平面
地平面的原则相同。
LD总线的走线
LCD是通过8位的8080总线连接,能够达到20MHz的速率,这些信号可以在顶层和底层走线,但要尽可能短。
软件部分:
软件部分包括三层:
- HAL,包含了针对板上每一个器件的python类.
- GUI, 它由一些 LVGL 驱动程序和一些提供类似“亲爱的 pygui”API 的高层组成。
- Scope, 它由对象的状态组成并允许获取过程。
HAL中最简单的元素之一是 DAC。 它仅使用一个 I2C 端口和一个 GPIO。 要设置输出值,此类只需要对 I2C 中的特定地址执行一次写入。 板上有 3 个 DAC,都连接到同一个 I2C。 DAC 使用数字输入引脚来设置自己的地址,这用作仅选择一个 DAC 所需的启用引脚。 由于 RP2040 没有足够的引脚,A0 引脚连接到 IO_EXP,软件处理它以访问所需的 DAC。
DAC class
输入输出扩展器通过 I2C 总线控制并处理上述 3 个 DAC A0 引脚和模拟开关。 该类提供了配置每个引脚的方法(但在本项目中所有引脚都应输出)以及设置值的方法。
IO扩展类
TFT 是电路板中第二复杂的器件,仅次于 CPU。 LCD 支持不同的接口,但本硬件通过M0:2 引脚将其设置为8 位8080 接口。
该接口有 8 位数据和 4 位控制信号。 LCD 初始化需要设置大约 20 个寄存器的值来为 LCD 工作做准备。 完成后,只需要 2 个操作:1 设置窗口和 2,发送数据。 这是使用 LCD 的最佳方式。 软件在缓冲区中绘制图像,然后将缓冲区传输到 LCD。 由于 LCD 具有其定义的工作区域,因此可以使用 DMA 轻松快速地完成完整的数据传输。
当前版本仅支持之前使用SPI的原型版本,未来有望构建使用PIO的Intel 8bit 8080接口。
TFT类
ADC类
ADC08100 使用 8 个数据信号和一个时钟信号,全部通过 PIO 处理。 用户设置缓冲区地址、大小、采样率和采样计数,然后 PIO+DMA 进行捕获。 捕获的样本数用于配置 DMA,采样率直接来自 CPU 时钟,注意样本数可以大于缓冲区大小,DMA 将以循环缓冲区的方式存储数据。 PIO 程序只有两条指令。 1 读取输入引脚和 2,将它们推送到 DMA。 由于 PIO 端的“sideset”指令,与之前的指令同时生成 ADC 时钟,因此它可以在 ADC 输出可用的同一时刻读取。
一些关于 DMA 循环缓冲区对齐和 micropython 缓冲区对齐的注释。 DMA periferials 只能在 2s 补码对齐缓冲区中执行循环模式,这意味着如果您的缓冲区有 256 字节,它应该放在地址 0xXXXXXX00 到 0xXXXXXXFF 中。 由于 micropython 不允许在特定地址分配缓冲区,绕过此限制的唯一方法是分配一个更大的缓冲区,该缓冲区肯定具有 00-FF 范围,然后使用它。
在需要两个通道的情况下,应在转换期间管理多路复用器。 这种管理可以通过 ADC PIO 程序完成。 当我能够进行硬件测试时,我发现这个多路复用器不适合这个设计的目的。 多路复用器有一个稳定时间,它会在改变通道时发生,这会阻止切换到 50MHz,因此如果同时使用两个通道,ADC 将无法在最大频率下工作。 这是当前原型的局限性,也是未来需要改进的地方。
通道复用
触发器基于 DAC 和比较器。 我个人从来没有意识到运算放大器和比较器之间的区别。 区别在于速度,当ADC饱和需要恢复到线性状态时,它有一个限制参数叫做Overload Recovery Time,量级为100nS。 比较器从不在饱和区工作,所以它们根本没有这种影响。 还有一个必须满足某些要求的传播延迟,但 TLV3501 可以轻松检测到 200MHz 的信号。 DAC 模块用于生成阈值。 请注意,此 DAC 由数字域供电,这可能会在未来版本中更改。
触发
触发 PIO 程序使用了一种特殊的操作技巧。 用户可以对 PRE 和 POST 样本计数进行编程,然后停止 ADC PIO。 预触发样本保留触发事件之前的数据。 这样做是因为 ADC 数据存储在一个连续的循环缓冲区中。 检测到触发后,计数器递减以存储触发后样本,然后将一个简单值推送到 DMA。 诀窍在于,该 DMA 配置为将其值写入 ADC 使用的 DMA 的控制寄存器,从而停止它。 这个简单的技巧让整机ADC+TRIGGER无需任何CPU干预。
触摸屏控制
TSC 是一个简单的 I2C 设备,可以处理电阻式触摸屏。 它有一些寄存器来读取 X 和 Y 坐标的原始 ADC 值。 该类还对这些值进行平均,并通过校准值将它们转换为像素坐标。
DMA
DMA 类用于 ADC、触发器和 LCD 类。 负责 DMA 配置和使用,是 DMA 控制寄存器的简化接口。
使用非常简单:配置、启用、等待和禁用。 这个简单的过程允许以最大时钟速度进行内存到内存或外设从/到内存的传输。
示波器
作用域类实现了所有作用域的内部结构,它在屏幕中的表示也处理了整个采集过程(所有都隔离在一个函数中)。
初始操作是定义用户界面。 这是通过创建一个由行和列组织的树来完成的,其中添加了一些元素,如按钮和标签。 此 GUI 构建方法源自 dearpygui,并作为一项额外功能添加到 LVGL 中以简化应用程序设计。
GUI 在类的 build_ui 方法中定义,行为通过添加到 GUI 的回调定义。 它们中的大多数只是简单地存储用户值(请注意,屏幕上的每个按钮几乎都有一个回调)。 然后 cb_run 回调配置 ADC 并执行采集。
显示驱动
显示驱动程序是 LVGL 和我们的 LCD 之间的粘合剂。 它初始化 LVGL 库,设置其绘图缓冲区并定义显示刷新回调和触摸屏读取回调。
LVGL 使用绘图缓冲区来绘制 GUI 的特定部分。 绘制完成后,它会调用其显示刷新回调以将此缓冲区绘制(发送)到 LCD。 回调参数是绘图坐标和缓冲区地址,正是我们使用DMA绘制LCD所需要的参数。 如果使用双缓冲区,我们可以互换缓冲区,这样 DMA 和 CPU 每次都交换缓冲区。
输入读取回调由 LVGL 定期调用。 它应该读取触摸屏并简单地将状态(按下/未按下)和 X/Y 坐标返回给 LVGL 库。
DearPyGUI
DearPygui 是一个 python 库,允许使用上下文管理器定义 GUI,此功能简化了 GUI 设计。 这就是为什么我创建了一个简单的模块来为 LVGL 提供此功能。 该模块定义了支持上下文管理的行和列类,以构建布局对齐的小部件树。
按照这个你可以自己来试试: LVGL/MicroPython仿真器
捕获块当用户按下“运行”按钮时执行捕获。 它是使用前面章节中评论的 ADC 和触发器对象执行的。 配置完成后,启动ADC,然后触发。 然后将触发读取功能状态,即等待 DMA 完成,即在 PIO 循环内等待检测上升/下降事件。 发生这种情况时,采集会稍等片刻以填充缓冲区,然后停止,然后使用数据更新 LVGL 小部件图表。
重复采样
重复采样是一项很酷的功能,如果您的前端具有宽带宽,则可以轻松实现。
发生器设置为 1MHz 周期的 100nS 脉冲。
左下方:100MSps 的 RPScope,信号“实时”显示。右边:在奈奎斯特频率下采样我们可以玩这个“放大效果”。 采样频率为 1MHz,但生成的信号移动了几 KHz,因此 100nS 脉冲现在为 200 像素宽(并反转)。 这相当于 2GSps。