本帖最后由 LINUX课程 于 2024-9-11 10:53 编辑
软件版本:vitis2021.1(vivado2021.1) 操作系统:WIN10 64bit 硬件平台:适用XILINX Z7/ZU系列FPGA
1 概述 本课就以AXI-Lite总线实现PWM(Pulse Width Modulation,脉冲宽度调制)自定义IP作为验证AXI-Lite总线应用的方案,实现2路PWM,通过点亮LED观察效果,带领大家快速进入实战状态。 本文实验目的: - 设计一个简单的PWM发生器,并且通过AXI-LITE-SLAVE寄存器实现频率调整、占空比调整
- 通过VITIS-SDK实现对自定义IP中寄存器的读写访问
2 系统框图 3 创建IP 3.1 利用模板创建AXI-Lite IP 1:打开VIVADO软件,新建一个工程 2:单击ToolsàCreate and Package NEW IP,单击Next。 3:由于需要挂在到总线上,因此创建一个带AXI总线的用户IP,选择Create a new AXI4 peripheral,单击Next。 4:输入要创建的IP名字,此处命名为PWM_LITE_ML,选择保存路径,单击Next。 5:NameàS00_AXI; Interface Type(接口类型)àLite; Data Width(Bits)(数据位宽)à32位; Number of Registers(寄存器数量)à8 ;单击next。 设置总线形式为Lite总线,Lite总线是简化的AXI总线,消耗的资源少,当然性能比完整版的AXI总线差一点。因为频率要求不高,因此采用Lite总线就够了。设置寄存器数量为8,因为后面我们需要用到8个寄存器。 6:选择Edit IP,单击Finish完成 3.2 修改IP源码 IP创建后,需要对其进行修改,建立我们能够实际使用的IP。 1:打开PWM_LITE_ML_v1_0.v文件。 修改如下: 1、添加PWM_o端口
修改为
| 2、添加端口
修改为
|
2:打开PWM_LITE_ML_v1_0_S00_AXI.v文件。 修改如下: 1、添加PWM_o端口(注意录制的视频教程接口PWM_o名字改为了PWM)
修改为
|
2、添加PWM.v的端口 说明:slv_reg0-slv_reg7为PS部分写入PL的寄存器。通过这8个寄存器的值,我们可以控制PWM的占空比。 下面这段代码就是PS写PL部分的寄存器,一共有8个寄存器。 always @( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 ) begin slv_reg0 <= 0; slv_reg1 <= 0; slv_reg2 <= 0; slv_reg3 <= 0; slv_reg4 <= 0; slv_reg5 <= 0; slv_reg6 <= 0; slv_reg7 <= 0; end else begin if (slv_reg_wren) begin case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 3'h0: for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin // Respective byte enables are asserted as per write strobes // Slave register 0 slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end 3'h1: for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin // Respective byte enables are asserted as per write strobes // Slave register 1 slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end 3'h2: for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin // Respective byte enables are asserted as per write strobes // Slave register 2 slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end 3'h3: for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin // Respective byte enables are asserted as per write strobes // Slave register 3 slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end 3'h4: for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin // Respective byte enables are asserted as per write strobes // Slave register 4 slv_reg4[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end 3'h5: for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin // Respective byte enables are asserted as per write strobes // Slave register 5 slv_reg5[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end 3'h6: for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin // Respective byte enables are asserted as per write strobes // Slave register 6 slv_reg6[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end 3'h7: for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin // Respective byte enables are asserted as per write strobes // Slave register 7 slv_reg7[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end default : begin slv_reg0 <= slv_reg0; slv_reg1 <= slv_reg1; slv_reg2 <= slv_reg2; slv_reg3 <= slv_reg3; slv_reg4 <= slv_reg4; slv_reg5 <= slv_reg5; slv_reg6 <= slv_reg6; slv_reg7 <= slv_reg7; end endcase end end end |
3:新建一个PWM.v 文件实现PWM输出。 module PWM( input clk, input rst_n, input [31:0]fre_set, input [31:0]wav_set, output PWM_o ); reg [31:0]fre_cnt; always @(posedge clk)begin if(rst_n==1'b0)begin fre_cnt <=32'd0; end else begin if(fre_cnt<fre_set) begin fre_cnt <= fre_cnt+1'b1; end else begin fre_cnt<=32'd0; end end end assign PWM_o = (wav_set>fre_cnt); endmodule |
4:进一步修改。去掉“_v1_0”。 1、PWM_LITE_ML_v1_0_S00_AXI.v中: module PWM_LITE_ML_v1_0_S00_AXI #修改为àmodule PWM_LITE_ML_S00_AXI #
修改为
|
2、PWM_LITE_ML_v1_0.v中: module PWM_LITE_ML_v1_0 # 修改为àmodule PWM_LITE_ML #
修改后
| PWM_LITE_ML_v1_0_S00_AXI # 修改为àPWM_LITE_ML_S00_AXI #PWM_LITE_ML_v1_0_S00_AXI_inst 修改为àPWM_LITE_ML_S00_AXI_inst
修改后
| 5:修改后,保存。出现如下界面,选择Automatically pick new top module。
6:重新封装。选择ToolsCreat and Pakage New IP,单击Next。 7:选择Package your current project,单击Next。 8:选择Overwrite。 9:选择Package IPRewiew and Package Re-Package IP。完成创建自定义IP。 4 硬件电路分析 这里PWM输出的IO引出到LED上,调整PWM的占空比可以实现LED的亮度调节。 5 搭建SOC系统工程 详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“01Vitis Soc开发入门”这篇文章。 5.1 SOC系统工程 1:中断设置 本实验可以不设置中断 2:设置GP Master接口 3:设置复位输出 4:设置PL时钟 5:添加自定义IP 设置自定义IP路径,并且添加IP 5.2 设置AXI外设地址分配 只要添加的AXI总线外设都要正确分配地址,这一步不能遗漏 5.3 编译并导出平台文件 以下步骤简写,有不清楚的看Linux基础篇第四章。 1:打开soc_prj内工程。 2:生成Bit文件。 3:导出到硬件: FileàExport HardwareàInclude bitstream 4:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。 5:打开vitis,并添加设备树模板,不清楚请参考Linux基础篇第四章。 6:创建工程文件,选择device tree创建,创建完成后编译工程。 7:获得设备树以及启动文件,打开虚拟机将文件拷贝到开发包的指定位置。 5.4 设备树修改及驱动编译
1:设备树修改 本章vitis会生成pl端的设备树,需要手动添加。另外soc_dts内有写好的设备树,新手请不要自行修改,直接使用教程提供的设备树。设备树的修改如下: 这次直接使用vitis生成的设备树来写驱动,所以不对生成的设备树进行修改。 2:编译系统 拷贝上一步写好的设备树到指定路径,拷贝其他文件后,编译uboot,编译kernel,制作镜像并烧录系统。具体步骤参考Linux基础篇第四章。 3:编译驱动 将对应的demo拷贝至/home/uisrc下,同时确保/home/uisrc下有软件开发包uisrc-lab-xlnx。 进入demo的路径,使用make编译驱动。 其中.ko结尾的文件为我们编译出来的驱动。 4:拷贝程序 将对应的文件拷贝至sd卡上: 6 程序分析 这个驱动需要根据设备树来编写驱动,所以采用了一个比较熟悉的开发模式,即基于设备树的平台驱动开发。 - #include <linux/ide.h>
- #include <linux/module.h>
- #include <linux/of_platform.h>
- #include <linux/gpio.h>
- #include <linux/delay.h>
- #define ZYNQMP_GPIO_NR_GPIOS 118
- #define MIO_PIN_51 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 51)
- // #define MIO_PIN_42 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 42)
- struct AxiPWM
- {
- int addr_width;
- int data_width;
- struct device_node *dev_node;
- int reg_num;
- unsigned int *reg_value;
- };
- struct AxiPWM *AxiPWM_data;
- static unsigned int *pwm0_fre = NULL;
- static unsigned int *pwm0_wav = NULL;
- static unsigned int *pwm1_fre = NULL;
- static unsigned int *pwm1_wav = NULL;
- static unsigned int *pwm2_fre = NULL;
- static unsigned int *pwm2_wav = NULL;
- static unsigned int *pwm3_fre = NULL;
- static unsigned int *pwm3_wav = NULL;
- static struct of_device_id AxiPWM_of_match[] = {
- {.compatible = "xlnx,PWM-LITE-ML-1.0"},
- {},
- };
- int of_AxiPWM_data(struct AxiPWM *pdata, struct platform_device *pdev)
- {
- struct device_node *np = pdev->dev.of_node;
- int ret = 0;
- pdata->reg_num = of_property_count_elems_of_size(np, "reg", sizeof(int));
- if (pdata->reg_num < 0)
- {
- dev_err(&pdev->dev, "get reg_num failed\n");
- return ret;
- }
- pdata->reg_value = (unsigned int *)kmalloc(sizeof(unsigned int) * pdata->reg_num, GFP_KERNEL);
- if (!pdata->reg_value)
- {
- kfree(pdata->reg_value);
- dev_err(&pdev->dev, "kmalloc failed\n");
- return -1;
- }
- ret = of_property_read_u32_array(np, "reg", pdata->reg_value, pdata->reg_num);
- if (ret != 0)
- {
- kfree(pdata->reg_value);
- dev_err(&pdev->dev, "get reg failed\n");
- return ret;
- }
- ret = of_property_read_u32(np, "xlnx,s00-axi-addr-width", &pdata->addr_width);
- if (ret < 0)
- {
- dev_err(&pdev->dev, "get addr_width failed\n");
- return ret;
- }
- ret = of_property_read_u32(np, "xlnx,s00-axi-data-width", &pdata->data_width);
- if (ret < 0)
- {
- dev_err(&pdev->dev, "get data_width failed\n");
- return ret;
- }
- return 0;
- }
- static int AxiPWM_probe(struct platform_device *pdev)
- {
- int ret = 0;
- int i = 0;
- struct device *dev = &pdev->dev;
- struct AxiPWM *pdata = dev_get_platdata(dev);
- if (!pdata)
- {
- pdata = devm_kzalloc(dev, sizeof(struct AxiPWM), GFP_KERNEL);
- if (!pdata)
- return -ENOMEM;
- platform_set_drvdata(pdev, pdata);
- }
- ret = of_AxiPWM_data(pdata, pdev);
- AxiPWM_data = pdata;
- // 打印属性值
- printk("addr_width = %d\r\n", AxiPWM_data->addr_width);
- printk("data_width = %d\r\n", AxiPWM_data->data_width);
- printk("reg_num = %d\r\n", AxiPWM_data->reg_num);
- for (i = 0; i < AxiPWM_data->reg_num; i += 2)
- {
- printk("reg = %#X %#X \r\n", AxiPWM_data->reg_value[i], AxiPWM_data->reg_value[i + 1]);
- }
- pwm0_fre = ioremap(AxiPWM_data->reg_value[0] + 4 * 0, 4);
- pwm0_wav = ioremap(AxiPWM_data->reg_value[0] + 4 * 1, 4);
- pwm1_fre = ioremap(AxiPWM_data->reg_value[0] + 4 * 2, 4);
- pwm1_wav = ioremap(AxiPWM_data->reg_value[0] + 4 * 3, 4);
- pwm2_fre = ioremap(AxiPWM_data->reg_value[0] + 4 * 4, 4);
- pwm2_wav = ioremap(AxiPWM_data->reg_value[0] + 4 * 5, 4);
- pwm3_fre = ioremap(AxiPWM_data->reg_value[0] + 4 * 6, 4);
- pwm3_wav = ioremap(AxiPWM_data->reg_value[0] + 4 * 7, 4);
- printk("ioremap is done!\r\n");
- writel(0x63, pwm0_fre);
- writel(0x63, pwm1_fre);
- // writel(0x63, pwm2_fre);
- // writel(0x63, pwm3_fre);
- printk("writel is done!\r\n");
- ret = gpio_request(MIO_PIN_51, "led0");
- if (ret < 0)
- {
- printk("gpio request led0 error!\n");
- return ret;
- }
- // ret = gpio_request(MIO_PIN_42, "led1");
- // if (ret < 0)
- // {
- // printk("gpio request led1 error!\n");
- // return ret;
- // }
- ret = gpio_direction_output(MIO_PIN_51, 0);
- if (ret != 0)
- {
- printk("gpio direction input MIO_PIN_51 fail!\n");
- }
- // ret = gpio_direction_output(MIO_PIN_42, 0);
- // if (ret != 0)
- // {
- // printk("gpio direction input MIO_PIN_42 fail!\n");
- // }
- gpio_set_value(MIO_PIN_51, 0);
- // gpio_set_value(MIO_PIN_42, 0);
- for (i = 0; i <= 600; i++)
- {
- writel(i % 200 < 100 ? i % 200 : 200 - i % 200, pwm0_wav);
- writel(i % 200 < 100 ? i % 200 : 200 - i % 200, pwm1_wav);
- // printk("%d\n",readl(pwm0_wav));
- msleep(20);
- }
- return ret;
- }
- static int AxiPWM_remove(struct platform_device *pdev)
- {
- gpio_free(MIO_PIN_51);
- // gpio_free(MIO_PIN_42);
- kfree(AxiPWM_data->reg_value);
- iounmap(pwm0_fre);
- iounmap(pwm0_wav);
- iounmap(pwm1_fre);
- iounmap(pwm1_wav);
- iounmap(pwm2_fre);
- iounmap(pwm2_wav);
- iounmap(pwm3_fre);
- iounmap(pwm3_wav);
- return 0;
- }
- static struct platform_driver AxiPWM_device_driver = {
- .driver = {
- .name = "AxiPWM",
- .owner = THIS_MODULE,
- .of_match_table = of_match_ptr(AxiPWM_of_match),
- },
- .probe = AxiPWM_probe,
- .remove = AxiPWM_remove,
- };
- static int __init AxiPWM_init(void)
- {
- return platform_driver_register(&AxiPWM_device_driver);
- }
- static void __exit AxiPWM_exit(void)
- {
- platform_driver_unregister(&AxiPWM_device_driver);
- }
- late_initcall(AxiPWM_init);
- module_exit(AxiPWM_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("uisrc");
复制代码这里依旧使用的基于设备树的平台驱动进行开发,改动主要在probe函数部分。 行7~9,定义了ps端两个led灯的gpio标号,目的是为了后面关闭ps灯让pl的灯看起来更清楚。 行21~28,定义了四组pwm寄存器,fre为pwm的频率,wav为pwm的占空比。 行106~113,映射4组共8个寄存器。 行115~118,设置4个pwm寄存器的频率为99。 行120~143,使用GPIO子系统关闭ps端的灯,方便观察pl的led灯。 行145~150,设置了三组呼吸灯,单次2s,共计6s。 7 演示结果 SD2.0 启动 01 而模式开关为 ON OFF(7100 需要先将系统烧录进qspi,然后才能从qspi启动sd卡,参考Linux基础篇第四章) 将 PS 端串口线连接电脑,如果要使用 ssh 登录,将网口线同样连接至电脑,最后给开发板通电。每次重新上电,需要重新插拔 PS 串口,否则会登录失败。 接入12V直流电源开机。 找到刚才放驱动的目录: 使用sudo insmod axipwm.ko命令装载驱动,密码为root: 可以看到板子上的pl区域灯变成了呼吸灯。 |