VLバイト順事故¶
1. 発生した問題¶
VL53L0XをTCA9548A経由で複数接続し、距離を取得する処理を実装した。
センサの初期化およびI²C通信は正常に行われていたが、距離取得時に以下のような実際の距離とは大きく異なる値が返される問題が発生した。
例:
実際の距離:約53 mm
取得値:51201 など
また、取得値が毎回一定ではなく変化する場合もあり、センサそのものやTCA9548Aの通信に問題がある可能性を考えて調査を行った。
なおこのバグ発生前にVL2にアクセスできないバグが発生したが原因はハードのミスであったため修正した。
2. 調査¶
距離取得処理を確認したところ、単発測距関数である
uint16_t readRangeSingleMillimeters(statInfo_t_VL53L0X *extraStats)
から、
readRangeContinuousMillimeters(extraStats)
が呼び出され、最終的にVL53L0Xの距離レジスタを読み取っていることを確認した。
距離レジスタの読み取りには以下の関数が使用されていた。
uint16_t readReg16Bit(uint8_t reg)
uint16_t value;
i2cStat = HAL_I2C_Mem_Read(
&VL53L0X_I2C_Handler,
g_i2cAddr | I2C_READ
reg,
1,
msgBuffer,
2
I2C_TIMEOUT
);
memcpy(&value, msgBuffer, 2);
return value;
}
ここで、VL53L0Xから取得した2バイトのデータをmemcpy()によって直接uint16_tへコピーしていた。
3. 原因¶
VL53L0Xの16bitレジスタはMSB(上位バイト)→ LSB(下位バイト)の順でデータが送信される。
一方、使用しているSTM32F303のCortex-M4はリトルエンディアンでデータを扱う。
そのため、例えばVL53L0Xから距離53 mmが0x00 0x35として取得された場合、memcpy(&value, msgBuffer, 2);によってそのままuint16_tへコピーすると、CPU側ではバイト順が逆に解釈される。
本来、
0x00 0x35
↓
0x0035
↓
53 mmとなるべきところが、
0x00 0x35
↓
0x3500
↓
13568
のような異常値として扱われる。
つまり、VL53L0Xからのデータ取得自体は正常だったが、STM32側で16bit値を解釈する際のバイト順が適切ではなかったことが原因だった。
4. 修正方法¶
memcpy()による直接コピーをやめ、上位バイトと下位バイトを明示的に結合するよう変更した。
uint16_t readReg16Bit(uint8_t reg) {
uint8_t buffer[2];
i2cStat = HAL_I2C_Mem_Read(
&VL53L0X_I2C_Handler,
g_i2cAddr | I2C_READ,
reg,
1,
buffer,
2,
I2C_TIMEOUT
);
return ((uint16_t)buffer[0] << 8) | buffer[1];
}
この処理では、buffer[0]を上位8bit、buffer[1]を下位8bitとして扱っている。
例えば、buffer[0] = 0x00 buffer[1] = 0x35の場合、(0x00 << 8) | 0x35となり、0x0035 = 53として正しく距離を取得できる。
5. その他に確認した問題¶
調査の過程で、複数バイト書き込みを行うwriteMulti()についても、送信用バッファのサイズが実際の書き込みサイズに対して不足する可能性があることが判明した。
初期化処理では複数バイトのレジスタ書き込みが行われるため、固定サイズの小さなバッファを使用すると、バッファの範囲を超えてデータを書き込む可能性がある。
そのため、writeMulti()についても、指定されたバイト数を安全に扱えるよう修正する必要がある。やる気が出た時にやることにする。
6. 結果¶
readReg16Bit()のバイト順処理を修正することで、VL53L0Xから取得する距離値が正常な値として扱えるようになった。
今回の問題では、センサやTCA9548Aの通信そのものが失敗していたわけではなく、I²Cで取得した複数バイトのデータをCPU側でどのように数値へ変換するかが問題となっていた。