簡(jiǎn)單實(shí)用IO輸入輸出框架
在一個(gè)嵌入式系統中,可能存在許多輸入或輸出的IO口,輸入有霍爾傳感器、紅外對管等,輸出有LED、電源控制開(kāi)關(guān)等。
如果說(shuō)硬件可以一次成型,那么隨便一份代碼都可以完成IO的配置工作,但研發(fā)階段的產(chǎn)品,硬件各種修改是難免的,每一次 IO 的修改,對于底層開(kāi)發(fā)人員來(lái)說(shuō),可能都是一次挑戰。
因為一旦有某一個(gè) IO 配置錯誤,或者原來(lái)的配置沒(méi)有修改正確(比如一個(gè) IO 在原來(lái)的硬件適配中是輸入,之后的硬件需要修改成輸出),那么你很難查出來(lái)這是什么問(wèn)題,因為這個(gè)時(shí)候不僅硬件修改了,軟件也修改了,你需要先定位到底是軟件問(wèn)題還是硬件問(wèn)題,所以一個(gè)好用的 IO 的配置框架就顯得很有必要了。
有道友會(huì )說(shuō),不如使用 CubeMx 軟件進(jìn)行開(kāi)發(fā)吧。
1、這個(gè)軟件適用于 ST 單片機,以前還能用,現在,除非你家里有礦,不然誰(shuí)用的起STM32?基本上都國產(chǎn)化了(雖然有些單片機號稱(chēng)兼容,但到底還是有些差異的)。
2、公司原本的代碼就是使用標準庫,只是因為IO 的變化,你就需要把整個(gè)庫換掉嗎?時(shí)間上允許嗎?你確定修改后不會(huì )出現大問(wèn)題?
3、國產(chǎn)化的芯片可沒(méi)有所謂的標準庫和HAL庫供你選擇,每一家都有各自的庫,如果你的產(chǎn)品臨時(shí)換方案怎么辦?
4、HAL 效率問(wèn)題。
今天魚(yú)鷹介紹一個(gè)簡(jiǎn)單實(shí)用的框架,可用于快速增加或修改IO配置,甚至修改底層庫。
假設有3個(gè) LED 作為輸出、3 個(gè)霍爾傳感器作為輸入:
輸入配置代碼:
#define GPIOx_Def GPIO_TypeDef* #define GPIOMode_Def GPIOMode_TypeDef typedef struct { GPIOx_Def gpio; uint16_t msk; GPIOMode_Def pull_up_down; } bsp_input_pin_def; #define _GPIO_PIN_INPUT(id, pull, gpiox, pinx) [id].gpio = (GPIOx_Def)gpiox, [id].msk = (1 << pinx), [id].pull_up_down = (GPIOMode_Def)pull #define GPIO_PIN_INPUT(id, pull, gpiox, pinx) _GPIO_PIN_INPUT(id, pull, gpiox, pinx) #define bsp_pin_get_port(gpiox) ((uint16_t)((GPIO_TypeDef *)gpiox)->IDR) #define bsp_pin_get_value(variable,id) do{ bsp_pin_get_port(bsp_input_pin[id].gpio) & bsp_input_pin[id].msk ? variable |= (1 << id) : 0;} while(0) #define BSP_GPIO_PUPD_NONE GPIO_Mode_IN_FLOATING #define BSP_GPIO_PUPD_PULLUP GPIO_Mode_IPU #define BSP_GPIO_PUPD_PULLDOWN GPIO_Mode_IPD typedef enum { PIN_INPUT_HALL_0 = 0, // 輸入 IO 定義 PIN_INPUT_HALL_1, PIN_INPUT_HALL_2, PIN_INPUT_MAX }bsp_pin_input_id_def; static const bsp_input_pin_def bsp_input_pin [PIN_INPUT_MAX] = { GPIO_PIN_INPUT(PIN_INPUT_HALL_0, BSP_GPIO_PUPD_NONE, GPIOA, 0), GPIO_PIN_INPUT(PIN_INPUT_HALL_1, BSP_GPIO_PUPD_NONE, GPIOB, 8), GPIO_PIN_INPUT(PIN_INPUT_HALL_2, BSP_GPIO_PUPD_NONE, GPIOE, 9), }; // 單個(gè) IO 初始化函數 void bsp_pin_init_input(GPIOx_Def gpiox, uint32_t msk, GPIOMode_TypeDef pull_up_down) { uint32_t temp; assert_param((msk & 0xffff0000) == 0 && gpiox != 0); temp = ((uint32_t) gpiox - (uint32_t) GPIOA) / ( (uint32_t) GPIOB - (uint32_t) GPIOA); /* enable the led clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA << temp, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode = (GPIOMode_Def)pull_up_down; GPIO_InitStruct.GPIO_Pin = msk; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init((GPIO_TypeDef*)gpiox, &GPIO_InitStruct); } // 所有 IO 初始化 void gpio_input_init() { bsp_input_pin_def *info; info = (bsp_input_pin_def *)&bsp_input_pin; for(int i = 0; i < sizeof(bsp_input_pin)/sizeof(bsp_input_pin[0]); i++) { bsp_pin_init_input(info->gpio, info->msk, info->pull_up_down); info++; } } // 最多支持 32 個(gè) IO 輸入 uint32_t bsp_input_all(void) { uint32_t temp = 0; bsp_pin_get_value(temp, PIN_INPUT_HALL_0); bsp_pin_get_value(temp, PIN_INPUT_HALL_1); bsp_pin_get_value(temp, PIN_INPUT_HALL_2); return temp; } // 讀取單個(gè) IO 狀態(tài) uint32_t bsp_input_level(bsp_pin_input_id_def id) { return (bsp_pin_get_port(bsp_input_pin[id].gpio) & bsp_input_pin[id].msk) ? 1 : 0; } typedef enum { HW_HAL_LEVEL_ACTIVE = 0, // 可直接修改為 0 或 1,另一個(gè)枚舉值自動(dòng)修改為相反值 HW_HAL_LEVEL_NO_ACTIVE = !HW_HAL_LEVEL_ACTIVE, }hw_input_hal_status_def; typedef struct { hw_input_hal_status_def hal_level0; uint8_t hal_level1; uint8_t hal_level2; }bsp_input_status_def; bsp_input_status_def bsp_input_status; int main(void) { USRAT_Init(9600);//必須,進(jìn)入調試模式后點(diǎn)擊全速運行 gpio_input_init(); while(1) { uint32_t temp = bsp_input_all(); bsp_input_status.hal_level0 = (hw_input_hal_status_def)((temp >> PIN_INPUT_HALL_0) & 1); bsp_input_status.hal_level1 = ((temp >> PIN_INPUT_HALL_1) & 1); bsp_input_status.hal_level2 = ((temp >> PIN_INPUT_HALL_2) & 1); } } 調試的時(shí)候,我們可以很方便的查看每個(gè) IO 的狀態(tài)是怎樣的,而不用管 0 或 1 到底代表什么意思: 圖片 輸出配置代碼: #define GPIOx_Def GPIO_TypeDef* #define GPIOMode_Def GPIOMode_TypeDef typedef struct { GPIOx_Def gpio; uint32_t msk; uint32_t init_value; } bsp_output_pin_def; #define _GPIO_PIN_OUT(id, gpiox, pinx, init) [id].gpio = gpiox, [id].msk = (1 << pinx), [id].init_value = init #define GPIO_PIN_OUT(id, gpiox, pinx, init) _GPIO_PIN_OUT(id, gpiox, pinx, init) #define _bsp_pin_output_set(gpiox, pin) (gpiox)->BSRR = pin #define bsp_pin_output_set(gpiox, pin) _bsp_pin_output_set(gpiox, pin) #define _bsp_pin_output_clr(gpiox, pin) (gpiox)->BRR = pin #define bsp_pin_output_clr(gpiox, pin) _bsp_pin_output_clr(gpiox, pin) typedef enum { PIN_OUTPUT_LED_G, PIN_OUTPUT_LED_R, PIN_OUTPUT_LED_B, PIN_OUTPUT_MAX }bsp_pin_output_id_def; static const bsp_output_pin_def bsp_output_pin [PIN_OUTPUT_MAX] = { GPIO_PIN_OUT(PIN_OUTPUT_LED_G, GPIOA, 0, 0), GPIO_PIN_OUT(PIN_OUTPUT_LED_R, GPIOF, 15, 0), GPIO_PIN_OUT(PIN_OUTPUT_LED_B, GPIOD, 10, 0), }; void bsp_pin_init_output(GPIOx_Def gpiox, uint32_t msk, uint32_t init) { uint32_t temp; assert_param((msk & 0xffff0000) == 0 && gpiox != 0); temp = ((uint32_t) gpiox - (uint32_t) GPIOA) / ( (uint32_t) GPIOB - (uint32_t) GPIOA); /* enable the led clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA << temp, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode = (GPIOMode_Def)GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Pin = msk; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init((GPIO_TypeDef*)gpiox, &GPIO_InitStruct); if(init == 0) { bsp_pin_output_clr(gpiox, msk); } else { bsp_pin_output_set(gpiox, msk); } } void bsp_output_init() { bsp_output_pin_def *info; info = (bsp_output_pin_def *)&bsp_output_pin; for(int i = 0; i < sizeof(bsp_output_pin)/sizeof(bsp_output_pin[0]); i++) { bsp_pin_init_output(info->gpio, info->msk, info->init_value); info++; } } void bsp_output(bsp_pin_output_id_def id, uint32_t value) { assert_param(id < PIN_OUTPUT_MAX); if(value == 0) { bsp_pin_output_clr(bsp_output_pin[id].gpio, bsp_output_pin[id].msk); } else { bsp_pin_output_set(bsp_output_pin[id].gpio, bsp_output_pin[id].msk); } } int main(void) { USRAT_Init(9600);//必須,進(jìn)入調試模式后點(diǎn)擊全速運行 bsp_output_init(); while(1) { bsp_output(PIN_OUTPUT_LED_G, 1); bsp_output(PIN_OUTPUT_LED_B, 0); bsp_output(PIN_OUTPUT_LED_R, 1); } }
這個(gè)框架有啥好處呢?
1、自動(dòng)完成 GPIO 的時(shí)鐘初始化工作,也就是說(shuō)你只需要修改引腳即可,不必關(guān)心時(shí)鐘配置,但對于特殊引腳(比如PB3),還是得另外配置才行。
2、應用和底層具體 IO 分離,這樣一旦修改了 IO,應用代碼不需要進(jìn)行任何修改。
3、增加或刪減 IO 變得很簡(jiǎn)單,增加 IO時(shí),首先加入對應枚舉,然后就可以添加對應的 IO 了。刪除 IO時(shí),只要屏蔽對應枚舉值和引腳即可。
4、參數檢查功能, IO 刪除時(shí),因為屏蔽了對應的枚舉,所以編譯時(shí)可以幫你發(fā)現問(wèn)題,而增加 IO 時(shí),它可以幫你在運行時(shí)檢查該 IO是否進(jìn)行配置了,可以防止因為失誤導致的問(wèn)題。
5、更改庫時(shí)可以很方便,只需要修改對應的宏即可,目前可以順利在 GD32 和 STM32 庫進(jìn)行快速更換。
6、對于輸入 IO 而言,可以方便的修改有效和無(wú)效狀態(tài),防止硬件修改有效電平。對于輸出 IO 而言,可以設定初始 IO 電平狀態(tài)。
7、代碼簡(jiǎn)單高效,盡可能的復用代碼,增加一個(gè) IO 只需要很少的空間。
8、缺點(diǎn)就是,只對同種配置的 IO 可以這樣用。
好好看看,或許能學(xué)到不少技巧哦。
*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。