ライブラリ完備!組立式ラズパイI/O増設ボード MCC DAQ HATSファミリ誕生

最大8段までスタックOK!自分仕様の計測制御アプリを3分クッキング

組み合わせ自在!最大8スタック可能なラズパイ用のI/O拡張ボード・ファミリ MCC DAQ HATSシリーズ

Digilent社のMCC DAQ HATS for Raspberry Piシリーズは,ラズベリー・パイに簡単に取り付けて利用できるデータ測定用インターフェースです.端的には,アナログ信号やディジタル信号の入出力端子を増設できる,ということです.

複数のラインアップがありますが,本稿では比較的汎用性の高い次の2つのボードを紹介します.

  1. アナログ入力拡張ボード MCC 118(写真1)
    8本のアナログ信号入力端子をもち,アナログ入力は±10V範囲の12ビット精度で,入力取り込みの最大速度は100kSPS(サンプル/秒)です.
  2. アナ・デジ入出力拡張ボードMCC 152(写真2)
    2本のアナログ出力と8本のディジタル入出力端子をもち,アナログ出力は0~+5V範囲の12ビット精度で,切り替えの最大速度は5kSPSです.
写真1 分解能12ビット,サンプリング・レート 100kSPSのA-Dコンバータを8チャネル搭載するアナログ入力拡張ボード“MCC118” 写真2 分解能12ビットのD-Aコンバータを2チャネル,データ幅 8ビットのディジタルI/Oを搭載するアナ・デジ入出力拡張ボード“MCC152”

同シリーズには,MCC 118の改良型と思われるMCC 128(入力が16ビット精度,レンジ切り替え可能),熱電対用インターフェースを備えるMCC 134,圧電素子用インターフェース“IEPE”を備えるMCC 172があります.

MCC DAQ HATS for Raspberry Piの特徴は,最大8枚の異種・同種ボードをスタックできる点です.MCC 152を積み重ねると,その分,出力ポート数を増やすことができます.また,MCC 152とMC 118を積み重ねると,アナログ入力とアナログ出力が可能なLinuxボードになります.

3分クッキング その1
開発環境をセットアップする

準備 メモリ4GBのラズベリー・パイ4モデルBを例に

MCCボード以外にラズベリー・パイ本体が必要です.

本稿では,メモリ4GBのラズベリー・パイ4モデルBと,もっとも標準的な環境と思われるRaspberry Pi OSの2022.04.04リリース版の組み合わせを使いました.極端なCPUパワーが求められるようには思えないので,おそらく他のラズベリー・パイ姉妹機やOSバージョンでも問題ないでしょう.

C/C++またはPython 2.7/3.4のソフトウェア開発環境も必要ですが,これらは標準的環境のもとではインストール済みなので,特別な作業はありません.

手順1 ラズベリー・パイに接続する

本器には,ラズベリー・パイへのボード取り付け方法を説明するマニュアルが付属していますが,簡単なので読まなくてもOKです.

写真3に示すようにピン・ヘッダが同梱されているので,基板の裏から挿し込んだうえでラズベリー・パイのGPIOピン・ヘッダに取り付けます.

写真3 同梱のピン・ヘッダを基板の裏側から挿し込んで,ラズベリー・パイ4のGPIOヘッダに取り付ける 写真4 ラズベリー・パイ4の上にMCC 118とMCC 152を装着したところ(矢印はアドレス設定用のピン・ヘッダ)

写真4のように,2枚以上のボードをスタックすることができます.その場合は組み付けをする前に次の2点を済ませてください.

各ボードにアドレス設定用の6ピン・ヘッダがあります.ここでアドレスとは,ソフトウェアから各ボードを区別できるようにするための3ビットの(0~7の)数値です.プラグを挿し込むと(写真5),各ボードのアドレスが異なった値に設定されます.どれでもよいですが,アドレス値として“0”の設定されたボードが存在している必要があります.

