博客專欄

        EEPW首頁 > 博客 > 簡單實用IO輸入輸出框架

        簡單實用IO輸入輸出框架

        發布人:魚鷹談單片機 時間:2021-05-12 來源:工程師 發布文章

        在一個嵌入式系統中,可能存在許多輸入或輸出的IO口,輸入有霍爾傳感器、紅外對管等,輸出有LED、電源控制開關等。

        如果說硬件可以一次成型,那么隨便一份代碼都可以完成IO的配置工作,但研發階段的產品,硬件各種修改是難免的,每一次 IO 的修改,對于底層開發人員來說,可能都是一次挑戰。

        因為一旦有某一個 IO 配置錯誤,或者原來的配置沒有修改正確(比如一個 IO 在原來的硬件適配中是輸入,之后的硬件需要修改成輸出),那么你很難查出來這是什么問題,因為這個時候不僅硬件修改了,軟件也修改了,你需要先定位到底是軟件問題還是硬件問題,所以一個好用的 IO 的配置框架就顯得很有必要了。

        有道友會說,不如使用 CubeMx 軟件進行開發吧。


        1、這個軟件適用于 ST 單片機,以前還能用,現在,除非你家里有礦,不然誰用的起STM32?基本上都國產化了(雖然有些單片機號稱兼容,但到底還是有些差異的)。

        2、公司原本的代碼就是使用標準庫,只是因為IO 的變化,你就需要把整個庫換掉嗎?時間上允許嗎?你確定修改后不會出現大問題?

        3、國產化的芯片可沒有所謂的標準庫和HAL庫供你選擇,每一家都有各自的庫,如果你的產品臨時換方案怎么辦?

        4、HAL 效率問題。

        今天魚鷹介紹一個簡單實用的框架,可用于快速增加或修改IO配置,甚至修改底層庫。

        假設有3個 LED 作為輸出、3 個霍爾傳感器作為輸入:

        輸入配置代碼:

        #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),   
        };
        // 單個 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 個 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;
        }
        // 讀取單個 IO 狀態
        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,另一個枚舉值自動修改為相反值
            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);//必須,進入調試模式后點擊全速運行
            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);
            }                      
        }
        調試的時候,我們可以很方便的查看每個 IO 的狀態是怎樣的,而不用管 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);//必須,進入調試模式后點擊全速運行
            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);
            }                      
        }

        這個框架有啥好處呢?

        1、自動完成 GPIO 的時鐘初始化工作,也就是說你只需要修改引腳即可,不必關心時鐘配置,但對于特殊引腳(比如PB3),還是得另外配置才行。

        2、應用和底層具體 IO 分離,這樣一旦修改了 IO,應用代碼不需要進行任何修改。

        3、增加或刪減 IO 變得很簡單,增加 IO時,首先加入對應枚舉,然后就可以添加對應的 IO 了。刪除 IO時,只要屏蔽對應枚舉值和引腳即可。

        4、參數檢查功能, IO 刪除時,因為屏蔽了對應的枚舉,所以編譯時可以幫你發現問題,而增加 IO 時,它可以幫你在運行時檢查該 IO是否進行配置了,可以防止因為失誤導致的問題。

        1.jpg

        5、更改庫時可以很方便,只需要修改對應的宏即可,目前可以順利在 GD32 和 STM32 庫進行快速更換。

        6、對于輸入 IO 而言,可以方便的修改有效和無效狀態,防止硬件修改有效電平。對于輸出 IO 而言,可以設定初始 IO 電平狀態。

        7、代碼簡單高效,盡可能的復用代碼,增加一個 IO 只需要很少的空間。

        8、缺點就是,只對同種配置的 IO 可以這樣用。


        好好看看,或許能學到不少技巧哦。


        *博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。



        關鍵詞: ST

        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 左贡县| 南宁市| 龙南县| 金平| 九江市| 漾濞| 庆云县| 乐至县| 中西区| 双柏县| 道真| 富裕县| 海丰县| 永顺县| 大足县| 雷州市| 汝城县| 秦皇岛市| 兴隆县| 商南县| 新兴县| 方正县| 庆城县| 桃源县| 鄂伦春自治旗| 巴彦淖尔市| 镶黄旗| 师宗县| 肥东县| 丰城市| 合作市| 阿尔山市| 遵义市| 虎林市| 江永县| 平果县| 高陵县| 黄大仙区| 军事| 二连浩特市| 临朐县|