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時鐘域,這麽設計為什麽暫時不太懂,大概是害怕漏掉信號的變化?
可以看到,這裏對總線讀數據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