当前位置:   article > 正文

手把手教你在FPGA上搭建一个Cortex-M3软核

fpga搭建arm m4软核

关注+星标公众,不错过精彩内容

cdcf9c7f18485a7e5367c61a611a7250.gif

转自 | 电子电路开发学习

本文将手把手教你如何基于ARM DesignStart计划,在FPGA上搭建一个Cortex-M3软核处理器

以Xilinx Artix-7™系列FPGA为例,介绍如何定制一颗ARM Cortex-M3 SoC软核,并添加GPIO和UART外设,使用Keil MDK环境开发应用程序,Jlink下载、调试ARM程序,最终的实现效果是LED闪烁,串口输出Hello World信息。

f1ffac16affa4c4aca8d62a7edd85d26.jpeg

都有哪些内容?

  • 必要的基础知识

  • Cortex-M3 FPGA IP核下载

  • 硬件准备

  • 软件准备

  • Cortex-M3软核搭建

      • 新建Vivado工程

      • 添加IP核搜索路径

      • 创建BlockDesign设计

      • 添加IP核,GPIO和UART外设

      • SWD接口引出

      • 外设基地址分配

      • 管脚分配

      • Bit流文件生成下载

  • Cortex-M3软核程序设计

      • 新建Keil工程

      • GPIO输入输出控制实现

      • 串口数据发送和接收实现

      • 延时函数实现

      • Flash编程算法生成

      • 下载运行

  • 开源地址

  • 参考资料

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,获取下载链接,复制到浏览器下载。

fa1bd2a64a426a4f20d882c7447e4510.jpeg
ARM官网

压缩包解压之后,共有4个文件夹:

93e20231f29b007e13b98ea8ecdcbaf1.jpeg
压缩包内容

各个文件夹存放的内容:

  • 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文件内容已经加密,没有可读性。

bc6ea90ecf18e859af8c87d8249c181c.jpeg
IP核源码

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开发板:

135a2ac8478f430ebc2c2d39a61601b8.jpeg arty-a7开发板

正点原子达芬奇Pro开发板:

ee9b568da5ec62eeb133fae3297e55bf.jpeg
正点原子达芬奇Pro开发板

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,用于存放本次示例所有的工程文件,并新建以下几个文件夹:

6e25ce137a8909e2c5f3f6b83c5540ee.jpeg
目录结构

每个文件夹的功能:

  • 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,打开工程创建向导,输入工程名称,工程的存放路径为之前我们新建的文件夹。

2ebf61591f25e776c4535b46b279a5fd.jpeg
新建工程

选择FPGA芯片的完整型号:XC7A100TFGG484

86ed742f5686dd4dbf4b97f5e117f56f.jpeg
选择芯片型号

最终创建完成之后的工程目录

c71e80c0b363d5e8aa0fa56883807c75.jpeg
Vivado工程目录
5.2 添加IP核搜索路径

为了能在BlockDesign中搜索到ARM Cortex-M3处理器IP核,我们需要把ARM 软核IP所在的路径添加到搜索路径。

c4e347e14ebc803f078729c63a2b7074.jpeg
添加到搜索路径
5.3 创建BlockDesign设计

为了方便后续使用图形化的方式连接各IP核,我们采用BlockDesign图形化的设计方式,这样可以快速的搭建出一颗定制化的软核处理器。

新建BlockDesign,命名为cm3_core,保存到最初创建的bd文件夹中。

在画布中添加Cortex-M3处理器核:

3445e9c23be80bbf1a004323572be2b6.jpeg
添加ARM核

双击Cortex-M3 IP核进行一些基本配置,我们不需要Trace功能,选择No Trace,使用SWD接口调试,禁用JTAG端口:

66f8e4de8a03ab12c9f4a01a301a5b8f.jpeg
配置ARM核

指令空间和数据空间大小,这里设置成64KB,都不进行初始化。

0639595160b7155486cc7909e3c26ab7.jpeg
ITCM核DTCM配置
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画布中,并按照下图进行连接:

51b5fe354ebc340a920528b626ca0ac8.jpeg
原理图连接

从官方手册中可以知道,ARM提供的软核IP中已经包括了ITCM和DTCM存储器,所以我们无需添加外部的BRAM来作为程序和数据的存储区。

