1. Vivado创建MicroBlaze核

1.0 Vivado安装

这里以 Vivado 2022.2 为例讲解它的安装,该软件安装需要100多G硬盘空间,另外在运行时对CPU和内存的消耗也比较大,建议4核8GB 起步。双击运行 Vivado 2022.2 安装程序,Windows弹出的防火墙选择运行,并跳过连接 Xilinx 服务器。

install1

弹出欢迎界面选择Next下一步.

install2

安装产品选项中选择 Vitis,该选项也会安装 Vivado 软件。

install3

要安装的工具和设备使用默认即可。

install4

下拉并勾选所有的 License.

install5

使用默认安装到C盘路径下.

install6

点击 Next 开始漫长的安装过程,大概需要几个小时。

install7

文件拷贝安装完成之后,将会开始安装Xilinx的硬件驱动,选择同意即可。

install8

所有软件安装完成将会弹窗提示安装成功.

install9

Windows Defender防火墙弹窗勾选允许访问.

install10

接下来从开始菜单直接运行 Manage Xilinx Licenses 2022.2, 或启动 Vivado 2022.2 程序后点击 “Help” -> “Manage Lincense…” 打开许可证管理器。点击 “Load License” -> “Copy Lincense” 并选择相应的 Lincense 文件加载即可。

install11

至此Vivado软件包安装顺利完成。如果接下来需要在 Linux 服务器上使用 JTAG 远程调试 Windows 下的开发板,则需要再次运行安装程序,安装下面的 “Hardware Server” 程序。

install12

1.1 创建 MicroBlaze 项目

1.1.1 MicroBlaze 介绍

MicroBlaze 是 Xilinx 公司提供的一款 32/64 位软核嵌入式处理器,是一款高度灵活可配置的易用型处理器, 它能够利用 FPGA 内部通用资源和相关 IP 核,实现可编程片上系统(SOPC)的设计。该处理器采用32 位 RISC(Reduced Insrtction System Computer)优化结构和 Harvard 总线结构,广泛适用于 Spartan、 Virtex和 Artix 等系列的 FPGA。

MicroBlaze 软核嵌入式处理器是高度可定制的 IP 核,支持 70 多个配置选项,有 32 个 32 位通用寄存器以及 2 个 32 位特殊寄存器—PC 指针寄存器和 MSR 状态标志寄存器。另外 MicroBlaze 软核处理器还配有指令和数据缓存、 浮点单元、内存管理单元和许多其他选项,从而大大提高其运算性能。 MicroBlaze 软核嵌 入式处理器的所有指令字长都是 32 位,具有 3 个操作数和两种寻址模式, 指令按功能可划分为:逻辑运算、算术运算、分支、存储器读/写和特殊指令等等。指令以并行流水线的方式执行,其流水线可分为取指、译码和执行。

下图是 MicroBlaze 的框图,展示了固定的硬件特性模块和可配置选项,如指令和数据缓存。

microblaze_mcu

本文将详细介绍如何在 Artix7 FPGA上设计 MicroBlaze 处理器并添加相应的外设硬件,然后在其上运行PetaLinux和FreeRTOS操作系统的详细过程。

1.1.2 创建MicroBlaze工程

打开 Vivado,进入 Vivado 界面后,点击“Quick Start”栏的“Create Project”。 然后在弹出的创建 Vivado工程向导界面,点击“Next”。 设置工程名为“DavinciPro_MicroBlaze” ,工程路径可使用任意路径,这里我将工程放在 E:\VivadoWSP\Microblaze 文件夹下。 注意,工程名和路径只能由英文字母、数字和下划线组成,不能包含中文、空格以及特殊字符!

prj_create1

在 Prioject Type 界面我们选择“RTL Project” 。本次实验不需要添加源文件和约束文件,所以勾选“Do not specify sources at this time” 。

prj_create2

选择器件型号的方式有两种,一种是根据 Parts,另一种是根据 Boards。此处我们使用 Parts选择器件,在 Family 栏里选择“Artix-7”, Speed 栏选择“-2” ,在 Package 栏选择“fgg484” 。然后根据所使用的芯片型号,在下面的器件列表中选择“xc7a100tfgg484-2”.

prj_create3

在完成工程创建后,接下开我们将要进行“Block Design”的设计。 首先在 Vivado 界面左侧“Flow Nevagator”栏点击“Creat Block Design”,并在弹出提示框后,在“Design name”一栏将名称改为“system”点击“OK” 。

prj_create4

1.1.3 创建MicroBlaze核

在生成的“Diagram”页面,我们点击“+” 按钮(或快捷键 Ctrl+I)添加 IP 核,在弹出的搜索框中输入“micro”,选择“MicroBlaze”。

create_microblaze1

双击“microblaze_0”模块进入 MicroBlaze 的配置界面,第一页里提供模板选择和一般设置。 点击 Select Configuration 右侧的目录框我们能够看到多个模板选项(Minimum Area、 Maximum Performance 等),因为接下来我们会运行Linux操作系统,这里就选择“Linux with MMU”。

create_microblaze2

配置完成后点击“Next”进入第二页 General 页面, General 页面能够选择单元的选择和优化。 直接保持默认就可以了,点击 Next 进入下一页 。

create_microblaze3

Exception 页面同样保持默认设置,点击 Next 进入下一页 。

create_microblaze4

Cache 页面同样保持默认设置,点击 Next 进入下一页 。

create_microblaze5

MMU 页面同样保持默认设置,点击 Next 进入下一页 。

create_microblaze6

Debug 页面同样保持默认设置,点击 Next 进入下一页 。

create_microblaze7

Buses 页面这里需要使能 AXI 总线的指令和数据接口,然后点击 OK 完成配置。

create_microblaze8

完成之后点击“Run Block Automation”开始自动布线 。

create_microblaze9

在弹出的界面里,Preset选择”Application”, Local Memory设置为 128KB,勾选 Interrupt Controller 并设置 Clock Connection,然后点击“OK”

create_microblaze10

接下来再次自动布线并勾选下面选项。

create_microblaze11

自动布线完成后重新布局生成下图。

create_microblaze12

1.2 添加DDR3内存

在“Diagram”页面,我们点击“+” 按钮(或快捷键 Ctrl+I)添加 IP 核,在弹出的搜索框中输入“mig”,选择“Memory Interface Generator”。

add_ddr1

双击添加的 MIG 模块开始对其进行配置。首页将显示项目的一些基本信息,直接点击 “Next”。

add_ddr2

该页保持默认设置,点击 Next 进入下一页 。

add_ddr3

该页保持默认设置,点击 Next 进入下一页 。

add_ddr4

该页保持默认 DDR3 SDRAM 设置,点击 Next 进入下一页 。

add_ddr5

该页我们需要作如下配置:

  • 配置 Clock Period 为 2500 ps, 后面的主频将自动调整为 400MHz;

  • Memory Type 选择 MT41K128M16XX-15E;

  • 我们的开发板上DDR3内存由两片DDR芯片并联,这里将 Data Width 调整为 32;

add_ddr6

该页保持默认设置,点击 Next 进入下一页 。

add_ddr7

该页调整 Input Clock Period 为 5000ps,其它选项保持不变。

add_ddr8

该页我们需要作如下配置:

  • System Clock 和 Reference Clock 都调整为 No Buffer;

  • 勾选 Internal Vref 选项;

add_ddr9

该页保持默认设置,点击 Next 进入下一页。

add_ddr10

该页选择 Fixed Pin Out,点击 Next 进入下一页 。

add_ddr11

该页点击 Read XDC/UCF 按钮,导入开发板 DDR3 引脚配置文件 DavinciPro_MicroBlaze_DDR3_Pin.ucf,如果没有该文件则需要对着原理图来配置相应引脚。然后点击 Validate 后进入下一步。

add_ddr12

该页保持默认设置,点击 Next 进入下一页。

add_ddr13

该页保持默认设置,点击 Next 进入下一页。

add_ddr14

此后接受相关协议,开始完成 MIG 模块的配置。

add_ddr15

MIG配置生成之后,不要点击自动布线,否则MIG将会连接到 AXI Interconnect 上。这里我们将创建 AXI SmartConnect ,并将 MIG 连接到该总线上,这样DDR3的速度会更快一些。我们点击“+” 按钮(或快捷键 Ctrl+I)添加 IP 核,在弹出的搜索框中输入“smart”,选择“AXI SmartConnect”。

add_ddr16

接下来使用下面步骤开始手动布线:

  • 删除 MicroBlaze 上连到 AXI Interconnect 上的 M_AXI_DC 和 M_AXI_IC 两根线,并手动连线到 AXI SmartConnect 的 S00_AXI 和 S01_AXI 上;

  • 删除 MicroBlaze 上连到 AXI Interconnect 上的 M_AXI_IP 连线,并手动连接到 AXI Interconnect 上的 S01_AXI 上;

  • 现在 AXI Interconnect 上少了两个引脚,双击 AXI Interconnect 模块,调整 Number of Slave Interfaces 为2;

  • 手动连接 AXI SmartConnect 的 M00_AXI 引脚到 MIG 模块的 S_AXI 引脚上;

  • 手动连接 AXI SmartConnect 的 aclk 引脚到 MicroBlaze 的 Clk 引脚上;

  • 手动连接 AXI SmartConnect 的 aresetn 引脚到 AXI Interconnect 的 ARESETN 引脚上;

  • 手动连接 MIG 模块的 aresetn 引脚到 AXI Interconnect 的 ARESETN 引脚上;

  • 删除Processor System Reset 的 ext_reset_in 引脚连线及外部结点reset_rtl_0,手动连接该引脚到 MIG 模块的 ui_clk_sync_rst 引脚上;

  • 删除Processor System Reset 的 dcm_locked 引脚连线及外部结点,手动连接该引脚到 MIG 模块的 mmcm_locked 引脚上;

  • 在 MIG 模块上鼠标右键,选中”Make External” ,并重命名引脚为 DDR3。

