/* 外設基地址 */
#define PERIPH_BASE 0x40000000UL
/* 總線基地址 */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000UL)
#define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000UL)
/* GPIO 外設基地址 */
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000UL)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400UL)
#define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800UL)
#define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00UL)
#define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000UL)
#define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400UL)
#define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800UL)
#define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00UL)
#define GPIOI_BASE (AHB1PERIPH_BASE + 0x2000UL)
#define GPIOJ_BASE (AHB1PERIPH_BASE + 0x2400UL)
#define GPIOK_BASE (AHB1PERIPH_BASE + 0x2800UL)
數字後面的"UL"是後綴,代表unsigned longlong
這些地址在前幾天都已經介紹過了,現在看來應該熟悉多了,相同類型的地址都是以一個基地址去做偏移。
typedef struct
{
__IO uint32_t MODER; /*!< GPIO 模式暫存器 Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO 輸出類型暫存器 Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO 輸出速度暫存器 Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO 上拉/下拉暫存器 Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO 輸入數據暫存器 Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO 輸出數據暫存器 Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO 置位/復位暫存器 Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO 配置鎖定暫存器 Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO 富用功能配置暫存器 Address offset: 0x20-0x24 */
} GPIO_TypeDef;
這樣我們就可以以結構體的方式來使用暫存器,增加可讀性。
最後再來簡化一下名稱,以GPIOx的方式直接獲取GPIOx的基位址。
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)
#define GPIOI ((GPIO_TypeDef *) GPIOI_BASE)
#define GPIOJ ((GPIO_TypeDef *) GPIOJ_BASE)
#define GPIOK ((GPIO_TypeDef *) GPIOK_BASE)
stm32幾乎都把所有參數都用define的方式來定義增加可讀性,我們再附上GPIO_Pin,以及GPIO_PinState的定義吧
#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */
#define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */
#define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */
#define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */
#define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */
#define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */
#define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */
#define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */
#define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */
#define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */
#define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */
#define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */
#define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */
#define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */
#define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */
#define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */
#define GPIO_PIN_All ((uint16_t)0xFFFF) /* All pins selected */
可以發現GPIO_PIN_0所對應到的數字為0x0001轉換為2進位的話就是0b 0000 0000 0000 0001,我們再來做幾個轉換,觀察一下
GPIO_PIN_1 對應到0x0002 = 0b 0000 0000 0000 0010
GPIO_PIN_2 對應到0x0004 = 0b 0000 0000 0000 0100
GPIO_PIN_3 對應到0x0008 = 0b 0000 0000 0000 1000
GPIO_PIN_4 對應到0x0010 = 0b 0000 0000 0001 0000
這樣應該很清楚了吧,一次就是只有一個bit會是1,這樣就可以用每個位元來代表pin0~15剛好16個位元!
而最後一個GPIO_PIN_ALL 對應到0xffff轉換成二進位就是每個位元都是1喔
接著是GPIO_PinState的部分:
typedef enum
{
GPIO_PIN_RESET = 0,
GPIO_PIN_SET
}GPIO_PinState;
這裡比較特別,是用C語言列舉的語法(enum)事實上他與define沒什麼不同啦,如果不太認識的可以再翻一下C語言的相關書籍。總之GPIO_PIN_RESET代表0,而GPIO_PIN_SET代表1(enum的會照著順序定義下去,可以省略不寫)
之前在使用GPIO輸出高電位的時候曾經見過這個函式,但當時我們沒有對這個函式的API做進一步的講解,經過我們好幾天對於暫存器的介紹,現在來好好地看看這個函式吧
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
if(PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U;
}
}
而當我們要使用這個函式的時候會打
HAL_GPIO_WritePIn(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
雖然一開始要記得這些變數名稱不是很容易,也會覺得相比Arudino的digitatWrite()的語法難上許多,但是這可是stm32經過暫存器封裝後的結果!要是不這麼打,我們就要自己去操縱那些暫存器,並且還要記得許多暫存器的基位址與偏移位址,因此這樣的打法現在看來是不是更清楚了呢。
這個函式一開始的assert_param只是要確定傳進來的參數是符合的我們要求的,可以先忽略,底下if-else才是真正實際操縱暫存器的部分,至於實際上的運作原理就交給大家思考囉~(可以再回去看一下昨天對於BSRR暫存器的介紹)