3d7e6451012bb57110d353b5db4947f5.jpeg
Cortex-M3内核结构

内核中提供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位用于连接按键。

ad647230d1f8debdf68dba3b6aeefc80.jpeg
GPIO配置

UART配置成115200波特率,8位数据位,无奇偶校验。

e4b00ea7bc576fbdcd3d5e56fead6ad1.jpeg
UART配置

配置完成之后,将它们连接的到互联IP的主机接口上:

098001755f2a6be5d4192c0f7224eb46.jpeg
原理图连接

这两组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核。

4d4ff1da01907a39bb35a64d16364c4f.jpeg
DAP-Link

这里我们不使用DAP-Link调试器,而是使用Jlink SWD模式。SWD模式一共需要两根线,一个是SWCLK时钟信号,一个是SWDIO双向数据信号,处理器提供了3个管脚:SWDI,SWDO和SWDOEN,我们还需要实现一个双向端口模块。

基于IOBUF原语实现的双向端口模块,内容如下:

  1. module swdio_tri_buffer(
  2.     //Inputs
  3.     input swd_o,
  4.     input swd_oe,
  5.     //Outputs
  6.     output swd_i,
  7.     //Inouts
  8.     inout swd_io
  9. );
  10. IOBUF swd_iobuf_inst(
  11.     .O(swd_i),
  12.     .I(swd_o),
  13.     .IO(swd_io),
  14.     .T(!swd_oe)
  15. );
  16. endmodule

将它添加到我们的设计中。

61bd4b85eb80bcb7f00cd997e8440b1e.jpeg
SWD接口连接

最终的BlockDesign设计如下图所示:

9b0ad8ff01f86ec1f5d231a5054bebb9.jpeg
原理图连接
5.7 分配外设基地址

添加完外设IP之后,我们还需要对外设进行基地址和空间分配,在地址编辑框,右键选择自动分配。

d208b94c658faa1cfd9a6d47ec0f5208.jpeg
基地址分配

分配完成之后,使用设计验证(Validate Design)功能,可以检查当前BlockDesign设计连接的合法性。

2207ad9148d60b8bf9d9eb171a0c6ca1.jpeg
验证设计
5.8 生成Wrapper并例化到顶层

为了方便后续添加自定义的FPGA逻辑模块,我们将Cortex-M3软核处理器作为一个处理器例化到顶层设计中。

在BlockDesign源文件上右键,先选择Generate Output Products,耐心等待生成完成之后,选择Create HDL Wrapper

75b0d8e072e7a2a0cb9a7379b5ffdd0a.jpeg
生成Wrapper

之后就会生成一个_wrapper的verilog文件。

新建顶层文件top_hdl.v并保存到rtl文件夹,将_wrapper例化到顶层。

  1. module top_hdl(
  2.     //Inputs
  3.     input clk,
  4.     input rst_n,
  5.     input swclk,
  6.     input uart_rxd,
  7.     input [3:0] sw,
  8.     //Outputs
  9.     output [3:0] led,
  10.     output uart_txd,
  11.     //Inouts
  12.     inout swdio
  13. );
  14. cm3_core_wrapper cm3_core_wrapper_ut0(
  15.     //Inputs
  16.     .cm3_clk(clk),
  17.     .cm3_resetn(rst_n),
  18.     .cm3_gpio_in_tri_i(sw[3:0]),
  19.     .cm3_swclk(swclk),
  20.     .cm3_uart_rxd(uart_rxd),
  21.     //Outputs
  22.     .cm3_gpio_out_tri_o(led[3:0]),
  23.     .cm3_uart_txd(uart_txd),
  24.     //Inouts
  25.     .cm3_swdio(swdio)
  26. );
  27. endmodule   //top_hdl end
5.9 管脚分配

综合(Synthesis)完成之后,使用Vivado的图形化工具进行管脚分配,尤其注意要将SWDIO和SWDCLK引出到排针管脚上,方便后续使用外接的Jlink调试器进行ARM程序下载。

0639a47c11e21c0ade679ae99e1d2ff0.jpeg
分配管脚

或者直接新建XDC文件,使用约束语句进行管脚分配。

