使用一个外设之前,你要弄明白这个外设是干什么的,它是怎样工作的,它的输入输出接口都是啥,你还要知道怎样给外设分配地址。
GPIO的具体硬件结构在这里不多说了。(如果你要自己写代码,那硬件结构必须弄得明明白白。)我们用到的GPIO模块是ARM公司提供的,拿来用就行,GPIO的硬件结构懂个大概就行。
写外设的时候,往往要和寄存器打交道,寄存器分为片内寄存器和外部接口寄存器,内部寄存器就是R0-R15,R0-R15也叫通用寄存器,CM3内部还有有特殊功能寄存器,这个大家应该都清楚。下面括号内容可以不看。
(寄存器就是存储单元,给有特定功能的内存单元取一个别名,这个别名就是我们经常说的寄存器,比如PC指针寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。访问寄存器要使用结构体指针。
那GPIO有哪些寄存器呢,这些寄存器是干什么的??
寄存器用于 GPIO 的控制,使用 4 个寄存器即可操作一个 GPIO,可以很方便的实现 GPIO 的各种应用。这4个寄存器分别是
1.控制方向的寄存器
2.控制输出电平状态的2个寄存器
3.反映引脚电平状态的寄存器
通过地址操控这些寄存器,就可以使用GPIO。硬件代码里要给GPIO这个从设备分配地址,KEIL软件代码里要写好结构体,通过结构体控制GPIO。打个比方,硬件代码就好比水箱。写软件代码好比往水箱里灌水。
例程以2个16位GPIO为例,需要几个端口就定义几个。
在顶层文件中定义GPIO地址(定义地址的目的是让CPU访问它,后面还要对地址解码,解码时把定义好的地址传送到decode模块中,你也可以仅在decode中定义地址,但推荐在顶层定义,以参数化的形式传递到decode模块),定义端口,定义gpio的中断。顶层是和总体架构文件,有大量的例化。注意,系统文件我们要自己修改。
在顶层定义gpio的地址
parameter BASEADDR_GPIO0 = 32'h4001_0000, // GPIO0 peripheral base address
parameter BASEADDR_GPIO1 = 32'h4001_1000, // GPIO1 peripheral base address
在顶层例化gpio_0
cmsdk_ahb_gpio #(
.ALTERNATE_FUNC_MASK(16'hFFFF),
.ALTERNATE_FUNC_DEFAULT(16'h0000),
.BE(0)
)
u_ahb_gpio_0 (
// AHB Inputs
.HCLK(clk),
.HRESETn(cpuresetn),
.FCLK(clk),
.HSEL(gpio0_hsel),
.HREADY(1'b1),
.HTRANS(HTRANSS),
.HSIZE(HSIZES),
.HWRITE(HWRITES),
.HADDR(HADDRS[11:0]),
.HWDATA(HWDATAS),
.ECOREVNUM(4'b0),
.PORTIN(p0_in),
.HREADYOUT (gpio0_hreadyout),
.HRESP (gpio0_hresp),
.HRDATA (gpio0_hrdata),
.PORTOUT(p0_out),
.PORTEN(p0_outen),
.PORTFUNC(p0_altfunc),
.GPIOINT(gpio0_intr[15:0]),
.COMBINT(gpio0_combintr));
还要把以上定义的系统参数传递到地址解码文件(decode.v)中,地址解码就相当于多路选择器,根据地址多选1,选中你需要的外设模块进行访问。在系统文件最重要的就是把GPIO挂到系统总线上,解码和挂总线如下,其中挂总线部分省略了大量无关代码:
decode被例化到顶层的样子,它的作用是输出一堆外设的选择信号
// AHB address decode
cmsdk_mcu_addr_decode #(
.BASEADDR_GPIO0 (BASEADDR_GPIO0),
.BASEADDR_GPIO1 (BASEADDR_GPIO1),
.BOOT_LOADER_PRESENT (BOOT_LOADER_PRESENT)
)
u_addr_decode (
// System Address
.code_haddr (code_haddr),
.code_hsel (code_hsel),
.sys_haddr (sys_haddr),
.sys_hsel (sys_hsel),
.remap_ctrl (remap_ctrl),
//把选择信号输出
.apbsys_hsel (apbsys_hsel),
.gpio0_hsel (gpio0_hsel),
.gpio1_hsel (gpio1_hsel),
);
decode文件中关于gpio的译码逻辑。decode文件生成选择信号,输出给系统顶层。
assign gpio0_hsel = (sys_haddr[31:12]==
BASEADDR_GPIO0[31:12]) & sys_hsel; // 0x40010000
assign gpio1_hsel = (sys_haddr[31:12]==
BASEADDR_GPIO1[31:12]) & sys_hsel; // 0x40011000
//把GPIO挂到总线上
.HSEL5 (gpio0_hsel), // Input Port 5
.HREADYOUT5 (gpio0_hreadyout),
.HRESP5 (gpio0_hresp),
.HRDATA5 (gpio0_hrdata),
.HSEL6 (gpio1_hsel), // Input Port 6
.HREADYOUT6 (gpio1_hreadyout),
.HRESP6 (gpio1_hresp),
.HRDATA6 (gpio1_hrdata),
cmsdk_ahb_gpio.v这个文件是不需要你写的,添加进来即可,这个文件里还包括2个和gpio相关的文件,这2个文件是用来实现gpio底层逻辑的,都是ARM提供,添加进来即可。
再来讲一下decode.v文件,这个也是需要我们自己修改的。每添加一个外设,就要相应修改decode文件中的代码,下面是对外设解码的相关逻辑。
// ----------------------------------------------------------
// Peripheral Selection decode logic
// ----------------------------------------------------------
assign apbsys_hsel = (sys_haddr[31:16]==16'h4000) &
sys_hsel; // 0x40000000
assign gpio0_hsel = (sys_haddr[31:12]==
BASEADDR_GPIO0[31:12]) & sys_hsel; // 0x40010000
assign gpio1_hsel = (sys_haddr[31:12]==
BASEADDR_GPIO1[31:12]) & sys_hsel; // 0x40011000
assign sysctrl_hsel = (sys_haddr[31:12]==20'h4001F) &
sys_hsel;
在顶层module中,把IO设置为inout数据类型,在顶层模块定义一个参数parameter,用来设置GPIO模块的基地址。把参数都例化到system.v这个文件
inout wire [15:0] P0,
inout wire [15:0] P1,
localparam BASEADDR_GPIO0 = 32'h4001_0000; //外设基地址
localparam BASEADDR_GPIO1 = 32'h4001_1000;
顶层定义几个连线变量,用于例化到system.v中
wire [15:0] p1_in; // I/O port #1 input
wire [15:0] p1_out; // I/O port #1 output
wire [15:0] p1_outen; // I/O port #1 output enable (tristate buffer control)
wire [15:0] p1_altfunc; // I/O port #1 alternate function
例化到system.v
// IO Ports
.p0_in (p0_in),
.p0_out (p0_out),
.p0_outen (p0_outen),
.p0_altfunc (p0_altfunc),
.p1_in (p1_in),
.p1_out (p1_out),
.p1_outen (p1_outen),
.p1_altfunc (p1_altfunc),
除了端口要例化,GPIO外设基地址也要例化。
.BASEADDR_GPIO0 (BASEADDR_GPIO0), // GPIO0 Base Address
.BASEADDR_GPIO1 (BASEADDR_GPIO1), // GPIO1 Base Address
外设基地址既不是输入变量,也不是输出变量,例化时要把它写道"#()"的括号里,学过verilog的人应该都知道。
至此,gpio已经刮到了总线上。接下来就是写用keil软件的过程,我们需要在启动文件添加一些中断,还要写一些结构体,用于设置GPIO的各种参数,给GPIO结构体中变量赋值的过程就是写寄存器的过程,我们透过结构体操控了寄存器,进而控制了GPIO。
扩展外设步骤:
- 扩展Decoder
- 在Interconnect中增加Slave Port
- 在Interconnect中将新增的Slave Port与扩展的Decoder HSEL信号以及Slave MUX连接
- 顶层连接外设、外设接口、总线
- C语言程序中编写外设接口结构体