2015-12-03 134 views
8

我正在用嵌入式C做一些工作,使用加速度計作爲14位二進制補碼來返回數據。我將這個結果直接存入uint16_t。後來在我的代碼中,我試圖將這個「原始」形式的數據轉換爲一個有符號的整數,以表示/處理其餘代碼。將原始14位二進制補碼轉換爲有符號16位整數

我無法讓編譯器理解我正在嘗試做什麼。在下面的代碼中,我檢查第14位是否被設置(表示數字是負數),然後我想反轉這些位並加1以獲得數字的大小。

int16_t fxls8471qr1_convert_raw_accel_to_mag(uint16_t raw, enum fxls8471qr1_fs_range range) { 
    int16_t raw_signed; 
    if(raw & _14BIT_SIGN_MASK) { 
    // Convert 14 bit 2's complement to 16 bit 2's complement 
    raw |= (1 << 15) | (1 << 14); // 2's complement extension 
    raw_signed = -(~raw + 1); 
    } 
    else { 
    raw_signed = raw; 
    } 
    uint16_t divisor; 
    if(range == FXLS8471QR1_FS_RANGE_2G) { 
    divisor = FS_DIV_2G; 
    } 
    else if(range == FXLS8471QR1_FS_RANGE_4G) { 
    divisor = FS_DIV_4G; 
    } 
    else { 
    divisor = FS_DIV_8G; 
    } 

    return ((int32_t)raw_signed * RAW_SCALE_FACTOR)/divisor; 
} 

此代碼不幸運行不起作用。反彙編表明,出於某種原因,編譯器正在優化我的聲明raw_signed = -(~raw + 1);我如何實現我期望的結果?

