原创 关于FPGA与单片机的通信的一些想法

2009-7-24 20:42 4200 7 9 分类: FPGA/CPLD





下面只是我自己的一些想法,对于时钟同步的问题里面没有考虑,只是简单的测试一下而已,应付急需。。。。在我第一次写这篇文章的时候,iIove314就提出这个问题,我也看了一些rippe和特权的一些博客文章,对于异步时钟的问题真的还是挺棘手的。。。不过还是放出来,大家指点一下比较好,后续会对异步时钟的处理上花点时间,在拿出来。。。



单片机与FPGA的通信可以使用总线否认方式来搜寻,但是实际上很多情况下由于接口的限制不能够使用总线,另一另外的角度说,其实FPGA和单片机的通信方式有很多,只要能够稳定的传输就可以了,串口也行,并口也行,总线也行,但是不同的情况需要具体对待,下面是一些常用的情况



1.           
仿8086时序(用的最多的一种方式)



1)一般情况下的使用



单片机和FPGA之间有N位数据线、M位地址线、时钟SCLK、读写选择RW、使能CS,不管是读和写数据,总是先把地址放在接口上,在时钟的上升沿将数据输入或者让读出;RW高电平读数据,低电平写数据;CS线低电平有效,也可以作为内部寄存器更新数据线。一般情况下读和写是放在2个文件里面的,而且根据不同的情况需要在做不同的调整



-----------------------------------------------------------------------------------------------------------



写数据的参考文件:WriteData
_1.v



module
WriteData(MCU_CtrlData,MCU_CtrlAddr,MCU_clk,MCU_rw,



                 trigger_way,



                 trigger_trigger,



      
          MngClk_fifowrclk,



                 ReadData_select,



                 FIFO_aclr,



                 FRE_b_e,



                 AC_DC,



                 D10_D1);



input  
MCU_clk;



input  
MCU_rw;



input 
[3:0]MCU_CtrlData;



input 
[3:0]MCU_CtrlAddr;



output [3:0]trigger_way;



output [3:0]MngClk_fifowrclk;



output [3:0]ReadData_select;



output 
trigger_trigger;



output 
[1:0]FRE_b_e;



output 
FIFO_aclr;



output  AC_DC;



output  D10_D1;



 



reg   
[3:0]trigger_way;



reg   
trigger_trigger;



reg   
[3:0]MngClk_fifowrclk;



reg   
[3:0]ReadData_select;



reg    FIFO_aclr;



reg    [1:0]FRE_b_e;



reg    AC_DC;



reg    D10_D1;



//////////////////////////



//地址定义



parameter [3:0]param_trigger_way       = 0;



parameter [3:0]param_trigger_trigger   = 1;



parameter
[3:0]param_MngClk_fifowrclk  = 2;



parameter
[3:0]param_ReadData_select   = 3;



parameter
[3:0]param_FIFO_aclr         = 4;



parameter
[3:0]param_FRE_b_e           = 5;



parameter
[3:0]param_AC_DC             = 6;



parameter
[3:0]param_D10_D1            = 7;



/////////////////////////



always @ (posedge
MCU_clk)



begin



    if(!MCU_rw)



    begin



    case(MCU_CtrlAddr)



    param_trigger_way      :



                      
trigger_way       <=
MCU_CtrlData;



    param_trigger_trigger  :



                      
trigger_trigger   <=
MCU_CtrlData[0];



    param_MngClk_fifowrclk :



                      
MngClk_fifowrclk  <= MCU_CtrlData;



    param_ReadData_select :



                       ReadData_select   <= MCU_CtrlData; 



    param_FIFO_aclr       :



                       FIFO_aclr         <= MCU_CtrlData[0];  



    param_FRE_b_e         :



                       FRE_b_e           <= MCU_CtrlData[1:0];  



    param_AC_DC           :



                       AC_DC             <= MCU_CtrlData[0];  



    param_D10_D1          :



                       D10_D1            <= MCU_CtrlData[0];                                                                           



    endcase



    end



end





endmodule



 





下面是一个读数据的例子:可以参考文件ReadData_1l.v



/***************************************



 读数据



 cnt_std为外部提供的32位数据



 addr
地址线
8



 data
数据线 8



 sclk
时钟线



 在时钟的上升沿将对应地址的数据读出



 ****************************************/



module parial2serial(cnt_std,addr,data,sclk);



input  [31:0]cnt_std;



input  [7:0]addr;



output [7:0]data;



input  sclk;



reg    [7:0]data;



always @ (posedge sclk)



begin



   case(addr)



   8'b00000000:



         
data <= cnt_std[7:0];



   8'b00000001:



         
data <= cnt_std[15:8];



   8'b00000010:



         
