初めてのRaspberry Pi Pico ㉑ C言語 12ビットADC MCP3208


 Picoには12ビット4チャネルのA-Dコンバータが内蔵されています。もし8チャネル分足りなかったら、という想定で外部にICを追加します。

MCP3208のおもなスペック

  • ビット数 12
  • チャネル数 8(シングルエンド)、4(疑似差動)
  • 基準電圧 内蔵なし、端子あり
  • 変換速度 100ksps(5V時)
  • インターフェース SPI(モード0,0および1,1)、クロック1.6MHz(5V時)、0.8MHz(2.7V時)
  • 動作電圧 2.7~5.5V
  • ピン数 16ピンDIP

接続

MCP3208の端子 Picoの端子(GPIO) 名称
1 ch0 - -
2 ch1 - -
3 ch2 - -
4 ch3 - -
5 ch4 - -
6 ch5 - -
7 ch6 - -
8 ch7 - -
9 DGND GND GND
10 /CS GP5 SPI0 CSn
11 Din GP3 MOSI SPI0 Tx
12 Dout GP4 MISO SPI0 RX
13 CLK GP2 SPI0 SCK
14 AGND GND GND
15 Vref 3V3 3.3V
16 Vdd 3V3 3.3V

 入力のch0には、電池駆動の簡易電源TL431の出力をつないでいます。約2.5Vです。ch1~ch7はGNDもしくは3.3Vにつなぎ、結果をみます。

プログラムmcp3208.c

 フォルダ名はmcp3208としました。CMakeLists.txtは省略します。17回以前を参考にしてください。

 MCP3208データシート

 入力チャネルの指定は、表の左の4ビットで行います。

Single/Diff D2 D1 D0 入力のタイプ チャネル
1 0 0 0 シングルエンド ch0
1 0 0 1 シングルエンド ch1
1 0 1 0 シングルエンド ch2
1 0 1 1 シングルエンド ch3
1 1 0 0 シングルエンド ch4
1 1 0 1 シングルエンド ch5
1 1 1 0 シングルエンド ch6
1 1 1 1 シングルエンド ch7
0 0 0 0 疑似差動 ch0=IN+、ch1=IN-
0 0 0 1 疑似差動 ch0=IN-、ch1=IN+
0 0 1 0 疑似差動 ch2=IN+、ch3=IN-
0 0 1 1 疑似差動 ch2=IN-、ch3=IN+
0 1 0 0 疑似差動 ch4=IN+、ch5=IN-
0 1 0 1 疑似差動 ch4=IN-、ch5=IN+
0 1 1 0 疑似差動 ch6=IN+、ch7=IN-
0 1 1 1 疑似差動 ch6=IN-、ch7=IN+

 コマンドを送るフォマットは次のとおりです。
  Startbit チャネル指定4バイト
 例えば、シングルエンドch0であれば、
  1 1000
 シングルエンドch1であれば、
  1 1001
です。最後のチャネル指定ビットD0がMCP3208へ送られるタイミングで、約1ビットおいて、NULLビットに引き続き、
  D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
のA-D変換結果データが、MCP3208からPicoへ送られてきます。
 SPIは基本8ビット単位なので、LSBのD0から前にさかのぼって、8ビット単位で送るべきデータを整理します。シングルエンドch0の場合です。
  x x x x x x 1 1 0 0 0 x x x x x x x x x x x x x x
 SPIはクロックを送り続けないとデータはやってきません。上記のxはダミーなので、0でも1でもかまいません。ただし、最初の5ビットのいずれかを'1'にすると、Startbitと解釈されるかもしれないので、'0'にします。'0'でもクロックは発生します。したがって、ここでは、ch0のデータは、次のようにしました。
  0b00000110 0x00 0xff

 本プログラムは、疑似差動入力に対応していません。

 3バイト送ったので、読み出したデータは3バイトです。1バイト目はごみなので捨てます。2バイト目は上位桁D11 D10 D9 D8が右詰めで入っています。3バイト目は、D7 D6 D5 D4 D3 D2 D1 D0です。
 12ビットのデータなので、Vrefの3.3Vをかけ、4096で割って電圧を求めます。Vrefは、できるだけ正確にテスタで測った値を記入しておきます。

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

#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"

#define PIN_MISO 4
#define PIN_CS   5
#define PIN_SCK  2
#define PIN_MOSI 3

#define SPI_PORT spi0
static float Vref = 3.30;


static inline void cs_select() {
    asm volatile("nop \n nop \n nop");
    gpio_put(PIN_CS, 0);  // Active low
    asm volatile("nop \n nop \n nop");
}

static inline void cs_deselect() {
    asm volatile("nop \n nop \n nop");
    gpio_put(PIN_CS, 1);
    asm volatile("nop \n nop \n nop");
}

void setup_SPI(){
    // This example will use SPI0 at 0.5MHz.
    spi_init(SPI_PORT, 500 * 1000);
    gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
    gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
    gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);

    // Chip select is active-low, so we'll initialise it to a driven-high state
    gpio_init(PIN_CS);
    gpio_set_dir(PIN_CS, GPIO_OUT);
    gpio_put(PIN_CS, 1);
}

int readADC(uint8_t ch){
    uint8_t writeData[] = {0b00000110, 0x00, 0xff};
    switch(ch){
      case 0:
        writeData[0] = 0b00000110;
        writeData[1] = 0b00000000; 
      break;
      case 1:
        writeData[0] = 0b00000110;
        writeData[1] = 0b01000000; 
      break;
      case 2:
        writeData[0] = 0b00000110;
        writeData[1] = 0b10000000; 
      break;
      case 3:
        writeData[0] = 0b00000110;
        writeData[1] = 0b11000000; 
      break;
      case 4:
        writeData[0] = 0b00000111;
        writeData[1] = 0b00000000; 
      break;
      case 5:
        writeData[0] = 0b00000111;
        writeData[1] = 0b01000000; 
      break;
      case 6:
        writeData[0] = 0b00000111;
        writeData[1] = 0b10000000; 
      break;
      case 7:
        writeData[0] = 0b00000111;
        writeData[1] = 0b11000000; 
    }
    // printf("\n %0b %0b %0b\n",writeData[0],writeData[1],writeData[2]);
    uint8_t buffer[3];
    cs_select();
    sleep_ms(1);
    spi_write_read_blocking(SPI_PORT, writeData, buffer, 3);
    sleep_ms(1);
    cs_deselect();

    return (buffer[1] & 0x0f) << 8 | buffer[2];
}

int main() {
    stdio_init_all();

    printf("\nHello, MCP3208 Reading raw data from registers via SPI...\n");

    setup_SPI();

    for (uint8_t i=0; i<8; i++){
        printf("ch%d is %.4fV\n", i, Vref * readADC(i) / 4096);
    }
    return 0;
}

 実行結果です。ch0はTL431出力、ch1はGND、ch2は3.3V、ch3はGND、ch4は3.3V、ch5はGND、ch6は3.3V、ch7はGNDにつないでいます。