初めてのRaspberry Pi Pico ㉕ C言語 16ビットADC ADS1115


 Picoには12ビット4チャネルのA-Dコンバータが内蔵されています。ENOB(effective number of bits:有効ビット数)が9.5なので、もう少し高いビット数がほしいという想定で外部にICを追加します。

ADS1115のおもなスペック

  • ビット数 16
  • チャネル数 4(シングルエンド)、2(差動)
  • 基準電圧 内蔵
  • 電源電圧 2.7~5.5V
  • 入力 差動(A0-A1、A0-A3、A1-A3、A2-A3)、シングル(A0、A1、A2、A3とGND)
  • 変換方式 ΔΣ型
  • データ・レート 128SPS以下で16ビット
  • インターフェース I2C
  • I2C転送速度 最大3.4MHz
  • スレーブ・アドレス デフォルトは0x48
  • プログラマブル・ゲイン・アンプ フルスケール;±6.144V、±4.096V、±2.048V、±1.024V、±0.512V、±0.256V

接続

 ボード化されたADS1115は、Adafruit製の旧製品で、スイッチサイエンスで購入しました。

ADS1115ボードの端子 Picoの端子(GPIO) 名称
Vcc 3.3V 3.3V
GND GND GND
SCL GP9 SCL
SDA GP8 SDA
A0 - -
A1 - -
A2 - -
A3 - -

 プルアップ抵抗10kΩがSDA/SCLに入っていましたが取りました。
 ADS1115の入力には、直流標準電圧電流発生器TR6142で電圧を入力しています。1.0000だと小数点第4位まで正確です。

プログラム

 フォルダ名はads1115としました。cmake makeの手順は省略します。17回以前を参考にしてください。プログラムは、ads1115.hとads1115.cに分けました。

  ADS1115データシート

CMakeLists.txt
add_executable(ads1115
        ads1115.c
        )

# Pull in our (to be renamed) simple get you started dependencies
target_link_libraries(ads1115 pico_stdlib hardware_i2c)

# create map/bin/hex file etc.
pico_add_extra_outputs(ads1115)

設定レジスタ デフォルト b10001100

 設定用ポインタ・レジスタのアドレスは0x01です。ads1115.hに記述しました。

  • bit1 OS。'1'で変換開始。デフォルト1
  • bit14、bi13、bit12 チャネル・セレクト。
  • - 000 : AINP = AIN0 and AINN = AIN1 (デフォルト)
  • - 001 : AINP = AIN0 and AINN = AIN3
  • - 010 : AINP = AIN1 and AINN = AIN3
  • - 011 : AINP = AIN2 and AINN = AIN3
  • - 100 : AINP = AIN0 and AINN = GND
  • - 101 : AINP = AIN1 and AINN = GND
  • - 110 : AINP = AIN2 and AINN = GND
  • - 111 : AINP = AIN3 and AINN = GND
  • bit11、bit10、bit9 PGA。
  • - 000 : FSR = ±6.144 V
  • - 001 : FSR = ±4.096 V
  • - 010 : FSR = ±2.048 V (デフォルト)
  • - 011 : FSR = ±1.024 V
  • - 100 : FSR = ±0.512 V
  • - 101 : FSR = ±0.256 V
  • - 110 : FSR = ±0.256 V
  • - 111 : FSR = ±0.256 V
  • bit8 MODE。1;連続、0;ワンショット(デフォルト)
  • bit7、bit6、bit5 サンプル・レート。
  • - 000 : 8 SPS
  • - 001 : 16 SPS
  • - 010 : 32 SPS
  • - 011 : 64 SPS
  • - 100 : 128 SPS (デフォルト)
  • - 101 : 250 SPS
  • - 110 : 475 SPS
  • - 111 : 860 SPS
  • bit4 COMP_MODE;コンパレータ。省略
  • bit3 COMP_POL ;コンパレータ。省略
  • bit2 COMP_LAT ;コンパレータ。省略
  • bit1、bit0 COMP_QUE[1:0]  ;コンパレータ。省略(デフォルト3h ディセーブル)

例;電源を入れた直後のデフォルト設定値。 変換スタート、差動A0-A1チャネル、フルスケール±2.048V、ワンショット、128SPS、コンパレータは無効;1000010110000011h

ads1115.h
//ads1115 Config Register reset = 8583h

/// input
#define D1 0x0000 // Differential  : AINP = AIN0 and AINN = AIN1 (default)
// #define MUX001 0x1000 // not use  : AINP = AIN0 and AINN = AIN3
// #define MUX010 0x2000 // not use  : AINP = AIN1 and AINN = AIN3
#define D2 0x3000 // Differential  : AINP = AIN2 and AINN = AIN3
#define S1 0x4000 // Single-ended: AINP = AIN0 and AINN = GND
#define S2 0x5000 // Single-ended: AINP = AIN1 and AINN = GND
#define S3 0x6000 // Single-ended: AINP = AIN2 and AINN = GND
#define S4 0x7000 // Single-ended: AINP = AIN3 and AINN = GND

/// Gain
#define Gain0 0x0000   // 000 : FSR = ±6.144 V
#define Gain1 0x0200   // 001 : FSR = ±4.096 V
#define Gain2 0x0400   // 010 : FSR = ±2.048 V (default)
#define Gain4 0x0600   // 011 : FSR = ±1.024 V
#define Gain8 0x0800   // 100 : FSR = ±0.512 V
#define Gain16 0x0a00  // 101 110 111: FSR = ±0.256 V