各ボードに端子台(ワイヤを挿し込んでねじで固定できるポート)がありますが,ラズベリー・パイに装着する前に,ワイヤを接続します.ボードを装着し終えた後では,最上段のボードの端子台しか操作できなくなるからです.

ボードの取り付けが終わったらラズベリー・パイの電源を入れます.

写真5 プラグを挿すと,各ボードにアドレスが割り当てられる
すべてのボードのアドレスが異なっていることと,アドレス0のボードが存在していることが必要

手順2 ソフトウェア・ライブラリをインストールする

このままではボードを動かすデモがありませんし,ソフトウェア開発もできません.

ラズベリー・パイが立ち上がったら,ソフトウェア・ライブラリであるMCC DAQ HAT library for Raspberry Pi(デモも同梱されている)をインストールします.

次に手順を説明します.マニュアルはこちらでも参照できます.

マニュアルにしたがって,ラズベリー・パイ上で “sudo apt update”や “sudo apt install git”を実行しておきます.

githubからライブラリを取得してインストールします.

適当な作業ディレクトリを作り(ここでは“~/work”),図1の矢印のようにコンソールからコマンドを入力していくと,インストールが始まります.インストールの最後に「Python 2をサポートしますか?」と聞かれるので,適宜“yes/no”で回答します.私は不要なので,図2のように“n”と答えましたが,“y”でもかまいません.

図1 ソフトウェア開発用ライブラリ“MCC DAQ HAT library”をインストールする
赤矢印は,入力したコマンド
図2 「Python 2に対応している」と回答するとインストールが終了する
ここまでのセットアップ作業で問題が起きそうな箇所は多くない

デモ・ソフトの別の入手方法

前述のgithubの緑色の[Code]ボタンを押すと,プルダウン・メニューが出ます.

Download ZIPを選択すると(図3),“daqhats-master.zip”というZIPファイルにアーカイブされた形でダウンロードされます.このZIPファイルを作業ディレクトリ下へunzipし,後は図1と同様にビルドを行います.

図3 ソフトウェア開発用ライブラリはMCC DAQ HAT libraryのホームページからもダウンロードできる

前述のように,このライブラリにはデモソフトが含まれているので,すぐにボードを動かしてみることができます.

デモソフトは,ライブラリをダウンロードしたディレクトリ(ここでは“~/work”)の下に,ボード品番ごとに分かれた形で置かれています(図4).とても良くできていて,ライブラリのAPIの使い方の参考になるでしょう.

図4 ライブラリの“daqhats/example”サブディレクトリの下に,各ボード用のPython言語とC言語で記述されたデモソフトがある

図5に示すように,実際に,MCC 118(A-Dコンバータ)ボードの動作を確かめることができました.

図5 MCC 118のデモ・ソフトを使ってボードが動作しているか確認

ここまでの作業については,環境依存の部分がほとんどありませんし,拍子抜けするくらいスムーズに行くはずです.ボードが動かないときは,前述のアドレス設定が間違っていないか,コネクタの破損がないか,チェックしてみてください.

3分クッキング その2
アナ・デジ入出力拡張ボード MCC 152で作るのこぎり波発生器

のこぎり波状に電圧が上がっては下がる信号を出力する

先ほどインストールしたライブラリを利用して,アプリケーション・ソフトウェアを作り,自分仕様のラズパイI/Oボードにチューニングします.ソフトウェアは,C言語またはPython言語で作成できます.

アナ・デジ入出力拡張ボード MCC 152とラズベリー・パイを組み合わせたシンプルな作例として,A0端子の出力電圧を0Vから3Vへ少しずつ上がるのこぎり波を発生する信号源を実現してみます.

C言語バージョン

リスト1にC言語ソースを示します.

githubから,MCC 152を制御するためのAPI(C言語版)の説明があります.

たくさんあって難しそうに見えるかもしれませんが,使い方はリスト1に示すように単純です. 簡単にソースコードの説明をしましょう.

