TOPに戻る

Raspberry Pi Picoでプログラミング ⑭ spi A-DコンバータMCP3208

 前回、10ビットA-DコンバータMCP3008を利用しました。ここでは12ビットA-DコンバータMCP3208を使います。ビット数が増えると、より細かくアナログ電圧を扱えます。言い換えれば、量子化誤差が小さくなります。

A-DコンバータMCP3208

 Picoには12ビットA-Dコンバータが内蔵されています。ここでは、12ビットA-DコンバータMCP3208を外付けして、同じ電圧を読み込んで比較します。

MCP3208のおもなスペック

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

 基準電圧Vref入力、アナログ・グラウンドAGNDとディジタル・グラウンドDGNDが独立した端子で用意されています。MCP3008とピンの並びは同じです。

接続

ソースの作成

 pico/worksフォルダにあるmcp3008フォルダをコピーしてmcp3208フォルダを作ります。

 pico/worksフォルダのCMakeLists.txtの内容です。


cmake_minimum_required(VERSION 3.12)

# Pull in SDK (must be before project)
include(pico_sdk_import.cmake)

project(pico_examples C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR})

# Initialize the SDK
pico_sdk_init()

# Add blink example
add_subdirectory(cmake)
add_subdirectory(blink)
add_subdirectory(serial)
add_subdirectory(clock)
add_subdirectory(i2cscanner)
add_subdirectory(lps25hb)
add_subdirectory(tmp117)
add_subdirectory(aht20)
add_subdirectory(mcp3008) add_subdirectory(mcp3208)

 pico/works/mcp3208フォルダのCMakeLists.txtの内容です。


add_executable(mcp3208
        mcp3208.c
        )

# Pull in our pico_stdlib which pulls in commonly used features
target_link_libraries(mcp3208 pico_stdlib 
        hardware_spi hardware_gpio hardware_adc)

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

 mcp3208.cの内容です。Vrefは岩通VOAC7602の読み取り値です。

 チャネルのデータの作り方は、

  A-Dコンバータ その4 12ビットSPI MCP3208 -(1)

に詳しい解説があるので参照してください。


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

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

#define SPI_PORT spi0
static float Vref = 3.2562;


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 2MHz.
    spi_init(SPI_PORT, 2 * 1000 * 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, 0x00};
    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();

    adc_init();
    adc_gpio_init(26);
    adc_select_input(0);
    const float conversion_factor = Vref / (1 << 12);

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

    setup_SPI();

    while (1) {
        for (uint8_t i=0; i<8; i++){
            printf("ch%d is %.4fV\n", i, Vref * readADC(i) / 4096);
            sleep_ms(10);
        }
        uint16_t result = adc_read();
        printf("ADC voltage: %f V\n", result * conversion_factor);
        sleep_ms(5000);
    }
    return 0;
}

 ターミナルで、pico/works/buildにおります。

cmake ..

 pico/works/build/mcp3208におります。

make -j4

 Resetボタンを押したまま、BOOTSELボタンを押し、Resetボタンを離してから、BOOTSELボタンを離します。RPI-RP2ドライブがマウントされました。

 RPI-RP2ドライブへ、mcp3208.uf2をドラッグします。

 ch0には、Analog Discovery Pro ADP3450のSupplies出力をつないでいます。ch1からch7は何もつないでいません。Supplies出力をDMMで測った電圧は1.79221Vです。

 実行中の様子です。

基準電圧Vrefに安定した電圧を供給

 TL431は基準電圧源ICでポピュラです。MCP3208のVref端子とPicoのADC_VREF端子にこの電圧を入力して、測定します。DMMで測ると2.54802Vでした。これをプログラムのVrefに記入します。

 入力電圧は、DMMで1.79220Vでした。

 

 Vref=電源電圧のとき、 ADCは0.013963V、MCP3208は0.00591Vの誤差。

 Vref=TL431のとき、ADCは0.049767V、MCP3208は0.0454Vの誤差。

 電源電圧を基準電圧源として使ったほうが、誤差が少ない結果になりました。TL431の出力を直接、両方のVrefに入力したのが原因かもしれません。通常、OPアンプを使ったボルテージフォロワを入れることが多いです。

 ただ、PicoのADC_VREF端子の動作がよくわかっていません。入力端子と思えるのですが、実際は電圧が出ています。ADCのAPIファンクションには、外部基準電圧の入力切り替えというような関数はないです。

連載 Raspberry Pi Picoでプログラミング

(1) ラズパイ4の準備(1) USBブートの設定

(2) ラズパイ4の準備(2) 標準入出力の用意

(3) ラズパイ4の準備(3) LチカとHello, world!の実行

(4) ラズパイ4の準備(4) リモート環境の設定

(5) プログラミングの環境整備とLチカ

(6) Hello, World!

(7) 使用するピンと機能

(8) クロックの値の表示

(9) i2cscanner

(10) i2c APIと気圧センサLPS25

(11) i2c 温度センサTMP117

(12) i2c 湿度センサAHT20

(13) spi APIとA-DコンバータMCP3008

(14) spi A-DコンバータMCP3208

(15) gpioファンクション