摘要: 本文主要讲如何自定义函数读写外AXI外设,摆脱对SDK库函数的依赖。 本文举AXI_GPIO这个IP来讲解如自定义函数实现对AXI_GPIO的控制。编写过STM32程序的人应该都知道控制一个GPIO端口需要控制连个寄存器。一个是输入输出方向控制寄存器,另一个是数据寄存器。通过方向控制寄存器设置GPIO的输入输出方向,然后通过读写数据寄存器可以实现对GPIO的读写。
一、在Vivado中定制硬件 完整的硬件结构如下: 首先添加一个处理器IP:zynq 然后添加一个AXI_GPIO,设置为8位输入。 再然后自动连接即可。 接下来生成顶层文件,将顶层文件修改如下,主要是将8位输入设置为8’b10001000。
//Copyright 1986-2017 Xilinx, Inc. All Rights Reserved. //-------------------------------------------------------------------------------- //Tool Version: Vivado v.2017.2 (win64) Build 1909853 Thu Jun 15 18:39:09 MDT 2017 //Date : Sat May 25 07:53:09 2019 //Host : DESKTOP-4K25U7B running 64-bit major release (build 9200) //Command : generate_target design_1_wrapper.bd //Design : design_1_wrapper //Purpose : IP block netlist //-------------------------------------------------------------------------------- `timescale 1 ps / 1 ps module design_1_wrapper (DDR_addr, DDR_ba, DDR_cas_n, DDR_ck_n, DDR_ck_p, DDR_cke, DDR_cs_n, DDR_dm, DDR_dq, DDR_dqs_n, DDR_dqs_p, DDR_odt, DDR_ras_n, DDR_reset_n, DDR_we_n, FIXED_IO_ddr_vrn, FIXED_IO_ddr_vrp, FIXED_IO_mio, FIXED_IO_ps_clk, FIXED_IO_ps_porb, FIXED_IO_ps_srstb // sws_8bits_tri_i ); inout [14:0]DDR_addr; inout [2:0]DDR_ba; inout DDR_cas_n; inout DDR_ck_n; inout DDR_ck_p; inout DDR_cke; inout DDR_cs_n; inout [3:0]DDR_dm; inout [31:0]DDR_dq; inout [3:0]DDR_dqs_n; inout [3:0]DDR_dqs_p; inout DDR_odt; inout DDR_ras_n; inout DDR_reset_n; inout DDR_we_n; inout FIXED_IO_ddr_vrn; inout FIXED_IO_ddr_vrp; inout [53:0]FIXED_IO_mio; inout FIXED_IO_ps_clk; inout FIXED_IO_ps_porb; inout FIXED_IO_ps_srstb; // input [7:0]sws_8bits_tri_i; wire [14:0]DDR_addr; wire [2:0]DDR_ba; wire DDR_cas_n; wire DDR_ck_n; wire DDR_ck_p; wire DDR_cke; wire DDR_cs_n; wire [3:0]DDR_dm; wire [31:0]DDR_dq; wire [3:0]DDR_dqs_n; wire [3:0]DDR_dqs_p; wire DDR_odt; wire DDR_ras_n; wire DDR_reset_n; wire DDR_we_n; wire FIXED_IO_ddr_vrn; wire FIXED_IO_ddr_vrp; wire [53:0]FIXED_IO_mio; wire FIXED_IO_ps_clk; wire FIXED_IO_ps_porb; wire FIXED_IO_ps_srstb; wire [7:0]sws_8bits_tri_i; //输入值为0x88 assign sws_8bits_tri_i = 8'b10001000; design_1 design_1_i (.DDR_addr(DDR_addr), .DDR_ba(DDR_ba), .DDR_cas_n(DDR_cas_n), .DDR_ck_n(DDR_ck_n), .DDR_ck_p(DDR_ck_p), .DDR_cke(DDR_cke), .DDR_cs_n(DDR_cs_n), .DDR_dm(DDR_dm), .DDR_dq(DDR_dq), .DDR_dqs_n(DDR_dqs_n), .DDR_dqs_p(DDR_dqs_p), .DDR_odt(DDR_odt), .DDR_ras_n(DDR_ras_n), .DDR_reset_n(DDR_reset_n), .DDR_we_n(DDR_we_n), .FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn), .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp), .FIXED_IO_mio(FIXED_IO_mio), .FIXED_IO_ps_clk(FIXED_IO_ps_clk), .FIXED_IO_ps_porb(FIXED_IO_ps_porb), .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb), .sws_8bits_tri_i(sws_8bits_tri_i)); endmodule再接着生成比特文件,导入到SDK。
二、在SDK中自定义函数实现对GPIO的读取,并打印输出
根据STM32的编程经验,我们首先找到GPIO的数据寄存器和控制寄存器的地址。一般这两个地址都是等于一个基地址+对应的偏移地址。在Block Design中查看GPIO的基地址。
基地址为:0x41200000 查看GPIO的手册,查找对应的偏移地址。
AXI_GPIO有两个通道,这里我们只用了其中一个通道。查表得到数据和控制寄存器的偏移地址是: 0x0000,0x0004
根据手册中说的,当控制寄存器设置为1时当作输入,设置为0时当作输出。 1:输入 0:输出
创建一个HelloWorld工程。 修改helloworld.c如下:
#include <stdio.h> #include "platform.h" #include "xil_printf.h" //基地址 #define gpio_base_addr 0x41200000 //数据偏移地址 #define data_offset 0x00000000 //控制寄存器偏移地址 #define con_offset 0x00000004 int main() { //数据地址指针 u32 *data_addr=NULL; //控制地址指针 u32 *con_addr=NULL; //设置GPIO为输入,PS:其实在定制硬件的时候就已经设置为输入了,这里不用设置为输入也可以。而且这里设置为输出也不会影响硬件定制的输入,但是控制寄存器里的值确实已经变了。这是我已经实验过的。 con_addr=gpio_base_addr+con_offset; *(con_addr) = 0xFF; //读取控制寄存器的值 u8 sw_con; sw_con = *(con_addr); //读取数据寄存器的值 u8 sw_status; data_addr=gpio_base_addr+data_offset; sw_status = *(data_addr); //分别打印输出数据寄存器和控制寄存器的值 printf("addres=0x%x,sw_status=0xx\n",(data_addr),sw_status); printf("addres=0x%x,sw_status=0xx\n",(con_addr),sw_con); }运行程序: 串口输出:
查看内存:
可以看到内存数据和串口输出的数据一样。
本文通过直接使用物理地址操作GPIO,摆脱了对SDK函数的依赖。而且可以发现,PL端的任何IP都可以看作是PS的外设,由PS分配地址,统一管理。需要注意的就是,不同的IP,操作的寄存器不同,这里需要自己去查手册。