TOPに戻る

ラズパイ5 pythonの仮想環境 ④ 温湿度センサSHT45<その1>CRC

 4回にわたって、温湿度センサSHT45を利用します。センシオンの製品で、SHT3xなどのシリーズが入手しやすいです。SHT4xは最近の製品ですが、I2Cバスにつなげて測定データのやり取りはほとんど同じでに見えます。

 温度のデータを2バイト送ってくると、1バイトのCRCが、湿度を2バイトで送ってくると、続いてそのCRCが送られるので、1回の読み出しで6バイトのデータをハンドリングします。

 CRCは、近距離ではほとんど無視できますが、I2Cバスを1mほど伸ばしたセンサが市販されており、運用すると、ノイズがのり、データが化けることがあります。その際、CRCのチェックし正しいデータが届いていないなら新しいデータを読み込むことが必要になります。

 ここでは、CRCの計算を取り上げ、次回、センサのデータを読み取る話をします。という予定でしたが、次回、SHT31と同じようにsmbus2ライブラリを利用してアクセスしても、エラーになったので、利用していません。

CRCの計算

 CRC(Cyclic Redundancy Check;巡回冗長検査)は、送られてくるデータがノイズなどで別の値に化けてないかを確認するコードです。種類がたくさんあって、マイコンなどでは多項式の長さが9ビットのCRC-8が使われることが多いようです。CRC-8の多項式にも何種類かがあります。

 その中でも、最初、1-wire用センサに利用を始めたCRC-8-Dallas方式の多項式は、

  x8 + x5 + x4 + 1 

です。DallasはMaximに買収されたので、一般的には、CRC-8-Dallas/Maximと呼ばれているようです。Maximは現在、アナログ・デバイセズ社に買収されていますが、名称に変化はないようです。

C言語のCRC-8-Dallas/Maxim

 SHT45はセンシオンの製品で、データシートには、CRCの計算方法が説明してあります。同社の空気品質センサSGP40のデータシートに、C言語のコードが掲載されています。

Polynomial ; 0x31 (x8 + x5 + x4 + 1)

Initialization ; 0xFF

Reflect input ; False

Reflect output ; False

Final XOR ; 0x00

uint8_t CalcCrc(uint8_t data[2]) {
    uint8_t crc = 0xFF;
    for(int i = 0; i < 2; i++) {
        crc ^= data[i];
        for(uint8_t bit = 8; bit > 0; --bit) {
            if(crc & 0x80) {
                crc = (crc << 1) ^ 0x31u;
            } else {
                crc = (crc << 1);
            }
        }
    }
    return crc;
}

 Adafruitのライブラリでは、次のように計算しています。

uint8_t Adafruit_SHT31::crc8(const uint8_t *data, int len)
{
 const uint8_t POLYNOMIAL(0x31);
 uint8_t crc(0xFF);
  
   for ( int j = len; j; --j ) {
       crc ^= *data++;
       for ( int i = 8; i; --i ) {
           crc = ( crc & 0x80 )
           ? (crc << 1) ^ POLYNOMIAL
           : (crc << 1);
       }
     }
     return crc;
}

 マキシムの資料ではルックアップ・テーブルが掲載されています。

Var
 CRC : Byte;
Procedure Do_CRC(X: Byte);
{
 This procedure calculates the cumulative Maxim 1-Wire CRC of all bytes passed to it.
The result accumulates in the global variable CRC.
}
Const
 Table : Array[0..255] of Byte = (
 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
 70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,
 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,
 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238,
 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
 202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53);
Begin
 CRC := Table[CRC xor X];
End;

 matlabへ移植した例です。

function [data] = CRC8(inputData)
% CRC-8 Dallas/Maxim/AnalogDevices
% x^8+X^5+X^4+1. X=2, Polynomial=0x131=[1 0 0 1 1 0 0 0 1]
% 初期化(InitialConditions)=0xff 反射入力(ReflectInputBytes)=false 
% 反射出力(ReflectChecksums)=false 最終XOR(FinalXOR)=0x00
%
    %inputData = 0xBEEF
    Polynomial = 0x131;
    initialConditions = 0xff00;
    % init XOR
    data = bitxor(inputData,  initialConditions);
    % fprintf('Get the xor of data and initialConditions %0s',dec2bin(data));

    Polynomial = uint32(Polynomial);
    % fprintf('CRC-8 Polynomial is %0s',dec2bin(Polynomial));

    % '0000000' zero padding input data
    data = bitshift(uint32(data),8);
    % fprintf('zero padding(8) -> 24bit data %0s',dec2bin(data));

    for i = 1:12
        length_data = length(de2bi(data, 'left-msb'));
        if length_data < 9  % 8ビット
            break
        end
        length_Polynomial = length(de2bi(Polynomial, 'left-msb'));
        % fprintf('zero padding input data       %0s',dec2bin(data));
        Polynomial = bitshift(Polynomial, length_data-length_Polynomial);
        data = bitxor(data, Polynomial);
        % fprintf('zero padding Polynomial       %0s', dec2bin(Polynomial));
        % fprintf('xor                           %0s    0x%x', dec2bin(data),data);
    end
    % fprintf('input=0x%x CRC=0x%x', inputData, data);