add_ddr17

双击Clocking Wizard模块:

  • 在 Clocking Options 里将 Input Clock Information 的 Primary 输入时钟设置为 50MHz,并调整 Source 为 Single ended clock capable pin;

  • 在 Output Clocks 菜单里将 clk_out1 的 Output Freq 设置为 200MHz, 并设置最下面的 Reset Type 为 Active Low;

  • 删除无用的 diff_clock_rtl_0 结点。

add_ddr18

add_ddr19

  • 删除 Clocking Wizard 模块上的 reset 连线,并鼠标右键点击 reset 引脚,选中”Make External” ,并重命名结点为 resetn_0。

  • 鼠标右键点击 clk_in1 引脚,选中”Make External” ,生成新结点 clk_in1_0。

  • 鼠标右键点击 clk_out1 引脚,选中”Disconnect Pin” 删除连线后,再将该引脚同时连接到 MIG 模块的 clk_ref_i 和 sys_clk_i 引脚上;

  • 将 locked 引脚连接到 MIG 模块的 sys_rst 引脚上;

add_ddr20

  • 右键点击 Processor System Reset 的 aux_reset_in 引脚上,到 Clocking Wizard 模块上的 locked 引脚上;

  • 手动连接 Processor System Reset 的 mb_debug_sys_rst 引脚到 MDM 模块上的 Debug_SYS_Rst 引脚上;

  • 手动连接 MIG 模块的 ui_clk 引脚到 AXI SmartConnect 的 aclk 引脚上;

add_ddr21

接下来点击 Diagram 上的 Validate 按钮,在弹窗中点击 “Yes”。

add_ddr22

点击重新布局按钮后如下图所示。

add_ddr23

1.3 添加外设

1.3.1 添加定时器外设

操作系统运行需要定时器提供时间,这里点击“+” 按钮(或快捷键 Ctrl+I)添加 IP 核,在弹出的搜索框中输入“timer”,选择“AXI Timer”。

add_timer1

在自动布线连接 AXI Timer 后,再手动布线连接 AXI Timer 上的 interrupt 引脚到中断控制器的 Concat 器件上。

add_timer2

1.3.2 添加串口外设

操作系统运行需要串口Console,这里点击“+” 按钮(或快捷键 Ctrl+I)添加 IP 核,在弹出的搜索框中输入“uart”,选择“AXI Uartlite”。

add_uart1

在自动布线连接 AXI Uartlite 后,再手动布线连接 AXI Uartlite 上的 interrupt 引脚到中断控制器的 Concat 器件上,并重命名外部结点为UART0。

add_uart2

双击 AXI Uartlite 模块,设置默认波特率为 115200 bps。

add_uart3

1.3.3 添加按键Led

开发板上有4个按键和4个Led灯,这里点击“+” 按钮(或快捷键 Ctrl+I)添加 IP 核,在弹出的搜索框中输入“gpio”,选择“AXI GPIO”来添加用户按键和Led。并重命名两个模块输出结点为 UserKeys 和 UserLeds。

add_keyled

鼠标双击连接 UserLeds 的 AXI GPIO 模块,设置 GPIO Width 为 4。因为 Led 所在的引脚只会作为GPIO输出使用,这里勾选 All Outputs 选项。

add_keyled

鼠标双击连接 UserKeys 的 AXI GPIO 模块,设置 GPIO Width 为 4。因为按键需要用到中断,这里需要勾选 Enable Interrupt 选项。

add_keyled

因为按键需要多一路中断线,此时需双击 microblaze_0_xlconcat 模块,将 Number of Ports 加1。

add_keyled

然后将按键 “AXI GPIO” 上的 ip2intc_irpt 引脚连接到 microblaze_0_xlconcat 模块上新的中断引脚上。

add_keyled

1.3.4 添加I2C接口

在开发板上有一路I2C连接了 NXP PCF8563 RTC芯片 和 Atmel AT24C64 EEPROM芯片。这里点击“+” 按钮(或快捷键 Ctrl+I)添加 IP 核,在弹出的搜索框中输入“iic”,选择“AXI IIC”来添加 I2C控制器,并重命名模块输出结点为 I2C0。同样I2C需要多一路中断线,此时需双击 microblaze_0_xlconcat 模块,将 Number of Ports 加1。然后将 I2C 的中断线 ip2intc_irpt 连接到 microblaze_0_xlconcat 模块上新的中断引脚上。

add_i2c

双击 I2C0 的 AXI IIC 模块,分别配置 SCL Inertial delay 值为30, 配置SDA Inertial delay 值为5,否则 EEPROM 芯片可以正常工作,而 RTC 芯片不可以正常工作。具体参考链接: https://support.xilinx.com/s/question/0D52E00006iHrtfSAC/set-inertialdelay-in-iic-module-?language=en_US

add_i2c

1.4 编译导出硬件平台

1.4.1 创建约束文件

现在,我们的Vivado硬件设计已经完成,如下图所示。

add_xdc1

接下来我们开始创建约束文件。

add_xdc2

创建约束文件 setpin.xdc 的内容如下:

# clock & reset
create_clock -period 20.000 -name clk_in1_0 [get_ports clk_in1_0]
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS15} [get_ports clk_in1_0]
set_property -dict {PACKAGE_PIN U7 IOSTANDARD LVCMOS15} [get_ports resetn_0]
set_property CLOCK_DEDICATED_ROUTE BACKBONE [get_nets system_i/clk_wiz_1/inst/clk_in1_system_clk_wiz_1_0]

# UART
set_property -dict {PACKAGE_PIN E14 IOSTANDARD LVCMOS33} [get_ports UART0_rxd]
set_property -dict {PACKAGE_PIN D17 IOSTANDARD LVCMOS33} [get_ports UART0_txd]

# User Leds
set_property -dict {PACKAGE_PIN V9 IOSTANDARD LVCMOS15} [get_ports {UserLeds_tri_o[0]}]
set_property -dict {PACKAGE_PIN Y8 IOSTANDARD LVCMOS15} [get_ports {UserLeds_tri_o[1]}]
set_property -dict {PACKAGE_PIN Y7 IOSTANDARD LVCMOS15} [get_ports {UserLeds_tri_o[2]}]
set_property -dict {PACKAGE_PIN W7 IOSTANDARD LVCMOS15} [get_ports {UserLeds_tri_o[3]}]

# User Keys
set_property -dict {PACKAGE_PIN T4 IOSTANDARD LVCMOS15} [get_ports {UserKeys_tri_io[0]}]
set_property -dict {PACKAGE_PIN T3 IOSTANDARD LVCMOS15} [get_ports {UserKeys_tri_io[1]}]
set_property -dict {PACKAGE_PIN R6 IOSTANDARD LVCMOS15} [get_ports {UserKeys_tri_io[2]}]
set_property -dict {PACKAGE_PIN T6 IOSTANDARD LVCMOS15} [get_ports {UserKeys_tri_io[3]}]

# I2C0
set_property -dict {PACKAGE_PIN F13 IOSTANDARD LVCMOS33} [get_ports I2C0_scl_io]
set_property -dict {PACKAGE_PIN A19 IOSTANDARD LVCMOS33} [get_ports I2C0_sda_io]

如下图所示。

add_xdc3

1.4.2 综合实现

创建 HDL Wrapper 文件。

build1

点击 ”Generate Bitstream” 按钮开始综合、实现并生成比特流文件。

build2

1.4.3 导出xsa文件

在比特流文件生成之后,接下来导出给 FreeRTOS 或 Linux 等软件开发所需的 .xsa 硬件平台文件。

xsa1

导出 .xsa 的过程中记得一定要勾选上 “Include bitstream”。

xsa2

导出到默认的路径即可。

xsa3

下面时生成的 system_wrapper.xsa 文件,接下来做 FreeRTOS 或 PetaLinux 开发时都需要该文件。

xsa4

1.5 TCL备份与恢复

Vivado 在完成硬件设计后,如果直接保存项目会比较大。这时候我们可以将整个项目导出 TCL 脚本,此后我们可以再使用这个 TCL 脚本来恢复本项目。接下来我们将描述 TCL 脚本的导出与恢复。

1.5.1 TCL脚本项目备份

点击 File -> Project -> Write Tcl… 开始导出 TCL 脚本。

TCL1

设置 TCL 脚本的导出路径并勾选 “Recreate Block Designs using Tcl”,注意导出 TCL 脚本的路径应该与 .xpr 文件同级。

TCL2

导出后生成的 TCL 存放路径如下。

TCL3

打开生成的 TCL 脚本,可以看到要备份的文件信息,接下来拷贝、备份下面提示的 DavinciPro_MicroBlaze.srcs 文件夹下的相关文件。

# 1. This project restoration tcl script (DavinciPro_MicroBlaze.tcl) that was generated.
#
# 2. The following source(s) files that were local or imported into the original project.
#    (Please see the '$orig_proj_dir' and '$origin_dir' variable setting below at the start of the script)
#
#    "E:/VivadoWSP/Microblaze/DavinciPro_MicroBlaze/DavinciPro_MicroBlaze.srcs/sources_1/bd/system/ip/system_mig_7series_0_0/mig_a.prj"
#    "E:/VivadoWSP/Microblaze/DavinciPro_MicroBlaze/DavinciPro_MicroBlaze.srcs/constrs_1/new/setpin.xdc"
#    "E:/VivadoWSP/Microblaze/DavinciPro_MicroBlaze/DavinciPro_MicroBlaze.srcs/utils_1/imports/synth_1/system_wrapper.dcp"

我们将 DavinciPro_MicroBlaze.srcs 文件夹和 DavinciPro_MicroBlaze.tcl 脚本拷贝到新的文件夹下,并删除 DavinciPro_MicroBlaze.srcs 下除 TCL 脚本提示备份的文件以外的其他无关文件。

TCL4

1.5.2 TCL脚本项目恢复

接下来我们介绍如何使用 TCL 脚本恢复项目。

