nrfx_err_t nrfx_gpiote_out_init(nrfx_gpiote_pin_t pin,nrfx_gpiote_out_config_t const * p_config)
{NRFX_ASSERT(nrf_gpio_pin_present_check(pin));NRFX_ASSERT(m_cb.state == NRFX_DRV_STATE_INITIALIZED);NRFX_ASSERT(p_config);nrfx_err_t err_code = NRFX_SUCCESS;if (pin_in_use(pin)){err_code = NRFX_ERROR_INVALID_STATE;}else{if (p_config->task_pin){int8_t channel = channel_port_alloc(pin, NULL, true);if (channel != NO_CHANNELS){nrf_gpiote_task_configure((uint32_t)channel,pin,p_config->action,p_config->init_state);}else{err_code = NRFX_ERROR_NO_MEM;}}else{pin_in_use_set(pin);}if (err_code == NRFX_SUCCESS){if (p_config->init_state == NRF_GPIOTE_INITIAL_VALUE_HIGH){nrf_gpio_pin_set(pin);}else{nrf_gpio_pin_clear(pin);}nrf_gpio_cfg_output(pin);pin_configured_set(pin);}}NRFX_LOG_INFO("Function: %s, error code: %s.", __func__, NRFX_LOG_ERROR_STRING_GET(err_code));return err_code;
}
nrfx_gpiote_out_init 函數的主要作用是初始化一個 GPIO 引腳作為 GPIOTE(通用外設中斷和事件)的輸出引腳。它接收一個引腳編號和一個配置結構體指針作為參數,根據配置對引腳進行相應的初始化設置,并返回初始化結果的錯誤碼。
函數參數 ? nrfx_gpiote_pin_t pin:要初始化的 GPIO 引腳編號。
? nrfx_gpiote_out_config_t const * p_config:指向 GPIO 輸出配置結構體的指針,該結構體包含了引腳的各種配置信息,如是否作為任務引腳、引腳動作、初始狀態等。
nrfx_gpiote_out_config_結構體定義如下 :
typedef struct
{nrf_gpiote_polarity_t action; /**< Configuration of the pin task. */nrf_gpiote_outinit_t init_state; /**< Initial state of the output pin. */bool task_pin; /**< True if the pin is controlled by a GPIOTE task. */
} nrfx_gpiote_out_config_t;
其中nrf_gpiote_outinit_t是枚舉類型,其定義如下 :
typedef enum
{NRF_GPIOTE_INITIAL_VALUE_LOW = GPIOTE_CONFIG_OUTINIT_Low, ///< Low to high.NRF_GPIOTE_INITIAL_VALUE_HIGH = GPIOTE_CONFIG_OUTINIT_High ///< High to low.
} nrf_gpiote_outinit_t;
NRFX_ASSERT(nrf_gpio_pin_present_check(pin));
? ? NRFX_ASSERT(m_cb.state == NRFX_DRV_STATE_INITIALIZED);
? ? NRFX_ASSERT(p_config);
NRFX_ASSERT 是一個斷言宏,用于在開發和調試階段檢查某些條件是否滿足。如果條件不滿足,程序會觸發斷言失敗,幫助開發者快速定位問題。
nrf_gpio_pin_present_check(pin) 檢查指定的引腳是否存在
?m_cb.state == NRFX_DRV_STATE_INITIALIZED 檢查 GPIOTE 驅動的狀態是否已經初始化。
?p_config 檢查配置結構體指針是否有效。
if (pin_in_use(pin))
? ? {
? ? ? ? err_code = NRFX_ERROR_INVALID_STATE;
? ? }
pin_in_use(pin) 函數檢查指定的引腳是否已經被使用。如果該引腳已經被使用,則將錯誤碼設置為 NRFX_ERROR_INVALID_STATE,表示狀態無效。
else
? ? {
? ? ? ? if (p_config->task_pin)
? ? ? ? {
? ? ? ? ? ? int8_t channel = channel_port_alloc(pin, NULL, true);
? ? ? ? ? ? if (channel != NO_CHANNELS)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? nrf_gpiote_task_configure((uint32_t)channel,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? pin,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? p_config->action,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? p_config->init_state);
? ? ? ? ? ? }
? ? ? ? ? ? else
? ? ? ? ? ? {
? ? ? ? ? ? ? ? err_code = NRFX_ERROR_NO_MEM;
? ? ? ? ? ? }
? ? ? ? }
如果引腳未被使用,檢查配置結構體中的 task_pin 字段。
如果 task_pin 為真,表示該引腳要作為任務引腳使用。
channel_port_alloc(pin, NULL, true) 函數嘗試為該引腳分配一個 GPIOTE 通道。如果分配成功,返回通道編號;如果沒有可用通道,返回 NO_CHANNELS。
如果通道分配成功,調用 nrf_gpiote_task_configure 函數對該通道進行配置,傳入通道編號、引腳編號、引腳動作和初始狀態等參數。
如果通道分配失敗,將錯誤碼設置為 NRFX_ERROR_NO_MEM,表示內存不足(這里實際是沒有可用的 GPIOTE 通道)。
channel_port_alloc函數代碼如下:
static int8_t channel_port_alloc(uint32_t pin, nrfx_gpiote_evt_handler_t handler, bool channel)
{int8_t channel_id = NO_CHANNELS;uint32_t i;uint32_t start_idx = channel ? 0 : GPIOTE_CH_NUM;uint32_t end_idx =channel ? GPIOTE_CH_NUM : (GPIOTE_CH_NUM + NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS);// critical sectionfor (i = start_idx; i < end_idx; i++){if (m_cb.handlers[i] == FORBIDDEN_HANDLER_ADDRESS){pin_in_use_by_te_set(pin, i, handler, channel);channel_id = i;break;}}// critical sectionreturn channel_id;
}
代碼解釋如下 :
函數概述 channel_port_alloc 是一個靜態函數,其作用是為指定的引腳分配一個通道。
該函數會在特定的通道范圍內查找可用通道,若找到,就將該通道分配給指定引腳,并返回通道編號;若未找到,就返回 NO_CHANNELS。
函數參數
? pin:類型為 uint32_t,代表要分配通道的引腳編號。
? handler:類型為 nrfx_gpiote_evt_handler_t,是一個事件處理函數指針,當該引腳發生事件時會調用此函數。
? channel:類型為 bool,用于確定通道分配的范圍。若為 true,就在普通通道范圍內分配;若為 false,就在低功耗事件通道范圍內分配。 ?
若成功分配通道,返回通道編號;若未找到可用通道,返回 NO_CHANNELS。
代碼詳細解釋
static int8_t channel_port_alloc(uint32_t pin, nrfx_gpiote_evt_handler_t handler, bool channel)
{
? ? int8_t ? channel_id = NO_CHANNELS;
? ? uint32_t i;
? ??
根據 channel 參數確定通道分配的起始和結束索引
uint32_t start_idx = channel ? 0 : GPIOTE_CH_NUM; 這行代碼的作用是根據 channel 的值來確定通道分配范圍的起始索引。
? 若 channel 為 true(非零),則 start_idx 被賦值為 0。這意味著通道分配從編號為 0 的通道開始。
? 若 channel 為 false(零),則 start_idx 被賦值為 GPIOTE_CH_NUM。這表明通道分配從編號為 GPIOTE_CH_NUM 的通道開始。
uint32_t end_idx = channel ? GPIOTE_CH_NUM : (GPIOTE_CH_NUM + NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS);
這行代碼的作用是根據 channel 的值來確定通道分配范圍的結束索引。
? 若 channel 為 true(非零),則 end_idx 被賦值為 GPIOTE_CH_NUM。這意味著通道分配的范圍是從 0 到 GPIOTE_CH_NUM - 1。
? 若 channel 為 false(零),則 end_idx 被賦值為 GPIOTE_CH_NUM + NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS。這表明通道分配的范圍是從 GPIOTE_CH_NUM 到 GPIOTE_CH_NUM + NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS - 1。
總結 這兩行代碼的目的是根據 channel 的值選擇不同的通道分配范圍:
? 當 channel 為 true 時,通道分配范圍是從 0 到 GPIOTE_CH_NUM - 1,通常代表普通通道的范圍。
? 當 channel 為 false 時,通道分配范圍是從 GPIOTE_CH_NUM 到 GPIOTE_CH_NUM + NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS - 1,一般代表低功耗事件通道的范圍。
? ??
? ? // 進入臨界區,這里代碼注釋表示是臨界區,但代碼中未實際體現臨界區的保護代碼
? ? // 臨界區用于確保在多線程或多任務環境下,對共享資源的操作是原子的
? ? // 遍歷指定范圍的通道
? ? for (i = start_idx; i < end_idx; i++)
? ? {
? ? ? ? // 檢查當前通道的處理函數地址是否為禁止使用的地址
? ? ? ? if (m_cb.handlers[i] == FORBIDDEN_HANDLER_ADDRESS)
? ? ? ? {
? ? ? ? ? ? // 如果是,調用 pin_in_use_by_te_set 函數將該引腳標記為已使用,并關聯處理函數
? ? ? ? ? ? pin_in_use_by_te_set(pin, i, handler, channel);
? ? ? ? ? ? // 將通道編號賦值給 channel_id
? ? ? ? ? ? channel_id = i;
? ? ? ? ? ? // 找到可用通道后,跳出循環
? ? ? ? ? ? break;
? ? ? ? }
? ? }
? ? // 退出臨界區,同樣這里代碼注釋表示是臨界區,但未實際體現臨界區的保護代碼
? ? // 返回通道編號,如果未找到可用通道,返回 NO_CHANNELS
? ? return channel_id;
}
? ?代碼邏輯總結
1. 把 channel_id 初始化為 NO_CHANNELS,這意味著默認情況下未找到可用通道。
2. 依據 channel 參數確定通道分配的起始和結束索引。
3. 進入臨界區(代碼注釋表明是臨界區,但未實際實現臨界區保護),以此保證在多線程或多任務環境下對共享資源的操作是原子的。
4. 遍歷指定范圍的通道,檢查每個通道的處理函數地址是否為禁止使用的地址。
5. 若找到可用通道,調用 pin_in_use_by_te_set 函數將該引腳標記為已使用,并關聯處理函數,然后將通道編號賦值給 channel_id,接著跳出循環。
6. 退出臨界區(代碼注釋表明是臨界區,但未實際實現臨界區保護)。
7. 返回通道編號,若未找到可用通道,返回 NO_CHANNELS。? ? ? ??
nrf_gpiote_task_configure函數代碼如下:
__STATIC_INLINE void nrf_gpiote_task_configure(uint32_t idx, uint32_t pin,nrf_gpiote_polarity_t polarity,nrf_gpiote_outinit_t init_val)
{NRF_GPIOTE->CONFIG[idx] &= ~(GPIOTE_CONFIG_PORT_PIN_Msk |GPIOTE_CONFIG_POLARITY_Msk |GPIOTE_CONFIG_OUTINIT_Msk);NRF_GPIOTE->CONFIG[idx] |= ((pin << GPIOTE_CONFIG_PSEL_Pos) & GPIOTE_CONFIG_PORT_PIN_Msk) |((polarity << GPIOTE_CONFIG_POLARITY_Pos) & GPIOTE_CONFIG_POLARITY_Msk) |((init_val << GPIOTE_CONFIG_OUTINIT_Pos) & GPIOTE_CONFIG_OUTINIT_Msk);
}
?nrf_gpiote_task_configure 是一個靜態內聯函數,其用途是對 nRF GPIOTE(通用引腳輸入 / 輸出任務和事件)模塊的特定通道配置寄存器進行設置。借助該函數,你能夠指定特定通道所關聯的引腳、引腳極性以及初始輸出值。
函數參數
? idx:uint32_t 類型,代表要配置的 GPIOTE 通道的索引。
? pin:uint32_t 類型,代表要關聯到該通道的引腳編號。
? polarity:nrf_gpiote_polarity_t 類型,用于指定引腳的極性,例如高電平觸發、低電平觸發等。 ? init_val:nrf_gpiote_outinit_t 類型,用于指定引腳的初始輸出值。 ?
? ?
? ?代碼邏輯步驟
1. ?清除相關位:
?NRF_GPIOTE->CONFIG[idx] &= ~(GPIOTE_CONFIG_PORT_PIN_Msk |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?GPIOTE_CONFIG_POLARITY_Msk |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?GPIOTE_CONFIG_OUTINIT_Msk);
? ?
??NRF_GPIOTE->CONFIG[idx] 表示 GPIOTE 模塊中第 idx 個通道的配置寄存器。 ?GPIOTE_CONFIG_PORT_PIN_Msk、GPIOTE_CONFIG_POLARITY_Msk 和 GPIOTE_CONFIG_OUTINIT_Msk 分別是用于選擇引腳、極性和初始輸出值的位掩碼。
?~ 是按位取反運算符,&= 是按位與賦值運算符。這行代碼的作用是把配置寄存器中與引腳、極性和初始輸出值相關的位清零,為后續設置新值做準備。 ?
2. ?設置新值:
NRF_GPIOTE->CONFIG[idx] |= ((pin << GPIOTE_CONFIG_PSEL_Pos) & GPIOTE_CONFIG_PORT_PIN_Msk) |
? ? ? ? ? ? ? ? ? ? ? ? ? ?((polarity << GPIOTE_CONFIG_POLARITY_Pos) & GPIOTE_CONFIG_POLARITY_Msk) |
? ? ? ? ? ? ? ? ? ? ? ? ? ?((init_val << GPIOTE_CONFIG_OUTINIT_Pos) & GPIOTE_CONFIG_OUTINIT_Msk);
? ?
??<< 是左移運算符,用于把 pin、polarity 和 init_val 移動到配置寄存器中對應的位置。
& 是按位與運算符,用于確保只有對應位掩碼覆蓋的位被設置。
| 是按位或運算符,|= 是按位或賦值運算符。這行代碼的作用是將移動并掩碼處理后的 pin、polarity 和 init_val 值設置到配置寄存器中。 ? ?
總結 此函數的主要功能是先清除 GPIOTE 通道配置寄存器中與引腳、極性和初始輸出值相關的位,然后將新的引腳編號、極性和初始輸出值設置到這些位上,從而完成對指定通道的配置。? ?
? ? ?
(2)nrfx_gpiote_out_task_enable函數
void nrfx_gpiote_out_task_enable(nrfx_gpiote_pin_t pin)
{NRFX_ASSERT(nrf_gpio_pin_present_check(pin));NRFX_ASSERT(pin_in_use(pin));NRFX_ASSERT(pin_in_use_by_te(pin));nrf_gpiote_task_enable((uint32_t)m_cb.pin_assignments[pin]);
}