冒頭で“daqhats_utils.h”をインクルードすると,APIを利用できるようになります.このファイルはライブラリの“daqhats\examples\c”サブディレクトリにあります.ソースと同じ場所へコピーしておくとよいでしょう.

“select_hat_device()”でボードのアドレスを取得します.これはラズベリー・パイに本ボードを装着するときに,ピン・ヘッダに設定した値です.

MCC 152_open()でボードをオープンします. MCC 152_a_out_write()で,指定したアナログ出力チャネル(ここではAD0端子)の電圧を設定します.設定電圧の数値はdouble型です.

リスト2のmakefileを使って,このソース“sample.c”をコンパイル(コマンド・ラインで“make”)します.生成されたバイナリ“sample”を実行すると,図6に示すようにA0端子に波形が出てきます.

図6 リスト1とリスト3のプログラムで生成したアナログ出力信号の波形
#include 
#include 
#include "daqhats_utils.h"  // daqhats\examples\c\daqhats_utils.h

int main()
{
    uint8_t     brd_addr;
    double      analog_val;
    

    // select the device to be used.
    if (select_hat_device(HAT_ID_MCC_152, &brd_addr) != 0) {
        return 1;
    }
    printf("MCC152 address %d\n", brd_addr);

    // open a connection to the device.
    if (mcc152_is_open(brd_addr) == 0) {
        if (mcc152_open(brd_addr) != RESULT_SUCCESS) {
            printf("open failure\n");
            return 1;
        }
    }

    analog_val  = 0.0;

    while (1) {
        double      output_buf;
        uint8_t     ch      = 0;
        uint32_t    options = OPTS_DEFAULT;

        // analog output (ch 0)
        if (mcc152_a_out_write(brd_addr, ch, options, analog_val) != RESULT_SUCCESS) {
            mcc152_close(brd_addr);
            return 1;
        }

        usleep(1000);       // 1/1000 sec

        analog_val  += 0.1;
        if (analog_val > 3.0) {
            analog_val  = 0.0;
        }
    }

    return 0;
}

リスト1 のこぎり波(0~3V)を出力するMCC 152のCプログラム(sample.c)
NAME = sample
OBJ = $(NAME).o
LIBS = -ldaqhats -lm
CFLAGS = -O2 -Wall -I/usr/local/include -I./ -g
CC = gcc
EXTENSION = .c

all: $(NAME)

%.o: %$(EXTENSION) $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

$(NAME): $(OBJ)
    $(CC) -o $@ $^ $(CFLAGS) $(LIBS)

.PHONY: clean

clean:
    @rm -f *.o *~ core $(NAME)

リスト2 リスト1をビルドするためのMakefile

Python言語バージョン

リスト3に示すのは,リスト1とまったく同じ処理をするPythonコードがです.

呼び出している各関数(クラスメソッド)の役割はC言語と同じですが,関数名や引数の並び順序が少し違っている場合もあります.

Python版のAPIは,ここに説明があります.

Pythonコードを実行する際には,daqhats\examples\python\mcc* の下にある“daqhats_utils.py”を同じディレクトリにコピーしておきます.

APIに用意されている他の関数は,ほとんどがディジタルI/O端子の入出力方向設定や入出力処理です.少し面白い機能として,指定したディジタル入力端子の値が変化したことを通知する(APIでは割り込みと呼んでいる)ものもあります.

3分クッキング その3
アナログ入力拡張ボード MCC 118で作る計測用マルチ・チャネルA-D変換器

アナログ入力拡張ボード MCC 118も基本的なアプリケーションの組み方は同じです.ボードのアドレスを取得してオープンし,ポートの値を読み取ります.

リスト4リスト5にソースコードを示します.実行すると,図7のように各入力ポートの電圧が1秒ごとに表示されます.

#!/usr/bin/env python

from __future__ import print_function
from time import sleep
from daqhats import mcc152, OptionFlags, HatIDs, HatError
from daqhats_utils import select_hat_device