部分约束语句:

  1. set_property PACKAGE_PIN R4 [get_ports clk]
  2. set_property PACKAGE_PIN V13 [get_ports swclk]
  3. set_property PACKAGE_PIN V14 [get_ports swdio]
  4. set_property PACKAGE_PIN E14 [get_ports uart_rxd]
  5. set_property PACKAGE_PIN D17 [get_ports uart_txd]
  6. set_property PACKAGE_PIN U7 [get_ports rst_n]
  7. set_property PACKAGE_PIN V9 [get_ports {led[3]}]
  8. set_property PACKAGE_PIN Y8 [get_ports {led[2]}]
  9. set_property PACKAGE_PIN Y7 [get_ports {led[1]}]
  10. set_property PACKAGE_PIN W7 [get_ports {led[0]}]
  11. set_property PACKAGE_PIN T4 [get_ports {key[3]}]
  12. set_property PACKAGE_PIN T3 [get_ports {key[2]}]
  13. set_property PACKAGE_PIN R6 [get_ports {key[1]}]
  14. set_property PACKAGE_PIN T6 [get_ports {key[0]}]

如果你的板子和我的(正点原子达芬奇Pro)一样,那么可以直接使用以上管脚约束。

如果你分配的时钟管脚不是FPGA的全局时钟管脚,需要添加BUFG原语进行缓冲。

5.10 Bit流文件生成和下载

我的板子使用的是QSPI Flash,为了提高下载和启动速度,在生成Bit流时,配置生成选项:数据压缩、50M读取速度,4位数据线

bb91c566a4ee4f226b33d9e7a638fbe5.jpeg
生成Bit流配置

或者直接使用XDC语句进行约束:

  1. set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
  2. set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]
  3. set_property CONFIG_VOLTAGE 3.3 [current_design]
  4. set_property CFGBVS VCCO [current_design]
  5. set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]

以上约束不是必须的,只是为了提高下载和配置速度。

耐心等待工程综合完成,生成Bit流文件,综合的速度和处理器主频、核心数有关。

和常规的FPGA下载方式一样,将生成的软核Bit文件通过Xilinx下载器下载到FPGA内部,先不要固化到外部SPI Flash 。

手头没有Xilinx下载器的,可以参考之前的文章,自己做一个JTAG-HS2下载器

开源、低成本的Xilinx FPGA下载器

4736f473fcc53b342c3a207bff766797.jpeg

5.11 Jlink连接测试

下载完成之后,现在FPGA内部运行的就是一颗基于ARM Cortex-M3的软核处理器了,使用Jlink等调试工具可以连接到芯片。

Jlink调试器的SWCLK和SWDIO连接到我们分配的管脚V13和V14上。

手头没有Jlink的,也可以参考之前的文章,自己做一个Jlink-OB

手把手教你制作Jlink-OB调试器

5829500c5cad610ae089b8dc752c3756.jpeg

使用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:

ed63f63f7f2bf622c36c1f6af39d9217.jpeg
选择器件型号

如果以上配置均正确,就能看到已经连接到的ARM Cortex-M3核心。如果没有,说明FPGA工程配置有错误,需要确认是否和以上配置流程一致。

392edb032a658d4bb9513fb3fa764a53.jpeg
连接到ARM核心

至此,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个文件夹:

  1. application        //用户源文件
  2. object            //编译生成的文件
  3. project            //Keil的工程文件
6.1 新建Keil工程

打开Keil-MDK,选择Project->New Project,新建一个工程,命名为ds_cm3_prj,保存到project目录下。

26aa34dd873516b98547021b98f1b82c.jpeg
Keil工程目录

器件型号选择我们新安装的ARM Cortex-M3 DS_CM3内核。

325197e76a57d560426112fc4de18164.jpeg
选择器件型号

组件管理界面中,添加CMSIS内核文件和Startup启动文件:

83bf1f81f168ebbe5983562c03f1c036.jpeg
添加内核文件

并按照如下结构组织文件:

2c90fc8ee5cbdc3c3cebdf3f34f18b29.jpeg
文件结构
6.2 设置RAM和ROM地址

在工程选项中设置片上ITCM的起始地址0x0、大小64K,片上DTCM起始地址0x20000000、大小64K:

5061e2e8be45e085d32998d30ff51e92.jpeg
RAM地址配置

起始地址来源于使用手册中图4-1系统内存地址映射,可以看到其中ITCM和DTCM的起始地址:

632b70f18bd940e0b536f6bc642868eb.jpeg
ITCM和DTCM起始地址

大小是我们在Cortex-M3内核配置中设置的大小:

759de7bc650acecbed5337cdfbb22399.jpeg
ITCM和DTCM大小

设置完成之后,新建main.c文件,输入以下内容,编译工程,应该无错误输出。

  1. #include "DS_CM3.h"
  2. #include "system_DS_CM3.h"
  3. int main(void)
  4. {
  5.     while(1)
  6.     {
  7.     }
  8. }
6.3 GPIO输入输出控制

通过查看AXI GPIO的使用手册,通道1的数据寄存器偏移地址为0,通道2的数据寄存器偏移地址为0x08,根据Vivado中的连接,LED连接到通道1,按键连接到通道2上,所以只需要对这两个寄存器地址进行读写,就可以实现LED的控制和拨码开关状态的读取。

83ee4c8729ccf803fd254c796d174c7e.jpeg
AXI GPIO寄存器定义

在Vivado地址分配界面,可以看到GPIO和UART的基地址分别为:0x4000_0000和0x4060_0000。

df0c0048be631300f24b7cd5085afcb8.jpeg
外设基地址

LEL控制和拨码开关读取:

  1. *(volatile uint32_t *) (0x40000000+0x0) = 0x0f;    //GPIO通道1低4位写1
  2. *(volatile uint32_t *) (0x40000000+0x0) = 0x00;    //GPIO通道1低4位写0
  3. uint32_t sw = 0;
  4. sw = *(uint32_t *) (0x40000000+0x08);    //获取GPIO通道2的32位输入状态
6.4 串口数据发送和接收

向串口发送FIFO写入一字节数据:

  1. while((*(volatile uint32_t *)(0x40600000 + 0x08)) & 0x08 != 0x08);    //等待发送FIFO不满
  2. *(volatile uint32_t *) (0x40600000+0x04) = 0x41;    //向串口发送FIFO写入字符'A'=0x41

从串口接收一字节数据:

  1. uint8_t dat = 0;
  2. if((*(volatile uint32_t *)(0x40600000 + 0x08)) & 0x01 == 1)    //串口接收FIFO中有数据
  3.     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的变化,可以被人眼所看到,需要使用延时函数对亮灭进行延时。

使用系统滴答定时器实现一个延时函数:

  1. volatile uint32_t cnt = 0;    //volatile类型
  2. void SysTick_Handler(void)
  3. {
  4.     cnt++;
  5. }
  6. void delay_ms(uint32_t t)
  7. {
  8.     cnt = 0;
  9.     while(cnt-t>0);
  10. }

为了让延时函数准确延时,我们还需要更改工程中的系统时钟频率,和FPGA中配置的内核时钟保持一致。

5d04c8a69169f0382f2d079688ef57df.jpeg

系统时钟

