本文档介绍了AG32开发中,MCU与CPLD交互的具体方式以及例子。如需了解AG32更多资料可发邮件:sales@agm-micro.com
一、MCU和CPLD直接交互
cpld工程创建及编译的操作流程,参考文档《AG32下fpga和cpld的使用入门》
在工程中,用户逻辑部分编写是从analog_ip.v的接口下开始的。
mcu和cpld之间的交互,可以分为:
1. mcu传递信号给cpld;(如mcu的gpio传递高低信号到cpld)
2. cpld传递信号给mcu;(如:对mcu产生中断信号)
3. mcu读写数据到cpld;
4. 不建议,cpld做为主设备对mcu写。
也就是说,在mcu和cpld交互中,cpld更像一个外设。
其中,前两种较为简单。后两种要使用AHB总线来操作。
下边针对四种情况分别说明:
1. mcu传递信号给cpld;
这种使用较简单。步骤如下:
在ve中定义信号:
表示,用mcu的gpio(gpio4_1)来输入信号到cpld。
然后,prepare LOGIC工程后,可以看到analog_ip.v接口中的信号:
input iocvt_chn_out_data,
input iocvt_chn_out_en,
这里的iocvt_chn_out_data,就是对接到mcu的gpio4_1的信号。
当控制mcu的gpio4_1高低切换时,cpld中的iocvt_chn_out_data,会对应来变化。
具体样例,可以参考网盘“logic样例3.mcu信号到cpld到pin”的样例,该样例中,展示了mcu控制cpld继续控制led的过程。
除了gpio信号输出到cpld,其他比如pwm输出信号等,都可以输入到cpld。
2. cpld传递信号给mcu;
这种方式和1相近,只不过是反向。
可以在mcu中定义gpio4_2为输入并使能中断,则cpld中设置信号高低时,将触发 mcu的中断。
在VE中定义信号:
GPIO4_2
iocvt_chn:INPUT
表示,用mcu的gpio(gpio4_2)信号来源于cpld的iocvt_chn。
然后,prepare LOGIC工程后,可以看到analog_ip.v接口中的信号:
output iocvt_chn_in_data,
这里的iocvt_chn_in_data,就是对接到mcu的gpio4_2的信号。
当cpld中控制iocvt_chn_in_data信号高低时,mcu中的gpio4_2对应变化。
这里不再举例。
3. mcu读写数据到cpld;
在地址设计中,cpld的地址区间是:0x60000000 ~
0x7FFFFFFF
当mcu对这个区间内的地址访问时,相当于访问了cpld的“寄存器”。
mcu是全局寻址,对这个空间的访问和对ram(0x20000000起)空间的访问是一样的方式,在C代码中,可以这样写:
读cpld:int cpRdReg = *((int *)0x60000000);
写cpld:*((int *)0x60000004) = cpWtReg;
Mcu端读写cpld较为简单,直接通过上述语句就可以了。
当mcu读写动作发生时,cpld端是如何反应的?
当上述mcu读写动作发生时,AHB总线会把动作拆解为读写信号,传递到analog_ip.v的接口,用户cpld程序需要响应该信号。
以下,以写动作 *((int *)0x60000004) = cpWtReg 为例,描述cpld端会发生的事情。
回顾下analog_ip.v中的接口部分:
其中slave_ahb_开头的一组信号,是cpld作为主端时用的,暂时不用理会。
Mem_ahb_开头的一组信号,是cpld作为从端使用的。
当mcu有读写操作时,mem_ahb_这组信号将发生变化。
这部分是遵循标准的AHB总线协议的。如果对AHB总线印象不深,请自行百度。
可参考的讲解:
https://blog.csdn.net/weixin_46022434/article/details/104987905
几个信号的概述(更详细的讲解请自行百度):
Ahb_htrans: 当前传输类型(00: IDLE、01: BUSY、10: NONSEQ、11: SEQ)
Ahb_ready:mcu读时要mcu要准备好cpld才会写
Ahb_hwrite: 要读还是要写(1为写,0为读)
Ahb_haddr[32]: 要操作的地址
Ahb_hsize:transfer的大小,以字节为单位
Ahb_hburst:批量传输
Ahb_hwdata[32]:写的数据,32位
Ahb_hreadyout:输出信号,mcu写时cpld是否准备好
Ahb_hresp:输出信号,响应信号(OK、retry、error、split)
Ahb_hrdata[32]:读的数据,32位
根据AHB时序,在一次传输中,cpld(slave端)会先拿到addr地址,读或写的标记,然后交互ready信号后,开始数据传输。
大致如下图(无等待类型的图):
比如,mcu要读0x60000004的寄存器:
mcu端直接C语言这样调用:int cpRdReg = *((int
*)0x60000004);
cpld端,可以根据以上信号做如下处理:
----------------------------------------------
//mcu的读操作响应
//mcu端用C语言:int value = *((int *)0x60000004);
reg [31:0] hrdata_reg; //定义32位的hrdata_reg
always @(posedge sys_clock) begin //clk上升沿触发
if (mem_ahb_htrans ==
2'b10 && //NONSEQ状态,第一次传输
mem_ahb_hready && //master已ready,可以给数据线写入了
!mem_ahb_hwrite && //读(0 读,1 写)
mem_ahb_haddr[23:0] == 'h04) //读地址为0x60000004(cpld用相对偏移)
begin
hrdata_reg <=
hwdata_reg; //把另一准备好的数据给到hrdata_reg
end
end
assign mem_ahb_hrdata = hrdata_reg;//绑定hrdata_reg到读的数据线上
-----------------------------------------------
以上代码,加入到analog_ip.v的module下,就可以完成cpld对mcu读动作的响应。
比如,mcu要写0x60000000的寄存器:
mcu端直接C语言这样调用:*((int *)0x60000000)
= value;
cpld端,可以根据以上信号做如下处理:
----------------------------------------------
//mcu的写操作响应
//mcu端用C语言:*((int *)0x60000000) = value;
reg [31:0] hwdata_reg; //定义32位的hwdata_reg
always @(posedge sys_clock) begin //clk上升沿触发
if (mem_ahb_htrans ==
2'b00 && //IDLE状态
mem_ahb_hreadyout && //cpld已ready状态,ahb上数据可以写过来
mem_ahb_hwrite && //写(0 读,1 写)
mem_ahb_haddr[23:0] == 'h00) //写地址为0x60000000(cpld用相对偏移)
begin
hwdata_reg <=
mem_ahb_hwdata; //把收到的数据给到hwdata_reg
end
end
//这个过程,是把mcu写进来的数据收到hwdata_reg中
-----------------------------------------------
这部分的实例代码,请参考网盘上cpld样例工程《5.mcu读写cpld寄存器》。
注意:这里展示的,仅仅是基于AHB总线上的数据交互。
在实际应用中,比如要实现一个串口之类的,往往是慢速设备,这些是要挂载到apb上的。慢速设备要经过ahb到apb的bridge,才能最终使用。请继续往下看。
二、mcu通过ahb转apb后的数据交互
上节讲述了mcu和cpld之间交互数据的实现方式。
但数据是在ahb层面的响应,慢速设备不能直接使用。
慢速设备需要ahb转为apb后,使用apb的信号来交互。这种情况,转变为mcu和apb之间的交互。
mcu和apb之间的交互,相比mcu和aph之间的交互,多了一层ahb到apb的转换。这个转换是借助于ahb2apb.v模块来实现的(在example/analog下找该.v文件)。
该模块:输入是ahb的一组信号,输出是apb的一组信号。使用如下图:
如果实现mcu和apb的交互,则需要操作的是转换后的这组apb信号。
关于apb总线的使用,更多信息请自行百度。
这里只是简述下apb信号列表(与ahb略有不同):
apb_psel:片选
apb_penable:表示传输进入第二周期(准备好了读/写)
apb_pwrite:传输方向(1-写;0-读)
apb_paddr[32]:地址总线,要操作的地址
apb_pwdata[32]:写的数据,32位
apb_prdata[32]:读的数据,32位
以下展示在apb下如何实现跟mcu的交互,仍以ahb的两个寄存器为例。
1. 首先需要增加ahb转apb的信号关联;
如上图。
Ahb2apb模块会把ahb信号转换为apb信号。接下来操作apb信号即可。
2. 在转换后的apb信号中,实现写和读的操作。
mcu读操作时:
比如,mcu要读0x60000004的寄存器:
mcu端直接C语言这样调用:int cpRdReg = *((int
*)0x60000004);
cpld端,可以根据以上信号做如下处理:
----------------------------------------------
//mcu的读操作响应
//mcu端用C语言:int value = *((int *)0x60000004);
reg [31:0] ardata_reg; //定义32位的hrdata_reg
always @(posedge apb_clock) begin //clk上升沿触发
if (!apb_pwrite
&& //读 (0 读,1 写)
apb_penable
&& //是否准备好
apb_paddr[11:0]
== ‘h04) //读地址为0x60000004(cpld内部用相对偏移)
begin
ardata_reg <=
awdata_reg; //把另一准备好的数据给到hrdata_reg
end
end
assign apb_prdata = ardata_reg;//绑定hrdata_reg到读的数据线上
-----------------------------------------------
mcu写操作时:
比如,mcu要写0x60000000的寄存器:
mcu端直接C语言这样调用:*((int *)0x60000000)
= value;
cpld端,可以根据以上信号做如下处理:
----------------------------------------------
//mcu的写操作响应
//mcu端用C语言:*((int *)0x60000000) = value;
reg [31:0] awdata_reg; //定义32位的hwdata_reg
always @(posedge apb_clock) begin //clk上升沿触发
if (apb_pwrite
&& //写 (0 读,1 写)
apb_penable
&& //是否准备好
apb_paddr[11:0]
== ‘h00) //写地址为0x60000000(cpld内部用相对偏移)
begin
awdata_reg <=
apb_pwdata; //把收到的数据给到hwdata_reg
end
end
//这个过程,是把mcu写进来的数据收到hwdata_reg中
-----------------------------------------------
这个功能实现后,其实是个简单的“空外设”。可以用它做为实现复杂功能外设的基础。
这部分的实例代码,请参考网盘上cpld样例工程《5.mcu读写cpld寄存器》。
样例展示到这里,mcu和cpld的交互上:交互信号、跟ahb交互数据、跟apb交互数据,基本的交互通路已经建立。
接下来,用户根据自己的需求,在cpld中交互到数据后,编写自己需要的功能即可。
三、DMA在CPLD中的使用
cpld中实现DMA的逻辑:
1. MCU为master,cpld为slave,mcu对cpld的交互方式为存取寄存器的方式;
2. mcu中配置好DMA(读取cpld中准备好的数据);
3. cpld中准备好数据后,触发dma信号,dma自动搬运到mcu指定的ram;
4. 搬运一次后,dma给cpld一个clear信号,完成一次dma搬运;
5. 等到cpld中再次准备好数据,将再次触发dma信号,重复3和4;
对于cpld来说,mcu来读取数据和dma来读取数据,是一致的。
dma来读取时,只是每次读完后会多给cpld一个clear信号。
更多细节,请参考网盘上《7.cpld中配合实现mcu的dma读取》部分的样例。
在这个样例中,展示了两部分代码:
1. mcu中,配置dma读取;为了测试,mcu会在另一地址给cpld写数据;
2. cpld中,会对mcu写进来的数据缓存,缓存后触发dma的信号,让dma来读取数据。而dma从cpld里读取数据后会给cpld一个clear信号,标志一次dma交互完成。