重新打开 Vivado 软件,在 TCL Console 命令行输入下面两条命令开始重新生成 Vivado 项目工程。

cd e:/VivadoWSP/MicroBlazeBackup
source DavinciPro_MicroBlaze.tcl

TCL5

命令执行完成之后,重新自动布线将会生成新的 Block Design。

TCL6

2. MicroBlaze FreeRTOS开发

2.1 创建并运行 FreeRTOS 系统

FreeRTOS系统的开发是在 Vitis软件中完成的,接下来打开 Vitis 并设置其工作路径,这里我们将它设置为 Vivado设计的 Microblaze 项目路径下。

vitis_conf

2.1.1 创建硬件平台

在 Vitis 里选择 “File” > “New” > “Platform Project” ,创建硬件平台。

vitis_hw1

设置硬件平台名为 MicroBlaze_FreeRTOS.

vitis_hw2

选择 Vivado 编译导出的 .xsa 文件,操作系统选择 freertos10_xlinx, CPU处理器默认选择 microblaze_0.

vitis_hw3

接下来弹出的窗口就可以看到相应的硬件平台。

vitis_hw4

2.1.2 创建应用程序

在 Vitis 里选择 “File” > “New” > “Application Project” ,创建应用程序项目。

vitis_app1

欢迎页面直接点击下一步。

vitis_app2

选择前面创建好的硬件平台 MicroBlaze_FreeRTOS,点击 Next .

vitis_app3

设置应用项目名为 Artix7_FreeRTOS

vitis_app4

选择默认的 freertos10_xilinx_domain.

vitis_app5

选择 Hello World 项目模板.

vitis_app6

接下来的弹窗显示 FreeRTOS 项目创建成功了。

vitis_app7

2.1.3 编译并运行FreeRTOS

点击锤子按钮开始编译项目源码。

freertos1

编译完成后,在 FreeRTOS 项目上鼠标右键,选择 “Run As” -> “1 Lauch Hardware” 开始烧录程序到 FPGA 开发板上去。

freertos2

烧录完成后,我们在开发板的串口上可以看到 FreeRTOS Hello 程序的正常输出。

freertos3

查看程序的链接脚本,我们发现程序是链接到了 DDR3 内存上。至此,可以验证前面的 MicroBlaze、DDR3内存和串口硬件设计是正常的了。

freertos4

2.2 FreeRTOS添加流水灯

2.2.1 GPIO 编程接口API

在 Vitis 的 platform BSP 里,点击相应外设右侧的 Import Examples 链接,就可以添加相应外设的示例工程代码。通过这些示例工程,我们可以学习 Xilinx 的各种接口的编程 API。

接下来点击 axi_gpio_0 的示例代码。

gpio_api1

在弹出的串口中,我们选择LED的示例工程 xgpio_example。下面 xgpio_intr_tapp_example 则是按键的示例代码,在开发按键时可以参考该工程。

gpio_api2

打开 xgpio_example.c 源码如下。

gpio_api3

接下来,我们学习 GPIO 控制 Led 的函数API。

XGpio_Initialize() 函数

该函数用来初始化GPIO,其中第一个参数 Gpio 是GPIO的句柄,其定义如下:

XGpio Gpio; /* The Instance of the GPIO Driver */

第二个参数 GPIO_EXAMPLE_DEVICE_ID 定义如下,它是连接 Led 的GPIO0 的设备ID。

#define GPIO_EXAMPLE_DEVICE_ID  XPAR_GPIO_0_DEVICE_ID
#define XPAR_GPIO_0_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID

XGpio_SetDataDirection() 函数

该函数用来设置GPIO引脚的方向,其中第一个参数 Gpio 是GPIO的句柄,第二个参数是哪组GPIO,因为Led配置在 GPIO0这组上,所以其定义为1。

#define LED_CHANNEL 1

第三个参数为这组GPIO上所有GPIO口的方向位掩码。其中相应位设置为1表示该引脚位输入模式,而设置为0表示输出模式。这样如果要将4个Led都设置为输出模式,则可以用下面代码实现。

XGpio_SetDataDirection(&Gpio, LED_CHANNEL, 0x0F);

XGpio_DiscreteWrite() 函数

该函数用来设置相应GPIO口为高电平,其中第一个参数 Gpio 是GPIO的句柄,第二个参数是哪组GPIO,第三个参数为这组GPIO上要设置为高电平的引脚的位掩码。这样如果想将4个Led灯全部点亮,则可以使用下面代码。

XGpio_SetDataDirection(&Gpio, LED_CHANNEL, 0x0F);

XGpio_DiscreteClear() 函数

该函数用来设置相应GPIO口为低电平,其中第一个参数 Gpio 是GPIO的句柄,第二个参数是哪组GPIO,第三个参数为这组GPIO上要设置为高电平的引脚的位掩码。这样如果想将4个Led灯全部点亮,则可以使用下面代码。

XGpio_SetDataDirection(&Gpio, LED_CHANNEL, 0x0F);

在了解了 GPIO 操作的相关函数API后,接下来我们开始在 FreeRTOS 的项目工程中添加流水灯控制的任务。

2.2.2 添加 Led 操作函数

在FreeRTOS的项目工程目录 src 下创建并编写 keyled.h 头文件如下。

#ifndef _KEYLED_H_
#define _KEYLED_H_

typedef enum
{
    OFF,
    ON,
} status_t;

typedef enum
{
    Led0,
    Led1,
    Led2,
	Led3,
    LedMax,
} lednum_t;
#define BITMASK_ALLLED	((1<<LedMax)-1)

/* Initial leds GPIO port */
extern int init_led(void);

/* Turn $which(Led0~Led3) led to $status(ON/OFF) */
extern void turn_led(lednum_t which, status_t status);

#endif

在 src 下创建并编写 keyled.c 源文件如下。

#include "FreeRTOS.h"
#include "task.h"
#include "xgpio.h"
#include "keyled.h"

#define LED_CHANNEL			1 /* Only 1 channel in Vivado design for GPIO0(UserLeds) */

static XGpio          s_LedGpio;
static unsigned int   s_LedStatus = 0;  // All leds set be off as default

int init_led(void)
{
	int rv;

	/* Initialize the GPIO driver */
	rv = XGpio_Initialize(&s_LedGpio, XPAR_GPIO_0_DEVICE_ID);
	if (rv != XST_SUCCESS) {
		return XST_FAILURE;
	}

	/* Set all the led GPIO direction as output(0) */
	XGpio_SetDataDirection(&s_LedGpio, LED_CHANNEL, ~BITMASK_ALLLED);

    /* Set all the led status as off */
    XGpio_DiscreteWrite(&s_LedGpio, LED_CHANNEL, s_LedStatus);

	return XST_SUCCESS;
}

void turn_led(lednum_t which, status_t status)
{
	if( which >= LedMax )
		return ;

	taskENTER_CRITICAL();
	{
		if( ON == status )
			s_LedStatus |= (1<<which);
		else
			s_LedStatus &= ~(1<<which);

		XGpio_DiscreteWrite(&s_LedGpio, LED_CHANNEL, s_LedStatus);
	}
	taskEXIT_CRITICAL();

	return ;
}
  • MicroBlaze 的 GPIO操作函数都是通过设置相应的位来控制 GPIO 输出电平,这样我们需要一个全局变量 s_LedStatus 来保存当前各个 Led 灯的状态;

  • 因为 s_LedStatus 是全局变量,而 turn_led() 可能会被不同的任务调用,所以在使用它时需要临界区保护。

2.2.3 流水灯代码实现

freertos_hello_world.c 文件的合适位置添加 Led 的相关代码如下。

... ...
#include "keyled.h"
... ...
    
static void prvLedTask( void *pvParameters );
... ...
    
static TaskHandle_t xLedTask;
... ...
    
int main( void )
{
    ... ...
	xTaskCreate( prvLedTask,                /* Task function */
				 ( const char * ) "Led",    /* Task name */
				 configMINIMAL_STACK_SIZE,  /* Task stack size */
				 NULL,                      /* Task parameter */
				 tskIDLE_PRIORITY + 2,      /* Task priority */
				 &xLedTask );               /* Task handler */
    ... ...
} 
... ...
    
/*-----------------------------------------------------------*/
static void prvLedTask( void *pvParameters )
{
	int           led;

	if( init_led() )
	{
		xil_printf("Initial leds failure\r\n");
		return;
	}
	xil_printf("Leds task start running\r\n");

	for( ;; )
	{
		for(led=Led0; led<LedMax; led=(led+1)%LedMax)
		{
			turn_led(led, ON);
			vTaskDelay(pdMS_TO_TICKS(150));

			turn_led(led, OFF);
			vTaskDelay(pdMS_TO_TICKS(150));
		}
	}
}

编译并烧录运行该程序,我们可以看到开发板上四个Led流水灯程序正常运行。

2.3 FreeRTOS添加按键中断

在前面创建的 keyled.h 头文件里添加按键的相关代码如下。

... ...

typedef enum
{
    Key0,
    Key1,
    Key2,
	Key3,
    KeyMax,
} keynum_t;
#define BITMASK_ALLKEY	((1<<KeyMax)-1)
... ...

/* Initial keys GPIO port and interrupt */
extern int init_key(void);
... ....

在前面创建的 keyled.c 源文件相关位置添加/修改按键的相关代码如下。

#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include "xgpio.h"
#include "xintc.h"
#include "keyled.h"

#define LED_CHANNEL			1 /* Only 1 channel in Vivado design for GPIO0(UserLeds) */
#define KEY_CHANNEL			1 /* Only 1 channel in Vivado design for GPIO1(UserKeys) */

#define LED_DEVICE_ID		XPAR_GPIO_0_DEVICE_ID      /* UserLeds is on GPIO0 */
#define KEY_DEVICE_ID		XPAR_GPIO_1_DEVICE_ID      /* UserKeys is on GPIO1 */
#define KEY_INTERRUPT_ID	XPAR_INTC_0_GPIO_1_VEC_ID  /* UserKeys interrupt ID */

