PGA付きADC【MSX8725C】をRaspberryPiPicoで使えるようにした

2023/06/10　たまに読み取りの値がおかしくなるバグを修正した。デフォルトではVrefにVbattを使用する設定になっており、Vref設定する関数でVbgとしたときにそこを書き直すのを忘れていた。とりあえずそのぶぶんを追加。


マルツで売ってる測定レンジ0%-100%の酸素センサーを使ってみたところ、付属の基板だとゼロ校正とスパン校正(20.9%,99.5%)がちゃんとできてるのかよくわからないとか読み取りの分解能が低いとか物足りない部分があったのでゲインアンプ付きADコンバータを別に用意して読み取ることにした。付属の基板をArduinoで動かす公式のサンプルコードを参考にしてRaspberryPiPicoを使ってMicroPythonで動かすコードも書いたけど今回は省略。

使ったセンサーはこちら
GRAVITY: ELECTROCHEMICAL OXYGEN【SEN0496】https://www.marutsu.co.jp/pc/i/42472321/
メーカーページは以下
https://wiki.dfrobot.com/SKU_SEN0496_Gravity_Electrochemical_Oxygen_Sensor_0_100_I2C
酸素センサーのデータシートはこちら
https://dfimg.dfrobot.com/nobody/wiki/c5b6e77ef61f18172ffda4a2e4ebec19.pdf
大気条件(酸素濃度20.09%)で9-13 mVの電圧を発生するらしい。普通こういうセンサーは直線的な出力特性をもつし100%酸素ならその5倍くらいの電圧になるのかな？
測定レンジを0%-100％にするときは70mVくらいまで測れるように設定すれば大丈夫かな？

ちょっとした実験に使ってみたところ分解能がいまいちだったので適当なADコンバータで直接読み取ることにした。
ADCはマルツで売ってる
A/Dコンバーターモジュール(レベルシフト回路内蔵)【MSX8725C】
https://www.marutsu.co.jp/pc/i/243036/
これを使うことにした。

コードの作成にあたって以下の作例を参考にさせて頂きました。
https://www.denshi.club/pc/raspi/5raspberry-pi-zeroiot10a-d7ltc2450-2.html
https://qiita.com/pirotan628/items/9ca181ee34e998c7dd24


RapberryPiPicoWで動かすために以下のコードを書いた
参照電圧やゲインを変更したら自動で読み取り値に反映されてほしいよねという気持ちでつくった。
~~ただしたまに値が2/5程度になってしまうバグがでるけど原因がまだ特定できていないのとりあえず動いてるのでヨ シ！~~（2023/06/10　Vmuxの設定を忘れていたのが原因なようで追加しました。）
オフセット値の設定もできるけど今回は使わないので省略
素人なので分かり易さ重視

