摘要:
本文主要讲如何自定义函数读写外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
#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=0x%08x\n",(data_addr),sw_status);
printf("addres=0x%x,sw_status=0x%08x\n",(con_addr),sw_con);
}
可以看到内存数据和串口输出的数据一样。
本文通过直接使用物理地址操作GPIO,摆脱了对SDK函数的依赖。而且可以发现,PL端的任何IP都可以看作是PS的外设,由PS分配地址,统一管理。需要注意的就是,不同的IP,操作的寄存器不同,这里需要自己去查手册。