文号
947612
阅读
965
回复
6
收藏
0
所有教程由网友发布,仅供参考,请谨慎采纳。科创不对教程的科学性、准确性、可靠性负责。
低成本的RP2040产生高频时序脉冲
2025/11/11
IP:北京

最近要研制一个高速扫描系统,用于大脑神经元电压探针ASAP5高速成像,需要用到10MHz时序脉冲信号,首先想到单片机,比用数字电路芯片要灵活啊。比如RP2040就有强大的PIO,实现几十MHz的时序脉冲都不成问题。不过看起来PIO坑还是有点多,暂时还顾不过来。后来测试了Ardunio IDE下编程,直接写数字口,用标准的digitalWrite,只能达到1MHz多一点。

后来参考网上资料,尝试改用gpio_put写数字口,发现速度超快,数字口输出脉冲可达100MHz(使用for循环超过20MHz),如果设置超频时钟,频率能更高。该方案实现起来很简单,几乎不增加软件学习成本,RP2040价格可以低至几元一片,能灵活产生高达几十MHz的简单高频信号,或复杂一些的时序脉冲信号。

下面是一个例程,在Ardunio IDE里面直接编译上传就可以,单片机连电脑的说明见我以前的帖子。程序设置2口为中断口,每收到一个正脉冲就触发4、5、6口产生16.67MHz脉冲,数字口7的gpio_put是拿来耗时间调整频率的,也可以用其他代码来调延时。较长延时则可以用标准delayMicroseconds、sleep等函数。例程中使用了for循环,速度会降低,没有for循环的情况下直接反复设置高低电平才能达到最高速度,例程中串口显示是调试用的,真正使用时要关掉。

后期改进代码使用多通道设置函数,如gpio_init_mask、gpio_put_all,可以达到多通道信号完全同步。实测发现,脉冲时序精度还是很高的,没有明显跳动,但是中断响应需要1us时间,且有4-5ns的跳动。要实现对外部脉冲的高速响应,可能就要用到PIO编程了。


#define delay_8ns __asm__("nop");

#define delay_16ns delay_8ns delay_8ns


long i;


void gpio2_callback(uint gpio, uint32_t events)

{

Serial.println("Test");

for(i=0;i<10000000;i++)

{

gpio_put(4, 1);

gpio_put(5, 1);

gpio_put(6, 1);

gpio_put(7, 1);

//delay_8ns;

gpio_put(4, 0);

gpio_put(5, 0);

gpio_put(6, 0);

//gpio_put(7, 0);

//delay_8ns;

}

}


void setup() {

// put your setup code here, to run once:

Serial.begin(115200);

gpio_init(4);

gpio_set_dir(4, GPIO_OUT);

gpio_put(4, 0);

gpio_init(5);

gpio_set_dir(5, GPIO_OUT);

gpio_put(5, 0);

gpio_init(6);

gpio_set_dir(6, GPIO_OUT);

gpio_put(6, 0);

gpio_init(7);

gpio_set_dir(7, GPIO_OUT);

gpio_put(7, 0);

gpio_init(2);

gpio_set_dir(2, GPIO_IN);

gpio_set_function(2, GPIO_FUNC_SIO);

gpio_set_irq_enabled_with_callback(2, GPIO_IRQ_EDGE_RISE, true, &gpio2_callback);

sleep_ms(10);

}


void loop() {

// put your main code here, to run repeatedly:

}


优化了一下代码,多通道同步设置:

#define delay_8ns __asm__("nop");

#define delay_40ns delay_8ns delay_8ns delay_8ns delay_8ns delay_8ns

#define delay_80ns delay_40ns delay_40ns


long i;


void gpio2_callback(uint gpio, uint32_t events)

{

    gpio_put_all(255);      //all pins to 1: 11111111, can be 3(00000011) to set pin 0 and 1 to 1

    gpio_put_all(0);        //all pins to 0: 00000000

    gpio_put_all(255);      

    gpio_put_all(0);        

    delay_8ns;      

    gpio_put_all(16);       //set pin 4 to 1, others to 0: 00010000

    gpio_put_all(64);       //set pin 6 to 1, others to 0: 01000000

    gpio_put_all(16);      

    gpio_put_all(0);        //can add more circles


  for(i=0;i<1000;i++)       // for loop is slower

  {

    gpio_put_all(255);      

    gpio_put_all(0);        

  }

}


void setup() {

  gpio_init_mask (255);           //pin 0-7 init: 11111111

  gpio_set_dir_all_bits(255-4);   //pin 0-7 to GPIO_OUT: 11111111, -4 means pin 2 to GPIO_IN

  gpio_pull_down(2);

  gpio_set_irq_enabled_with_callback(2, GPIO_IRQ_EDGE_RISE, true, &gpio2_callback);

  sleep_ms(10);

}


void loop() {

}

[ 修改于11个月22天前 - 2025/11/12 17:00:35 ]
11个月23天前IP:广东
947621
1楼

如果对相位抖动要求高,不适合用MCU来做。

CPLD才是更合适的。

引用
评论
1
11个月23天前IP:四川
947622
2楼

时间间隔发生器有专门的产品,在科研中有着广泛的应用。

引用
评论
Tangshm作者
11个月22天前IP:北京
947623
3楼

产品化的共振扫描成像用FPGA,CPLD大概也行。这种共振扫描镜成像需要一些实时控制,它的扫描频率经常有一点漂移,要自动锁定行同步信号,奇偶行扫描也偶尔需要看着输出的图像来手动对齐一下,还要控制场扫描Y振镜,到达一定行数再控制Y振镜回扫,还是需要编程和通信的,比如奇偶行对齐可以用个旋钮加AD采集,也可以电脑上设置然后串口通信。单片机能实现就最简单了,要是不嫌麻烦的话FPGA肯定是最强了,万能钥匙!RP2040主循环空的情况下,实测相位是没什么抖动的,程序都放在中断调用函数里,行扫描线调头的空闲期间才运行。需要注意的是,多口输出的情况下相互是没有完全对齐的。

引用
评论
11个月20天前IP:广东
947658
4楼

RP2040的GPIO才是它的特色,这个有什么坑吗?

引用
评论
Tangshm作者
11个月20天前IP:北京
947665
5楼

不算是坑吧,主要是学习成本,还没来得及认真学这个。PIO需要专门编程,用MicroPython好像最容易,GPIO则是指的我现在用的这个,可直接Ardunio下使用代码

引用
评论(3)
11个月19天前修改于 11个月19天前IP:广东
947684
6楼

sio hw->gpio out = xxxx;

这个指令我记得是单周期的。即使不是,也是极快的。

而且,SIO是用的内核时钟,不是IOB时钟,指令跑多快就更新多快,不拖后腿。

熟悉PIO,并且要求更高的话还是用PIO,写个几行小汇编就搞定,ChatGPT能帮你不少忙。有两大优势:与FPGA/CPLD一样的节拍级定时精度;与主频同样的频率,轻松200M。

引用
评论
笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的