1.必要的基础知识
为了更快的完成在FPGA上实现ARM Cortex-M3软核,一些必要的基础知识还是要有的!
FPGA开发基础知识,如FPGA开发流程,设计、综合、布局、布线、约束、下载
Xilinx Vivado开发环境使用基础,如BlockDesign设计方式,管脚分配,Bit流文件生成与下载
ARM Cortex-M3内核的使用基础,如STM32、MM32、GD32、CH32等微控制器的开发。
Keil-MDK开发环境的使用基础,基本的工程建立、编译、下载流程。
如果以上知识都具备,那么,恭喜你!可以在2小时内完成ARM Cortex-M3软核在FPGA上的实现。
2.Cortex-M3 FPGA IP核下载
首先,我们需要从ARM官网上获取ARM Cortex-M3 FPGA软核IP包。
下载地址如下:
https://silver.arm.com/browse/AT426
文件名称:Cortex-M3 DesignStart FPGA-Xilinx edition(r0p1-00rel0)
文件大小:7.52MB
MD5SUM:cd67536c29023429cde47130d51b6f49
官网下载需要先注册账号,如果下载速度很慢,可以在公众号后台回复:220318,获取下载链接,复制到浏览器下载。
![ARM官网 3341503b19bb2ceaab9bac25736475d4.png](https://i-blog.csdnimg.cn/blog_migrate/81e3369f3433113a9379339f696fbb87.png)
压缩包解压之后,共有4个文件夹:
![压缩包内容 b22926d1720298eaba11115a9823fd31.png](https://i-blog.csdnimg.cn/blog_migrate/d1fcf0f5fde63d6a9f5ff33dd08e68a1.png)
各个文件夹存放的内容:
docs
存放ARM Cortex-M3处理器参考手册、DesignStart FPGA版本使用说明、基于Arty-A7开发板的顶层BlockDesign框图等文件。
hardware
存放基于Digilent Arty-A7开发板的Vivado工程,顶层BlockDesign文件,管脚约束文件,Testbench文件等。
software
存放Keil-MDK工程,SPI Flash的编程算法文件等。
vivado
包括DesignStart Cortex-M3 Xilinx FPGA版本的IP核文件,其中Arm_ipi_repository文件夹就是内核源文件了,IP文件内容已经加密,没有可读性。
![IP核源码 395f163be5ed45518443c997cb00488c.png](https://i-blog.csdnimg.cn/blog_migrate/037e5f7247f245f6b9d94e220a9726ca.png)
3.硬件准备
为了完成DS CM3在FPGA上的搭建,我们至少需要以下硬件:
一块Artix-7™开发板,用于构建Cortex-M3软核SoC,我使用的是正点原子达芬奇Pro开发板,FPGA型号为XC7A100T。
Xilinx FPGA下载器,用于下载软核Bit流到FPGA,如Platform Usb Cable,JTAG-HS2/HS3等。
ARM Cortex-M3调试器,用于调试ARM核程序下载和调试,如JlinkV9,Jlink-OB等。
官方的DS CM3 IP核是基于Digilent的Arty-A7开发板,FPGA型号为XC7A35T/100T,Vivado版本为v2019.1,如果你手头正好有这块开发板,那么可以直接使用官方提供的示例工程。
Digilent Arty-A7开发板:
![arty-a7开发板 0414415b39869884fe6fce067e56d917.png](https://i-blog.csdnimg.cn/blog_migrate/33ffc82e6f2b399a2c078592bc9480ac.png)
正点原子达芬奇Pro开发板:
![正点原子达芬奇Pro开发板 30ce838b9423862cb69c11835bae89fb.png](https://i-blog.csdnimg.cn/blog_migrate/da1f808c0e3d2809d626971cced2416a.png)
4.软件准备
Xilinx Vivado开发环境,官方建议版本为2018.2以上,我使用的是2018.3版本
Keil MDK开发环境,如5.33版本
DS_CM3的Keil器件包
从Keil官网上下载DesignStart Cortex-M3所专用的器件支持包,下载链接如下:
https://keilpack.azureedge.net/pack/Keil.V2M-MPS2_DSx_BSP.1.1.0.pack
5.Cortex-M3软核搭建
准备好以上软硬件,就可以开始Cortex-M3软核的搭建了。
首先,新建一个文件夹,命名为cortex_m3_on_xc7a100t,用于存放本次示例所有的工程文件,并新建以下几个文件夹:
![目录结构 8db5ee5066e3160c7d77e87e369e8a10.png](https://i-blog.csdnimg.cn/blog_migrate/1ae8325e8aa0b7d1a85e1b8b0aa70b40.png)
每个文件夹的功能:
bd文件夹
用来存放BlockDesign设计
cm3_core文件夹
用来存放的是ARM Cortex-M3内核IP核文件,
doc文件夹
用来存放设计文档
flash文件夹
用来存放生成的bit和mcs文件
rtl文件夹
用来存放用户设计的verilog源文件
xdc文件夹
用来存放管脚、时序约束文件
其中cm3_core文件夹,需要将官方压缩文件文件中的Arm_ipi_repository文件夹复制过来,路径为AT426-BU-98000-r0p1-00rel0\vivado\Arm_ipi_repository
以上文件夹准备好之后,就可以开始新建工程了。
5.1 新建Vivado工程
打开Vivado 2018.3,打开工程创建向导,输入工程名称,工程的存放路径为之前我们新建的文件夹。
![新建工程 230e040ab8835385b42c743857712ae9.png](https://i-blog.csdnimg.cn/blog_migrate/8ffe57c3fa0aafea282c5af7ffe47273.png)
选择FPGA芯片的完整型号:XC7A100TFGG484。
![选择芯片型号 06ef7f959d1589f3a2b0c270bf4525b7.png](https://i-blog.csdnimg.cn/blog_migrate/7c6574021b43508a0128cd16e0b35ebc.png)
最终创建完成之后的工程目录
![Vivado工程目录 a81909d0df18b47edf989dbe5af0a729.png](https://i-blog.csdnimg.cn/blog_migrate/96b094ec60e03cfd284bc389769446b4.png)
5.2 添加IP核搜索路径
为了能在BlockDesign中搜索到ARM Cortex-M3处理器IP核,我们需要把ARM 软核IP所在的路径添加到搜索路径。
![添加到搜索路径 9becf556f21aac41105d5c75c4054501.png](https://i-blog.csdnimg.cn/blog_migrate/5645ce2c181fb088ebe3927a51ed83e4.png)
5.3 创建BlockDesign设计
为了方便后续使用图形化的方式连接各IP核,我们采用BlockDesign图形化的设计方式,这样可以快速的搭建出一颗定制化的软核处理器。
新建BlockDesign,命名为cm3_core,保存到最初创建的bd文件夹中。
在画布中添加Cortex-M3处理器核:
![添加ARM核 20398b274f0c02f02915e0467b26b8ff.png](https://i-blog.csdnimg.cn/blog_migrate/15cf82b886acb0afed9ded5ad06abd25.png)
双击Cortex-M3 IP核进行一些基本配置,我们不需要Trace功能,选择No Trace,使用SWD接口调试,禁用JTAG端口:
![配置ARM核 a9dde11312bb7182894b2faa421c4461.png](https://i-blog.csdnimg.cn/blog_migrate/039345a2e6d9cc45e7f1fbfbf2f21153.png)
指令空间和数据空间大小,这里设置成64KB,都不进行初始化。
![ITCM核DTCM配置 38e8cf4cb74984eb816eefa163d9984c.png](https://i-blog.csdnimg.cn/blog_migrate/c20586938af954604568052fc75ce2be.png)
5.4 添加一些必要的IP核
时钟PLL
用于提供给内核、总线、外设时钟,这里我们配置成50MHz单端输入,PLL输出配置成50MHz,如果时钟频率设置更高,综合后会提示WNS,TNS时序不满足,可能会影响系统的正常运行。
处理器复位IP
用于提供内核、外设、互联组件所需要的复位信号,不需要进行定制,保持默认设置。
总线互联IP
Cortex-M3内核为AHB总线,而且内部已经转换成了AXI3总线,而Xilinx官方提供的GPIO/UART等外设IP核是AXI4-Lite总线,所以需要添加一个总线互联矩阵,用于将不同协议进行转换,从机数量配置为1,主机数量配置为2,连接到处理器的SYS总线。
基本逻辑门IP
Cortex-M3内核需要低电平复位,而复位IP输出为高电平复位,需要在中间插入一个非门来进行转换。
常量IP
本次软核搭建不涉及中断部分,所以IRQ和NMI都给定常量0即可,如果需要将中断接入处理器,可以通过Concat核将多个中断源合并成一个连接到IRQ。
将以上IP添加到BlockDesign画布中,并按照下图进行连接:
![原理图连接 5c90f21a1b9ecfdcc3f02fdaedfd8269.png](https://i-blog.csdnimg.cn/blog_migrate/cbeff729766dffcf766e43c6fa5bc222.png)
从官方手册中可以知道,ARM提供的软核IP中已经包括了ITCM和DTCM存储器,所以我们无需添加外部的BRAM来作为程序和数据的存储区。
![Cortex-M3内核结构 8d1c740d02b668368a47714d4732efd1.png](https://i-blog.csdnimg.cn/blog_migrate/e9c5c57fa52c3f25f811e7f2b1c74d8b.png)
内核中提供ITCM和DTCM都是基于RAM实现,这也就意味着后续我们使用Keil下载程序只是下载到RAM中,掉电数据会丢失。
至此,ARM Cortex-M3处理器内核就搭建完成了,下面来添加GPIO和UART外设。
5.5 添加GPIO和UART外设
一些常用的单片机,如STM32,芯片内部的TIM、UART、SPI、CAN等外设一般是固定数量的,而我们使用FPGA来搭建ARM软核SoC就比较灵活了,如果你不需要SPI,那就不用添加SPI外设,需要10个UART就添加10个UART,外设配置比较灵活,当然这些外设都是基于FPGA逻辑资源实现的,实际添加的数量会受限于FPGA芯片的逻辑资源大小。
下面以添加一组AXI GPIO和一组AXI UART为例,介绍如何使用ARM软核来控制这两个外设。
Xilinx官方提供的AXI GPIO外设具有以下特性:
内部有两个通道,通道1和通道2,每个通道最多支持32个管脚
每个管脚可以配置成输入或输出模式
每个管脚可以设置复位初值
支持中断输出
提供的AXI UART外设有以下特性:
全双工
支持5-8位数据位
支持奇偶校验
可配置波特率110-230400
这里我们将GPIO配置成双通道,通道1为输出模式,低4位用于连接LED,通道2为输入模式,低4位用于连接按键。
![GPIO配置 4be730ede5489c79fd51da772cde26ac.png](https://i-blog.csdnimg.cn/blog_migrate/a005b822420156194412c32c7f3ba7ac.png)
UART配置成115200波特率,8位数据位,无奇偶校验。
![UART配置 7ed577277aeb7d4a8223efd1578ad50b.png](https://i-blog.csdnimg.cn/blog_migrate/4d0454bde0c956baf0f5e5055b811526.png)
配置完成之后,将它们连接的到互联IP的主机接口上:
![原理图连接 f3e608675b348464938c3d1e8f5acff3.png](https://i-blog.csdnimg.cn/blog_migrate/99ff2cb5afd5f13b1108b9d631be781b.png)
这两组IP的时钟可以和处理器使用同样的时钟,复位可以使用复位IP输出的外设复位信号。
关于AXI GPIO和AXI UART的详细使用,可以查看官方文档:
pg144-axi-gpio.pdf
https://www.xilinx.com/support/documentation/ip_documentation/axi_gpio/v2_0/pg144-axi-gpio.pdf
pg142-axi-uartlite.pdf
https://www.xilinx.com/support/documentation/ip_documentation/axi_uartlite/v2_0/pg142-axi-uartlite.pdf
5.6 SWD接口的引出
官方的DesignStart IP核资料中,除了Cortex-M3处理器,还有一个DAP-Link调试核,如果使用DAP-Link调试器需要添加这个IP核。
![DAP-Link e777e06a2c1f8676967253dca5db3dd1.png](https://i-blog.csdnimg.cn/blog_migrate/e9e44231f3c9ba5f2f5d243c4f329efc.png)
这里我们不使用DAP-Link调试器,而是使用Jlink SWD模式。SWD模式一共需要两根线,一个是SWCLK时钟信号,一个是SWDIO双向数据信号,处理器提供了3个管脚:SWDI,SWDO和SWDOEN,我们还需要实现一个双向端口模块。
基于IOBUF原语实现的双向端口模块,内容如下:
module swdio_tri_buffer(
//Inputs
input swd_o,
input swd_oe,
//Outputs
output swd_i,
//Inouts
inout swd_io
);
IOBUF swd_iobuf_inst(
.O(swd_i),
.I(swd_o),
.IO(swd_io),
.T(!swd_oe)
);
endmodule
将它添加到我们的设计中。
![SWD接口连接 a7c6a7d8f93d65c0a9a4d1db31fae906.png](https://i-blog.csdnimg.cn/blog_migrate/a35a045a88afd0d7c59dd3edfd6e205a.png)
最终的BlockDesign设计如下图所示:
![原理图连接 cf4221dc10cc9194b02b1f885783be3d.png](https://i-blog.csdnimg.cn/blog_migrate/8392d1e44786930ace9de7a5dbab8d35.png)
5.7 分配外设基地址
添加完外设IP之后,我们还需要对外设进行基地址和空间分配,在地址编辑框,右键选择自动分配。
![基地址分配 5c77d55dfde7ca963350921a47142531.png](https://i-blog.csdnimg.cn/blog_migrate/767749be4e16550782754ece092f6e45.png)
分配完成之后,使用设计验证(Validate Design)功能,可以检查当前BlockDesign设计连接的合法性。
![验证设计 d8560b69e7e69968f3372b6427891a19.png](https://i-blog.csdnimg.cn/blog_migrate/2c10cdcd9bce642a5899a126d46ad759.png)
5.8 生成Wrapper并例化到顶层
为了方便后续添加自定义的FPGA逻辑模块,我们将Cortex-M3软核处理器作为一个处理器例化到顶层设计中。
在BlockDesign源文件上右键,先选择Generate Output Products
,耐心等待生成完成之后,选择Create HDL Wrapper
。
![生成Wrapper 4e1feae73f6293bfebe4a07e4e71e488.png](https://i-blog.csdnimg.cn/blog_migrate/de4d35857fd7bc3cc099028d14101a8a.png)
之后就会生成一个_wrapper的verilog文件。
新建顶层文件top_hdl.v并保存到rtl文件夹,将_wrapper例化到顶层。
module top_hdl(
//Inputs
input clk,
input rst_n,
input swclk,
input uart_rxd,
input [3:0] sw,
//Outputs
output [3:0] led,
output uart_txd,
//Inouts
inout swdio
);
cm3_core_wrapper cm3_core_wrapper_ut0(
//Inputs
.cm3_clk(clk),
.cm3_resetn(rst_n),
.cm3_gpio_in_tri_i(sw[3:0]),
.cm3_swclk(swclk),
.cm3_uart_rxd(uart_rxd),
//Outputs
.cm3_gpio_out_tri_o(led[3:0]),
.cm3_uart_txd(uart_txd),
//Inouts
.cm3_swdio(swdio)
);
endmodule //top_hdl end
5.9 管脚分配
综合(Synthesis)完成之后,使用Vivado的图形化工具进行管脚分配,尤其注意要将SWDIO和SWDCLK引出到排针管脚上,方便后续使用外接的Jlink调试器进行ARM程序下载。
![分配管脚 74bd803086b4afc93b68c96fab43b090.png](https://i-blog.csdnimg.cn/blog_migrate/ea821c83c37c6bea821a56fbdce12b69.png)
或者直接新建XDC文件,使用约束语句进行管脚分配。
部分约束语句:
set_property PACKAGE_PIN R4 [get_ports clk]
set_property PACKAGE_PIN V13 [get_ports swclk]
set_property PACKAGE_PIN V14 [get_ports swdio]
set_property PACKAGE_PIN E14 [get_ports uart_rxd]
set_property PACKAGE_PIN D17 [get_ports uart_txd]
set_property PACKAGE_PIN U7 [get_ports rst_n]
set_property PACKAGE_PIN V9 [get_ports {led[3]}]
set_property PACKAGE_PIN Y8 [get_ports {led[2]}]
set_property PACKAGE_PIN Y7 [get_ports {led[1]}]
set_property PACKAGE_PIN W7 [get_ports {led[0]}]
set_property PACKAGE_PIN T4 [get_ports {key[3]}]
set_property PACKAGE_PIN T3 [get_ports {key[2]}]
set_property PACKAGE_PIN R6 [get_ports {key[1]}]
set_property PACKAGE_PIN T6 [get_ports {key[0]}]
如果你的板子和我的(正点原子达芬奇Pro)一样,那么可以直接使用以上管脚约束。
如果你分配的时钟管脚不是FPGA的全局时钟管脚,需要添加BUFG原语进行缓冲。
5.10 Bit流文件生成和下载
我的板子使用的是QSPI Flash,为了提高下载和启动速度,在生成Bit流时,配置生成选项:数据压缩、50M读取速度,4位数据线。
![生成Bit流配置 aba584bb624c28fecfd8fea457541e8c.png](https://i-blog.csdnimg.cn/blog_migrate/2f0962e50e1a9e9d64fe4308db559a3a.png)
或者直接使用XDC语句进行约束:
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property CFGBVS VCCO [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]
以上约束不是必须的,只是为了提高下载和配置速度。
耐心等待工程综合完成,生成Bit流文件,综合的速度和处理器主频、核心数有关。
和常规的FPGA下载方式一样,将生成的软核Bit文件通过Xilinx下载器下载到FPGA内部,先不要固化到外部SPI Flash 。
手头没有Xilinx下载器的,可以参考之前的文章,自己做一个JTAG-HS2下载器!
5.11 Jlink连接测试
下载完成之后,现在FPGA内部运行的就是一颗基于ARM Cortex-M3的软核处理器了,使用Jlink等调试工具可以连接到芯片。
将Jlink调试器的SWCLK和SWDIO连接到我们分配的管脚V13和V14上。
手头没有Jlink的,也可以参考之前的文章,自己做一个Jlink-OB!
使用Keil开发DesignStart Cortex-M3软核的程序,需要先安装一个DesignStart专用的器件包。
下载地址如下:
https://keilpack.azureedge.net/pack/Keil.V2M-MPS2_DSx_BSP.1.1.0.pack
打开一个STM32 Keil工程,器件修改为刚刚安装的ARM DS_CM3,在Option->Debug-Setting
界面中选择SWD方式,第一次连接会提示需要选择一个器件,这里选择Cortex-M3:
![选择器件型号 393d2cf2b11ecf802fec39e7865d3861.png](https://i-blog.csdnimg.cn/blog_migrate/3bc90b2b3a55fb4547902220a55489f5.png)
如果以上配置均正确,就能看到已经连接到的ARM Cortex-M3核心。如果没有,说明FPGA工程配置有错误,需要确认是否和以上配置流程一致。
![连接到ARM核心 e6728748b21e7576d9ab3323263e8c82.png](https://i-blog.csdnimg.cn/blog_migrate/752b245864c0d66ad3898facefe52a63.png)
至此,ARM Cortex-M3软核基本搭建完成,接下来我们使用Keil来编写ARM核的程序,实现GPIO和UART的控制。
6.Cortex-M3软核程序设计
和常规的ARM Cortex-M3内核单片机开发流程类似,使用Keil新建工程,源文件,根据外设使用手册,读写指定的寄存器实现GPIO的控制,UART数据写入,编译下载,调试。
在之前创建的cortex_m3_on_xc7a100t
文件夹下,新建mdk_prj文件夹,用于保存Keil-MDK的工程,并新建以下3个文件夹:
application //用户源文件
object //编译生成的文件
project //Keil的工程文件
6.1 新建Keil工程
打开Keil-MDK,选择Project->New Project
,新建一个工程,命名为ds_cm3_prj,保存到project目录下。
![Keil工程目录 58c4238b48f2118e7f7e716cc32db931.png](https://i-blog.csdnimg.cn/blog_migrate/0f34657c63e5b72d5f83ff5eb59388e8.png)
器件型号选择我们新安装的ARM Cortex-M3 DS_CM3内核。
![选择器件型号 1e32a606b5d2209056561a0624bfd2ae.png](https://i-blog.csdnimg.cn/blog_migrate/2d8d11a6514cf947ae9dda154e84d91d.png)
组件管理界面中,添加CMSIS内核文件和Startup启动文件:
![添加内核文件 8e3230f08b2a5d694d1a9246c3088cb2.png](https://i-blog.csdnimg.cn/blog_migrate/c9c7748a97487279ec877023cffc10db.png)
并按照如下结构组织文件:
![文件结构 7b9f38d3992c251fb46859557a84d632.png](https://i-blog.csdnimg.cn/blog_migrate/731dbf816361fadcc9be5d6b7edb646a.png)
6.2 设置RAM和ROM地址
在工程选项中设置片上ITCM的起始地址0x0、大小64K,片上DTCM起始地址0x20000000、大小64K:
![RAM地址配置 3637d41d43898b6298b3270a3cfda70c.png](https://i-blog.csdnimg.cn/blog_migrate/47f5da5c68b8ecc970d88e04b82a00b9.png)
起始地址来源于使用手册中图4-1系统内存地址映射,可以看到其中ITCM和DTCM的起始地址:
![ITCM和DTCM起始地址 043a54095ec1af19fd7551b3319e492f.png](https://i-blog.csdnimg.cn/blog_migrate/8e7a4cef31c8a0c55fb716e00df982c0.png)
大小是我们在Cortex-M3内核配置中设置的大小:
![ITCM和DTCM大小 2581e083544eb392c30aa1ed3c4cd8cb.png](https://i-blog.csdnimg.cn/blog_migrate/596562a2dcb88ad95652b76533f6fcb3.png)
设置完成之后,新建main.c文件,输入以下内容,编译工程,应该无错误输出。
#include "DS_CM3.h"
#include "system_DS_CM3.h"
int main(void)
{
while(1)
{
}
}
6.3 GPIO输入输出控制
通过查看AXI GPIO的使用手册,通道1的数据寄存器偏移地址为0,通道2的数据寄存器偏移地址为0x08,根据Vivado中的连接,LED连接到通道1,按键连接到通道2上,所以只需要对这两个寄存器地址进行读写,就可以实现LED的控制和拨码开关状态的读取。
![AXI GPIO寄存器定义 2760aa95b17b80e19f7ece390c02c66a.png](https://i-blog.csdnimg.cn/blog_migrate/6514196a54cba6b1c48f0a2872cd8ccb.png)
在Vivado地址分配界面,可以看到GPIO和UART的基地址分别为:0x4000_0000和0x4060_0000。
![外设基地址 41202887bb2d4ac13bdf2c285aeff096.png](https://i-blog.csdnimg.cn/blog_migrate/7a4340652b7b51eeefaa863bbada8488.png)
LEL控制和拨码开关读取:
*(volatile uint32_t *) (0x40000000+0x0) = 0x0f; //GPIO通道1低4位写1
*(volatile uint32_t *) (0x40000000+0x0) = 0x00; //GPIO通道1低4位写0
uint32_t sw = 0;
sw = *(uint32_t *) (0x40000000+0x08); //获取GPIO通道2的32位输入状态
6.4 串口数据发送和接收
向串口发送FIFO写入一字节数据:
while((*(volatile uint32_t *)(0x40600000 + 0x08)) & 0x08 != 0x08); //等待发送FIFO不满
*(volatile uint32_t *) (0x40600000+0x04) = 0x41; //向串口发送FIFO写入字符'A'=0x41
从串口接收一字节数据:
uint8_t dat = 0;
if((*(volatile uint32_t *)(0x40600000 + 0x08)) & 0x01 == 1) //串口接收FIFO中有数据
dat = (*(volatile uint32_t *)(0x40600000 + 0x00)); //从接收FIFO中读取1字节数据。
关于AXI GPIO和AXI UART寄存器的详细说明,可以查看官方文档:
pg144-axi-gpio.pdf
https://www.xilinx.com/support/documentation/ip_documentation/axi_gpio/v2_0/pg144-axi-gpio.pdf
pg142-axi-uartlite.pdf
https://www.xilinx.com/support/documentation/ip_documentation/axi_uartlite/v2_0/pg142-axi-uartlite.pdf
6.5 延时函数实现
为了让LED的变化,可以被人眼所看到,需要使用延时函数对亮灭进行延时。
使用系统滴答定时器实现一个延时函数:
volatile uint32_t cnt = 0; //volatile类型
void SysTick_Handler(void)
{
cnt++;
}
void delay_ms(uint32_t t)
{
cnt = 0;
while(cnt-t>0);
}
为了让延时函数准确延时,我们还需要更改工程中的系统时钟频率,和FPGA中配置的内核时钟保持一致。
完成的main.c文件内容:
#include "DS_CM3.h"
#include "system_DS_CM3.h"
//C库
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#define BASEADDR_LED 0x40000000
#define BASEADDR_UART 0x40600000
#define CHANNEL_LED 1
#define CHANNEL_SW 2
#define XGPIO_CHAN_OFFSET 8
#define XGpio_WriteReg(BaseAddress, RegOffset, Data) Xil_Out32((BaseAddress) + (RegOffset), (uint32_t)(Data))
#define XGpio_ReadReg(BaseAddress, RegOffset) XGpio_In32((BaseAddress) + (RegOffset))
#define XUL_TX_FIFO_OFFSET 4 /* transmit FIFO, write only */
#define XUL_STATUS_REG_OFFSET 8 /* status register, read only */
#define XUL_SR_TX_FIFO_FULL 0x08 /* transmit FIFO full */
#define XUartLite_GetStatusReg(BaseAddress) XUartLite_ReadReg((BaseAddress), XUL_STATUS_REG_OFFSET)
#define XUartLite_ReadReg(BaseAddress, RegOffset) XGpio_In32((BaseAddress) + (RegOffset))
#define XUartLite_IsTransmitFull(BaseAddress) \
((XUartLite_GetStatusReg((BaseAddress)) & XUL_SR_TX_FIFO_FULL) == \
XUL_SR_TX_FIFO_FULL)
#define XUartLite_WriteReg(BaseAddress, RegOffset, Data) Xil_Out32((BaseAddress) + (RegOffset), (uint32_t)(Data))
volatile uint32_t cnt = 0;
void SysTick_Handler(void)
{
cnt++;
}
void delay_ms(uint32_t t)
{
cnt = 0;
while(cnt-t>0);
}
uint32_t XGpio_In32(uint32_t Addr)
{
return *(volatile uint32_t *) Addr;
}
void Xil_Out32(uint32_t Addr, uint32_t Value)
{
volatile uint32_t *LocalAddr = (volatile uint32_t *)Addr;
*LocalAddr = Value;
}
uint32_t XGpio_DiscreteRead(uint32_t Addr, uint8_t Channel)
{
return XGpio_ReadReg(Addr, (Channel-1)*XGPIO_CHAN_OFFSET);
}
void XGpio_DiscreteWrite(uint32_t Addr, uint8_t Channel, uint32_t Data)
{
XGpio_WriteReg(Addr, (Channel-1)*XGPIO_CHAN_OFFSET, Data);
}
void XUartLite_SendByte(uint32_t BaseAddress, uint8_t Data)
{
while (XUartLite_IsTransmitFull(BaseAddress));
XUartLite_WriteReg(BaseAddress, XUL_TX_FIFO_OFFSET, Data);
}
void cm3_print(const char *ptr)
{
while (*ptr != (char)0) {
XUartLite_SendByte(BASEADDR_UART, *ptr);
ptr++;
}
}
void MyUartPrintf(char *fmt,...)
{
unsigned char UsartPrintfBuf[296];
va_list ap;
unsigned char *pStr = UsartPrintfBuf;
va_start(ap, fmt);
vsnprintf((char *)UsartPrintfBuf, sizeof(UsartPrintfBuf), (const char *)fmt, ap);
va_end(ap);
while(*pStr != 0)
{
XUartLite_SendByte(BASEADDR_UART, *pStr);
pStr++;
}
}
void led_blink(void)
{
XGpio_DiscreteWrite(BASEADDR_LED, CHANNEL_LED, 0);
delay_ms(500);
XGpio_DiscreteWrite(BASEADDR_LED, CHANNEL_LED, 0xf);
delay_ms(500);
}
int main(void)
{
uint32_t sw = 0;
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock/1000);
cm3_print("Hello DesignStart ARM Cortex-M3 on FPGA Xilnx Artix-7 XC7A100T \r\n");
MyUartPrintf("SystemCoreClock = %ld\r\n", SystemCoreClock);
while(1)
{
led_blink();
sw = XGpio_DiscreteRead(BASEADDR_LED, CHANNEL_SW);
MyUartPrintf("key state = %d-%d-%d-%d\r\n", sw>>3, sw>>2&1, sw>>1&1, sw&1);
}
}
实现的功能是,4颗LED每100ms闪烁一次,同时串口输出此时拨码开关的实时状态。
编译无误后,就可以进行程序下载了。
6.6 Flash编程算法生成
使用Jlink下载程序需要指定Flash编程算法,但是Keil自带的算法中并没有我们所需要的:
![下载算法 64f08627187f6b72b5de807863110dbd.png](https://i-blog.csdnimg.cn/blog_migrate/5a0a1e95b6ccd22fa9959b26bd86ccaf.png)
所以我们需要定制一份Flash编程算法,打开Keil安装目录下的\ARM\Flash
文件夹,将_Template
文件夹复制出一份,并命名为DS_CM3,
![复制模板 8b32c46556d033a54879404a79617a7f.png](https://i-blog.csdnimg.cn/blog_migrate/27075a5cf35e0c0e99c23c55d9964dc5.png)
打开其中的Keil工程:
![下载算法 9a4d59a0afc5372c0968cff3b05cb9f1.png](https://i-blog.csdnimg.cn/blog_migrate/fbded07382ef5e058e4b09cb79a5198c.png)
这个工程可以自己设置要编程的Flash起始地址、大小,擦除大小等。
FlashDev.c文件填入以下内容,和我们之前ITCM的配置保持一致,起始地址0x0,大小64K:
#include "..\FlashOS.H" // FlashOS Structures
struct FlashDevice const FlashDevice = {
FLASH_DRV_VERS, // Driver Version, do not modify!
"MyCM3onFPGA", // Device Name
ONCHIP, // Device Type
0x00000000, // Device Start Address
0x00010000, // 修改为64KB
1024, // Programming Page Size
0, // Reserved, must be 0
0xFF, // Initial Content of Erased Memory
100, // Program Page Timeout 100 mSec
3000, // Erase Sector Timeout 3000 mSec
// Specify Size and Address of Sectors
0x010000, 0x000000, // 只有一个扇区,起始地址为0
SECTOR_END
};
FlashPrg.c文件,实现一些存储区擦除的函数:
#include "..\FlashOS.H" // FlashOS Structures
#include "string.h"
int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {
return (0); // Finished without Errors
}
int UnInit (unsigned long fnc) {
return (0); // Finished without Errors
}
int EraseChip (void) {
memset((unsigned char *)0, 0, 0x10000);
return (0); // Finished without Errors
}
int EraseSector (unsigned long adr) {
memset((unsigned char *)adr, 0, 1024);
return (0); // Finished without Errors
}
int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) {
memcpy((unsigned char *)adr, buf, sz);
return (0); // Finished without Errors
}
编译无误后,会在工程目录下生成一个FLM文件。
![新生成的下载算法 083d049b18d177e2a3cc51ff74d55ce4.png](https://i-blog.csdnimg.cn/blog_migrate/2a92a2b57337b370d24e282392d2927a.png)
将它复制到上一级目录:
![新生成的下载算法 68c2a67ce1d1f586e5c64dba7726236f.png](https://i-blog.csdnimg.cn/blog_migrate/80500787ff0d1cfc802204e0296a93ba.png)
6.7 编译下载运行
再打开我们的ARM核Keil工程,添加DS_CM3 Flash编程算法:
![添加Flash编程算法 000048ce530f540879d22ddaa0487bfa.png](https://i-blog.csdnimg.cn/blog_migrate/020be903decb32c46356b8af0bed5890.png)
点击下载按钮,把ARM程序下载到ARM核:
![43 a416e6693adbb208e5e797d5fba4c757.png](https://i-blog.csdnimg.cn/blog_migrate/9d59c7dda5ce2bc194278b3d1e927907.png)
可以看到LED每500ms闪烁一次,串口数据每1s输出一次,同时按下按键,串口输出按键的状态。
![43 b7b597dc8ae9ea7805fd18897c855c7f.png](https://i-blog.csdnimg.cn/blog_migrate/76323d52f44be104412b5e4c8070d5ce.png)
和其他ARM内核芯片一样,也是支持在线调试的:
![43 02bfd01dd9d6fa549a63ac68f756ba02.png](https://i-blog.csdnimg.cn/blog_migrate/7fb04b5a4b07678ab23f6089248f80ad.png)
由于ARM程序是下载到Cortex-M3软核内的RAM存储区,所以掉电后程序会丢失。如何将程序下载到片外的SPI Flash中,我还没有成功实现。
-END-
往期推荐:点击图片即可跳转阅读
我是张巧龙,一名教电子的大学老师,欢迎关注!