完成的main.c文件内容:

  1. #include "DS_CM3.h"
  2. #include "system_DS_CM3.h"
  3. //C库
  4. #include <stdarg.h>
  5. #include <string.h>
  6. #include <stdio.h>
  7. #define BASEADDR_LED     0x40000000
  8. #define BASEADDR_UART     0x40600000
  9. #define CHANNEL_LED     1
  10. #define CHANNEL_SW       2
  11. #define XGPIO_CHAN_OFFSET 8
  12. #define XGpio_WriteReg(BaseAddress, RegOffset, Data)   Xil_Out32((BaseAddress) + (RegOffset), (uint32_t)(Data))
  13. #define XGpio_ReadReg(BaseAddress, RegOffset)             XGpio_In32((BaseAddress) + (RegOffset))
  14. #define XUL_TX_FIFO_OFFSET           4     /* transmit FIFO, write only */
  15. #define XUL_STATUS_REG_OFFSET        8     /* status register, read only */
  16. #define XUL_SR_TX_FIFO_FULL          0x08  /* transmit FIFO full */
  17. #define XUartLite_GetStatusReg(BaseAddress)          XUartLite_ReadReg((BaseAddress), XUL_STATUS_REG_OFFSET)
  18. #define XUartLite_ReadReg(BaseAddress, RegOffset)   XGpio_In32((BaseAddress) + (RegOffset))
  19. #define XUartLite_IsTransmitFull(BaseAddress) \
  20.     ((XUartLite_GetStatusReg((BaseAddress)) & XUL_SR_TX_FIFO_FULL) == \
  21.       XUL_SR_TX_FIFO_FULL)
  22. #define XUartLite_WriteReg(BaseAddress, RegOffset, Data)  Xil_Out32((BaseAddress) + (RegOffset), (uint32_t)(Data))
  23. volatile uint32_t cnt = 0;
  24. void SysTick_Handler(void)
  25. {
  26.     cnt++;
  27. }
  28. void delay_ms(uint32_t t)
  29. {
  30.     cnt = 0;
  31.     while(cnt-t>0);
  32. }
  33. uint32_t XGpio_In32(uint32_t Addr)
  34. {
  35.     return *(volatile uint32_t *) Addr;
  36. }
  37. void Xil_Out32(uint32_t Addr, uint32_t Value)
  38. {
  39.     volatile uint32_t *LocalAddr = (volatile uint32_t *)Addr;
  40.     *LocalAddr = Value;
  41. }
  42. uint32_t XGpio_DiscreteRead(uint32_t Addr, uint8_t Channel)
  43. {
  44.     return XGpio_ReadReg(Addr, (Channel-1)*XGPIO_CHAN_OFFSET);
  45. }
  46. void XGpio_DiscreteWrite(uint32_t Addr, uint8_t Channel, uint32_t Data)
  47. {
  48.     XGpio_WriteReg(Addr, (Channel-1)*XGPIO_CHAN_OFFSET, Data);
  49. }
  50. void XUartLite_SendByte(uint32_t BaseAddress, uint8_t Data)
  51. {
  52.     while (XUartLite_IsTransmitFull(BaseAddress));
  53.     XUartLite_WriteReg(BaseAddress, XUL_TX_FIFO_OFFSET, Data);
  54. }
  55. void cm3_print(const char *ptr)
  56. {
  57.     while (*ptr != (char)0) {
  58.         XUartLite_SendByte(BASEADDR_UART, *ptr);
  59.         ptr++;
  60.     }
  61. }
  62. void MyUartPrintf(char *fmt,...)
  63. {
  64.     unsigned char UsartPrintfBuf[296];
  65.     va_list ap;
  66.     unsigned char *pStr = UsartPrintfBuf;
  67.     va_start(ap, fmt);
  68.     vsnprintf((char *)UsartPrintfBuf, sizeof(UsartPrintfBuf), (const char *)fmt, ap);                      
  69.     va_end(ap);
  70.     while(*pStr != 0)
  71.     {
  72.         XUartLite_SendByte(BASEADDR_UART, *pStr);
  73.         pStr++;
  74.     }
  75. }
  76. void led_blink(void)
  77. {
  78.     XGpio_DiscreteWrite(BASEADDR_LED, CHANNEL_LED, 0);
  79.     delay_ms(500);
  80.     XGpio_DiscreteWrite(BASEADDR_LED, CHANNEL_LED, 0xf);
  81.     delay_ms(500);
  82. }
  83. int main(void)
  84. {
  85.     uint32_t sw = 0;
  86.     SystemCoreClockUpdate();
  87.     SysTick_Config(SystemCoreClock/1000);
  88.     cm3_print("Hello DesignStart ARM Cortex-M3 on FPGA Xilnx Artix-7 XC7A100T \r\n");
  89.     MyUartPrintf("SystemCoreClock = %ld\r\n", SystemCoreClock);
  90.     while(1)
  91.     {
  92.         led_blink();
  93.         sw = XGpio_DiscreteRead(BASEADDR_LED, CHANNEL_SW);
  94.         MyUartPrintf("key state = %d-%d-%d-%d\r\n", sw>>3, sw>>2&1, sw>>1&1, sw&1);
  95.     }
  96. }

实现的功能是,4颗LED每100ms闪烁一次,同时串口输出此时拨码开关的实时状态。

编译无误后,就可以进行程序下载了。

6.6 Flash编程算法生成

使用Jlink下载程序需要指定Flash编程算法,但是Keil自带的算法中并没有我们所需要的:

0df3c73b167dd531bdc6528e7552350f.jpeg
下载算法

