センサ・シリーズ 温湿度①SHT31 その3 CRC
通信では、外来ノイズなどによって、データのビットが反転することが日常起こります。したがって、送信側は、データにCRCデータをくっつけて送り、受信側ではデータもしくはアドレスなども含めてCRCを計算し、一致しなかったら再送を要求します。ウキペディアにも書かれているように、CRCにはたくさんの種類があります。短めのCRC 8にもいろいろな種類があります。8というのはCRCのデータ長が8ビットです。
SHT31で採用されているCRC8はダラス・マキシムと呼ばれる計算方法です。もともと1-Wireの通信方式を決めたダラス社をマキシム社が買収したからそういう名称で呼ばれます。2020年にアナログ・デバイセズ社によってマキシム社は買収されたので、将来は、ダラス・マキシム・アナログ・デバイセズと呼ばれるかもしれません。
生成多項式はx8 + x5 + x4 + 1です。Xに2を代入して計算すると0x131です。先頭の1は省略されて0x31をPolynomialと呼びます。
使用環境
|
●CRCの計算
現在、マキシムのサイトは整理され、同社のWebページに解説のあったXORを使った計算方法は見つからなくなっています。1-Wireの接続方法の古いページも消えてしまいました。
アナログ・デバイセズのCRC-8-ATMを計算する方法はこちらにあります。
同じくCRC-8-ATMは、質問サイトに、初期値の話が掲載されています。
CRC8 ATMの計算方法 https://okwave.jp/qa/q8815769.html
CRC-16-CCITTの初期値に関する手計算の例はこちらにあります。
https://taekwongineer.hatenablog.jp/entry/2020/03/25/233948
CRC-8の手計算は、こちらにもあります。
http://zeropaso.gozaru.jp/snetw1gai2.html
これらの情報から、CRC8ダラス・マキシム方式を手計算の方式で導きます。
●条件
SHT31のデータシートに掲載されている条件とサンプルです。
| 特性 | 値 | 注意点 |
|---|---|---|
| 名称 | CRC-8 | |
| 幅 | 8ビット | |
| 適用対象 | 読み出し/書き込みデータ | 温度と湿度はそれぞれ2バイト・データで、そのデータが計算対象 |
| 多項式 | 0x31(x8 + x5 + x4 + 1) | 0x131を使う |
| 初期化 | 0xff | XORの計算では、最初にこの0xffを使う |
| 反射入力 | 偽 | MSBファースト |
| 反射出力 | 偽 | MSBファースト |
| 最終XOR | 0x00 | 0x00はXORしても変化はないので無視 |
| サンプル | 0xbeef=>0x92 |
正しいかどうかを確かめるのに、下記のWebに行き、0xffffや0x0000などの特定な値なども検証しました。
http://www.sunshine2k.de/coding/javascript/crc/crc_js.html
●最初のプログラム
温度と湿度のデータは16ビットです。最初に、このデータの後ろに'00000000'(zeroPadding)をつなげて24ビット・データにします。文字列でdataを作ります。
data = str(bin(inputData)) + '00000000'
初期値は0xffですが、XORするためにこの8ビットを24ビットに拡張します。XORするときに影響のないように'0000000000000000'を後ろに追加します。つまり0xff0000(InitialValue)にします。
文字列のdataをint(data, 2)すると、先頭にある0はなくなり、'1'から始まる数値になります。これで、最初のXORをします。
data = int(data, 2) ^ 0xff0000
dataは整数なので、先頭のビットは'1'です。
次からXORをするために、dataのビット長とPolynomialのビット長を引き算し、Polynomialに対してその差を左にシフトします。すると、Polynomialの先頭の'1'とdataの先頭の'1'始まりで桁数が一致します。
ここまでの計算の様子を示します。
whileループに入ります。終了条件はXORした結果のビット長が8ビット以下になったときです。
ループの中では、XORした結果dataのビット長が減少してくるので、そのビット長にPolynomialのビット長を合わせるように右シフトします。
inputData = 0xbeef
Polynomial = 0x131
InitialValue = 0xff0000
FinalXorValue = 0x00
# InputReflected = False
# ResultReflected = False
print('input data {:024b}'.format(inputData))
zeroPadding = '00000000'
data = str(bin(inputData)) + zeroPadding
print('zeroPadding {:024b}'.format(int(data, 2)))
print('InitialValue {:024b}'.format(InitialValue))
data = int(data, 2) ^ InitialValue
print('InitialValue XOR {:024b}'.format(data))
print('Polynomial {:024b}'.format(Polynomial))
print('---')
print('InitialValue XOR {:24b}'.format(data))
shiftN = (len(str(bin(data))) - 2) - (len(str(bin(Polynomial))) - 2)
Polynomial = Polynomial << shiftN
print('Polynomial {:24b}'.format(Polynomial))
while len(str(bin(data ^ Polynomial))) >= 8:
print('---')
data = data ^ Polynomial
print('data XOR {:24b} 0x{:x}'.format(data, data))
shiftN = (len(str(bin(Polynomial))) - 2) - (len(str(bin(data))) - 2)
Polynomial = Polynomial >> shiftN
print('Polynomial {:24b}'.format(Polynomial))
print('\nCRC8 =', hex(data ^ FinalXorValue))
|
実行結果です。
●関数にした
最後のFinalXorValueとのXORはビット長を考慮していませんが、0x00とxorしても影響がないです。もしほかの値だったら、修正すべきだと思います。
inputData = 0xbeef
def CRC8M(inputData):
Polynomial = 0x131
InitialValue = 0xff0000
FinalXorValue = 0x00
data = str(bin(inputData)) + '00000000' # zeroPadding
data = int(data, 2) ^ InitialValue
Polynomial = Polynomial << ((len(str(bin(data))) - 2) - (len(str(bin(Polynomial))) - 2))
while len(str(bin(data ^ Polynomial))) >= 8:
data = data ^ Polynomial
Polynomial = Polynomial >> ((len(str(bin(Polynomial))) - 2) - (len(str(bin(data))) - 2))
return data ^ FinalXorValue
print('\ninput data 0x{:x} output 0x{:x}'.format(inputData, CRC8M(inputData)))
|
●前回のプログラムにCRCの計算を追加した
前回のプログラムにCRCの測定値と計算値を併記しました。しかし、上記の方法では終了条件の判定がうまくできないことがあったので、修正しました。
import smbus
import time
i2c = smbus.SMBus(1)
addr = 0x44
def CRC8M(inputData):
Polynomial = 0x131
InitialValue = 0xff0000
FinalXorValue = 0x00
zeroPadding = '00000000'
data = str(bin(inputData)) + zeroPadding
data = int(data, 2) ^ InitialValue
shiftN = (len(str(bin(data)))) - (len(str(bin(Polynomial))))
Polynomial = Polynomial << shiftN
for i in range(14):
data = data ^ Polynomial
shiftN = (len(str(bin(Polynomial)))) - (len(str(bin(data))))
Polynomial = Polynomial >> shiftN
if len(str(bin(data ^ Polynomial)))-2 <= 8:
data = data ^ Polynomial
return data ^ FinalXorValue
# main
i2c.write_byte_data(addr, 0x23, 0x34)
time.sleep(0.015)
while 1:
dataTmsb, dataTlsb, dataTcrc, dataHmsb, dataHlsb, dataHcrc = i2c.read_i2c_block_data(addr, 0xff, 6)
rawT = dataTmsb << 8 | dataTlsb
rawR = dataHmsb << 8 | dataHlsb
temp = -45 + rawT * 175 / 65535
RH = 100 * rawR / 65535
print(' temp= {0:.2f} `C (CRC={1:x} {2:x}), humi= {3:.2f} RH% (CRC={4:x} {5:x})'
.format(temp, dataTcrc, CRC8M(rawT), RH, dataHcrc, CRC8M(rawR)))
time.sleep(3)
|
実行例です。かっこでくくっているCRCの値の左は読み出した数値で、右が2バイトのデータから計算した値です。