PS端程序开发学习_20260309

工作流程

通过vivado导出xsa硬件文件

创建工程文件后,对文件内的相关ip核进行配置。 alt text 点击zynq ip核,re-customlise IP,即可对ip核进行配置。

构建vitis工程

随后完成工程文件其他配置,生成比特流文件后,依次展开File - Export - Export Hardware… ,跟随系统进行设置,即可选择路径导出.xsa结尾的硬件文件。

之后打开Vitis Classic 2023.2(相较于基于现代框架的Vitis 2023.2,更适合仅进行简单的嵌入式 C/C++ 开发),选择好工作路径后启动。

之后先添加设备树模板。 alt text

进入界面配置。 alt text 点击new后将设备树添加进来,apply应用修改后关闭。

随后File-New-Platform Project,创建新工程文件。 alt text

输入工程名后,添加前面的xsa硬件文件,选择设备树与CPU属性,配置好其它参数。 alt text

之后右键点击创建的工程文件soc_base,选择build project,进行编译。 alt text 控制台输出信息Build Finished (took 2m:17s.31ms),表明编译完成。

在对应路径下,找到相关的文件:

  1. PS和PL端的配置文件 system_wrapper.bit
    {工程位置}\soc_sdk\soc_base\hw\
  2. FSBL启动文件 fsbl.elf
    {工程位置}\soc_sdk\soc_base\export\soc_base\sw\soc_base\boot\
  3. pmu配置文件 pmufw.elf
    {工程位置}\soc_sdk\soc_base\export\soc_base\sw\soc_base\boot\

这几个是大部分的移植系统所需要的文件。

将system_wrapper.bit、fsbl.elf 两个文件分别改名为 system.bit、zynqmp_fsbl.elf。 alt text

制作烧写系统,虚拟机上进行交叉编译

随后将前面找到并改名了的文件,导入虚拟机的
{解压目录}/uisrc-lab-xlnx/boards/mzux/ubuntu/output/target
路径下。

然后将zu_dts文件夹中的设备树文件zynqmp_mzux.dts,分别复制到:
{解压目录}/uisrc-linuxb/sources/uboot/arch/arm/dts
{解压目录}/uisrc-linuxb/sources/kernel/arch/arm64/boot/dts/xilinx

打开终端,依次输入指令,制作烧写系统:

  1. 配置环境变量:source scripts/mzuxcfg.sh
  2. 制作引导程序:make_uboot.sh
  3. 制作Linux内核:make_kernel.sh
  4. 创建镜像:create_image.sh
  5. 插入读卡器,格式化TF卡:make_parted.sh
  6. 将系统拷贝进TF卡中:deploy_image.sh

将驱动程序代码、应用程序代码和MakeFile移植进虚拟机进行交叉编译,获得驱动程序和应用程序,实验所使用的程序为KernelPrint.c和KernelPrintApp.c。
将获得的程序移回电脑。

开发板运行程序

开发板模式设置为SD卡启动模式,模式开关设置为0011,即ON-ON-OFF-OFF。
alt text

通过xshell与开发板传输文件。

使用串口连接开发板。之后在Xshell中,选择新建会话。 alt text

协议选择SERIAL。 alt text

串口号选择电脑与开发板链接所用的串口。之后点击连接。 alt text

连接后终端界面显示connected,表明连接成功。需要注意,显示连接成功后再给开发板通电。
通电后显示开发板的Linux系统开机信息。 alt text 需要注意,如果开机时卡在Starting Kernel......,说明Linux 内核在启动初期就崩溃或挂起,并且未能将调试/错误信息输出到当前串口,问题可能出在前面的系统移植的相关文件中,可以尝试检查前面vitis工程构建的流程。

开机完成后,登录Linux系统。其中用户名为uisrc,密码为root。 alt text

进入root模式,跳转到/home/uisrc路径下。

sudo -i
cd /home/uisrc

输入rz,会跳出文件传输界面,可以将前面获得的驱动程序和应用程序文件导入开发板。
alt text
rz是lrzsz包中的一个命令,用于通过Zmodem协议接收文件。
如果输入指令后,显示没有该指令,则直接下载lrzsz包即可。

apt update
apt install lrzsz

再输入ls,检查文件是否传输到对应位置。 alt text

通过insmod安装驱动,再通过lsmod检查当前安装的驱动。 alt text
使用chmod改变KernelPrintApp权限,输入 chmod 777 KernelPrintApp

之后即可运行程序,并通过指令测试程序功能。

输入./KernelPrintApp /dev/KernelPrint_0 r,测试读功能。
alt text
写在驱动中的信息,被应用调用成功了。 说明驱动的 read 功能正常。

进行写测试,在命令行输入./KernelPrintApp /dev/KernelPrint_0 w “Just do it!”。再打印内核输出。
alt text
可以看到驱动收到应用程序的输入,并在内核中打印了出来。这说明驱动的 write 功能正常。

程序分析(以寄存器操作MIO例程为例)

该例程用于控制开发板上led灯亮灭功能。

按照上述流程,完成板子上的linux系统配置、驱动程序编译,并将驱动程序上传到开发板后,输入insmod led_drv_v1.ko安装驱动程序。 alt text

可见开发板上相关led灯熄灭。

之后输入rmmod led_drv_v1.ko卸载驱动程序后,相关led灯再次亮起。

内存映射