```python:PiPicoW_SX8725C.py
import machine,re,math,utime

class SX8725C:
    def __init__(self, i2c_addr=0x48, i2c_id=0, sda_pin=0, scl_pin=1, freq=100000):
        self.id = i2c_id
        self.sda = sda_pin
        self.scl = scl_pin
        self.i2c = machine.I2C(id=i2c_id, sda=machine.Pin(sda_pin), scl=machine.Pin(scl_pin), freq=freq)
        self.addr = i2c_addr
        self.reg_map = [0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x70]
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        self.pga1_gain = 1
        self.pga2_gain = 1
        self.pga3_gain = 1
        self.vref = 1.22

    def load_reg_params(self):
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        dict_str = {hex(reg):'{:08b}'.format(value[0]) for reg, value in sx8725c.reg_params.items()}
        return dict_str
    
    @staticmethod
    def print_8bit(value_bytes):# エラーが出たときの確認用
        value_bytes = int.from_bytes(value, "big")
        print("raw: ",value, type(value))
        value = '{:08b}'.format(value)
        print("8bit: ",value, type(value))
    
    @staticmethod
    def set_bit(byte_value, bit_position, bit_value):#e.g. self.set_bit(RegACCfg1, 0, 1)
        byte_list = bytearray(byte_value)
        if bit_value:
            byte_list[0] |= (1 << bit_position)
        else:
            byte_list[0] &= ~(1 << bit_position)
        return bytes(byte_list)
    
    @staticmethod
    def set_bit_range(byte_value, start_bit, end_bit, new_value):#e.g. self.set_bit_range(RegACCfg5,1,5,0b10001)
        mask = ((1 << (end_bit - start_bit + 1)) - 1) << start_bit
        new_value &= ((1 << (end_bit - start_bit + 1)) - 1)
        return bytes([(byte_value[0] & ~mask) | (new_value << start_bit)])
    
    def enable_adc_pga(self, adc="on", pga1="off", pga2="off", pga3="off"):
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        RegACCfg1 = self.reg_params[0x53]

        if adc == "on":
            RegACCfg1 = self.set_bit(RegACCfg1, 0, 1)
        elif adc == "off":
            RegACCfg1 = self.set_bit(RegACCfg1, 0, 0)
            
        if pga1 == "on":
            RegACCfg1 = self.set_bit(RegACCfg1, 1, 1)
        elif pga1 == "off":
            RegACCfg1 = self.set_bit(RegACCfg1, 1, 0)
            
        if pga2 == "on":
            RegACCfg1 = self.set_bit(RegACCfg1, 2, 1)
        elif pga2 == "off":
            RegACCfg1 = self.set_bit(RegACCfg1, 2, 0)
            
        if pga3 == "on":
            RegACCfg1 = self.set_bit(RegACCfg1, 3, 1)
        elif pga3 == "off":
            RegACCfg1 = self.set_bit(RegACCfg1, 3, 0)

        self.i2c.writeto_mem(self.addr, 0x53, RegACCfg1)
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
    
    def set_pga_gain(self,pga1=1,pga2=1,pga3="12/12"):
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        RegACCfg2 = self.reg_params[0x54]
        RegACCfg3 = self.reg_params[0x55]
        
        if pga1 == 1:
            RegACCfg3 = self.set_bit(RegACCfg3, 7, 0)
            self.pga1_gain = 1
        elif pga1 == 10:
            RegACCfg3 = self.set_bit(RegACCfg3, 7, 1)
            self.pga1_gain = 10
        
        if pga2 == 1:
            RegACCfg2 = self.set_bit_range(RegACCfg2,4,5,0b00)
            self.pga2_gain = 1
        elif pga2 == 2:
            RegACCfg2 = self.set_bit_range(RegACCfg2,4,5,0b01)
            self.pga2_gain = 2
        elif pga2 == 5:
            RegACCfg2 = self.set_bit_range(RegACCfg2,4,5,0b10)
            self.pga2_gain = 5
        elif pga2 == 10:
            RegACCfg2 = self.set_bit_range(RegACCfg2,4,5,0b11)
            self.pga2_gain = 10
        
        gain3 = re.match('([0-9]+)/12',pga3)
        if gain3:
            reg = int(gain3.group(1))
            if 0 <= reg <= 32:
                RegACCfg3 = self.set_bit_range(RegACCfg3,0,6,reg)
                self.pga3_gain = reg/12
        
        self.i2c.writeto_mem(self.addr, 0x54, RegACCfg2)
        self.i2c.writeto_mem(self.addr, 0x55, RegACCfg3)
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
    
    def set_analog_input(self,VinP="AC3",VinN="AC2"):
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        RegACCfg5 = self.reg_params[0x57]
        
        if VinP == "AC3":
            if VinN == "AC2":
                RegACCfg5 = self.set_bit_range(RegACCfg5,1,5,0b00001)
            elif VinN == "AC0":
                RegACCfg5 = self.set_bit_range(RegACCfg5,1,5,0b10011)
        elif VinP == "AC2":
            if VinN == "AC3":
                RegACCfg5 = self.set_bit_range(RegACCfg5,1,5,0b01001)
            elif VinP == "AC0":
                RegACCfg5 = self.set_bit_range(RegACCfg5,1,5,0b10010)
        elif VinP == "AC1":
            if VinN == "AC0":
                RegACCfg5 = self.set_bit_range(RegACCfg5,1,5,0b10001)
        
        self.i2c.writeto_mem(self.addr, 0x57, RegACCfg5)
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
    
    def set_sampling_params(self,fs=62.5,osr=32,nelconv=2):
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        RegACCfg0 = self.reg_params[0x52]
        RegACCfg2 = self.reg_params[0x54]
        
        if fs == 62.5:
            RegACCfg2 = self.set_bit_range(RegACCfg2,6,7,0b00)
        elif fs == 125:
            RegACCfg2 = self.set_bit_range(RegACCfg2,6,7,0b01)
        elif fs == 250:
            RegACCfg2 = self.set_bit_range(RegACCfg2,6,7,0b10)
        elif fs == 500:
            RegACCfg2 = self.set_bit_range(RegACCfg2,6,7,0b11)
        
        if isinstance(osr, int) and 0 < osr and (osr & (osr - 1)) == 0:
            if 8 <= osr <= 1024:
                reg = int(math.log2(osr))-3
                RegACCfg0 = self.set_bit_range(RegACCfg0,2,4,reg)
        
        if isinstance(nelconv, int) and 0 < nelconv and (nelconv & (nelconv - 1)) == 0:
            if 1 <= nelconv <= 8:
                reg = int(math.log2(nelconv))
                RegACCfg0 = self.set_bit_range(RegACCfg0,5,6,reg)
        
        self.i2c.writeto_mem(self.addr, 0x52, RegACCfg0)
        self.i2c.writeto_mem(self.addr, 0x54, RegACCfg2)
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        
    def set_reg_mode(self,chopper="state1",MultForceOn="off",MultForceOff="off"):
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        RegMode = self.reg_params[0x70]
        
        if chopper == "state0":
            RegMode = self.set_bit_range(RegMode,4,5,0b00)
        elif chopper == "state1":
            RegMode = self.set_bit_range(RegMode,4,5,0b01)
        elif chopper == "Nelconv":
            RegMode = self.set_bit_range(RegMode,4,5,0b10)
        elif chopper == "Nelconv/2":
            RegMode = self.set_bit_range(RegMode,4,5,0b11)
        
        if MultForceOn == "off":
            RegMode = self.set_bit(RegMode,3,0)
        elif MultForceOn == "on":
            RegMode = self.set_bit(RegMode,3,1)
        
        if MultForceOff == "off":
            RegMode = self.set_bit(RegMode,2,0)
        elif MultForceOff == "on":
            RegMode = self.set_bit(RegMode,2,1)
        
        self.i2c.writeto_mem(self.addr, 0x70, RegMode)
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
    
    def set_vref(self,Vmux="Vbgd1",VrefD0Out="off",VrefD1In="off",Vref=1.22):
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        RegACCfg5 = self.reg_params[0x57]
        RegMode = self.reg_params[0x70]
        
        if Vmux == "Vbgd1":
            RegACCfg5 = self.set_bit(RegACCfg5,0,1)
        elif Vmux == "Vbatt":
            RegACCfg5 = self.set_bit(RegACCfg5,0,0)
        if VrefD0Out == "off":
            RegMode = self.set_bit(RegMode,1,0)
        elif VrefD0Out == "on":
            RegMode = self.set_bit(RegMode,1,1)
        if VrefD1In == "off":
            RegMode = self.set_bit(RegMode,0,0)
        elif VrefD1In == "on":
            RegMode = self.set_bit(RegMode,0,1)
        
        self.i2c.writeto_mem(self.addr, 0x57, RegACCfg5)
        self.i2c.writeto_mem(self.addr, 0x70, RegMode)
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        self.vref = Vref
    
    def set_start_cont(self,start=1,cont=1):
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
        RegACCfg0 = self.reg_params[0x52]
        
        if start == 0:
            RegACCfg0 = self.set_bit(RegACCfg0,7,0)
        elif start == 1:
            RegACCfg0 = self.set_bit(RegACCfg0,7,1)
        if cont == 0:
            RegACCfg0 = self.set_bit(RegACCfg0,1,0)
        elif cont == 1:
            RegACCfg0 = self.set_bit(RegACCfg0,1,1)
        
        self.i2c.writeto_mem(self.addr, 0x52, RegACCfg0)
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
    
    def reset_params(self):
        self.i2c.writeto_mem(self.addr, 0x52, 0b01000000)
        self.reg_params = {reg: self.i2c.readfrom_mem(self.addr, reg, 1) for reg in self.reg_map}
    
    def read_raw(self):
        lsb = self.i2c.readfrom_mem(self.addr,0x50,1)
        msb = self.i2c.readfrom_mem(self.addr,0x51,1)
        x = (msb[0]<<8)|lsb[0]
        x = (-(x&32768)|(x&32767))
        return x
    
    def read(self):
        val = self.read_raw()
        val = val * 1000 * (self.vref/65535) / (self.pga1_gain*self.pga2_gain*self.pga3_gain)
        return val
```

