SPRESENSEのマルチコアでLEDを制御してみた [SPRESENSE]
前回はスレッドでLEDを制御しましたが、今回は同じ内容をマルチコアでやってみたいと思います。まさに、プチ”並列コンピューティング”ですね。マルチコアにする場合は、複数のArduino IDEを立ち上げて作業します。
それぞれのスケッチを紹介します。まず、MainCore用のスケッチから。SpresenseでMPを使うには、MPライブラリというものを使います。書き込むときにメインかサブか間違わないように、SUBCORE定義を使って書き込みエラーを回避しています。
次に、サブコア側のスケッチです。こちらもメインと同様に書き込みを間違えないようにする工夫を入れています。このスケッチを、そのままサブコア1~サブコア4に書き込みます。(LEDは4つあるので)
サブコア側は、メッセージ出力にSerialライブラリを使わずに、MPLog を使います。ハードウェアの競合を避けるための仕組みだと思います。
メインのスケッチの書き込み時は、Arduino IDE の”Core”の指定を”MainCore”に指定します。
サブのスケッチの書き込み時は、"Core"の指定を”SubCore”に指定してください。
ところで、同じ Arduino IDE プロセス内で、メインとサブのスケッチを開いてしまうと(”File”の”New”とか”Open”を使って開いた場合)、”Core”の指定が連動してしまうのでやっかいです。例えば”メイン書き込み時にMainCore”に指定すると、サブのスケッチも”MainCore”になってしまいます。
これを回避するには、作ったスケッチを一旦保存してファイルをクリックして開いてください。Arduino IDEがそれぞれ別プロセスで起動するので、コア指定の連動がなくなります。ちょっと便利な技です。
動いている姿は地味なので動画にしてません。静止画でどうぞー。(って、使いまわしですが)^^;
しかし、こんなに簡単に並列コンピューティングが試せるなんてすごい世の中になったものです。私が学生の頃は、大学の研究室にあるような代物でしたけど。並列コンピューティングを学習したい人はぜひ試してみてください!
それぞれのスケッチを紹介します。まず、MainCore用のスケッチから。SpresenseでMPを使うには、MPライブラリというものを使います。書き込むときにメインかサブか間違わないように、SUBCORE定義を使って書き込みエラーを回避しています。
#include <MP.h> #ifdef SUBCORE #error "You must select MainCore" #endif #define START 100 #define STOP 99 #define MAXLOOP 60 static int gMainLoop = 0; void setup() { Serial.begin(115200); int8_t sndid = START; for (int subcore = 1; subcore <= 4; ++subcore) { MP.begin(subcore); } static uint16_t param[4][3]; /* need specify "static" to fix the address */ param[0][0] = 0; /* LED number */ param[0][1] = 100; /* LED HIGH duration in msec */ param[0][2] = 200; /* LED LOW duration in msec */ MP.Send(sndid, &param[0], 1); /* SubCore 1 */ delay(100); param[1][0] = 1; /* LED number */ param[1][1] = 200; /* LED HIGH duration in msec */ param[1][2] = 200; /* LED LOW duration in msec */ MP.Send(sndid, &param[1], 2); /* SubCore 2 */ delay(100); param[2][0] = 2; /* LED number */ param[2][1] = 500; /* LED HIGH duration in msec */ param[2][2] = 100; /* LED LOW duration in msec */ MP.Send(sndid, &param[2], 3); /* SubCore 3 */ delay(100); param[3][0] = 3; /* LED number */ param[3][1] = 100; /* LED HIGH duration in msec */ param[3][2] = 500; /* LED LOW duration in msec */ MP.Send(sndid, &param[3], 4); /* SubCore 4 */ delay(100); } void loop() { ++gMainLoop; Serial.println("MainLoop: " + String(gMainLoop)); sleep(1); if (gMainLoop >= MAXLOOP) { int8_t sndid = STOP; uint32_t dummy = 0; for (int subcore = 1; subcore <= 4; ++subcore) { MP.Send(sndid, dummy, subcore); delay(100); } Serial.println("Stop all process on subcores"); while(1); } }
次に、サブコア側のスケッチです。こちらもメインと同様に書き込みを間違えないようにする工夫を入れています。このスケッチを、そのままサブコア1~サブコア4に書き込みます。(LEDは4つあるので)
サブコア側は、メッセージ出力にSerialライブラリを使わずに、MPLog を使います。ハードウェアの競合を避けるための仕組みだと思います。
#include <MP.h> #ifndef SUBCORE #error "You must select SubCore" #endif #define START 100 #define STOP 99 int pin_num = 0; int durationH; int durationL; void setup() { int8_t rcvid; uint16_t *param; int led; MP.begin(); MP.RecvTimeout(MP_RECV_BLOCKING); int ret = MP.Recv(&rcvid, ¶m); if (rcvid != START) { MPLog("Unknown msdid: process halt\n"); while(1); } led = param[0]; durationH = param[1]; durationL = param[2]; MPLog("========================================\n"); MPLog("SubCore %d\n", SUBCORE); MPLog("LED%d blink interval\n", led); MPLog("High duration: %03d msec\n", durationH); MPLog("Low duration: %03d msec\n", durationL); MPLog("========================================\n\n"); if (led > 3 || led < 0) { MPLog("Error: number of LED is %d", led); while(1); } switch(led) { case 0: pin_num = LED0; break; case 1: pin_num = LED1; break; case 2: pin_num = LED2; break; case 3: pin_num = LED3; } pinMode(pin_num, OUTPUT); MP.RecvTimeout(MP_RECV_POLLING); } void loop() { int8_t rcvid; uint32_t dummy; MP.Recv(&rcvid, &dummy); if (rcvid == STOP) { MPLog("SubCore %d is stopped\n", SUBCORE); while(1); } digitalWrite(pin_num, HIGH); delay(durationH); digitalWrite(pin_num, LOW); delay(durationL); // msec to usec /* You can use "delay(msec)" because * * the process is running on a different cpu */ }
メインのスケッチの書き込み時は、Arduino IDE の”Core”の指定を”MainCore”に指定します。
サブのスケッチの書き込み時は、"Core"の指定を”SubCore”に指定してください。
ところで、同じ Arduino IDE プロセス内で、メインとサブのスケッチを開いてしまうと(”File”の”New”とか”Open”を使って開いた場合)、”Core”の指定が連動してしまうのでやっかいです。例えば”メイン書き込み時にMainCore”に指定すると、サブのスケッチも”MainCore”になってしまいます。
これを回避するには、作ったスケッチを一旦保存してファイルをクリックして開いてください。Arduino IDEがそれぞれ別プロセスで起動するので、コア指定の連動がなくなります。ちょっと便利な技です。
動いている姿は地味なので動画にしてません。静止画でどうぞー。(って、使いまわしですが)^^;
しかし、こんなに簡単に並列コンピューティングが試せるなんてすごい世の中になったものです。私が学生の頃は、大学の研究室にあるような代物でしたけど。並列コンピューティングを学習したい人はぜひ試してみてください!
SPRESENSE でスレッドを走らせてみた! [SPRESENSE]
Spresense SDK の Example を見ていると、いくつかのサンプルでスレッドを使っています。NuttX にはスレッドを生成する関数があるようです。
http://nuttx.org/Documentation/NuttxUserGuide.html#taskcreate
これを使って SPRESENSE の四つのLEDをそれぞれのスレッドでコントロールするスケッチを書いてみました。
スケッチの中でも記述していますが、スレッド内で delay() を使うことはできないようです。どうも delay関数は、busy-wait のようなのでタスクを握って離しません。Spresense SDK のサンプルでも usleep を使っていますので、usleep を使うようにしましょう。
また、プライオリティは高いほど優先度が高いようです。肝心の loop 関数の優先度がよくわからないのが困りものですが、いろいろ試してみたら 120 位のようです。
動作の様子は地味なので動画にしてませんSPRESENSEを持っている方は試してみてくださいねー。
(^^)/~
http://nuttx.org/Documentation/NuttxUserGuide.html#taskcreate
#include <sched.h> int task_create(char *name, int priority, int stack_size, main_t entry, char * const argv[]) | ||
Input Parameters | ||
name: | The name of the new task | |
priority: | The priority of the new task | |
stack_size: | The size (in bytes) of the stack needed | |
entry: | The entry point of a new task | |
argv: | A pointer to an array of input parameters. The array should be terminated with a NULL argv[] value. If no parameters are required, argv may be NULL. | |
Returned Value | ||
Returns the non-zero task ID of the new task or ERROR if memory is insufficient or the task cannot be created (errno is not set). |
これを使って SPRESENSE の四つのLEDをそれぞれのスレッドでコントロールするスケッチを書いてみました。
#include <sched.h> /*** for task_create **/ #include <stdlib.h> /*** for atoi ***/ #define MAXLOOP 60 static int gMainLoop = 0; // Thread static int led_thread(int argc, char* argv[]) { if (argc != 4) { Serial.println("Invalid argument number at led0_thread: " + String(argc)); return; } String func = String(argv[0]); int led = atoi(argv[1]); int durationH = atoi(argv[2]); int durationL = atoi(argv[3]); Serial.println("========================================"); Serial.println("Task name : " + func); Serial.println("LED" + String(led) + " blink interval"); Serial.println("High duration: " + String(durationH) + " msec"); Serial.println("Low duration: " + String(durationL) + " msec"); Serial.println("========================================\n"); if (led > 3 || led < 0) { Serial.println("Error: number of LED is " + String(led)); return -1; } int pin_num = 0; switch(led) { case 0: pin_num = LED0; break; case 1: pin_num = LED1; break; case 2: pin_num = LED2; break; case 3: pin_num = LED3; } while (gMainLoop < MAXLOOP) { digitalWrite(pin_num, HIGH); usleep(durationH * 1000); // msec to usec digitalWrite(pin_num, LOW); usleep(durationL * 1000); // msec to usec /* You cannot use "delay(msec)" because it is * * a busywait that will not yield the process */ } Serial.println("Exit " + func); return 0; } void setup() { char* param[4]; String str[3]; int i; Serial.begin(115200); pinMode(LED0, OUTPUT); pinMode(LED1, OUTPUT); pinMode(LED2, OUTPUT); pinMode(LED3, OUTPUT); str[0] = String("0"); /* LED number */ str[1] = String("100"); /* LED HIGH duration in msec */ str[2] = String("200"); /* LED LOW duration in msec */ for (i = 0; i < 3; ++i) { param[i] = str[i].c_str(); } param[3] = NULL; // the last array must NULL terminatz task_create("led0 thread", 130, 1024, led_thread, param); str[0] = String("1"); /* LED number */ str[1] = String("200"); /* LED HIGH duration in msec */ str[2] = String("200"); /* LED LOW duration in msec */ for (i = 0; i < 3; ++i) { param[i] = str[i].c_str(); } param[3] = NULL; // the last array must NULL terminate task_create("led1 thread", 130, 1024, led_thread, param); str[0] = String("2"); /* LED number */ str[1] = String("500"); /* LED HIGH duration in msec */ str[2] = String("100"); /* LED LOW duration in msec */ for (i = 0; i < 3; ++i) { param[i] = str[i].c_str(); } param[3] = NULL; // the last array must NULL terminate task_create("led2 thread", 130, 1024, led_thread, param); str[0] = String("3"); /* LED number */ str[1] = String("100"); /* LED HIGH duration in msec */ str[2] = String("500"); /* LED LOW duration in msec */ for (i = 0; i < 3; ++i) { param[i] = str[i].c_str(); } param[3] = NULL; // the last array must NULL terminate task_create("led3 thread", 130, 1024, led_thread, param); } void loop() { ++gMainLoop; Serial.println("MainLoop: " + String(gMainLoop)); sleep(1); if (gMainLoop >= MAXLOOP) { sleep(1); // wait for the end of all processes Serial.println("End of all processes"); while (1); } }
スケッチの中でも記述していますが、スレッド内で delay() を使うことはできないようです。どうも delay関数は、busy-wait のようなのでタスクを握って離しません。Spresense SDK のサンプルでも usleep を使っていますので、usleep を使うようにしましょう。
また、プライオリティは高いほど優先度が高いようです。肝心の loop 関数の優先度がよくわからないのが困りものですが、いろいろ試してみたら 120 位のようです。
動作の様子は地味なので動画にしてませんSPRESENSEを持っている方は試してみてくださいねー。
(^^)/~
SPRESENSE にピンマイクをつけて録音してみた! [SPRESENSE]
SPRESENSEにピンマイクをつけて、録音をしてみました。演奏はへたくそですが、ウクレレを使ってみました。
分かりやすく接続をイラストにまとめてみました。ボタンを押して録音開始、また再度ボタンを押して録音停止できるようにしました。
スケッチはこちらです。それぞれの録音データは、RECXXX.mp3 と3桁の追番で別ファイルとして記録するように書いています。
実際に録音してみた音がこちらです。へたくそなウクレレで恐縮です。^^;
分かりやすく接続をイラストにまとめてみました。ボタンを押して録音開始、また再度ボタンを押して録音停止できるようにしました。
スケッチはこちらです。それぞれの録音データは、RECXXX.mp3 と3桁の追番で別ファイルとして記録するように書いています。
#include <SDHCI.h> #include <Audio.h> SDClass theSD; AudioClass *theAudio = AudioClass::getInstance(); bool bRecording = false; bool bStart = false; int gCounter = 0; File myFile; void changeState() { // interrupt handler bStart = ~bStart; } void recorder_end() { if (bRecording) { bRecording = false; theAudio->stopRecorder(); theAudio->closeOutputFile(myFile); myFile.close(); theAudio->setReadyMode(); theAudio->end(); } digitalWrite(LED0, LOW); Serial.println("End recording"); } void recorder_begin() { int err; theAudio->begin(); theAudio->setRecorderMode(AS_SETRECDR_STS_INPUTDEVICE_MIC, 200); // the gain up to 30db err = theAudio->initRecorder(AS_CODECTYPE_MP3 ,"/mnt/sd0/BIN" ,AS_SAMPLINGRATE_48000 ,AS_CHANNEL_MONO); if (err != AUDIOLIB_ECODE_OK) { Serial.println("Recorder initialize error"); while(1); } Serial.println("Recording initialized"); char filename[16] = {0}; sprintf(filename, "REC%03d.MP3", gCounter); myFile = theSD.open(filename ,FILE_WRITE); if (!myFile) { Serial.println("File open error\n"); while(1); } theAudio->startRecorder(); bRecording = true; ++gCounter; digitalWrite(LED0, HIGH); Serial.println("Start recording"); } int intPin = 0; void setup() { Serial.begin(115200); pinMode(intPin, INPUT_PULLUP); theSD.begin(); attachInterrupt(digitalPinToInterrupt(intPin) ,changeState ,FALLING); } void loop() { int err; if (!bStart && !bRecording) { return; } else if (bStart && !bRecording) { recorder_begin(); return; }else if (!bStart && bRecording) { recorder_end(); return; } err = theAudio->readFrames(myFile); if (err != AUDIOLIB_ECODE_OK) { Serial.println("File End! = " + String(err)); recorder_end(); } usleep(40000); return; }
実際に録音してみた音がこちらです。へたくそなウクレレで恐縮です。^^;
SPRESENSE でピンマイクを使えるようにしてみた [SPRESENSE]
SPRESENSEで録音するときは、今までむき出しのコンデンサマイクを使っていました。ケーブルの引き回しが面倒だし、ノイズもよく拾うのでちょっとイマイチ。
安いというのが大きな理由ですが、Amazonでマイクを探していたらピンマイクが200円程度で売られているを発見!
この値段ならピンマイクを使えたほうがいいんじゃない?ということで、ピンマイクをSPRESENSEにつなぐためのヘッドホンジャックの基板を探してみました。なんとこちらは一つ90円程度。
ということで、さっそく両方購入。
マイクのヘッドホンのピンは次のような単純な構造。先端がマイクのシグナル線で、根本のほうがGNDです。
シグナル線は2.2kΩを介してバイアス電源でプルアップしなければならないので、ヘッドホンジャック基板のひとつの配線をバイアス用にパターンカットします。
抵抗とワイヤーをはんだ付けをしました。思ったよりすっきりまとまりました。
SPRESENSEにつけてみました。結構よい感じです。
次は、これでどんな感じで音がとれるか試してみたいと思います。
(^^)/~
安いというのが大きな理由ですが、Amazonでマイクを探していたらピンマイクが200円程度で売られているを発見!
この値段ならピンマイクを使えたほうがいいんじゃない?ということで、ピンマイクをSPRESENSEにつなぐためのヘッドホンジャックの基板を探してみました。なんとこちらは一つ90円程度。
ということで、さっそく両方購入。
マイクのヘッドホンのピンは次のような単純な構造。先端がマイクのシグナル線で、根本のほうがGNDです。
シグナル線は2.2kΩを介してバイアス電源でプルアップしなければならないので、ヘッドホンジャック基板のひとつの配線をバイアス用にパターンカットします。
抵抗とワイヤーをはんだ付けをしました。思ったよりすっきりまとまりました。
SPRESENSEにつけてみました。結構よい感じです。
次は、これでどんな感じで音がとれるか試してみたいと思います。
(^^)/~