/// Data Rate
#define DR8 0x0000   // 000 : 8 SPS
#define DR16 0x0020  // 001 : 16 SPS
#define DR32 0x0040  // 010 : 32 SPS
#define DR64 0x0060  // 011 : 64 SPS
#define DR128 0x0080 // 100 : 128 SPS (default)
#define DR250 0x00a0 // 101 : 250 SPS
#define DR475 0x00c0 // 110 : 475 SPS
#define DR860 0x00e0 // 111 : 860 SPS

プログラムads1115.c

 読み出すポインタ・レジスタのアドレスは0x00です。
 読み出したデータは2バイトで、0から32767までのデータです。2の補数形式で、D15は符号を表します。
  D15 D14 D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0

 I2Cの転送速度は100kHzにしました。

ads1115.c
/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "ads1115.h"

#define I2C_PORT  i2c0
static int addr = 0x48;
static uint16_t config = 0x8583;
#define configPointer 0x01
#define conversionPointer 0x00
#define READ_BIT 0x80

void setup_I2C(){
    // This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 100kHz.
    i2c_init(I2C_PORT, 100 * 1000);
    gpio_set_function(8, GPIO_FUNC_I2C);
    gpio_set_function(9, GPIO_FUNC_I2C);
    gpio_pull_up(8);
    gpio_pull_up(9);
}

static void I2C_read_registers(uint8_t reg, uint8_t *buf, uint16_t len) {
    // For this particular device, we send the device the register we want to read
    // first, then subsequently read from the device. The register is auto incrementing
    // so we don't need to keep sending the register we want, just the first.
    reg = reg | READ_BIT;
    i2c_write_blocking(I2C_PORT, addr, &reg, 1, true); 
    i2c_read_blocking(I2C_PORT, addr, buf, len, false);
}

static void I2C_write_register16(uint8_t reg, uint16_t data) {
    uint8_t buf[3];
    buf[0] = reg;
    buf[1] = (uint8_t)(data >> 8);
    buf[2] = (uint8_t)(data & 0xff);
    i2c_write_blocking(I2C_PORT, addr, buf, 3, true);
}

float readADC(uint16_t channel, uint16_t PGAgain, uint16_t DataRate){
    config = config | channel | PGAgain | DataRate;
    I2C_write_register16(configPointer, config);

    uint8_t buffer[2];
    I2C_read_registers(conversionPointer, buffer, 2);
    uint16_t temp = (buffer[0]<<8) | buffer[1];
    int16_t tempSign = -(temp & 0b1000000000000000) | (temp & 0b0111111111111111);
    float V = tempSign / 32767.f;

    return V;
}

int main() {
    stdio_init_all();
    printf("\nstart ADS1115\n");
    setup_I2C();

    printf("\n==S1=0x0200=DR8 input: %.5fV\n", readADC(S4, Gain1, DR8));
    printf("\n==S1=0x0200=DR8 input: %.5fV\n", readADC(S4, Gain2, DR8));
    printf("\n==S1=0x0200=DR8 input: %.5fV\n", readADC(S4, Gain4, DR8));
    printf("\n==S1=0x0200=DR8 input: %.5fV\n", readADC(S4, Gain2, DR128));
    printf("\n==S1=0x0200=DR8 input: %.5fV\n", readADC(S4, Gain4, DR128));
/*
    printf("\n==G1=0x0200=DR8 input: %.5fV\n", readADC(D1, Gain1, DR8));

    printf("-G1-DR16-- input: %.5fV\n", readADC(D1, Gain1, DR16));
    printf("-G1-DR32-- input: %.5fV\n", readADC(D1, Gain1, DR32));
    printf("i-G1-DR64--input: %.5fV\n", readADC(D1, Gain1, DR64));
    printf("-G1-DR128--input: %.5fV\n", readADC(D1, Gain1, DR128));
    printf("-G1-DR250--input: %.5fV\n", readADC(D1, Gain1, DR250));
    printf("-G1-DR475--input: %.5fV\n", readADC(D1, Gain1, DR475));
    printf("-G1-DR860--input: %.5fV\n", readADC(D1, Gain1, DR860));

    printf("==G2=0x0400=DR8 input: %.5fV\n", readADC(D1, Gain2, DR8));
    printf("-G2-DR16-- input: %.5fV\n", readADC(D1, Gain2, DR16));
    printf("-G2-DR32-- input: %.5fV\n", readADC(D1, Gain2, DR32));
    printf("-G2-DR64-- input: %.5fV\n", readADC(D1, Gain2, DR64));
    printf("-G2-DR128--input: %.5fV\n", readADC(D1, Gain2, DR128));
    printf("-G2-DR250--input: %.5fV\n", readADC(D1, Gain2, DR250));
    printf("-G2-DR475--input: %.5fV\n", readADC(D1, Gain2, DR475));
    printf("-G2-DR860--input: %.5fV\n", readADC(D1, Gain2, DR860));

    printf("==G4=0x0600=DR8 input: %.5fV\n", readADC(D1, Gain4, DR8));
    printf("-G4-DR16-- input: %.5fV\n", readADC(D1, Gain4, DR16));
    printf("-G4-DR32-- input: %.5fV\n", readADC(D1, Gain4, DR32));
    printf("-G4-DR64-- input: %.5fV\n", readADC(D1, Gain4, DR64));
    printf("-G4-DR128--input: %.5fV\n", readADC(D1, Gain4, DR128));
    printf("-G4-DR250--input: %.5fV\n", readADC(D1, Gain4, DR250));
    printf("-G4-DR475--input: %.5fV\n", readADC(D1, Gain4, DR475));
    printf("-G4-DR860--input: %.5fV\n", readADC(D1, Gain4, DR860));
*/

    return 0;
}

 mainのprinf文はテスト時の残骸です。
 フルスケール±6.144Vと±4.096Vのレンジは電源電圧3.3Vを超えて入力できません。16ビットの確度は、多くがDR128以下のデータレートで得られます。