mbed日記 (2)
長嶋 洋一
2014年10月27日(月)
昨夜は無事に3回目のジスロマックを服用し、なんとかこの週末で体調は回復してきた。 朝イチで研究室に出てみると、昨日の苦闘のテーブルがそのままだった。(^_^;)
この日の午前は、来月末のCGクリエイター検定試験の受験者登録、郵便局に出かけていってその団体受験の受験料の振り込みがあり、さらにSUACの図書館から借りる本のリスト(→mbed日記(1)の最後)に関して、肝心の「筋電」が無かった事に気付いて、以下のように追加して、図書館をあちこち歩き回ってかき集め、自費で買った本も含めて、計30冊ほどの参考文献が研究室に並んだ。
■筋電■ 筋電図からみた歩行の発達 : 歩行分析・評価への応用. 岡本勉, 岡本香代子著. 歩行開発研究所, 2007. 筋電図実践マニュアル : 各種検査法の手技とデータ解釈. シン・J・オー著/白井康正監訳/白井康正, 玉井健介, 武内俊次訳. メディカル サイエンス インターナショナル, 1999. 筋電図のための解剖ガイド : 四肢・体幹, 第3版. Aldo O. Perotto著/栢森良二訳. 西村書店, 1997. 筋電図の読み方. 木下真男, 高宮清之共著. 新興医学出版社, 1998. 筋電図・誘発電位マニュアル, 改訂3版. 藤原哲司著. 金芳堂, 1999. ニューエクササイズウォーキング : 筋電図的研究から開発した運動としての歩行. 岡本勉, 岡本香代子著. 歩行開発研究所, 2004. 脳波・筋電図用語事典, 新訂第2版. ★禁帯出 堀浩 [ほか] 共著. 永井書店, 1999. 末梢神経麻痺の評価 : 電気診断学とリハビリテーション. 栢森良二〔ほか〕編. 医歯薬出版, 1992. ■書架で発見(^_^)■ 生体計測とセンサ 戸川達男著 東京 : コロナ社, 1986.12 計測のためのフィルタ回路設計 : 各種フィルタの実践からロックイン・アンプまで. 遠坂俊昭著. CQ出版, 1998 (C&E Tutorial). 事象関連電位 : 事象関連電位と神経情報科学の発展. 丹羽真一, 鶴紀子編著. 新興医学出版社, 1997. ディジタル信号処理とDSP : パソコンによるシミュレーションとDSPプログラミング 三上直樹著 東京 : CQ出版社, 1999.11 ■自分で購入した本■ 表面筋電図. 木塚朝博, 増田正, 木竜徹, 佐渡山亜兵共著. 東京電機大学出版局, 2006 (バイオメカニズム・ライブラリー). 入門 独立成分分析 村田 昇著 , 東京電機大学出版局 (2004/07) 詳解 独立成分分析 - 信号解析の新しい世界 アーポ ビバリネン (著), エルキ オヤ (著), ユハ カルーネン (著),東京電機大学出版局 (2005/02)
SUACでは教員は50冊程度まで、3-4ヶ月、本を借りられるが、僕はいつも読んだらどんどん返却している。 ただし、1冊だけ「禁帯出」(学生なら即日返却、教員でも早期返却)の、「脳波・筋電図用語事典, 新訂第2版」が、チラッと眺めても中身が濃く、どうも原稿執筆の最後まで使いそうなので、図書館に電話して、事情を了解してもらった。 まぁ当然だが、もともと借りる人のほとんど無い本らしい。(^_^;)
そして昼前には、ゼミ卒業生の山村知世さんからメイルが届いた。 これ とか これ とか これ とか これ とか これ とか これ とか、頑張って活躍している(^_^)。 また焼津の個展に行く約束を打診するメイルを返信した。
そしてここから、少しずつ筋電センサ回路の製作を進めていると、フト、回路図のバグを発見した。 これは過去に辿ってみると、なんと「第4世代」の回路図の、OPアンプのピン配置に「+」と「-」の取り違えのミスがあった僕のバグが、そのまま回路図として残っていたことに起因しそうである。 実際にセンサを製作した時には気付いて正しく作っていたが、その回路図データのマスターを修正し忘れていたのである。(^_^;) そこで、 mbed日記(1) にあった この回路図 をまずは修正した。
そして、今回、製作しようとして気付いたミスも、おそらく照岡さんが僕のこのバグ回路図をうっかりそのまま引用しているのでは・・・と思われたので、まずはチェック確認依頼のメイルを出して、明日にでも届くだろう返信を待つことにした。 ここで焦ってあれこれ試行錯誤しても混乱の元なのである。 そして、時間があったので、図書館から借りてきた文献、約30冊をザッと眺めてみると、約20冊は「関係ナシ」「不要」などということで、あっさりと明日には返却できる、と判明した。
中には、親子で筋電図と歩行について研究している医者の自費出版の2冊があったが、表面筋電位でなく、「滅菌した微細な針を刺す」(^_^;)という、今であれば倫理委員会を通らないような電極でアーティファクトを低減させていた。 被験者が自分と奥さん、というのはまだいいが、乳幼児の歩行データを取るために、自分の娘が生まれてスグにこの「滅菌した微細な針を刺す」電極でデータを取っているのは、今なら「幼児虐待」「児童虐待」モノである。 その被験者となった娘さんも医者となって研究を継承しているようので、まぁ美談と言えば美談だが・・・。 そして。以下のように、「今後の筋電関係のためのSUAC図書館文献リスト」がスッキリと整理された。
■これだけあれば筋電情報処理関係はOK■ 生体信号処理の基礎 佐藤俊輔, 吉川昭, 木竜徹共著 東京 : コロナ社, 2004.1 生体情報計測. 星宮望著. 森北出版, 1997. 生体情報工学. 赤澤堅造著. 東京電機大学出版局, 2001 (バイオメカニズム・ライブラリー). DSP処理のノウハウ : RISC CPU(SH2)で実現する : 変復調/フィルタ/FFT/SBC/DCT. 西村芳一著. CQ出版, 2000 (Design wave books). ウェーブレット : 信号処理とシステム推定への応用. R. K. ヤング著/袋谷賢吉訳. トッパン, 1997. 基礎からのデジタル信号処理. 宇山靖政著. 東京電機大学出版局, 1995. 計測と信号処理. 鳥居孝夫著. コロナ社, 1997 (メカトロニクス教科書シリーズ:11). ディジタル信号処理の基礎 : はじめて学ぶディジタルフィルタとFFT. 三上直樹著. CQ出版, 1998 (Try computing books). 筋電図・誘発電位マニュアル, 改訂3版. 藤原哲司著. 金芳堂, 1999. 脳波・筋電図用語事典, 新訂第2版. ★禁帯出 堀浩 [ほか] 共著. 永井書店, 1999. 表面筋電図. ★持っている(^_^) 木塚朝博, 増田正, 木竜徹, 佐渡山亜兵共著. 東京電機大学出版局, 2006 (バイオメカニズム・ライブラリー). ディジタル信号処理とDSP : パソコンによるシミュレーションとDSPプログラミング 三上直樹著 東京 : CQ出版社, 1999.11 入門 独立成分分析 村田 昇著 , 東京電機大学出版局 (2004/07) ★持っている(^_^) 詳解 独立成分分析 - 信号解析の新しい世界 アーポ ビバリネン (著), エルキ オヤ (著), ユハ カルーネン (著),東京電機大学出版局 (2005/02) ★持っている(^_^)2014年10月28日(火)
朝イチで研究室に出てみると、NIMEコミュニティから訃報が届いていた。 CNMAT/IRCAMで世界のComputer MusicとNIMEの進展に貢献していた、David Wessel先生がお亡くなりになったという。 これまで、国際会議で何度となくお会いして、僕の研究/公演にもコメントをいただいたりした。 たしか、SUACで開催したNIME2004にも来日して参加してくれた、この世界での有名人である。 まだまだ精力的に元気だったのに。合掌。
そして、風邪から回復しつつある照岡さんからのメイルで、昨日、気付いた回路図のミス指摘は正しかったと確認できた。 もっとも照岡さんは照岡さんで、「以前にボルテージフォロアとして使ってたIC(部品)を、使い回しした時に、ミラー処理を忘れて、上下がひっくり返ったままになってました」とのことで、僕も照岡さんも、それぞれミスしていたのだった(^_^;)。 まぁ、ミスがあるからこそ、iPS細胞も発見され、青色LEDも発明されたので、ミスはOKなのである。 さっそく、 mbed日記(1) にあった 以下の回路図 を修正した。 これで、昨日は中途半端に ここまで だった、その続きの製作に入っていける。 今日は終日、M2のリュ君も研究室に来て修了制作のハンダ付けなので、2人でテープルに向かい合ってのハンダ付けである。(^_^)
・・・そして午後になって、なんとちょっとしたミスが1箇所あっただけで、無事に新筋電センサのフロントエンド回路部分が完成してしまった(^_^)。 写真は ここ にあるので、以下にはその中から1枚だけ置いておく。
もちろん、脱力した状態でばっちりハムが乗っているので、ここはmbedのA/D入力以降で、まずはノッチフィルタでハムの整数倍をカットするのだが、10倍ずつ3段のアンプで奇麗に信号が出ている気がするので、A/D前にアナログ的な「整流→平滑(積分)回路を入れるのでなく、このまま取り込んで料理できないか、検討してみよう。 以下は、ちょっとだけリュ君に手伝ってもらったものの、ほぼ一人で撮った動画である。(^_^;)
バッタもんの「筋電マウス(電気マッサージ電極付きマウス・中国製)」の、かなりショボい電極から、ベタベタに分厚い導電ジェル(厚さ3mmほど(^_^;))を除去しただけの超廉価電極であるが、締め付けてみると立派に働いてくれた。 CQの原稿はこの程度で十分なので信号処理に傾注するとして、新楽器については、さらに電極ベルト(もちろん純銀円盤電極(^_^))についても、検討していくことにしよう。 2014年10月29日(水)
水曜日は4-5限に「サウンドデザイン」があり、放課後にアカペラがあるので、作業はできて半日である。 今日は、今後の作業のために、「急がば回れ」ということで、半日がかりで「治具」を作り、完成させた。 過去に買っていたUSBオーディオインターフェースを解体して、NucleoF401REのA/D入力ピンに来ている筋電センサの信号をオーディオとしてMaxにレコーディングする入力と、Maxから出力する信号をNucleoF401REのA/Dに入れるために+3.3Vレンジの中点電位のオフセットを乗せて出力するアンプ、である。 写真は ここ にあるので、以下にはその中から1枚だけ置いておく。
そして午後になって、まだ先週末の喉風邪の影響が完全に消えていないので、大事をとってメンバーに「本日アカペラ休み」を連絡した。 碧風祭を控えた週でもあり、ここで風邪ひきさんを作り出してはいけない(^_^;)。 通常の講義とかであれば問題ないが、一緒の空気を喉の奥まで呼吸するアカペラでは、ちょっとでも風邪っぽい場合には「お休み」こそ、大事なのだ。
2014年10月30日(木)
阪神タイガースの夢も「にんべん」がついて「儚く」なってきたが(^_^;)、まぁ、今シーズンは十分に 熱き甲子園 を堪能してきたので、今夜、負けても、もう満腹である。CQ出版のN氏からは、「生体信号処理の原稿執筆に際して参考になる書籍を見つけました。[表面筋電図]東京電機大学出版局のものです。amazonに在庫あります。至急入手いただきたいのです」というメイルが届いたが、その本はとっくの昔に自費で購入していたのだった(^_^;)。 そこで、筋電関係の資料館として、 こんなページ を作っていくことにした。 まだまだ、これから書き込んでいくので、このURLはしばらくは非公開である。
そして、午後にちょっと医者に(薬をもらいに)出かけるまでの間に、昨日作った「治具」を活用して、今後、いちいちmbedのプログラミングをする時に筋電ベルトをつけなくていいように、筋電信号をMax6でレコーディングした。 環境ハムも乗っている30秒の長さ(サンプリング44.1KHz)のステレオサウンドファイルを3本、記録した。 写真は ここ にあるので、以下にはその中から1枚だけ置いておく。
以下は、たった一人で撮った動画である。(^_^;)
この動画の中で、Max6で この実験パッチ によって、計3回、30秒間のデータをAIFFファイルとして記録している。 以下は、 test01.aif のデータである。
以下は、 test02.aif のデータである。
以下は、 test03.aif のデータである。
最後のデータを見て気付いたが、腕の上の方の筋電ベルトをチャンネル(1)、下の方の筋電ベルトをチャンネル(2)として、オシロの画面もこれに合わせたつもりだったが、治具のコネクタを上下対称にして、ここに逆さまに差し込んだようで(^_^;)、波形を見るとチャンネルが反対になっていた。 ・・・まぁ、これは誤差ということで。(^_^;)
2014年10月31日(金)
昨日の帰りはちょっと早目に大学を出て、午後イチに行った医者とはまた別の医者で。2ヶ月分の薬を処方してもらった。 まぁ、こうやってお薬とともに生きることで、人間ドックでもひっかからなくなり(「要経過観察」・「要精密検査」→「治療中」となる)、日々、美味しいお酒も飲めるので、効くと思って続けているサプリメントと同様に、これは「人生のお伴」である。(^_^;)そこで昨日の午後の残った時間には、 筋電関係の資料館 を作り進めて終ったが、まだ途中である。 今日は碧風祭前日の全学休講日(設置日)であり、ゼミもないが生産造形の4回生の卒制相談アポがあり、さらに午後には、メディア造形有志の「お化け屋敷」の設置をフォローする予定なので、たぶん今日はmbedをがりがり進めるのでなく、 ここ をさらに拡充することになるだろう。
そして夜中に思い出してメモしておいた事は、ここで備忘録として記録しておく必要のある、 MacBookAirのSMCリセット についてである。 昨日の この動画 を撮ろうとした直前に起きたトラブルで、なんとMacBookAirにたった一つしかない外界との接点のUSBポートが、まったく何も認識しなくなったのだ。 いつもであれば、以下の「PRAMリセット」でOKなのに、これも効かなかった。
そこで慌てず騒がず、ネットで調べてみると、ちゃんと ここ に、Intel Macでバッテりーを外せないノートMacの「SMC (システム管理コントローラ) のリセット」が以下のように載っていた。 これは知らなかったので、ここで記録しておくわけだ。
- Mac の電源を切る
- 「command + option + P + R」キーを同時に押しながら電源ボタンを押す
- コンピュータが再起動し、2度目の起動音が聞こえるまでキーを押したままにする
- キーを放す
そして、朝には嬉しいメイルが届いていた。 SUACメディアデザインウィーク「スケッチング」ワークショップ の参加者募集に対して、久しぶりに、筑波大学の内山先生と、意欲ある学生2人からの参加申し込みがあった。 内山先生とは、過去に 講演 と ワークショップ のために筑波大学に行ったこともあり、一方、筑波軍団は メディアアートフェスティバル2009/文化庁メディア芸術祭浜松展 に作品展示参加してくれるとともに。 ワークショップ にも参加して、一緒に「何か作った」のである。(^_^)
- Mac の電源を切る
- 電源アダプタを電源に接続する
- 「shift + control + option」キーを同時に押しながら電源ボタンを押す
- すべてのキーと電源ボタンを同時に放す
- 電源ボタンを押してコンピュータを起動する (→ここでPRAMリセットもやればいい)
・・・そして午後に、生産の4回生と卒制についての相談が終ったところに、注文していた秋月電子のオシロが届いた。 今回の記事の信号スクリーンショットはこれでいく可能性がある。 まだmbedでなく、手に乗ったハムノイズであるが、上のように表示できた。 またまた、まだまだ、Windows XPの出番である。(^_^;)
2014年11月1日(土)
昨日は ここ をかなり進めて、15:30から「お化け屋敷」にマルチメディア室のiMacを搬入したが、まだ会場の設営が出来ていないので、「仕掛け」の設置は当日(つまり今日)の朝8時、という事になった。 そして朝から雨の降る、つまりインドア企画がスターになる碧風祭の当日となった。朝8時から11時までかかって、なんとか7セットの仕掛けのうち6セットが稼働したところで「お化け屋敷」スタートとなり、疲れ果てて研究室に戻ってぼーっとしてから買い出しに出かけて、屋台のあれこれ(お好み焼き、鹿鍋、じゃがバター、フランクフルト)とともに「SUACビール」で昼食。 疲れたので、展示を巡るのは明日にした。
・・・そして午後の苦闘3時間ほどで、上のように、ようやくmbedで実験する環境までが整った。 実際には、サンプルのLEDプリンクは走ったものの、まだXBeeでMaxにシリアルで送ることが出来ていないのだが、ここから明日にでも進めていきたい。 備忘録として、以下がmbedをMacで書き込むための手続きである。
- NucleoF401REの「+5V」ジャンパは外部電源でもOK、XBeeを動かす場合にはEXTにしておく
- デスクトップにマウントされたフォルダにバイナリをドラッグドロップして書き込むのは、1回ずつ !
- 書き込んだらエラーを無視して(^_^;)アンマウントして、USBケーブル一旦、抜いて、また挿すと、次もOK
- 書き込みOKかどうかは基板右上の赤緑LEDの点滅で確認する
2014年11月2日(日)
過去に、碧風祭で2日間ずっと雨、という事は無かった記憶があるが、やはり2日目のこの日、朝こそポツポツ降っていたが、開始時にはそこそこ天気が良くなり、予報では午後(夕方)からまた雨というものの、「出会いの広場」の屋台も、そこそこ賑わっていたようである。朝9時に「お化け屋敷」の立ち上げに行って、その後は碧風祭に出かけることもなく午前から午後まで研究室に籠って、mbedと格闘しながら昨日の続きに没頭した。 まずは、NucleoF401REのArduinoシールド上に筋電センサ回路とともに設置した秋月電子のXBeeゲタ基板の配線をチェックしたり、XBeeの入出力を繋いだループバックでMax6のserialから送った情報を送り返す部分に問題が無いことをチェックしたり・・・と少しずつ問題の可能性を潰していった。
そして遂に、「If the communication between the target MCU PA2 (D1) or PA3 (D0) and shield or extension board is required, SB62 and SB63 should be ON, SB13 and SB14 should be OFF.」という、マニュアルに上の記述を発見して、ソルダーブリッジを変更して、ArduinoのTX/RXと互換のシリアル(2)ポートからXBeeに繋ぐルートを構築した。 これは、今後NucleoF401REとXBeeを繋ぐ場合には必要となる事で、さっそく今週には院生のリュ君にも教える内容なので、以下に整理しておこう。
これで無事にXBeeを経由して、NucleoF401REとMax6が通信できるようになった。 既にArduinoで作っていたプロトコルを改良して、6つのA/Dポートの識別情報「a」〜「f」に続いてデータを送るものが、これまで2文字の16進数(00〜FF)だったのを、NucleoF401REのA/Dが12ビット精度ということで3文字の16進数(000〜FFF)に拡張して、0〜4095までの数値を転送するようにした。 Max側の実験ソフトは これ である。 かなりのいじわるテストとして、タイマー「0.01秒」、つまり10msecごとに0〜4095までの数値をインクリメントして、これを新プロトコルで38400baudのXBeeでMaxに刻々と転送して表示した様子が、以下である。 ときどきスキップしているのはご愛嬌であるが(^_^;)、なかなかに高性能である。
- SB62, SB63 (USART) → ON
PA2 and PA3 on STM32 MCU are connected to D1 and D0 (pin 7 and pin 8) on Arduino connector CN9 and ST Morpho connector CN10 as USART signals. Thus SB13 and SB14 should be OFF.- SB13, SB14 (ST-LINK-USART) → OFF
PA2 and PA3 on STM32F103CBT6 (ST-LINK MCU) are disconnected to PA3 and PA2 on STM32 MCU.
以下は、そのNucleoF401REのプログラムである。 いったん稼働し出すと、昨日の備忘メモのうち、
というのは省略して、エラー表示に「OK」をクリックすれば、消えないフォルダに新しいバイナリをドラッグドロップして、何度でも書き込めるのだった。
- 書き込んだらエラーを無視して(^_^;)アンマウントして、USBケーブル一旦、抜いて、また挿すと、次もOK
以下は、その様子のスクリーンショットである。 ようやく、1台のMacで、テスト/デバッグ用のMax6プログラムと、NucleoF401REの開発環境(Web上)とが同居して、サクサクと進められる環境が整ってきた。#include "mbed.h" Serial xbee(PA_2, PA_3); DigitalOut myled(LED1); int hex_conv(int data); int main() { int i = 1; xbee.baud(38400); while(1) { wait(0.01); i = (i+1)%4096; xbee.putc(97); xbee.putc(hex_conv(i/256)); xbee.putc(hex_conv((i%256)/16)); xbee.putc(i%16); xbee.putc(13); myled = !myled; } } int hex_conv(int data) { data = data%16; switch(data){ case 0: return(48); case 1: return(49); case 2: return(50); case 3: return(51); case 4: return(52); case 5: return(53); case 6: return(54); case 7: return(55); case 8: return(56); case 9: return(57); case 10: return(97); case 11: return(98); case 12: return(99); case 13: return(100); case 14: return(101); case 15: return(102); } return(0); }
そして、マニュアルを眺めて気付いたのは、NucleoF401REというのは、本来のメインMCUだけでなく、基板をカットできる、ホストPCとUSBで繋がる部分にも、その通信(プログラムのアップロード)用の別のMCU(こちらもARM)が載っているのだった。 なるほど、それでMacOSXであっても、ちゃんとドライブとしてマウントされるのだ。 これまで、Arduinoや他のmbedの場合には、MCUが「ときにプログラムのアップロード」「ときに本番のプログラム動作」というのを一人二役で切り替えていたので、リセット回りが煩雑だったのに対して、画期的なアイデアである(^_^)。 ここで15時になった。 碧風祭はあと2時間で、今度は撤収が待っている。 ここまでをWebに上げて、ちょっと覗いてみようか、という感じである。
2014年11月3日(月)
祝日である。 例年、碧風祭の天気がいいのは、この「晴れの特異日」である11/3を含んでいるからなのだが、今年は週末の関係でズレたために雨に祟られたのだ(^_^;)。 ちなみに、一昨日と昨日の碧風祭の様子は、 これ と これ である。 今年は天気が悪くて「出会いの広場」にあまり出なかったこともあり、ほぼ「お化け屋敷のメイキング」となっている。お休みで邪魔も入らず、研究室に出てみてもほとんどメイルも届いていない。 上に並べたのは、今朝、目覚めた瞬間に思い付いてメモしていた内容である。 ここらに向けて、今日はがんがん攻めて、mbedをモノにしてしまおう、という作戦なのだ。
- FORMAT 24bits化
- サブモジュール化 include
- loopスピード評価
- シリアル受信(→双方向通信)
- 割り込み
- システムタイマー
- A/D
- D/A
- MIDI
- イベントドリブン
まず最初に確認したいのは、上のように、昨日、動作を確認した「test001_XBee」というプログラムでは、「mbed」というフォルダ以下のライブラリを「build」したのだが、これは中にあるヘッダファイルを見るためには有効でも、開発において「必須」では無いのか、という事である。 そこで「test001_XBee」でなく、サンプルにあった、「mbed」をビルドしていないプログラムを複製して、ここに同じ「main.cpp」をコピーしてコンパイル→実行してみると、ちゃんと動作した。 結論として、「mbed」のビルドはとりたてて必要ではないと判った。 ただし、せっかくヘッダファイルが直接に読めるので、「test001_XBee」だけは、参照用として残しておこう。
次に行ったのは、XBeeを経由してMax6と通信するプロトコルである。 上にあるのは、過去にArduinoの6チャンネルA/D情報をMax6に送るために作ったパッチを改造して作った、昨日の「12ビットデータ」版のMaxパッチである。 これは昨日の日記で書いたように、NucleoF401REのA/Dが12ビット精度ということで3文字の16進数(000〜FFF)に拡張して、0〜4095までの数値を転送したものである。 しかし考えてみれば、このトリッキーなパッチは、Arduinoのストリング生成ライブラリが、例えば8ビットデータを16進数表現すると、2桁にならない「0〜15」の場合には「00〜0f」でなく「0〜f」と長さが変わることに対応したものだった。
ところが今回のNucleoF401REでは、「printf()」でなく「putc()」を使っているので、末尾のリターンコード「13」を含めて、全てのデータを直接、ハンドリング出来るので、冒頭にわざわざ「a=」などの文字列を付けることも不要で、ナマの16ビットデータ(0〜65535)でも24ビットデータ(0〜16777215)でも送れるのだった。 mbedはたぶん32ビットCPUなので、せめて外界とのやりとりは24ビットデータで行いたい。 そうなれば、たった6ポートで「a」から「f」までを識別するだけでなく、20ビットデータの上にある4ビットで、最大16種類のプロトコルを新たに定義してもいいのである。 そこで、過去に 続・Propeller日記(1) でやったような方法で、まずは以下のようにプロトコルを定義してみた。
これによって、過去にAKI-H8とかArduinoとかPropellerで作ってきた、「アナログ→MIDI」機器と同様に、センサ等のアナログ情報は最大128種類の識別とともに0〜127の分解能で表現できるし、精度を上げたければ「生データ伝送として、000000〜7FFFFFの23ビットデータ」を使ってもいいし、さらに 続・Propeller日記(3) のように、拡張MIDIプロトコルの中にトリッキーに機能拡張を盛り込むことも出来る。
- NucleoF401RE〜XBee ←【この部分のプロトコル定義】→ XBee〜Max6
- 通信は38400のスピードで双方向、同一フォーマットとする
- データは常に7バイト単位で、最終バイトのデータは「CR」=13、とする
- 先頭から6バイトは16進文字(0〜9,A〜F)で大文字(a〜f はNG)で、000000〜FFFFFFの24ビットデータとなる
- データのMSBがゼロである場合は、生データ伝送として、000000〜7FFFFFの23ビットデータとする
- データのMSBが1である場合は、最上位8ビット[8n〜Fn](n=0〜F)を。以下のように定義する
- 最上位8ビット[8n](n=0〜F) - MIDIステータス[8n]に準じて、3-4バイトと5-6バイトのMSBはゼロ
- 最上位8ビット[9n](n=0〜F) - MIDIステータス[9n]に準じて、3-4バイトと5-6バイトのMSBはゼロ
- 最上位8ビット[An](n=0〜F) - MIDIステータス[An]に準じて、3-4バイトと5-6バイトのMSBはゼロ
- 最上位8ビット[Bn](n=0〜F) - MIDIステータス[Bn]に準じて、3-4バイトと5-6バイトのMSBはゼロ
- 最上位8ビット[Cn](n=0〜F) - MIDIステータス[Cn]に準じて、3-4バイトのMSBはゼロ、5-6バイトは無効
- 最上位8ビット[Dn](n=0〜F) - MIDIステータス[Dn]に準じて、3-4バイトのMSBはゼロ、5-6バイトは無効
- 最上位8ビット[En](n=0〜F) - MIDIステータス[En]に準じて、3-4バイトと5-6バイトのMSBはゼロ
- 最上位8ビット[Fn](n=0〜F) - reserved
そして上のように、24ビット転送バージョンのMax6側の受信パッチが完成した。 NucleoF401REのメインループでは、タイマー「0.01秒」、つまり10msecごとに0〜16777215までの数値をインクリメントして38400baudのXBeeでMaxに刻々と転送しているつもりだが、データが抜け落ちているかは、ちゃんと確認していないので、まだ不明である(^_^;)。 そして、今後、オリジナルとして汎用に使えそうなソフトウェア部品をmain()から切り離すための実験をして、新たなヘッダファイル「submodule01.h」をメインでincludeすればいい、と解明して、以下のように改訂したプログラムでも同じ動作をする事を確認して、今朝のテーマのうち次の2件が片付いた。
- FORMAT 24bits化
- サブモジュール化 include
main.cpp
#include "mbed.h" #include "submodule01.h" int main() { int i = 0; xbee.baud(38400); while(1) { wait(0.1); i = (i+1)%16777216; hex_send(i>>16 & 255); hex_send(i>>8 & 255); hex_send(i & 255); xbee.putc(13); } }submodule01.h
コンパイラから鬱陶しい「Warning」をもらわないための、ここまでの経験則・注意点としては、以下がある。Serial xbee(PA_2, PA_3); int hex_conv(int data) { data = data%16; switch(data){ case 0: return(48); case 1: return(49); case 2: return(50); case 3: return(51); case 4: return(52); case 5: return(53); case 6: return(54); case 7: return(55); case 8: return(56); case 9: return(57); case 10: return(65); case 11: return(66); case 12: return(67); case 13: return(68); case 14: return(69); case 15: return(70); } return(0); } int hex_send(int data) { data = data & 255; xbee.putc(hex_conv(data / 16)); xbee.putc(hex_conv(data % 16)); return(0); }「関数の位置(前後関係)」とは、コンパイラはincludeされた上から順に処理するので、定義されていないものが出て来るとエラーとなる。 例えば上のsubmodule01.hの場合に、まず「int hex_conv(int data)」があり、その下にこれを参照する「int hex_send(int data)」があるのでエラーが出ない。 これを逆に配置すると、まず「int hex_send(int data)」を処理する際に、その中にある「int hex_conv(int data)」が定義されていない、と叱られる(^_^;)。 この場合、冒頭にまず1行「int hex_conv(int data);」というのを置けばいいというのが最近のC言語なのだが、まぁ面倒であれば新たに作ったものは下に追加していけばいい。
- 関数の位置(前後関係)
- リターンを置く
「リターンを置く」というのは、例えば上のsubmodule01.hの場合には、「int hex_conv(int data)」に食わせたデータへの反応としては、内部のswitch()で全ての場合が尽くされているので、最後の「return(0);」は不要である。 ところがコンパイラは形式的Warningとして、以下のように文句を言ってくるのである(^_^;)。 仕方ないので、「なんでもいいので最後はreturn(0);」という対応をすると、「!」マークは現れない。
次に行ったのが、「loopスピード評価」である。 まだNucleoF401REについてよく知らないので、まずはその実力をおよそ見てみよう、という事である。 XBee経由の通信でのデータ落ちという要因を避けるために、まず「main.cpp」を以下のように改訂してみた。
main.cpp
つまり、メインループにあった「wait(0.1);」というのを止めて(こんな無駄足を入れるプログラムは実用的ではない(^_^;))、パラメータ「j」をひたすらインクリメントして、100万回に一度だけ、XBeeで送るデータをインクリメントして送信する、というプログラムである。 これを受けるMax6パッチの画面は以下のようになった。#include "mbed.h" #include "submodule02.h" int main() { int i = 0; int j = 0; xbee.baud(38400); while(1) { j++; if(j > 1000000){ j = 0; i = (i+1)%16777216; hex_send(i>>16 & 255); hex_send(i>>8 & 255); hex_send(i & 255); xbee.putc(13); } } }
目視で、XBeeから届くデータは毎秒数回程度で、データはきちんとスキップなくインクリメントされた。 このパッチでは、データが届くたびにゲートをON/OFFして、ゲートONの期間に「metro 10」、つまり10msec間隔のbangが何発来ているか、をカウンタで計測して、それを刻々とmultisliderで表示している。 MacOSはUnixなのでバックグラウンド処理によりでこぼこがあるが、ほぼ平均して「25発」という成績であった。 つまり、およその成績としては、以下のようになる。 さすがに、まずまず、速い。(^_^)
NucleoF401REの無限ループ1000000回 ≒ 250msec NucleoF401REの無限ループ1000回 ≒ 250μsec
NucleoF401REの無限ループ1回 ≒ 250nsec
ここで昼休みを経て、午後はまず「NucleoF401REでのXBeeからのシリアル受信」である。 まだ、「NucleoF401RE〜XBee ←【この部分のプロトコル定義】→ XBee〜Max6」というやつの半分、「→」の方向しか出来ていないので、デバッグ等に活用するためにも、ここはまず、双方向に発展させる必要がある。 いずれ最終的には割り込みで記述したいが、十分に速いようなので、まずはポーリングからである。 開発ツールの「Serial Class Reference」を見ると、「Serial」(の一部)は以下のようになっている。
これはおそらく、「readable()」だろう。 解説として「Determine if there is a character available to read.」とあり、戻り値は「1 if there is a character available to read, 0 otherwise」とある。 ここにおそらく、「xbee.getc()」で取れるのでは・・・という読みである。#includeInherits mbed::SerialBase, and mbed::Stream. Public Member Functions Serial (PinName tx, PinName rx, const char *name=NULL) Create a Serial port, connected to the specified transmit and receive pins. void baud (int baudrate) Set the baud rate of the serial port. void format (int bits=8, Parity parity=SerialBase::None, int stop_bits=1) Set the transmission format used by the serial port. int readable () Determine if there is a character available to read. int writeable () Determine if there is space available to write a character. void attach (void(*fptr)(void), IrqType type=RxIrq) Attach a function to call whenever a serial interrupt is generated. ・・・しかし、ここで難航することとなった。 「if(xbee.readable() == 1){」として、シリアル受信バッファにキャラクタが存在したら送信を・・・と思っているのに、エラーでなくWarningで「Warning: Statement is unreachable in "main.cpp"」と出て、つまり暖かく無視されて、実行できないのだ。 調べているうちに、シリアルの定義に、ストリーム不要の「RawSerial xbee(PA_2, PA_3);」というのを見つけたが、ここでも「xbee.readable()」は同じWarningで受け付けられなかった(^_^;)。 こうなれば、もうイキナリ、王道の「割り込み」で行くしかない。
そして、困った時には ここ と立ち返って、検索に「serial attach pointer」と入れてみると、ちゃんと こんな サンプルに到達して、これをインポートしてソースを眺めてみると、以下のようなサンプルがあった(一部)。
このプログラムは一見、良さげであるが、実はバッファのポインタ処理(インクリメントとマスクの処理)が抜けている大バグ有りであるが、やり方は判ったので、もうぱっくりとイタダキである。 とりあえず、Max6からはたった1文字の16進データを送ることにした。 これをNucleoF401REの方では、上のパラメータ「j」をひたすらインクリメントして、100万回に一度だけ、0〜16777215までの数値をインクリメントして38400baudのXBeeでMaxに送信するとともに、XBeeからの受信割り込みがあると、その4ビットデータを8ビット3ブロックのそれぞれ上位4ビットとして、つまり「F0」を受信すると「F0F0F0(cr)」というデータとしてさらに送る、という以下のプログラムにしてみた。#include "mbed.h" #include "ctype.h" #define BFR_SIZE 32 // Needs to be a power of two. #define BFR_WRAP_MASK 0x1F // Modulo 32 unsigned char rcvBuffer[BFR_SIZE]; // Receive buffer. unsigned char inIdx; // Read by background, written by Int Handler. unsigned char outIdx; // Read by Int Handler, written by Background. DigitalOut myled(LED1); Serial pc (USBTX,USBRX); void SerialRecvInterrupt (void) { unsigned char c; c = pc.getc(); // On receive interrupt, get the character. while (!pc.writeable()){}; // Wait until xmit buffer is empty. pc.putc(c); // Echo char back out the serial port. while (!pc.writeable()){}; // Make sure transmit buffer is empty. pc.putc(toupper(c)); // Echo the uppercase value of input char. } int main() { int i; for (i=0; i < BFR_SIZE; i++) { // Clear buffer if you have OCD. rcvBuffer[i] = 0; } inIdx = 0; // Initialize bfr input and output pointers. outIdx = 0; pc.baud(38400); // USART to PC USB USART. pc.attach (&SerialRecvInterrupt, pc.RxIrq); // Recv interrupt handler pc.printf ("Serial interrupt demo\n"); while(1) { myled = 1; wait(0.2); myled = 0; wait(0.2); } }Max6のパッチも改良して、以下のように、たった1文字の16進データ(この例では「F」)をmetroで等間隔に叩いて送ることとして、その時間間隔を「2000msec」・「1000msec」・「500msec」・「250msec」・「100msec」と、次第に密度を上げてみて、どのくらい、この通信システムが「へこたれる」のか(^_^;)、をテストしてみた。#include "mbed.h" #include "submodule02.h" void receiver(){ int data; data = xbee.getc(); hex_send(data<<4); hex_send(data<<4); hex_send(data<<4); xbee.putc(13); return; } int main() { int i = 0; int j = 0; xbee.baud(38400); xbee.attach(&receiver, xbee.RxIrq); while(1) { j++; if(j > 1000000){ j = 0; i = (i+1)%16777216; hex_send(i>>16 & 255); hex_send(i>>8 & 255); hex_send(i & 255); xbee.putc(13); } } }
その結果を記録してみた動画が以下である。 さっきの実験の、何もない状態で「ほぼ平均して25発」という数値が、何故か5つ刻みではあるものの、Max6からNucleoF401REへの送信データがあるとときどき低下して、さらにその時間密度を上げると相当に影響されるのが良く判る。 まだ受信割り込みにはFIFOを使っていないし、逆に送信についてはバッファフルのチェックすら入れていないので、かなり未完成であるものの(つまりMIDIデータのように1バイトの欠落も許されないような使い方は無理)、とりあえず、双方向の通信が出来る、と確認できた。
YouTube ・・・そしてここから、遂に本格的にXBeeでの通信に受信だけでなく送信にもFIFOバッファを積む必要がある・・・と判明して、ちまちまプログラミングを始めたところで夕方になった。 基本的にはAKI-H8でアセンブラで書いたものも、Propellerでアセンブラとspinで書いたものも、かつてIndyでC言語で書いたものも、皆んな同じことをしているのだが、だいぶ久しぶりということで、ここらが一日の仕事の限界である。 まずまず今日も進んだので、明日にはリュ君の作業と並行して、さらに進めていくことにしよう。
2014年11月4日(火)
連休も終わり、だいぶ肌寒くなってきた。 またまた夜中に目覚めてメモしていた内容を、7時前から研究室で実験する朝となった。 昨日のmbed実験で、後半の実験で「何故か5つ刻みのデータ」というところが気になっていて、以下の2点に気付いたのだ。 この両方の要因を解消して、新たにこの「NucleoF401RE〜XBee ←【通信】→ XBee〜Max6」というシステムの性能をチェックしておきたい、という事である。そこでさっそく、Max6のserialオブジェクトを叩くところを「qmetro 2」に改訂し、いっぽうNucleoF401REのプロクラムを以下のように改訂して実験した。 以下はメインループの「足踏み」時間として「wait(0.01);」、つまり10msecごとにXBee経由で7バイトフォーマットのデータを、tx_readyをチェックしつつ、連続的に送信する。
- Max6のserialオブジェクトを叩くmetroが50msecというのは遅いか?
- NucleoF401REのシリアル連続送信で、tx_readyをチェックする必要あり
main.cpp
#include "mbed.h" #include "submodule01.h" int main() { int i = 0; xbee.baud(38400); while(1) { wait(0.01); i = (i+1)%16777216; hex_send(i>>16 & 255); hex_send(i>>8 & 255); while(!xbee.writeable()){}; hex_send(i & 255); xbee.putc(13); } }submodule01.h
上のブログラムでは「wait(0.01);」、つまり10msecごとにXBee経由で7バイトフォーマットのデータを連続的に送信しているが、この時間間隔を「10msec」「5msec」「2msec」「1msec」「500μsec」の5種類で実験してみた。 受け取る方のMax6のパッチも、速度計測の部分を改訂して、一定間隔でゲートを開けて、その時間内に7バイトフォーマットのデータが何発、届いたか、をグラフで表示した。 当然、NucleoF401REの平均転送速度(時間あたりのデータ密度)が上がると、対応してゲートを開ける時間を変化させて、ほどよく見えるようにした。RawSerial xbee(PA_2, PA_3); int hex_conv(int data) { data = data%16; switch(data){ case 0: return(48); case 1: return(49); case 2: return(50); case 3: return(51); case 4: return(52); case 5: return(53); case 6: return(54); case 7: return(55); case 8: return(56); case 9: return(57); case 10: return(65); case 11: return(66); case 12: return(67); case 13: return(68); case 14: return(69); case 15: return(70); } return(0); } int hex_send(int data) { data = data & 255; while(!xbee.writeable()){}; xbee.putc(hex_conv(data / 16)); while(!xbee.writeable()){}; xbee.putc(hex_conv(data % 16)); return(0); }ここで先に、最大性能の「理論値」をおさらいしておこう。 今回は38400bpsであるが、経験則の「MIDIの3バイト情報の転送はおよそ1msecが最大性能」というのと比較して確認する事になる。 38400bpsということは、「毎秒38400ビット」のシリアル通信である。 今回のフォーマットはdefaultのままで、
ということで、データ8ビット、パリティビット無し、ストップビット=1ビットで、必ず必要なスタートビット=1ビットと合わせて、1バイトの転送に10ビットが必要である。 そこで、「1データ(1バイト+スタート+ストップ)=10ビット」ということから、「毎秒38400ビット」→「毎秒3840データ」という事になる。void format ( int bits = 8, Parity parity = SerialBase::None, int stop_bits = 1 ) [inherited] Set the transmission format used by the serial port. 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)MIDIは31250bpsで、同じ「データ8ビット、パリティビット無し、ストップビット=1ビット」なので「毎秒3125データ」=「1msecあたり3.125データ」であり、経験則の3バイトメッセージという事は「3.125/3≒1.04」から「1msecあたり1.04メッセージ」という事で「1メッセージはおよそ1msec」という事だった。 今回の38400bpsは単純にMIDIよりも38400/31250≒1.229倍だけ高速になるので、「1msecあたり3.84データ」であり、今回のデータ転送フォーマットは既に定義したように1メッセージが「6バイトHEX(24ビットデータ)+1バイト(cr)」の計7データなので、1メッセージの転送にかかる最小時間(最大性能)は「約1.823msec」という事になり、ギッシリとデータが連続しているという理論値では、毎秒あたり548メッセージほどの伝送が限界である。
上は、NucleoF401REのメインループの「足踏み」時間として「wait(0.01);」、つまり10msecごとにXBee経由で7バイトフォーマットのデータを、tx_readyをチェックしつつ、連続的に送信したものである。 Max6の受信プログラムの方では、ゲートを開ける時間を200msecとしている。 メインパッチ内のマルチスライダーは奇麗な鋸歯状波となっていて、データの欠落は無さそうである。 メインパッチの右側のサブパッチ内のグラフ(multislider)の縦軸は「0〜50」となっているので、スクリーンショットを問った瞬間の「18」という値よりも、最も多い実効値としては「15」あたりである。 つまりこの状態では、毎秒75〜90メッセージを受け取っていて、またまだ「余裕」である。(^_^)
上は、NucleoF401REのメインループの「足踏み」時間として「wait(0.005);」、つまり5msecごとにXBee経由で7バイトフォーマットのデータを、tx_readyをチェックしつつ、連続的に送信したものである。 Max6の受信プログラムの方では、ゲートを開ける時間は、同じ200msecとしている。 メインパッチの右側のサブパッチ内のグラフは、スクリーンショットを問った瞬間の「29」という値がほぼ実効値であり、この状態では、毎秒145メッセージを受け取っていて、またまだ「余裕」である。(^_^)
上は、NucleoF401REのメインループの「足踏み」時間として「wait(0.002);」、つまり2msecごとにXBee経由で7バイトフォーマットのデータを、tx_readyをチェックしつつ、連続的に送信したものである。 Max6の受信プログラムの方では、ゲートを開ける時間を100msecとしている。 メインパッチの右側のサブパッチ内のグラフは、スクリーンショットを問った瞬間の「38」よりも、実効値はもっと低い「30」付近であり、毎秒150メッセージを受け取っている事になり(飽和している)、あまり変化していない。 メインパッチ内のマルチスライダーを見ると、鋸歯状波のトップ付近がでこぼこしていて、XBeeを経由するデータ伝送のどこかで、ややデータの欠落が起きているようにも見える。 1メッセージの転送にかかる限界の理論値が「約1.823msec」とすれば、この2msecというのは実質的には限界を越えつつある、というところだろう。
上は、NucleoF401REのメインループの「足踏み」時間として「wait(0.001);」、つまり1msecごとにXBee経由で7バイトフォーマットのデータを、tx_readyをチェックしつつ、連続的に送信したものである。 Max6の受信プログラムの方では、ゲートを開ける時間を50msecとしている。 メインパッチ内のマルチスライダーを見ると、鋸歯状波のトップ付近のでこぼこが顕著になり、XBeeを経由するデータ伝送のどこかで、データの欠落が起きている。 メインパッチの右側のサブパッチ内のグラフにも大きな変化があり、頻繁にデータ落ちがあるように見える。 は、スクリーンショットを問った瞬間の「13」を実効値とすれば、毎秒260メッセージを受け取っていることになるが、これは実力値としてはちょっと怪しくなってくる。 1メッセージの転送にかかる限界の理論値の2倍の密度で無理矢理に送ろうとしているので、これは仕方ない。
上は、NucleoF401REのメインループの「足踏み」時間として「wait(0.0005);」、つまり500μsecごとにXBee経由で7バイトフォーマットのデータを、tx_readyをチェックしつつ、連続的に送信したものである。 Max6の受信プログラムの方では、ゲートを開ける時間を25msecとしている。 ここまで来ると、Max6の受信プログラムの方の表示もかなり危ういし、1メッセージの転送にかかる限界の理論値の4倍の密度ということで、これはもう、「駄目よ、駄目駄目・・・」という事である。(^_^;)
そして朝9時過ぎにM2のリュ君が研究室にやって来て、 このように 頑張って、とりあえず修了制作のシステムのハンダ付けが完了した。 来週はいよいよ、NucleoF401REのプログラミングである(^_^)。
そして午後にリュ君が帰ってから夕方まで、NucleoF401REとMax6との双方向シリアル通信をFIFOバッファ形式にする、という、けっこう山場となるプログラミングに取りかかった。 これがいつものように難航しているところに、 このように 注文していた以下のブツが届いた。
この届いたブツに目もくれず、さらに行き詰まって苦しんでいると、卒業生の野口さんがやって来た。 そこでプチ人生相談をして(本人はいま横で「大人生相談だ」と言っているが(^_^;))、コーヒーを入れてもらって試行錯誤していると、なんと、何故か、急転直下、解決してしまった(^o^)。 ここで とりあえずのversion としてfixしておくことにした。 以下がその「test003_XBee」というNucleoF401REソースプログラムと、Max6のスクリーンショットである。main.cpp
#include "mbed.h" #include "submodule02.h" void tx_fifo_check(){ int data; if(tx_top != tx_end){ data =txFIFO[tx_end]; hex_send(data>>16 & 255); hex_send(data>>8 & 255); hex_send(data & 255); half_send(13); ++tx_end &= 63; } return; } int rx_fifo_check(){ unsigned char data; if(rx_top != rx_end){ data = rxFIFO[rx_end]; ++rx_end &= 255; if (data < 33){ phase = 0; return(1); } raw_data[phase] = data; if(++phase > 5) phase = 0; return(0); } return(0); } int main(){ int i, j, k, sum; i = j = k = 0; rx_top = rx_end = tx_top = tx_end = phase = 0; xbee.baud(38400); xbee.attach(&rx_fifoset, xbee.RxIrq); k = 14777215; while(1){ tx_fifo_check(); if(rx_fifo_check() == 1){ sum = 0; for (i=0; i<6; i++){ sum += conv_hex(raw_data[i])<<(4*(5-i)); } tx_fifoset(sum); } if(++j > 1000000){ j = 0; myled = !myled; k &= 16777215; tx_fifoset(k++); } } }submodule02.h
unsigned char rxFIFO[256], raw_data[6]; unsigned char rx_top, rx_end, tx_top, tx_end, phase; int txFIFO[64]; RawSerial xbee(PA_2, PA_3); DigitalOut myled(LED1); void rx_fifoset(void){ rxFIFO[rx_top] = xbee.getc(); ++rx_top &= 255; return; } void tx_fifoset(int data){ txFIFO[tx_top] = data; ++tx_top &= 63; return; } unsigned char hex_conv(unsigned char data){ data &= 15; if(data < 10) return(data+48); else return(data+55); } unsigned char conv_hex(unsigned char data){ if((data > 47) && (data < 58)) return(data-48); else if((data > 64) && (data < 71)) return(data-55); return(0); } void hex_send(unsigned char data){ while(!xbee.writeable()){}; xbee.putc(hex_conv(data / 16)); while(!xbee.writeable()){}; xbee.putc(hex_conv(data % 16)); return; } void half_send(unsigned char data){ data &= 15; while(!xbee.writeable()){}; xbee.putc(data); return; }
だいぶリハビリ効果で、久しぶりのC言語で、いつものFIFOバッァリングを送信・受信ともに実装できた。 実際には「6バイトHEX文字+(cr)」という変則フォーマットなので、NucleoF401REの側では、受信FIFOは256バイトの深さで1文字単位(unsigned char)で格納しているが、送信FIFOの方は24ビットデータ(int)で64ワードの深さとしている。
2014年11月5日(水)
漬け物でもカレーでも、「一晩、寝かせる」と味が出て来る。 そして昨日、ようやく完成に至った筈のNucleoF401REのFIFOプログラミングに重大な欠点がある事に気付いたのも、一晩、寝た後であった(^_^;)。 シリアル受信については、「xbee.attach(&rx_fifoset, xbee.RxIrq);」と受信割り込みのハンドラを規定して、その「void rx_fifoset(void)」では受信した1バイトデータを256段のFIFOに積んでいて、メインルーチンから呼ばれる「rx_fifo_check()」の中では、FIFOから読み出したデータを6バイトの配列に格納して、7バイトフォーマットの最後の「13(cr)」を確認してイベント発生を通知しているので、ここまでまったく無駄が無い。 この部分は、完成している。ところがシリアル送信については、考えてみればFIFOを6バイトHEXの格納された24ビットの「int」配列を64段に積んでいるが、メインルーチンから呼ばれる「tx_fifo_check()」の中では、FIFOから読み出すべきデータがある時の処理に、重大な欠陥があった。 取り出した24ビットデータを4ビットずつのHEX1文字データとして「hex_send(unsigned char data)」を読んでいるが、なんとここでは、「while(!xbee.writeable()){};」と足踏みしてシリアルポートのready、つまりバッファが空になってUARTにデータを書き込めるまで、ただ待っているので、これはまったく駄目なのだった(^_^;)。 そこでまず、以下のようにMax6側のチェッカーを「NucleoF401RE_001.maxpat」と統合・改訂した。 スクリーンショットでは、サブパッチ内での処理も可視化した。
そして、現状の「駄目な送信FIFOもどき」の処理能力を明確に計測して、その後に「本当の送信FIFO」を作って、その新たな処理能力を計測して比較するために、チェッカーを「NucleoF401RE_001.maxpat」から「NucleoF401RE_002.maxpat」に改訂するとともに、その計測がより明確になるように、昨日の「駄目な送信FIFOもどき」の「test003_XBee」というソースプログラムの「main.cpp」だけを以下のように変更して、「submodule02.h」は、敢えてそのままにした。
main.cpp
#include "mbed.h" #include "submodule02.h" void tx_fifo_check(){ int data; if(tx_top != tx_end){ data =txFIFO[tx_end]; hex_send(data>>16 & 255); hex_send(data>>8 & 255); hex_send(data & 255); half_send(13); ++tx_end &= 63; } return; } int rx_fifo_check(){ unsigned char data; if(rx_top != rx_end){ data = rxFIFO[rx_end]; ++rx_end &= 255; if (data < 33){ phase = 0; return(1); } raw_data[phase] = data; if(++phase > 5) phase = 0; return(0); } return(0); } int main(){ int i, j, k, l, sum; i = j = k = l = 0; rx_top = rx_end = tx_top = tx_end = phase = 0; xbee.baud(38400); xbee.attach(&rx_fifoset, xbee.RxIrq); while(1){ tx_fifo_check(); if(rx_fifo_check() == 1){ sum = 0; for (i=0; i<6; i++){ sum += conv_hex(raw_data[i])<<(4*(5-i)); } tx_fifoset(sum); } if(++j > 1000000){ j = 0; myled = !myled; } if(++l > 2000){ l = 0; k &= 8388607; tx_fifoset(k++); } } }つまり、NucleoF401REが内部的にインクリメントする数値はMSBの立たない23ビット(0〜8388607)としてMaxに連続的に送信し、Maxから一定間隔で送られてシリアル受信→シリアル送信にエコーバックする数値は、23ビット乱数にMSBを立たせた値(8388608〜16777215)とすることで、チェッカーではそのルーツを区別してカウントできるようにしたのである。 データの間隔としてはちょっと実験してみて、理論的最大密度の1/2〜1/3となるよう調整して、メインルーチン2000回ごとに1メッセージを送るようにした。 ボード上のLEDの点滅は「メインルーチン1000000回ごとに1回」としていて、見たところではおよそこれが「2〜3秒」という周期であった。
これを使って、実際に現状の「駄目な送信FIFOもどき」をまず計測したのが以下である。 4つのテストでは、「metro 100」、つまり0.1秒ごとに到達した7バイト形式メッセージが何発だったか、を記録しているが、その間にMax6側から送ったメッセージの状態が
というように、それぞれ違っている。
- Max6からの送信ナシ
- Max6から50msecごとに1メッセージを送信してエコーバック
- Max6から40msecごとに1メッセージを送信してエコーバック
- Max6から30msecごとに1メッセージを送信してエコーバック → 途中で通信が死んだ(^_^;)
これで、今度はこのチェッカープログラム「NucleoF401RE_002.maxpat」を変更しないで、NucleoF401REのプログラムの方を「本当の送信FIFO」に変更したものと比較することになる。 そこで、NucleoF401REの「test003_XBee」というソースプログラムを「test004_XBee」に複製・リネームして、その中を「main.cpp」と「submodule03.h」という構成に定義して、その両方の修正に取りかかった。 ・・・そして集中すること約30分、遂に以下のように、プログラム「test004_XBee」が完成した。(^_^)
main.cpp
#include "mbed.h" #include "submodule03.h" void tx_message(int data){ int i; for (i=0; i<6; i++){ tx_fifoset(hex_conv((data>>(4*(5-i))) & 15)); } tx_fifoset(13); } int main(){ int i, j, k, l, sum; i = j = k = l = 0; rx_top = rx_end = tx_top = tx_end = phase = 0; xbee.baud(38400); xbee.attach(&rx_fifoset, xbee.RxIrq); while(1){ tx_fifo_check(); if(rx_fifo_check() == 1){ sum = 0; for (i=0; i<6; i++){ sum += conv_hex(raw_data[i])<<(4*(5-i)); } tx_message(sum); } if(++j > 1000000){ j = 0; myled = !myled; } if(++l > 10000){ l = 0; k &= 8388607; tx_message(k++); } } }submodule03.h
unsigned char rxFIFO[256], txFIFO[256], raw_data[6]; unsigned char rx_top, rx_end, tx_top, tx_end, phase; RawSerial xbee(PA_2, PA_3); DigitalOut myled(LED1); void tx_fifo_check(){ if(xbee.writeable() == 1){ if(tx_top != tx_end){ xbee.putc(txFIFO[tx_end]); ++tx_end &= 255; } } return; } int rx_fifo_check(){ unsigned char data; if(rx_top != rx_end){ data = rxFIFO[rx_end]; ++rx_end &= 255; if (data < 33){ phase = 0; return(1); } raw_data[phase] = data; if(++phase > 5) phase = 0; return(0); } return(0); } void rx_fifoset(void){ rxFIFO[rx_top] = xbee.getc(); ++rx_top &= 255; return; } void tx_fifoset(unsigned char data){ txFIFO[tx_top] = data; ++tx_top &= 255; return; } unsigned char hex_conv(unsigned char data){ data &= 15; if(data < 10) return(data+48); else return(data+55); } unsigned char conv_hex(unsigned char data){ if((data > 47) && (data < 58)) return(data-48); else if((data > 64) && (data < 71)) return(data-55); return(0); }
上は、同じチェッカープログラム「NucleoF401RE_002.maxpat」の動作中の画面例であり、「Max6から40msecごとに1メッセージを送信してエコーバック」という、かなりキツ目の条件である。 これ以上、30msecにすると「死ぬ」のは同じなので、この部分は同様である。 ところが、NucleoF401REのメインルーチンのデータ送出頻度はちょっと実験してみて、ループのカウンタ値(変数インクリメントで内部処理に入る回数)を、さきほどの2000回から10000回にしてある。 つまり、1/5の頻度なのに、ほぼ同じ処理を実行できている事になる。 ボード上のLEDの点滅は同じ条件の「メインルーチン1000000回ごとに1回」であり、当然ながらおよそこれが「1秒に2回程度」と速くなった。
これはつまり、ほぼ同じ限度ぎりぎりの双方向通信をする際に、昨日までの「駄目な送信FIFOもどき」では、およそ5倍の頻度で送信ルーチンを読んでは足踏みしていたのに対して、無駄のないこちらの「本当の送信FIFO」では、およそ5倍の性能アップとなった事を意味する。 LED表示の変数「j」のカウントアップ100万回の所要時間が1/5程度になったという事は、その間におよそ5倍の余計な仕事を突っ込んでも構わないという事であり、メインルーチンの処理能力として5倍程度になった、というわけである。 この実験だけではあまり顕在化していないが、多数のジョブを並行して走らせて、特に今後のA/D変換のタイマー割り込みなどを実装した場合、ここで無駄足をふまない事は、ホストとのXBee通信で処理性能が落ちない、という重要なメリットになるのである。
朝から 「スケッチング」ワークショップ に関連して10本以上のメイルをやりとりさせたり、事務仕事(書類書き)などを交えつつも、地味に進展したところで、3限がほぼ終わりとなった。 4-5限は「サウンドデザイン演習」、いよいよjitter大会となり、放課後は先週お休みしたアカペラである。 明日は「鳥獣戯画」の後半展などのために京都に日帰りするので、次のmbedは金曜日以降となるが、まずまず、この区切りまで到達したのは良かった。(^_^)
2014年11月7日(金)
昨日は、京都日帰りで、 「スケッチング」ワークショップ 「鳥獣戯画」の後半展 に行ったが、前回、先月に行った前半展と昨日とは、「それぞれ展示開始から数日」・「同じ木曜日」・「同じ10時に並び始め」とほぼ同じ条件だった。 しかし、ほぼ同じ条件なのに、と、人気と話題のためか、相当に混んでいた。 僕は「並んで待つ」というのが嫌いなので、家族は行っても留守番してディズニーランドとかに行かない人間なのだが(^_^;)、今回はじっと130分、並んだが、やはり今回も、それだけの価値はあった。 晩には京都駅前で照岡さんと落ち合って、おおいに語り、おおいに飲んだ。(^_^)
- 「鳥獣戯画」前半展(10/8)
- 建物に入るまでの行列 - 30分
- 鳥獣戯画の展示室での行列 - 20分
- 牛歩で粘って観た時間 - 15分(4巻合計)
- 「鳥獣戯画」後半展(11/6)
- 建物に入るまでの行列 - 90分
- 鳥獣戯画の展示室での行列 - 40分
- 牛歩で粘って観た時間 - 15分(4巻合計)
そして今日の2限のゼミは、M2のリュ君と準ゼミの藤石さんが集まったものの、土佐谷さんは地元での就活で欠席、森川さんは謎の欠席で、久しぶりの藤石さんとともにmbed大会となった。 プログラミングが好きな藤石さんは、Raspberry Piをやろうか・・・と考えていたようだが、ちらっと見せたNucleoF401REの開発ステップの美しさに、ターゲットを変えるかどうか、悩むことにした。 また、来週月曜日に行う「任天堂の分解」にも立ち会うこととなった。
そして午後になり、2-3日前に届いていたブツをようやく繋ぐことができた。 以下のように、コンパクトなUSBオシロスコープである。 BitScope というもので、ホストPCがWindowsだけでなくMacでもLinuxでもRaspberry PiでもOK、ということで、業者に発注してオーストラリアから取り寄せていたものである。 繋いでみるとアッサリと動いて、サイトからダウンロードしていた オシロスコープ のプログラムも動いてくれた。
なんと、オシロだけでなく、スペアナまで付いていることも判った。 こうなると、以下のように、NucleoF401REの開発や筋電センサの実験、そして原稿執筆のための信号データのスクリーンショットまで、全てMac上で作業できることになり、邪悪なWindowsXPパソコンを使うことも、いちいちcuteFTPで転送する手間も不要となる。 これは最大の進歩かもしれない。(^_^)
2014年11月9日(日)
昨日はアカペラの有志と8時間マラソン、そして今日もぼちぼち休養日ということで、本格的なmbedはまた明日からであるが、忘れない程度に、ちょっとだけ進めてみた。 前回は、NucleoF401REからひたすら高密度でインクリメントする7バイトフォーマットのデータをXBee経由で送ってMax6で受けつつ、タイマーでMax6から7バイトフォーマットのデータをXBee経由でNucleoF401REに送り、これをマージしてNucleoF401REがエコーバックする、といういじわるテストを行った。そこで今日は、NucleoF401REから7バイトフォーマットのデータをXBee経由で送るインクリメントの間隔を、基板上のLED点滅と同じ0.5秒程度と圧倒的に低速にして、このデータは単に「シリアル通信の生存証明」用にした。 そして、Max6から7バイトフォーマットのデータをXBee経由でNucleoF401REに送り、これをNucleoF401REがマージしてエコーバックする、その時間密度の上限を探る、といういじわるテストを行った。 こちらはMax6から色々なコマンドやパラメータをNucleoF401REに送ることに使うので、より実戦的なものとなる。
上がその模様であり、NucleoF401REのプログラムは以下の「test005_XBee」に、 Max6のパッチ は「NucleoF401RE_003.maxpat」へと進化した。 以下のプログラム「test005_XBee」では、mainが今後の実験のためにシンプルになり、ライブラリ的に使えるモジュールは「submodule04.h」の方に移動してある。 これは、今後、ここを起点として進めていける安定バージョンである。
main.cpp
#include "mbed.h" #include "submodule04.h" int main(){ int i, j, k, sum; i = j = k = 0; rx_top = rx_end = tx_top = tx_end = phase = 0; xbee.baud(38400); xbee.attach(&rx_fifoset, xbee.RxIrq); while(1){ tx_fifo_check(); if(rx_fifo_check() == 1){ sum = 0; for (i=0; i<6; i++){ sum += conv_hex(raw_data[i])<<(4*(5-i)); } tx_message(sum); /* Echo Back */ } if(++j > 1000000){ j = 0; myled = !myled; k &= 8388607; tx_message(k++); } } }submodule04.h
unsigned char rxFIFO[256], txFIFO[256], raw_data[6]; unsigned char rx_top, rx_end, tx_top, tx_end, phase; RawSerial xbee(PA_2, PA_3); DigitalOut myled(LED1); void tx_fifo_check(){ if(xbee.writeable() == 1){ if(tx_top != tx_end){ xbee.putc(txFIFO[tx_end]); ++tx_end &= 255; } } return; } int rx_fifo_check(){ unsigned char data; if(rx_top != rx_end){ data = rxFIFO[rx_end]; ++rx_end &= 255; if (data < 33){ phase = 0; return(1); } raw_data[phase] = data; if(++phase > 5) phase = 0; return(0); } return(0); } void rx_fifoset(void){ rxFIFO[rx_top] = xbee.getc(); ++rx_top &= 255; return; } void tx_fifoset(unsigned char data){ txFIFO[tx_top] = data; ++tx_top &= 255; return; } unsigned char hex_conv(unsigned char data){ data &= 15; if(data < 10) return(data+48); else return(data+55); } unsigned char conv_hex(unsigned char data){ if((data > 47) && (data < 58)) return(data-48); else if((data > 64) && (data < 71)) return(data-55); return(0); } void tx_message(int data){ int i; for (i=0; i<6; i++){ tx_fifoset(hex_conv((data>>(4*(5-i))) & 15)); } tx_fifoset(13); }肝心の実験結果であるが、上のスクリーンショットの右上のパッチで、Max6側からmetroでカウンタをインクリメントしてXBee経由でNucleoF401REに送る間隔が、図のように「45msec」までは、安定して確実にデータがエコーバックされてくる。 これを手作業で「44msec」とか「43msec」にすると、データの欠落とともに動作が異常になってきた。 この実験では、ホストのMacのUSBハブを経由して、同時に
という処理を走らせているので、かなりの加重実験である。 その中で「45msec」、安全係数を見て50msecとすれば「毎秒20回」のレートで、7バイトフォーマットのデータを安定に往復できる、というのは、経験的には、なかなかの能力である。(^_^)
- NucleoF401RE
- XBee受信
- 「治具」のUSBオーディオ経由で2チャンネルのサイン波を出力
- BitScope により、この2チャンネルの信号をオシロ/スペアナ表示
2014年11月10日(月)
新しい週の始まりである。 昨日は前夜の睡眠不足でサエなかったので(^_^;)、10時間以上みっちり睡眠をとってのスタートである。 今日は2限に学生たちとWiiFitバランスボードを分解してセンサを取り出す予定、3限にはマル秘の入試関係業務の予定があるが、それ以外はmbed日和である(^_^)。 明日には1-5限にM2のリュ君の修了制作アポが入っていて、いよいよリュ君もmbedのプログラミングを始めるので、指導教官としてそれに先行してmbedを使いこなしておかないといけない、という「日々是競争」の楽しい時期なのだ。昨日までの実験で、およそNucleoF401REの開発ツールの様子、XBeeを経由しての双方向シリアル通信と割り込みハンドラの扱い、mbedがそこそこ高速であること、久しぶりのC言語の思い出しリハビリ、などが済んだので、いよいよ今日から、タイマー割り込みによる正確な時間管理と、それに続いてセンサ処理の本命、「A/Dコンバータ」に取りかかるのである。 NucleoF401REのmbedサイト の右側に並んだ「Example programs」には、「Nucleo_sleep」というものがあったが、これはソースを眺めてみると、単に「InterruptIn event(USER_BUTTON);」でボタン(スイッチ)をイベント監視対象として定義して、それが押された、というイベントに対して処理を定義するだけのものだった。
さらに探すと、「Nucleo_display_time」というサンプルがあった。 これは、内部のタイマーIC(RTC : Real Tine Clock)の数値を呼び出すものなので、タイマー割り込みが見つからなければ、最悪はこの値をポーリングすれば「ほぼ正確」な時間差を生成することは可能である。 プログラムは(整理すると)以下のようになっていた。
#include "mbed.h" int main() { printf("RTC example\n"); set_time(1387188323); // Set RTC time to 16 December 2013 10:05:23 UTC printf("Date and time are set.\n"); while(1) { time_t seconds = time(NULL); printf("Time as a basic string = %s", ctime(&seconds)); wait(1); } }・・・しかしどうも、これは分解能が「seconds」単位のようである(^_^;)。 これでは使えないので、こうなると、NucleoF401REのページでなく、mbedの全体から「タイマー割り込み」を探すことになる。 シリアル割り込みでの経験から、おそらく同じARMプロセッサのmbedファミリでは、他のメーカのCPUのプログラムでもお互いにほぼ動作するし、I/Oポートと違ってタイマーで外部ピンを使用しない場合には、おそらく何も変更しないでそのまま使える可能性が大きい。
そこでmbedのサイトで「timer interrupt」で検索すると、さっそく RIT (Repetitive Interrupt Timer) Library というページがあった。 しかしこれは、再帰的にタイマー割り込みを使うための環境設定ということでストライクではなかった。(^_^;)
そしてさらに、 Ticker と Timer と Timeout という、どうやらここがタイマー割り込み関係の解説らしいページを発見して、さらにこれを活用している PWM_LED_Sequence というサンプル、さらに ASyncTicker というサンプルにも到達した。 そこでまず、 Ticker にある、以下のサンプルを眺めてみた。
#include "mbed.h" Ticker flipper; DigitalOut led1(LED1); DigitalOut led2(LED2); void flip() { led2 = !led2; } int main() { led2 = 1; flipper.attach(&flip, 2.0); // the address of the function to be attached (flip) and the interval (2 seconds) while(1) { led1 = !led1; wait(0.2); } }これは判りやすい(^_^)。 「LED1」はメインルーチン中で「wait(0.2)」で点滅するが、「LED2」は「flipper.attach(&flip, 2.0);」でタイマー割り込み(2秒)に定義された「flip()」で反転する、というものである。 さらに「API summary」の中には、「attach」だけでなく、「attach_us」、すなわち「Attach a function to be called by the Ticker , specifiying the interval in micro-seconds.」と、欲しかった「マイクロ秒単位の時間指定」があった(^_^)。 ただしこの下を見ると、以下のような情報があった。 これは注意点である。
この最後の「RTOS Timer」というのは、 ここ に書かれているように、リアルタイムOSを構築するために、スレッドとして時間管理をする、というための機能のようである。 ただし、今回のプロジェクトではmbedのスタンドアロン動作として、そこまで本格的にするものではないので、これはパスする事にした。
- Warning - Note that timers are based on 32-bit int microsecond counters, so can only time up to a maximum of 2^31-1 microseconds i.e. 30 minutes. They are designed for times between microseconds and seconds. For longer times, you should consider the time()/Real time clock.
- RTOS Timer - Consider using the mbed RTOS Timer instead of a Ticker. In this way your periodic function will not be executed in a ISR, giving you more freedom and safety in your code.
そして、以下のように、 Ticker を使ったタイマー割り込みのプログラム「test006_timer」が完成してしまった(^_^)。 ここでは、「timer_setup.attach_us(&timer_interrupt, 100);」と、割り込み周期をマイクロ秒単位で「100」に、つまり「0.1msec」ごとにタイマー割り込みが起きて、とりあえず「timer_value[5];」と5種類のタイマーを定義したそれぞれの値をインクリメント(→16ビットマスクで0〜65535)している。 メインの方では、ボード上のLEDを4999カウント、つまり500msecで点滅させつつ、さらにシリアル送信として9999カウント、つまり1秒ごとにインクリメント送信している。 なお、この時間を「10」に、つまり「0.01msec」ごとにタイマー割り込みが起きるようにすると、LEDの点滅は変化が無かったが、シリアルの方はデータが送れなくなったので、あくまで経験値であるが、タイマー割り込みは「100μsec=0.1msec」を、とりあえずの最大性能として使うことにしよう。
main.cpp
#include "mbed.h" #include "submodule05.h" int timer_value[5]; Ticker timer_setup; void timer_interrupt(){ int i; for (i=0; i<5; i++) ++timer_value[i] &= 65535; } int main(){ int i, j, sum; i = j = 0; rx_top = rx_end = tx_top = tx_end = phase = 0; xbee.baud(38400); xbee.attach(&rx_fifoset, xbee.RxIrq); for (i=0; i<5; i++) timer_value[i] = 0; timer_setup.attach_us(&timer_interrupt, 100); while(1){ tx_fifo_check(); if(rx_fifo_check() == 1){ sum = 0; for (i=0; i<6; i++) sum += conv_hex(raw_data[i])<<(4*(5-i)); tx_message(sum); /* Echo Back */ } if(timer_value[0] > 4999){ timer_value[0] = 0; myled = !myled; } if(timer_value[1] > 9999){ timer_value[1] = 0; j &= 8388607; tx_message(j++); } } }submodule05.h
unsigned char rxFIFO[256], txFIFO[256], raw_data[6]; unsigned char rx_top, rx_end, tx_top, tx_end, phase; RawSerial xbee(PA_2, PA_3); DigitalOut myled(LED1); void tx_fifo_check(){ if(xbee.writeable() == 1){ if(tx_top != tx_end){ xbee.putc(txFIFO[tx_end]); ++tx_end &= 255; } } } int rx_fifo_check(){ unsigned char data; if(rx_top != rx_end){ data = rxFIFO[rx_end]; ++rx_end &= 255; if (data < 33){ phase = 0; return(1); } raw_data[phase] = data; if(++phase > 5) phase = 0; return(0); } return(0); } void rx_fifoset(void){ rxFIFO[rx_top] = xbee.getc(); ++rx_top &= 255; } void tx_fifoset(unsigned char data){ txFIFO[tx_top] = data; ++tx_top &= 255; } unsigned char hex_conv(unsigned char data){ data &= 15; if(data < 10) return(data+48); else return(data+55); } unsigned char conv_hex(unsigned char data){ if((data > 47) && (data < 58)) return(data-48); else if((data > 64) && (data < 71)) return(data-55); return(0); } void tx_message(int data){ int i; for (i=0; i<6; i++) tx_fifoset(hex_conv((data>>(4*(5-i))) & 15)); tx_fifoset(13); }そしてここで2限となり、卒制の応援をする生産造形の菅内さんと、記録を手伝いに準ゼミの藤石さんが研究室にやってきた。 そして、仕入れたばかりの新品のWiiバランスボードを このように 分解・解析した。 どんな体重の人でも乗ったり飛び跳ねても壊れないし危険がない、という装置はどのように作ればいいか、というまさに教科書のような王道設計に圧倒されつつ、「アルミ棒の変形を歪みゲージで計測」という、その方式が判明した。 これを、菅内さんの作品に仕込んで体重計測をするためには、Arduinoに体重情報を送るためのホイートストンブリッジ回路が必須である。 忘れないように、以下をここに置いておこう。(^_^;)
さて、いよいよ次は「A/Dコンバータ」である。 ここ によれば、スペックとしては「12bit ADC 2.4 Msps up to 10 channels」とある。 現状は、A0とA1に筋電センサの2チャンネルを、さらにA2とA3には。USBオーディオ治具からのステレオ音声信号が接続されている。 「2.4 Msps」というのはとてつもなく速いので、タイマー割り込みで等間隔にA/D変換をスタートさせる、という従来の方法よりも、単にタイマー割り込みで時間間隔を計測して、そこからいきなり「A/Dデータの入力」とやってもいいような気がする(^_^;)が、やはりここは、実際に変換して値が返ってくる時間を計測してみよう。
そこでとりあえず、 Nucleo_read_analog_value というサンプルを見てみると、なんとも簡単な、以下のサンプルがあった。
#include "mbed.h" AnalogIn analog_value(A0); DigitalOut myled(LED1); // Calculate the corresponding acquisition measure for a given value in mV #define MV(x) ((0xFFF*x)/3300) int main() { while(1) { uint16_t meas = analog_value.read_u16(); // Converts and read the analog input value if (meas > MV(1000)) { // If the value is greater than 1000 mV toggle the LED myled = !myled; } wait(0.2); // 200 ms } }12ビットデータなので、A/D入力の数値は16進で「000〜FFF」であり、フルスケールが3.3Vなので、3300で割れば値はmv単位の電圧となる。 上のプログラムでは、200msecごとにA/D計測しているのでチャタリング防止が不要であり、単に1Vより越えたかどうかでLEDを点滅させている、というわけである。 変数としては「uint16_t」で定義すればいい、と判ったので、これならさっそく実験できる。 ・・・しかし昼休みが終わり、しばしのお仕事タイムとなったので、ここで中断である。(^_^;)
そして4限となり、実験再開である。 上のサンプルプログラムでは、メインの無限ループ「while(1)」の中でアナログ入力値を求めて、おそらくここでA/D変換が開始され、「analog_value.read_u16()」の値が戻ってくるまで、わずかな「待ち」が入り、さらにそれよりずっと長い「wait(0.2)」で足踏みしている。 しかし実戦的なプログラムでは、もちろん「wait(0.2)」のような無駄足は厳禁だが、さらに言えばA/D変換の待ち時間が不明なまま残されるというのは避けたいのである。 そこで、ゲットしたばかりのタイマー割り込みのテクを使っていく事にした、
まずは、Max6からのシリアルを受信してエコーバック送信する部分をコメントアウトして、タイマーヘ割り込み周期を「timer_setup.attach_us(&timer_interrupt, 1);」として1μsecに設定し、ボード上のLEDを499999カウントとしても、体感ではほぼ同じ500msecで点滅する事を確認した。 ちょっと1マイクロ秒の割り込みというのは無茶だが(^_^;)、動くのならここから実験である。
以下が、刻々とパラメータを変更して実験した結果である。 ここでは、「タイマー割り込み周期」の時間を色々に変えて、その都度、メインルーチン中の「ボード上のLEDがほぼ1秒で点滅する、すなわち約500msec」というインターバルを設定して、「test_event()」というサブルーチンを呼ぶ。 「test_event()」では、まずタイマー割り込みのたびにインクリメントされる「timer_value[2]」をゼロクリアしてから、A/Dデータ入力呼び出しを1000回、行う。 そして、1000回のA/D変換後の「timer_value[2]」の値をシリアル経由でMax6に(500msecごとに)返す。
これで見たところだと、「タイマー割り込み時間」が50μsec〜100μsec〜200μsecというあたりでは、1回のA/D変換の所要時間はほぼ同じ「3.5μsec」ほどである。 これが「タイマー割り込み時間=20μsec」では「4.1μsec」ほど、「タイマー割り込み時間=10μsec」では「6.2μsec」と悪化していて、明らかに、A/D変換よりもpriorityの高いタイマー割り込みによって、A/D変換の逐次処理の足が引っ張られている事が判る。 逆に言えば、「タイマー割り込み時間=20μsec」と「タイマー割り込み時間=100μsec」との間に、タイマー割り込みと共存しつつ安定した性能の最大値がある事になる。
- タイマー割り込み時間=5μsec → データが返らず(^_^;)
- タイマー割り込み時間=10μsec → A/D入力を1000回行ったカウンタ値=626〜627
- タイマー割り込み時間=20μsec → A/D入力を1000回行ったカウンタ値=215
- タイマー割り込み時間=50μsec → A/D入力を1000回行ったカウンタ値=74
- タイマー割り込み時間=100μsec → A/D入力を1000回行ったカウンタ値=35
- タイマー割り込み時間=200μsec → A/D入力を1000回行ったカウンタ値=17
そこでさらに電卓片手に、この区間で同様に実験した結果が以下である。 カッコ内は、割り込み周期とカウンタ値を乗算した値で、両端の時間は上のデータを並べてある。
ここからの検討としては、割り込み周期としては、もっとも高速には「50μsec」=「0.05msec」あたりまで使えそうなこと、A/D変換の処理時間は、マージンを入れて「4μsec」、すなわち1チャンネルであれば「250KHz」あたりまで行けそう・・・という事になる。 CDオーディオが44.1KHz、最近話題のハイレゾですら128KHzなのに、それよりも高性能であり、筋電情報であれば「1000Hzサンプリング」なので、まったく余裕の極致、という事になる。 こうなれば、NucleoF401REについては今後、A/D変換については、セトリング時間とか、コンバージョンタイムとか、の過去の注意点はほとんど無視できて、サンプリングの基準をタイマー割り込みの精度で設定して、そこで「A/Dデータを要求」というシンプルなコードで十分であろう、と結論づけた。
- タイマー割り込み時間=20μsec → A/D入力を1000回行ったカウンタ値=215 (4100)
- タイマー割り込み時間=30μsec → A/D入力を1000回行ったカウンタ値=132 (3960)
- タイマー割り込み時間=40μsec → A/D入力を1000回行ったカウンタ値=95 (3800)
- タイマー割り込み時間=50μsec → A/D入力を1000回行ったカウンタ値=74 (3700)
- タイマー割り込み時間=60μsec → A/D入力を1000回行ったカウンタ値=60 (3600)
- タイマー割り込み時間=70μsec → A/D入力を1000回行ったカウンタ値=51 (3570)
- タイマー割り込み時間=80μsec → A/D入力を1000回行ったカウンタ値=44 (3520)
- タイマー割り込み時間=90μsec → A/D入力を1000回行ったカウンタ値=39 (3510)
- タイマー割り込み時間=100μsec → A/D入力を1000回行ったカウンタ値=35 (3500)
これで、タイマー割り込みとA/D入力についてはだいぶ見えてきて、明日のリュ君の準備は出来たので、せっかくのUSBオーディオ治具を使って、さらにいじわるテストをしてみる事にした(^_^;)。 11月4日あたりに書いたことだが、今回のNucleoF401REとMax6とのシリアル通信(38400bpsのXBee)では、データ転送フォーマットとして定義した1メッセージが「6バイトHEX(24ビットデータ)+1バイト(cr)」の計7データから、1メッセージの転送にかかる最小時間(最大性能)は「約1.823msec」という事になり、「ギッシリとデータが連続しているという理論値では、毎秒あたり548メッセージほどの伝送が限界」という事になる。 そこで、タイマー割り込みを「50μsec」として、この100回ごと、つまり「5msec」ごとに、1ポートのアナログ入力をA/D変換して、その12ビットデータをMax6にシリアル送信して、どのくらいまでアナログ入力にMaxのUSBオーディオ治具からA/Dに与えるサイン波の波形が再現できるか、というテストに挑戦した。
結果は上のようなものである。 USBオーディオ治具の出力で、パイポーラの出力をOPアンプの回路で+3.3Vレベルの脈流に変換するところに47μFのBPコンデンサを入れている関係でHPFとなり、最大に出力を上げてもダイナミックレンジが稼げていないが、ちゃんと「0.5Hz」のサイン波形が見て取れる。 ただし、周波数の精度は相当に悪くて、とても「0.5Hz」とは言えない(^_^;)。 以下が、この実験に使ったNucleoF401REのプログラム「test008_ADC」である。
main.cpp
#include "mbed.h" #include "submodule07.h" AnalogIn analog_value0(A0); AnalogIn analog_value1(A1); AnalogIn analog_value2(A2); AnalogIn analog_value3(A3); int main(){ int i, j, sum; i = j = 0; rx_top = rx_end = tx_top = tx_end = phase = 0; for (i=0; i<5; i++) timer_value[i] = 0; xbee.baud(38400); xbee.attach(&rx_fifoset, xbee.RxIrq); timer_setup.attach_us(&timer_interrupt, 50); while(1){ if(timer_value[2] > 99){ timer_value[2] = 0; uint16_t data = analog_value3.read_u16(); tx_message(data); } if(timer_value[0] > 9999){ timer_value[0] = 0; myled = !myled; } /* if(timer_value[1] > 19999){ timer_value[1] = 0; j &= 8388607; tx_message(j++); } */ tx_fifo_check(); if(rx_fifo_check() == 1){ sum = 0; for (i=0; i<6; i++) sum += conv_hex(raw_data[i])<<(4*(5-i)); tx_message(sum); /* Echo Back */ } } }submodule07.h
unsigned char rxFIFO[256], txFIFO[256], raw_data[6]; unsigned char rx_top, rx_end, tx_top, tx_end, phase; int timer_value[6]; RawSerial xbee(PA_2, PA_3); Ticker timer_setup; DigitalOut myled(LED1); void timer_interrupt(){ int i; for (i=0; i<6; i++) ++timer_value[i] &= 65535; } void tx_fifo_check(){ if(xbee.writeable() == 1){ if(tx_top != tx_end){ xbee.putc(txFIFO[tx_end]); ++tx_end &= 255; } } } int rx_fifo_check(){ unsigned char data; if(rx_top != rx_end){ data = rxFIFO[rx_end]; ++rx_end &= 255; if (data < 33){ phase = 0; return(1); } raw_data[phase] = data; if(++phase > 5) phase = 0; return(0); } return(0); } void rx_fifoset(void){ rxFIFO[rx_top] = xbee.getc(); ++rx_top &= 255; } void tx_fifoset(unsigned char data){ txFIFO[tx_top] = data; ++tx_top &= 255; } unsigned char hex_conv(unsigned char data){ data &= 15; if(data < 10) return(data+48); else return(data+55); } unsigned char conv_hex(unsigned char data){ if((data > 47) && (data < 58)) return(data-48); else if((data > 64) && (data < 71)) return(data-55); return(0); } void tx_message(int data){ int i; for (i=0; i<6; i++) tx_fifoset(hex_conv((data>>(4*(5-i))) & 15)); tx_fifoset(13); }なかなかに進んだような1日となった。 ここ で分解・解析したセンサは、菅内さんの作品に向けて、新たな宿題として加わったが、これは 「スケッチング」ワークショップ と絡めて、並行して進めていくことになる。 少しずつ色々な締め切りが近づいてきて、少しずつプレッシャーとなっていくが、なかなかにmbedは面白いので、頑張って進めていこう。
2014年11月11日(火)
いよいよぼちぼち、CQに書く原稿の内容にNucleoF401REの活用が近づいてきた。 一昨日、以下の 今回の筋電センサ について、確認で照岡さんに質問していた以下のメイルの返信も来て、だいぶスッキリしてきた。
2段のAD8607のアンプのフィルタ特性についてです。 1段目のAD8607の入力側のHPFはカットオフ4.825Hz、出力側はゲイン設定の1MΩに330pFが パラってあるのでここは緩いLPFでカットオフ482.5Hz、全体としてBPF。 2段目のAD8607の入力側のHPFはカットオフ1.592Hz(ほぼ素通し)、出力側はゲイン設定の1MΩに 1000pFがパラってあるのでここは緩いLPFでカットオフ159.2Hz、全体としてBPFというかほぼLPF、 ということでいいでしょうか。 これをNucleoF401REのA/Dでサンプリングするのに、どういうサンプリングレートにするか、考え中です。 1KHzもあったら十分、できれば500Hzあたりでどうか、を検討中です。これに対して、照岡さんのリブライが以下である。 さすが、ベテランのノウハウが詰まっている。(^_^)
その通りです。今回両方のカットオフを合わせなかったのは、まずHPFの方は、JISの筋電の時定数が1次の HPF(0.032秒f0=5Hz)と規定されているので、それに合わせるため(あまり急峻な特性にならないように) 2段目のカットオフを1.6Hzに下げたのです。 また、LPFの方ですが、こちらも筋電検出には、160Hzもあれば十分なのですが、あまり急峻にすると 立ち上がり(例のトリガ)が気持ち鈍るので、同じく1段目を緩くしています。 なお、1段目ではなく、2段目の方を絞ったのは、1段目アンプ由来のノイズも、気持ち吸収してもらおうと 思ったためです(^^;)。 先のLPFフィルタをアンチエイリアスフィルタとして位置づけるなら(ちょっと弱いですが)、1段目のf0も 160Hzに絞ってもいいかもですね。 #ただ、筋電はホワイトノイズみたいなものですので、アンチエイリアスフィルタは無くても良いかもですが、、、まっとうなディジタルフィルタ(FIR、IIR、...)は文献も多いが、筋電についてはあまり使わないので、ここは軽く扱うことになりそうな予感である。 その一方で、せっかくなので、過去にはアナログで組んでいた「検波→平滑」回路からA/D後のディジタルでやろうという方針なので、まずはこの部分をNucleoF401REでなくMax6のシミュレーションで確認することにした。 ちょっと「検波」で検索してみると、 こんな素敵なサイト があった。 かつて小学生の頃、真空管ラジオを作り、トランジスタラジオを作った、アマチュア無線(ハム)の世界である。(^_^)
そして、上は最初に実験したMax6のパッチ「detection_test1.maxpat」の様子である。 ここでは、画面左上の低速の「metro 20」でサイン波形(搬送波=キャリア。AM放送であればその放送周波数)を生成して、これに画面中央上の「0.2から0.8までアップダウンする」振幅値を乗算した、「仮想的な振幅変調波形」をまず生成している。 そして画面下段には、3つの検波プロックを並べてその出力を比較した。 下段左は「半波整流回路」であり、入力信号がゼロ以上の時にだけその値が出力され、負の場合にはゼロ出力となる。 これは、ダイオードの整流作用に対応している。 その下には、この出力を20段と50段の移動平均した波形が並んでいて、左側の20段ではゼロ付近の尖った部分がやや丸まり、右側の50段ではかなり平滑されているのが判る。 ただしこの50段の移動平均(平滑)出力を、画面右端の4つの比較ウインドウの2番目として、その上の「変調信号」と比較すると、とても似たものになっていないのが判る。 半波整流回路の平滑回路として移動平均を使うためには、50段ではまだまだ不足しているわけである。
画面下段に並んだ3つの検波プロックのうち、真ん中は「全波整流回路」であり、入力信号の絶対値が出力される。 これは、ダイオードを2個使った全波整流回路に対応している。 その下には、この出力を20段と50段の移動平均した波形が並んでいて、左側の20段では搬送波の振動がまだ残るものの、右側の50段ではほぼ平滑されて、「変調信号」と似た信号になっているのが判る。 画面右端の4つの比較ウインドウの3番目は、この全波整流回路の50段の移動平均(平滑)出力を1.8倍したもので、搬送波の周波数がかなり低いためにドリフトしているものの、50段あればまずまず平滑できている事が判る。
そして画面下段に並んだ3つの検波プロックの右側は「二乗整流回路」であり、乗算器により入力信号の2乗が出力される。 これは、2乗特性を持ったトランジスタによる二乗整流回路に対応している。 その下には、この出力を20段と50段の移動平均した波形が並んでいて、左側の20段では搬送波の振動がまだ残るものの、右側の50段ではほぼ平滑されて、「変調信号」と似た信号になっているのが判る。 画面右端の4つの比較ウインドウのいちばん下は、この二乗整流回路の50段の移動平均(平滑)出力を3.0倍したもので、ドリフトと2乗演算による線形性の崩れ(変化分の強調)が確認できる。
上は同じ「detection_test1.maxpat」の実験で、搬送波に相当する画面左上のサイン波形を「metro 3」で高速化し、これに同じ「0.2から0.8までアップダウンする」振幅値を乗算したものである。 画面下段左の「半波整流回路」では、20段と50段の移動平均した波形のいずれでも搬送波の成分が除去できず、「使えない」ことが判明した。 画面下段の真ん中の「全波整流回路」では、画面右端の4つの比較ウインドウの3番目の、50段の移動平均(平滑)出力を1.8倍した波形が、ほぼ「変調信号」を正確に反映している事が判る。 画面下段右の「二乗整流回路」では、画面右端の4つの比較ウインドウのいちばん下の、50段の移動平均(平滑)出力を3.0倍した波形が、2次関数的なデフォルメとともに「変調信号」を反映している事が判る。 ・・・以上から、ディジタル信号処理として「検波」の部分を実現するためには、「全波整流」→「50段の移動平均」という方針がまず、決定した。
・・・このあたりまで進めてきたが、この午前中は研究室にM2のリュ君がやってきて、いよいよ初めてのmbedプログラミングに着手していた。 ところが昼前になって、以下のような「The build system did not finish successfully.」という謎のエラーが出て、さっきまで出来ていたコンパイルが成功しない、と質問してきた。 そんなエラーは、僕も見たことも体験したことも無い。(^_^;)
そして、アカウントを変えても現象が同じで変わらない、というので、今日に限ってまだ起動していなかったオンラインコンパイラを僕のお仕事パソコンでも起動して、昨日も走らせたmbedプログラムをコンパイルすると、なんと上のように同じエラーが出た(^_^;)。 これはつまり、mbedのサイトの中にある、mbedコンパイラが壊れている、という現象なのだが、たまたま今日に限って、僕が実機のNucleoF401REを使っていなかったので、気付かなかった模様である。
これはもう、世界中から「おかしいよー」との声が出て、ARMのエンジニアがバグを解決するまで、誰もmbed開発を進められない、という状況である。 企業がお仕事でやっている製品開発の現場ではパニックになっていると思われる(^_^;)が、まぁこちらは、「明日まで待ってみよう」という程度である。 ちょうど、まだまだMax6でのシミュレーション実験をしているところだったので、たまたまであるが、実害が無かったのだ。 リュ君は午後もアポを入れていた予定を変更して、下宿のWindowsで試すことにして、午前で帰った。 ・・・まぁ、こういう事もあるのが、コンパイラ自身まで日々、成長している分野の宿命である。
上は改訂したMax6のパッチ「detection_test3.maxpat」の様子である。 午前の実験で「全波整流」→「50段の移動平均」という方針が立ったので、その続きとして、実際にNucleoF401REの整数演算でこの信号処理を行うための課題について実験した。 画面左上の低速の搬送波「metro 20」のサイン波形と、画面中央上の「0.2から0.8までアップダウンする」振幅値を乗算した、「仮想的な振幅変調波形」については午前の実験と同じである。 画面下段に5つ並ぶブロックのうち、左端は浮動小数点演算をして、グラフの縦軸も中央ゼロとなっており、画面右端の6つの比較ウインドウの2番目は「全波整流」→「50段の移動平均」の結果グラフで、いちばん上の「変調信号」グラフと比較するものである。
そして、実はこれ以外のグラフは「0〜255」の整数として、信号処理結果を表示している。 「仮想的な振幅変調波形」のグラフ(-1.0〜+1.0)に対しては、1.0を加えて127.99倍してから整数化したデータを、画面下段の左から2番目〜5番目(右端)に供給している。 画面下段の左から2番目のブロックでは、縦軸(0〜255)の中央値に対して、それを越えた値はそのまま、下回る値の場合には反転させたデータを取り出していて、これは浮動小数点演算の場合と同じである。 問題は画面下段の左から3番目〜5番目(右端)の3つである。 ここでは、入力されたA/D信号データの中点が、DCオフセットによって中央値「128」でない場合を想定していて、3番目(中央)では折り返しデータを「120」に、4番目では「140」に、5番目(右端)では「105」として、同様の計算をしてグラフ化した。
画面右端の6つの比較ウインドウの2番目の浮動小数点演算と3番目の整数演算の中央値「128」では、搬送波の周波数がかなり低いためにややドリフトしているものの、最上段の「変調信号」ほほぼ追従している。 しかしその下の3つのグラフでは、「全波整流」直後のグラフではそこそこそれっぽいものの、「50段の移動平均」の結果グラフはほぼ全滅で、大きくドリフトしている。
上は同じ「detection_test3.maxpat」の実験で、搬送波に相当する画面左上のサイン波形を「metro 3」で高速化したものである。 画面右端の6つの比較ウインドウの2番目の浮動小数点演算と3番目の整数演算の中央値「128」では、最上段の「変調信号」ほほぼ追従しているものの、その下の3つのグラフでは、大きなドリフトのために、「全波整流」→「50段の移動平均」というデータが元の「変調信号」をまったく再現できない事が判った。 こうなると、A/D入力信号をAC結合でなくDC入力する場合には、中点電位の設定精度が大きく影響することになる。 今回の筋電センサでは、センサ回路の電源電圧は+5Vレギュレータの出力を+3.3Vレギュレータに入れて生成し、さらにセンサ回路の中点電位(単電源の動作点)は、専用の電圧シャントレギュレータで正確にその1/2を設定しているので、これが大きく効いてくるわけだ。
2014年11月12日(水)
朝7時に研究室に出てみると、オンラインmbedコンパイラは昨日のトラブルが嘘のように、何事も無かったごとく「直って」いた(^_^;)。 やはり、この手のトラブルは、地球が一周するだけ待っていればいいというのか、逆に、地球が一周する(地球の裏側のエンジニアが気付いて修復する)までは解決しないのだ。そして午前中かかって、研究室に集めた文献を調べて生体情報計測に関する執筆内容を整理したり、NucleoF401REで実際にディジタル信号処理をどうやっていこうか・・・と調べていたが、午後になって、素晴らしい発見があった。 もともと、この日記の先頭あたりに参考文献として挙げた、
という2冊がまだ研究室に残っていて、いよいよディジタルフィルタとかFFTのmbedコードをごりごり書くことになったとしたら(出来れば避けたいが(^_^;))、これで勉強しようと思っていたのである。 すると、なんとちょうど「インターフェース」誌に三上氏が連載を書いているらしく、それもマイコンとしてNucleoF401REを使っているらしい・・・という事実が判明した。 CQ出版の「インターフェース」誌のサイトからダウンロードサービスのリンクを辿ると、なんとなんと、mbedのサイトの中の こんなページ に行き着き、三上氏がまさにNucleoF401REのサンプルブログラムをたくさん上げているのだった。 そして、以下のような、まさにズバリというサンプルが並んでいた。ディジタル信号処理の基礎 : はじめて学ぶディジタルフィルタとFFT. 三上直樹著. CQ出版, 1998 (Try computing books). ディジタル信号処理とDSP : パソコンによるシミュレーションとDSPプログラミング 三上直樹著 東京 : CQ出版社, 1999.11mbedはオープンソースの世界であり、ここにサンプルを上げているということは、そのコードを美味しく活用させていただける、という事である。 どうも、オーディオ帯域でエフェクトとかの実例を記載されているようだが、これはまさに「渡りに船」である。 せっかくなので、引用元を明記しつつ、ここはがっつりと便乗させてもらう事にして、これらのライブラリを自分のNucleoF401RE開発環境にインポートした(^_^;)。
- FFT_Sampling 本プログラムを使った実験は,CQ出版社のインターフェース 2014年11月号以降で紹介しています.
- FIR_LPF_Direct 本プログラムを使った実験は,CQ出版社のインターフェース 2014年12月号で紹介しています.
- FIR_LPF_Symmetry 本プログラムを使った実験は,CQ出版社のインターフェース 2014年12月号で紹介しています.
- IIR_LPF 本プログラムを使った実験は,CQ出版社のインターフェース 2014年11月号以降で紹介しています.
そして、この三上氏の記事を読むために、3ヶ月分だけ「インターフェース」誌の購入を書店に発注した。 実は、1992年11月号、つまりサラリーマンから退社独立を決めた頃から22年間ずっと、自宅で「トランジスタ技術」誌を定期購読していて、これは分厚い広告を切り取って、全て1106研究室の本棚の奥に並んでいるのだが、久しぶりに「インターフェース」誌も並ぶことになる。 ・・・ここで4限となった、続きはまた明日である。
2014年11月13日(木)
フト思い出したが、「11月13日」というのは、45年ぐらい昔の遠い記憶によれば、もしかすると「茨城県民の日」である。 学校がお休みになるというので、小学生の時に、水戸から常磐線で東京に行き、青梅鉄道公園への一人旅をした、という遠い記憶がある。 ただし、茨城県民だったのは高校卒業までの17年間で、その後、京都で下宿していた4年間を経て、就職して1981年4月に浜松に来て以来、もう静岡県民を33年半も続けているので、茨城県というのは「母親が暮らす」「葬儀で行く」「同窓会で行く」ぐらいだけの関係になってしまった。 ちなみに、「静岡県民の日」というのは、あるのかどうかも知らない。(^_^;)さて、SUACの木曜日というのは、月に一度の教授会があるために午後に専任教員の科目が無く、委員会などの会議があったりする「穴場」の曜日である。 学生委員会は教授会の前週ということで来週なので、今日の予定は「2限にマル秘の入試関係業務」・「3限に3回生の根木クンのアポ(映像作品のために作曲した音楽についてのコメント要望)」・「4限に的場先生のアポ(週末のSUACイベントにに関する取材)」、というだけであり、つまりは、mbedを進める「稼ぎ時」である。 明日の金曜日は午前のゼミ、そして午後には焼津に行って卒業生の山村知世さんの個展に行き、さらに卒業生と美味しく飲んで翌日に朝帰り(^_^;)という予定なので、その意味でも今日はがんがん進めていきたい。 まず、以下のように、お仕事パソコンのデスクトップに溜まっていた、原稿執筆に関係してダウンロードしたドキュメント類を圧縮して格納しておくことにした。
上のサンプルコードはいずれもC言語なので(Raspberry PiがPythonかどうかは未確認)、いずれ参照することになるかもしれない。 mbedサイトのオンラインコンパイラのバグも直ったようなので、いよいよ「A/D変換した信号の処理」について、実際にNucleoF401REで行う実験からのスタートである。 昨日は、 三上直樹氏のページ から「FFT」・「FIR」・「IIR」というキーワードのサンプルをインボートしてみたが、せっかくなので、並んでいる他のサンプルも全て眺めてみることにした。 まずはいちばん下(最古、といっても4ヶ月前の7月15日)の Ticker_10kHz である。 やはり三上氏もNucleoF401REを料理するのに、最初はタイマ割り込みなのだ、と安心した(^_^)。 これは以下のように、50μsecごとのタイマ割り込みをセットして、メインは無限ループとして、割り込みイベントごとにディジタル出力ポートを反転させることで、100μsec=10kHzの出力をオシロで確認する、というものだった。
- eHealthのサイトにあった、ArduinoとRaspberry Piのサンプルコード
- CQ出版のサイトにあった、三上直樹氏のFFT記事に関するサンプルコード
#include "mbed.h" Ticker timer_; DigitalOut pinOut_(D8); int flip_ = 0; void TimerIsr(){ pinOut_.write(flip_); flip_ = !flip_; } int main(){ timer_.attach_us(&TimerIsr, 50); // interval: 50 micro seconds while(true) {} }その次の USB_Printf は、僕が実験しているようにXBeeでMax6にシリアル通信するのでなく、defaultのホストPCとのUSB通信でメッセージを週力表示するものだったので、これはパスである。 その次の DSProcessingIO というのは、A/D入力と外部に置いたD/Aコンバータのためのライブラリであるが、これは昨日の「FFT」・「FIR」・「IIR」にも同じものがあったので、後で必要があれば参照することにした。 その次の FreqConv と、その後にあった Echo は、既に一緒にインポートしていた事が判明した。
残るは2つ、名前から似たようなものと思われる AD_DA と ADC_DAC_Hello である。 その後の最新の2つは「Nucleo F401/411RE で使える I2C 接続の LCD ACM1602NI 用のライブラリ・・・」とあるので、今回はとりあえず不要なのでパスである。 上の2つは以下のように、いずれも DSProcessingIO の定義を参照しているだけで、中身は「A/D入力したデータをD/A出力する」というだけであった。
AD_DA
#include "mbed.h" #include "AdcInternal.hpp" #include "MCP4922Single.hpp" Adc adc_; Dac dacA_(Dac::DAC_A); Ticker timer_; void TimerIsr(){ float value = adc_.Read(); // AD dacA_.Write(value); // DA } int main(){ timer_.attach_us(&TimerIsr, 20); while (true) {} }ADC_DAC_Hello
#include "mbed.h" #include "AdcInternal.hpp" #include "MCP4922Dual.hpp" const float FS_ = 10.0e3f; // sampling frequency: 10 kHz Adc adc_; // default, input: A0 DacDual dacAB_; // object of DacDual class Ticker timer_; // for timer interrupt void TimerIsr(){ // Called every 0.1 ms float value = adc_.Read(); // AD dacAB_.Write(value, -value); } int main(){ timer_.attach_us(&TimerIsr, 1.0e6f/FS_); while (true) {} }これでとりあえず、外堀は確認できた。 タイマ割り込みとA/D入力部分については、当然であるが三上氏もまったく同じことをしていた。 十分にA/Dコンバータが速いので、ディジタル信号処理のタイミング生成としてタイマ割り込みを使って、その都度、A/D変換の所要時間を気にせず(過去の低速A/Dコンバータであればここにも変換完了割り込みを使っていたが)、A/D変換データが返ってくるのを待つ方針でOKである、と確認できたのは、一つの収穫である。(^_^)
そして次に気付いたのは、三上氏のプログラムでは、A/D入力した値がfloatだった事である。 ジィジタル信号処理をするのに、いちいち整数値を使うのは大変では・・・と思っていたが、まぁC言語であれば内部的にうにゃうにゃと変換してくれて、Cプログラムの記述は浮動小数点で行けるらしい。 そこで探っていくと、内蔵A/D入力のための汎用ライブラリ「AdcInternal.hpp」の中に、「Read」メソッドとして以下の記述を発見した。
return 2*(adc_.read() - 0.5f); これはつまり、A/D入力のフルスケール12ビットをfloat表現した場合には「0.0〜1.0」となる、という事であり、中点電位のオフセットとして0.5を引いてから2倍して、フルスケールfloat表現の「-1.0〜1.0」にしているのだろう。 ここで思い出して「AnalogIn Class Reference」を見てみると、以下のように確かに書いてあった(^_^;)。
Public Member Functions AnalogIn (PinName pin) Create an AnalogIn, connected to the specified pin. float read () Read the input voltage, represented as a float in the range [0.0, 1.0]. unsigned short read_u16 () Read the input voltage, represented as an unsigned short in the range [0x0, 0xFFFF]. operator float () An operator shorthand for read()逆にD/A出力する際には、内部的に浮動小数点演算していた値をD/Aコンバータの整数値に戻す必要があるので、またまた探っていくと、外付けD/AコンパータMCP492のための汎用ライブラリ「MCP4922Single.hpp」の中に、以下の記述を発見した。 実際にはこの実行の前に、「値が-1より小さかったら-1に」・「値が1より大きかったら1に」、というリミティングをかけている。 アナログアンプであれば勝手に歪んでリミッタがかかってくれるが、ディジタルではこれをしないと大変なことになる。(^_^;)
WriteDac((uint16_t)((value + 1.0f)*2047)); これはつまり、float表現の「-1.0〜1.0」に1を加えて「0.0〜2.0」として、そこに11ビットの最大値2047を乗算することで、結果として12ビット整数値の「0〜4095」を得ているわけである。 そこでさっそく、プログラム「test008_ADC」を、浮動小数点表現版のプログラム「test009_ADC」に改訂し、合わせて実験用Max6パッチを「NucleoF401RE_005.maxpat」と改訂してみた。 こちらは、USBオーディオ治具からNucleoF401REのA/Dに与える信号を、880Hzのサイン波に0.2Hzのサイン波を乗算して振幅変調(リング変調)して、時間的に変動(2.5秒の周期で振幅が0.0〜1.0倍)するものとした。 これは、個々には1kHzに近い筋電インパルスが重畳する筋電信号のイメージである。
そして、根木クンのアポとか的場先生のアポに対応しつつも、なんとか上のように実験が出来た。 ここではNucleoF401REは、50μsecのタイマ割り込みを設定して、メインルーチン内でタイマ割り込み100回ごと、つまり5msecのサンプリングでA/Dコンバータのデータを受けて、以下の処理結果をXBee経由でホストのMax6に転送している。 ホストのMax6からXBee経由で受け取るコマンドとしては、以下の2種類を設定した。
- 全波整流(検波)のON/OFF
- ゲインとして1倍〜15倍
上がそのスクリーンショットと動作の模様のYouTube動画、 これ が実験用のMaxパッチ、以下がNucleoF401REのソースプログラムである。
main.cpp
#include "mbed.h" #include "submodule08.h" int main(){ int i, j, sum, gain; i = j = 0; gain = 1; for (i=0; i<5; i++) timer_value[i] = 0; xbee.baud(38400); xbee.attach(&rx_fifoset, xbee.RxIrq); timer_setup.attach_us(&timer_interrupt, 50); // 50usec while(1){ if(timer_value[2] > 99){ // 5msec timer_value[2] = 0; float data = (float)gain * (analog_value3.read() - 0.5f); if(j == 1){ if (data < 0) data = -data; } tx_message((uint16_t)((data + 1.0f) * 2047)<<4); } if(timer_value[0] > 9999){ // 500msec timer_value[0] = 0; myled = !myled; } tx_fifo_check(); if(rx_fifo_check() == 1){ sum = 0; for (i=0; i<6; i++) sum += conv_hex(raw_data[i])<<(4*(5-i)); tx_message(sum); /* Echo Back */ if(sum>>16 == 0x80){ switch((sum & 0xff00)>>8){ case 0x00: j = sum & 0x01; break; case 0x01: gain = sum & 0x0f; break; } } } } }submodule08.h
unsigned char rxFIFO[256], txFIFO[256], raw_data[6]; unsigned char rx_top, rx_end, tx_top, tx_end, phase; int timer_value[6]; RawSerial xbee(PA_2, PA_3); Ticker timer_setup; AnalogIn analog_value0(A0); AnalogIn analog_value1(A1); AnalogIn analog_value2(A2); AnalogIn analog_value3(A3); DigitalOut myled(LED1); void common_setup(){ rx_top = rx_end = tx_top = tx_end = phase = 0; } void timer_interrupt(){ int i; for (i=0; i<6; i++) ++timer_value[i] &= 65535; } void tx_fifo_check(){ if(xbee.writeable() == 1){ if(tx_top != tx_end){ xbee.putc(txFIFO[tx_end]); ++tx_end &= 255; } } } int rx_fifo_check(){ unsigned char data; if(rx_top != rx_end){ data = rxFIFO[rx_end]; ++rx_end &= 255; if (data < 33){ phase = 0; return(1); } raw_data[phase] = data; if(++phase > 5) phase = 0; return(0); } return(0); } void rx_fifoset(void){ rxFIFO[rx_top] = xbee.getc(); ++rx_top &= 255; } void tx_fifoset(unsigned char data){ txFIFO[tx_top] = data; ++tx_top &= 255; } unsigned char hex_conv(unsigned char data){ data &= 15; if(data < 10) return(data+48); else return(data+55); } unsigned char conv_hex(unsigned char data){ if((data > 47) && (data < 58)) return(data-48); else if((data > 64) && (data < 71)) return(data-55); return(0); } void tx_message(int data){ int i; for (i=0; i<6; i++) tx_fifoset(hex_conv((data>>(4*(5-i))) & 15)); tx_fifoset(13); }これで奇麗に検波(整流)できたので、次は平滑(積分)である。 とりあえずはMax6でのシミュレーションのように、50回の移動平均を試してみるが、せっかくなのでもっと多い移動平均、例えば100回と200回、というのもやってみたい。 サンプリングで入力された値を50段とかのバッファの最後に積んで、累算→除算の後でバッファの全部のデータをシフトさせる、というのが普通だが、考えてみれば50段の累算のうち49段分は、前回の計算とかぶっているので、ポインタだけループさせて加算値からバッファの最古値を引いてもいいような気がする。 そこで、
という2つのモードを比較してみることにして、まずは共通部分の「submodule08.h」を敢えて変更せずに「main.cpp」だけ改訂したNucleoF401REプログラム「test010_ADC」として、前者(うまい手??)からトライしてみよう。 Max6の方からは移動平均の段数として、「なし」「50段」「100段」「200段」の4種類などを指定すればいい。 ・・・ここで帰宅となったので、続きは明日からである。
- 途中の加算値を保持して先頭の値を減算してから最後の値を加算して最後に除算
- 馬鹿正直に、毎回、「累算→除算の後で全バッファのデータをシフト」
2014年11月14日(金)
今年の初めての寒波がやってきて、研究室の窓際(北側)に、半年以上しまっていたストーブを出した。 朝イチで研究室に届いていたメイルは、以下のように、遂に Max7 が正式にリリースされた、という情報であった。 だいぶ前からベータテストの案内が届いていたが、遂に来たか・・・というところである。
Greetings fellow Maxers,
I would like to first let you all know what a pleasure it is to work on software that you put to amazing use. Hearing about and seeing the projects you work on brings a smile to my face, whether it's mind expanding music, audio signal controlled laser performances, slime mold audio/video installations, creative tools for persons with disabilities, or just plain fun. I speak on behalf of all of my colleagues here at Cycling '74 when I say it is truly fulfilling to be an enabler of your epic creative journeys.
And so, after several years of hard work and lots of feedback from users such as yourself, it is with extreme pleasure that we are able to announce the latest release of our beloved software, Max 7. We have made significant improvements in performance, usability, and workflow, as well as adding new features for you to manipulate audio, video, and 3d graphics. Some of the highlights include a flexible pitch and time engine for warping audio, an improved video engine for high performance video playback, and a new 3d rendering architecture to support shadows and other post-processing effects like shallow depth of field and motion blur.
The workflow and usability enhancements should be clear from your first experience with the software. We’ve implemented a simple and elegant online demo, authorization and purchase experience connected to your Cycling '74 account. From there, in the new user interface, you can take a tour of new features in Max 7, try our new interactive video lessons, and visit the beautiful new documentation browser.
Happy Maxing!
Joshua Kit Clayton
CTO Cycling '74なんと、メッセージの発信者はJoshua Kit Claytonであった。 彼はミュージシャン/アーティストであり、jitterを開発したプログラマでもある。 2002年8月には、世界に先駆けて日本のSUACからjitterを発表したワークショップの時に、SUACに来てくれたのである。 その模様は ここ にある(いちばん下の「DSP Summer School 2002 in SUAC」の壮観をご覧あれ)。 そして、一緒に連れてきた彼女と2人で、SUAC講堂でのメディアアートフェスティバル2002のコンサートで、「世界で初めてのjitterを用いたライヴパフォーマンス」をしてくれた。 Maxの生みの親の David Zicarelli とともに世界のMaxを推進しているが、CTOというのはDavidのCEOに次ぐ重役なのだろう。
・・・そして、ここからなんと延々と1時間以上、アメリカのCycling74スタッフと、ほぼチャットのように英語のメイルを10本ほど往復させることになった。 まずはMax7のサイトを見て、要求システムが見当たらなかったので質問すると、1分後に返ってきたのがキッカケである。 SUACではMax6を全体で60本以上、購入しているので、これをMax7に上げるとなれば、大変なのだ。 そしてあれこれやりとりして判明したのは、以下のような事であった。
研究室のお仕事パソコンは10.6.8だし、SUACのマルチメディア室のiMacも2016年3月までは現状のマシンなので10.6.8ということで、当分はMax7は様子見となりそうである。 持ち歩きMacBookAirも2台とも10.6.8で止めていて、他に研究室にはXCodeを入れてiPhone/iPadアプリの開発をするために3台のMac miniを10.7に上げてあるのだが、ここにMax7を入れても、持ち歩いて実験できないので、ちょっと「待ち」状態である。
- Max7はMaxOSX 10.7以上でないと駄目(;_;)
- Max7はMax5やMax6のパッチは読み込める
- SUACはproxyがオンライン・インストールを通さないので、例外的に、個別に"challeng & response"という伝統的な手作業でオーソライズしているが、これはMax7では駄目らしい。特例として、テクニカルスタッフが解決法を実験してみたい
新しいMacBookAirを仕入れて実験・・・というのを調べてみると、なんと現在のMacOSXはYosemite(ヨセミテ)という10.10だそうで(^_^;)、さらにネットでは「OSX 10.10のバグ情報」というのがさんざん溢れている。 ちょうど今は、Yosemiteが入ったMacBookAirしか選べないという、まさに「買ってはいけない時期」なのだ。 しかし、たまたま3年目の学部長特別研究費が、「P板.com」で新しい基板を作るのをやめた関係でまだ余裕があるので、「人柱」になって購入するチャンスではある。 そこで事務局に購入の手続き(10万円を超えるので入札となる)を依頼して、Cycling'74に「Just now I have ordered new MacBookAir, to check Max7 with Yosemite. After getting it (maybe 2-3weeks later), I will download and contact you to install without internet. This is the test for our University.」とメイルしてみると、1分後に「I look forward to hearing from you.」と返ってきた。(^_^)
・・・そしてゼミと昼休みを挟んで、午後はmbedタイムである。 夕方には焼津に向かってSUACからバスで出かける予定なので、2時間1本勝負というところだ。 A/D入力に対する検波(整流)に続く、平滑(積分)にとりかかろうとしたところで昨日は終っていた。 サンプリングで入力された値を50段とかのバッファの最後に積んで移動平均をとるのに、50段の累算のうち49段分の加算は、前回の計算とかぶっているので、ポインタだけループさせて加算値からバッファの最古値を引く、という方法のトライである。
そして遂に 出来たのが、上のような実験ツールである。 Max6パッチは「 NucleoF401RE_006.maxpat 」となり、設定パラメータを表示してある。 共通部分の「submodule08.h」を敢えて変更せずに「main.cpp」だけ改訂したNucleoF401REプログラム「test010_ADC」として、以下のようになった。
main.cpp
#include "mbed.h" #include "submodule08.h" float mean_sum, ad_data[201]; int average_mode, max_count, ad_pointer[5]; void sum_clear(){ int i; for (i=0; i<5; i++) ad_pointer[i] = 0; for (i=0; i<201; i++) ad_data[i] = 0; mean_sum = 0; } float move_mean_calc(float data){ mean_sum = mean_sum - ad_data[ad_pointer[0]] + data; ad_data[ad_pointer[0]] = data; ad_pointer[0]++; if(ad_pointer[0] == max_count) ad_pointer[0] = 0; return(mean_sum / (float)max_count); } int main(){ int i, detection, sum, gain; i = 0; detection = 0; gain = 1; average_mode = 0; for (i=0; i<5; i++) timer_value[i] = 0; sum_clear(); xbee.baud(38400); xbee.attach(&rx_fifoset, xbee.RxIrq); timer_setup.attach_us(&timer_interrupt, 50); // 50usec while(1){ if(timer_value[2] > 99){ // 5msec timer_value[2] = 0; float data = (float)gain * (analog_value3.read() - 0.5f); if(detection == 1){ if (data < 0) data = -data; } if(average_mode != 0) data = move_mean_calc(data); tx_message((uint16_t)((data + 1.0f) * 2047)<<4); } if(timer_value[0] > 9999){ // 500msec timer_value[0] = 0; myled = !myled; } tx_fifo_check(); if(rx_fifo_check() == 1){ sum = 0; for (i=0; i<6; i++) sum += conv_hex(raw_data[i])<<(4*(5-i)); tx_message(sum); /* Echo Back */ if(sum>>16 == 0x80){ switch((sum & 0xff00)>>8){ case 0x00: detection = sum & 0x01; break; case 0x01: gain = sum & 0x0f; break; case 0x02: average_mode = sum & 0x07; max_count = 5; switch(average_mode){ case(2): max_count = 10; break; case(3): max_count = 20; break; case(4): max_count = 50; break; case(5): max_count = 100; break; case(6): max_count = 200; break; } sum_clear(); break; } } } } }以下が、これを使った実験で、色々にパラメータを変えたスクリーンショットである。 「detection ON/OFF」は、検波(整流)のON/OFF、「Gain」は1倍〜15倍が指定できるが、ここでは入力オーディオをそこそこのレベルにしているので、最大で3倍である。 メニューバーで設定したのが移動平均の段数で、サウンドをリング変調している、そのエンベロープに追従するには、5段もあれば十分で、100段とか200段にすると鈍り過ぎになる事が判った。 ただしこれは、サウンドの基準周波数が880Hzの場合なので、2KHzあたりにすると、5段や10段では波形がうまく平滑されない。 このパラメータは今後に残しておいた方がいいのかもしれない。
・・・こんなところで、ぼちぼち焼津に向かう時間となった。 続きは、朝帰りというか昼間に浜松に帰って来る、明日以降となる。(^_^;)
2014年11月15日(土)
・・・そして土曜日の昼前、好天の焼津から好天の浜松に帰還した。 SUACでは「ばんばんケンバン」とかいうイベントをしているようで、先日の的場先生が取材に来たのは、この講演会に出るためらしかった。 昨日の焼津は こんな感じ で、卒業生と楽しく飲み過ぎて体調がややイマイチなので(^_^;)、予定通りにイベントをバスして研究室に籠った。昨日の実験を受けて、とりあえず今日、確認しておきたかったのは、この日記の上の方、10月30日に新・筋電センサを製作して、3本のサウンドファイルとして保存していた、ナマの筋電情報を、昨日のソフトの条件で「可視化」してみる事である。 そこで、NucleoF401REのプログラムは改訂せず、Max6パッチだけ以下のような「NucleoF401RE_007.maxpat」に改訂して、それまでリング変調したサイン波形だったA/D入力を、筋電データファイルをループ再生する信号に置き換えた。 筋電データは2チャンネルのステレオであるが、ここまでNucleoF401REの実験はモノラルなので、ステレオの左チャンネル信号だけを使った。 ただし、3番目のデータだけは、実験で筋電センサ電極がやや接触不良だったようなデータなので、これだけ有効な右チャンネルに切り替えている。
上は、 test01.aif の筋電データの、波形表示の上段(左チャンネル)である。 なお、Max6のバッファメモリの波形表示は時間的に左から右に記録されているが、Max6パッチ内のmultisliderの波形表示は、時間的に右にスクロールするので、筋電信号とはちょうど左右反対で、「時間的には右から左」となっている事に注意されたい。 また、ゲインは全て「5倍」として、ほぼスルスケール化してある。 1個目は、筋電信号をA/D変換しただけの「生データ」であり、 2個目は、筋電信号を検波(整流)したデータであり、 3個目は、検波(整流)したデータを5段の移動平均で平滑化したデータであり、 4個目は、検波(整流)したデータを10段の移動平均で平滑化したデータであり、 5個目は、検波(整流)したデータを20段の移動平均で平滑化したデータであり、 6個目は、検波(整流)したデータを50段の移動平均で平滑化したデータであり、 7個目は、検波(整流)したデータを100段の移動平均で平滑化したデータであり、 8個目は、検波(整流)したデータを200段の移動平均で平滑化したデータである。
上は、 test02.aif の筋電データの、波形表示の上段(左チャンネル)である。 1個目から8個目までのデータの説明は、上の例とまったく同じである。
上は、 test03.aif の筋電データの、波形表示の下段(右チャンネル)である。 波形バッファの表示を見ると明らかなように、実験で筋電センサ電極がやや接触不良だったように、30秒間のデータの最後のあたりまで、ずっと一定の小レベルで筋電信号が取れていないようなので、ここだけ、Maxパッチを臨時に繋ぎ換えて、右チャンネルを左から出力させた。 1個目から8個目までのデータの説明は、上の例とまったく同じである。
このNucleoF401REのプログラムでは、XBee経由でMax6に情報を送る限度の伝送密度のほぼ上限として、A/D変換のサンプリングレートは5msec、つまり200Hzとなっている。 しかしNucleoF401REの性能はまだまだ余力があるので、シリアル伝送のデータ密度とは別に、サンプリングは1kHzでも5kHzでも可能である。 その条件のもとでこれらの波形から考察すると、まず「200段の移動平均」は、データが鈍り過ぎているので不適である。 「およその力加減」という状態を見るには、「100段の移動平均」が適しているように見える。 一方、激しい動きのレスポンスとしては、「50段の移動平均」がかなり良好である。 筋電信号の高周波成分の影響で、この実験では、20段より少ない移動平均では、あまり十分な平滑性能が出ていないように見受けられる。
2014年11月16日(日)
この日はほぼ1日がかりで、 Wiiバランスボードからの改造 に没頭した。 ホイートストンブリッジのための新回路を作るのでなく、任天堂の基板を解析して、最大でも0.5Vレベルながら歪みゲージのセンサ出力電圧を引き出した。 ゲージからのアンプは「200倍」のゲインだった。 そしてこれをさらに10倍に増幅してフルスケール化してGainerに入力した。 4チャンネルのうち1チャンネルは、チップ部品が壊れて使えなくなったが、まぁ両足に1個ずつであれば、なんとかなるだろう。いよいよ1週間後には 「スケッチング」ワークショップ となるので、ぼちぼち、「新作を初演」と書いてしまったので、作曲、その初期段階として「何か作る」というのを開始しないといけない。 これから1週間は、おそらくCQ原稿のmbedは無理だが、しかしせっかくなので、新しい音楽インターフェースをmbedで作る、というのはネタになる(^_^)。 ここらが区切りなので、「mbed日記(2)」はここまでとして、次は mbed日記(3) としていこう。
「日記」シリーズ の記録