1. 程式人生 > >arm cortex-m0plus源碼學習(三)GPIO

arm cortex-m0plus源碼學習(三)GPIO

byte gen 類型 when 情況 代碼示例 16bit 變量 進行

概述:

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   ] ==       1b0);
  wire        nxt_gpiodir_wren      = write_trans & (HADDR[10: 4] == 7b1000000);
  wire        nxt_gpiointmask_wren  = write_trans & (HADDR[10: 4] == 7b1000001);
//----------------------------------------------------------------------------- // 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 <= 7b0; // 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] == 7b1000000)? gpiodir : ( //Offset 0x400 returns Direction Register (read_mux[10: 4] == 7b1000001)? gpiointmask : ( //Offset 0x410 returns Interrupt Mask Register (read_mux[10 ] == 1b0)? 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{1b0}}; // Disable all buffers on reset gpiointmask <= {32{1b0}}; gpiodata_o <= {32{1b0}}; 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{1b0}}; // 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] == 2b0) & HSEL;

  always @ (hsize_zero or haddr_r or mask8 or type_mask)
    case({hsize_zero,haddr_r})
        3b100  : type_mask2 = {8‘h00, 8h00, 8h00, mask8};
        3b101  : type_mask2 = {8‘h00, 8h00, mask8, 8h00};
        3b110  : type_mask2 = {8‘h00, mask8, 8h00, 8h00};
        3b111  : type_mask2 = {mask8, 8‘h00, 8h00, 8h00};
        3b000,3‘b001,3b010,3‘b011:  type_mask2 = type_mask;
        default : type_mask2 = {32{1bx}};
    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{1b0}};

  always @(posedge HCLK or negedge HRESETn)
    if(~HRESETn)
        mask8 <= {8{1b0}};      // 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 = 1b1;        // All accesses to GPIO are zero-wait
  assign      HRESP     = 1b0;        // Generate OK responses only

也就是說GPIO模塊永遠響應OK,且完全按AHB-Lite定義的讀寫時序,第一個HCLK寫地址,第二個HCLK讀或寫數據

技術分享

arm cortex-m0plus源碼學習(三)GPIO