def main():
    brd_addr    = select_hat_device(HatIDs.MCC_152)
    hat         = mcc152(brd_addr)
    options     = OptionFlags.DEFAULT
    ch          = 0

    analog_val  = 0.0

    while True:
        hat.a_out_write(ch, analog_val, options)
        analog_val  = analog_val + 0.1
        if analog_val > 3.0:
            analog_val  = 0.0
        sleep(0.001)

if __name__ == '__main__':
    main()

リスト3 のこぎり波(0~3V)を出力するMCC 152のPythonプログラム(sample.py)
図7 リスト4とリスト5のプログラムを実行すると電圧値が表示される

C言語のAPIはこちらに,Python言語のAPIはこちらに説明があります.

このAPIの“a_in_read()”は,1回しか入力を読み取りません.定期的に高速に読み取る関数を呼び出す方法は後述します.

#include 
#include 
#include "daqhats_utils.h"  // daqhats\examples\c\daqhats_utils.h

int main()
{
    uint8_t     brd_addr;   // board address

    // select the device to be used.
    if (select_hat_device(HAT_ID_MCC_118, &brd_addr) != 0) {
        return 1;
    }
    printf("MCC118 address = %d\n", brd_addr);

    // open a connection
    if (mcc118_is_open(brd_addr) == 0) {
        if (mcc118_open(brd_addr) != RESULT_SUCCESS) {
            printf("open failure\n");
            return 1;
        }
    }

    while (1) {
        double      input_buf;
        uint8_t     ch;
        uint32_t    options = OPTS_DEFAULT;

        // read all input channels
        for (ch = 0; ch < 8; ch++) {
            if (mcc118_a_in_read(brd_addr, ch, options, &input_buf) != RESULT_SUCCESS) {
                mcc152_close(brd_addr);
                return 1;
            }
            printf("CH%d = %fV ", ch, input_buf);
        }
        printf("\n\n");

        usleep(1000 * 1000);    // 1 sec
    }

    return 0;
}

リスト4 アナログ入力端子の電圧を繰り返し読み取るMCC 118のCプログラム(sample.c)

3分クッキング その4
アナ・デジ入出力拡張ボードMCC 152で作る方形波ジェネレータ

ソースコードの説明

アナ・デジ入出力拡張ボード MCC 152を使って任意波形ジェネレータを作ります.

リスト6に,2本のアナログ出力ポートに一定周期で出力を出すC言語のソースコードを示します.このままでは方形波しか出力されませんが,任意の波形を出すように改造するのは簡単です.

リスト1に示した基本的なソースコードとの差異は次の2点です:

  1. “MCC 152_a_out_write_all()”を呼んで,ポートを出力に設定している.これは,すべてのアナログ出力ポートに同時に値を転送するための関数である
  2. ある値を出力してから次の値を出すまで,一定時間(ウェイト)待つ.このウェイトのための“usleep()”を別スレッドで実行している.API関数の実行時間の影響を少しでも減らすためである
#!/usr/bin/env python

from __future__ import print_function
from time import sleep
from sys import stdout
from daqhats import mcc118, OptionFlags, HatIDs, HatError
from daqhats_utils import select_hat_device, enum_mask_to_string

def main():
    brd_addr            = select_hat_device(HatIDs.MCC_118)
    hat                 = mcc118(brd_addr)
    options             = OptionFlags.DEFAULT

    while True:
        for ch in range(0, 7+1):
            input_buf = hat.a_in_read(ch, options)
            print('CH{}={:2.5}V  '.format(ch, input_buf), end='')
        print('\n')
        stdout.flush()
        sleep(1)

if __name__ == '__main__':
    main()
リスト5 アナログ入力端子の電圧を繰り返し読み取るMCC 118のPythonプログラム(sample.py)
// add "-lpthread" to LIBS definition in makefile

#include 
#include 
#include 
#include "daqhats_utils.h"  // daqhats\examples\c\daqhats_utils.h

#define INTERVAL    5000        // usec

volatile bool   timer_flag  = false;

