STM32開發筆記03—Bit-Banding


架構圖

帶位操作原理

以往我們在使用暫存器時,都是在操作該暫存器32bits(4bytes)的儲存地址,要對其中單一bit進行操作,可以仰賴bit operation來完成。而bit-banding(位帶操作)的目的就是實現直接操作單一比特位

為了達成這個目的首先我們有必要理解STM32的特殊存儲區段位帶區位帶別名區

下圖中的紅色方框代表位帶區的範圍,可以看到SRAM中位帶區佔有1MB,外設區段部分,1MB的儲存範圍剛好涵蓋了AHB, APB1, APB2,可以說我們使用的大部分外設,例如GPIO, UART, SPI等等都在位帶區當中

外設的位帶區地址範圍: 0x40000000~0x400F0000
SRAM的位帶區地址範圍: 0x20000000~0x200FFFFF

位帶區不僅可以用來儲存暫存器數據外還有一個膨脹32倍位帶別名區,這個位帶別名區就是操作單一比特位的關鍵。首先我們來看看為何位帶別名區是位帶區的32倍

如圖所示,每個比特位都可以用一個完整的32bits地址來代替,有就是說一個32bits的暫存器在位帶別名區會被擴展成32*32=1024bits,因此位帶別名區的儲存空間為1MB的位帶區空間乘上32等於32MB。由此可知,我們可以直接操作位帶別名區地址來達到操作位帶區單一比特位的目的

需要注意膨脹過後的位帶別名區只有LSB(最低位)比特位有效

地址轉換

不過要操作位帶別名區之前須要先知道位帶區與位帶別名區之間的關係,幸好地址之間的轉換是有跡可循的:

  • 外設地址轉換公式: 0x42000000 + (A-0x40000000)*32+n*4
  • SRAM地址轉換公式: 0x22000000 + (A-0x20000000)*32+n*4

上述公式可以拆解成: 位帶別名區地址 = 位帶別名區首地址 + (目標位帶區地址 – 位帶區首地址)*32 + 第n個比特位*4

該轉換公式簡單來說就是地址膨脹後的位置運算,各個位帶別名區的起始位置為:

  • 外設位帶別名區首地址: `042000000
  • SRAM位帶別名區首地址: 0x22000000

為了在運算當中不用在程式中編寫兩個不同的運算式,我們試著將兩個公式用一條語句來完成:

(bit_band_addr & 0xf0000000) + 0x02000000 + ((bit_band_addr & 0x000fffff << 5) + n << 2)

程式案例

位帶操作的實作非常簡單,主要就下列4個步驟,其中最重要的就是地址轉換以及轉換成指標類型

  1. 尋找指定位帶區地址
  2. 轉換成位帶別名地址
  3. 轉換成指標類型
  4. 進行賦值

因為我們計算出來的"地址"充其量也就只是一個整數值而已,必須經過類型轉換告訴編譯器它是一"地址",引此我們使用macro來處理運算出來的位帶別名區數值

下列程式碼我們只要使用macroBIT_GPIOH(暫存器地址, 比特位)就可以成功轉換成位帶別名區地址:

#define BITBAND(addr, bitnum)              ((addr&0xf0000000) + 0x2000000 + ((addr&0x000fffff)<<5) + (bitnum<<2))
#define MEM_ADDR(addr)                     *(volatile uint32_t*)(addr)
#define BIT_GPIOH(addr, n)                 MEM_ADDR(BITBAND(addr, n))

舉個簡單的LED操作來說,透過位帶別名區也可以控制GPIO的輸出,例如以下範例程式碼,只要知道別名區地址後,操作方式其實跟普通的暫存器操作無二致:

#include "bsp_led.h"
void GPIO_Initial_API(uint16_t LED){    
    BIT_GPIOH(BIT_MODER, (LED*2)) = SET;
        BIT_GPIOH(BIT_MODER, ((LED*2)+1)) = RESET;

        BIT_GPIOH(BIT_TYPER, LED) = GPIO_OType_PP;

        BIT_GPIOH(BIT_SPEEDER, (LED*2)) = SET;
        BIT_GPIOH(BIT_SPEEDER, ((LED*2)+1)) = SET;

        BIT_GPIOH(BIT_PuPr, (LED*2)) = SET;
        BIT_GPIOH(BIT_PuPr, ((LED*2)+1)) = RESET;

        BIT_GPIOH(BIT_ODR, LED) = SET;
}

void LED_Init(void){
        /*Enable peripheral cloack*/
        RCC_AHB1PeriphClockCmd(LED_RCC, ENABLE);

        /*Initialize pin to set*/
        GPIO_Initial_API(LED1);
        GPIO_Initial_API(LED2);
        GPIO_Initial_API(LED3);
}

void LED_button_toggle(uint16_t LED, uint8_t key){
    if(key){
        BIT_GPIOH(BIT_ODR, LED) = BIT_GPIOH(BIT_IDR, LED) ^ 0x01;
    }
}

附上header對照

#ifndef __BSP_LED_H_
#define __BSP_LED_H_
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include <stdio.h>
#include <stdint.h>
#include "time.h"

/*Increase portability of bsp_led*/
#define LED_Red_PIN         GPIO_Pin_10
#define LED_Green_PIN         GPIO_Pin_11
#define LED_Blue_PIN         GPIO_Pin_12
#define LED_PORT             GPIOH
#define LED_RCC             RCC_AHB1Periph_GPIOH

#define LED1                10
#define LED2                11
#define LED3                12

#define SET                 1
#define RESET               0

#define BIT_MODER             (GPIOH_BASE)
#define BIT_TYPER             (GPIOH_BASE + 0x04)
#define BIT_SPEEDER         (GPIOH_BASE + 0x08)
#define BIT_PuPr             (GPIOH_BASE + 0x0c)
#define BIT_IDR             (GPIOH_BASE + 0x10)
#define BIT_ODR             (GPIOH_BASE + 0x14)


#define BITBAND(addr, bitnum)                  ((addr&0xf0000000) + 0x2000000 + ((addr&0x000fffff)<<5) + (bitnum<<2))
#define MEM_ADDR(addr)                         *(volatile uint32_t*)(addr)
#define BIT_GPIOH(addr, n)                     MEM_ADDR(BITBAND(addr, n))

extern void LED_Init(void);
extern void LED_button_toggle(uint16_t PIN, uint8_t key);

#endif /*__BSP_LED_H_*/

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

%d 位部落客按了讚: