SSブログ

SPRESENSEのマルチコアでLEDを制御してみた [SPRESENSE]

前回はスレッドでLEDを制御しましたが、今回は同じ内容をマルチコアでやってみたいと思います。まさに、プチ”並列コンピューティング”ですね。マルチコアにする場合は、複数のArduino IDEを立ち上げて作業します。


SpresenseMPスケッチ.jpg


それぞれのスケッチを紹介します。まず、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”に指定します。


2019-10-27 (3).png


サブのスケッチの書き込み時は、"Core"の指定を”SubCore”に指定してください。


2019-10-27 (2).png


ところで、同じ Arduino IDE プロセス内で、メインとサブのスケッチを開いてしまうと(”File”の”New”とか”Open”を使って開いた場合)、”Core”の指定が連動してしまうのでやっかいです。例えば”メイン書き込み時にMainCore”に指定すると、サブのスケッチも”MainCore”になってしまいます。

これを回避するには、作ったスケッチを一旦保存してファイルをクリックして開いてください。Arduino IDEがそれぞれ別プロセスで起動するので、コア指定の連動がなくなります。ちょっと便利な技です。


動いている姿は地味なので動画にしてません。静止画でどうぞー。(って、使いまわしですが)^^;


DSC_0001_BURST20191019170915259.JPG


しかし、こんなに簡単に並列コンピューティングが試せるなんてすごい世の中になったものです。私が学生の頃は、大学の研究室にあるような代物でしたけど。並列コンピューティングを学習したい人はぜひ試してみてください!







nice!(30)  コメント(0) 
共通テーマ:趣味・カルチャー

SPRESENSE でスレッドを走らせてみた! [SPRESENSE]

Spresense SDK の Example を見ていると、いくつかのサンプルでスレッドを使っています。NuttX にはスレッドを生成する関数があるようです。


nuutx.png
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を持っている方は試してみてくださいねー。
(^^)/~



DSC_0001_BURST20191019170915259.JPG








nice!(29)  コメント(0) 
共通テーマ:趣味・カルチャー

SPRESENSE にピンマイクをつけて録音してみた! [SPRESENSE]

SPRESENSEにピンマイクをつけて、録音をしてみました。演奏はへたくそですが、ウクレレを使ってみました。


sDSC_0787.jpg


分かりやすく接続をイラストにまとめてみました。ボタンを押して録音開始、また再度ボタンを押して録音停止できるようにしました。


Spresense_mp3_recorder.png


スケッチはこちらです。それぞれの録音データは、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;
}



実際に録音してみた音がこちらです。へたくそなウクレレで恐縮です。^^;










nice!(30)  コメント(4) 
共通テーマ:趣味・カルチャー

SPRESENSE でピンマイクを使えるようにしてみた [SPRESENSE]

SPRESENSEで録音するときは、今までむき出しのコンデンサマイクを使っていました。ケーブルの引き回しが面倒だし、ノイズもよく拾うのでちょっとイマイチ。


sDSC05336.jpg


安いというのが大きな理由ですが、Amazonでマイクを探していたらピンマイクが200円程度で売られているを発見!





この値段ならピンマイクを使えたほうがいいんじゃない?ということで、ピンマイクをSPRESENSEにつなぐためのヘッドホンジャックの基板を探してみました。なんとこちらは一つ90円程度。





ということで、さっそく両方購入。


0_sDSC_0799.jpg


マイクのヘッドホンのピンは次のような単純な構造。先端がマイクのシグナル線で、根本のほうがGNDです。


1_sDSC_0796-2.jpg


シグナル線は2.2kΩを介してバイアス電源でプルアップしなければならないので、ヘッドホンジャック基板のひとつの配線をバイアス用にパターンカットします。


2_sDSC_0788.jpg


抵抗とワイヤーをはんだ付けをしました。思ったよりすっきりまとまりました。


3_sDSC_0789.jpg


SPRESENSEにつけてみました。結構よい感じです。


4_sDSC_0793.jpg


次は、これでどんな感じで音がとれるか試してみたいと思います。
(^^)/~





nice!(32)  コメント(2) 
共通テーマ:趣味・カルチャー