void timer_func(void)
{
    printf("start timer thread\n");
    while (1) {
        usleep(INTERVAL);
        timer_flag  = true;
    }
}


int main()
{
    uint8_t     brd_addr;
    double      analog_val[2];

    pthread_t   th;

    // select the device to be used.
    if (select_hat_device(HAT_ID_MCC_152, &brd_addr) != 0) {
        return 1;
    }
    printf("MCC152 address %d\n", brd_addr);

    // open a connection to the device.
    if (mcc152_is_open(brd_addr) == 0) {
        if (mcc152_open(brd_addr) != RESULT_SUCCESS) {
            printf("open failure\n");
            return 1;
        }
    }

    // start timer thread
    pthread_create(&th, NULL, (void *)timer_func, NULL);

    analog_val[0]  = 0.0;
    analog_val[1]  = 3.0;

    while (1) {
        uint32_t    options = OPTS_DEFAULT;

        // analog output (ch 0,1)
        if (mcc152_a_out_write_all(brd_addr, options, analog_val) != RESULT_SUCCESS) {
            mcc152_close(brd_addr);
            return 1;
        }

        if (analog_val[0] == 3.0) {
            analog_val[0]  = 0.0;
            analog_val[1]  = 3.0;
        }
        else {
            analog_val[0]  = 3.0;
            analog_val[1]  = 0.0;
        }

        while (timer_flag == false)
            ;
        timer_flag  = false;
    }

    return 0;
}

リスト6 MCC 152で作る周期を変更できる方形波ジェネレータのソースコード(C言語, sample.c)

数十Hzなら使える

どれだけ正確な周波数の波形が得られるかが気になります.

表1に示すのは,PCオシロスコープで測定してみた一例を示します.Linux(Raspberry Pi OS)に時間の正確性を求めることは元来難しいのですが,それなりの精度があると言えるのは数十Hzまで,多少なりとも似たような周波数だと言えるのは,最大でも数百Hzです.

表1 リスト6を実行すると出力されるアナログ信号の周期精度

3分クッキング その5
MCC 152とMCC 118で作る高精度任意波形ジェネレータ

MCC 118で正確なパルスを生成してMCC 152に入力

リスト6の作例より周波数精度の高い波形を出力する方法を検討しました.ここでは簡便に実現できる方法を紹介します.ただし,産業用途が目的なら,Linux OSではなくリアルタイムOSを使うのが定石です.

基本的なアイディアとは,次のようなものです.

アナログ入力拡張ボード MCC 118に読み取り動作を行わせると,0~100kHzの正確な読み取りクロックを出力します.このパルス信号を時刻信号として,ラズベリー・パイのGPIOに入力します.このパルス信号のH→L/L→Hの切り替わりをラズベリー・パイで捉えて,アナ・デジ入出力拡張ボードのMCC 152の出力関数を呼び出します.

ハードウェアの準備

写真6に示すように,MCC 152とMCC 118をスタック装着します.MCC 118のCLKピンをラズベリー・パイの40ピン(GPIO21に相当)接続します.

写真6 MCC 152とMCC 118をスタック装着して作った波形ジェネレータ
周波数精度が少し改善される

リスト7にC言語のソースコードを示します.

ソースコードの冒頭に,“gpio”の設定や読み取りを行う関数が追加されています.“main()”関数では,ボードやGPIOの初期化に続いて,“MCC 118_a_in_scan_start()”を呼んでMCC 118側でポート連続読み取りを開始させます(この処理は次節でも使うので改めて説明する).

これにより,関数引数に渡した値RATE_SPSと同じ周波数のパルスが,MCC 118 CLKピンから出てきます.

Whileループの中でパルス信号の読み取りを行い,値が切り替わったときに“MCC 152_a_out_write_all()”を呼びます.ループの最後に“MCC 118_a_in_scan_read()”を呼んでいますが,これはMCC 118の動作を継続させることを目的に,MCC 118で入力ポート読み取りを行うものです.