static XGpio          s_KeyGpio;
static XGpio          s_LedGpio;
static unsigned int   s_LedStatus = 0;  // All leds set be off as default
... ...

extern EventGroupHandle_t g_xKeyEventGroup;

void key_IrqHandler( void *CallbackRef )
{
	BaseType_t       xHigherPriorityTaskWoken = pdFALSE;
	BaseType_t       xResult;
	XGpio *keyGpio = (XGpio *)CallbackRef;
	unsigned int     i, value;

	/* Read GPIO port value and find which key pressed */
	value = XGpio_DiscreteRead(keyGpio, KEY_CHANNEL);
	for(i=Key0; i<KeyMax; i++)
	{
		if( !(value&(1<<i)) )
		{
			if( g_xKeyEventGroup )
			{
				xResult = xEventGroupSetBitsFromISR(g_xKeyEventGroup, 1<<i, &xHigherPriorityTaskWoken);
				if( xResult != pdFAIL )
				{
					/* If xHigherPriorityTaskWoken is now set to pdTRUE then a context switch should be requested. */
					portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
				}
			}
		}
	}

	/* Clear the Interrupt */
	XGpio_InterruptClear(keyGpio, KEY_CHANNEL);
}

int init_key(void)
{
	int rv;

	/* Initialize the GPIO driver */
	rv = XGpio_Initialize(&s_KeyGpio, KEY_DEVICE_ID);
	if (rv != XST_SUCCESS)
    {
		return XST_FAILURE;
	}

	/* Set all the keys GPIO direction as input(1) */
	XGpio_SetDataDirection(&s_KeyGpio, KEY_CHANNEL, BITMASK_ALLKEY);

	/* Must call FreeRTOS API to hook up interrupt service routine */
	xPortInstallInterruptHandler(KEY_INTERRUPT_ID, key_IrqHandler, &s_KeyGpio);

	/* Must call FreeRTOS api to enable the interrupt */
	vPortEnableInterrupt(KEY_INTERRUPT_ID);

	/* Enable the GPIO channel interrupts and enable interrupts for the GPIO device */
	XGpio_InterruptEnable(&s_KeyGpio, KEY_CHANNEL);
	XGpio_InterruptGlobalEnable(&s_KeyGpio);

	return XST_SUCCESS;
}
  • 在 FreeRTOS 里初始化按键GPIO的中断时,必须调用 FreeRTOS 里 port.c 里提供的 xPortInstallInterruptHandler()vPortEnableInterrupt() 函数,不能参考裸机代码来,否则 vTaskDelay() 将不能正常工作。

  • 在 MicroBlaze FreeRTOS 里不能设置中断的触发类型,这是因为在 Vivado 硬件设计时,我们将中断控制器的中断类型设置为自动。另外,在这里也没有接口能够配置中断的优先级。

  • 在按键的中断服务处理程序里,由于这4个按键共用同一路中断,所以我们需要读取GPIO相应寄存器的值来判断究竟是哪个按键按下了。在判断出哪个按键发生事件后,再通过 FreeRTOS 的事件组(Event Group) 来通知相应的按键任务。

  • 在这里作为示例测试代码,我们并没有在中断服务处理程序里对按键作消抖处理。

freertos_hello_world.c 文件的合适位置添加 按键任务相关代码如下。

... ...

#include "event_groups.h"
... ...

static void prvKeyTask( void *pvParameters );
... ...

static TaskHandle_t xKeyTask;
... ...

int main( void )
{
	... ...
	xTaskCreate( prvKeyTask,                /* Task function */
				 ( const char * ) "Key",    /* Task name */
				 configMINIMAL_STACK_SIZE,  /* Task stack size */
				 NULL,                      /* Task parameter */
				 tskIDLE_PRIORITY+3,        /* Task priority */
				 &xKeyTask );               /* Task handler */
	... ...
}
... ...

/*-----------------------------------------------------------*/

/* FreeRTOS event group for GPIO keys, will be set in the IRQ handler */
EventGroupHandle_t				g_xKeyEventGroup = NULL;

static void prvKeyTask( void *pvParameters )
{
	EventBits_t          evbits;

	if( init_key() )
	{
		xil_printf("Initial keys failure\r\n");
		return;
	}

	g_xKeyEventGroup=xEventGroupCreate();
	if( !g_xKeyEventGroup )
	{
		xil_printf("Create keys event group failure\r\n");
		return ;
	}

	xil_printf("Keys task start running\r\n");

	for( ;; )
	{
		/* Wait for any one of the keys pressed without timeout */
		evbits = xEventGroupWaitBits(g_xKeyEventGroup, BITMASK_ALLKEY, pdTRUE, pdFALSE, portMAX_DELAY);
		if( evbits & (1<<Key0) )
		{
			xil_printf("KEY0 Pressed\r\n");
		}
		else if( evbits & (1<<Key1) )
		{
			xil_printf("KEY1 Pressed\r\n");
		}
		else if( evbits & (1<<Key2) )
		{
			xil_printf("KEY2 Pressed\r\n");
		}
		else if( evbits & (1<<Key3) )
		{
			xil_printf("KEY3 Pressed\r\n");
		}
	}
}
  • 按键的中断初始化必须在相应的任务里调用,这是因为中断控制器是在 vTaskStartScheduler() 里初始化的。所以在 vTaskStartScheduler() 被调用之前不能初始化使能外设中断,也不能 vTaskDelay() 等依赖中断的函数。

  • 在按键的任务里,我们创建事件组并等待按键中断设置的相应的事件位,从而判断究竟是哪个按键按下了。xEventGroupWaitBits() 函数最后一个参数为超时时间,这里设置为 portMAX_DELAY 表示永不超时。

接下来我们编译并烧录运行该程序,在按下按键时就能正确识别相应的按键事件了。

keyled1

下面是裸机系统下按键中断初始化代码,在 FreeRTOS 操作系统中并不能直接使用该代码来初始化。这是因为在 FreeRTOS 的 port.c 文件中,已经定义了中断控制器的实例 XIntc xInterruptControllerInstance; ,如果我们这里再定义一个 s_Intc 就会导致中断控制器被重新改写,这样其他中断就不能响应了。

static XIntc s_Intc;
int init_key(void)
{
	int rv;

	/* Initialize the GPIO driver */
	rv = XGpio_Initialize(&s_KeyGpio, KEY_DEVICE_ID);
	if (rv != XST_SUCCESS) {
		return XST_FAILURE;
	}

	/* Set all the keys GPIO direction as input(1) */
	XGpio_SetDataDirection(&s_KeyGpio, KEY_CHANNEL, ((1<<LedMax)-1));

	/* Initialize interrupt controller driver */
	XIntc_Initialize(&s_Intc, INTC_DEVICE_ID);

	/* Hook up interrupt service routine */
	XIntc_Connect(&s_Intc, KEY_INTERRUPT_ID, key_IrqHandler, &s_KeyGpio );

	/* Enable the interrupt vector at the interrupt controller */
	XIntc_Enable(&s_Intc, KEY_INTERRUPT_ID);

	/* Start the interrupt controller */
	XIntc_Start(&s_Intc, XIN_REAL_MODE);

	/* Enable the GPIO channel interrupts and enable interrupts for the GPIO device */
	XGpio_InterruptEnable(&s_KeyGpio, KEY_CHANNEL);
	XGpio_InterruptGlobalEnable(&s_KeyGpio);

	/* Initialize the exception table and register the interrupt controller handler with the exception table*/
	Xil_ExceptionInit();
	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XIntc_InterruptHandler, &s_Intc);

	/* Enable non-critical exceptions */
	Xil_ExceptionEnable();

	return XST_SUCCESS;
}

2.4 FreeRTOS添加EEPROM

在FreeRTOS的项目工程目录 src 下创建并编写 eeprom.c 源文件如下。

#include "xparameters.h"
#include "xiic.h"
#include "xil_exception.h"
#include "xil_printf.h"
#include "xintc.h"
#include "FreeRTOS.h"
#include "task.h"

/*+---------------------+
 *|   AT24C64 EEPROM    |
 *+---------------------+*/
#define EEPROM_I2CADDR		0x50 /* 7-bits adress is 0x50, A2~A0 connect to ground */
#define PAGE_SIZE			32   /* AT24C64 EEPROM page size is 32 bytes */
#define ADDR_LEN			2    /* AT24C64 EEPROM offset address is 2 bytes */
#define EEPROM_TEST_ADDR	128  /* EEPROM test offset address */


/*+---------------------+
 *|      I2C Master     |
 *+---------------------+*/
#define IIC_DEVICE_ID		XPAR_IIC_0_DEVICE_ID
#define IIC_INTR_ID			XPAR_INTC_0_IIC_0_VEC_ID


/*+---------------------+
 *| Function Prototypes |
 *+---------------------+*/
int init_i2c(void);
int i2c_write(uint8_t slave_addr, uint8_t *buf, uint16_t bytes);
int i2c_read(uint8_t slave_addr, uint8_t *buf, uint16_t bytes);
static void SendHandler(XIic *InstancePtr);
static void ReceiveHandler(XIic *InstancePtr);
static void StatusHandler(XIic *InstancePtr, int Event);
static void dump_buf(const char *prompt, uint8_t *buf, uint32_t size);


/*+---------------------+
 *| Variable Definition |
 *+---------------------+*/

XIic				IicInstance;		/* The instance of the IIC device */
XIntc				Intc; 				/* The instance of the Interrupt Controller Driver */
volatile uint8_t	TransmitComplete;	/* Flag to check completion of Transmission */
volatile uint8_t	ReceiveComplete;	/* Flag to check completion of Reception */


/*+---------------------+
 *| Function Definition |
 *+---------------------+*/

