Picoには12ビット4チャネルのA-Dコンバータが内蔵されています。もし16チャネル分足りなかったら、という想定で外部に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を2個使って16チャネルにします。PicoのSPIバスとは共通の配線です。ただし、2個目のチップ・セレクト信号のCSは別途GP6に配線します。1個目のチップ・セレクト信号のCSはGP5に配線します。
1個目のMCP3208の端子 | 2個目のMCP3208の端子 | Picoの端子(GPIO) | 名称 |
---|---|---|---|
1 ch0 | 1 ch0 | - | - |
2 ch1 | 2 ch1 | - | - |
3 ch2 | 3 ch2 | - | - |
4 ch3 | 4 ch3 | - | - |
5 ch4 | 5 ch4 | - | - |
6 ch5 | 6 ch5 | - | - |
7 ch6 | 7 ch6 | - | - |
8 ch7 | 8 ch7 | - | - |
9 DGND | 9 DGND | GND | GND |
10 /CS | 10 /CS | GP5/GP6 | SPI0 CSn/SPI0 SCK |
11 Din | 11 Din | GP3 | MOSI SPI0 Tx |
12 Dout | 12 Dout | GP4 | MISO SPI0 RX |
13 CLK | 13 CLK | GP2 | SPI0 SCK |
14 AGND | 14 AGND | GND | GND |
15 Vref | 15 Vref | 3V3 | 3.3V |
16 Vdd | 16 Vdd | 3V3 | 3.3V |
1個目のMCP3208の入力のch0には、電池駆動の簡易電源TL431の出力をつないでいます。約2.5Vです。ch1~ch6は無接続、ch7はGNDにつなぎ、2個目のMCP3208の入力のch0には電源ラインをつないでいます。約3.3Vです。ch1~ch6は無接続、ch7はGNDにつなぎ、結果をみます。 |
プログラムmcp3208x2.c
フォルダ名はmcp3208x2としました。CMakeLists.txtは省略します。17回以前を参考にしてください。
入力チャネルの指定は、表の左の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は、できるだけ正確にテスタで測った値を記入しておきます。
前回のreadADC()関数の引数に、チップ・セレクト信号CSを加えました。0は1個目、0以外は2個目です。
/**
* 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_CS2 6
# 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");
}
static inline void cs2_select() {
asm volatile("nop \n nop \n nop");
gpio_put(PIN_CS2, 0); // Active low
asm volatile("nop \n nop \n nop");
}
static inline void cs2_deselect() {
asm volatile("nop \n nop \n nop");
gpio_put(PIN_CS2, 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);
gpio_init(PIN_CS2);
gpio_set_dir(PIN_CS2, GPIO_OUT);
gpio_put(PIN_CS2, 1);
}
int readADC(uint8_t ch, uint8_t CS){ // cs 0 or 2
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 == 0 ? cs_select() : cs2_select();
sleep_ms(1);
spi_write_read_blocking(SPI_PORT, writeData, buffer, 3);
sleep_ms(1);
CS == 0 ? cs_deselect() : cs2_deselect();
return (buffer[1] & 0x0f) << 8 | buffer[2];
}
int main() {
stdio_init_all();
printf("\nHello, MCP3208x2 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, 0) / 4096);
}
printf("-----\n");
for (uint8_t i=0; i<8; i++){
printf("ch%d is %.4fV\n", i, Vref * readADC(i, 2) / 4096);
}
return 0;
}
実行結果です。1個目のch0はTL431出力、ch7はGNDに、2個目のch0は3.3V、ch7はGNDにつないでいます。どちらもch1~ch6は何もつないでいないので不定です。