#include 
#include 
#include 
#include "daqhats_utils.h"

#include 

#define RATE_SPS    500.0

#define GPIO_PORT   21      // pin 40

void gpio_export(int pin)
{
    int     hd_gpio;
    char    wbuf[64];
    if ((hd_gpio = open("/sys/class/gpio/export", O_WRONLY)) < 0) {
        fprintf(stderr, "cannot open gpio_export (forget to sudo?)\n");
        return;
    }
    sprintf(wbuf, "%d", pin);
    write(hd_gpio, wbuf, strlen(wbuf));
    close(hd_gpio);
}

void gpio_setdir(int pin, int dir)
{
    int     hd_gpio;
    char    wbuf[64];
    sprintf(wbuf, "/sys/class/gpio/gpio%d/direction", pin);
    if ((hd_gpio = open(wbuf, O_WRONLY)) < 0) {
        fprintf(stderr, "cannot open %s (forget to sudo?)\n", wbuf);
        return;
    }
    if (dir == 0) {
        sprintf(wbuf, "out");
    }
    else {
        sprintf(wbuf, "in");
    }
    write(hd_gpio, wbuf, strlen(wbuf));
    close(hd_gpio);
}

void gpio_in(int pin, uint8_t *val)
{
    int     hd_gpio;
    char    wbuf[64];
    char    rbuf;
    sprintf(wbuf, "/sys/class/gpio/gpio%d/value", pin);
    if ((hd_gpio = open(wbuf, O_RDONLY)) < 0) {
        fprintf(stderr, "cannot open %s (forget to sudo?)\n", wbuf);
        return;
    }
    read(hd_gpio, &rbuf, 1);
    close(hd_gpio);
    if (rbuf == '1')
        *val    = 1;
    else
        *val    = 0;
}

int main()
{
    uint8_t     addr_152, addr_118;
    uint32_t    options     = OPTS_CONTINUOUS;
    double      analog_val[2];

    uint8_t     prev_trig   = 0;

    // select the device to be used.
    if (select_hat_device(HAT_ID_MCC_118, &addr_118) != 0) {
        return 1;
    }
    printf("MCC118 dev_addr %d\n", addr_118);

    if (select_hat_device(HAT_ID_MCC_152, &addr_152) != 0) {
        return 1;
    }
    printf("MCC152 dev_addr %d\n", addr_152);

    // open a connection to the device.
    if (mcc118_is_open(addr_118) == 0) {
        if (mcc118_open(addr_118) != RESULT_SUCCESS) {
            printf("unable to open device at dev_addr %d\n", addr_118);
            return 1;
        }
    }

    if (mcc152_is_open(addr_152) == 0) {
        if (mcc152_open(addr_152) != RESULT_SUCCESS) {
            printf("unable to open device at dev_addr %d\n", addr_152);
            return 1;
        }
    }

    // configure GPIO
    gpio_export(GPIO_PORT);
    gpio_setdir(GPIO_PORT, 1);      // input

    // generate trigger clock
    mcc118_a_in_scan_stop(addr_118);
    mcc118_a_in_scan_cleanup(addr_118);
    if (mcc118_a_in_scan_start(addr_118, CHAN7, 100 * 1000 * 1000 /* set large number as far as possible */, RATE_SPS, options) != RESULT_SUCCESS) {
        fprintf(stderr, "mcc118 scan_start() error\n");
        return -1;
    }

    // main loop
    while (1) {
        uint8_t trig;
        // check trigger
        gpio_in(GPIO_PORT, &trig);

        if (trig != prev_trig) {
            // analog output (ch 0, 1)
            if (mcc152_a_out_write_all(addr_152, options, analog_val) != RESULT_SUCCESS) {
                mcc152_close(addr_152);
                return 1;
            }

            if (analog_val[0] == 3.0) {
                analog_val[0]  = 0.0;
                analog_val[1]  = 3.0;
            }
            else {
                analog_val[0]  = 3.0;
                analog_val[1]  = 0.0;
            }
        }
        prev_trig   = trig;

        // read MC118 (to generate clk)
        uint16_t    read_status;
        double      rxbuf[10];
        uint32_t    read_num;
        mcc118_a_in_scan_read(addr_118, &read_status, -1, -1, rxbuf, 10, &read_num);
    }

    return 0;
}