在led_drv_v1.c程序中,首先进行内存映射,将物理地址转换为虚拟地址,便于进行寄存器操作。

	//硬件初始化
	//内存映射
	gpio_addr = ioremap(GPIO_BASE_ADDR, GPIO_BASE_SIZE);
	iou_slcr = ioremap(IOU_SLCR_ADDR, IOU_SLCR_SIZE);
	printk("gpio_addr init over!\n");

其中,有:

/* gpio 寄存器物理基地址 */
#define GPIO_BASE_ADDR 0xFF0A0000
#define IOU_SLCR_ADDR 0xFF180000

/* gpio 寄存器所占空间大小 */
#define GPIO_BASE_SIZE 0x368
#define IOU_SLCR_SIZE 0x714

查阅硬件文档: alt text alt text 可见,GPIO的物理基地址为0x00FF0A0000。GPIO中,第一个寄存器的偏移地址为0x0000000000,最后一个为0x0000000364,由于位宽为32位,即4字节,因此GPIO的容量为0x368。
IOU的相关参数配置同理。

MIO配置为GPIO

之后将MIO配置为GPIO。

	/* MIO17 MIO23 设置成GPIO */
	iowrite32((ioread32(GPIO_PIN_17) & 0x0), GPIO_PIN_17);
	printk("gpio MIO17 init over!\n");
	iowrite32((ioread32(GPIO_PIN_23) & 0x0), GPIO_PIN_23);
	printk("gpio MIO23 init over!\n");

其中,有:

/* gpio 引脚设置GPIO功能*/
#define GPIO_PIN_17 (unsigned int *)((unsigned long)iou_slcr + 0x00000044)
#define GPIO_PIN_23 (unsigned int *)((unsigned long)iou_slcr + 0x0000005C)

我们需要配置的是MIO17和MIO23,通过查阅官方文档即可知晓偏移地址: alt text alt text

在官方文档中,对MIO17的1至7位赋值为0x0,可以将其配置为GPIO。MIO23同理。 alt text

配置MIO电流输出大小

    /* gpio 控制电流0*/
    #define IOU_SLCR_BANK0_CTRL0 (unsigned int *)((unsigned long)iou_slcr + 0x00000138)
    /* gpio 控制电流1*/
    #define IOU_SLCR_BANK0_CTRL1 (unsigned int *)((unsigned long)iou_slcr + 0x0000013c)
    /*......*/
	/* MIO17 MIO23设置输出驱动电流大小 */
	iowrite32((ioread32(IOU_SLCR_BANK0_CTRL0) | 0x3FFFFFF), IOU_SLCR_BANK0_CTRL0);
	iowrite32((ioread32(IOU_SLCR_BANK0_CTRL1) & 0x0), IOU_SLCR_BANK0_CTRL1);

前面我们选择配置的是MIO17和MIO23,均属于Bank0所负责的区域,因此需要配置Bank0。

偏移地址同样可以查阅官方文档获知。 alt text

我们需要配置bank0的电流为8mA,因此根据文档说明,bank0_ctrl需要配置为10,即bank0_ctrl0 = 1,bank0_ctrl1 = 0。根据文档对寄存器进行配置。 alt text

GPIO输入输出方向配置

	/* MIO17 MIO23 设置成输出 */
	iowrite32((ioread32(GPIO_DIRM_0) | 0x00820000), GPIO_DIRM_0);
	/*  MIO17 MIO23 使能 */
	iowrite32((ioread32(GPIO_OEN_0) | 0x00820000), GPIO_OEN_0);

我们需要调用GPIO,同时还需要设置GPIO为输出信号的状态,这样才能输出信号控制LED灯亮灭。

将0x00820000转换为2进制,第17位和第23位为1,表明MIO17 MIO23已设置成输出,MIO17 MIO23的使能信号已拉高。

控制LED灯亮灭

    /* DATA方式按灭LED0,LED1*/
	iowrite32((ioread32(GPIO_DATA_0) & 0xFF7DFFFF), GPIO_DATA_0);
	printk("GPIO_DATA_0 = 0x%x\n", *GPIO_DATA_0);

通过GPIO_DATA_0来输出bank0的相关信号。同样,0xFF7DFFFF中,第17位和第23位为0,表明此时输出信号设为0,控制灯熄灭。

程序修改为LED灯闪烁

由上述分析可知,如果调整iowrite32(write_to_GPIO_DATA_0, GPIO_DATA_0)中的write_to_GPIO_DATA_0的值,即可控制相关LED的亮灭情况。

将程序修改为周期性控制LED闪烁的功能。

添加模块,用于初始化定时器并启动,设定500ms后开始闪烁。

    timer_setup(&led_timer, led_timer_callback, 0);
    mod_timer(&led_timer, jiffies + msecs_to_jiffies(500));

同时设置闪烁功能:

// 新增:定时器变量
static struct timer_list led_timer;

// 新增:定时器回调函数,每0.5秒翻转LED状态
static void led_timer_callback(struct timer_list *t)
{
    unsigned int val;
    // 读取当前GPIO数据
    val = ioread32(GPIO_DATA_0);
    // 翻转MIO17和MIO23
    val ^= 0x00820000;
    iowrite32(val, GPIO_DATA_0);
    // 重新设置定时器,500ms后再次触发
    mod_timer(&led_timer, jiffies + msecs_to_jiffies(500));
}

之后按照前面的流程,编译获得.ko驱动程序,再在开发板上应用调用。可以看到LED闪烁。


本文章使用limfx的vscode插件快速发布