|
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灯的闪烁。 通过这个样例,会了解到:
准备工作:复制上一份example工程作为试验工程;然后屏蔽掉main()函数中对TestGpio()的调用,改为while(1);放开platformio.ini文件中的ip_name和logic_dir两个选项(该动作为:开启自定义logic功能),如图: 操作步骤: 1. 在VE文件里配置引脚关系 可以先删除掉其他的引脚定义,只保留时钟配置。然后添加cpld控制Pin脚的定义如下: 这里添加的 LED_D3 PIN_32:OUTPUT #LED2 解释如下:
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. VE中定义3个信号:
2. prepare LOGIC生成cpld工程后,可以看到user_ip接口处的定义:
3.在user_ip.v中关联下信号:
4.在mcu代码里,初始化gpio4_1并toggle切换:
然后就可以看到一个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中定义信号:
2. prepare LOGIC工程后,可以看到analog_ip.v接口中的信号:
mcu端gpio部分不再描述。较为简单,也不再举例。 三、mcu通过寄存器方式操作cpld从这个案例开始,需要了解AHB总线的基础知识了。如果对AHB总线不是很了解,可以自行百度再学习下。这里有个比较好的讲解:https://blog.csdn.net/weixin_46022434/article/details/104987905 这里只描述用到的部分:
整个访问过程分为两部分: 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时序,在一次传输中,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端,可以根据以上信号做如下处理: 这部分的实例代码,参考以下例程 注意:这里展示的,仅仅是基于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下如何实现跟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端,可以根据以上信号做如下处理: 这个功能实现后,其实是个简单的“空外设”。可以用它做为实现复杂功能外设的基础。 这部分的实例代码,参考以下例程 样例展示到这里,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的逻辑:
对于cpld来说,mcu来读取数据和dma来读取数据,是一致的,cpld无从区分到底是mcu来读还是dma来读。 dma来读取时,只是每次读完后会多给cpld一个clear信号。 这部分的实例代码,参考以下例程 在这个样例中,展示了两部分代码:
更多信息参考工程 源码及注释。 七、cpld中如何使用ram1. cpld本身有自带的ram: cpld内部自带ram,为4个M9K块,每个M9K大小为8192 bits。 (即:4个M9K总空间为4K bytes) 更详细信息,请参考《AGRV2K_Rev3.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那边强制访问这片区域就行了。 八、更多样例(样例汇总),参考如下需要获取更多的“资料”和“支持”和“样片测样申请”可以联系我们 提供“芯片测样-发送资料-技术支持-批量采购” 联系方式: 姚工 13661545024(VX同号) (加的时候备注下公司名和个人名字) 在线商城:agm-micro.taobao.com 公司网站:www.agm-micro.com 资料网站: www.tcx-micro.com |