FPGA实现索尼传感器subLVDS接口接收(以IMX178为例)

  • Post author:
  • Post category:所有文章
  • Post comments:0评论
  • Post last modified:2025年1月31日
  • Reading time:3 mins read

网上关于这方面的文章很少(好吧实际上是没有)。虽然很多厂商提供了IP核,但一来他们是黑盒设计,二来要钱,三来对FPGA芯片有限制。

Github上有个开源的subLVDS接收设计,但是没有文档,然后他用的Xilinx和我Lattice有区别。最后还是只能靠自己摸索了。

本文同步在我的B站专栏发布

理论

索尼subLVDS传输协议

emmm,这个从哪里开始说起好呢。这玩意你随便找个用了这个接口的传感器手册就行了,让我从最底层往高层说

从最小的层面来说,传感器使用DDR方式传输数据,也就是上升沿和下降沿都有数据发出。
传感器作为主机发出时钟。每经过10/12/14个时钟边沿为一个像素,对应10/12/14比特读出模式。

然后可以选择4/8/10通道并行传输,一起传输完一行数据。

接着是重点:每个通道在发送数据前都有帧起始标识和帧结束标识,被称为SAV和EAV。SAV和EAV被拆成4个包发出,每个包的长度根据传感器的模式而定,对应10/12/14比特的读出模式。
这样一来,SAV和EAV的总长度就有40/48/56比特三种。

同时,通过使用不同的SAV4和EAV4标记有效行和无效行:

这样一来,我们就可以知道一行像素什么时候开始,什么时候结束,以及该行是否有效。

FPGA的DDR接口实现原理

DDR的特点是在时钟的上升沿和下降沿都要对数据进行锁存,第一次接触时,天真的我以为只要在Verilog里写always @(posedge or negedge)就行了,但很明显这种写法连综合器的语法检查都过不去.

为了处理这种特殊情况,FPGA设计了专用的双边沿触发器来对DDR数据进行锁存。数据锁存后,数据宽度变为原来的2倍(或者4、8倍,视设计和硬件而定),这时候就可以用同样的时钟速度(或者1/2,1/4倍输入时钟分频)但只使用其中一个边沿来驱动后面的处理逻辑。

这里借用Lattice ECP3的4:1速率DDR结构图:

图源《LatticeECP3-High-Speed-IO-Interface》第115页

其中ECLK是用来驱动特殊的双边沿锁存器时钟,在接收情况下通常是外部设备发出,SCLK用于驱动后续处理逻辑。当然,这部分的设计需要参考对应厂家的手册,这里仅作概念说明用。

SCLK可以从ECLK分频而来,在Lattice FPGA使用CLKDIVF原语调用ECLK分频器

图源《ECP5-and-ECP5-5G-sysCLOCK-PLL-DLL-Design-and-User-Guide》第29页

当然了,我是个很懒的人,实际上用了IP核来生成这个接口。不过根本原理是没变的,这里IP的作用只是帮我们把写原语的过程省去而已。对于其他品牌的FPGA来说,只要参照厂商手册生成DDR接口即可。

吐槽:FPGA这玩意,也就刚入门时觉得美好了,“万能芯片”的美好幻想。
写久了才知道限制一堆,用起来特麻烦。

移位寄存器检测帧起始和结束标识

好了,对DDR接口有了一定了解后,第一个问题,就是怎么检测帧同步标识?

从上面我们知道,FPGA的DDR接口单次能接收的数据量都是2的次方数(2、4、8…),那索尼这前不着村后不着店的10/12/14比特,只能分为多次采集。

在这种情况下,用来检测帧头帧尾最佳方法就是移位寄存器法了:

假设有一个足够大的移位寄存器,能够容纳下完整的SAV或EAV标识。数据以先进先出的方式依次滑过寄存器:

然后在检测窗口守着,当SAV或EAV完整的出现在窗口中时就发出同步信号:

假设我们使用了12比特读出模式,这个移位寄存器最小需要48比特长。

数据的采集

