1. 程式人生 > 其它 >[GPIO]推薦一種超簡單的硬體位帶bitband操作方法,讓變數,暫存器控制,IO訪問更便捷,無需使用者計算位置

[GPIO]推薦一種超簡單的硬體位帶bitband操作方法,讓變數,暫存器控制,IO訪問更便捷,無需使用者計算位置

說明:

M3,M4核心都支援硬體位帶操作,M7核心不支援。


硬體位帶操作優勢


優勢1:

比如我們在地址0x2000 0000定義了一個變數unit8_ta, 如果我們要將此變數的bit0清零,而其它bit不變。

a & = ~0x01

這個過程就需要讀變數a,修改bit0,然後重新賦值給變數a,也就是讀 - 修改 - 寫經典三部曲,如果我們使用硬體位帶就可以一步就完成,也就是所謂的原子操作,優勢是不用擔心中斷或者RTOS任務打斷。

優勢2:
操作便捷,適合用於需要頻繁操作修改的場合,移植性強。不頻繁的直接標準庫或者HAL庫配置即可。


背景知識

這個點知道不知道都沒有關係,不影響我們使用硬體位帶,可以直接看下面案例的操作方法,完全不需要使用者去了解。



位帶操作就是對變數每個bit的操作,以M4核心的STM32F4為例:

(1)將1MB地址範圍 0x20000000 - 0x200FFFFF對映到32MB空間範圍0x22000000 -0x23FFFFFF ----> 這個對應STM32F4的通用RAM空間。

也就是說1MB空間每個bit都拓展為32bit來訪問控制

下面這個圖非常具有代表性。
0x20000000地址的位元組變數 bit0 對映到0x22000000來控制。
0x20000000地址的位元組變數 bit1 對映到0x22000004來控制。
0x20000000地址的位元組變數 bit2 對映到0x22000008來控制。
..........依次類推



(2)將1MB地址範圍 0x40000000 - 0x400FFFFF對映到32MB空間範圍0x42000000 -0x43FFFFFF ----> 這個對應STM32F4的外設空間。



同樣也是1MB空間每個bit都拓展為32bit來訪問控制


(3)舉例,比如訪問0x2000 0010地址裡面位元組變數的bit2

那麼實際要訪問的就是:

bit_word_addr = bit_band_base + (byte_offset x 32) + (bit_number × 4)

0x22000208 = 0x22000000 + (0x10*32) + (2*4)

通過對地址空間0x22000208 進行賦值為0x01就表示bit2置位,賦值為0x00就表示bit2清零,對這個地址空間讀取操作就可以反應bit2的數值。


超簡單實現方案和四個經典案例

這種硬體未帶讓使用者去使用非常不方便,還需要倒騰地址計算。

這裡以MDK為例,提供一種IDE支援的,直接加字尾__attribute__((bitband))即可,對於M3和M4可以直接轉換為硬體位帶實現。

案例1:超簡單控制RAM空間變數:



定義:

typedef struct {
  uint8_t bit0 : 1;
  uint8_t bit1 : 1;
  uint8_t bit2 : 1;
  uint8_t bit3 : 1;        
  uint8_t bit4 : 1;
  uint8_t bit5 : 1;
  uint8_t bit6 : 1;
  uint8_t bit7 : 1;                
} TEST __attribute__((bitband));

TEST tTestVar;

我們定義了一個8bit的變數tTestVar,控制每個bit的方法如下:

tTestVar.bit0 = 1;
tTestVar.bit1 = 1;        
tTestVar.bit2 = 1;
tTestVar.bit3 = 0;        
tTestVar.bit4 = 0;
tTestVar.bit5 = 1;        
tTestVar.bit6 = 1;
tTestVar.bit7 = 1;        

看彙編,已經修改為硬體位帶:



案例2:超簡單控制GPIO輸入輸出暫存器:

GPIO裡面最常用的就是輸入輸出。

GPIO輸出暫存器定義如下,每個bit控制一個IO引腳。

我們軟體定義如下:

typedef struct {
  uint16_t ODR0 : 1;
  uint16_t ODR1 : 1;
  uint16_t ODR2 : 1;
  uint16_t ODR3 : 1;        
  uint16_t ODR4 : 1;
  uint16_t ODR5 : 1;
  uint16_t ODR6 : 1;
  uint16_t ODR7 : 1;        
  uint16_t ODR8 : 1;
  uint16_t ODR9 : 1;
  uint16_t ODR10 : 1;
  uint16_t ODR11 : 1;        
  uint16_t ODR12 : 1;
  uint16_t ODR13 : 1;
  uint16_t ODR14 : 1;
  uint16_t ODR15 : 1;        
  uint16_t Reserved : 16;        
} GPIO_ORD  __attribute__((bitband));

GPIO_ORD *GPIOA_ODR = (GPIO_ORD *)(&GPIOA->ODR);
GPIO_ORD *GPIOB_ODR = (GPIO_ORD *)(&GPIOB->ODR);
GPIO_ORD *GPIOC_ODR = (GPIO_ORD *)(&GPIOC->ODR);
GPIO_ORD *GPIOD_ODR = (GPIO_ORD *)(&GPIOD->ODR);
GPIO_ORD *GPIOE_ODR = (GPIO_ORD *)(&GPIOE->ODR);
GPIO_ORD *GPIOF_ODR = (GPIO_ORD *)(&GPIOF->ODR);
GPIO_ORD *GPIOJ_ODR = (GPIO_ORD *)(&GPIOJ->ODR);
GPIO_ORD *GPIOK_ODR = (GPIO_ORD *)(&GPIOK->ODR);

GPIO輸入暫存器定義如下:

我們軟體定義如下:

typedef struct {
  uint16_t IDR0 : 1;
  uint16_t IDR1 : 1;
  uint16_t IDR2 : 1;
  uint16_t IDR3 : 1;        
  uint16_t IDR4 : 1;
  uint16_t IDR5 : 1;
  uint16_t IDR6 : 1;
  uint16_t IDR7 : 1;        
  uint16_t IDR8 : 1;
  uint16_t IDR9 : 1;
  uint16_t IDR10 : 1;
  uint16_t IDR11 : 1;        
  uint16_t IDR12 : 1;
  uint16_t IDR13 : 1;
  uint16_t IDR14 : 1;
  uint16_t IDR15 : 1;        
  uint16_t Reserved : 16;        
} GPIO_IDR __attribute__((bitband));

GPIO_IDR *GPIOA_IDR = (GPIO_IDR *)(&GPIOA->IDR);
GPIO_IDR *GPIOB_IDR = (GPIO_IDR *)(&GPIOB->IDR);
GPIO_IDR *GPIOC_IDR = (GPIO_IDR *)(&GPIOC->IDR);
GPIO_IDR *GPIOD_IDR = (GPIO_IDR *)(&GPIOD->IDR);
GPIO_IDR *GPIOE_IDR = (GPIO_IDR *)(&GPIOE->IDR);
GPIO_IDR *GPIOF_IDR = (GPIO_IDR *)(&GPIOF->IDR);
GPIO_IDR *GPIOJ_IDR = (GPIO_IDR *)(&GPIOJ->IDR);
GPIO_IDR *GPIOK_IDR = (GPIO_IDR *)(&GPIOK->IDR);

實際操作效果動態,注意看除錯狀態暫存器變化,控制GPIOA的PIN0到PIN3



案例3:超方便的暫存器修改:

比如定時器TIM1的CR暫存器:

我們的定義如下:

typedef struct {
  uint16_t CEN  : 1;
  uint16_t UDIS : 1;
  uint16_t URS  : 1;
  uint16_t OPM  : 1;
  uint16_t DIR  : 1;        
  uint16_t CMS  : 2;
  uint16_t APRE : 1;
  uint16_t CKD  : 2;        
  uint16_t Reserved : 6;        
} TIM_CR1 __attribute__((bitband));

TIM_CR1 *TIM1_CR1 = (TIM_CR1 *)(&TIM1->CR1);

實際操作動態效果,注意看除錯狀態暫存器變化,設定TIM1 CR1暫存器的每個bit控制:

由於標準庫,HAL庫配置這些已經非常方便了,我們再使用這種方式意義不是很大,但對於需要頻繁操作的地方,這種方式就非常好使了,言簡意賅,移植性強,強力推薦,而且是原子操作方式,不用怕中斷打斷。

案例4:應用進階:
最後我們來個進階,比如我們通過32位頻寬的FMC匯流排擴展出來32個GPIO,如果我們採用如下使用方式就非常不直觀

#defineHC574_PORT *(uint32_t *)0x64001000

操作bit1 =0清零,就需要如下操作:

HC574_PORT &= ~(1<<1);

操作bit2和bit10置位,就需要如下操作:

HC574_PORT |= ( ( 1<< 2) | (1<<10))

這種操作會導致以後的程式碼修改非常不便,別人移植使用也非常不方便。如果我們改成如下方式,就方便太多了。

typedef struct                                
{
        uint32_t tGPRS_TERM_ON : 1;   
        uint32_t tGPRS_RESET :1;   
        uint32_t tNRF24L01_CE :1;   
        uint32_t tNRF905_TX_EN :1;  
        uint32_t tNRF905_TRX_CE :1;

        uint32_t tNRF905_PWR_UP :1;   
        uint32_t tESP8266_G0 :1;  
        uint32_t tESP8266_G2 :1;   
               
        uint32_t tLED1 :1;           
        uint32_t tLED2  :1;         
        uint32_t tLED3  :1;         
        uint32_t tLED4 :1;           
        uint32_t tTP_NRST   :1;      
        uint32_t tAD7606_OS0  :1;   
        uint32_t tAD7606_OS1  :1;   
        uint32_t tAD7606_OS2  :1;   
               
        uint32_t tY50_0 :1;         
        uint32_t tY50_1  :1;         
        uint32_t tY50_2  :1;         
        uint32_t tY50_3  :1;         
        uint32_t tY50_4  :1;         
        uint32_t tY50_5  :1;         
        uint32_t tY50_6  :1;         
        uint32_t tY50_7   :1;               

        uint32_t tAD7606_RESET  :1;
        uint32_t tAD7606_RANGE  :1;  
        uint32_t tY33_2 :1;         
        uint32_t tY33_3  :1;         
        uint32_t tY33_4  :1;         
        uint32_t tY33_5  :1;         
        uint32_t tY33_6  :1;         
        uint32_t tY33_7   :1;        
       
}FMCIO_ODR __attribute__((bitband));

FMCIO_ODR *FMC_EXTIO = (FMCIO_ODR *)0x60001000;

比如控制AD7606的OS0引腳高電平就是

FMC_EXTIO->tAD7606_OS0= 1;

控制OS0引腳是低電平就是:

FMC_EXTIO->tAD7606_OS0= 0;

簡單易用,超方便。

M7核心為什麼不支援

M核心權威指南作者Joseph Yiu回覆:
1、Cache問題,如果SRAM所在區域開啟了讀寫Cache,使用位帶操作的話,會有資料一致性問題。
2、位帶需要匯流排鎖機制,在AHB匯流排協議中這相對容易實現,但在AXI匯流排協議中這有點混亂,並且在鎖定序列期間,它可能導致其他匯流排主控的延遲更長。

微信公眾號:armfly_com 安富萊論壇:www.armbbs.cn 安富萊淘寶:https://armfly.taobao.com