[X]关闭

[米联客-XILINX-H3_CZ08_7100] LINUX驱动篇连载-19 PL 自定义AXI-Lite-PWM

文档创建者:LINUX课程
浏览次数:362
最后更新:2024-09-10
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 2_LINUX应用开发
本帖最后由 LINUX课程 于 2024-9-11 10:53 编辑

软件版本:vitis2021.1(vivado2021.1)
操作系统:WIN10 64bit
硬件平台:适用XILINX Z7/ZU系列FPGA
登录“米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!

1 概述
本课就以AXI-Lite总线实现PWM(Pulse Width Modulation,脉冲宽度调制)自定义IP作为验证AXI-Lite总线应用的方案,实现2路PWM,通过点亮LED观察效果,带领大家快速进入实战状态。
本文实验目的:
  • 设计一个简单的PWM发生器,并且通过AXI-LITE-SLAVE寄存器实现频率调整、占空比调整
  • 通过VITIS-SDK实现对自定义IP中寄存器的读写访问
2 系统框图
image.jpg
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。
image.jpg
4:输入要创建的IP名字,此处命名为PWM_LITE_ML,选择保存路径,单击Next。
image.jpg
5:NameàS00_AXI
Interface Type(接口类型)àLite
Data Width(Bits)(数据位宽)à32位;
Number of  Registers(寄存器数量)à8 ;单击next
image.jpg
设置总线形式为Lite总线,Lite总线是简化的AXI总线,消耗的资源少,当然性能比完整版的AXI总线差一点。因为频率要求不高,因此采用Lite总线就够了。设置寄存器数量为8,因为后面我们需要用到8个寄存器。
6:选择Edit IP,单击Finish完成
image.jpg
3.2 修改IP源码
IP创建后,需要对其进行修改,建立我们能够实际使用的IP。
1:打开PWM_LITE_ML_v1_0.v文件。
image.jpg
修改如下:
1、添加PWM_o端口
image.jpg           修改为                image.jpg
2、添加端口
   image.jpg                       修改为                image.jpg
2:打开PWM_LITE_ML_v1_0_S00_AXI.v文件。
image.jpg
修改如下:
1、添加PWM_o端口(注意录制的视频教程接口PWM_o名字改为了PWM)
   image.jpg 修改为       image.jpg