數學的作品在紙上,但我覺得由於某種原因,編譯器和我一起戰鬥:(。

+0

你可以試試'if(raw&_14BIT_SIGN_MASK)raw_signed =(int16_t)(raw | 0xC000);' –

+0

沒有語言「emb編輯C「。左移一個無法在變量中表示的有符號值是未定義的行爲。 – Olaf

+1

如果你打開所有的編譯器警告,你有沒有得到任何相關的診斷?如果在關閉優化的情況下進行編譯,程序是否按預期工作? –

回答

9

將14位2的補碼值轉換爲16位有符號整數Ë保持的值被簡單地的梅特:

int16_t accel = (int16_t)(raw << 2)/4 ; 

左移推動符號位到16位的符號位的位置,由四個分頻恢復幅值,但保持其符號。分界線避免了實施定義的右移行爲,但通常會導致在指令集上允許的單個算術右移。因爲raw << 2int表達式,並且除非int是16位,否則該演員陣容是必需的,該分裂將簡單地恢復原始值。

但是,將加速度計數據左移兩位並將其看作傳感器首先是16位,會更簡單。將所有事情規範化爲16位的優點是,如果您使用的傳感器的位數最多爲16位,則代碼無需更改。數量級將只是四倍,而最低有效兩位將爲零 - 沒有任何信息在任何情況下都會增加或丟失,而且縮放比例是任意的。

int16_t accel = raw << 2 ; 

在這兩種情況下,如果你想無符號大小則是乾脆:

int32_t mag = (int32_t)labs((int)accel) ; 
+1

在UB警察喋喋不休之前,我意識到嚴格地說,這不能保證在補碼不是有符號編碼的情況下工作,但在真實世界中它是有效的。在嵌入式系統中,您會希望知道硬件的行爲,而性能通常比某些假設機器的可移植性更重要。 – Clifford

+2

@chux:謝謝 - 修正。爲了清晰起見,您的兩步解決方案可能會更好。就我個人而言,我真的不打擾恢復到14位幅度縮放;這是一個不必要的步驟。 – Clifford

+0

「......將加速度計數據左移兩位並將其視爲傳感器首先是16位的缺點。「'is with'int16_t mag = abs(accel);''可以嘗試做'abs(0x8000)' – chux

6

我會做簡單的算術來代替。結果是14位有符號,其表示爲從0到2^14號 - 1.測試如果數爲2^13或以上(表示否定),然後減去2^14

int16_t fxls8471qr1_convert_raw_accel_to_mag(uint16_t raw, enum fxls8471qr1_fs_range range) 
{ 
    int16_t raw_signed = raw; 
    if(raw_signed >= 1 << 13) { 
    raw_signed -= 1 << 14; 
    } 

    uint16_t divisor; 
    if(range == FXLS8471QR1_FS_RANGE_2G) { 
    divisor = FS_DIV_2G; 
    } 
    else if(range == FXLS8471QR1_FS_RANGE_4G) { 
    divisor = FS_DIV_4G; 
    } 
    else { 
    divisor = FS_DIV_8G; 
    } 

    return ((int32_t)raw_signed * RAW_SCALE_FACTOR)/divisor; 
} 

請檢查我的算術(我有13。和14是否正確?)

+0

感謝這段代碼,你的算法是正確的只是爲了練習,我最終使用了John提供的代碼來理解必要的操作/強制轉換,但是這個代碼也是有效的(並且產生了更少的頭部劃痕)! – secretformula

+1

這個答案是最優的,因爲它不依賴於本機簽名類型是2的恭維,也不依賴於'raw_signed'正好是16位 – dbush

+0

@secretformula大部分編程時間都花費在維護和調試代碼上,從長遠來看,它總是能夠避免「頭疼」輸入代碼! – UncleO

1

假設int在您的特定C實現爲16個位寬,表達(1 << 15),您在使用損壞raw,產生未定義的行爲。在這種情況下,編譯器可以自由生成代碼來執行幾乎任何事情 - 或者什麼也不做 - 如果條件的分支被執行,其中表達式被評估。

另外,如果int是16位寬,則表達式-(~raw + 1)和所有中間值將具有類型unsigned int == uint16_t。這是「通常的算術轉換」的結果,因爲(16位)int不能表示uint16_t類型的所有值。結果將有高位設置,因此超出int類型所表示的範圍,因此將其分配給類型爲int的左值會產生實現定義的行爲。您必須諮詢您的文檔以確定它定義的行爲是否符合您的預期和想要的行爲。

如果您改爲執行14位符號轉換,強制關閉高位((~raw + 1) & 0x3fff),那麼結果(所需負值的倒數)可由16位帶符號的int表示,所以明確轉換爲int16_t是明確定義的並保留(正)值。你想要的結果是與之相反的結果,你可以簡單地通過否定它來獲得結果。總評:

raw_signed = -(int16_t)((~raw + 1) & 0x3fff); 

當然,如果int在你的環境中寬度大於16位的話,我認爲沒有理由預期你的原始代碼是行不通的。這不會使上述表達式無效,但是,不管缺省值int的大小如何,該表達式都會產生一致定義的行爲。

+0

感謝您幫助我瞭解我的錯誤。我最終與Cliffords一起回答,因爲它更簡潔。再次感謝你! – secretformula

+0

不同意「如果int是16位寬......」段落。 ' - (〜raw + 1)'將全部用16位無符號數學完成。結果將在'int'的範圍內,因此對於不是所有16位組合的問題都不存在於'raw'中,只有2^14個組合。 – chux

1

假設當代碼到達return ((int32_t)raw_signed ...,它在[-8192 ... +8191]範圍內的值:

如果RAW_SCALE_FACTOR是4的倍數,則一點積蓄就可以了。

因此,而不是

int16_t raw_signed = raw << 2; 
raw_signed >>= 2; 

代替

int16_t fxls8471qr1_convert_raw_accel_to_mag(uint16_t raw,enum fxls8471qr1_fs_range range){ 
    int16_t raw_signed = raw << 2; 
    uint16_t divisor; 
    ... 
    // return ((int32_t)raw_signed * RAW_SCALE_FACTOR)/divisor; 
    return ((int32_t)raw_signed * (RAW_SCALE_FACTOR/4))/divisor; 
} 
0

爲14位二進制補碼轉換爲符號的值,你可以翻轉的符號位,減去偏移:

int16_t raw_signed = (raw^1 << 13) - (1 << 13); 
相關問題