文章
  • 文章
搜索
首页 >> 技术文档 >>官方资料 >>MCU文档 >> AG32 mcu+cpld 联合交互编程(案例和描述)第二部分(手把手教程)
详细内容

AG32 mcu+cpld 联合交互编程(案例和描述)第二部分(手把手教程)

在看这里具体案例前,请确认已经了解了上一章节描述的概念

.

案例列表如下:

1. cpld控制pin脚(LED闪烁)

2. mcu通过cpld控制pin脚(LED闪烁)

3. mcu通过寄存器方式操作cpld;

4. mcu通过ahb转apb方式操作cpld外设;

5. cpld实现一个简单的UartTx的外设;

6. dma在cpld中的使用(以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功能),如图:

210d821e-41a0-4c7a-85a4-7d3566de6490.png

操作步骤:

6a05b1e2-52ae-4372-8ac6-5391a7252fa2.png



1. 在VE文件里配置引脚关系

可以先删除掉其他的引脚定义,只保留时钟配置。然后添加cpld控制Pin脚的定义如下:


cbe8be3a-3151-4dcb-b052-37e4f3bdfe62.png

这里添加的 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 两项:

ad13a1dc-6879-42cf-9dec-5e8b848e7c6e.png

然后,可以在该user_ip的模块外边(即:endmodule 之后)添加led模块代码如下:

78d6f80c-9fe6-4ae3-a5ce-4ae053b89d09.png


此时,user_ip.v文件里其实有两个moudle: user_ip 和 led。

接下来,让led模块在user_ip模块内实例化,让两模块关联起来即可。

即:在 user_ip的module内添加led的实例化 如下:


a1d119ea-20aa-4148-8918-7679ec4e28a7.png

这里的操作过程如果还是不理解,请自行下载样例工程

1.led灯闪烁.zip



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信号

0f501fe1-9ef7-46a2-8fbf-d418ccaa6967.png


新建工程的过程,这里不再描述。只描述需要注意的改动点。

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灯交替闪烁。

这里的样例,参考以下文件

3.mcu信号到cpld到pin.zip



回顾:

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部分不再描述。较为简单,也不再举例。


三、mcu通过寄存器方式操作cpld

从这个案例开始,需要了解AHB总线的基础知识了。如果对AHB总线不是很了解,可以自行百度再学习下。这里有个比较好的讲解:https://blog.csdn.net/weixin_46022434/article/details/104987905

这里只描述用到的部分:

在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中的接口部分:

25cd3773-a5d5-461d-a2e4-9ee9b3cdc55d.png

其中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信号后,开始数据传输。

大致如下图(无等待类型的图):


370250df-c342-44f4-8396-846fd347934f.png

比如,mcu要读0x60000004的寄存器:

mcu端直接C语言这样调用:int cpRdReg = *((int *)0x60000004);

cpld端,可以根据以上信号做如下处理:

3f08d28d-24f3-4f8d-8d9c-bb3cec40628f.png


以上代码,加入到analog_ip.v的module下,就可以完成cpld对mcu读动作的响应。

比如,mcu要写0x60000000的寄存器:

mcu端直接C语言这样调用:*((int *)0x60000000) = value;

cpld端,可以根据以上信号做如下处理:

5a05984d-7759-44b8-b25f-bba3516a1434.png


这部分的实例代码,参考以下例程

5.mcu读写cpld寄存器.zip


注意:这里展示的,仅仅是基于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的一组信号。使用如下图:

93fe5340-03bc-4c8e-b21b-0932045feb73.png

如果实现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端,可以根据以上信号做如下处理:

9f48dab6-9de5-4350-b2d8-1329a768facc.png

mcu写操作时:

  比如,mcu要写0x60000000的寄存器:

  mcu端直接C语言这样调用:*((int *)0x60000000) = value;

  cpld端,可以根据以上信号做如下处理:

5b90f930-a6dc-479e-8b90-971a7b56c692.png

这个功能实现后,其实是个简单的“空外设”。可以用它做为实现复杂功能外设的基础。


这部分的实例代码,参考以下例程

5.mcu读写cpld寄存器.zip


样例展示到这里,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工程中的代码和注释。

这部分的实例代码,参考以下例程

6.UartTx例程.zip


这个样例完全理解完,就可以尝试读ADC的代码了。

在example/analog工程,展示了ADC模块做为外设,与mcu之间的数据交互(ADC采集后的数据,被mcu读取)。

相当于:ADC硬核+ADC的cpld逻辑,实现了一个完整的“ADC外设”。

关于ADC/DAC/CMP的代码解析,参考以下文档:

Analog代码分析.pdf


六、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来读取数据,是一致的,cpld无从区分到底是mcu来读还是dma来读。

dma来读取时,只是每次读完后会多给cpld一个clear信号。

这部分的实例代码,参考以下例程

7.cpld中配合实现mcu的dma读取.zip


在这个样例中,展示了两部分代码:

  1. mcu中,配置dma读取;为了测试,mcu会在另一地址给cpld写数据;

  2. 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_Rev3.0.pdf》中的说明:

d926bdba-b4ba-49f7-aa8b-e2e482ccb677.png

使用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 中可以看到定义如图:

9cd3d2ad-cdbd-4e95-b36b-c6dcd06cf15b.png


如果只用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


99aabc6a-4ca6-456f-87e5-573bfe76988b.png


如果想共用mcu的一段sram,在这段ram中,比如cpld只写,mcu只读,也可以。只需要mcu那边强制访问这片区域就行了。


八、更多样例(样例汇总),参考如下


1.led灯闪烁.zip

2.adc例程(新建cpld空工程并添加adc).zip

4.adc+cpld控制led.zip

6.UartTx例程.zip

5.mcu读写cpld寄存器.zip

7.cpld中配合实现mcu的dma读取.zip

spi双向传输例程.zip

8.adc+spiFull的例程.zip



在线商城.png


需要获取更多的“资料”和“支持”和“样片测样申请”可以联系我们

提供“芯片测样-发送资料-技术支持-批量采购”


联系方式: 姚工 13661545024(VX同号)

(加的时候备注下公司名和个人名字)

在线商城:agm-micro.taobao.com

公司网站:www.agm-micro.com

资料网站: www.tcx-micro.com










关于我们

品牌中心

产品中心

新闻动态

咨询热线:13661545024(全国技术销售热线)

上海天晨芯科技有限公司

销售邮箱:sales@chipmorn.com

技术邮箱:jun.yao@chipmorn.com

公司地址:上海浦东新区东方路1365号5号楼

seo seo