======================
(野村さんへのメモ)
・「★補足解説2」の部分は囲み記事として添えて下さい
======================

■第4部　マイコンでトライ！ 生体情報の信号処理の基礎■

第2章　mbedによる筋電情報システムの例

2-3　NucleoF401REの基本プログラミング

●オンラインIDEとバイナリ書き込み

筆者自身が本稿の執筆のために「初めてのmbed」を同時進行で体験していったのですが、本稿は「mbed入門」ではないので、NucleoF401REのプログラミングを実際に行うプロセスについては、ステップごとにごく簡単に紹介するにとどめます。ここではMac OSX の例を紹介しますが、WindowsでもLinuxでも同じです。

(1)
まずmbedのサイト(http://mbed.org/)から「mbed Developer」のページ(http://developer.mbed.org/)に行き、ユーザ登録して会員となって、ログインすると図2-3-1のようになります。

(図2-3-1　mbedのサイト[ログインした状態])

(2)
「Platforms」のタブからプラットフォーム選択のページ(https://developer.mbed.org/platforms/)に行き、図2-3-2のように「ST NucleoF401RE」を選択します。

(図2-3-2　mbedのサイトからプラットフォームを選択)

(3)
NucleoF401REのページ(https://developer.mbed.org/platforms/ST-Nucleo-F401RE/)に行くので、図2-3-3のようにページ右上の「Compiler」をクリックします。

(図2-3-3　オンラインコンパイラを選択)

(4)
図2-3-4のように別ウインドウでオンラインIDE(統合開発環境 - プログラム開発ツール)(https://developer.mbed.org/compiler/)が開きます。この内容はログインしたユーザごとにmbedサイトのサーバに保存されているので、毎回、これを呼び出して改訂したり新しいプログラムを追加していきます。

(図2-3-4　オンラインIDEの画面例)

(5)
プログラムを開発して、「Compile」ボタンでコンパイルして、エラーが無くコンパイル成功すると、図2-3-5のようにファームウェアのバイナリをダウンロードするか聞いてくるので、「Save File」で保存します。

(図2-3-5　コンパイル成功の結果をダウンロード)

(6)
NucleoF401REをパソコンにUSB接続すると、デスクトップに「NUCLEO」というアイコンが登場してマウントされるので、図2-3-6のようにダウンロード保存したバイナリファイルをドラッグ&ドロップします。NucleoF401REのホストUSBコネクタ横のLEDが「赤/緑」で点滅して、バイナリがメインCPUのフラッシュEEPROMに書き込みされます。

(図2-3-6　「NUCLEO」アイコンにバイナリをドラッグ&ドロップ)

(7)
書き込みが終ると、図2-3-7のように「正常にアンマウント出来ません」というエラー表示が出ますが、これを気にせず「OK」しておけば、また何度でも同じように新しいプログラムを書き込みできます。たまに「NUCLEO」というアイコンが消えた時には、NucleoF401REのUSBをいったん抜いてまた差し込むと、「NUCLEO」アイコンが登場する(マウントされる)ので、同様に作業を続けられます。

(図2-3-7　形式的なエラー表示を暖かく無視する)

なお、オンラインIDEの注意点として、複数のコンピュータから同じIDでログインしてIDEを開いたり、ブラウザの別タブ/ウインドウでさらにもう1画面のIDEを開いたりすると、mbedサーバ側で同一アカウントのIDEが同時に複数、開かれた相互の矛盾を避けるために、図2-3-8のように警告が出ます。この場合には慌てず「Reflesh Tree」を選択すれば、環境を最新の状況に整理してくれますが、混乱を避ける(古いバージョンを誤って上書きしない)ためにも、「複数のIDEは開かない」マナーを推奨します(この警告が出たら使っていないIDEを閉じればOK)。

(図2-3-8　同時に複数のコンパイラを開いた場合の画面例)

●メインルーチン速度の性能評価

前節(2-2)で、NucleoF401REのArduino互換シールド上に筋電センサ回路とXBee無線伝送回路が完成したので、まず最初に、NucleoF401REの能力をおよそ試すために、「ソフトウェア・タイマ」によるメインループの性能評価をしました。前節(2-2)の最後に定義した「6バイトの16進文字+1バイトの"CR"」という7バイトメッセージとして、000000〜7FFFFFという23ビットデータを刻々とインクリメントしてXBeeからホストに送る、というプロクラムを、メインルーチンの無限ループとして「100万回に1回」という頻度で実行したものです。

(リスト2-3-1)

図2-3-9は、ホスト側でこのNucleoF401REからのXBee情報を受信してチェックしている様子です。この画面例は「Max6」で、現在では「Max7」となっていますが、Maxパッチ(プログラム)はMax6と互換性があります。このMaxパッチは、データが届くたびにゲート(スイッチ)をON/OFFして、ゲートONの期間に「metro 10」、つまり10msec間隔のトリガが何発来ているか、をカウンタで計測して、それを刻々と表示しています。 
(詳しくは→ 1-1コラムの「参考情報Webページ」を参照して下さい)

(図2-3-9　メインルーチン無限ループ「100万回に1回」をXBee受信するMaxパッチ画面例)

MacOSはUnixなのでバックグラウンド処理によりでこぼこがありますが、ほぼ平均して「25発」という成績でした。つまり、およそ成績は以下のようになります。
	・NucleoF401REの無限ループ1000000回 ≒ 250msec
	・NucleoF401REの無限ループ1000回 ≒ 250μsec
	・NucleoF401REの無限ループ1回 ≒ 250nsec 
変数のインクリメント以外に何もしないとはいえ、メインルーチンの1周がマイクロ秒以下というのは、筆者のこれまで多数のマイコン経験でも無かった性能であり、生体信号の情報処理を実行するプラットフォームとして期待させるものです。

●シリアル通信とFIFO処理 - 割り込み受信、ポーリング送信

上のリスト2-3-1では、シリアル送信の頻度は毎秒4回程度ととても低く、そのたびに「xbee.putc()」を呼び出してバッファにデータを書き込むことでシリアル送信しましたが、実際のシステムではこんな悠長なことは出来ません。ホストPCからの高密度コマンド類をシリアル受信するには、受け損ね(データ落ち)を避けるため「割り込み」が必須で、送信についてもデータ量が増えた場合には、「xbee.putc()」を呼び出す前に「xbee.writeable()」をまずチェックして(ポーリング)、送信バッファが空である事を確認して、無駄な足踏みで「バッファ・エンプティ」を待たない必要があります。

そこで、XBeeを経由してのホストPCとの通信を双方向にして、定番のFIFOバッファを介して、実用的な通信プログラムとして作ってみたのが、リスト2-3-2です。ここでは、
	・0.5秒程度の間隔でNucleoF401REからインクリメントして送るメッセージ★を「生存証明」とする
	・ホストPCから受信するメッセージと★とをマージしてそのままエコーバック送信する
	・ホストPCから送信して受信を確認するデータの密度を上げていって「へこたれ限界」を調べる
としました。

(リスト2-3-2)

リスト2-3-2はシンプルなものですが、生体情報処理に限らず、マイコン・プログラミングとして重要な要素が詰まっているので、簡単に解説します。まず「FIFOバッファ」ですが、非同期シリアル受信に割り込みを使っているだけでは無意味で、受信割り込みとFIFOバッファのセットにより、強力な受信機能を実現します。図2-3-10はFIFO(First In, First Out)バッファの様子で、別名「リングバッファ」と呼ばれるように、連続するバッファメモリがリング状に繋がったイメージです。

(図2-3-10　FIFOバッファとは)

本システムではFIFOバッファを連続した256バイト、アクセス・ポインタを8ビットにして、ポインタをインクリメントしてオーバーフローするとゼロに戻ることで自動的にリングバッファが構成されます。このようなFIFOバッファを受信(RX)用と送信(TX)用にそれぞれ定義して、書き込みポインタと読み出しポインタもそれぞれ、FIFOバッファサイズに対応して定義します。受信、送信、いずれの場合にも動作は同じですが、本システム(リスト2-3-2)では以下のようにしています。
	・XBee受信があると割り込みにより受信FIFOに受信データを積む
	・メインルーチンから毎回「rx_fifo_check()」を呼び、結果が1だとメッセージを受け取り、送信FIFOに積む
	・「rx_fifo_check()」は、受信FIFOにデータが無ければ0を返す
	・「rx_fifo_check()」は、受信FIFOにデーがあればメッセージ配列に積み、7バイトが完結しなければ0を返す
	・「rx_fifo_check()」は、受信FIFOにデーがあればメッセージ配列に積み、7バイトが完結すると1を返す
	・メインルーチンでは約0.5秒ごとにインクリメント値メッセージを7バイトフォーマットで送信FIFOに積む
	・メインルーチンから毎回「tx_fifo_check()」を呼ぶ
	・「tx_fifo_check()」は、シリアル送信バッファが「空」でない場合には何もしないで返る
	・「tx_fifo_check()」は、シリアル送信バッファが「空」であれば送信FIFOを調べて、データがあれば1バイト送信する

このようにやや面倒なことをしているのは、FIFOバッファの「シリアル情報の時間的密度を吸収する」という特性からです。図2-3-11は、FIFOバッファを使うケースを示したもので、ある時には時間的に高い密度でデータがやってきて、ある時にはデータがほとんど来ない期間もある、というものです。MIDI情報はまさにこれで、例えばジャズピアニストが両手で4和音を刻んでいる場合、和音のたびに8音(=24バイト)のMIDI信号が一気に出ますが、音符と音符の間隔はスカスカに空いています。この場合、シリアル受信割り込みの割り込みルーチン内での処理にもたもたしていると、次の受信割り込みに間に合わなくてデータ受信が欠落します。筆者が「ArduinoはMIDIに使えない」(2-1)と書いたのは、Arduinoがちょっとした和音でも受け損ねて「音が消えない」状態に陥ることを確認したからです。生体信号には「生体リズム」(心拍、呼吸、反復運動など)があり、まさにこのような「疎密のある」情報なので、そのシリアル情報伝送にFIFOは最適です。

(図2-3-11　FIFOバッファを使うケース)

図2-3-12は、NucleoF401REにリスト2-3-2のプログラムを走らせて、ホストPCのMa6側で実験している様子です。NucleoF401REからは「生存証明」として約0.5秒間隔でインクリメントデータがXBee経由で送信され(画面の「3E1」)、同時にホストPCのMa6では「metro」で23ビットカウンタをインクリメントしてXBee経由でNucleoF401REに送る間隔が、「45msec」であっても安定して確実にデータがエコーバックされ、グラフ表示されています。 ホストPCは同時にMa6プログラムから440Hzと880Hzのサイン波を「USBオーディオ治具」を経由して出力していて、これもホストPCに接続されたUSBオシロスコープ/スペクトルアナライザで計測表示しています。このように「45msec」、安全係数を見て50msecとすれば「毎秒20回」のレートで、7バイトフォーマットのデータを安定にホストとXBeeシリアル無線で往復できる、というのは、経験的になかなかの能力と言えます。

(図2-3-12　XBee双方向通信を実験するMaxパッチ画面例)

●タイマ割り込み

NucleoF401REでA/D変換した生体情報を信号処理するには、正確な「時間」が必要となります。上述のソフトタイマ、つまりメインループの中で呼ばれるたびにインクリメントされる変数というのは、メインループで呼ばれる他の処理によって不定期に延びるので、この数値を時間的な基準とすると、誤差がどんどん増大します。そこで、システム・タイマの割り込みによって、処理に影響されない「等間隔の時間基準」を作る必要があります。

mbedでは、非同期シリアル受信と同じような形式(attach)で、タイマ割り込みハンドラを定義できます。リスト2-3-3のプログラムでは、「timer_setup.attach_us(&timer_interrupt, 100);」と、割り込み周期をマイクロ秒単位で「100」と設定していまます。つまり「0.1msec」ごとにタイマ割り込みが起きて、ここでは「timer_value[5];」と5種類のタイマーを定義したそれぞれの値をインクリメントした上で、16ビットマスクで0〜65535の値にしています。

(リスト2-3-3)

リスト2-3-3ののNucleoF401REメインルーチンの方では、ボード上のLEDを5000カウント、つまり500msecで点滅させつつ、さらにシリアル送信として10000カウント、つまり1秒ごとにインクリメント送信しています。試しにこの割り込み周期をマイクロ秒単位で「10」に、つまり「0.01msec」ごとにタイマ割り込みが起きるようにすると、ボード上のLEDの点滅は変化がありませんが、シリアル通信の方はデータが送りきれず欠落しました。これは、シリアル受信割り込み処理の期間に、さらにタイマ割り込みが競合するためです。なお、★補足解説2にあるように、本システムの場合にはホストPCとXBee経由でメッセージ交換する最大性能(1メッセージを連続伝送する所要時間)は「約2ミリ秒」です。

==================
★補足解説2　ここから↓

本システムでホストPCとXBee経由でシリアル通信する伝送速度の最大性能について整理します。今回の通信速度は、38400bps、つまり「毎秒38400ビット」のシリアル通信です。 フォーマットはmbedのシリアル環境のdefaultのままで、

    void format 	( 	int  	bits = 8,
    		Parity  	parity = SerialBase::None,
    		int  	stop_bits = 1 
    	) 		[inherited]

    Parameters:
        bits	The number of bits in a word (5-8; default = 8)
        parity	The parity used (SerialBase::None, SerialBase::Odd, SerialBase::Even; default = SerialBase::None)
        stop	The number of stop bits (1 or 2; default = 1) 

ということで、
	・データ8ビット
	・パリティビット無し
	・ストップビット=1ビット
	・スタートビット=1ビット
という形式で、1バイトの転送には計10ビットが必要です。

そこで「毎秒38400ビット」→「毎秒3840データ」=「1msecあたり3.84データ」となります。本システムのデータ転送フォーマットは1メッセージが「6バイトHEX(24ビットデータ)+1バイト(cr)」の計7データなので、1メッセージの転送にかかる最小時間(最大性能)は「約1.823msec」という事になり、ギッシリとデータが連続しているという理論値では、毎秒あたり548メッセージほどの伝送が限界となります。 

★補足解説2　ここまで↑
==================

●A/D変換入力 - ポーリングと高速変換

NucleoF401REのアナログ入力のA/Dコンバータのスペックは「12bit ADC 2.4 Msps up to 10 channels」とあります。従来のマイコン内蔵A/Dコンバータとか単独のA/Dコンバータ・チップでは、
	・「セトリング時間」(入力電圧をサンプル/ホールドしてA/D変換を開始するまでの時間)
	・「コンバージョン時間」(ビットごとに逐次比較するステップの合計で、A/D変換が完了するまでの時間)
などのパラメータがありました。これに対応して、マイコンの内部に、シリアル通信ポートと同様に、「A/D変換が終了したら割り込み」という機構とか、「A/D変換中のフラグ(←これをポーリングする)」が完備していました。

しかし、前項で使えるようになった「タイマ割り込み」を使って正確な時間で計測実験してみると、NucleoF401REのA/Dコンバータ変換時間は驚異的に高速で、普通に「A/D変換スタート」→「A/D変換が終了」と返ってくるプロセスを多数ループさせて計測してみると、変換時間はマージンを入れて「4μsec」、つまり1チャンネルであれば「250KHzサンプリング」まで行けそう、と確認できました。(この実験では、最高性能の1μsec割り込みから全て試してみたのですが、A/Dコンバータとシリアル通信が共存するタイマ割り込みの上限値としては、もっとも高速には前項の2倍、「50μsec」=「0.05msec」あたりまで行ける事も確認しました。)

CDオーディオが44.1KHzサンプリング、最近話題のハイレゾオーディオですら128KHzサンプリングなのに、「250KHz」というのはそれよりも高性能であり、筋電情報であれば「1000Hzサンプリング」で十分なので、まったく余裕の極致、という事になりました。 そこで本システム(筋電情報システム)のNucleoF401REのA/D変換については、セトリング時間やコンバージョン時間を無視して、サンプリング周期の基準をタイマー割り込みの精度で設定し、タイマイベント内で「A/Dデータを要求する」(返ってくるのを待つ)というシンプルなコードで十分、と結論づけました。 実際にmbedサイトで公開されているNucleoF401REサンプルコードを調べてみましたが、同じ手法でA/D変換を実装していました。

さて、NucleoF401REのA/Dコンバータの具体的なプログラミングについて紹介しましょう。mbedサイトの「Nucleo_read_analog_value」というサンプル
(http://developer.mbed.org/teams/ST/code/Nucleo_read_analog_value/?platform=ST-Nucleo-F401RE)
を見てみると、
==================
#include "mbed.h"
 
AnalogIn analog_value(A0);
DigitalOut myled(LED1);

#define MV(x) ((0xFFF*x)/3300)
 
int main() {
    while(1) {      
        uint16_t meas = analog_value.read_u16();
        if (meas > MV(1000)) myled = !myled;
        wait(0.2); 		// wait 200 ms
    }
}
==================
というものがありました。12ビットデータなので、A/D入力の数値は16進で「000〜FFF」であり、フルスケールが3.3Vなので、3300で割れば値はmv単位の電圧となります。上のプログラムでは、200msecごとにA/D計測して、1Vより越えたかどうかでLEDを点滅させています。データを受ける変数は「uint16_t」で定義します。

ここではリスト2-3-4のように、タイマー割り込みを「50μsec」として、この100回ごと、つまり「5msec」ごとに、1ポートのアナログ入力をA/D変換して、その12ビットデータをホストPCのMaxにXBee経由でシリアル送信しています。さらに同時に、ホストPCのMaxからUSBオーディオ治具を経由して、NucleoF401REのアナログA/D入力にサイン波形の電圧を与えています。いきなり筋電信号のような複雑な信号でなく、サイン波形がどの程度、再現できるかというテストです。

(リスト2-3-4)

結果は図2-3-12のようなものです。 A/Dコンバータのサンプリング周波数が5msec=200Hzということで、サイン波形信号の周波数を0.5Hzとして、NucleoF401REからXBee経由でホストPCに戻ってきた12ビットA/D変換後の信号波形を表示しています。USBオーディオ治具の出力で、パイポーラの出力をOPアンプ回路で+3.3Vレベルの脈流に変換するところに47μFのBPコンデンサを入れている関係でHPFとなり、0.5Hzでは最大に出力を上げてもダイナミックレンジが稼げず、周波数の精度は相当に悪いのですが、まずまず「0.5Hz」のサイン波形が見て取れます。これでNucleoF401REの道具立ては完了です。

(図2-3-13　サイン波供給→A/D変換→XBee送信の実験Maxパッチ画面例)