int test_eeprom(void)
{
	uint8_t		WriteBuffer[ADDR_LEN+PAGE_SIZE];/* Page write need extra 2 bytes offset address */
	uint8_t		ReadBuffer[PAGE_SIZE];			/* Page read buffer */
	int			Status;
	int         i;

	/*+-----------------------------+
	 *| Write test data into EEPROM |
	 *+-----------------------------+*/

	/* Initialize the data to write and clear the read buffer */
	WriteBuffer[0] = (uint8_t) (EEPROM_TEST_ADDR >> 8);
	WriteBuffer[1] = (uint8_t) (EEPROM_TEST_ADDR);

	for(i=0; i<PAGE_SIZE; i++)
	{
		WriteBuffer[ADDR_LEN+i] = 0x5A;
		ReadBuffer[i] = 0;
	}

	/* Write data to the EEPROM */
	dump_buf("WriteBuffer:", WriteBuffer+2, PAGE_SIZE);
	Status = i2c_write(EEPROM_I2CADDR, WriteBuffer, sizeof(WriteBuffer));
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	/*+-----------------------------+
	 *| Read test data from EEPROM  |
	 *+-----------------------------+*/

	/* Send the offset address in EEPROM */
	WriteBuffer[0] = (uint8_t) (EEPROM_TEST_ADDR >> 8);
	WriteBuffer[1] = (uint8_t) (EEPROM_TEST_ADDR);

	Status = i2c_write(EEPROM_I2CADDR, WriteBuffer, ADDR_LEN);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	/* Read data from the EEPROM */
	Status = i2c_read(EEPROM_I2CADDR, ReadBuffer, sizeof(ReadBuffer));
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}
	dump_buf("ReadBuffer :", ReadBuffer, PAGE_SIZE);

	/*+-----------------------------+
	 *|    Verify the test data     |
	 *+-----------------------------+*/

	for(i=0; i<PAGE_SIZE; i++)
	{
		/* First two bytes are EEPROM offset address */
		if (ReadBuffer[i] != WriteBuffer[ADDR_LEN+i])
		{
			return XST_FAILURE;
		}
		ReadBuffer[i] = 0;
	}

	xil_printf("Successfully ran IIC eeprom Example\r\n");
	return XST_SUCCESS;
}


int init_i2c(void)
{
	XIic_Config *ConfigPtr;	/* Pointer to configuration data */
	int Status;

	/* Initialize the IIC driver so that it is ready to use. */
	ConfigPtr = XIic_LookupConfig(IIC_DEVICE_ID);
	if (ConfigPtr == NULL) {
		return XST_FAILURE;
	}

	Status = XIic_CfgInitialize(&IicInstance, ConfigPtr, ConfigPtr->BaseAddress);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	/* Call FreeRTOS API to hook up interrupt service routine */
	xPortInstallInterruptHandler(IIC_INTR_ID, XIic_InterruptHandler, &IicInstance);

	/* Call FreeRTOS api to enable the interrupt */
	vPortEnableInterrupt(IIC_INTR_ID);

	/* Set the Handlers for transmit and reception */
	XIic_SetSendHandler(&IicInstance, &IicInstance, (XIic_Handler) SendHandler);
	XIic_SetRecvHandler(&IicInstance, &IicInstance, (XIic_Handler) ReceiveHandler);
	XIic_SetStatusHandler(&IicInstance, &IicInstance, (XIic_StatusHandler) StatusHandler);

	return XST_SUCCESS;
}

int i2c_write(uint8_t slave_addr, uint8_t *buf, uint16_t bytes)
{
	int Status;

	/* Set the defaults */
	TransmitComplete = 0;
	IicInstance.Stats.TxErrors = 0;

	/* Set the Slave address */
	Status = XIic_SetAddress(&IicInstance, XII_ADDR_TO_SEND_TYPE, slave_addr);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	/* Start the IIC device, not send start condition here */
	Status = XIic_Start(&IicInstance);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	/* Send the Data, will send start condition here */
	Status = XIic_MasterSend(&IicInstance, buf, bytes);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	/* Wait till all the data is received */
	while ((!TransmitComplete) || (XIic_IsIicBusy(&IicInstance) == TRUE)) {
		/*
		 * This condition is required to be checked in the case where we
		 * are writing two consecutive buffers of data to the EEPROM.
		 * The EEPROM takes about 2 milliseconds time to update the data
		 * internally after a STOP has been sent on the bus.
		 * A NACK will be generated in the case of a second write before
		 * the EEPROM updates the data internally resulting in a
		 * Transmission Error.
		 */
		if (IicInstance.Stats.TxErrors != 0) {
			//xil_printf("I2C Master Tx Error happened\r\n");

			/* Enable the IIC device */
			Status = XIic_Start(&IicInstance);
			if (Status != XST_SUCCESS) {
				return Status;
			}


			if (!XIic_IsIicBusy(&IicInstance)) {
				/* Send the Data */
				Status = XIic_MasterSend(&IicInstance, buf, bytes);
				if (Status == XST_SUCCESS) {
					IicInstance.Stats.TxErrors = 0;
				}
			}
		}
	}

	/* Stop the IIC device */
	Status = XIic_Stop(&IicInstance);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	return XST_SUCCESS;
}

int i2c_read(uint8_t slave_addr, uint8_t *buf, uint16_t bytes)
{
	int Status;

	/* Set the Slave address */
	Status = XIic_SetAddress(&IicInstance, XII_ADDR_TO_SEND_TYPE, slave_addr);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	/* Set the defaults */
	ReceiveComplete = 0;

	/* Start the IIC device, not send start condition here */
	Status = XIic_Start(&IicInstance);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	/* Receive the Data, will send start condition here */
	Status = XIic_MasterRecv(&IicInstance, buf, bytes);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	/* Wait till all the data is received */
	while ((!ReceiveComplete) || (XIic_IsIicBusy(&IicInstance) == TRUE)) {

	}

	/* Stop the IIC device */
	Status = XIic_Stop(&IicInstance);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	return XST_SUCCESS;
}

static void SendHandler(XIic *InstancePtr)
{
	TransmitComplete = 1;
}

static void ReceiveHandler(XIic *InstancePtr)
{
	ReceiveComplete = 1;
}

static void StatusHandler(XIic *InstancePtr, int Event)
{

}

static void dump_buf(const char *prompt, uint8_t *buf, uint32_t size)
{
	if( prompt )
		xil_printf("%s\r\n", prompt);

	for(int i=0; i<size; i++)
	{
		xil_printf("0x%02x ", buf[i]);

		if( !((i+1)%16) )
			xil_printf("\r\n");
	}
}

freertos_hello_world.c 文件的合适位置添加 EEPROM 的相关测试代码如下。

... ...
    
static void prvI2cTask( void *pvParameters );
... ...
    
static TaskHandle_t xI2cTask;
... ...
    
int main( void )
{
    ... ...
	xTaskCreate( prvI2cTask,
				 ( const char * ) "I2C",
				 configMINIMAL_STACK_SIZE,
				 NULL,
				 tskIDLE_PRIORITY + 4,
				 &xI2cTask );
    ... ...
} 
... ...
    
/*-----------------------------------------------------------*/
extern int init_i2c(void);
extern int test_eeprom(void);
static void prvI2cTask( void *pvParameters )
{
	xil_printf("I2C EEPROM Task running\r\n");

	init_i2c();
	test_eeprom();

	for( ;; )
	{
		vTaskDelay( pdMS_TO_TICKS(100) );
	}
}

编译并烧录运行该程序,我们可以在串口输出上看到如下信息。

test_eeprom

3. MicroBlaze PetaLinux开发

PetaLinux 是一套直接构建在 Yocto 项目顶层的定制工具,用于实现与赛灵思平台的集成。因此,在某种意义上 PetaLinux 仍属于 Yocto。但赛灵思更进一步,通过提供一套额外的工具,简化了开发流程,使新手使用起来及其方便。我们可以将Vivado和PetaLinux安装到同一台电脑上,也可以分开安装。在这里,我们在Windows系统下安装Vivado、Vitis灯工具,而PetaLinux则安装在 Ubuntu22 服务器上,接下来我们将会详细讲解 PetaLinux的开发流程。

3.1 创建并运行 PetaLinux系统

3.3.1 创建 PetaLinux 项目

使用下面命令安装 PetaLinux-v2023 到 /tools/petalinux/ 路径下。

guowenxue@ubuntu22:~$ sudo mkdir -p /tools/petalinux/
guowenxue@ubuntu22:~$ sudo chmod 777 /tools/petalinux/
guowenxue@ubuntu22:~$ bash petalinux-v2023.1-05012318-installer.run --dir /tools/petalinux/

使用 source 命令导入 PetaLinux 相关命令。

guowenxue@ubuntu22:~$ source /tools/petalinux/settings.sh

使用 petalinux-create 命令创建基于 microblaze 处理器的 PetaLinux 工程。

guowenxue@ubuntu22:~$ petalinux-create --type project --template microblaze --name Artix7_PetaLinux
INFO: Create project: Artix7_PetaLinux
INFO: New project successfully created in /home/guowenxue/Artix7_PetaLinux

guowenxue@ubuntu22:~$ ls Artix7_PetaLinux/
project-spec

将 Vivado 设计导出的 .xsa 文件拷贝到 PetaLinux 项目路径下:

guowenxue@ubuntu22:~$ cd Artix7_PetaLinux/
guowenxue@ubuntu22:~/Artix7_PetaLinux$ mkdir -p hardware-spec
guowenxue@ubuntu22:~/Artix7_PetaLinux$ cp ~/system_wrapper.xsa hardware-spec/
guowenxue@ubuntu22:~/Artix7_PetaLinux$ ls
hardware-spec  project-spec

使用 petalinux-config 命令导入 .xsa 硬件描述文件到 PetaLinux工程中。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-config --get-hw-description ./hardware-spec/
[INFO] Sourcing buildtools
[INFO] Getting hardware description...
INFO: Renaming system_wrapper.xsa to system.xsa
[INFO] Extracting yocto SDK to components/yocto. This may take time!
[INFO] Generating Kconfig for project

