搞这个高速数据传输,真可谓“一波三十折”,仅方案都试了好几个。分别使用了:示波器方案;NiosII方案;ARM方案。最后直接使用fpga+dp83848实现了高速数据采集。
系统的硬件成本较低,使用EP4CE6芯片,外加ADC芯片和DP83848模块就可以了,连外置ram都省了。淘宝上的虚拟示波器方案,则是用usb的phy传输到电脑的,但考虑到上位机程序编写,使用网络的udp协议还是非常方便的。当然在fpga上从底层实现udp协议还是费了很多功夫的。
硬件上,ADC芯片使用TI的AD7476,12bit,1M转换率,spi接口,3根线到fpga即可。当扩展多片adc时,可以将cs和clk共用,多用几个miso就可以了,后来用这个系统采集8颗ad7884,就是用了8个miso,实现ADC芯片组的同步采集。
网络芯片dp83848,在很多板子上见到过,单芯片仅几元钱,网上也有现成的单独模块出售,却要卖50大洋。这里使用rmii接口,也是直接连接fpga,一共9根线,时钟从网络模块上提供。
实际应用中发现,MDIO和MDC用于控制寄存器的两根线可以不要的,直接用rmii的7根线就可以了,而且RX和TX可以完全分开来搞,这个系统主要搞的当然是TX了,不过TX搞定后,RX一两天就也搞定了。
对fpga系统,直接淘宝上买最小系统就可以了:时钟是必不可缺的,最好带两个按键,再带两个指示灯,再把所有的io引出,嗯,完美,我买的就这样的,自带50M时钟,带两个触点开关和两个红色led,IO从板子两侧全部引出,不带sram/sdram/flash,唯一遗憾是没有2.5V的IO块,不能实现lvds接口,否则可以使用更高速的ADC芯片了。
硬件上需要注意的地方是,RMII接口的时钟是用网络模块提供的,频率50MHz,而不是用fpga提供,好处是保证网络模块送出的数据和时钟同步,但缺陷也是明显的,线绝不能太长,我用杜邦接口作的线束连接,线长不超过10公分是没有问题的,同时送给网络模块的数据也要保证和时钟的同步,可能是我做的线短,还没有出现发送数据和时钟不同步导致的问题。
以上是硬件上的操作。
*********************************************************************************
硬件完成,就要开始做软件这个大头戏了,当然需要一步一步,一步两步是爪牙,是魔鬼的步伐了。建议最开始先实现数据报的发送,然后再考虑数据流结构,最后实现各个模块功能。
对于数据采集,我用的乒乓操作,将ADC数据存储到ram中,再转给rmii接口。这里一定要分清各个模块的功能,不能越位。
数据流:ADC接口模块->fifo模块->发送控制器->内部ram1/内部ram2->RMII发送模块。ADC接口模块采集模数转换芯片的数据,fifo用于缓存数据,发送控制器将fifo中的数据打包成标准的udp数据包并保存在ram中,两个ram实现乒乓操作,RMII实现将ram中的数据送到phy芯片。
第一个是ADC芯片的接口模块,也就是spi接口,功能非常单一:采集spi数据,送入到fifo;这也是最长改动的模块,每换一款adc芯片就要改动,同时控制信号也有这个模块产生,在合适的时候产生合适的电平,经常需要结合示波器调试,特别是adc的取样时间确定,也是非常麻烦的。
我的编程风格是将各个模块生成框图,再连接起来,这样看着方便,如下图这样,考虑到后期扩展,直接将miso作成了16根,想用几根用几根。
数据送入fifo,fifo是系统自带的模块,由于数据实时传输,不用太深,64words就可以了,我用的16bits宽度,毕竟告诉数据采集很少用高于16位ADC芯片的。
最复杂的就是发送控制器了,将原始数据,打包成udp包。包括但不限于:生成Ethernet包的头部ip包的头部udp包的头部,计算udp的checksum,计算ip的headchecksum,将fifo中的数据按特定格式存放,告知RMII模块发送数据长度及数据有效标识,还有一个乒乓操作的控制信号产生。总之,这个是最麻烦的,同时完成后也尽量不要改动这里面的代码。
下图中的fifo interface是从fifo中读取数据,ram interface则是存放完整的udp包,由于udp包含了各种head,生成这些head会占用时钟,这也是fifo存在的意义。而最下侧的rmii control则是告知rmii接口数据的长度,已经是否可以发送了。
正常的,发送控制器将数据打包后保存到ram,通知rmii发送就可以了,但这里用的单口ram,rmii读取的时候就不能保存udp包了,会将网络利用率降低一半,因此还需要一个乒乓操作控制器,一个ram写入udp数据的时候,另一个正在读取并通过rmii接口发送。这里通过一个ram_chose信号实现控制。
框图很复杂,其实就是将两个ram变成了一个而已。
ram模块,当然使用8位宽度,至于深度,由于最大的网络数据包长度为1518字节,这里选择了2048长度,足够。
最后就是rmii的发送模块了,该模块只是将ram中的数据,按照rmii的协议发送,也就是将8位的byte,转成2位的tx1/tx0。外加一点工作是,产生前导向符,也就是一串的10101010,和最后一个10101011。再加一点工作是,计算数据包的crc32,追加后数据后面,这里crc32计算,要选对多项式,务必请使用:`define POLYNOMIAL 32'h04C11DB7,计算crc32也是个无比蛋疼的过程,首先是实时计算,每发送一个字节就要更新CRC,计算到最后还要取反才能发送。最后再加一点工作是,从wiki百科得知,数据包发送需要有12个字节的间隔,也就是不能一直发。由于现在网卡都是全双工的了,也就不需要考虑碰撞检测了。
网络模块的4根线就到这个上面的,注意时钟是网络模块提供的oscin。
最后发一张整体的bdf图
******************************************************************************************
由于这个方案设计的时候重要工作已经放在fpga上了,故上位机的程序就比较好办了。使用udp协议监听制定的端口,将收到的数据包按格式解码并显示就可以了,使用任何一门语言都可以方便实现,也不涉及底层代码编写。这里使用labVIEW实现。
下图是实现数据的采集。
当然要实现更过的功能,上位机还是要费点功夫的,比如实现触发功能,多通道信号的恢复,数字信号处理识别等。其实相当于做了一套dmq设备,并将接口进行了简化,在udp数据实时传输实现的基础上,按客户需求实现测量功能则已没有瓶颈。
最后放一张当时调试成功的界面,用了1颗AD7874实现3M采样率时的网络传输截图,由于我按照16位宽度做的,故网络使用率约48%多一些。
*********************************************************************
后面再更新RMII接收的方法,比较容易实现的。
接口上,将RMII的时钟和rx1/rx0以及crs/dv接入fpga,并进行对应的解码即可。
解码时已将数据包放入到ram中,不过似乎没什么用,直接进行数据包的解码了,再根据接口确定命令就可以了。