前面我们提到,移位寄存器要滑动多次才能将一个完整的像素数据存进来。假设传感器设置为12比特模式,DDR接口为4:1速率。想完整接收一个像素,就需要移位寄存器滑动3次,也就是下面时序图中标有Valid Data的部分:

问题来了,过程中的第1、第2次滑动过后,移位寄存器里的数据是无效的。就必须想办法忽略中间的两次滑动,只采集第3次滑动后的结果。下面就说明一下我的思路。

我这里用的打拍方法,当接收到同步成功信号后开始打拍。当打到第二拍时,完整数据已被存入移位寄存器,此时设FIFO写使能为高。打到第三拍时,由于FIFO写使能为高,FIFO便会将数据存入其中,然后新数据进入移位寄存器,打拍计数器重设,由此实现了有效数据的采集。

跨时钟域/乒乓操作

跨时钟域操作由双端口双时钟FIFO完成。FIFO的数据输入输出宽度均为96比特,输入端时钟由传感器提供,输出端时钟由板载50MHz晶振提供。

而乒乓FIFO是FPGA处理高速数据时的基本操作,网上其他教程很多。因为有帧结束标识,我们只需要在检测到帧结束时切换FIFO读写就行,在此就不赘述了。

实操

好了,前面的理论都是纸上谈兵,下面就来实操一下罢。

硬件介绍

FPGA板子用的是之前文章里自己做的Lattice ECP5板子,感兴趣的可以去看看这篇文章

传感器用的是IMX178,画个简单的四层板,把必要信号都引出来。别的地方没啥好介绍的,可以提一嘴的就是模拟供电用的TPS7A8300这款为射频器件设计的LDO,性价比很高。
除此之外板子上还搭载了一个STM32F07单片机,用来配置传感器。

IP核生成

subLVDS接口:

双端口双时钟FIFO:

Verilog编写和仿真

代码就不在这里放出来了,工程传到Github上去感兴趣的可以看看,链接在文末,主要是在文章里放一大段代码很影响观感。

下面是仿真结果:

注意圈起来的地方,是两个FIFO的信号。可以看到两个FIFO的读写信号反复切换。

吐槽:Modelsim,你们工程师真的受得了这屎一样的UI吗?那我问你。

上板验证

使用Lattice的ILA工具,监控FIFO的写信号、数据读入读出、读出行数计数器这几个信号。综合后时序如下(已进行时钟约束),倒也是挺意外的,我以为时序会爆爆爆(不过也离违例不远了)。

下面是采集结果,需要关注的主要是红框处的数据,前者是数据采集,后者是数据读出。如果前后都能对上,那就说明我们的乒乓FIFO操作是正确有效的。

放大第一个红框内的数据,在FIFO_WE为高时,记下FIFO_IN_CH1的数据,这里取前五个:

1AF、1AC、1B7、1A9、1B1

然后放大第二个红框内的数据,观察FIFO_OUT_CH1的前五个数据是否相同

1AF、1AC、1B7、1A9、1B1

数据一致,说明乒乓操作是有效且正确的。

另外,我还加入了一个计数器。根据手册,当传感器使用630万像素读出、8通道传输时,应该有395个数据包。除去开头的SAV1~SAV4不会被采集,那么计数器应该为395-4=391

结果完全正确

未来改进空间

之所以使用12比特模式,最大的原因还是在于它能够用3次移位寄存器的滑动完全采集,如果是10/14比特,需要滑动2.5/3.5次,出现了小数,就必须乘以2得到整数。这样一来就变成了滑动5/7次,对资源占用更多,可能对时序也有不良影响。

所以未来的改进空间主要在如何添加10和14比特读出模式的支持。不过话说回来,这么一个小底传感器,使用14比特读出能带来多大的信噪比提升我依然持怀疑态度,而10比特就没必要用,何必白白损失两位。

另外时序改进也是一个要点,但我暂时还没有对时序路径做更深入的分析(指不知道瓶颈出现在代码的哪一行,Lattice时序分析工具还没用明白),因此就暂时放缓。

接下来的目标就是让传感器原始图像(未Debayer)通过那LCD显示屏接口显示出来,能出图才是第一要务。

Github地址

发表回复