2013-03-12 25 views
4

我在小型嵌入式設備重新設計(PID控制器)中解決的主要問題是設備參數存儲。我在這裏部分介紹的舊解決方案是節省空間的,但在添加新參數時仍然很笨拙。它是基於其不得不下面給出在示例像匹配第EEPROM地址的設備參數ID:小型嵌入式設備的EEPROM參數結構

// EEPROM variable addresses 

#define EE_CRC      0   // EEPROM CRC-16 value 

#define EE_PROCESS_BIAS    1   // FLOAT, -100.00 - 100.00 U 
#define EE_SETPOINT_VALUE   3   // FLOAT, -9999 - 9999.9 
#define EE_SETPOINT_BIAS    5   // CHAR, -100 - 100 U 
#define EE_PID_USED     6   // BYTE, 1 - 3 
#define EE_OUTPUT_ACTION    7   // LIST, DIRE/OBRNU 
#define EE_OUTPUT_TYPE    8   // LIST, GRIJA/MOTOR 

#define EE_PROCESS_BIAS2    9   // FLOAT, -100.00 - 100.00 U 
#define EE_SETPOINT_VALUE2   11   // FLOAT, -9999 - 9999.9 
#define EE_SETPOINT_BIAS2   13   // CHAR, -100 - 100 U 
#define EE_PID_USED2    14   // BYTE, 1 - 3 
#define EE_OUTPUT_ACTION2   15   // LIST, DIRE/OBRNU 
#define EE_OUTPUT_TYPE2    16   // LIST, GRIJA/MOTOR 

#define EE_LINOUT_CALIB_ZERO  17   // FLOAT, -100.0 - 100.0 
#define EE_LINOUT_CALIB_GAIN  19   // FLOAT, -2.0 - 2.0 

每個地址被硬編碼,並且定義的下一個地址根據先前的數據的大小(注意凹凸地址之間的間隔)。由於沒有EEPROM數據存儲器被浪費,所以效率很高,但難以擴展而不會引入錯誤。

在代碼的其他部分(即HMI菜單,數據存儲...)剛剛給出的代碼將使用參數列表相匹配的地址,類似如下:

// Parameter identification, NEVER USE 0 (zero) as ID since it's NULL 
// Sequence is not important, but MUST be same as in setparam structure 

#define ID_ENTER_PASSWORD_OPER    1 
#define ID_ENTER_PASSWORD_PROGRAM   2 
#define ID_ENTER_PASSWORD_CONFIG   3 
#define ID_ENTER_PASSWORD_CALIB   4 
#define ID_ENTER_PASSWORD_TEST    5 
#define ID_ENTER_PASSWORD_TREGU   6 

#define ID_PROCESS_BIAS     7 
#define ID_SETPOINT_VALUE     8 
#define ID_SETPOINT_BIAS     9 
#define ID_PID_USED      10 
#define ID_OUTPUT_ACTION     11 
#define ID_OUTPUT_TYPE     12 

#define ID_PROCESS_BIAS2     13 

...       
在代碼中使用這些

然後參數,例如在用戶菜單structrues如下,我已經建立了使用我自己的參數類型(結構)項目:

struct param {      // Parametar decription 
    WORD ParamID;     // Unique parameter ID, never use zero value 
    BYTE ParamType;     // Parametar type 
    char Lower[EDITSIZE];   // Lowest value string 
    char Upper[EDITSIZE];   // Highest value string 
    char Default[EDITSIZE];   // Default value string 
    BYTE ParamAddr;     // Parametar address (in it's media) 
};         

typedef struct param PARAM; 

現在的參數列表被構建爲結構的數組:

