ESP32でシリアルMP3モジュールを動作
使ったもの
HiLetgo YX5300シリアルMP3音楽プレーヤーモジュール UART制御 for Arduino/AVR/ARM/PIC
短めのジャンパ線
DSD TECH SH-U09BL USB-TTLシリアルケーブル、CP2102Nチップ付き 1.2M/4FT ※ソフトウェアシリアル確認用
適当なスピーカー(ボリューム付きだと良い)
準備
下記リンクよりMP3モジュールのライブラリをzip形式で保存し、インストール。
https://github.com/salvadorrueda/SerialMP3Player
このライブラリではSoftwareSerialを使っているが、ESP32はそのままではSoftwareSerialが機能しない。 SoftwareSerialを使うためにライブラリ「EspSoftwareSerial」をインストールする。
※参考 otomizu.work
ライブラリマネージャを開き、「espso」と入力すると該当ライブラリが表示されるのでインストールする。
ソフトウェアシリアルをテストしてみる。 RXを22、TXを23とした。
#include <SoftwareSerial.h> SoftwareSerial mySerial(22, 23); void setup() { mySerial.begin(9600); } void loop(){ mySerial.println("Hello"); }
TeraTermで確認
実装
接続は下記の通り。 実際は取付の都合でMP3モジュールのライトアングルピンヘッダを取り外し、ストレートのピンヘッダに変更した。
サンプルソースを元に書込み。 RX→22、TX→23に変更。 元はArduino用なのでsetup()とloop()が先に宣言されていたが、諸々の都合で宣言順番を入替えている。
/****************************************************************************** Basic Commands examples for the SerialMP3Player YX5300 chip. Copy the files of "SDcard_example" to an empty SD card Connect the Serial MP3 Player to the Arduino board GND → GND VCC → 5V TX → pin 11 RX → pin 10 After compile and upload the code, you can test some basic commands by sending the letters ? - Display Menu options. P01 - Play 01 file F01 - Play 01 folder S01 - Play 01 file in loop p - play a - pause s - stop > - Next < - Previous ... Some commands like 'P' must be followed by two digits. This example code is in the public domain. https://github.com/salvadorrueda/ArduinoSerialMP3Player by Salvador Rueda *******************************************************************************/ #include "SerialMP3Player.h" #define TX 23 #define RX 22 SerialMP3Player mp3(RX,TX); char c; // char from Serial char cmd=' '; char cmd1=' '; void menu(char op, int nval){ // Menu switch (op){ case '?': case 'h': Serial.println("SerialMP3Player Basic Commands:"); Serial.println(" ? - Display Menu options. "); Serial.println(" P01 - Play 01 file"); Serial.println(" F01 - Play 01 folder"); Serial.println(" S01 - Play 01 file in loop"); Serial.println(" V01 - Play 01 file, volume 30"); Serial.println(" p - Play"); Serial.println(" a - pause"); Serial.println(" s - stop "); Serial.println(" > - Next"); Serial.println(" < - Previous"); Serial.println(" + - Volume UP"); Serial.println(" - - Volume DOWN"); Serial.println(" v15 - Set Volume to 15"); Serial.println(" c - Query current file"); Serial.println(" q - Query status"); Serial.println(" x - Query folder count"); Serial.println(" t - Query total file count"); Serial.println(" r - Reset"); Serial.println(" e - Sleep"); Serial.println(" w - Wake up"); break; case 'P': Serial.println("Play"); mp3.play(nval); break; case 'F': Serial.println("Play Folder"); mp3.playF(nval); break; case 'S': Serial.println("Play loop"); mp3.playSL(nval); break; case 'V': Serial.println("Play file at 30 volume"); mp3.play(nval,30); break; case 'p': Serial.println("Play"); mp3.play(); break; case 'a': Serial.println("Pause"); mp3.pause(); break; case 's': Serial.println("Stop"); mp3.stop(); break; case '>': Serial.println("Next"); mp3.playNext(); break; case '<': Serial.println("Previous"); mp3.playPrevious(); break; case '+': Serial.println("Volume UP"); mp3.volUp(); break; case '-': Serial.println("Volume Down"); mp3.volDown(); break; case 'v': Serial.println("Set to Volume"); mp3.setVol(nval); mp3.qVol(); break; case 'c': Serial.println("Query current file"); mp3.qPlaying(); break; case 'q': Serial.println("Query status"); mp3.qStatus(); break; case 'x': Serial.println("Query folder count"); mp3.qTFolders(); break; case 't': Serial.println("Query total file count"); mp3.qTTracks(); break; case 'r': Serial.println("Reset"); mp3.reset(); break; case 'e': Serial.println("Sleep"); mp3.sleep(); break; case 'w': Serial.println("Wake up"); mp3.wakeup(); break; } } void decode_c(){ // Decode c looking for a specific command or a digit // if c is a 'v', 'P', 'F', 'S' or 'V' wait for the number XX if (c=='v' || c=='P' || c=='F' || c=='S' || c=='V'){ cmd=c; }else{ // maybe c is part of XX number if(c>='0' && c<='9'){ // if c is a digit if(cmd1==' '){ // if cmd1 is empty then c is the first digit cmd1 = c; }else{ // if cmd1 is not empty c is the second digit menu(cmd, ((cmd1-'0')*10)+(c-'0')); cmd = ' '; cmd1 = ' '; } }else{ // c is not a digit nor 'v', 'P', 'F' or 'S' so just call menu(c, nval); menu(c, 0); } } } void setup() { mp3.showDebug(1); // print what we are sending to the mp3 board. Serial.begin(9600); // start serial interface mp3.begin(9600); // start mp3-communication delay(500); // wait for init mp3.sendCommand(CMD_SEL_DEV, 0, 2); //select sd-card delay(500); // wait for init menu('?',0); // print the menu options. } // the loop function runs over and over again forever void loop() { if (Serial.available()){ c = Serial.read(); decode_c(); // Decode c. } // Check for the answer. if (mp3.available()){ Serial.println(mp3.decodeMP3Answer()); // print decoded answers from mp3 } }
9600bpsでシリアルモニタ開始。 "?" を入力するとコマンドヘルプが表示される。
動画
はんだ付け動画をアップしました
YouTubeに動画をアップしました。 www.youtube.com 技術書典11で販売している書籍『GPIB-USBインターフェースを自作して計測器をPC制御してみた』で、実際にGPIBコネクタとArduino Nano(互換ボード)をはんだ付けしている動画です。 少しカメラの位置がずれてますが(汗)実際に制作してみたい方への参考になれば幸いです。
【新刊】技術書典11に出展しました!
こちらのブログでは告知が遅くなったのですが、技術書典11さんにてオンライン出展させていただいております。
techbookfest.org
新刊を一冊出しました。
タイトルは『GPIB-USBインターフェースを自作して計測器をPC制御してみた』。
500円とお求めやすい価格となっております。
その名の通り、GPIB-USB変換のインターフェースを作り、計測器を実際にPCから制御するまでの一連をまとめました。
ノウハウを出し惜しみ無く書いたので、この本があれば同じように再現できると思います。
古い計測器を自動制御かけたいけどGPIBしかついていない、GPIB制御のツールが手元に無い、既存のソフトウェアは導入が大変……そんな方々に朗報です。
昔GPIB使ってたなぁ、とかどんな動作していたんだろう?と思い出に浸りたい方にもぜひ!
Arduinoの色んな応用について知りたい方や、何かしらのプロトコルを作りたい方にもヒントになるかもしれません。
もし売れたら続刊も出る、かも!
よろしくお願いします!
計測器棚を整理しました。
メタルシェルフを追加で購入して、床に置きっぱなしだった計測器を棚に整理しました!!
横河の直流電源なんかは脚が取れてたので、自作なんかしてみたり。
部屋や机のスペースの関係で自室には直接置けないため、キャスターでの移動を考えて配置したのですがわりかしうまくいった気がします。
YouTubeチャンネルを開設しました。
CRC-15-CANを計算するプログラムを書いた
(この記事はQiitaで書いた下記記事と同一です。)
はじめに
仕事でCAN通信のプログラムを書いているんだけど、CRCエラーの表示が出る。 デバッグのために、CRCの変換プログラムが必要になっった。 CANのCRCはCRC-15という特殊なフォーマットでネット上に資料が少ない。 ソースや調べたことをまとめておくことにした。
実装例
言語はCを使用。
#include <stdio.h> #include <stdint.h> // int to bin char* int_to_binstr(int bin, int len) { int i; static char buff[32]; for (i = 0; i < len; i++) { if (bin & (1UL << (len - i - 1))) { buff[i] = '1'; } else { buff[i] = '0'; } } buff[len] = '\0'; return buff; } uint16_t can_crc_next(uint16_t crc, uint8_t data) { uint8_t i; crc ^= (uint16_t)data << 7; for (i = 0; i < 8; i++) { crc <<= 1; if (crc & 0x8000) { crc ^= 0xc599; } } return crc & 0x7fff; } void cal_crc15(unsigned char* buff, int len) { uint16_t crc; crc = 0; printf("crc15 0x"); for (int i = 0; i < len; i++) { printf("%02X ", buff[i]); } printf("\n-> "); for (int i = 0; i < len; i++) { crc = can_crc_next(crc, buff[i]); } char binstr[16]; printf("0x%04X(%s)\n", crc, int_to_binstr(binstr, (int)crc, 15)); } int main() { int i; uint8_t data[] = { 0x00, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; cal_crc15(data, 11); }
CRC元データについて
CRCの元データはSOFからデータまで。スタッフビットは除く。 バイナリはHEXにする。 SOF側が半端なビットになるようにする。
uint8_t data[] = { 0x00, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
プログラムの動作
CRCの初期値は0。
crc = 0;
データをHEXごとに取り出して7ビット左シフトしてCRCと排他的論理和をとる。
crc ^= (uint16_t)data << 7;
CRCを1ビット左シフト
crc <<= 1;
CRCの最上位ビットが1なら、CRC15の生成多項式x15 + x14 + x10 + x8 + x7 + x4 + x3 + 1(=0xc599)との排他的論理和を取る。
if (crc & 0x8000) { crc ^= 0xc599; }
15ビットなので16ビットの最上位ビットを省く。
return crc & 0x7fff;
注意
CANコントローラ(MCP2515)の吐き出した値と一致することは確認できているが、CAN15の仕様と完全に一致しているかは確認できていない。 おそらく大丈夫だとは思うが。
参考URL
http://forum.easyelectronics.ru/viewtopic.php?f=49&t=34508
任意の CRC 値になるバイト列を逆算してみよう - Qiita
Catalogue of parametrised CRC algorithms