在弹出的配置菜单栏里,取消 Copy final images to tftpboot 选项,我们并不需要使能 TFTP 来下载启动。

	Image Packaging Configuration  --->
		Root filesystem type (INITRAMFS)  --->
		
		[ ] Copy final images to tftpboot

可以使用petalinux-config 命令可以配置 u-boot、Linux内核、根文件系统 和 busybox,这里暂时使用默认配置,不作任何修改。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-config -c u-boot
guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-config -c kernel
guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-config -c rootfs
guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-config -c busybox

在 Microblaze 的 PetaLinux中,默认是使用的轻量级SSH服务程序 dropbear 。因为我们并没有使能网卡,另外 SSH 服务在 Linux系统启动时非常慢,所以这里我们将其直接移除了。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ mkdir -p project-spec/meta-user/recipes-core/images/
guowenxue@ubuntu22:~/Artix7_PetaLinux$ vim project-spec/meta-user/recipes-core/images/petalinux-image-minimal.bbappend  

IMAGE_INSTALL:remove = " packagegroup-core-ssh-dropbear" 

3.3.2 编译 PetaLinux 项目

使用 petalinux-build 命令编译 MicroBlaze 的 PetaLinux 项目。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-build
[INFO] Sourcing buildtools
[INFO] Building project
[INFO] Silentconfig project
[INFO] Silentconfig rootfs
[INFO] Generating workspace directory
INFO: bitbake petalinux-image-minimal
... ...

编译完成后生成的系统镜像将会存放到 images/linux/ 路径下,在 MicroBlaze 下的PetaLinux系统中,默认使用 ramdisk 根文件系统。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ ls images/linux/
boot.scr  fs-boot.elf  image.ub      pxelinux.cfg  rootfs.cpio.gz         rootfs.jffs2     rootfs.tar.gz  system.dtb  u-boot.elf    vmlinux
config    image.elf    linux.bin.ub  rootfs.cpio   rootfs.cpio.gz.u-boot  rootfs.manifest  system.bit     u-boot.bin  u-boot-s.bin

3.3.3 开发板运行 PetaLinux

将开发板的 J-tag 接口连接到 Windows 主机上并上电,然后运行 Launch Hardware Server 批处理脚本程序启动 J-TAG 网络调试代理。

HWServer

在 PetaLinux 编译的 Linux 服务器上,使用 petalinux-boot 命令,通过网络 J-TAG 调试将系统镜像烧录到开发板上运行。下面的IP地址是我的 Windows 系统的IP地址,而 3121 是 Launch Hardware Server 服务监听的默认端口号。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-boot --jtag --fpga --hw_server-url TCP:192.168.2.18:3121
guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-boot --jtag --kernel --hw_server-url TCP:192.168.2.18:3121

上面的烧录过程比较慢,烧录完成后系统将会自动启动 Linux操作系统。下面是系统启动的全部过程,其默认登录用户名为 petalinux ,第一次登录将被强制要求设置密码。

U-Boot 2023.01 (Mar 29 2023 - 13:08:40 +0000)

Model: Xilinx MicroBlaze
DRAM:  512 MiB
Core:  6 devices, 5 uclasses, devicetree: embed
Loading Environment from nowhere... OK
In:    serial
Out:   serial
Err:   serial
Model: Xilinx MicroBlaze
U-BOOT for microblaze-generic

Hit any key to stop autoboot:  0 
JTAG: Trying to boot script at 9f200000
## Executing script at 9f200000
Trying to load boot images from jtag
## Booting kernel from Legacy Image at 80000000 ...
   Image Name:   Linux-6.1.5-xilinx-v2023.1
   Image Type:   MicroBlaze Linux Kernel Image (uncompressed)
   Data Size:    9986564 Bytes = 9.5 MiB
   Load Address: 80000000
   Entry Point:  80000000
   Verifying Checksum ... OK
## Loading init Ramdisk from Legacy Image at 82e00000 ...
   Image Name:   petalinux-image-minimal-microbla
   Image Type:   MicroBlaze Linux RAMDisk Image (uncompressed)
   Data Size:    8517903 Bytes = 8.1 MiB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
## Flattened Device Tree blob at 81e00000
   Booting using the fdt blob at 0x81e00000
Working FDT set to 81e00000
   Loading Kernel Image
   Loading Ramdisk to 9f698000, end 9feb790f ... OK
   Loading Device Tree to 9f691000, end 9f697394 ... OK
Working FDT set to 9f691000

Starting kernel ...

Ramdisk addr 0x9f698000, 
FDT at 0x9f691000
earlycon: uartlite_a0 at MMIO 0x40600000 (options '115200n8')
printk: bootconsole [uartlite_a0] enabled
cma: Reserved 16 MiB at 0x9e400000
Linux version 6.1.5-xilinx-v2023.1 (oe-user@oe-host) (microblazeel-xilinx-linux-gcc (GCC) 12.2.0, GNU ld (GNU Binutils) 2.39.0.20220819) #1 Fri Apr 21 07:47:58 UTC 2023
setup_memory: max_mapnr: 0x20000
setup_memory: min_low_pfn: 0x80000
setup_memory: max_low_pfn: 0xa0000
setup_memory: max_pfn: 0xa0000
Zone ranges:
  DMA      [mem 0x0000000080000000-0x000000009fffffff]
  Normal   empty
  HighMem  empty
Movable zone start for each node
Early memory node ranges
  node   0: [mem 0x0000000080000000-0x000000009fffffff]
Initmem setup node 0 [mem 0x0000000080000000-0x000000009fffffff]
setup_cpuinfo: initialising cpu 0
setup_cpuinfo: Using full CPU PVR support
wb_msr
pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
pcpu-alloc: [0] 0 
Built 1 zonelists, mobility grouping on.  Total pages: 130048
Kernel command line: console=ttyUL0,115200 earlycon root=/dev/ram0 rw
Dentry cache hash table entries: 65536 (order: 6, 262144 bytes, linear)
Inode-cache hash table entries: 32768 (order: 5, 131072 bytes, linear)
mem auto-init: stack:all(zero), heap alloc:off, heap free:off
Memory: 485796K/524288K available (6090K kernel code, 566K rwdata, 1520K rodata, 180K init, 296K bss, 22108K reserved, 16384K cma-reserved, 0K highmem)
NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
irq-xilinx: /amba_pl/interrupt-controller@41200000: num_irq=4, sw_irq=0, edge=0x2
xilinx_timer_init: Timer base: 0xf0020000, Clocksource base: 0xf0020010
clocksource: xilinx_clocksource: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604467 ns
sched_clock: 32 bits at 100MHz, resolution 10ns, wraps every 21474836475ns
/amba_pl/timer@41c00000: irq=1, cpu_id 0
xilinx_timer_shutdown
xilinx_timer_set_periodic
Calibrating delay loop... 49.56 BogoMIPS (lpj=247808)
pid_max: default: 4096 minimum: 301
Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
devtmpfs: initialized
clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns
futex hash table entries: 16 (order: -4, 448 bytes, linear)
NET: Registered PF_NETLINK/PF_ROUTE protocol family
DMA: preallocated 128 KiB GFP_KERNEL pool for atomic allocations
DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA pool for atomic allocations
audit: initializing netlink subsys (disabled)
audit: type=2000 audit(0.290:1): state=initialized audit_enabled=0 res=1
gpio gpiochip1: (40010000.gpio): not an immutable chip, please consider fixing it!
pps_core: LinuxPPS API ver. 1 registered
pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <giometti@linux.it>
PTP clock support registered
vgaarb: loaded
clocksource: Switched to clocksource xilinx_clocksource
NET: Registered PF_INET protocol family
IP idents hash table entries: 8192 (order: 4, 65536 bytes, linear)
tcp_listen_portaddr_hash hash table entries: 256 (order: 0, 5120 bytes, linear)
Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear)
TCP established hash table entries: 4096 (order: 2, 16384 bytes, linear)
TCP bind hash table entries: 4096 (order: 5, 163840 bytes, linear)
TCP: Hash tables configured (established 4096 bind 4096)
UDP hash table entries: 256 (order: 1, 12288 bytes, linear)
UDP-Lite hash table entries: 256 (order: 1, 12288 bytes, linear)
NET: Registered PF_UNIX/PF_LOCAL protocol family
RPC: Registered named UNIX socket transport module.
RPC: Registered udp transport module.
RPC: Registered tcp transport module.
RPC: Registered tcp NFSv4.1 backchannel transport module.
PCI: CLS 0 bytes, default 32
Trying to unpack rootfs image as initramfs...
workingset: timestamp_bits=30 max_order=17 bucket_order=0
Key type cifs.idmap registered
romfs: ROMFS MTD (C) 2007 Red Hat, Inc.
io scheduler mq-deadline registered
io scheduler kyber registered
Serial: 8250/16550 driver, 4 ports, IRQ sharing disabled
40600000.serial: ttyUL0 at MMIO 0x40600000 (irq = 3, base_baud = 0) is a uartlite
printk: console [ttyUL0] enabled
printk: console [ttyUL0] enabled
printk: bootconsole [uartlite_a0] disabled
printk: bootconsole [uartlite_a0] disabled
brd: module loaded
NET: Registered PF_PACKET protocol family
Key type dns_resolver registered
Freeing initrd memory: 8316K
Key type encrypted registered
Freeing unused kernel image (initmem) memory: 180K
This architecture does not have kernel memory protection.
Run /init as init process
  with arguments:
    /init
  with environment:
    HOME=/
    TERM=linux
INIT: version 3.04 booting
Starting mdev... OK
Fri Mar  9 12:34:56 UTC 2018
random: crng init done
Configuring packages on first boot....
 (This may take several minutes. Please do not power off the machine.)
Running postinst /etc/rpm-postinsts/100-sysvinit-inittab...
update-rc.d: /etc/init.d/run-postinsts exists during rc.d purge (continuing)
 Removing any system startup links for run-postinsts ...
  /etc/rcS.d/S99run-postinsts