PARAM code setparam[] = { 
    {NULL, NULL, NULL, NULL, NULL, NULL},     // ID 0 doesn't exist 

    {ID_ENTER_PASSWORD_OPER, T_PASS, "0", "9999", "0", NULL}, 
    {ID_ENTER_PASSWORD_PROGRAM, T_PASS, "0", "9999", "0", NULL}, 
    {ID_ENTER_PASSWORD_CONFIG, T_PASS, "0", "9999", "0", NULL}, 
    {ID_ENTER_PASSWORD_CALIB, T_PASS, "0", "9999", "0", NULL}, 
    {ID_ENTER_PASSWORD_TEST, T_PASS, "0", "9999", "0", NULL}, 
    {ID_ENTER_PASSWORD_TREGU, T_PASS, "0", "9999", "0", NULL}, 

    {ID_PROCESS_BIAS, T_FLOAT, "-100.0", "100.0", "0", EE_PROCESS_BIAS}, 
    {ID_SETPOINT_VALUE, T_FLOAT, "-999", "9999", "0.0", EE_SETPOINT_VALUE}, 
    {ID_SETPOINT_BIAS, T_CHAR, "-100", "100", "0", EE_SETPOINT_BIAS}, 
    {ID_PID_USED, T_BYTE, "1", "3", "1", EE_PID_USED}, 
    {ID_OUTPUT_ACTION, T_LIST, "0", "1", "dIrE", EE_OUTPUT_ACTION}, 
    {ID_OUTPUT_TYPE, T_LIST, "0", "1", "GrIJA", EE_OUTPUT_TYPE}, 

    {ID_PROCESS_BIAS2, T_FLOAT, "-100.0", "100.0", "0", EE_PROCESS_BIAS2}, 

...

從本質上說,每一個參數都有它獨特的ID,這個ID必須匹配硬編碼的EEPROM的地址。由於參數大小不固定,我不能將參數ID本身用作EEPROM(或其他介質)地址。在上面的例子中的EEPROM組織爲16位字,但它不會在原則上(更多的空間被浪費了字符,所以我寧願8位機構在未來反正)關係

問題:

有沒有更好的方法來做到這一點?一些散列表,衆所周知的模式,類似問題的標準解決方案? EEPROM的尺寸現在更大,我不介意使用固定的參數大小(浪費32位布爾參數)以換取更優雅的解決方案。它看起來像固定大小的參數,我可以使用參數ID作爲地址。這種方法有明顯的缺點,我沒有看到?

我現在使用的是分佈式硬件(HMI,I/O和主控制器是分開的),我想使用所有設備都知道這個參數結構的結構,例如遠程I/O知道如何縮放輸入值,並且HMI知道如何顯示和格式化數據,全部僅基於參數ID。換句話說,我需要一個地方,所有的參數將被定義。

我做了我的Google調查,但對於那些不包含某些數據庫的小設備卻找不到什麼。我甚至想過一些XML定義會爲我的數據結構生成一些C代碼,但也許有一些更適合小型設備(高達512 K閃存,32 K RAM)的優雅解決方案?

回答

1

我不確定這是否實際上比您擁有的更好,但這裏有一個想法。爲了便於維護,請考慮將EEPROM地址的知識封裝到「eeprom」對象中。現在你有一個參數對象,每個實例都知道它的數據存儲在物理EEPROM中的什麼地方。如果參數對象不知道EEPROM,可能會更容易維護。而是一個單獨的eeprom對象負責物理EEPROM和參數對象實例之間的接口連接。

另外,考慮將EEPROM數據的版本號添加到保存在EEPROM中的數據。如果設備固件更新並且EEPROM數據的格式發生變化,則此版本號允許新固件識別並轉換舊版本的EEPROM數據。

+0

謝謝,這是取消對特定存儲組織的依賴的好建議。我使用FreeRTOS,所以你所說的'eeprom'對象在我的情況下是一些接收具有參數ID和命令(存儲,獲取,檢查,備份...)的消息並處理存儲到EEPROM的任務,或者任何其他媒體。我可以擁有所有的參數結構和常量數據(默認,最小,最大...)存儲在這個不可變對象中,並且我應該確保任何需要關於內部數據組織特定的任務都應該詢問這個「參數」對象(或任務),無論它需要知道什麼。 – 2013-03-13 17:30:05

+0

是的,有一個確定數據的版本是必須的,否則,如果在新的電路板上使用舊的EEPROM,或者在同一設備的固件更新期間,可能會發生各種各樣的壞事。舊版本只是用每個參數的'default'字段的默認值覆蓋數據,當我們想要保留客戶配置時,安全但耗時。該器件基於EPROM,因此無論如何都不會在操作過程中進行固件更新,這在今天被認爲是標準。 – 2013-03-13 17:40:38

2

如果您不擔心跨更改或處理器的兼容性,您可以簡單地將結構複製到RAM和EEPROM之間,並且只訪問RAM副本的單個成員。

如果您希望直接在EEPROM中直接訪問各個成員,那麼您也可以相對容易地創建一個工具,它可以編譯結構中的定義列表以及編譯器的已知包裝規則。

+0

謝謝,這正是我的主要代碼所做的。我從來沒有直接從EEPROM中使用參數,除非有什麼改變。主控制迴路的開始(每100ms)將檢查從EEPROM複製參數的RAM塊的CRC,並且如果與先前的CRC相比有變化,則整個參數塊將被刷新。 – 2013-03-12 17:40:16

+0

你的第二個建議也值得調查。我討厭依賴於編譯器的具體細節,但我只能在RTOS任務(直接稱爲taskDeviceConfig)內直接訪問存儲介質(EEPROM,NVRAM,SD卡),負責非易失性存儲。當接收到具有特定參數ID的請求的消息時,該任務(僅此任務)將執行提取單個值並將其發送到接收方隊列所需的任何魔術。 – 2013-03-12 17:48:13

+1

通常,在系統上運行的所有交互組件都將具有結構打包的通用基本思想,因爲交換結構在許多OS服務函數中很常見。所以我不認爲你需要將自己限制爲一個訪問者。此外,您可以適度謹慎地使用指針算術在運行時計算元素到結構中的偏移量。 – 2013-03-12 18:44:30

2

這是我會做的。

我會創建一個結構的typedef與您希望在EEPROM中擁有的變量。

使用你的例子會是這個樣子:

typedef struct eeprom_st 
{ 
    float process_biass; 
    float setpoint_value; 
    char setpoint_bias; 
    .... 
} eeprom_st_t; 

比我創建偏移定義來標記結構存儲在EEPROM中。

而且我會一個指針到該類型使用它作爲一個虛擬對象:

#define EEPROM_OFFSET 0 
eeprom_st_t *dummy; 

比我會用offsetof以獲得特定的變量,我需要這樣的偏移:

eeprom_write(my_setpoint_bias, EEPROM_OFFSET + offsetof(eeprom_st_t,setpoint_bias), 
sizeoff(dummy->setpoint_bias)); 

爲了使它更優雅,我會把eeprom寫入程序變成一個宏。