AG32 MCU中CPLD使用基础(二) 目录 一、mcu与cpld的交互 1. mcu传递信号给cpld;
2. cpld传递信号给mcu;
3. mcu从cpld里读写数据;
二、 mcu与cpld的apb数据交互
三、 ADC样例与UartTx样例
四、 dma在cpld中的使用
五、 cpld中使用ram
六、 一些小技巧:如何定义信号量数组 一、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中定义信号: GPIO4_1 iocvt_chn:OUTPUT 表示,用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端,可以根据以上信号做如下处理:
以上代码,加入到analog_ip.v的module下,就可以完成cpld对 mcu读动作的响应。
比如,mcu要写0x60000000的寄存器: mcu端直接C语言这样调用: *((int *)0x60000000) = value; cpld端,可以根据以上信号做如下处理:
这部分的实例代码,请参考网盘上cpld样例工程《5.mcu读写cpld 寄存器》。
注意:这里展示的,仅仅是基于AHB总线上的数据交互。
在实际应用中,比如要实现一个串口之类的,往往是慢速设备,这些是要挂载到apb上的。慢速设备要经过ahb到apb的bridge,才能最终使用。请继续往下看。 二、MCU与cpld的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要写0x60000000的寄存器:
mcu 端直接 C 语言这样调用: *((int *)0x60000000) = value;
cpld 端,可以根据以上信号做如下处理:
这个功能实现后,其实是个简单的“空外设”。可以用它做为实现复杂功能外设的基础。这部分的实例代码,请参考网盘上 cpld 样例工程《5.mcu 读写 cpld 寄存器》。
样例展示到这里,mcu和cpld的交互上:交互信号、跟ahb交互数据、跟apb交互数据,基本的交互通路已经建立。
接下来,用户根据自己的需求,在 cpld 中交互到数据后,编写自己需要的功能即可。
三、ADC样例与UartTx样例 这里借助ADC样例和UartTx 样例,展示下更多的使用方法。
ADC和UartTx,都是接到apb的。
功能方面,都包括:设置寄存器,读取寄存器。
ADC 样例:
SDK 安装后的example/analog工程,展示了ADC模块做为外设,与 mcu 之间的数据交互(ADC 采集后的数据,被mcu读取)。
这里的ADC模块,在cpld里做的工作,是把串行数据转换成并行数据。
因为 AG32 芯 片中集成的 ADC 硬核是串行数据输出的,而 mcu 访问数据都是并行的。为了实现mcu端访问数据的统一,这里增加了ADC 的 cpld 代码,实现该串行转并行的功能。
相当于:ADC硬核+ADC的cpld逻辑,实现了一个完整的“ADC 外设”。 ADC的用户cpld代码,都是在 analog_ip.v中实现的。
直接以analog_ip.v中的实现来说明:
1. 在analog_ip的接口下边,首先是ahb2apb的信号转换。
转换后,后续的ADC/DAC/CMP,都是以 apb 信号线来操作。
2. 定义出信号线数组,用于连接实例化的6个对象:3个ADC/2个DAC/1个CMP;
这个里边,wire [PER_CNT-1:0] select 是定义片选,注意它的取位,这里取值后的数组,分别对应到 ’h000,’h1000,’h2000,’h3000,’h4000,’h5000。
其他数组,都分别对应 6 个实例化以后的连线。
3. 然后用 for 循环 genarate 实例化 ADC/DAC/CMP;
包含 3 个 ADC,2 个 DAC,1 个 CMP。
实例化时,连线都对应到上边定义的数组中去了。
4. 接下来的 pr_select,是记录当前片选是哪个。
索引值:0 ADC0;1 ADC1;2 ADC2;3 DAC0;4 DAC1;5 CMP
5. 接下来的 apb_prdata,
6. 再接下来,就是 3 个模块 ADC/DAC/CMP 各自的实现了。
具体内容不再解析。
注意几个点:
A. mcu中设置寄存器时,都是值下来后,在ADC/DAC/CMP中缓存到reg中;
如:0x60005004是CMP的CHANNAL寄存器,mcu对这里寄存器设置值时,
最终cpld里执行:
B. mcu中要获取的值,也类似传递。
UartTx 样例: 这个样例比ADC样例简单些。
这个样例中,实现一个串口功能(只有TX功能,固定为115200频率)。
对mcu来说,它也是cpld实现的一个“外设”。
它的 cpld 实现逻辑,跟上边的ADC样例思路是相同的,都是先 ahb 转 apb,然后实例化外设,mcu 写数据时 apb 接收后处理,mcu 读数据时 apb 触发后处理。
读的时候,是读的串口的 state 状态;
写的时候,会把写的数据继续丢给 uart 模块处理(转化为 IO 的高低波形输出)
mcu 端,在 while(1)里边:
查询 cpld的写状态,当状态合适时,发数据给 cpld,cpld 根据时序转换为波形输出到定义的PIN。
更多细节,请参考 cpld 工程中的代码和注释。
该工程位于网盘上“logic样例/6.UartTx 例程”。
四、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 交互完成。
五、CPLD中使用ram cpld本身有自带的ram,为4个M9K块,每个M9K大小为8192 bits。
(即:4个M9K 总空间为4Kbytes)
更详细信息,请参考《AGRV2K_Rev2.0.pdf》中的说明。
除了cpld自带的内存,cpld还可以使用mcu的内存sram。
AG32整个芯片系列,内存sram大小都是128K。
如果mcu用不了128K,希望分一些给cpld来用,比如,分出来32K给 cpld。可以按照如下方式设置:
1. 限制mcu的使用,让mcu只使用前96K;
限制mcu对ram的使用,需要修改ld配置(分散加载相关)来实现。
在路径:AgRV_pio\packages\framework-agrv_sdk\misc\devices下,在文件AgRV2K_mem.ld中可以看到定义如图:
如果只用96K,则修改上边的SRAM_SIZE=96K即可。
修改文件并保存后,需要重启VSCODE工程,让设置项使能。
2. cpld中对后32K的使用;
cpld 使用后 32K,起始地址是从 0x20000000 + 96K 的地址开始。
即:从 0x20018000开始,长度32K,到 0x20020000 结束。
cpld 中对于 sram 的寻址方式和 mcu 相同。
cpld对sram的读写,请参考:
SDK 下的 examples\custom_ip\logic\ram2ahb.v 和 ahb2ram.v
六、小技巧 如何定义信号量数组:如果cpld里想用信号量数组,并使每个元素对应到不同PIN,可以在 ve 里定义如下:
mcu_a[0] PIN_31
mcu_a[1] PIN_32
mcu_a[2] PIN_33
mcu_a[3] PIN_34
...
如图:
那么,prepar LOGIC后,将在自动生成的cpld框架.v接口里,表示如下:
这里就表现为数组的形式了。 联系海振远科技
|