リスト7 周波数精度をやや高めたMCC 152波形ジェネレータのCソースコード(sample.c)

1kHz程度まで使えるようになった

表2に,MCC 152からアナログ波形生成をさせた結果を示します.1kHzぐらいまで,出力周波数が正確になりました.ただし,周波数の揺らぎは残ります.

リスト7のソフトウェアをコマンド・ラインから実行する際には,“sudo”してください.

表2 リスト7を実行すると出力されるアナログ信号の周期精度

3分クッキング その5
MCC 118で作るシンプル波形ロガー

ストレージのある限りDC~数Hzのアナログ信号を記録できる

MCC 118を使ってシンプルな波形ロガーを作ってみます.

図8に示すように,各チャネルの電圧波形をリアルタイムで表示します.2チャネルぶんしか表示していませんが,8チャネルまで簡単に拡張できます.MCC118を複数枚接続すればチャネル数をさらに増やすことができます.なお,描画速度の限界から,速い信号波形を表示することはできません.正確には測定していませんが,高々数Hzくらいでしょう.

このようなGUI付きのアプリは,C言語よりはPython言語のほうが作るのが簡単です.

図8 MCC118を使ったシンプルな波形ロガーを製作(記録サイズ:ストレージのある限り,帯域:DC~数十Hz)

Python言語バージョン

リスト8に示すのは,Python言語で記述したソースコードです.

グラフ描画を行うライブラリ“matplotlib”に対して,MCC 118のAPIで読んだポート電圧を引き渡しているだけです.プログラム実行を終了するときは,Ctrl-Cを複数回押してください.

matplotlibがない場合は “pip install matplotlib”でインストールできます.

3分クッキング その6
MCC 118で作る100kSPS×同時8チャネル波形ロガー

MCC 118のパフォーマンスを引き出す

MCC 118のスペックの上限 100kSPS,8チャネルまでのアナログ信号を観測できるロガーを作りました.帯域はDC~約50kHz(ナイキスト限界による)です.MCC118を複数枚接続すればチャネル数をさらに増やすことができます.

ストレージ容量の許す範囲で,設定したサンプリング・レートとサンプリング期間でデータを取り,csvファイルに記録します.

ソースコード

リスト9にC言語で記述したソースコードを示します.次のような処理を行っています

データを保存するためのバッファを“malloc”で確保しておきます.データの型は“double”です.API関数“MCC 118_a_in_scan_start()”を呼んで,サンプリングをスタートさせます.

このソース・コードでは,1チャネルぶんしかサンプリングをしませんが,第2引数に与えている引数(CHANNEL)の値を変えれば,複数チャネルの同時サンプリングが可能になります.その場合は,チャネル数に応じたバッファを確保してください.

“MCC 118_a_in_scan_start()”を呼ぶだけでは,データを取得できません.“MCC 118_a_in_scan_read()”を使って,先ほど確保したバッファへのデータ転送を行います.  この関数の引数に指定した分量のデータが読めずに,リターンしてくる場合があります.全部でどれだけのデータを読み込めたか累算しながら,繰り返しこのリード関数を呼びます.これは,Linuxの“write()/read()”システム・コールの使い方と同様です.

作例はありませんが,サンプリングを外部トリガ信号によって開始させることも可能です.

#!/usr/bin/env python

from __future__ import print_function
from time import sleep
from sys import stdout
from daqhats import mcc118, OptionFlags, HatIDs, HatError
from daqhats_utils import select_hat_device, enum_mask_to_string

import matplotlib.pyplot as plt
import numpy as np

def main():
    brd_addr    = select_hat_device(HatIDs.MCC_118)
    hat         = mcc118(brd_addr)
    options     = OptionFlags.DEFAULT