モジュールここまで。もっとスマートにかければいいんだけど完璧よりも前進を優先してとりあえず動けばヨシ！
PiPicoW_SX8725C.pyとtest.pyを同じディレクトリに置いて


```python:test.py
from PiPicoW_SX8725C import SC8724C
# SX8725Cのインスタンス化
sx8725c = SX8725C(i2c_id=1, sda_pin=14, scl_pin=15)
print("Register parameters:")
[print(hex(reg), ":", '{:08b}'.format(value[0])) for reg, value in sx8725c.reg_params.items()]
print("_______________________________________________________________")

# 各種設定
sx8725c.enable_adc_pga(adc="on", pga1="on", pga2="on", pga3="off")
sx8725c.set_analog_input(VinP="AC3",VinN="AC2")
sx8725c.set_pga_gain(pga1=10,pga2=5,pga3="12/12")
sx8725c.set_sampling_params(fs=500,osr=1024,nelconv=8)
sx8725c.set_reg_mode(chopper="Nelconv",MultForceOn="off",MultForceOff="off")
sx8725c.set_vref(Vmux="Vbgd1",VrefD0Out="off",VrefD1In="off",Vref=1.22)
sx8725c.set_start_cont()
print("Register parameters:")
[print(hex(reg), ":", '{:08b}'.format(value[0])) for reg, value in sx8725c.reg_params.items()]
print("_______________________________________________________________")

try:
    while True:
        val = sx8725c.read()
        print(val)
        utime.sleep(1)
except KeyboardInterrupt:
    pass
```
これでレジスタの設定値を表示した後に測定が開始されるはず。
今回はA3-A2の差を測定する。測定環境は大気条件(酸素濃度20.9％)なので10mV前後の値が出力される。