ここでは専ら音を鳴らす事を紹介。
item | qty |
Raspberry Pi Pico | 1 |
BTE110 PicoSHIELD | 1 |
BTE097 DX Busrepeater | 1 |
enclosure with speaker (3D pdf) | 1 |
Robot Cable-X4P 100mm | 1 |
Qwiic cable | 1 |
pin socket 2x6 | 1 |
頒布版はPicoはPicoSHIELDにピンソケットを介して装着し、スピーカを内蔵した電気的な絶縁を目的としたエンクロージャーに封入。
基板の短絡保護のためエンクロージャ(PLA+で生成)を作った上で2個のスピーカを内蔵し、PicoSHIELDのオーディオアンプに接続。基板に直接アクセスする際に分解する際は、ケーブル長が短いので断線させないよう少しずつこじ開ける事。
ちなみに少々まとめて出力したため、一部に変形がある。また嵌合力は積層同士の摩擦によるもので、開閉を繰り返したり熱を加えると弱まる。
PicoとPicoSHIELDの間はさほど長くないワイヤで接続してあるので、余程の事が無い限り分離しない事。
またPico WはArduino Picoライブラリにおける対応が不十分なところが見受けられるため、込み入った用途の場合は推奨しない。
PicoのUSBコネクタへ供給するUSBバスパワーでも運用できるが、オーディオへのノイズの流入が激しい。PicoSHIELDのCN2から安定した電源を給電する事を推奨する。
PicoSHIELDはDynamixel XシリーズのRS-485 I/F版を想定しているが、TTL I/F版を使用したい場合は同梱のDX BusrepeaterでI/Fを使って変換する。
通常はPicoに備わっているBOOTSELボタンで動作モードを切り替えるが、その場合は電源の操作が強いられる。PicoSHIELDではPB1の操作のみでPicoの動作モードを切り替える事ができる。
PB1を長押ししてPicoのブートローダをUSB接続のストレージ状態にし、コンパイル済みの*.uf2ファイルをそのストレージにコピーするだけなのだが、Arduino IDEを介すと転送に失敗するケースが散見される。
とりあえず転送に失敗するのと何かと面倒なため、こちらではArduino IDEではコンパイルだけを行い(メニューのスケッチ→コンパイル済みバイナリをエクスポート)、転送は個別に行うという手法が確立している。また転送する際はボリュームラベルを頼りにコピーするコマンドをあつらえた(Windows用のみ)。
コンパイル済みバイナリがエクスポートされたフォルダに展開し同梱の cp.cmd を起動すると、同じフォルダにある*.uf2ファイルをPicoのストレージを見つけた上でコピーしてくれる。
PicoのUSB(Serial)・UART0(Serial1)・UART1(Serial2)を使用して外部とのコミュニケーションを行う。起動時はUSBだが、何かしら受信したポート側に切り替わる。
pin | function |
CN1-2 | GP12(TXD0) |
CN1-3 | GP13(RXD0) |
CN1-5 | GND |
2chのPWMを活性化し、単純に高速でデータを転送しているだけ。PWM周波数が低いとアンプが発熱するという理由だけで可聴領域よりもはるかに高い192kHzのキャリア周波数を選んでいる。これより低い周波数でPWMを出力すると、平滑時のリプルにより無音であってもアンプが発熱、場合によってはエンクロージャーが熱により変形する。
なおarduino-pico Release 3.7.1以降に適用されたPWMキャリア周波数とDMA転送周期の分離を前提とした音再生を行っているので、それよりも古いライブラリではコンパイルできない。
様々なフォーマットを扱うのが面倒だったのでここではRAWデータのみを扱うが、データを保存する場所はメモリのサイズに依存する。
Picoのフラッシュはコードを含む2Mbyteが上限となる。
Picoのフラッシュに入り切らない程の音ネタを使うまでも無いと思うが、どうしてもと言う場合はmicroSDカードに保存して利用する事も可能。
音ネタのフォーマットをRAWファイルに変換するには、SoXを使うと簡単。とりあえずMP3からRAWデータに変換するバッチを含む実行ファイルを置いておく。
例えば44.1kHzステレオ符号付き16ビットRAWファイルに変換する場合は、同梱の conv_mp3_to_stereo44kraw.cmd に対して変換したいMP3ファイルをドラッグドロップする(複数も可)とconvertedフォルダにRAWファイルが生成される。
そもそもPicoをArduino IDEで使う事が間違いではあるが、いろいろなプラットホームに対応しているのでそれを使用する前提とする。もちろんPicoは単なるマイコンボードなのでプログラムなしに動く事はない。
間違いやすいのがArduino IDEにインストールするPico用のライブラリ。必ずこちらのリンクのライブラリを適用する事。
いずれのデモのアーカイブファイルのbuildフォルダ配下にコンパイル済みのバイナリと転送用のバッチが含まれているので、とりあえずArduino IDEが無くても動作確認は可能。
本来ならUSBカードリーダ化してmicroSDカードを挿抜せずともデータをPCから更新させるし、MP3等のフォーマットにも対応させるのだが、SDカードへのアクセスが致命的に遅いので止めている。MicroPythonを使ったとしても同様の制約を受けるのでイマイチ。
いずれにしても至極簡単かつ手抜きなツクリなので、適宜改造して活用してもらえればと。
本デモにおけるSerialの使い方の紹介。
3つのSerialのうち使用者がどれを選択するか分からないために、とりあえずどれか1つを自動的に選択するようにした。選択の条件は外部から送信がなされたポートかforceselで選択したポート。デフォルトはUSBでハードフロー制御を無効にするためにignoreFlowControl()を呼んでいる。
#include "SerialMulti.hpp" // インスタンス化 CSerialMulti SerialM; // 115.2kbpsで初期化 SerialM.begin(115200); // 書式化文字列送信 SerialM.printf("uhoho\n"); // 送信バッファフラッシュ SerialM.flush(); // 受信データ数取得 int n = SerialM.available(); // 受信データ読み出し char c = SerialM.read(); // ポートの強制選択 (0=Serial, 1=Serial1, 2=Serial2) SerialM.forcesel(2);
Picoに搭載されたフラッシュ上に配したRAWデータの再生。
フラッシュのサイズの都合もあるので、音データのフォーマットは無圧縮11.025kHzモノラル符号付き8ビットRAWデータとするが、再生時に周波数を変換できるので周波数はあまりこだわらない。フォーマットに従った音データを1.raw~9.rawの名前でファイルにし、コンパイル時にそれらのファイルを一緒にリンクして実行ファイルを生成する。
#include "RAWAudioPlayer.hpp" CRAWAudioPlayer RAWAudioPlayer; // 指定されたファイルをリンク INCBIN("hoge.raw", fuga); // リンクしたファイルを音データとして登録 std::vector<CRAWAudioPlayer::TPCMInfo> PCMInfo{ { (int8_t *)& fuga, (int)& _size_fuga, -1, 256, 127 }, }; // 初期化 RAWAudioPlayer.begin(& PCMInfo); // 0番目に登録したファイルをデフォルトの24(ド)の周波数で再生 RAWAudioPlayer.play(0, 24, 64);
異なる音源を合成して同時再生させる事ができるが、単純に足し算しているだけなので音割れに注意。またArduino IDEはソースをテンポラリにコピーした上でコンパイルするため、今回のような外部ファイルをリンクさせる際のファイル名として相対パスが使えない。
RAWファイルが保存されたmicroSDカードをPicoSHIELDのCN4に装着し再生。
音データのフォーマットは再生能力めいっぱいの無圧縮44.1kHzステレオ符号付き16ビットRAWデータ固定としている。そのフォーマットに従った音データを1.raw~9.rawの名前でファイルにし、予めmicroSDカードのルートに保存されている前提。
#include "SDAudioPlayer.hpp" CSDAudioPlayer SDAudioPlayer; // 初期化 SDAudioPlayer.begin(); // 指定されたファイルを再生 SDAudioPlayer.play("hoge.raw");
microSDカードはFAT32フォーマット(32GB以下)限定、使い倒したものは推奨せず、USBバスパワーではmicroSDカードへのアクセス時にアンプへ過度なノイズが加わるので外部電源推奨。
音つながりでPicoを昔ながらのサウンドチップ化しピコピコ音を鳴らす。
元ネタはDXMIOのサンプルで、PCMデータと合わせて簡易的なMMLを用いて楽曲を再生可。もともとシングルソースだったものを無理矢理分割したので少々強引。
#include "RAWAudioPlayer.hpp" #include "Synth.hpp" #include "TinyMusicSq.hpp" // インスタンス化 CRAWAudioPlayer RAWAudioPlayer; CTinyMusicSq MSq[_MAX_MUSICSQ]; // 初期化 RAWAudioPlayer.set_synthfunc(mixnote); // 波形設定 SetWaveForm(0, (const int8_t[]){ 0, -8, -16, -24, -32, -40, -48, -56, -64, -72, -80, -88, -96, -104, -112, -120, -128, 120, 112, 104, 96, 88, 80, 72, 64, 56, 48, 40, 32, 24, 16, -8 }, 32); // 再生 ConvertAndStartMusic(1, "t5@0o4l4v58$,50,100, [ cdefgab<cdefgab ]");
必要以上に無駄な事をしているのとサボり気味なので、鳴らなかったらご愛敬。
ロボット等の姿勢を検出するためにはIMUは必須だろうという事で、Qwiicに対応したadafruitのモジュールと通信するプログラム。
bno085monクラスはCEVA提供のライブラリを使ってBNO085と通信し、バックグラウンドでデータを取得し続けるためにFreeRTOSでタスクを生成している。
デモは取得を指示したデータの殆どをターミナルに吐き出す。また加速度・ジャイロ・地磁気のダイナミックキャリブレーションを有効にしてあるので、BNO08X Sensor Calibration Procedureを行う事でキャリブレーションが可能。
せっかくなのでDynamixelと通信するプログラムを作成。
デモに同梱したDXLIBはDynamixelのモデルを意識すること無くかつ物理値での指令に対応する追加APIが使用でき、それを利用して俗に言うモーションを簡単に再生するDXLMotionクラスを作った。
#include "DXLMotion.hpp" // インスタンス化 DXLMotion motion; // 対象IDの一覧 const std::vector<uint8_t> my_id_list {1,2,3}; // サーボの初期化 const DXLMotion::TServoInfo servo_info = { {1, 4, 0x4}, {2, 4, 0x4}, {3, 4, 0x4} }; motion.init_servo(& servo_info); // モーションデータの再生 const DXLMotion::TMotion motion1 = { my_id_list, { { { 0, 0, 0 }, 1.0 }, { { 45, 90, 30 }, 1.5 }, { { -30, 30, 0 }, 0.5 }, { { 123,-123, 270 }, 2.2 } } }; motion.play(& motion1);
デモは1MbpsでID=1~8のDynamixel X/Pシリーズを対象、モーションを再生する前に'i'を送信して接続されたDynamixelの検索と初期化が必須、Dynamixelは1台でもOK、モーションのフレーム切り替えにFreeRTOSを使用。
Pico W限定だが、ソケットとRS-485間をブリッジさせるプログラムで、PCと装置をUSBシリアル変換器等で接続している部分を無線化する。
基本的に単純なソケットサーバで、ポート番号23で待ち受け、ソケットで受信したデータをRS-485に送信、RS-485から受信したデータをソケットに送信するだけ。
APMODEのマクロでPico WそのものをWiFiのアクセスポイントにするか、Pico Wを既存のWiFiルータに接続するかを選択できる。詳細はソースにて。
COMポートを介して通信する既存のWindowsのアプリケーションを無線化する場合は、任意のIPアドレスとポート番号を割り当ててCOMポートを追加できるUSR-VCOMが使える。その場合はボーレート等の変更にも対応。
なおネットワーク環境やPCの負荷に大きく依存するため、既存のアプリの設定(特に受信タイムアウト)では正常に通信できない事も起こり得る。それを以てしても無線化する恩恵が大きい場合にのみ、本構成を検討する事。