__weak關鍵字:程序模塊相互獨立的大殺器

在STM32 HAL庫開發中,我們經常會看到__weak這個關鍵字,到底是什麼意思呢?出於這個好奇心我們來打開KEIL的幫助手冊找到它的出處:

意思就是,它是一個弱符號,可以用於修飾變量和函數;不過我們經常看到的是對函數的修飾,所以這裏我們僅討論下函數的修飾就可以了,也就是說,當一個函數前面加上__weak這樣的修飾符以後,允許用戶在其它文件中定義一個和__weak修飾過的一模一樣的函數,最終當編譯器編譯的時候,會選擇用戶定義的函數,如果用戶沒有重新實現這個函數,則編譯器就會去執行帶__weak修飾的函數。

所以在HAL庫裏,比如我們經常會看到像下面這樣的函數:

__weak void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)
{
    UNUSED(huart);
}

__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    UNUSED(huart);
}

....等等....

通常HAL庫源碼裏帶__weak這個弱函數很多內部都沒有實現,它把主動權讓給用戶自己根據自己的需要去定義一個一模一樣的函數,然後去做自己想做的事情,這裏的UNUSED起到一個防止編譯器報警告的作用,原型如下:

#define UNUSED(X) (void)X      /* To avoid gcc/g++ warnings */

這樣就非常好了,我們可以用這樣的機制輕鬆實現程序模塊的相互獨立,如何來實現呢?我把我最近做的項目做一個分享,我完成的是一個金屬檢測傳感器的模塊框架,爲了未來能夠不費吹灰之力移植到別的STM32的平臺,我是這麼來做的:

Metal.h

#define UNUSED_METAL(X) (void)X  

/*金屬傳感器數據採集結構體*/
typedef struct
{
    int Serial_Number ;              /*流水號*/
    uint16_t Heating_Value ;         /*加熱值*/
    uint16_t Heating_Signal_Value ;  /*信號值*/
    uint16_t Devalue ;               /*差值*/
} Metal_Sensor ;
extern Metal_Sensor Metal_Sensor_Device ;
extern Metal_Sensor Meatl_Sensor_Parse  ;

/*註冊金屬傳感器*/
void Register_Metal_Sensor(void);
/*獲取並解析傳感器數據*/
void Get_Metal_Sensor_Data(char *data, Metal_Sensor *sensor_data);

Metal.c

/*解析金屬數據格式結構體*/
typedef struct
{
    int Para1 ;
    int Para2 ;
    int Para3 ;
    int Para4 ;
} Parse_Metal_Passage_Value ;

Metal_Sensor Meatl_Sensor_Parse ;
Metal_Sensor Metal_Sensor_Device ;
__weak void __Register_Metal_Sensor(void);
__weak void CallBack_Metal_Logic(Metal_Sensor *sensor_data);


/*解析傳感器數據*/
void Split_Sensor_Metal_Data(char *Data, Parse_Metal_Passage_Value *Sensor_Data_Info)
{
    char *temp = NULL ;
    Sensor_Data_Info->Para1 = atoi(Data);
    temp = strstr(Data, " ");
    Sensor_Data_Info->Para2 = atoi(temp + 1);
    temp = strstr(temp + 1, " ");
    Sensor_Data_Info->Para3 = atoi(temp + 1);
    temp = strstr(temp + 1, " ");
    Sensor_Data_Info->Para4 = atoi(temp + 1);
    temp = strstr(temp + 1, " ");
}

/*金屬傳感器註冊*/
void Register_Metal_Sensor(void)
{
    __Register_Metal_Sensor();
}

void Get_Metal_Sensor_Data(char *data, Metal_Sensor *sensor_data)
{
    Parse_Metal_Passage_Value para_0 ;
    Split_Sensor_Metal_Data(data, &para_0);
    sensor_data->Serial_Number    = para_0.Para1 ;
    sensor_data->Heating_Value = para_0.Para2 ;
    sensor_data->Devalue  = para_0.Para3 ;
    sensor_data->Heating_Signal_Value   = para_0.Para4 ;
    CallBack_Metal_Logic(sensor_data);
}

__weak void __Register_Metal_Sensor(void)
{
    UNUSED_METAL(NULL);
}

__weak void CallBack_Metal_Logic(Metal_Sensor *sensor_data)
{
    UNUSED_METAL(sensor_data);
}

這裏提供給外面應用邏輯兩個接口,一個是用戶需要提供註冊金屬傳感器的邏輯,他只需要實現__Register_Metal_Sensor函數就可以完成金屬傳感器的註冊了;另一個是用戶拿到解析金屬傳感器的數據以後去做他自己要做的事情,那麼他只需要實現CallBack_Metal_Logic這個函數就可以了。

然後在另一個metal_detect_app.c文件中,直接實現這兩個與Metal.c中一模一樣的函數即可:

/*註冊金屬傳感器*/
void __Register_Metal_Sensor(void)
{
    HAL_UART_DMAStop(&huart6);
    memset(Metal_Sensor_Handler.SensorU6Buffer, 0, SENSOR_U6_BUFFER_SIZE);
    HAL_UART_Receive_DMA(&huart6, (uint8_t*)Metal_Sensor_Handler.SensorU6Buffer, SENSOR_U6_BUFFER_SIZE);
    //開啓空閒中斷
    __HAL_UART_ENABLE_IT(&huart6, UART_IT_IDLE);
}

/*調用具體的應用邏輯*/
void CallBack_Metal_Logic(Metal_Sensor *sensor_data)
{
    /*在不同的頁面執行不同的邏輯*/
    switch(Flow_Cursor.flow_cursor)
    {
    case MAIN_PAGE:
        //實現應用業務邏輯
        break ;

    case METAL_TEST_PAGE:
        //實現應用業務邏輯
        break ;

    default:
        break ;
    }

    /*重新開啓串口DMA接收*/
    memset(Metal_Sensor_Handler.SensorU6Buffer, 0, SENSOR_U6_BUFFER_SIZE);
    HAL_UART_Receive_DMA(&huart6, (uint8_t*)Metal_Sensor_Handler.SensorU6Buffer, SENSOR_U6_BUFFER_SIZE);
}

最後,在我的任務中做如下調用:

/* 金屬傳感器線程入口函數 */
static void metal_thread_entry(void *parameter)
{
    static rt_err_t result;
    /* 創建一個動態信號量,初始值是 0 */
    metal_sem = rt_sem_create("mt_sem", 0, RT_IPC_FLAG_FIFO);

    if (metal_sem == RT_NULL)
    {
        rt_kprintf("create mt_sem failed.\n");
        return ;
    }

    /*註冊金屬傳感器*/
    Register_Metal_Sensor();

    while (1)
    {
        /*獲取金屬傳感器信號量*/
        result = rt_sem_take(metal_sem, RT_WAITING_FOREVER);

        if (RT_EOK == result)
            Get_Metal_Sensor_Data((char *)Metal_Sensor_Handler.SensorU6Buffer, &Meatl_Sensor_Parse);
    }
}

這樣,就輕鬆的實現了模塊的相互獨立了。

往期精彩

如何優雅地打印 HEX 數據?

讓傳感器數據更直觀之LCD曲線顯示

項目資源太緊張了,如何根據map信息進行功能裁剪和優化?

整理了很久之前在碼雲/Github/CSDN上收藏的嵌入式產品級項目分享開源

覺得本次分享的文章對您有幫助,隨手點[在看]並轉發分享,也是對我的支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章