项目需求
与外部触发信号同步的多通道单脉冲信号发生器:
- 1个输入源,8个输出源
- 8个通道的输出延迟时间,需要能够单独分别调整,调整范围为100ns-1ms,调整分辨率为10ns
- 输入信号的单次脉宽测试范围为:10us-500ms
- 示意图:
需求分析
- 同步脉冲延迟时间的调整精度为10ns,即频率为100MHz,常规GPIO方法难以满足,需要使用PIO。当PIO状态机频率设定为100MHz时,一条PIO汇编指令的时长为10ns,因此只需调整指令执行次数即可达到对脉冲延时精确控制的目的。
- 题目要求输出8路,考虑到RP2040共有两个PIO模块(PIO0、PIO1),且每个PIO模块有4个独立的状态机,正好满足8个输出端口输出延迟时间能够单独分别调整的需求。
- 人机交互方面,在LCD上显示的引脚示意图通过形象的字符组成,并利用彩色(颜色对应于端口导线颜色)显示增强可读性。
实现方式及代码说明
一、LCD显示
屏幕显示驱动部分使用板卡自带的st7789.py,以下对各部分进行说明:
- 彩色显示:屏幕采用565编码方式,可通过st7789驱动程序中的“color565”函数将红色,绿色和蓝色值(0~255)转换为16位565编码(在屏幕显示中加入彩色的目的是用颜色标记对应端口):
st7789.color565(225,0,255) # 紫色
- 显示逻辑:在本项目中,被选中的参数使用反显(白底黑字,正常显示情况为黑底白字)突出光标位置,而可选位置通过“x“和”y“存储,当移动光标时,原先的光标位置通过”x_old“和”y_old“储存,恢复为正常显示状态,而新光标位置进行反显,大致逻辑如下:
x_old = x_pos
y_old = y_pos
display.text(font1, str((delays[y_pos] // 10**(5-x_pos)) % 10), x[x_pos], y[y_pos], BLACK, WHITE)
display.text(font1, str((delays[y_old] // 10**(5-x_old)) % 10), x[x_old], y[y_old], WHITE, BLACK)
二、按键交互
本项目按键交互采用中断方式实现。当选中了一个参数(某通道延时时间的某一位),对应位置会被高亮,此时按下左上方的按钮(A按钮),对应位数的数字加一;按下中间上方的按钮(B按钮),对应位数的数字减一;左右拨动右上方的按钮可在同一行左右移动,而居中按下可切换到下一个输出通道。同时,选中不同通道时,被选中的通道会在示意图中以叉表现出来(往右拨动按钮的代码逻辑示意如下,其他按钮对应代码的结构相同)。
if(x_pos == 5):
x_pos = 0
else:
x_pos = x_pos + 1
三、PIO实现
由PIO生成与外部触发信号同步的单脉冲信号主要由以下三个部分组成:
1、PIO汇编程序
完整PIO汇编程序如下:
## PIO程序
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def pulse_gen():
wrap_target()
wait(1, gpio, 20) # 等待GPIO20上升沿
mov(x, osr) # 加载延迟参数
label("delay_loop")
jmp(x_dec, "delay_loop") # 延时循环
set(pins, 1) [9] # 输出10ns脉冲
set(pins, 0) # 脉冲结束
wait(0, gpio, 20) # 结束流程,等待下一个上升沿
wrap()
首先,等待GPIO20端口(即CH0)给出输入脉冲的上升沿:
wait(1, gpio, 20) # 等待GPIO20上升沿
接下来将OSR中的延时时长数据移动到X寄存器中(注意,这是一个复制过程,OSR中的数据不会消失):
mov(x, osr) # 加载延迟参数
进入延时——通过”jmp“指令循环执行跳过预定的指令周期,”x_dec“表示对X寄存器中的数据自减:
jmp(x_dec, "delay_loop") # 延时循环
最后将对应引脚拉高后再拉低,此处设定脉冲宽度为10ns,并等待低电平以结束整个流程:
set(pins, 1) [9] # 输出10ns脉冲
set(pins, 0) # 脉冲结束
wait(0, gpio, 20) # 结束流程,等待下一个上升沿
其中,”wait(1, gpio, 20)“指令实际上只是等待输入信号的高电平,需要配合最后的”wait(0, gpio, 20)”以达到效果上检测上升沿的目的。
2、状态机的初始化
完整代码如下:
# 初始化状态机
state_machines = []
for i, pin in enumerate(OUTPUT_PINS):
pio = rp2.PIO(0 if i < 4 else 1)
sm = pio.state_machine(
i % 4, # 分配PIO
pulse_gen,
freq=100_000_000, # 频率为100MHz
set_base=Pin(pin),
in_base=Pin(20) # 设置输入基准为GPIO20
)
sm.active(1)
state_machines.append(sm) # 便于sm管理
将2×4个状态机分配到八个输出端口,并利用Python的一些语法特性使管理便捷。
3、refill_fifo函数:更改延时
每次更改延时时间时,调用此函数,将新的时间放入FIFO并拉取:
# FIFO管理
def refill_fifo():
for i, sm in enumerate(state_machines):
#while sm.tx_fifo() == 0:
delaytime = delays[i] - 6
sm.put(delaytime)
sm.exec("pull()")
功能框图
如图所示:
效果展示
在本项目中,每个“CH”右侧是对应通道的延时,通过按键调整延时,通过另一块RP2040配合PulseView采集波形:
题目要求输入信号的单次脉宽测试范围为10us-500ms,故选用10us、1ms、500ms三种脉宽的输入信号进行测试,输出结果如下,可得本项目在各脉宽都能正常工作:
10us情况(第一个通道为输入,其余为输出):
1ms情况:
500ms情况(由于逻辑分析仪采样频率调低,适当增加了输出通道的脉宽):
延时时间可随意调整:
代码附件
见文末附件处(含所使用Micropython固件版本和代码工程文件)。
参考资料
- RaspberryPi官方MicroPython文档
- RaspberryPi官方数据手册
- 硬禾学堂板卡介绍+题目解析直播
- DigiKey:Shawn的RP2040教程
- Github上的官方MicroPython例程包
- 2024年寒假练 - PWM发生器