初めまして、4月に新卒で入社したYoです。
ここでは3週間に渡るPi:Co研修について書いていきたいと思います。
はじめに
この記事におけるPi:Coとは、弊社が販売する「Pi:Co Classic3」を略したものです。
本製品はマイクロマウス学習キットとなっているので、はんだ付けからプログラミング、
マイクロマウスについての基礎知識と多くを学べます。
加えて、本製品はマイクロマウスクラシック規格に準拠した競技用ロボットでWindows10
での開発はもちろん、Windows11での開発も可能となっております。
マイクロマウス研修2024
弊社では新入社員全員がマイクロマウス研修を受けます。
今年度の研修は「Pi:Co Classic3」に沿って行われ、最終的にはテクニカルチャレンジへの
挑戦と合計3週間かけて行います。
この研修には「社員全員が共通のロボット知識を持つ」という意図もあったりします。
Pi:Co研修1週目
はんだ付けと組み立て
まずはPi:Coを開封していきます。
このようにバラバラの状態で梱包されているのではんだ付けしていきます。
こんな感じで出来上がってきました。
あとはこれらを組み立てれば完成です。あと少し。
1から作ったPi:Coの完成です。結構上手く出来たのではないでしょうか。
サンプルプログラム実行とオリジナルプログラム作成
Pi:Coが完成したので続いてはサンプルプログラムの実行です。
Pi:Coにはプログラミングが苦手な人にも優しいサンプルプログラムがいくつか
用意されているので、これらを順番に実行していきます。
今回の研修ではサンプルプログラムをひと通り実行したのち、これらを改造して
オリジナルのプログラム作成するという課題をやりました。
私はPi:Coがドからドまで2オクターブ鳴るようにプログラムを改造しました。
#define FREQ_C 523 //ドの周波数 #define FREQ_D 587 //レの周波数 #define FREQ_E 659 //ミの周波数 #define FREQ_F 698 //ファの周波数 #define FREQ_G 783 //ソの周波数 #define FREQ_A 880 //ラの周波数 #define FREQ_B 987 //シの周波数 #define FREQ_C6 1046 //ドの周波数 #define FREQ_D6 1174 //レの周波数 #define FREQ_E6 1318 //ミの周波数 #define FREQ_F6 1396 //ファの周波数 #define FREQ_G6 1567 //ソの周波数 #define FREQ_A6 1760 //ラの周波数 #define FREQ_B6 1975 //シの周波数 #define FREQ_C7 2093 //ドの周波数
このように周波数さえ分かれば簡単に実装できます。
第1回迷路走行記録会
実際の大会(APEC2024)で使用された16×16迷路に挑戦です。
持ち時間は5分で5回まで走行させてもよいルールです。
この迷路は直線区間が長く曲がり角が多い、さらに完走までの時間がかかるので
はっきり言って難易度結構高めでした。
結果は1回目の探索走行が2分35秒、2回目の最短経路走行が1分34秒でした。
なんとか完走できてよかったです。
Pi:Co研修2週目
Pi:Co研修2週目はスラローム走行を実装します。
スラローム走行ってどんな走行?
まずはPi:Coのサンプルプログラムに実装されている超信地旋回の説明から行います。
超信地旋回では、曲がり角で車体を停止させてから左右の車輪をお互いに逆回転させ
車体を旋回させます。一度、車体を停止させるので旋回までに時間がかかってしまいます。
今回実装する信地旋回によるスラロームでは、片方の車輪を停止させもう片方の車輪を
回して旋回します。これで大幅に旋回時間が短くなります。
スラローム走行の実装
信地旋回によるスラロームは超信地旋回のプログラムを応用することで実装できます。
超信地旋回時に車体が停止するところを消し、片方の車輪だけ停止するように
書き換えます。それだけだと壁にぶつかってしまうので旋回時に少し前進させます。
実装には少し時間がかかってしまいましたが、上図左下のスタート位置から
右上のゴールまでスラローム走行で4×6迷路完走できました。
第2回迷路走行記録会
今回の記録会は全日本マイクロマウス大会2023で使用された16×16迷路に挑戦です。
今回は制限時間なしで3回走行させるルールです。
結果は3回とも完走ならずでした。
壁に関するセンサの設定値が高すぎて両方の壁がなくなると姿勢が崩れてしまうのが
原因だったと考えられます。
テクニカルチャレンジ
テクニカルチャレンジとは?
「Pi:Co Classic3を題材にした課題に1週間かけて取り組む」というものです。
課題の選択肢は多く、賢く走るための実装やメカ・回路系など様々です。
私は「Pi:Coの走行中にBGMを流す」という課題に決めました。
走行中のBGM実装までの流れ
まずは走行していない状態でBGMが流れなければ始まらないので、
試しにBGMを1曲実装してみます。音の周波数は1週目に作ったものを流用します。
楽譜を見ながら実装したのですが楽譜の細かさに驚きました。
これでひとまず走行していない状態のPi:CoからBGMが流れるようになりました。
続いて、Pi:Coが走り終わった後に流れる短めのBGMを実装します。
某ゲームのレベルアップ音が良い長さだったのでそれを実装しました。
//BGM開始 SET_BUZZER_FREQ(FREQ_F6); //ブザーの周波数をファに設定 ファ↑ ENABLE_BUZZER; //ブザーを発振させる wait_ms(125); //0.25秒間 音を鳴らす DISABLE_BUZZER; //ブザーの発振を停止させる wait_ms(100); SET_BUZZER_FREQ(FREQ_F6); //ブザーの周波数をファに設定 ファ↑ ENABLE_BUZZER; //ブザーを発振させる wait_ms(125); //0.125秒間 音を鳴らす DISABLE_BUZZER; //ブザーの発振を停止させる wait_ms(100); SET_BUZZER_FREQ(FREQ_F6); //ブザーの周波数をファに設定 ファ↑ ENABLE_BUZZER; //ブザーを発振させる wait_ms(125); //0.125秒間 音を鳴らす DISABLE_BUZZER; //ブザーの発振を停止させる wait_ms(100); SET_BUZZER_FREQ(FREQ_F6); //ブザーの周波数をファに設定 ファ↑ ENABLE_BUZZER; //ブザーを発振させる wait_ms(125); //0.125秒間 音を鳴らす DISABLE_BUZZER; //ブザーの発振を停止させる wait_ms(125); SET_BUZZER_FREQ(FREQ_DD); //ブザーの周波数をミに設定 ミ↑(レ↑#) ENABLE_BUZZER; //ブザーを発振させる wait_ms(125); //0.125秒間 音を鳴らす DISABLE_BUZZER; //ブザーの発振を停止させる wait_ms(125); SET_BUZZER_FREQ(FREQ_G6); //ブザーの周波数をソに設定 ソ↑ ENABLE_BUZZER; //ブザーを発振させる wait_ms(125); //0.125秒間 音を鳴らす DISABLE_BUZZER; //ブザーの発振を停止させる wait_ms(100); SET_BUZZER_FREQ(FREQ_F6); //ブザーの周波数をファに設定 ファ↑ ENABLE_BUZZER; //ブザーを発振させる wait_ms(125); //0.125秒間 音を鳴らす ENABLE_BUZZER; //ブザーを発振させる wait_ms(250); //0.25秒間 音を鳴らす ENABLE_BUZZER; //ブザーを発振させる wait_ms(500); //0.5秒間 音を鳴らす DISABLE_BUZZER; //ブザーの発振を停止させる //BGM終了
こんな感じの力技になりました。
そして最後に最難関の「Pi:Coの走行中にBGMを流す」プログラムを実装します。
並列処理での実装は不可能そうなので割り込み処理で実装します。
interrupt.cというファイルに割り込み処理に関するコードの大半が書かれています。
ここに「バッテリ監視」というバッテリを監視しバッテリ残量が一定値を下回ると
Pi:Coの動きを止める割り込み処理があります。
このコード中にBGMが流れるコードを忍ばせようと思います。
#define quar 500 //四分音符 #define eight quar * 0.5 //八分音符 250 #define sixth quar * 0.25 //十六分音符 125 #define half quar * 2 //二分音符 1000 #define x_quar quar * 1.5 //付点四分音符 750 #define x_eight quar * 0.75 //付点八分音符 375 void int_cmt2(void) //1msタイマー用 { timer++; //1mSごとにカウントアップ battery_save(getBatteryVolt()); //バッテリ監視 static const unsigned int sounds[250] = { FREQ_G4, 0, FREQ_G4, 0, FREQ_C5, 0, FREQ_D5, 0, FREQ_E5, 0, FREQ_F5, 0, FREQ_G5, 0, FREQ_C6, 0, FREQ_B5, 0, FREQ_A5, 0, FREQ_A5, 0, FREQ_G5, 0, FREQ_FF, 0, FREQ_FF, 0, FREQ_A5, FREQ_G5, 0, FREQ_E5, 0, FREQ_E4, 0, FREQ_E4, 0, FREQ_E4, 0, FREQ_E4, 0, FREQ_Ff, 0, FREQ_Gg, 0, FREQ_A4, 0, FREQ_A4, 0, FREQ_B4, 0, FREQ_C5, 0, FREQ_D5, 0, FREQ_A4, 0, FREQ_A4, 0, FREQ_C5, 0, FREQ_C5, 0, FREQ_B4, 0, FREQ_A4, 0, FREQ_G4, 0, FREQ_E5, 0, FREQ_F6, 0, FREQ_E6, 0, FREQ_D5, 0, FREQ_C5, 0, FREQ_A4, 0, FREQ_C5, 0, FREQ_D5, 0, FREQ_E5, 0, FREQ_D5, 0, FREQ_C5, 0, FREQ_C5, 0, FREQ_B4, 0, FREQ_G4, 0, FREQ_G5, 0, FREQ_E5, 0, FREQ_F5, 0, FREQ_G5, 0, FREQ_A5, 0, FREQ_A4, 0, FREQ_B4, 0, FREQ_C5, 0, FREQ_F5, 0, FREQ_E5, 0, FREQ_C5}; static const unsigned int notes[250] = { x_eight, 100, sixth, 100, quar, 100, quar, 100, quar, 100, quar, 100, quar, 100, half, 100, x_eight, 100, sixth, 100, x_quar, 100, eight, eight, eight, 100, eight, 100, eight, 100, quar, 100, half, 100, x_eight, 100, sixth, 100, quar, 100, quar, 100, quar, 100, quar, 100, half, 100, eight, 100, eight, 100, eight, 100, half, 100, eight, 100, eight, 100, eight, 100, quar, 100, quar, 100, quar, 100, quar, 100, half, 100, eight, 100, eight, 100, eight, 100, half, 100, quar, 100, quar, 100, half, eight, eight, 100, eight, 100, eight, 100, half, 100, quar, 100 ,quar, 100, half, eight, eight, 100, eight, 100, eight, 100, half, eight, eight, 100, eight, 100, eight, 100, half, 100, x_quar, 100, 1500}; static unsigned int sound_count = 0; if(g_start_bgm == 0){ return; } ENABLE_BUZZER; //ブザーを発振する g_start_bgm++; if(g_start_bgm <= notes[sound_count]){ SET_BUZZER_FREQ(sounds[sound_count]); } else{ sound_count++; g_start_bgm = 1; } }
soundsでは音の周波数を設定していて、defineで定義した3オクターブ分の周波数から選択しています。
BGMの音と音の間は音が鳴らないように周波数を0に設定しています。
notesでは音の鳴る秒数(ms)を設定しています。音と音の間は100(ms)に設定してあります。
音符の拍数は四分音符の長さを基準にして定義しているので四分音符の拍数を変更すれば
BGMのテンポを変更できます。
PiCoClassic3.cにも割り込み処理開始とゴールした時の強制停止を書き加えたら
これでひとまず完成です。Pi:Coを動かしてみましょう。
果たして走行中にBGMは流れるんでしょうか。
テクニカルチャレンジ実装結果
結果としては、Pi:Coの走行開始からBGM流れました!と喜びも束の間、
途中からBGMが小刻みに途切れてしまいました。
迷路上のゴールに到着するまでBGMは流れたものの成功と言っても良いものか。
これでは悔しいので原因を探し出します。
static const unsigned int sounds[250] = { FREQ_G4, 0, FREQ_G4, 0, FREQ_C5, 0, FREQ_D5, 0, FREQ_E5, 0, FREQ_F5, 0, FREQ_G5, 0, FREQ_C6, 0, FREQ_B5, 0, FREQ_A5, 0, FREQ_A5, 0, FREQ_G5, 0, FREQ_FF, 0, FREQ_FF, 0, FREQ_A5, 0, FREQ_G5, 0, FREQ_E5, 0, FREQ_E4, 0, FREQ_E4, 0, FREQ_E4, 0, FREQ_E4, 0, FREQ_Ff, 0, FREQ_Gg, 0, FREQ_A4, 0, FREQ_A4, 0, FREQ_B4, 0, FREQ_C5, 0, FREQ_D5, 0, FREQ_A4, 0, FREQ_A4, 0, FREQ_C5, 0, FREQ_C5, 0, FREQ_B4, 0, FREQ_A4, 0, FREQ_G4, 0, FREQ_E5, 0, FREQ_F6, 0, FREQ_E6, 0, FREQ_D5, 0, FREQ_C5, 0, FREQ_A4, 0, FREQ_C5, 0, FREQ_D5, 0, FREQ_E5, 0, FREQ_D5, 0, FREQ_C5, 0, FREQ_C5, 0, FREQ_B4, 0, FREQ_G4, 0, FREQ_G5, 0, FREQ_E5, 0, FREQ_F5, 0, FREQ_G5, 0, FREQ_A5, 0, FREQ_A4, 0, FREQ_B4, 0, FREQ_C5, 0, FREQ_F5, 0, FREQ_E5, 0, FREQ_C5}; static const unsigned int notes[250] = { x_eight, 100, sixth, 100, quar, 100, quar, 100, quar, 100, quar, 100, quar, 100, half, 100, x_eight, 100, sixth, 100, x_quar, 100, eight, eight, eight, 100, eight, 100, eight, 100, quar, 100, half, 100, x_eight, 100, sixth, 100, quar, 100, quar, 100, quar, 100, quar, 100, half, 100, eight, 100, eight, 100, eight, 100, half, 100, eight, 100, eight, 100, eight, 100, quar, 100, quar, 100, quar, 100, quar, 100, half, 100, eight, 100, eight, 100, eight, 100, half, 100, quar, 100, quar, 100, half, eight, eight, 100, eight, 100, eight, 100, half, 100, quar, 100 ,quar, 100, half, eight, eight, 100, eight, 100, eight, 100, half, eight, eight, 100, eight, 100, eight, 100, half, 100, x_quar, 100, 1500}; static unsigned int sound_count = 0;
小刻みに途切れてしまっていたのはsoundsの2行目で定数が1つ抜けていたのが
原因だったようなので、正しいsoundsの設定は上記のようになります。
これで音が小刻みに途切れることなく綺麗に流れるようになりました。
(著作権の関係上BGMのプログラムを変更しています)
まとめ
今回はテクニカルチャレンジ実装に当てられる時間が1週間しかなかったですが、
走行中にBGMを流すという課題を実現できてよかったです。
楽譜の通りに拍数を合わせないと綺麗なBGMにならないので音の長さの調整に
苦労しました。
割り込み処理のプログラムを書くのは今回が初めてだったので特に難しかったです。
次はバッテリの残量がなくなった際に鳴るブザーをBGMに変えられたら面白そうだと
思ったので、実装してみたいです。