所以我们需要定制一份Flash编程算法,打开Keil安装目录下的\ARM\Flash文件夹,将_Template文件夹复制出一份,并命名为DS_CM3,

041cd23bf8d44648626139581c6ad8d9.jpeg
复制模板

打开其中的Keil工程:

8b4d90dd1042f20e46175d7f8f211521.jpeg
下载算法

这个工程可以自己设置要编程的Flash起始地址、大小,擦除大小等。

FlashDev.c文件填入以下内容,和我们之前ITCM的配置保持一致,起始地址0x0,大小64K:

  1. #include "..\FlashOS.H"        // FlashOS Structures
  2. struct FlashDevice const FlashDevice  =  {
  3.    FLASH_DRV_VERS,             // Driver Version, do not modify!
  4.    "MyCM3onFPGA",              // Device Name 
  5.    ONCHIP,                     // Device Type
  6.    0x00000000,                 // Device Start Address
  7.    0x00010000,                 // 修改为64KB
  8.    1024,                       // Programming Page Size
  9.    0,                          // Reserved, must be 0
  10.    0xFF,                       // Initial Content of Erased Memory
  11.    100,                        // Program Page Timeout 100 mSec
  12.    3000,                       // Erase Sector Timeout 3000 mSec
  13. // Specify Size and Address of Sectors
  14.    0x0100000x000000,         // 只有一个扇区,起始地址为0
  15.    SECTOR_END
  16. };

FlashPrg.c文件,实现一些存储区擦除的函数:

  1. #include "..\FlashOS.H"        // FlashOS Structures
  2. #include "string.h"
  3. int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {
  4.   return (0);                                  // Finished without Errors
  5. }
  6. int UnInit (unsigned long fnc) {
  7.   return (0);                                  // Finished without Errors
  8. }
  9. int EraseChip (void) {
  10.   memset((unsigned char *)000x10000);
  11.   return (0);                                  // Finished without Errors
  12. }
  13. int EraseSector (unsigned long adr) {
  14.   memset((unsigned char *)adr, 01024);
  15.   return (0);                                  // Finished without Errors
  16. }
  17. int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) {
  18.   memcpy((unsigned char *)adr, buf, sz);
  19.   return (0);                                  // Finished without Errors
  20. }
编译无误后,会在工程目录下生成一个FLM文件。
7aeff95cb875dc4ad1565fcea70148c8.jpeg
新生成的下载算法

将它复制到上一级目录:

3c3849086694700eae75088db728b7c2.jpeg
新生成的下载算法
6.7 编译下载运行

再打开我们的ARM核Keil工程,添加DS_CM3 Flash编程算法:

dfefbce538ca26189e93dc90882d07cf.jpeg
添加Flash编程算法

点击下载按钮,把ARM程序下载到ARM核:

2041d2f70a85705f703ce423ebbdfde9.jpeg
43

可以看到LED每500ms闪烁一次,串口数据每1s输出一次,同时按下按键,串口输出按键的状态。

e07c33dd1e6f649b73f026f93a1ff032.jpeg
43

和其他ARM内核芯片一样,也是支持在线调试的:

3110e4a951540245fab87f4c43220512.jpeg
43

由于ARM程序是下载到Cortex-M3软核内的RAM存储区,所以掉电后程序会丢失。如何将程序下载到片外的SPI Flash中,我还没有成功实现。

7.开源地址

本篇文章的pdf文件,Vivado工程,Keil工程,Keil器件支持 包,Flash编程算法文件,外设IP的参考文档,ARM M3软核IP资料包等资料我已经开源到Github和Gitee,地址如下:

  • Gitee

git clone https://gitee.com/whik/cortex_m3_on_xc7a100t.git
  • Github

git clone https://github.com/whik/cortex_m3_on_xc7a100t.git

------------ END ------------

c0204762dda5fa8eb99a7c45b858a995.gif

●专栏《嵌入式工具

●专栏《嵌入式开发》

●专栏《Keil教程》

●嵌入式专栏精选教程

关注公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。

005d9b633a29bca991cc516344461060.jpeg

1a894a19078a35312d1d2b26c6144c3b.png

点击“阅读原文”查看更多分享。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/474743
推荐阅读
相关标签
  

闽ICP备14008679号