假設有一種溫度傳感器,已經測得它的電壓和溫度的對應關系,將電壓值以ADC轉換后的數字量的值表示,形成溫度-AD值的對照表,如下。
大致成一條反比關系的曲線。
ADC的底層驅動已經寫好,對外有一個接口可以實時讀取該傳感器的AD值。現在要求,讀出AD值以后,通過二分查找,查找它對應的溫度值,然后存到另一個結構體中,供后續其它模塊使用。
寫二分查找函數,可以這么構思。
把AD值看作X軸,溫度值看作Y軸。函數有一個輸入,一個輸出。輸入就是從底層接口獲取的ad值,輸出就是查到的對應的溫度值。所以首先可以有兩個參數,前者作為形參,接收輸入;后者設定為一個指針,指向外面定義的一個存儲輸出的變量。其它參數有:兩張表(AD和溫度),還有限制查找范圍的表格的大小。
因此函數頭可以寫成下面這個樣子。前兩個是const SW類型的指針,指向兩張表,然后是表格的大小和輸入輸出。(所有基礎類型定義在其它頭文件里,此處SW表示signed word,是signed short類型)
B look_up_table_sw(const SW * p_XTable_SW, const SW * p_YTable_SW, UW TableSize_UW, SW ValueX_SW, SW *ValueY_SW)
函數返回的類型是“B”布爾類型,查到或者沒查到。
接下來考慮函數的實現。
最開始應該做個安全檢查,確保指針不是空的,表格是有的。
if (p_XTable_SW != NULL && p_YTable_SW != NULL && TableSize_UW > 0) { //...... }
保證這一步起碼沒問題后,再開始工作。
首先,看看AD值是不是超出AD表格的上下限,超出了也算找到,查到的溫度值就取作溫度相應的最高和最低值。
if (ValueX_SW <= p_XTable_SW[0]) { *ValueY_SW = p_YTable_SW[0]; ret = TRUE; } else if (ValueX_SW >= p_XTable_SW[TableSize_UW - 1]) { *ValueY_SW = p_YTable_SW[TableSize_UW - 1]; ret = TRUE; } else { //...... }
在else{}中繼續補充代碼。此時,說明AD值是在查找范圍之內的,下面才是二分查找的重點。
二分查找的思想,其實可以用一種猜數字的小游戲舉例。
魔術師對女孩說:“你在1-100隨便想一個數字,你只要每次告訴我猜大了還是猜小了,我一定能在10次以內猜到它。”女孩想好了一個數字(假如是88),魔術師每次都猜中間的數,不是整數就向下取整。第一次猜50,女孩說小了,那么魔術師瞬間就排除掉了一半的數,前1~49都不用想了。第二次猜75,女孩說小了。第三次猜87,小了。第四次猜93,大了。第五次猜90,大了。第六次就猜到了88。
二分查找實際上是個效率很高的查找算法,但是要求表格是張有序表,適用於不會怎么變但又查找頻繁的表格,就像本次要求中的,AD值和溫度值有明確的一一對應的、關系簡單的表格。
所以首先定義出表示猜大還是猜小的變量,和表示猜的中間數的變量。在一個循環中不斷查找,每次循環開始,刷新猜的中間數。結構如下。
UW nLow = 0x00; UW nHigh = TableSize_UW - 1; UW nMid = (nHigh + nLow) / 2; while (nLow < nHigh) {
nMid = (nHigh + nLow) / 2;
//...... }
在while{}中繼續補充代碼。考慮哪些情況下退出循環。
循環判斷條件寫的是查找下限小於上限,這是最最保底的判斷了,實際上只要能確定到要找的值在哪里,也就是說,我知道它是第幾個,或在第幾個的邊上一點點,就可以了。
比如我知道它是第三個,那么它就是Y軸數組里的Y(3),我知道它在第三個和第四個之間,那么它就在Y(3)和Y(4)之間,可以用某種方法(比如插值)算出來,這可以放到后面進行。簡單來說,退出這個循環的情形就是,我知道它在哪里了。
補充如下。
if (nMid == nLow) { break; } else if (((ValueX_SW >= p_XTable_SW[nMid]) && (ValueX_SW <= p_XTable_SW[nMid + 1])) || ((ValueX_SW <= p_XTable_SW[nMid]) && (ValueX_SW >= p_XTable_SW[nMid - 1]))) { break; } else if (ValueX_SW > p_XTable_SW[nMid]) { nLow = nMid + 1; } else { nHigh = nMid - 1; }
第一個if依然是個保底的判斷,這里可以自己演算一下,nMid最次的結果就是向下取整,最后和下限是一樣的值。然而一般在第二個else if中就可以跳出循環了,這一步就是確定了AD值的具體位置。然后是兩個沒確定位置的情況,讓下限變成原來的中間數(猜小了,往上猜),或讓上限變成原來的中間數(猜大了,往下猜)。每次循環都會刷新的nMid,就是每次重猜的數。代碼里的nMid表示的數組里的序號。
好,到這里,我知道了AD值的具體位置,但還有有三種情況。
①它正好是我猜的數(nMid)。
②它在我猜的數右邊一點。(nMid和nMid+1之間)
③它在我猜的數左邊一點。(nMid和nMid-1之間)
第一種好處理,它是X軸的第nMid個,對應的當然也就是Y軸的第nMid個。
if (p_XTable_SW[nMid] == ValueX_SW) { *ValueY_SW = p_YTable_SW[nMid]; ret = TRUE; }
第二種和第三種怎么處理,畫個圖更好理解。比如說下面的例子,a是從接口獲取的AD值,通過上面的查找,確定了它在第二個和第三個值之間。現在nMid就是2,a落在nMid和nMid+1之間。當然,我想知道的溫度b也落在了Y軸的nMid和nMid+1之間。
(畫直線只是簡單表示反比關系)
這里我采取線性插值的方式計算b是多少。所謂線性插值就是假設相鄰點之間是條直線,斜率是一樣的。
我們把局部畫一下,就像這樣:
可以用斜率或正切相等的方式,也可以用相似三角形,總之原理其實一樣,得到如下的關系式:
進而:
第三種情況在左邊,是完全一樣的,只要整體減小一格,把原來的x(mid)替換成x(mid-1),原來的x(mid+1)替換成x(mid),y(mid+1)替換成y(mid),y(mid)替換成y(mid-1)就行了。
正比關系的推導過程是一樣一樣的。
因此三種情況的代碼如下:
if (p_XTable_SW[nMid] == ValueX_SW) { *ValueY_SW = p_YTable_SW[nMid]; ret = TRUE; } else if (ValueX_SW < p_XTable_SW[nMid]) { *ValueY_SW = p_YTable_SW[nMid-1] - (ValueX_SW - p_XTable_SW[nMid-1]) * (p_YTable_SW[nMid-1] - p_YTable_SW[nMid]) / (p_XTable_SW[nMid] - p_XTable_SW[nMid-1]); ret = TRUE; } else { *ValueY_SW = p_YTable_SW[nMid] - (ValueX_SW- p_XTable_SW[nMid]) * (p_YTable_SW[nMid]-p_YTable_SW[nMid + 1]) / (p_XTable_SW[nMid + 1] - p_XTable_SW[nMid]); ret = TRUE; }
這樣,二分查找函數就寫完了,最后:
return ret;
整項工作要求的完整的二分查找函數的實現,放在lookup.cpp中,如下。
#include "lookup_pub.h" B look_up_table_sw(const SW * p_XTable_SW, const SW * p_YTable_SW, UW TableSize_UW, SW ValueX_SW, SW *ValueY_SW) { B ret = FALSE; if (p_XTable_SW != NULL && p_YTable_SW != NULL && TableSize_UW > 0) { if (ValueX_SW <= p_XTable_SW[0]) { *ValueY_SW = p_YTable_SW[0]; ret = TRUE; } else if (ValueX_SW >= p_XTable_SW[TableSize_UW - 1]) { *ValueY_SW = p_YTable_SW[TableSize_UW - 1]; ret = TRUE; } else { UW nLow = 0x00; UW nHigh = TableSize_UW - 1; UW nMid = (nHigh + nLow) / 2; while (nLow < nHigh) { nMid = (nHigh + nLow) / 2; if (nMid == nLow) { break; } else if (((ValueX_SW >= p_XTable_SW[nMid]) && (ValueX_SW <= p_XTable_SW[nMid + 1])) || ((ValueX_SW <= p_XTable_SW[nMid]) && (ValueX_SW >= p_XTable_SW[nMid - 1]))) { break; } else if (ValueX_SW > p_XTable_SW[nMid]) { nLow = nMid + 1; } else { nHigh = nMid - 1; } } if (p_XTable_SW[nMid] == ValueX_SW) { *ValueY_SW = p_YTable_SW[nMid]; ret = TRUE; } else if (ValueX_SW < p_XTable_SW[nMid]) { *ValueY_SW = p_YTable_SW[nMid-1] - (ValueX_SW - p_XTable_SW[nMid-1]) * (p_YTable_SW[nMid-1] - p_YTable_SW[nMid]) / (p_XTable_SW[nMid] - p_XTable_SW[nMid-1]); ret = TRUE; } else { *ValueY_SW = p_YTable_SW[nMid] - (ValueX_SW- p_XTable_SW[nMid]) * (p_YTable_SW[nMid]-p_YTable_SW[nMid + 1]) / (p_XTable_SW[nMid + 1] - p_XTable_SW[nMid]); ret = TRUE; } } } return ret; }
其它補充的源代碼附在后面。
App_Typedefine.h中對基礎類型作宏定義。
#ifndef _APP_TYPEDEFINE_H_ #define _APP_TYPEDEFINE_H_ // ==== basic type ==== typedef unsigned char UBYTE; typedef signed char SBYTE; typedef unsigned short UWORD; typedef signed short SWORD; typedef unsigned char BOOL; typedef unsigned char BOOLEAN; typedef float FLOAT; typedef double DOUBLE; typedef unsigned char APP_RESULT; #define APP_E_NOT_OK (0x01U) #define APP_E_OK (0x00U) typedef UBYTE UB; typedef SBYTE SB; typedef UWORD UW; typedef SWORD SW; typedef FLOAT F; typedef BOOL B; //typedef void VOID #define VOID void // Definition of local function #define LOCFUNC static // Definition of a function #define PUBFUNC // MACRO used for BOOL type #ifdef FALSE #undef FALSE #endif #define FALSE ((BOOL)0) #ifdef TRUE #undef TRUE #endif #define TRUE (!FALSE) // MACRO used for inline function, used for optimization #ifdef CSPLUS #define INLINE #else #define INLINE inline #endif // MACRO used for const #ifdef CONST #undef CONST #endif #define CONST const // MACRO used for static variable #define LOCVAR static // MACRO used for static functions #define LOCFUNC static // Keep Same as MPU_NULL_PST_GUARD_LowAddress #define NULL_PST ((void*)0xFEE00000) // Definition of NULL-pointer #ifndef NULL #define NULL (0) #endif // Definition of an invalid pointer value #define PTR_INVALID (0xFEE00000) #define NULL_POINTER(type) ((type *) PTR_INVALID) // min-max-constants for integer data types #define UB_MAX ((UB)255) #define UB_MIN ((UB)0) #define UBYTE_MAX ((UB)255) #define UBYTE_MIN ((UB)0) #define SB_MAX ((SB)127) #define SB_MIN ((SB)-128) #define SBYTE_MAX ((SB)127) #define SBYTE_MIN ((SB)-128) #define UW_MAX ((UW)65535) #define UW_MIN ((UW)0) #define UWORD_MAX ((UW)65535) #define UWORD_MIN ((UW)0) #define SW_MAX ((SW)32767) #define SW_MIN ((SW)-32768) #define SWORD_MAX ((SW)32767) #define SWORD_MIN ((SW)-32768) #define UL_MIN ((UL)0) #define UL_MAX ((UL)4294967295) #define ULONG_MIN ((UL)0) #define ULONG_MAX ((UL)4294967295) #define SL_MAX ((SL)2147483647) #define SL_MIN ((SL)-2147483648) #define SLONG_MAX ((SL)2147483647) #define SLONG_MIN ((SL)-2147483648) #endif
lookup_pub.h對查找函數作外部聲明,引用了基礎類型的頭文件。
#ifndef LOOKUP_PUB_H_INCLUDED #define LOOKUP_PUB_H_INCLUDED #include "App_TypeDefine.h" extern B look_up_table_sw(const SW * pSrcTable_SW, const SW * pDestTable_SW, UW TableSize_UW, SW ValueSrc_SW, SW *ValueDest_SW); #endif
HwaAdc_pub.h中定義了用於存值、供其它模塊使用的結構體,以及adc處理函數的外部聲明。
#ifndef _HWA_ADC_PUB_INCLUDED_ #define _HWA_ADC_PUB_INCLUDED_ #include "App_TypeDefine.h" #define ADC_TABLE_SIZE 27 typedef struct { UB state_UB; SW value_SW; }HWA_TEMP_ADC_INFO_ST; typedef struct { HWA_TEMP_ADC_INFO_ST TempInCar_st; // form ADC }IF_HWA_ADC_READ_INFO_ST; typedef struct { IF_HWA_ADC_READ_INFO_ST Adc_st; }IF_SRU_HWA_INFO_READ_PACKET_ST; extern const SW HWA_ADC_AD_Table_SW[ADC_TABLE_SIZE]; extern VOID hwa_adc_process(VOID); #undef EXTERN #endif
HwaAdc.cpp定義了一個內聯函數,用於把查到的溫度值存到上面定義的結構體內。實現了adc處理函數,調用查找函數然后傳送找到的溫度值。為便於測試,所以寫成下面這樣,ad值自己手動輸入,將傳出去的結果顯示出來。
其中定義的normvalue是起放大作用的,因為查找函數返回SW類型,查找后算的值也會向下取整,但是算出的有可能是個浮點數,那就體現不出小數點后面的精度了,所以先放大,再計算,最后返回的整數就能看到小數點后面幾位。
#include "lookup_pub.h" #include "HwaAdc_pub.h" #include <iostream> //展現精度 #define normvalue 100 IF_SRU_HWA_INFO_READ_PACKET_ST hwa_t5_write_pst; const SW HWA_ADC_AD_Table_SW[ADC_TABLE_SIZE] = { 22,25,29,33,38, 45,52,61,73,86, 102,123,147,178,217, 265,326,403,502,629, 794,1010,1293,1670, 2174,2856,3787 }; const SW HWA_ADC_INDOOR_TEMP_Table_SW[ADC_TABLE_SIZE] = { 90 * normvalue,85 * normvalue,80 * normvalue,75 * normvalue,70 * normvalue, 65 * normvalue,60 * normvalue,55 * normvalue,50 * normvalue,45 * normvalue, 40 * normvalue,35 * normvalue,30 * normvalue,25 * normvalue,20 * normvalue, 15 * normvalue,10 * normvalue,5 * normvalue,0 * normvalue,-5 * normvalue, -10 * normvalue,-15 * normvalue,-20 * normvalue,-25 * normvalue, -30 * normvalue,-35 * normvalue,-40 * normvalue }; INLINE VOID VDB_Set_Temp_InCar_V(SW val_sw) { hwa_t5_write_pst.Adc_st.TempInCar_st.value_SW = val_sw; } VOID hwa_adc_process(VOID) { UW adc_value_uw; SW adc_value_sw; //TempInCar_sw //adc_value_uw = analog_if_query_channel(HWA_ADC_CH_INDOOR_TEMP); std::cout << "Enter ad value input:"; while (std::cin >> adc_value_uw) { if (look_up_table_sw(HWA_ADC_AD_Table_SW, HWA_ADC_INDOOR_TEMP_Table_SW, ADC_TABLE_SIZE, (SW)adc_value_uw, &adc_value_sw)) { VDB_Set_Temp_InCar_V(adc_value_sw); } std::cout << "ad value from analogif is " << adc_value_uw << ","; std::cout << "TEMP value set out is:" << hwa_t5_write_pst.Adc_st.TempInCar_st.value_SW << "\n"; std::cout << "Enter ad value input:"; } }
最后是主函數main.cpp。
#include "HwaAdc_pub.h" #include <stdlib.h> int main() { hwa_adc_process(); system("pause"); }
測試結果
AD值剛開始輸入21,超出范圍, 所以溫度取到90度。
輸入23,24,可以看到是88.34度,86.67度。