end

pythonで書き換える

 C言語のプログラムをそのままpythonに変更しても、正しく計算をしません。C言語で、変数crcはuint8_tの型をもっています。例えば、'1'をこのプログラムで多用される左へ1シフトすると、0b00000010になります。そのまま左にシフトしていくと、0b10000000の次は、0になります。

 pythonで同じことをします。0b10000000を左に上記と同じだけシフトすると、0b100000000、9ビットになってしまいます。これは、pythonのintが、最大値の上限はなく、メモリの許す限り大きな値を扱うことが可能だという仕様のためです。

 したがって、左シフトをしたときは、& 0xffで、常に、下位8ビットを取り出して、次の演算に備えるようにします。

 テスト時に用いたプログラムです。左にシフトするだけのプログラムなので、& 0xffは、return時の結果に対して施しても同じ結果になるようです。

def crc8_update(data):
    print('data is {:#x} {:#x}'.format(data[0],data[1]))
    print('\ndata[0]= {:#x} {:b}'.format(data[0],data[0]))
    crc = 0xff
    crc ^= data[0]
    print('    crc= {:#x} {:08b}'.format(crc,crc))
    for i in range(8):
        print('\t',i)
        if(crc & 0x80):
            crc = (crc << 1 & 0xff) ^ 0x31 & 0xff
            print('\t  crc with XOR 0111001 {:#x}\t{:0>8b}'.format(crc,crc))
        else:
            crc = (crc << 1) & 0xff
            print('\t  crc shift <<1        {:#x}\t{:0>8b}'.format(crc,crc))
    crc ^= data[1]
    
    print('    crc= {:#x}  {:b}'.format(crc,crc))
    for i in range(8):
        print('\t',i)
        if(crc & 0x80):
            crc = (crc << 1 & 0xff) ^ 0x31  & 0xff
            print('\t  crc with XOR 0111001 {:#x}\t{:b}'.format(crc,crc))
        else:
            crc = (crc << 1)  & 0xff
            print('\t  crc shift <<1        {:#x}\t{:08b}'.format(crc,crc))
    return crc

data = [0x12,0x34]

print('\nstart\n')
CRC = crc8_update(data)
print('\ncrc is {:#x}'.format(CRC))

 C言語のテストはArduinoで行いました。

void setup() {
  Serial.begin(115200);
  while (!Serial) delay(10); 

}


uint8_t CalcCrc(uint8_t *data, uint8_t size) {
    uint8_t crc = 0xFF;
    for(int i = 0; i < 2; i++) {
        Serial.println("\n 0xffでXOR");
        Serial.print(data[i],HEX);;Serial.print("\t\t");Serial.println(data[i],BIN);
        Serial.print("crc=  0x");
        Serial.print(crc,HEX);;Serial.print("\t");Serial.println(crc,BIN);
        crc ^= data[i];        
        Serial.print("i= "); 
        Serial.println(i);

        for(uint8_t bit = 8; bit > 0; --bit) {
            Serial.print("\tbit= "); Serial.println(bit);
            if(crc & 0x80) {
                Serial.println("\t最上位ビットが1なので、左に1シフトし、0b0111001とXORする");
                crc = (crc << 1) ^ 0x31u;
                Serial.print("\t\tcrc= 0x"); Serial.print(crc,HEX);Serial.print("\t");Serial.println(crc,BIN);
            } else {
                Serial.println("\t\t最上位ビットが1でないので、左に1シフトするだけ");
                crc = (crc << 1);
                Serial.print("\t\t else crc= 0x"); Serial.print(crc,HEX);Serial.print("\t");Serial.println(crc,BIN);
            }
        }
    }
    return crc;
}

void loop() {
Serial.println("");
uint8_t data[] = {0xBE, 0xEF};
Serial.println(data[0],data[1]);
Serial.println("\nstart\n");
uint8_t crc = CalcCrc(data,2);
Serial.println("");
Serial.print("\n last crc= 0x"); Serial.print(crc,HEX);;Serial.print("\t");Serial.println(crc,BIN);

delay(1500000);
}

-