# initialize data list
    ch0_list    = [0]
    ch1_list    = [0]
    step_list   = [0]

    interval    = 0.05      # unit: sec
    step_len    = 20        # number of elements in X axis
    total_step  = 0

    while True:
        try:
            ch0_list.append(hat.a_in_read(0, options))
            ch1_list.append(hat.a_in_read(1, options))
            step_list.append(total_step)

            if len(step_list) > step_len:
                del ch0_list[0]
                del ch1_list[0]
                del step_list[0]

            plt.cla()

            plt.plot(ch0_list, label="ch0")
            plt.plot(ch1_list, label="ch1")
            plt.xlim(1, len(step_list))
            plt.xticks([])
            plt.ylabel("voltage")
            plt.yticks([0, 1, 2, 3, 4, 5])
            plt.legend()
            plt.grid()

            plt.draw()

            plt.pause(interval)
            total_step += 1

        except KeyboardInterrupt:
            break

if __name__ == '__main__':
    main()

リスト8 MCC118を使った波形ロガーのソースコード(Python, sample.py)
matplotlibのインストールが必要
#include 
#include 
#include "daqhats_utils.h"  // daqhats\examples\c\daqhats_utils.h

#define RATE_SPS        100000.0    // max 100ksps
#define SAMPLE_DURATION 1.0         // sec
#define CHANNEL         0
#define OUT_FNAME       "out.csv"

#define BUFFER_SIZE     ((unsigned int)(SAMPLE_DURATION * RATE_SPS))

int main()
{
    uint8_t     brd_addr;   // board address
    double      *buf;
    uint32_t    total_read;
    uint32_t    read_num;
    uint16_t    read_status;

    uint32_t    options = OPTS_DEFAULT;
    int         i;

    // select the device to be used.
    if (select_hat_device(HAT_ID_MCC_118, &brd_addr) != 0) {
        return 1;
    }
    printf("MCC118 address = %d\n", brd_addr);

    // open a connection
    if (mcc118_is_open(brd_addr) == 0) {
        if (mcc118_open(brd_addr) != RESULT_SUCCESS) {
            printf("open failure\n");
            return 1;
        }
    }

    // allocate buffer
    buf = (double *)malloc(sizeof (double) * BUFFER_SIZE);
    if (buf == (double *)NULL) {
        fprintf(stderr, "malloc() error\n");
        return 1;
    }

    // start fast sampling
    if (mcc118_a_in_scan_start(brd_addr, CHANNEL, BUFFER_SIZE, RATE_SPS, options) != RESULT_SUCCESS) {
        fprintf(stderr, "start() error\n");
        return 1;
    }
    fprintf(stderr, "sampling start\n");

    // read all data
    total_read  = 0;
    do {
        mcc118_a_in_scan_read(brd_addr, &read_status, -1, -1, buf + total_read, BUFFER_SIZE - total_read, &read_num);
        total_read  += read_num;

        if (read_status & STATUS_HW_OVERRUN) {
            printf("\n\nhardware overrun (%d)\n", total_read);
            break;
        }
        else if (read_status & STATUS_BUFFER_OVERRUN) {
            printf("\n\nbuffer overrun (%d)\n", total_read);
            break;
        }
    } while ( (read_status & STATUS_RUNNING) == STATUS_RUNNING && total_read < BUFFER_SIZE);

    fprintf(stderr, "sampling end\n");

    // write file
    FILE    *fpout  = fopen(OUT_FNAME, "wt");
    for (i = 0; i < BUFFER_SIZE; i++) {
        fprintf(fpout, "%f, %f\n", (1.0 / RATE_SPS) * (double)i, buf[i]);
    }
    fclose(fpout);

    free(buf);
    mcc118_close(brd_addr);
    return 0;
}

リスト9 MCC 118用の100kSPS波形ロガーのCソースコード( sample.c)

(c)2022 Sumio Morioka