Arduino,ESP32で変数が0⇒1⇒2⇒0⇒1⇒2と循環するスケッチ
Arduino、ESP32でスケッチを書いていますと、例えば変数が0⇒1⇒2⇒0⇒1⇒2と循環する場合があります。特にMIDI関連など作っておりますと、それ自体0から127の変数ですからよく出くわします。さてそんな時このようなコードを書いていないでしょうか。
0-127 変数循環スケッチ
uint8_t i; void setup() { Serial.begin(115200); } void loop() { i++; if(128 <= i) i=0; Serial.print("Number:"); Serial.println(i); delay(300); }
i++;で数を増し、指定数になったら0に戻す。いたって普通の書き方です。次はコードを少し改造します。
uint8_t i; uint8_t N=128; void setup() { Serial.begin(115200); } void loop() { Serial.print("Number:"); Serial.println(i); i=++i%N; delay(300); }
最初のスケッチより"i=++i%N"というスケッチに変更しました。これによりif文を1行消すことができました。ちょっとしたことですが1コード処理が速くなりました。
Midiコードで循環0戻しのif文の処理が増えた時、この"++i%N"を知りコードがずいぶんとすっきりしました。ちなみに1つ引いていくときは"--i%N"です。また使いどころの応用としては配列の添え字の処理などが考えられます。
そしてこのコードが最も有効と思われるプログラムがサークルバッファでして、もともとサークルバッファを使うくらいですのでタイミングがシビアなところに使われるわけです。
サークルバッファ
サークルバッファとは循環バッファ・リングバッファなどと呼ばれてまして、Wikiによりますとインデックス(添え数)をバッファサイズで割って剰余を取る正規化をし、一定の範囲に限定することで、直線状のバッファの両端を論理的に繋げる
とのことです。イメージとしてはリング状の配列にグルグルとデータを書き込み、処理が終わる前にデータを上書きしないバッファというところでしょうか。詳しくは++C++; // 未確認飛行 C 様が解説してくださっています。
似非サークルバッファと”++i%N”の実装
サークルバッファはライブラリなどありますのでここではサークルバッファ風のスケッチを作ってみようとおもいます。
シンプルな似非サークルバッファを実装します。0-127の乱数を発生させて配列にします。次にサークルバッファにこの乱数を代入します。この時バッファの番地をインデックス配列に保管します。使うときはインデックス配列の変化を検知して変化があればサークルバッファから読込みます。
uint8_t cBindex[128]; void setup() { Serial.begin(115200); Serial.println("CircleBuffer Test"); randomSeed(analogRead(0)); for (uint8_t i = 0; i < 128; i++) { cBindex[i]=1; } } byte randNumber[4]; uint8_t randNCnt=0; boolean randNfull = false; uint8_t cBuff1[256]; uint8_t cBCnt=0; uint8_t cBindexCnt; void loop() { /*****0-127乱数発生 配列に格納*****/ randNumber[randNCnt] = random(0, 127); Serial.println(randNCnt); if (3 <= randNCnt){ randNfull = true; Serial.print("RandomNumber:"); for (uint8_t i = 0; i < 4; i++) { Serial.print(randNumber[i],HEX); Serial.print(":"); } Serial.print(" "); } /*4つの配列なので0-3を循環させる*/ randNCnt=++randNCnt%4; /*********サークルバッファ********** サークルバッファの入れ物 cBuff1[cBCnt]= randNumber 0-127 バッファの番地の入れ物 cBindex[cBindexCnt] = cBCnt 0-255 0が使えないのでSetupで255に上書きした *************************************/ if(randNfull){ randNfull =false; cBindex[cBindexCnt]= cBCnt; Serial.print("bufPos;"); Serial.print(cBCnt); Serial.print(" "); cBindexCnt = ++cBindexCnt%128; for (uint8_t i = 0; i < 4; i++) { cBuff1[cBCnt] = randNumber[i]; cBCnt= ++cBCnt%256;/**4の倍数であわせる**/ } Serial.println(); } /****サークルバッファより値を取り出す。*/ uint8_t SearchCnt ; uint8_t Hit = 1; /*indexはすでに1繰り上がっている*/ SearchCnt = cBindexCnt; /*代入した番地の次からチェックしていく*/ for (uint8_t i = 0; i < 128; i++) { if(cBindex[SearchCnt] != 1) { Hit = cBindex[SearchCnt]; cBindex[SearchCnt]= 1; break; } /*Serial.print(SearchCnt);*/ /*Serial.print(":");*/ SearchCnt=++SearchCnt%128; } if(Hit != 1){ Serial.print("BuffDataNum: "); Serial.print(cBuff1[Hit+0] ,HEX); Serial.print(":"); Serial.print(cBuff1[Hit+1] ,HEX); Serial.print(":"); Serial.print(cBuff1[Hit+2] ,HEX); Serial.print(":"); Serial.print(cBuff1[Hit+3] ,HEX); Serial.print(": "); Serial.print("Search:"); Serial.print(Hit); Serial.println(); delay(300); } }
サークルバッファを使うと、変数が0に戻る循環する変数がたくさん出てきますので"++i%N"は覚えておいて損はしないでしょう。ちなみにdelay(300);をコメントアウトしても崩れません。
今回の乱数では間に合いましたが入力データストリームが多いときは配列を大きくする。サークルバッファより値を取り出す時、チェックの番地をもう少し後にずらすことで対策がとれるでしょう
サークルバッファの使いどころとしてはSPI通信で受け側データ処理,BLuetooth Midiの受信したデータ処理などで当方はつかってみました。
for文で回すときなどは++i%N より,i++; if(i>n)i-N;のほうが早い事もありますので適材適所で使用してください。
※上記コードは付属のパーツは必要ありません。またArduinoでもESP32でも動きます。
