前言:
AGM是AG32 MCU, 可编程SoC和异构MCU的解决方案提供商, 海振远科技可提供全系列的开发板及SDK资料,方便用户从0开始,快速上手开发。
AGM AG32 MCU和FPGA 目前广泛应用于工业,消费,测试测量和医疗等场景。
AG32内置的FPGA( AGRV2K) 可升级替代EP570, EPM1270, LATTICE XO2-256,XO2-640, XO2-1200 等。
AG32 的管脚可以灵活定义,引脚与STM32。并且内置2KLE FPGA, 非常适合MCU + FPGA/CPLD的应用场景。
目录
一、CPLD控制PIN脚(LED闪烁)
二、MCU通过CPLD控制PIN脚
三、MCU通过寄存器方式操作CPLD
1. MCU如何访问CPLD
2. CPLD如何判别及响应
四、MCU通过AHB转APB方式操作CPLD外设
五、CPLD实现一个UartTx“外设”
六、DMA在CPLD中的使用
七、CPLD中如何使用RAM
八、更多样例
1.ADC如何从0创建的例程
2.ADC+CPLD控制led灯的例程
3.ADC+SpiFull的例程
案例列表如下:
1. CPLD控制PIN脚(LED闪烁)
2. MCU通过CPLD控制PIN脚(LED闪烁)
3. MCU通过寄存器方式操作CPLD;
4. MCU通过AHB转APB方式操作CPLD外设;
5. CPLD实现一个简单的UartTx的外设;
6. DMA在cCPLD中的使用(以adc为例);
7. CPLD中如何使用RAM;
8. 其他更多样例(不再详解,自行看代码注释)
这里所有用到的案例例程,如果需要,请联系海振远科技获取。
另外,所有例程请务必参照描述自己重建一份。一则,通过自己手动创建,能更清晰学习整个操作过程;二则,网盘上的例程都是基于当时版本的,在最新版本上未必兼容;三则,需要自己修改的往往是很少的代码(框架和工程都是自动生成,自己只需要填充自己的逻辑),不用费力气去复制整个工程。
以下进入正题。
一、CPLD控制PIN脚(LED闪烁)
功能描述:在 CPLD里驱动两个led灯的闪烁。
这是第一个使用到MCU+ CPLD联合编程的样例,也是最简单的一个。在这个样例中,MCU部分屏蔽掉对LED灯的控制,然后在 CPLD里来驱动两个LED灯的闪烁。
通过这个样例,会了解到:
1.从头到尾新建一个 CPLD工程;
2. CPLD中如何和外部PIN脚关联;
3.如何通过CLK来驱动LED的闪烁。
准备工作:复制上一份example工程作为试验工程;然后屏蔽掉main()函数中对TestGpio()的调用,改为while(1);放开platformio.ini文件中的ip_name和logic_dir两个选项(该动作为:开启自定义logic功能),如图:
操作步骤:
就是上节讲到的四步骤,先复习下:
1.在VE文件里配置引脚关系;
2.建立CPLD空工程(使用prepare LOGIC命令)并编写逻辑;
3.Quartus下进行工程转换(和综合);
4.Supra下编译出最终的logic.bin;
1. 在VE文件里配置引脚关系
可以先删除掉其他的引脚定义,只保留时钟配置。然后添加CPLD控制PIN脚的定义如下:
这里添加的 LED_D3 PIN_32:OUTPUT #LED2 解释如下:
LED_D3: 这里是CPLD中用到的信号名称
PIN_32: 正常的管脚名字
OUTPUT: 对CPLD来说是输出的方向。除了OUTPUT,还有INPUT和INOUT。通过这些关键字限制信号的输出方向。
#: VE中的注释符。
整行的意思是:定义一个LED_D3的信号(cpld的信号)绑定到Pin32脚上。当LED_D3高低变化时,Pin32将跟着变化。
2. 建立CPLD空工程(使用prepare LOGIC命令)并编写逻辑
点击VSCode左边栏的 prepare LOGIC,则自动生成空的CPLD工程。该CPLD工程在logic文件夹下。(如果不熟悉,请回看上节描述)
此时,打开quartus工程,可以看到 user_ip 中包含了上边定义的 LED_D3和LED_D2 两项:
然后,可以在该user_ip的模块外边(即:endmodule 之后)添加led模块代码如下:
此时,user_ip.v文件里其实有两个moudle: user_ip 和 led。
接下来,让led模块在user_ip模块内实例化,让两模块关联起来即可。
即:在 user_ip的module内添加led的实例化 如下:
这里的操作过程如果还是不理解,请联系海振远科技获取样例工程。
3. Quartus下进行工程转换
点击Quartus下的tools->TCL Scripts(如果不熟悉,参考上节介绍),等待转换编译完成。
4. Supra下编译出最终的logic.bin
再打开Supra软件,对该CPLD工程进行编译(如果不熟悉,参考上节介绍),等待编译完成。
到这里,全部编译logic的动作完成。
接下来,对该logic进行烧录、对code进行编译并烧录。开发板重新上电后,就可以看到2个led灯的闪烁了。
回顾:
1.对用户逻辑来说,总入口就是user_ip的module;
2.这里新建了一个led的module,里边使用了user_ip的两个led信号和sysclk信号;
3.led内部对sysclk进行了分频的使用;
二、mcu通过CPLD控制pin脚
功能描述:MCU通过gpio控制CPLD的信号,然后再传递到PIN脚。实现“MCU <-> CPLD <-> PIN”的控制逻辑。
之前单纯用MCU时,是在VE文件里直接定义gpio到PIN的映射。定义该映射后,MCU来操作gpio的高低,就控制了LED灯的亮灭。
现在是在之前的控制中,增加一个CPLD的环节。
具体样例中,用MCU的gpio(gpio4_1)来输入信号到CPLD,然后CPLD把这个信号关联到2个pin上(开发板的2个led),然后MCU中切换gpio4_1时,两个led灯交替闪烁。
通过这个案例,可以了解到:
1.MCU的信号如何传递到CPLD
2.CPLD如何反向控制MCU信号
在MCU和CPLD的交互中,两者之间传递信息的方式大致分为四种:
1. MCU传递信号给CPLD;(如:MCU的gpio传递高低信号到CPLD)
2. cpld传递信号给MCU;(如:CPLD对MCU产生中断信号)
3. MCU读写寄存器方式操作CPLD数据;
4. 不建议,CPLD做为主设备对MCU进行读写。
也就是说,在MCU和CPLD交互中,CPLD更像一个“外设”。
其中,前两种较为简单。后两种要使用AHB总线来操作。这个案例中讨论的就是这里的方式1.(方式2也会顺便讨论)
新建工程的过程,这里不再描述。只描述需要注意的改动点。
1. VE中定义3个信号:
LED_TEST1 PIN_31 # LED3
LED_TEST2 PIN_32 # LED2
GPIO4_1 iocvt_chn:OUTPUT
其中前两个是CPLD中信号到引脚,第三个是MCU到CPLD信号。
2. prepare LOGIC生成CPLD工程后,可以看到user_ip接口处的定义:
inout LED_TEST1,
inout LED_TEST2,
input iocvt_chn_out_data,
input iocvt_chn_out_en,
3.在user_ip.v中关联下信号:
assign LED_TEST1 = iocvt_chn_out_data;
assign LED_TEST2 = !iocvt_chn_out_data;
这样,
当iocvt_chn_out_data为高时,LED_TEST1为高,即PIN_31为高,led3亮
当iocvt_chn_out_data为高时,LED_TEST2为低,即PIN_32为低,led2灭
4.在mcu代码里,初始化gpio4_1并toggle切换:
SYS_EnableAPBClock(APB_MASK_GPIO4);
GPIO_SetOutput(GPIO4, GPIO_BIT1);
while (1) {
UTIL_IdleUs(200e3);
GPIO_Toggle(GPIO4, GPIO_BIT1);
}
然后就可以看到一个gpio4_1控制两个led灯交替闪烁。
如需样例工程,请联系海振远科技获取。
回顾:
1.在VE里定义了2个CPLD信号到Pin脚,1个MCU到CPLD的信号;
2.在CPLD里,直接对MCU来的一个信号,绑定到2个CPLD信号上,传递给PIN脚;
描述完MCU控制CPLD信号,顺便描述下CPLD控制MCU信号。
这种方式和1相近,只不过是反向。
可以在MCU中定义gpio4_2为输入并使能中断,则CPLD中设置信号高低时,将触发MCU的gpio中断。(当然,这里也可以用local int,但还未整理,需要自己尝试实现)
1. 在VE中定义信号:
GPIO4_2 iocvt_chn:INPUT
表示,MCU的gpio(gpio4_2)信号将来源于CPLD的iocvt_chn。
2. 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对应变化。
MCU端gpio部分不再描述。较为简单,也不再举例。
从这个案例开始,需要了解AHB总线的基础知识了。如果对AHB总线不是很了解,可以自行百度再学习下。
这里只描述用到的部分:
在AG32芯片内部,可以认为:CPU、RAM、CPLD、DMA,这四个部分是挂在AHB总线下的。其他的外设都是挂在APB总线下的。
挂在AHB下的4部分,CPU、DMA、CPLD 这3个会成为master端,可能来抢占AHB总线。
在真实使用中,CPLD经常被用于slave端,有点类似于“外设”,但却是挂在AHB下的。既然CPLD是挂在AHB下的,那么它的接口就是要符合标准AHB访问协议的。
那么,当MCU来访问CPLD的数据时,CPLD就要根据这个接口来做对应的处理。
整个访问过程分为两部分:
1. MCU如何访问CPLD的“寄存器”;
2. 当MCU访问时,CPLD如何判别及响应;
分别描述。
1. MCU如何访问CPLD
这部分很简单。
AG32在对地址编码中,CPLD的地址区间设定为:0x60000000 ~ 0x7FFFFFFF
就像RAM的地址区间是 0x20000000 ~ 0x20020000,flash的地址区间是0x80000000 ~ 0x800x0000 一样。
当MCU对大于0x60000000这个区间内的地址访问时,解码器会自动丢给CPLD的接口。这时MCU就相当于访问了CPLD的“寄存器”。
MCU是全局寻址,对这个空间的访问和对RAM(0x20000000起)空间的访问是一样的方式,在C代码中,可以这样写:
读CPLD:int cpRdReg = *((int *)0x60000000);
写CPLD:*((int *)0x60000004) = cpWtReg;
MCU端读写CPLD,直接通过上述语句就可以了。理解和操作上都比较简单。
2. CPLD如何判别及响应
这部分描述起来比较麻烦。总体是:CPLD被AHB总线接口触发,并且回应要遵循AHB总线协议。
当上述MCU读写动作发生时,AHB总线会把动作拆解为读写信号,传递到analog_ip.v(新版本是user_ip.v)的接口,用户CPLD程序需要响应该信号。
以下,以写动作 *((int *)0x60000004) = cpWtReg 为例,描述cpld端会发生的事情。
回顾下analog_ip.v中的接口部分:
其中slave_ahb_开头的一组信号,是CPLD作为主端时用的,暂时不用理会。
Mem_ahb_开头的一组信号,是CPLD作为从端使用的。
当MCU有读写操作时,mem_ahb_这组信号将发生变化。
几个信号的概述(更详细的讲解请自行百度):
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 [0:0] isNewAction = 0;
reg [31:0] hwdata_reg; //定义32位的hwdata_reg
always @(posedge sys_clock) begin //clk上升沿触发
if (mem_ahb_htrans == 2'b10) //NONSEQ状态,第一次传输
begin
isNewAction <= 1;
end
if (mem_ahb_htrans == 2'b00 && //IDLE状态,真正开始写
isNewAction && //新动作
mem_ahb_hwrite && //写 (0 读,1 写)
mem_ahb_haddr[23:0] == 'h00) //写地址为0x60000000(cpld内部用相对偏移)。
begin
hwdata_reg <= mem_ahb_hwdata; //把收到的数据给到hwdata_reg
isNewAction <= 0;
end
end
//这个过程,是把MCU写进来的数据收到hwdata_reg中
-----------------------------------------------
这部分的实例代码,请联系海振远科技获取:
注意:这里展示的,仅仅是基于AHB总线上的数据交互。
在实际应用中,比如要实现一个串口之类的,往往是慢速设备,这些是要挂载到APB上的。慢速设备要经过AHB到APB的bridge,才能最终使用。请继续往下看。
四、MCU通过AHB转APB方式操作CPLD外设
上节讲述了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] == ADDR_READ) //读地址为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] == ADDR_WRITE)//写地址为0x60000000(cpld内部用相对偏移)。
begin
TestLedCtrl <= !TestLedCtrl; //led灯切换,表示有写操作触发
awdata_reg <= apb_pwdata; //把收到的数据给到hwdata_reg
end
end
//这个过程,是把MCU写进来的数据收到hwdata_reg中
-----------------------------------------------
这个功能实现后,其实是个简单的“空外设”。可以用它做为实现复杂功能外设的基础。
这部分的实例代码,请联系海振远科技获取:
样例展示到这里,MCU和CPLD的交互上:交互信号、跟AHB交互数据、跟APB交互数据,基本的交互通路已经建立。
接下来,用户根据自己的需求,在CPLD中交互到数据后,编写自己需要的功能即可。
五、CPLD实现一个UartTx“外设”
功能描述:用CPLD实现一个Uart的tx功能,并且认为是一个“外设”。(注意:只是个简单的tx功能,没有rx,也没有更多的功能)
这个例程是对MCU于APB交互的进一步演练。在功能方面,包括:设置寄存器,读取寄存器。
它的CPLD实现逻辑,跟ADC样例思路是相同的,都是先AHB转APB,然后实例化外设,MCU写数据时APB接收后处理,MCU读数据时APB触发后处理。
读的时候,是读的串口的state状态;
写的时候,会把写的数据继续丢给uart模块处理(转化为IO的高低波形输出)
MCU端,在while(1)里边:
查询CPLD的写状态,当状态合适时,发数据给CPLD,CPLD根据时序转换为波形输出到定义的PIN。
更多细节,请参考CPLD工程中的代码和注释。
这部分的实例代码,请联系海振远科技获取。
这个样例完全理解完,就可以尝试读ADC的代码了。
在example/analog工程,展示了ADC模块做为外设,与MCU之间的数据交互(ADC采集后的数据,被MCU读取)。
相当于:ADC硬核+ADC的CPLD逻辑,实现了一个完整的“ADC外设”。
关于ADC/DAC/CMP的代码解析,请联系海振远科技获取。
六、DMA在CPLD中的使用
CPLD中实现DMA的逻辑:
MCU为master,CPLD为slave,MCU对CPLD的交互方式为存取寄存器的方式;
MCU中配置好DMA(读取CPLD中准备好的数据);
CPLD中准备好数据后,触发DMA信号,DMA自动搬运到MCU指定的RAM;
搬运一次后,DMA给CPLD一个clear信号,完成一次DMA搬运;
等到CPLD中再次准备好数据,将再次触发DMA信号,重复3和4;
对于CPLD来说,MCU来读取数据和DMA来读取数据,是一致的,CPLD无从区分到底是MCU来读还是DMA来读。
DMA来读取时,只是每次读完后会多给CPLD一个clear信号。
这部分的实例代码,请联系海振远科技获取。
在这个样例中,展示了两部分代码:
MCU中,配置DMA读取;为了测试,mcu会在另一地址给CPLD写数据;
CPLD中,会对MCU写进来的数据缓存,缓存后触发DMA的信号,让DMA来读取数据。而DMA从CPLD里读取数据后会给CPLD一个clear信号,标志一次DMA交互完成。
七、CPLD中如何使用RAM
1. CPLD本身有自带的RAM:
CPLD内部自带RAM,为4个M9K块,每个M9K大小为8192 bits。
(即:4个M9K总空间为4K bytes)
更详细信息,请参考《AGRV2K_Rev2.0.pdf》中的说明:
使用M9K时,直接在Quartus下创建IP就可以了。
2. 使用MCU这边的RAM:
除了CPLD自带的内存,CPLD还可以使用MCU的内存sram。(这部分使用不太容易,如果CPLD基础薄弱,不建议使用)
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
如果想共用MCU的一段sram,在这段RAM中,比如CPLD只写,MCU只读,也可以。只需要MCU那边强制访问这片区域就行了。
八、更多样例
这些样例不再解释,具体请联系海振远科技获取。
样例包括:
1.ADC如何从0创建的例程
2.ADC+CPLD控制LED灯的例程
3.ADC+SpiFull的例程
AG32概述:
AG32系列32位微控制器旨在为MCU用户提供新的自由度和丰富的兼容外设以及兼容的引脚和功能。AG32VF407产品系列提供卓越的品质、稳定性和非凡的价值。
器件特征:
■ 最大 CPU 速度为 248 MHZ
■ 从 Flash 执行零等待
■ SRAM 128KB.FLASH 1MB
■ 支持浮点
■ 1个CAN2.0.5个UART2个I2C
■ 2 个基本定时器,5 个高级定时器
■ 支持 SDIO 、以太网 MAC
■ 支持 USB FS+OTG
■ 看门狗
■ 3个 12 位、最高 3M SPS ADC(17 通道)、2个 DAC
■ 2x 比较器
■ RTC, SPI
■ 内置2KLE FPGA逻辑单元
联系海振远科技
电话:0755-2780 9180; 15323895320;
邮箱: tech@hizyuan.com
Lucy@hizyuan.com