arm cortex-m0plus源码学习GPIO
Posted tszs_song
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了arm cortex-m0plus源码学习GPIO相关的知识,希望对你有一定的参考价值。
概述:
Cortex-m0的integration_kit提供三个GPIO接口,其中GPIO0传输到外部供用户使用,为EXTGPIO;GPIO1是内核自己的信号,不能乱改,会崩掉;GPIO2是一些中断,这里没开中断,可以读写相应的寄存器。
1. 寄存器寻址,先看手册:
这里倒没什么特别,第10位为1正好对应地址0x400,为DIR寄存器,同理0x410对应中断寄存器。
对应硬件描述语言:
// 本GPIO设备被选通、总线写、总线传输类型为连续或不连续时有效
wire write_trans = HSEL & HWRITE & HTRANS[1];
// 写信号有效时根据地址选择要写入的寄存器
wire nxt_gpiodata_o_wren = write_trans & (HADDR[10 ] == 1\'b0);
wire nxt_gpiodir_wren = write_trans & (HADDR[10: 4] == 7\'b1000000);
wire nxt_gpiointmask_wren = write_trans & (HADDR[10: 4] == 7\'b1000001);
//-----------------------------------------------------------------------------
// AHB register read mux
//-----------------------------------------------------------------------------
// Drive read mux next state from word address when selected
wire [10:4] nxt_read_mux = HSEL ? HADDR[10:4] : read_mux;
always @(posedge HCLK or negedge HRESETn)
if(~HRESETn)
read_mux <= 7\'b0; // Set select to input on reset
else if(HREADY) // When bus is ready
read_mux <= nxt_read_mux; // assign mux select next value
wire [31:0] rdata = (read_mux[10: 4] == 7\'b1000000)? gpiodir : ( //Offset 0x400 returns Direction Register
(read_mux[10: 4] == 7\'b1000001)? gpiointmask : ( //Offset 0x410 returns Interrupt Mask Register
(read_mux[10 ] == 1\'b0)? gpiodata_i : 32\'b0)); //Offset 0x0 to 0x3ff returns Data Register
2. GPIODATA读写
这个特别麻烦,手册里说的特别简洁,对应verilog一大堆。
首先是这个Note当初没有仔细看,后来做实验出问题也没想到,直到写这篇总结才发现,人家还给配了图,特意用来表明:gpio模块用了两个寄存器,两个寄存器是独立的。
看图说话:
然后总线的读数据HRDATA永远只读取mask过以后的gpio_in寄存器,总线的HWDATA永远能向gpio_out寄存器写入数据,自画丑图如下:
verilog:
//----------------------------------------------------------------------------- // IO Pad registers //----------------------------------------------------------------------------- assign GPIOEN = gpiodir; assign GPIOOUT = gpiodata_o; wire [31:0] nxt_gpiodata_i = GPIOIN; // Combine bus and register values using mask for next state of Data Out Register wire [31:0] nxt_gpiodata_o = gpiodata_o_wren ? ((HWDATA & type_mask2) | (gpiodata_o & ~type_mask2)) : gpiodata_o; always @(posedge HCLK or negedge HRESETn) if(~HRESETn) begin gpiodir <= {32{1\'b0}}; // Disable all buffers on reset gpiointmask <= {32{1\'b0}}; gpiodata_o <= {32{1\'b0}}; end else if (HREADY) // When bus is ready: begin gpiodir <= nxt_gpiodir; // update direction register gpiointmask <= nxt_gpiointmask; // update interrupt mask register gpiodata_o <= nxt_gpiodata_o; // updata data out register end always @ (posedge FCLK or negedge HRESETn) if(~HRESETn) gpiodata_i <= {32{1\'b0}}; // Reset all outputs to zero else gpiodata_i <= nxt_gpiodata_i; // Sample GPIOIN continuously
assign HRDATA = rdata & type_mask2;
并且gpiodata_i这个寄存器的赋值是在FCLK时钟域(HCLK是FCLK的门控时钟,二者同频同相),这么设计为什么暂时不太懂,是害怕漏掉信号的变化?
可以看到,这里对总线读数据HRDATA和gpiodata_o寄存器的写入用的是同一组mask信号:type_mask2,这个mask信号得来不易,先看代码:
//-----------------------------------------------------------------------------
// AHB register read/write mask for byte accesses on Data register
//-----------------------------------------------------------------------------
wire nxt_hsize_zero = (HSIZE[1:0] == 2\'b0) & HSEL;
always @ (hsize_zero or haddr_r or mask8 or type_mask)
case({hsize_zero,haddr_r})
3\'b100 : type_mask2 = {8\'h00, 8\'h00, 8\'h00, mask8};
3\'b101 : type_mask2 = {8\'h00, 8\'h00, mask8, 8\'h00};
3\'b110 : type_mask2 = {8\'h00, mask8, 8\'h00, 8\'h00};
3\'b111 : type_mask2 = {mask8, 8\'h00, 8\'h00, 8\'h00};
3\'b000,3\'b001,3\'b010,3\'b011: type_mask2 = type_mask;
default : type_mask2 = {32{1\'bx}};
endcase
type_mask2的真身,可以看到,当hsize_zero为0时type_mask2直接等于type_mask,hsize_zero顾名思义,就是HSIZE为0时有效,这里有点绕,总的来说就是,当HSIZE[1:0]不为0时(字或半字访问),type_mask2=type_mask,再看type_mask:
//----------------------------------------------------------------------------- // AHB write byte address control //----------------------------------------------------------------------------- // Decode term for access to least significant byte wire nxt_byte0 = ( HSIZE[1] ) | ( HSIZE[0] & ~HADDR[1] ) | ( ~HSIZE[0] & ~HADDR[1] & ~HADDR[0] ); // Decode term for access to byte 1 wire nxt_byte1 = ( HSIZE[1] ) | ( HSIZE[0] & ~HADDR[1] ) | ( ~HSIZE[0] & ~HADDR[1] & HADDR[0] ); // Decode term for access to byte 2 wire nxt_byte2 = ( HSIZE[1] ) | ( HSIZE[0] & HADDR[1] ) | ( ~HSIZE[0] & HADDR[1] & ~HADDR[0] ); // Decode term for access to most significant byte wire nxt_byte3 = ( HSIZE[1] ) | //整个字-32bit访问 ( HSIZE[0] & HADDR[1] ) | //半字访问--16bit访问的情况 ( ~HSIZE[0] & HADDR[1] & HADDR[0] ); //字节访问--8bit always @(posedge HCLK or negedge HRESETn) // when bus is ready,byte[3:0] <= nxt_byte[3:0];(这里删去了,为了方便看) wire [31:0] type_mask = { {8{byte3}}, {8{byte2}}, {8{byte1}}, {8{byte0}} };
就是字节使能,HSIZE是总线传输规模,根据AMBA3 AHB Lite手册:
HSIZE[1]=1时是32bit传输,此时type_mask=32\'hFFFF_FFFF
HSIZE[1]=0时,看HSIZE[0]
当HSIZE[0]=1时半字访问,再参考总线地址HADDR[1]的状态,看要访问上半字还是下半字,此时:
如果HADDR[1]=0,type_mask=32\'h0000_FFFF
如果HADDR[1]=1,type_mask=32\'hFFFF_0000
--而HADDR[0]的值是多少,是不care的。
当HSIZE[0]=0时字节访问,type_mask根据HADDR[1:0]的值分别令对应的字节为1:
如果HADDR[1:0]=00,type_mask=32\'h0000_00FF
如果HADDR[1:0]=01,type_mask=32\'h0000_FF00
如果HADDR[1:0]=10,type_mask=32\'h00FF_0000
如果HADDR[1:0]=11,type_mask=32\'hFF00_0000
以GPIO0的访问为例,如下:
总的来说就是:总线字(32bit)或半字(16bit)访问GPIO时,不进行位mask,只有字节(8bit)访问时mask8才有用武之地,下面是mask8:
// For byte accesses, the mask is in HADDR[9:2] wire [7:0] nxt_mask8 = (nxt_hsize_zero)? HADDR[9:2] : {8{1\'b0}}; always @(posedge HCLK or negedge HRESETn) if(~HRESETn) mask8 <= {8{1\'b0}}; // Reset mask to FF else if(HREADY) mask8 <= nxt_mask8; // Update AHB mask with next
其实非常简单粗暴,直接等于HADDR[9:0],并且手册里也给出了说明:
但是好死不死,给的例子也太不恰当了,写0x40000009访问第9bit,那是不是写0x40000008访问第8bit,那不是一直访问到0x40000020就行了,GPIODATA范围可是0x00~0x3ff,留这么多空间是要做啥?
并且,提供的软件程序(cm0pikmcu.h)都是这样的定义:
1 /*--------------------- General Purpose Input and Ouptut ---------------------*/
2 typedef union
3 {
4 __IO uint32_t WORD;
5 __IO uint16_t HALFWORD[2];
6 __IO uint8_t BYTE[4];
7 } GPIO_Data_TypeDef;
8
9 typedef struct
10 {
11 GPIO_Data_TypeDef DATA [256];
12 GPIO_Data_TypeDef DIR;
13 uint32_t RESERVED[3];
14 GPIO_Data_TypeDef IE;
15 } GPIO_TypeDef;
访问方式:
GPIO0->DATA[0].WORD = 0x55;
没看verilog之前怎么也理解不上去这个定义,想着GPIO,不就一个寄存器吗,自己写呗,于是有了下面的代码:
*(u32 *)(0x40000400) = 0xffffffff; //配置GPIO0->DIR寄存器为输出 *(u8 *)(0x40000000) = 0x55; //-现象:写不进去 *(u32 *)(0x40000000) = 0x12345678; //-现象:可以写,但总有几位写不进去。
事实上,GPIODATA的正确写入方式是:
不进行位屏蔽,按字节访问GPIO寄存器的正确方式:
C代码示例:
unsigned char c0, c1, c2, c3; *(unsigned int *)(0x40001400) = 0xffffffff; //GPIO2设为输出 *(unsigned int *)(0x40001000) = 0xa5a5a5a5; //32bit写时无mask *(unsigned short *)(0x40001000) = 0xff3c; //16bit写时无mask *(unsigned short *)(0x40001002) = 0xc300; //16bit写时无mask *(unsigned int *)(0x40001000) = 0x12345678; *(unsigned char *)(0x400013FC) = 0x55; //字节写,令mask=8,8bit全部写入 *(unsigned char *)(0x400013FD) = 0x55; *(unsigned char *)(0x400013FE) = 0x55; *(unsigned char *)(0x400013FF) = 0x55; c0 = *(unsigned char *)(0x400013FC); c1 = *(unsigned char *)(0x400013FD); c2 = *(unsigned char *)(0x400013FE); c3 = *(unsigned char *)(0x400013FF); *(unsigned char *)(0x400013FD) = 0xaa; c0 = *(unsigned char *)(0x400013FC); c1 = *(unsigned char *)(0x400013FD); c2 = *(unsigned char *)(0x400013FE); c3 = *(unsigned char *)(0x400013FF);
无论是memory查看还是读取到变量都没问题。
并且,由于GPIO模块内部的输入、输出寄存器没有互联,如果硬件上不在GPIO模块外把输出给到输入,你是永远也无法在KEIL里看到刚刚写进去的数据的。integration_kit里对例化的三个GPIO配置如下:
通过COMPIKMCU.v里32个buffer实现的:
bufif1 (EXTGPIO[31], mcu_extgpioout[31], mcu_extgpioen[31]); assign mcu_extgpioin = EXTGPIO;
GPIO1和GPIO2没有引到外部,在MCU.v的上一层,cm0p_ik_sys.v里一个赋值语句实现:
assign sys_gpio2in = sys_gpio2out;
其实GPIO1还比较复杂,并不全是输出给输入,反正是core的信号,没事先别乱动,比如像这样:
assign sys_gpio1in[23:22] = sys_gpio1out[23:22]; assign sys_gpio1in[21] = sys_halted; assign sys_gpio1in[20] = sys_lockup; assign sys_gpio1in[19] = SLEEPDEEP;
3. GPIO模块对总线的响应:
//----------------------------------------------------------------------------- // AHB tie offs //----------------------------------------------------------------------------- assign HREADYOUT = 1\'b1; // All accesses to GPIO are zero-wait assign HRESP = 1\'b0; // Generate OK responses only
也就是说GPIO模块永远响应OK,且完全按AHB-Lite定义的读写时序,第一个HCLK写地址,第二个HCLK读或写数据
以上是关于arm cortex-m0plus源码学习GPIO的主要内容,如果未能解决你的问题,请参考以下文章