ESP32でシリアルMP3モジュールを動作

使ったもの

準備

下記リンクよりMP3モジュールのライブラリをzip形式で保存し、インストール。

https://github.com/salvadorrueda/SerialMP3Player

このライブラリではSoftwareSerialを使っているが、ESP32はそのままではSoftwareSerialが機能しない。 SoftwareSerialを使うためにライブラリ「EspSoftwareSerial」をインストールする。

※参考 otomizu.work

ライブラリマネージャを開き、「espso」と入力すると該当ライブラリが表示されるのでインストールする。

f:id:positivefeedback:20211028215228p:plain

ソフトウェアシリアルをテストしてみる。 RXを22、TXを23とした。

#include <SoftwareSerial.h>
SoftwareSerial mySerial(22, 23);
void setup() {
  mySerial.begin(9600);
}
void loop(){
  mySerial.println("Hello");
}

TeraTermで確認

f:id:positivefeedback:20211028215728p:plain

実装

接続は下記の通り。 実際は取付の都合でMP3モジュールのライトアングルピンヘッダを取り外し、ストレートのピンヘッダに変更した。

f:id:positivefeedback:20211028220001p:plain

サンプルソースを元に書込み。 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でシリアルモニタ開始。 "?" を入力するとコマンドヘルプが表示される。

f:id:positivefeedback:20211028220238p:plain

動画

www.youtube.com

はんだ付け動画をアップしました

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の色んな応用について知りたい方や、何かしらのプロトコルを作りたい方にもヒントになるかもしれません。


もし売れたら続刊も出る、かも!   よろしくお願いします!

計測器棚を整理しました。

メタルシェルフを追加で購入して、床に置きっぱなしだった計測器を棚に整理しました!!

f:id:positivefeedback:20210619062457j:plain

横河の直流電源なんかは脚が取れてたので、自作なんかしてみたり。

f:id:positivefeedback:20210619062437j:plain

部屋や机のスペースの関係で自室には直接置けないため、キャスターでの移動を考えて配置したのですがわりかしうまくいった気がします。

YouTubeチャンネルを開設しました。

www.youtube.com

YouTubeに電子工作のチャンネルを開設しました。

「くじけない電子工作」ということで、失敗例も含めて色々アップできたらいいなと。


www.youtube.com

最初にアップした動画は過去に作った自作プリント基板です。 専用紙にレーザープリンタでパターンを印刷して、ラミネートでトナーを転写してエッチングしています。 初めての製作でしたが、単3電池2本の電池ボックスの上にうまく配置されるようそれなりに小さく作れたのではと思っています。 エッチングがなかなかうまく行かなかったりして、色々調べてから完成するまでに一年ぐらいかかった記憶があります。

今後はそういった試行錯誤も踏まえて動画をアップできればなと思っています。

CRC-15-CANを計算するプログラムを書いた

(この記事はQiitaで書いた下記記事と同一です。)

qiita.com

はじめに

仕事でCAN通信のプログラムを書いているんだけど、CRCエラーの表示が出る。 デバッグのために、CRCの変換プログラムが必要になっった。 CANのCRCCRC-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

USBのCRC:回路とCソース - Qiita

任意の CRC 値になるバイト列を逆算してみよう - Qiita

巡回冗長検査 - Wikipedia

Catalogue of parametrised CRC algorithms