data <= cnt_std[23:16];



  
8'b00000011:



         
data <= cnt_std[31:24];  



   default:



         
data <= cnt_std[7:0];



   endcase                    



 end



endmodule



-----------------------------------------------------------------------------------------------------



上面的例子中红色的部分是必须的,和上面说的一样,RW决定读写,SCLK上升沿读入或者写入数据到对应的地址;黑色的文字例如写模块中trigger_way,trigger_trigger,
MngClk_fifowrclk
,是其他模块的控制字,数据写入后就会改变其他模块的控制字,从而控制过程;读模块中cnt_std,是要读出的数据,这里的数据是一个32位的控制字,你不能能使用32位的数据线,所以你要分几次把数据读出,具体你在使用过程中根据具体情况来使用,传输过来的数据可能是一个RAM,可能是一个很大的数组,总之对应的地址对应着该有的数据就可以了。



上面的读和写模块虽然在2和文件中,但是在顶层文件中,这些数据线、地址线、时钟、RW是接在一起的;



上面的例子中写模块的数据线和地址线都是4位,在读模块中都是8位,其实数据和地址线具体多少可以具体情况再去改变,可能位数不相同。一个不很好的例子就是RAM的读写,RAM的读写,数据线是8位,地址线就不只8位了,需要根据你RAM的深度来决定



例如你的RAM4096字节,地址线就有12条,只要在时钟上升沿正确设置RW以及addr,就可以读出或者让写入数据     



                            



点击看大图



                                   RAM写数据时序



                       



点击看大图



                           RAM读数据时序



 



2)不使用地址线的情况



如果在管脚限制的情况下,尤其是在读写RAM的时候,内部开辟的空间太大的话,地址线占用很多的IO,这时候地址线可以采取内部产生或者分时输入的方式,对于分时输入的方法还没有测试过,倒是内部产生地址的方式用过,最好的例子是FIFO,不过FIFO经常使用的是内部的IP。很少自己去写,FIFO需要数据线,时钟,使能就可以了。就像5月份的示波表那样做。但是有时候用不到FIFO,地址自己产生就是用SCLK的边沿驱动计数器来产生地址。下面是一些例子(在数据很少的时候可以这样使用):



/*****************************************************************************



   作者:深夜孤灯



   整理时间:09-7-24-4-24



   描述:将并行数据转换为串行数据输出



   接口:



   clk_tra 上升沿将输入数据转换为寄存器数据



   ParDataIn 输入数据



   SerDataOut 输出数据  8



   clk 输出数据时钟上升沿有效



   在读取频率之前需要将srw拉低SCLK下降沿来初始化里面的地址计数器为0,在sclK的下降沿地址计数器增加 在时钟的上升沿 将对应地址的数据输出



*/



module
parial2serial(ParDataIn,SerDataOut,sclk,srw);



input  [63:0]ParDataIn;



input  sclk;



input  srw;



output
[7:0]SerDataOut;



reg
[7:0]SerDataOut;



reg [3:0]AddrCnt;



 



always @ (negedge
sclk)



begin



   if(!srw)



       AddrCnt 
= 4'b0000;



   else if(AddrCnt == 7)



              AddrCnt  = 
4'b0000;



        else



              AddrCnt  = 
AddrCnt + 4'b0001;



end



 



always @ (posedge
sclk)



begin



   if(srw)



   begin



   case(AddrCnt)



   4'b0000:



          SerDataOut <= ParDataIn[7:0];



   4'b0001:



          SerDataOut <= ParDataIn[15:8];



   4'b0010:



          SerDataOut <= ParDataIn[23:16];



   4'b0011:



         
SerDataOut <= ParDataIn[31:24];  



   4'b0100:



          SerDataOut <= ParDataIn[39:32];



   4'b0101:



          SerDataOut <= ParDataIn[47:40];



   4'b0110:



          SerDataOut <= ParDataIn[55:48];



   4'b0111:



          SerDataOut <= ParDataIn[63:56];  



   default:



          SerDataOut <= 8'b00000000;



endcase                    



   end



end





endmodule





这个例子在频率测量的时候测试过,在要求不高的时候可以使用。输出的数据可能不是这样的,但是原理一样的



下面是一个带有UPDATA信号的写数据的例子:



-----------------------------------------------------------------------------------



/***********************************************



作者:深夜孤灯



整理时间;09-7-24-4-51



描述:  通过内部产生地址来写入数据到寄存器,通过lock_up来把数据锁存到相应的模块寄存器中、



接口说明:



sclk    时钟



cs      使能,低电平有效



data    数据传输



lock_up 上升沿更新数据到寄存器



*************************************************/



module
parral_communicate(sclk,cs,data,lock_up,



                          frequence_word,



                          wave_select,



                          clk_select,



                          compare_word



                        );



