TOPに戻る

センサ・シリーズ 温湿度①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と呼びます。

 使用環境
  • Raspberry Pi Raspberry Pi 4 Model B 2MB
  • OS Raspberry Pi OS(32ビット)5.4.72
  • Python3 3.7.3
  • smbusライブラリ 4.1-1
  • i2ctoolsパッケージ 4.1
  • エディタ Mu

CRCの計算

 現在、マキシムのサイトは整理され、同社のWebページに解説のあったXORを使った計算方法は見つからなくなっています。1-Wireの接続方法の古いページも消えてしまいました。

 アナログ・デバイセズのCRC-8-ATMを計算する方法はこちらにあります。

  https://www.analog.com/jp/analog-dialogue/articles/cyclic-redundancy-and-correct-data-communications.html

 同じく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バイトのデータから計算した値です。

連載 ラズパイ センサ・シリーズ

(1) 温度①確度±0.1℃ TMP117 その1 温度の読み出し

(2) 温度①確度±0.1℃ TMP117 その2 アラートの設定

(3) 温度①確度±0.1℃ TMP117 その3 アラートでLED点灯

(4) 温度①確度±0.1℃ TMP117 その4 アラートでリレー駆動

(5) 温度②温度調節器 その1 Modbusの設定

(6) 温度②温度調節器 その2 Modbusで読み書き

(7) 温湿度①SHT31 その1 測定準備

(8) 温湿度①SHT31 その2 測定と波形

(9) 温湿度①SHT31 その3 CRC

(10) 温湿度①SHT31 その4 BLE