2、添加PWM.v的端口
image.jpg
修改为
image.jpg
说明: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 #
image.jpg     修改为            image.jpg
2、PWM_LITE_ML_v1_0.v中:
module PWM_LITE_ML_v1_0 #     修改为àmodule PWM_LITE_ML #
image.jpg                             修改后    image.jpg
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
image.jpg        修改后   image.jpg
5:修改后,保存。出现如下界面,选择Automatically pick new top module
image.jpg
6:重新封装。选择ToolsCreat and Pakage New IP,单击Next。
7:选择Package your current project,单击Next。
image.jpg
image.jpg
8:选择Overwrite。
9:选择Package IPRewiew and Package Re-Package IP。完成创建自定义IP。
4 硬件电路分析
这里PWM输出的IO引出到LED上,调整PWM的占空比可以实现LED的亮度调节。
image.jpg
5 搭建SOC系统工程
详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“01Vitis Soc开发入门”这篇文章。
5.1 SOC系统工程
image.jpg
1:中断设置
本实验可以不设置中断
image.jpg
2:设置GP Master接口
image.jpg
3:设置复位输出
image.jpg
4:设置PL时钟
image.jpg
5:添加自定义IP
设置自定义IP路径,并且添加IP
image.jpg
5.2 设置AXI外设地址分配
只要添加的AXI总线外设都要正确分配地址,这一步不能遗漏
image.jpg
5.3 编译并导出平台文件
以下步骤简写,有不清楚的看Linux基础篇第四章。
1:打开soc_prj内工程。
2:生成Bit文件。
3:导出到硬件: FileàExport HardwareàInclude bitstream
4:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。
image.jpg
5:打开vitis,并添加设备树模板,不清楚请参考Linux基础篇第四章。
6:创建工程文件,选择device tree创建,创建完成后编译工程。
7:获得设备树以及启动文件,打开虚拟机将文件拷贝到开发包的指定位置。
5.4 设备树修改及驱动编译
1:设备树修改
本章vitis会生成pl端的设备树,需要手动添加。另外soc_dts内有写好的设备树,新手请不要自行修改,直接使用教程提供的设备树。设备树的修改如下:
image.jpg
这次直接使用vitis生成的设备树来写驱动,所以不对生成的设备树进行修改。
2:编译系统
拷贝上一步写好的设备树到指定路径,拷贝其他文件后,编译uboot,编译kernel,制作镜像并烧录系统。具体步骤参考Linux基础篇第四章。
3:编译驱动
将对应的demo拷贝至/home/uisrc下,同时确保/home/uisrc下有软件开发包uisrc-lab-xlnx。
1725955370490.jpg
进入demo的路径,使用make编译驱动。
image.jpg
其中.ko结尾的文件为我们编译出来的驱动。
4:拷贝程序
将对应的文件拷贝至sd卡上:
image.jpg
6 程序分析
这个驱动需要根据设备树来编写驱动,所以采用了一个比较熟悉的开发模式,即基于设备树的平台驱动开发。
  1. #include <linux/ide.h>
  2. #include <linux/module.h>
  3. #include <linux/of_platform.h>
  4. #include <linux/gpio.h>
  5. #include <linux/delay.h>

  6. #define ZYNQMP_GPIO_NR_GPIOS 118
  7. #define MIO_PIN_51 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 51)
  8. // #define MIO_PIN_42 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 42)

  9. struct AxiPWM
  10. {
  11. int addr_width;
  12. int data_width;
  13. struct device_node *dev_node;
  14. int reg_num;
  15. unsigned int *reg_value;
  16. };
  17. struct AxiPWM *AxiPWM_data;

  18. static unsigned int *pwm0_fre = NULL;
  19. static unsigned int *pwm0_wav = NULL;
  20. static unsigned int *pwm1_fre = NULL;
  21. static unsigned int *pwm1_wav = NULL;
  22. static unsigned int *pwm2_fre = NULL;
  23. static unsigned int *pwm2_wav = NULL;
  24. static unsigned int *pwm3_fre = NULL;
  25. static unsigned int *pwm3_wav = NULL;

  26. static struct of_device_id AxiPWM_of_match[] = {
  27. {.compatible = "xlnx,PWM-LITE-ML-1.0"},
  28. {},
  29. };

  30. int of_AxiPWM_data(struct AxiPWM *pdata, struct platform_device *pdev)
  31. {
  32. struct device_node *np = pdev->dev.of_node;
  33. int ret = 0;

  34. pdata->reg_num = of_property_count_elems_of_size(np, "reg", sizeof(int));
  35. if (pdata->reg_num < 0)
  36. {
  37.   dev_err(&pdev->dev, "get reg_num failed\n");
  38.   return ret;
  39. }

  40. pdata->reg_value = (unsigned int *)kmalloc(sizeof(unsigned int) * pdata->reg_num, GFP_KERNEL);
  41. if (!pdata->reg_value)
  42. {
  43.   kfree(pdata->reg_value);
  44.   dev_err(&pdev->dev, "kmalloc failed\n");
  45.   return -1;
  46. }
  47. ret = of_property_read_u32_array(np, "reg", pdata->reg_value, pdata->reg_num);
  48. if (ret != 0)
  49. {
  50.   kfree(pdata->reg_value);
  51.   dev_err(&pdev->dev, "get reg failed\n");
  52.   return ret;
  53. }

  54. ret = of_property_read_u32(np, "xlnx,s00-axi-addr-width", &pdata->addr_width);
  55. if (ret < 0)
  56. {
  57.   dev_err(&pdev->dev, "get addr_width failed\n");
  58.   return ret;
  59. }

  60. ret = of_property_read_u32(np, "xlnx,s00-axi-data-width", &pdata->data_width);
  61. if (ret < 0)
  62. {
  63.   dev_err(&pdev->dev, "get data_width failed\n");
  64.   return ret;
  65. }

  66. return 0;
  67. }

  68. static int AxiPWM_probe(struct platform_device *pdev)
  69. {
  70. int ret = 0;
  71. int i = 0;

  72. struct device *dev = &pdev->dev;
  73. struct AxiPWM *pdata = dev_get_platdata(dev);
  74. if (!pdata)
  75. {
  76.   pdata = devm_kzalloc(dev, sizeof(struct AxiPWM), GFP_KERNEL);
  77.   if (!pdata)
  78.    return -ENOMEM;

  79.   platform_set_drvdata(pdev, pdata);
  80. }
  81. ret = of_AxiPWM_data(pdata, pdev);
  82. AxiPWM_data = pdata;

  83. // 打印属性值
  84. printk("addr_width = %d\r\n", AxiPWM_data->addr_width);
  85. printk("data_width = %d\r\n", AxiPWM_data->data_width);
  86. printk("reg_num = %d\r\n", AxiPWM_data->reg_num);
  87. for (i = 0; i < AxiPWM_data->reg_num; i += 2)
  88. {
  89.   printk("reg = %#X  %#X \r\n", AxiPWM_data->reg_value[i], AxiPWM_data->reg_value[i + 1]);
  90. }

  91. pwm0_fre = ioremap(AxiPWM_data->reg_value[0] + 4 * 0, 4);
  92. pwm0_wav = ioremap(AxiPWM_data->reg_value[0] + 4 * 1, 4);
  93. pwm1_fre = ioremap(AxiPWM_data->reg_value[0] + 4 * 2, 4);
  94. pwm1_wav = ioremap(AxiPWM_data->reg_value[0] + 4 * 3, 4);
  95. pwm2_fre = ioremap(AxiPWM_data->reg_value[0] + 4 * 4, 4);
  96. pwm2_wav = ioremap(AxiPWM_data->reg_value[0] + 4 * 5, 4);
  97. pwm3_fre = ioremap(AxiPWM_data->reg_value[0] + 4 * 6, 4);
  98. pwm3_wav = ioremap(AxiPWM_data->reg_value[0] + 4 * 7, 4);
  99. printk("ioremap is done!\r\n");

  100. writel(0x63, pwm0_fre);
  101. writel(0x63, pwm1_fre);
  102. // writel(0x63, pwm2_fre);
  103. // writel(0x63, pwm3_fre);
  104. printk("writel is done!\r\n");

  105. ret = gpio_request(MIO_PIN_51, "led0");
  106. if (ret < 0)
  107. {
  108.   printk("gpio request led0 error!\n");
  109.   return ret;
  110. }
  111. // ret = gpio_request(MIO_PIN_42, "led1");
  112. // if (ret < 0)
  113. // {
  114. //  printk("gpio request led1 error!\n");
  115. //  return ret;
  116. // }
  117. ret = gpio_direction_output(MIO_PIN_51, 0);
  118. if (ret != 0)
  119. {
  120.   printk("gpio direction input MIO_PIN_51 fail!\n");
  121. }
  122. // ret = gpio_direction_output(MIO_PIN_42, 0);
  123. // if (ret != 0)
  124. // {
  125. //  printk("gpio direction input MIO_PIN_42 fail!\n");
  126. // }
  127. gpio_set_value(MIO_PIN_51, 0);
  128. // gpio_set_value(MIO_PIN_42, 0);

  129. for (i = 0; i <= 600; i++)
  130. {
  131.   writel(i % 200 < 100 ? i % 200 : 200 - i % 200, pwm0_wav);
  132.   writel(i % 200 < 100 ? i % 200 : 200 - i % 200, pwm1_wav);
  133.   // printk("%d\n",readl(pwm0_wav));
  134.   msleep(20);
  135. }

  136. return ret;
  137. }

  138. static int AxiPWM_remove(struct platform_device *pdev)
  139. {
  140. gpio_free(MIO_PIN_51);
  141. // gpio_free(MIO_PIN_42);
  142. kfree(AxiPWM_data->reg_value);
  143. iounmap(pwm0_fre);
  144. iounmap(pwm0_wav);
  145. iounmap(pwm1_fre);
  146. iounmap(pwm1_wav);
  147. iounmap(pwm2_fre);
  148. iounmap(pwm2_wav);
  149. iounmap(pwm3_fre);
  150. iounmap(pwm3_wav);
  151. return 0;
  152. }

  153. static struct platform_driver AxiPWM_device_driver = {
  154. .driver = {
  155.   .name = "AxiPWM",
  156.   .owner = THIS_MODULE,
  157.   .of_match_table = of_match_ptr(AxiPWM_of_match),
  158. },
  159. .probe = AxiPWM_probe,
  160. .remove = AxiPWM_remove,
  161. };

  162. static int __init AxiPWM_init(void)
  163. {
  164. return platform_driver_register(&AxiPWM_device_driver);
  165. }

  166. static void __exit AxiPWM_exit(void)
  167. {
  168. platform_driver_unregister(&AxiPWM_device_driver);
  169. }

  170. late_initcall(AxiPWM_init);
  171. module_exit(AxiPWM_exit);

  172. MODULE_LICENSE("GPL");
  173. 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基础篇第四章)
2f5038eb9880afd532753935815b079.jpg
将 PS 端串口线连接电脑,如果要使用 ssh 登录,将网口线同样连接至电脑,最后给开发板通电。每次重新上电,需要重新插拔 PS 串口,否则会登录失败。
image.jpg
接入12V直流电源开机。
找到刚才放驱动的目录:
image.jpg
使用sudo insmod axipwm.ko命令装载驱动,密码为root:
image.jpg
可以看到板子上的pl区域灯变成了呼吸灯。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则