目錄
前言
HAL庫對GPIO的抽象
核心分析:HAL_GPIO_Init
前言
我們終于到達了熟悉的地方,對GPIO的初始化。經過漫長的鋪墊,我們終于歷經千辛萬苦,來到了這里。關于GPIO的八種模式等更加詳細的細節,由于只是點個燈,我們不做所有的分析。
HAL庫對GPIO的抽象
HAL庫對GPIO的抽象可以說是到了一個巔峰。使能一個GPIO,被化簡到了一個非常顯然的步驟。
-
使能對應GPIO所在的Port的時鐘
-
設置GPIO對應的模式——點燈的時候,我們是推挽強力的控制外設
-
使用HAL_GPIO_Init函數注冊到寄存器當中
-
做一些Post Init工作。比如說,我們明確的要求拉高拉低GPIO。這個,需要根據外設電路來實現
以我板子上外接的PA9作為一個例子把!
static void __open_gpioclk(){__HAL_RCC_GPIOF_CLK_ENABLE();
}
?
CCGPIOInitTypeDef led0_init = {.type = {.Pin ? ?= GPIO_PIN_9,.Mode ? = GPIO_MODE_OUTPUT_PP,.Speed ?= GPIO_SPEED_HIGH,.Pull ? = GPIO_PULLUP},.open_clock = __open_gpioclk,.post_init ?= __post_init,.port = GPIOF
};
?
void configure_ccgpio(CCGPIOType* type, ?CCGPIOInitTypeDef* initer)
{type->port = initer->port;type->pinType = initer->type.Pin;
?// oh shit, the open clock is missing initediniter->open_clock ? initer->open_clock() : __die();
?HAL_GPIO_Init(type->port, &initer->type);
?if(initer->post_init) initer->post_init(type);
}
核心分析:HAL_GPIO_Init
我們只是簡單的點個燈,這個函數就可被化簡為如下的邏輯
void HAL_GPIO_Init(GPIO_TypeDef ?*GPIOx, GPIO_InitTypeDef *GPIO_Init)
{uint32_t position;uint32_t ioposition = 0x00U;uint32_t iocurrent = 0x00U;uint32_t temp = 0x00U;
?/* 檢查參數 */assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));assert_param(IS_GPIO_PIN(GPIO_Init->Pin));assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
?/* 配置GPIO引腳 */for(position = 0U; position < GPIO_NUMBER; position++){/* 獲取IO引腳位置 */ioposition = 0x01U << position;/* 獲取當前IO引腳的狀態 */iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;
?...
?/* 配置引腳為高電平來點亮LED */GPIOx->ODR |= iocurrent; // 設置為高電平}}
}
這就是為什么我們可以使用Pin9 | Pin10完成我們的組操作,因為內部,我們是逐個比特的完成我們對GPIO的設置,對于每一個滿足——的確是我們要設置的GPIO
?
if(iocurrent == ioposition){/* --------------------- GPIO模式配置 ------------------------*//* 僅當輸出模式時才需要配置 */if(((GPIO_Init->Mode & GPIO_MODE) == MODE_OUTPUT)){/* 配置IO速度 */assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));temp = GPIOx->OSPEEDR;temp &= ~(GPIO_OSPEEDER_OSPEEDR0 << (position * 2U));temp |= (GPIO_Init->Speed << (position * 2U));GPIOx->OSPEEDR = temp;
?/* 配置IO輸出類型 */temp = GPIOx->OTYPER;temp &= ~(GPIO_OTYPER_OT_0 << position);temp |= (((GPIO_Init->Mode & OUTPUT_TYPE) >> OUTPUT_TYPE_Pos) << position);GPIOx->OTYPER = temp;
?/* 配置IO輸出模式 */temp = GPIOx->MODER;temp &= ~(GPIO_MODER_MODER0 << (position * 2U));temp |= ((GPIO_Init->Mode & GPIO_MODE) << (position * 2U));GPIOx->MODER = temp;}
也就是說,設置我們的輸出類型是下面的:
-
上拉是指在GPIO引腳與電源(通常是3.3V或5V)之間連接一個電阻,這樣當引腳處于輸入狀態時,如果沒有外部信號驅動該引腳,它會自動被拉到高電平。也就是被高電平拽上去了!
-
下拉是指在GPIO引腳與地(0V)之間連接一個電阻,這樣當引腳處于輸入狀態時,如果沒有外部信號驅動該引腳,它會自動被拉到低電平。也就是被低電平拽下去了!
-
無上下拉配置意味著不連接任何上拉或下拉電阻。當GPIO引腳處于輸入模式時,它的電平狀態將取決于外部電路。如果沒有外部驅動信號,這個引腳將處于浮空狀態(Hi-Z),可能會導致電平不穩定,容易受到噪聲干擾。
中的一種。至于GPIO的速度,則是分為低速中速高速。我們的輸出模式大致分兩種:
-
推挽輸出(Push-pull):這種輸出類型意味著GPIO引腳可以驅動電流流向負載,并且在輸出高電平和低電平時都會主動提供電流。即引腳會主動拉高電平和拉低電平。它是最常用的輸出類型。
-
開漏輸出(Open-drain):這種輸出類型意味著GPIO引腳只有在輸出低電平時才會提供電流(拉低電平),而在輸出高電平時,它不會輸出電流,而是處于高阻態(Hi-Z)。通常需要外部上拉電阻來將引腳拉到高電平。你會在使用軟件IIC的時候,再看到它。
所以,筆者按照給出的這個原理圖:配置為上拉的情況,確保初始化后不會立馬被點亮。
很好,現在,我們終于來到了點燈!
set_ccgpio_state(&led0, CCGPIO_LOW);
?
void set_ccgpio_state(CCGPIOType* type, CCGPIOState state)
{HAL_GPIO_WritePin(type->port, type->pinType, (state ? (GPIO_PIN_SET) : (GPIO_PIN_RESET)));
}
此時此刻,我們的GPIO就會被拉到低,形成一個高低的電壓差,存在的電流就把我們的LED導通了!