INIT: Entering runlevel: 5
Configuring network interfaces... Cannot find device "eth0"
Starting internet superserver: inetd.
Starting syslogd/klogd: done
Starting tcf-agent: OK

PetaLinux 2023.1+release-S05010539 Artix7_PetaLinux ttyUL0

Artix7_PetaLinux login: petalinux
You are required to change your password immediately (administrator enforced).
New password: 
Retype new password: 
Artix7_PetaLinux:~$ 
Artix7_PetaLinux:~$ uname -a
Linux Artix7_PetaLinux 6.1.5-xilinx-v2023.1 #1 Fri Apr 21 07:47:58 UTC 2023 microblaze GNU/Linux
Artix7_PetaLinux:~$ 

3.2 PetaLinux添加按键和Led

3.2.1 PetaLinux下GPIO测试

在 Microblaze 的 PetaLinux 默认镜像中,并没有按键测试和GPIO操作的基本命令,我们可以在其 .bbapend 文件中添加它们。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ vim project-spec/meta-user/recipes-core/images/petalinux-image-minimal.bbappend  

IMAGE_INSTALL:append = " \
    evtest \
    libgpiod \
    libgpiod-tools \
    "

修改完成后重新并编译生成 PetaLinux镜像。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-build 

使用下面命令重新烧录系统镜像并运行。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-boot --jtag --fpga --hw_server-url TCP:192.168.2.18:3121
guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-boot --jtag --kernel --hw_server-url TCP:192.168.2.18:3121

开发板的Linux系统启动后,我们可以使用 libgpiod 提供的相应GPIO操作命令工具来测试添加的Led和按键硬件是否能够正常工作。使用 gpioinfo 命令可以查看当前 GPIO 引脚信息。

Artix7_PetaLinux:~$ sudo gpioinfo
gpiochip0 - 4 lines:
        line   0:      unnamed       unused   input  active-high 
        line   1:      unnamed       unused   input  active-high 
        line   2:      unnamed       unused   input  active-high 
        line   3:      unnamed       unused   input  active-high 
gpiochip1 - 4 lines:
        line   0:      unnamed       unused   input  active-high 
        line   1:      unnamed       unused   input  active-high 
        line   2:      unnamed       unused   input  active-high 
        line   3:      unnamed       unused   input  active-high 
  • gpiochip0 对应的是 4 个用户Led输出控制引脚;

  • gpiochip1 对应的是 4 个用户按键输入检测引脚;

使用 gpioset 命令可以控制 4 个用户Led的亮灭。

Artix7_PetaLinux:~$ sudo gpioset gpiochip0 0=1 1=1 2=1 3=1   点亮4个Led灯
Artix7_PetaLinux:~$ sudo gpioset gpiochip0 0=0 1=0 2=0 3=0   熄灭4个Led灯

使用 gpioget 命令可以读取 4 个用户按键的输入检测。

Artix7_PetaLinux:~$ sudo gpioget gpiochip1 0  不按Key0按键执行该命令
1   
Artix7_PetaLinux:~$ sudo gpioget gpiochip1 0  按住Key0按键执行该命令
0

通过 libgpiod 提供的GPIO命令行工具测试,我们可以验证按键和Led灯的硬件设计都是正常的,接下来我们将在PetaLinux里的设备树里添加设备结点和驱动。

3.2.2 添加按键和Led驱动

PetaLinux生成的 .dts 文件是存放在 components/plnx_workspace/device-tree/device-tree/ 路径下,它是在 petalinux-build 编译时生成的。这样每次通过 Vivado 修改硬件并使用 petalinux-config 导入相关硬件信息后,还需要重新执行 petalinux-build 编译才能获取最新的设备树文件。

guowenxue@7e9b2d31cf4d:~/Artix7_PetaLinux$ ls components/plnx_workspace/device-tree/device-tree/
device-tree.mss           include/                  system-conf.dtsi          system_wrapper/           system_wrapper.bit  
hardware_description.xsa  pl.dtsi                   system-top.dts            system_wrapper_2/         system_wrapper.mmi   

system-top.dts 是总的 dts 文件,在它里面 #include 了两个 dtsi 文件。其中 pl.dtsi 是最底层的核心 dtsi 文件,而 system-user.dtsi 是用户添加们添加 led 和 按键节点 或其他设备结点时需要修改的文件。

guowenxue@7e9b2d31cf4d:~/Artix7_PetaLinux$ vim components/plnx_workspace/device-tree/device-tree/system-top.dts

/dts-v1/;
#include "pl.dtsi"
/ {
    chosen {
        bootargs = "earlycon";
        stdout-path = "serial0:115200n8";
    };  
    aliases {
        i2c0 = &axi_iic_0;
        serial0 = &axi_uartlite_0;
    };  
    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;
    };  
};
#include "system-user.dtsi"

在生成的核心 pl.dtsi 文件中,我们可以看到添加的两组 GPIO 结点 axi_gpio_0axi_gpio_1

guowenxue@ubuntu22:~/Artix7_PetaLinux$ vim components/plnx_workspace/device-tree/device-tree/pl.dtsi

        axi_gpio_0: gpio@40000000 {
            #gpio-cells = <2>;
            clock-frequency = <100000000>;
            clock-names = "s_axi_aclk";
            clocks = <&clk_bus_0>;
            compatible = "xlnx,axi-gpio-2.0", "xlnx,xps-gpio-1.00.a";
            gpio-controller ;
            reg = <0x40000000 0x10000>;
            xlnx,all-inputs = <0x0>;
            xlnx,all-inputs-2 = <0x0>;
            xlnx,all-outputs = <0x1>;
            xlnx,all-outputs-2 = <0x0>;
            xlnx,dout-default = <0x00000000>;
            xlnx,dout-default-2 = <0x00000000>;
            xlnx,gpio-width = <0x4>;
            xlnx,gpio2-width = <0x20>;
            xlnx,interrupt-present = <0x0>;
            xlnx,is-dual = <0x0>;
            xlnx,tri-default = <0xFFFFFFFF>;
            xlnx,tri-default-2 = <0xFFFFFFFF>;
        };  
        axi_gpio_1: gpio@40010000 {
            #gpio-cells = <2>;
            #interrupt-cells = <2>;
            clock-frequency = <100000000>;
            clock-names = "s_axi_aclk";
            clocks = <&clk_bus_0>;
            compatible = "xlnx,axi-gpio-2.0", "xlnx,xps-gpio-1.00.a";
            gpio-controller ;
            interrupt-controller ;
            interrupt-names = "ip2intc_irpt";
            interrupt-parent = <&microblaze_0_axi_intc>;
            interrupts = <2 2>;
            reg = <0x40010000 0x10000>;
            xlnx,all-inputs = <0x0>;
            xlnx,all-inputs-2 = <0x0>;
            xlnx,all-outputs = <0x0>;
            xlnx,all-outputs-2 = <0x0>;
            xlnx,dout-default = <0x00000000>;
            xlnx,dout-default-2 = <0x00000000>;
            xlnx,gpio-width = <0x4>;
            xlnx,gpio2-width = <0x20>;
            xlnx,interrupt-present = <0x1>;
            xlnx,is-dual = <0x0>;
            xlnx,tri-default = <0xFFFFFFFF>;
            xlnx,tri-default-2 = <0xFFFFFFFF>;
        };

接下来,我们在 meta-user 下的设备树文件 system-user.dtsi 里添加新的 leds 和 keys 节点如下。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ vim project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi 

/include/ "system-conf.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>

/ {
    leds {
        compatible = "gpio-leds";
        pinctrl-names = "default";
        status = "okay";

        led0 {
                label = "led0";
                gpios = <&axi_gpio_0 0 GPIO_ACTIVE_HIGH>;
                linux,default-trigger = "heartbeat";
                default-state = "on";
        };  

        led1 {
                label = "led1";
                gpios = <&axi_gpio_0 1 GPIO_ACTIVE_HIGH>;
                linux,default-trigger = "timer";
                default-state = "on";
        };  

        led2 {
                label = "led2";
                gpios = <&axi_gpio_0 2 GPIO_ACTIVE_HIGH>;
                linux,default-trigger = "gpio";
                default-state = "on";
        };  

        led3 {
                label = "led3";
                gpios = <&axi_gpio_0 3 GPIO_ACTIVE_HIGH>;
                linux,default-trigger = "none";
                default-state = "on";
        };  
    };  

    keys {
        compatible = "gpio-keys";
        pinctrl-names = "default";
        status = "okay";

        UserKey0 {
            label = "Key0";
            gpios = <&axi_gpio_1 0 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_0>;
        };  

        UserKey1 {
            label = "Key1";
            gpios = <&axi_gpio_1 1 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_1>;
        };  

        UserKey2 {
            label = "Key2";
            gpios = <&axi_gpio_1 2 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_2>;
        };  

        UserKey3 {
            label = "Key3";
            gpios = <&axi_gpio_1 3 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_3>;
        };
    };
};

对Linux内核进行 make menuconfig 配置,添加 Led和按键驱动。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-config -c kernel

Device Drivers  --->
    [*] LED Support  --->
        <*>   LED Class Support
        <*>   LED Support for GPIO connected LEDs
        [*]   LED Trigger support  --->
            <*>   LED Timer Trigger
            <*>   LED Heartbeat Trigger
            <*>   LED GPIO Trigger
            <*>   LED Default ON Trigger
            
    Input device support  --->
        <*> Generic input layer (needed for keyboard, mouse, ...)	
        <*>   Event interface
        [*]   Keyboards (NEW)  --->
            <*>   GPIO Buttons
            <*>   Polled GPIO buttons

用户配置生成的内核配置文件存放在 project-spec/meta-user/recipes-kernel/linux/linux-xlnx/ 路径下。

guowenxue@7e9b2d31cf4d:~/Artix7_PetaLinux$ ls project-spec/meta-user/recipes-kernel/linux/linux-xlnx/
bsp.cfg  user_2023-10-13-02-41-00.cfg

重新编译 PetaLinux系统。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-build 

编译完成之后,再重新烧录镜像并启动系统。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-boot --jtag --fpga --hw_server-url TCP:192.168.2.18:3121
guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-boot --jtag --kernel --hw_server-url TCP:192.168.2.18:3121

3.2.3 按键和Led驱动测试

开发板上的PetaLinux系统启动登录后,使用 evtest 命令测试 PetaLinux 中的三个用户按键,均可以正常工作。

Artix7_PetaLinux:~$ ls /dev/input/
event0

Artix7_PetaLinux:~$ sudo evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0:      keys
Select the device event number [0-0]: 0
Input driver version is 1.0.1
Input device ID: bus 0x19 vendor 0x1 product 0x1 version 0x100
Input device name: "keys"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 2 (KEY_1)
    Event code 3 (KEY_2)
    Event code 4 (KEY_3)
    Event code 11 (KEY_0)
Properties:
Testing ... (interrupt to exit)
Event: time 1520599062.821167, type 1 (EV_KEY), code 11 (KEY_0), value 1
Event: time 1520599062.821167, -------------- SYN_REPORT ------------
Event: time 1520599062.981168, type 1 (EV_KEY), code 11 (KEY_0), value 0
Event: time 1520599062.981168, -------------- SYN_REPORT ------------
Event: time 1520599063.581159, type 1 (EV_KEY), code 2 (KEY_1), value 1
Event: time 1520599063.581159, -------------- SYN_REPORT ------------
Event: time 1520599063.771163, type 1 (EV_KEY), code 2 (KEY_1), value 0
Event: time 1520599063.771163, -------------- SYN_REPORT ------------
Event: time 1520599065.981174, type 1 (EV_KEY), code 3 (KEY_2), value 1
Event: time 1520599065.981174, -------------- SYN_REPORT ------------
Event: time 1520599066.131197, type 1 (EV_KEY), code 3 (KEY_2), value 0
Event: time 1520599066.131197, -------------- SYN_REPORT ------------
Event: time 1520599066.591174, type 1 (EV_KEY), code 4 (KEY_3), value 1
Event: time 1520599066.591174, -------------- SYN_REPORT ------------
Event: time 1520599066.761177, type 1 (EV_KEY), code 4 (KEY_3), value 0
Event: time 1520599066.761177, -------------- SYN_REPORT ------------
^C
Artix7_PetaLinux:~$ 

另外在 /sys/class/leds/ 路径下也可以看到添加的4个Led灯都存在。

Artix7_PetaLinux:~$ ls /sys/class/leds/   
led0  led1  led2  led3

通过 trigger 文件查看我们在前面Linux内核配置里支持的led触发模式。

Artix7_PetaLinux:~$ cat /sys/class/leds/led3/trigger 
[none] timer heartbeat gpio default-on

当前 led3 默认触发模式为 none ,它与 gpio 的控制一致。使用下面命令可以控制相应 Led 灯的亮灭。

Artix7_PetaLinux:~$ sudo sh -c 'echo 1 > /sys/class/leds/led3/brightness'   点亮Led灯
Artix7_PetaLinux:~$ sudo sh -c 'echo 0 > /sys/class/leds/led3/brightness'   熄灭Led灯

修改 led3 的触发模式为 heartbeat , 这时就可以看到 Led3 工作在心跳灯模式了。

Artix7_PetaLinux:~$ sudo sh -c 'echo heartbeat > /sys/class/leds/led3/trigger'

修改 led3 的触发模式为 timer , 这时就可以看到 Led3 就会定时周期性闪烁了。

Artix7_PetaLinux:~$ sudo sh -c 'echo timer > /sys/class/leds/led3/trigger'

至此,在Vivado 下添加用户按键和Led灯硬件外设,同时在PetaLinux下添加用户按键和Led灯驱动并测试成功。通过该过程我们可以窥探FPGA MicroBlaze开发PetaLinux的整个流程。如果懂Linux内核驱动和Yocto系统开发,整个软件开发流程都是小儿科了。

3.3 PetaLinux添加I2C总线

3.3.1 添加RTC和EEPROM驱动

在生成的核心 .dtsi 文件中,我们可以看到新添加的 I2C 控制器 axi_iic_0

guowenxue@ubuntu22:~/Artix7_PetaLinux$ vim components/plnx_workspace/device-tree/device-tree/pl.dtsi

        axi_iic_0: i2c@40800000 {
            #address-cells = <1>;
            #size-cells = <0>;
            clock-frequency = <100000000>;
            clocks = <&clk_bus_0>;
            compatible = "xlnx,axi-iic-2.1", "xlnx,xps-iic-2.00.a";
            interrupt-names = "iic2intc_irpt";
            interrupt-parent = <&microblaze_0_axi_intc>;
            interrupts = <3 2>;
            reg = <0x40800000 0x10000>;
        };  

meta-user 下的设备树文件 system-user.dtsi 里添加 RTC 和 EEPROM 设备结点。

guowenxue@7e9b2d31cf4d:~/Artix7_PetaLinux$ vim project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
... ..
&axi_iic_0 {
    status = "okay";
    pinctrl-names = "default";

    rtc: rtc@51 {
        compatible = "nxp,pcf8563";
        reg = <0x51>;
        wakeup-source;
    };  

    eeprom: eeprom@50 {
        compatible = "atmel,24c64";
        reg = <0x50>;
        pagesize = <32>;
    };  
};

对Linux内核进行 make menuconfig 配置,添加 RTC 和 EEPROM 芯片驱动。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-config -c kernel

Device Drivers  --->
    I2C support  --->
        <*>   I2C device interface
    Misc devices  --->
        EEPROM support  --->
            <*> I2C EEPROMs / RAMs / ROMs from most vendors
    [*] Real Time Clock  --->
        <*>   Philips PCF8563/Epson RTC8564

重新编译 PetaLinux系统。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-build 

编译完成之后,再重新烧录镜像并启动系统。

guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-boot --jtag --fpga --hw_server-url TCP:192.168.2.18:3121
guowenxue@ubuntu22:~/Artix7_PetaLinux$ petalinux-boot --jtag --kernel --hw_server-url TCP:192.168.2.18:3121

3.3.2 RTC和EEPROM驱动测试

开发板上的PetaLinux系统启动登录后,我们可以看到 RTC 和 EEPROM 设备驱动都已工作。

Artix7_PetaLinux:~$ ls /dev/rtc0  
/dev/rtc0

Artix7_PetaLinux:~$ ls /sys/bus/i2c/devices/0-0050/
0-00504/   driver/    eeprom     modalias   name       of_node/   subsystem/ uevent  

使用 hwclock 命令测试 RTC 芯片是否正常工作。第一次使用hwclock -r 命令读RTC时间失败这时正常的,因为此前我们并没有设置过RTC芯片时间。

Artix7_PetaLinux:~$ sudo hwclock -r
rtc-pcf8563 0-0051: low voltage detected, date/time is not reliable.

使用 date 命令设置当前系统时间,再使用 hwclock -w 命令将系统时间写入到 RTC 芯片中去。

Artix7_PetaLinux:~$ sudo date -s "2023-10-9 14:38:30"
Artix7_PetaLinux:~$ sudo hwclock -w

接下来使用 hwclock -r 命令读取 RTC 芯片时间,这时可以看到 RTC 芯片正常工作了。

Artix7_PetaLinux:~$ sudo hwclock -r
Mon Oct  9 14:38:41 2023  0.000000 seconds

Artix7_PetaLinux:~$ sudo hwclock -r
Mon Oct  9 14:38:50 2023  0.000000 seconds

使用 cat 命令查看 EEPROM 芯片内的数据,这里以十六进制格式显示。

Artix7_PetaLinux:~$ sudo sh -c 'cat /sys/bus/i2c/devices/0-0050/eeprom | od -x'
0000000     0100    0302    0504    0706    0908    0b0a    0d0c    0f0e
0000020     1110    1312    1514    1716    1918    1b1a    1d1c    1f1e
0000040     2120    2322    2524    2726    2928    2b2a    2d2c    2f2e
0000060     3130    3332    3534    3736    3938    3b3a    3d3c    3f3e
0000100     4140    4342    4544    4746    4948    4b4a    4d4c    4f4e
0000120     5150    5352    5554    5756    5958    5b5a    5d5c    5f5e
0000140     6160    6362    6564    6766    6968    6b6a    6d6c    6f6e
0000160     7170    7372    7574    7776    7978    7b7a    7d7c    7f7e
0000200     8180    8382    8584    8786    8988    8b8a    8d8c    8f8e
0000220     9190    9392    9594    9796    9998    9b9a    9d9c    9f9e
0000240     a1a0    a3a2    a5a4    a7a6    a9a8    abaa    adac    afae
0000260     b1b0    b3b2    b5b4    b7b6    b9b8    bbba    bdbc    bfbe
0000300     c1c0    c3c2    c5c4    c7c6    c9c8    cbca    cdcc    cfce
0000320     d1d0    d3d2    d5d4    d7d6    d9d8    dbda    dddc    dfde
0000340     e1e0    e3e2    e5e4    e7e6    e9e8    ebea    edec    efee
0000360     f1f0    f3f2    f5f4    f7f6    f9f8    fbfa    fdfc    fffe
0000400     ffff    ffff    ffff    ffff    ffff    ffff    ffff    ffff
*
0020000

使用 echo 命令写入数据到 EEPROM 芯片中。

Artix7_PetaLinux:~$ sudo sh -c 'echo "hello" > /sys/bus/i2c/devices/0-0050/eeprom'

再次使用字符串形式查看 EEPROM 文件内容,这里发现 “hello”字符串已成功写入EEPROM中。

Artix7_PetaLinux:~$ sudo sh -c 'cat /sys/bus/i2c/devices/0-0050/eeprom'        
hello



 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~Artix7_PetaLinux:~$