input  sclk;



input  cs;



input  [7:0]data;



input  lock_up;



 



output [31:0]frequence_word;



output [2:0] wave_select;



output [3:0] clk_select;



output [7:0] compare_word;



 



reg [31:0]frequence_word;



reg [2:0] wave_select;



reg [3:0] clk_select;



reg [7:0] compare_word;



   



reg [7:0]temp_reg[5:0];



reg [3:0]counter;



 



//ganerate regster addres  



always@(negedge wr or
posedge cs)



begin



   if(cs)



     counter<=0;



   else if(!cs)



     counter<=counter+1;



end



 



//write data to regster 



always@(posedge sclk)



begin



  if(!cs)



     temp_reg[counter]<=data;



end



//sent data to inter regster,and
come into effect with the rising edge of lock_up



always@(posedge
lock_up)



begin



 
frequence_word<={temp_reg[3],temp_reg[2],temp_reg[1],temp_reg[0]};



 
wave_select<=temp_reg[4][2:0];



 
clk_select <=temp_reg[4][7:4];



 
compare_word<=temp_reg[5];



end





endmodule





当然你也可以不使用UP_data信号直接跟新数据,不过该文件使用在DDS信号发生器里面,需要32位的频率字,但是不肯可能一次写入32位数据,所以这个时候还是有用的;这种方式有种弊端就似乎每次你必须把所有地方都设置一次,因为地址的关系,不过你也可以通过SCLK的空操作来找到相应的地址。。。。。



上面列举其实就是一位寄存器的并入串出。只不过载入数据的方式不一样而已。



对于比较大的RAM,数据线可以通过双向复用来读取数据和设置寄存器,读取ram的地址可以通过内部来产生,控制的地址不会太大可以通过用的地址线或者也用内部的地址产生



3IO的分时复用



      FPGA可以使用双向口,这样的话,数据线可以设置为双向口,读数据和寄存器的设置分开进行。



4)有状态信号



    状态信号可以通过单独的管脚来输出,但是经常有管脚不够的情况,或许使用总线会比较合适,如果你已经在使用总线的方式在传输数据或者有输出数据的IO



5)内部有多个数据模块的情况



 



PARTNER CONTENT

文章评论2条评论)

登录后参与讨论

zhangshaobing517_935512703 2009-7-27 23:07

加上转载地址就可以了 。。。还要看看特权博客里面有关异步时钟问题的博文 ,挺好的

用户536736 2009-7-27 09:30

很好的资料,请问可以转载到我的空间 里去么,有时间好好研究一下啊,谢谢了
相关推荐阅读
zhangshaobing517_935512703 2011-03-21 01:28
KC24RT-300调试笔记
项目中需要使用LED驱动器,主要是为了让一串LED发出的光照一致,所以在试验中采用LED串联的方式比较好点,LED并联容易导致LED发光的 不均匀以及寿命减少。我在项目中采用金升阳公司的KC24RT-...
zhangshaobing517_935512703 2010-11-19 14:53
线程中CreateEvent和SetEvent及WaitForSingleObj
首先介绍CreateEvent是创建windows事件的意思,作用主要用在判断线程退出,程锁定方面.CreateEvent 函功能描述:创建或打开一个命名的或无名的事件对象.EVENT有两种状态:发信...
zhangshaobing517_935512703 2010-11-15 13:29
VS2008 BEGIN
Visual Studio 2008环境与VC6.0的环境存在着比较大的区别,下面就一些小小的区别在这里做一些探讨,欢迎指教!1、如果是调试控制台程序,很多时候点击“启动调试”后是一闪而过,此时可有两...
zhangshaobing517_935512703 2010-11-01 20:38
使用MFC的数组类
 MFC的数组类支持的数组类似于C++中的常规数组,可以存放任何数据类型。C++的常规数组在使用前必须将其定义成能够容纳所有可能需要的元素,而MFC数组类创建的对象可以根据需要动态地增大或减小,数组的...
zhangshaobing517_935512703 2010-09-07 13:14
循环
 循环设计的注意的事情:(1)双重循环的跳出问题,break只挑出所在的循环,如果使用双层FOR循环,单个BREAK就不可能跳出所有的双层(2)在迭代的时候,注意起始和终止的条件,尤其是终止问题(3)...
zhangshaobing517_935512703 2010-09-02 01:09
图像处理改进
1.特征点提取的算法  标志点的提取算法对结果的影响虽然没有经过试验或者计算的推算,每1个pix的偏差对结果的影响有多大,但是不可避免的,要想获得高精度的  测量结果,高精度的提取对结果的影响还是很大...
EE直播间
更多
我要评论
2
7
关闭 站长推荐上一条 /1 下一条