このプロジェクトは、2024年 SPRESENSE™ 活用コンテストに応募するために作成しました。
はじめに
SpreM5ScratchSense(スーパーエムファイブスクラッチセンス)は、SPRESENSEからM5Stackを介することで、Scratchとやり取りをできるようにしたシステムです。
現状では、SPRESENSEからはGPSのデータをScratchに送ることができ、ScratchからSPRESENSEの4つのLEDのオンオフが可能になっています。
登場人物
今回、利用したのは、以下のような技術/デバイスです。
ハードウエア
SPRESENSE
SPRESENSEは、Sonyの開発した高性能のマイコンボードです。
メインボード単体で、GPS情報が取得できたり、高性能の音の入出力、AI機能など色々なことができるようになっています。
マルチコアも利用可能で、全部で6つのコアを利用して、高い性能が求められる構成を取ることが可能です。
今回は、GPSによる情報と、LEDの点滅をScratchとやり取りする構成を取りました。
これで、Scratchから位置情報を使ったり、ScratchからSPRESENSEのLEDを点滅できるようになりました。
M5Stack
M5Stackは、オールインワンの使いやすいマイコンです。
今回利用したCore2で、システムで利用した機能には、以下のようなものがあります。
- ディスプレイ(320x240)
- 加速度センサー(IMU)
- LED
- スピーカー
- などなど
Scratch
Scratchは、MITのメディアラボで開発されているブロックプログラミング環境です。
子供たちなどのプログラミング初心者が、簡単にプログラミングできるようになっています。
Scratchの中でも、Stretch3(ストレッチスリー)というサーバーは公式の拡張機能として提供されていない色々な拡張機能が提供されており、楽しく利用することができます。
Microbit More
Microbit Moreは、Scratchからmicro:bitの全ての機能を利用できるようにした拡張機能です。
Stretch3サーバーやxcratchサーバーで利用することができます。
M5bitLess
M5bitLess(github, M5bitLess: M5Stack x Scratch3 = So Fun!!(ProtoPedia))は、M5StackとScratchを使うためのArduinoプログラムです。
M5bitLessで、M5Stackはmicro:bitのように振る舞うことで、Scratchからはmicro:bitとして見えるようになっています。
そのため、Microbit More拡張機能の機能が利用できるようになります。
M5bitLessでは、例えば以下のような機能が利用可能です。
- 5x5 Matrix LED表示エミュレーション
- 文字列表示
- 加速度センサー
- LED明滅
- トーン音
- などなど
Geo Scratch
Geo Scratchは、Scratchで地図情報を扱うことができる拡張機能です。
GPSの緯度および経度データを指定することで、その場所の地図を表示することなどが可能です。
更に、県名などの地名情報なども取得可能なため、多彩なアプリケーションを構築することが可能です。
これをGPS情報と組み合わせることで、Scratch上で様々な地図アプリケーションが実現可能です。
システム構成
このシステムでは、SPRESENSEはM5Stack Core2とシリアル経由で接続され、GPSやLEDのオンオフ状態をやり取りします。
M5Stack Core2とScratchは、Microbit Moreを使ってBluetooth経由で情報のやり取りを行います。
SPRESENSEから得たGPSやLEDの情報は、M5Stack Core2経由でScratchに送られます。
ハードウエア構成
利用したハードウエアは以下のとおりです。
名称 | SKU | 用途 |
---|---|---|
SPRESENSEメインボード | CXD5602PWBMAIN1 | GPSデータ取得 |
SPRESENSE用 BLEベースボード | SSCI-063340 | Bluetooth Low Energy対応 (未使用) |
M5Stack Core2 | M5STACK-K010-V11 | Scratchとのやりとり(M5bitLess) |
M5Stack Module Plus | M5STACK-PLUSEM | SPRESENSEとCore2の接続 |
GROVE - 4ピン - ジャンパオスケーブル | SEEED-110990210 | SPRESENSEとCore2の接続 |
データのやり取りについて(Microbit Moreラベルとデータ)
データは、Microbit Moreのラベルとデータ(M5bitLess label & data extension(ProtoPedia))という機能を用いて実現しています。ラベルとデータでは、任意の文字列(ラベル)に対して、任意の値(データ)を送ることができる仕組みです。
ScratchからSPRESENSEには、LED0
-LED3
にon
やoff
を設定することで、SPRESENSEのLEDの点滅が可能になっています。
SPRESENSEからScratchには、以下のようなデータが送られます。
Date
: 日時Time
: 時間Fix
: GPSのデータ取得状態alt
: 高度numSat
: 確認できている衛星の数numSatC
: 測位に使っている衛星の数HDOP
: HDOPLon
: 経度Lat
: 緯度
ソースコード
https://github.com/610t/SpreM5ScratchSense/ でソースコードを公開しています。
構成は、以下のようになっています。
SpreM5ScratchSense/examples/M5Stack/*
: M5Stack用のプログラム群です。SpreM5ScratchSense/examples/SPRESENSE/SPRESENSE.ino
: SPRESENSE用のプログラムです。SpreM5ScratchSense/examples/Scratch/GPS.sb3
: ScratchでGPSデータを扱う例です。
ここでは、主要なコードだけを紹介します。
M5Stack.ino
は、M5bitLess用のコードを今回のプロジェクト用に改造しています。
改造点は、以下の通りです。
- LEDのon/off命令をSPRESENSEに送る。
- SPRESENSEからのGPS関連データをScratchで操作できるようにする。
M5Stack.ino
//// Global variables.
// For Stack-chan
bool stackchan_mode = false;
// Screen size
int screen_w = 320;
int screen_h = 240;
// Converter 32bit float little endian and uint8_t x 4 each other.
static union {
uint8_t b[sizeof(float)];
float f;
} cd;
// PortB A/D I/O, GPIO I/O, PWM, Servo, etc.
int pin[17]; // Microbit More can handle P0-P16.
enum pin_mode_t {
PIN_ANALOG_INPUT,
PIN_ANALOG_OUTPUT,
PIN_DIGITAL_INPUT,
PIN_DIGITAL_OUTPUT,
PIN_PWM,
PIN_SERVO,
PIN_PULL,
PIN_EVENT
};
pin_mode_t pin_mode[17] = { PIN_ANALOG_INPUT };
#include <M5Unified.h>
#include <M5Dial.h>
#include <M5StackUpdater.h>
#include "esp_mac.h"
//// Global variables for M5Stack.
// Board name
m5::board_t myBoard = m5gfx::board_unknown;
//// FastLED for Atom Matrix, Lite.
#if !defined(CONFIG_IDF_TARGET_ESP32S3)
#include <FastLED.h>
#define NUM_LEDS 25
#define LED_DATA_PIN 27
CRGB leds[NUM_LEDS];
#endif
//// Mic for M5StickC/Plus
#include <driver/i2s.h>
#define PIN_CLK 0
#define PIN_DATA 34
#define READ_LEN (2 * 256)
#define SAMPLING_RATE 11025
uint8_t BUFFER[READ_LEN] = { 0 };
int16_t *adcBuffer = NULL;
int soundLevel = 0;
void i2sInit() {
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
.sample_rate = SAMPLING_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB
.channel_format = I2S_CHANNEL_FMT_ALL_RIGHT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2,
.dma_buf_len = 128,
};
i2s_pin_config_t pin_config;
pin_config.bck_io_num = I2S_PIN_NO_CHANGE;
pin_config.ws_io_num = PIN_CLK;
pin_config.data_out_num = I2S_PIN_NO_CHANGE;
pin_config.data_in_num = PIN_DATA;
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin_config);
i2s_set_clk(I2S_NUM_0, SAMPLING_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
}
void mic_record_task(void *arg) {
size_t bytesread;
while (1) {
int total = 0;
i2s_read(I2S_NUM_0, (char *)BUFFER, READ_LEN, &bytesread, (100 / portTICK_RATE_MS));
adcBuffer = (int16_t *)BUFFER;
for (int i = 0; i < READ_LEN / 2; i++) {
total += abs(adcBuffer[i]);
}
soundLevel = total / (READ_LEN / 2);
vTaskDelay(100 / portTICK_RATE_MS);
}
}
#include <Wire.h>
/// Drawing Function for M5Stack
#define Draw M5.Lcd
//// BLE related headers.
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
//// BLE characteristics.
#define MBIT_MORE_SERVICE "0b50f3e4-607f-4151-9091-7d008d6ffc5c"
#define MBIT_MORE_CH_COMMAND "0b500100-607f-4151-9091-7d008d6ffc5c" // R&W(20byte)
#define MBIT_MORE_CH_STATE "0b500101-607f-4151-9091-7d008d6ffc5c" // R(7byte)
#define MBIT_MORE_CH_MOTION "0b500102-607f-4151-9091-7d008d6ffc5c" // R(18byte) :pitch,roll,accel,and gyro
#define MBIT_MORE_CH_PIN_EVENT "0b500110-607f-4151-9091-7d008d6ffc5c" // R&N
#define MBIT_MORE_CH_ACTION_EVENT "0b500111-607f-4151-9091-7d008d6ffc5c" // R&N(20byte) :Buttons with timestamp
#define MBIT_MORE_CH_ANALOG_IN_P0 "0b500120-607f-4151-9091-7d008d6ffc5c" // R
#define MBIT_MORE_CH_ANALOG_IN_P1 "0b500121-607f-4151-9091-7d008d6ffc5c" // R
#define MBIT_MORE_CH_ANALOG_IN_P2 "0b500122-607f-4151-9091-7d008d6ffc5c" // R
#define MBIT_MORE_CH_MESSAGE "0b500130-607f-4151-9091-7d008d6ffc5c" // R : only for v2
#define ADVERTISING_STRING "BBC micro:bit [m5scr]"
/// Data of channels.
// COMMAND CH 20byte
uint8_t cmd[] = { 0x02, // microbit version (v1:0x01, v2:0x02)
0x02, // protocol 0x02 only
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00 };
// STATE CH 7byte
uint8_t state[] = {
0x00, 0x00, 0x00, 0x00, // GPIO 0-3
0x00, // lightlevel
0x00, // temperature(+128)
0x00 // soundlevel
};
// MOTION CH 18 byte
uint8_t motion[] = {
0x00, 0x00, // pitch
0x00, 0x00, // roll
0xff, 0xff, // ax
0xff, 0x00, // ay
0x00, 0xff, // az
0x00, 0x00, // gx
0x00, 0x00, // gy
0x00, 0x00, // gz
0x00, 0x00 // ??
};
// ACTION CH 20 byte
uint8_t action[] = {
0x01, // BUTTON cmd; BUTTON:0x01, GESTURE: 0x02
0x01, 0x00, // Button Name;1:A,2:B,100:P0,101:P1,102:P2,121:LOGO
0x00, // Event Name;1:DOWN, 2:UP, 3:CLICK, 4:LONG_CLICK, 5:HOLD, 6:DOUBLE_CLICK
0x00, 0x00, 0x00, 0x00, // Timestamp
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x12 // ACTION Event
};
// Define for label command
#define DATA_NUMBER 0x13
#define DATA_TEXT 0x14
// ANALOG PIN 2 byte
uint8_t analog[] = { 0x00, 0x00 };
BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristic[9] = { 0 };
bool deviceConnected = false;
// for pixel pattern
#define TEXT_SPACE 30
uint16_t pixel[5][5] = { 0 };
void drawPixel(int x, int y, int c) {
int ps = (screen_w < (screen_h - TEXT_SPACE)) ? screen_w / 5 : (screen_h - TEXT_SPACE) / 5; // Pixel size
if (c == TFT_RED && (myBoard == m5gfx::board_M5StackCoreInk || myBoard == m5gfx::board_M5Paper)) {
Draw.fillRect(x * ps, y * ps + TEXT_SPACE, ps, ps, TFT_WHITE);
} else {
Draw.fillRect(x * ps, y * ps + TEXT_SPACE, ps, ps, c);
}
#if !defined(CONFIG_IDF_TARGET_ESP32S3)
if (myBoard == m5gfx::board_M5Atom) {
if (c == TFT_BLACK) {
leds[x + y * 5] = CRGB::Black;
} else if (c == TFT_RED) {
leds[x + y * 5] = CRGB::Red;
} else if (c == TFT_BLUE) {
leds[x + y * 5] = CRGB::Blue;
}
FastLED.show();
}
#endif
};
void displayShowPixel() {
for (int y = 0; y < 5; y++) {
for (int x = 0; x < 5; x++) {
log_i("%1d", pixel[y][x] & 0b1);
if (pixel[y][x] & 0b1) {
drawPixel(x, y, TFT_RED);
} else {
drawPixel(x, y, TFT_BLACK);
}
}
}
};
void fillScreen(int c) {
Draw.fillScreen(c);
// for Atom Matrix and Lite
if (myBoard == m5gfx::board_M5Atom) {
for (int x = 0; x < 5; x++) {
for (int y = 0; y < 5; y++) {
drawPixel(x, y, c);
}
}
}
};
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
log_i("connect\n");
deviceConnected = true;
fillScreen(TFT_BLACK);
};
void onDisconnect(BLEServer *pServer) {
log_i("disconnect\n");
deviceConnected = false;
ESP.restart();
}
};
// dummy callback
class DummyCallbacks : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic *pCharacteristic) {
log_i("DUMMY Read\n");
}
void onWrite(BLECharacteristic *pCharacteristic) {
log_i("DUMMY Write\n");
}
};
// for cmd
// Global variable for drawing graphics using data & label.
uint32_t label_flag = 0;
uint32_t x_0, y_0, x_1, y_1, x_2, y_2 = 0; // (x,y) axis
uint32_t x_c, y_c = 0; // position of cursor for text
String str = ""; // String for text output
uint32_t size = 1; // text size
uint32_t tc = 0; // text color
uint32_t w, h = 0; // width & height
uint32_t r = 0; // radius for circle
uint32_t c = 0; // color
void getLabelDataValue(char *var_name, String label_str, uint32_t *var, int data_val) {
if (!label_str.compareTo(var_name)) {
*var = data_val;
}
}
// Stackchan Draw command
int norm_x(int x) {
return (int(x / 320.0 * screen_w));
}
int norm_y(int y) {
return (int(y / 240.0 * screen_h));
}
void clear_eyes() {
Draw.fillRect(norm_x(0), norm_y(0), norm_x(320), norm_y(120), TFT_BLACK);
}
void clear_mouth() {
Draw.fillRect(norm_x(0), norm_y(120), norm_x(320), norm_y(120), TFT_BLACK);
}
void draw_eye() {
clear_eyes();
Draw.fillCircle(norm_x(90), norm_y(93), norm_y(8), TFT_WHITE);
Draw.fillCircle(norm_x(230), norm_y(96), norm_y(8), TFT_WHITE);
}
void draw_closeeye() {
clear_eyes();
Draw.fillRect(norm_x(82), norm_y(93), norm_x(16), norm_y(4), TFT_WHITE);
Draw.fillRect(norm_x(222), norm_y(93), norm_x(16), norm_y(4), TFT_WHITE);
}
void draw_mouth() {
clear_mouth();
Draw.fillRect(norm_x(163 - 45), norm_y(148), norm_x(90), norm_y(4), TFT_WHITE);
}
void draw_openmouth() {
clear_mouth();
Draw.fillRect(norm_x(140), norm_y(130), norm_x(40), norm_y(40), TFT_WHITE);
}
// Microbit More Command handling
class CmdCallbacks : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic *pCharacteristic) {
log_i("CMD read\n");
pCharacteristic->setValue(cmd, 20);
}
void cmd_pin(const char *cmd_str) {
char pin_cmd = (cmd_str[0] & 0x0f);
int pin_num = cmd_str[1];
int pin_value = cmd_str[2];
log_i("CMD_PIN\n");
log_i(" pin:%d, dat:%d\n", pin_num, pin_value);
switch (pin_cmd) {
case 0x01:
// OUTPUT
log_i(" OUTPUT\n");
pin_mode[pin_num] = PIN_DIGITAL_OUTPUT;
pinMode(pin[pin_num], OUTPUT);
digitalWrite(pin[pin_num], pin_value);
break;
case 0x02:
// PWM
log_i(" PWM\n");
pin_mode[pin_num] = PIN_PWM;
analogWrite(pin[pin_num], pin_value);
break;
case 0x03:
// SERVO
log_i(" SERVO\n");
log_i(" range:%d, center:%d\n", cmd_str[3], cmd_str[4]);
pin_mode[pin_num] = PIN_SERVO;
analogWrite(pin[pin_num], pin_value / 1.80 + 2.5); // The pin_value means angle for servo.
break;
case 0x04:
// PULL
log_i(" PULL\n");
pin_mode[pin_num] = PIN_PULL;
break;
case 0x05:
// EVENT
log_i(" EVENT\n");
pin_mode[pin_num] = PIN_EVENT;
break;
}
}
void display_text(const char *text) {
log_i(">> text\n");
log_i("%s\n", text);
Draw.fillRect(0, 0, screen_w, TEXT_SPACE - 1, TFT_BLACK);
if (stackchan_mode) {
Draw.fillEllipse(0, 0, screen_w, TEXT_SPACE, TFT_WHITE);
Draw.fillTriangle(screen_w / 2 - screen_w * 0.1, TEXT_SPACE * 0.8,
screen_w / 2, TEXT_SPACE * 1.5,
screen_w / 2 + screen_w * 0.1, TEXT_SPACE * 0.5, TFT_WHITE);
Draw.setTextColor(TFT_BLACK);
} else {
Draw.setTextColor(TFT_WHITE);
}
int text_size = 4;
if (myBoard == m5gfx::board_M5StickC || myBoard == m5gfx::board_M5AtomS3) {
text_size = 2;
} else if (myBoard == m5gfx::board_M5StickCPlus || myBoard == m5gfx::board_M5StickCPlus2) {
text_size = 3;
}
Draw.setCursor(0, 0);
Draw.setTextSize(text_size);
Draw.println(text);
}
void cmd_display(const char *cmd_str) {
char cmd_display = cmd_str[0] & 0b11111;
switch (cmd_display) {
case 0x00:
// CLEAR
log_i(">> clear\n");
fillScreen(TFT_BLACK);
break;
case 0x01:
// TEXT
display_text(&(cmd_str[1]));
break;
case 0x02:
// PIXELS_0
log_i(">> pixel0\n");
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 5; x++) {
pixel[y][x] = (cmd_str[y * 5 + (x + 1)] & 0xb);
}
}
break;
case 0x03:
// PIXELS_1
log_i(">> pixel1\n");
for (int y = 3; y < 5; y++) {
for (int x = 0; x < 5; x++) {
pixel[y][x] = (cmd_str[(y - 3) * 5 + (x + 1)] & 0xb);
}
}
displayShowPixel();
break;
}
}
void cmd_audio(const char *cmd_str) {
char cmd_audio = cmd_str[0] & 0b11111;
switch (cmd_audio) {
case 0x00:
// STOP_TONE 0x00
log_i(">> Stop tone\n");
M5.Speaker.stop();
break;
case 0x01:
// PLAY_TONE 0x01
const uint8_t max_volume = 255;
log_i(">> Play tone\n");
uint32_t duration = (cmd_str[4] & 0xff) << 24
| (cmd_str[3] & 0xff) << 16
| (cmd_str[2] & 0xff) << 8
| (cmd_str[1] & 0xff);
uint16_t freq = 1000000 / duration;
uint8_t volume = map(cmd_str[5], 0, 255, 0, max_volume);
log_i("Volume:%d\n", volume);
log_i("Duration:%d\n", duration);
log_i("Freq:%d\n", freq);
M5.Speaker.setVolume(volume);
M5.Speaker.tone(freq);
break;
}
}
void display_label_data(char *label, char *data, float data_val) {
int label_location_x = 210;
int label_location_y = 40;
int font_height = 10;
int text_size = 1;
if (myBoard == m5gfx::board_M5Stack || myBoard == m5gfx::board_M5StackCore2 || myBoard == m5gfx::board_M5StackCoreS3) {
label_location_x = 210;
label_location_y = 40;
} else if (myBoard == m5gfx::board_M5StickC || myBoard == m5gfx::board_M5StickCPlus || myBoard == m5gfx::board_M5StickCPlus2) {
label_location_x = 0;
if (myBoard == m5gfx::board_M5StickC) {
label_location_y = 110;
} else if (myBoard == m5gfx::board_M5StickCPlus || myBoard == m5gfx::board_M5StickCPlus2) {
label_location_y = 170;
}
} else if (myBoard == m5gfx::board_M5Cardputer) {
label_location_x = 0;
label_location_y = 170;
} else if (myBoard == m5gfx::board_M5AtomS3) {
label_location_x = 0;
label_location_y = 100;
}
Draw.setTextSize(text_size);
Draw.setTextColor(TFT_WHITE);
// Draw.fillRect(0, label_location_y, screen_w, screen_h - label_location_y, TFT_BLACK);
// Draw.fillRect(label_location_x, label_location_y, screen_w - label_location_x, screen_h, TFT_BLACK);
Draw.fillRect(label_location_x, label_location_y, screen_w - label_location_x, screen_h - label_location_y, TFT_BLACK);
Draw.setCursor(label_location_x, label_location_y);
Draw.printf("Label:%s\n", label);
Draw.setCursor(label_location_x, label_location_y + font_height * 1);
Draw.printf("Data:%s\n", data);
Draw.setCursor(label_location_x, label_location_y + font_height * 2);
Draw.printf(" val:");
if (data_val < 100000) {
Draw.printf("%8.2f", data_val);
} else {
Draw.printf("too big");
}
}
void
set_variables(String label_str, float data_val, String data_str) {
// Display label & data?
getLabelDataValue("label", label_str, &label_flag, data_val);
// Store variables
getLabelDataValue("x0", label_str, &x_0, data_val);
getLabelDataValue("y0", label_str, &y_0, data_val);
getLabelDataValue("x1", label_str, &x_1, data_val);
getLabelDataValue("y1", label_str, &y_1, data_val);
getLabelDataValue("x2", label_str, &x_2, data_val);
getLabelDataValue("y2", label_str, &y_2, data_val);
getLabelDataValue("xc", label_str, &x_c, data_val);
getLabelDataValue("yc", label_str, &y_c, data_val);
if (!label_str.compareTo("str")) {
str = data_str;
}
getLabelDataValue("size", label_str, &size, data_val);
getLabelDataValue("tc", label_str, &tc, data_val);
getLabelDataValue("w", label_str, &w, data_val);
getLabelDataValue("h", label_str, &h, data_val);
getLabelDataValue("r", label_str, &r, data_val);
getLabelDataValue("c", label_str, &c, data_val);
}
void label_draw_cmd(String label_str, String data_str) {
if (!label_str.compareTo("cmd")) {
if (!data_str.compareTo("drawPixel")) {
Draw.drawPixel(x_0, y_0, c);
} else if (!data_str.compareTo("drawLine")) {
Draw.drawLine(x_0, y_0, x_1, y_1, c);
} else if (!data_str.compareTo("drawRect")) {
Draw.drawRect(x_0, y_0, w, h, c);
} else if (!data_str.compareTo("drawTriangl")) { // "drawTriangle" is over data length limit.
Draw.drawTriangle(x_0, y_0, x_1, y_1, x_2, y_2, c);
} else if (!data_str.compareTo("drawRoundRe")) { // "drawRoundRect" is over data length limit.
Draw.drawRoundRect(x_0, y_0, w, h, r, c);
} else if (!data_str.compareTo("fillScreen")) {
Draw.fillScreen(c);
} else if (!data_str.compareTo("fillRect")) {
Draw.fillRect(x_0, y_0, w, h, c);
} else if (!data_str.compareTo("fillCircle")) {
Draw.fillCircle(x_0, y_0, r, c);
} else if (!data_str.compareTo("fillTriangl")) { // "fillTriangle" is over data length limit.
Draw.fillTriangle(x_0, y_0, x_1, y_1, x_2, y_2, c);
} else if (!data_str.compareTo("fillRoundRe")) { // "fillRoundRect" is over data length limit.
Draw.fillRoundRect(x_0, y_0, w, h, r, c);
} else if (!data_str.compareTo("print")) {
Draw.setTextColor(tc);
Draw.setTextSize(size);
Draw.setCursor(x_c, y_c);
Draw.print(str);
}
}
}
void label_stackchan_cmd(String label_str, String data_str) {
if (!label_str.compareTo("stack")) {
if (!data_str.compareTo("eye")) {
draw_eye();
} else if (!data_str.compareTo("closeeye")) {
draw_closeeye();
} else if (!data_str.compareTo("mouth")) {
draw_mouth();
} else if (!data_str.compareTo("openmouth")) {
draw_openmouth();
} else if (!data_str.compareTo("say")) {
draw_openmouth();
delay(10);
draw_mouth();
delay(10);
} else if (!data_str.compareTo("on")) {
stackchan_mode = true;
} else if (!data_str.compareTo("off")) {
stackchan_mode = false;
}
}
}
void cmd_data(const char *cmd_str) {
log_i("CMD DATA\n");
// Show input data.
log_i(">>> Data input:");
for (int i = 0; i <= 20; i++) {
log_i("(%d)%02x%c:", i, cmd_str[i], cmd_str[i]);
}
log_i("\n");
// Convert from input data to label & data.
char label[9] = { 0 };
strncpy(label, &cmd_str[1], sizeof(label) - 1);
String label_str = String(label);
char data[12] = { 0 };
strncpy(data, &cmd_str[9], sizeof(data) - 1);
String data_str = String(data);
// Convert from 8bit uint8_t x 4 to 32bit float with little endian.
cd.b[0] = cmd_str[9];
cd.b[1] = cmd_str[10];
cd.b[2] = cmd_str[11];
cd.b[3] = cmd_str[12];
float data_val = cd.f;
log_i("Label str:%s, Data str:%s, Data value:%f.\n", label_str, data_str, data_val);
// Can't get correct command for number=0x13 and text=0x14. Why?
char cmd_data = cmd_str[20];
if (cmd_data == 0x13) {
log_i("Data is Number.\n");
} else if (cmd_data == 0x14) {
log_i("Data is Text.\n");
} else {
log_i("Data is Unknown:%02x.\n", cmd_data);
}
// Show label & data at display.
if (label_flag != 0) {
display_label_data(label, data, data_val);
}
// On and off LED at M5StickC family.
// Change the LED brightness level to an integer value labeled "led".
if (strcmp(label, "led") == 0) {
M5.Power.setLed(constrain(data_val, 0, 255));
}
// LED output to Spresense
const char *led = data_str.c_str();
if (strcmp(label, "led0") == 0) {
Serial2.printf("LED0:%s\n", led);
} else if (strcmp(label, "led1") == 0) {
Serial2.printf("LED1:%s\n", led);
} else if (strcmp(label, "led2") == 0) {
Serial2.printf("LED2:%s\n", led);
} else if (strcmp(label, "led3") == 0) {
Serial2.printf("LED3:%s\n", led);
}
// Set variables for drawing object.
set_variables(label_str, data_val, data_str);
// Do command: for drawing and stackchan
label_draw_cmd(label_str, data_str);
label_stackchan_cmd(label_str, data_str);
}
void onWrite(BLECharacteristic *pCharacteristic) {
log_i("CMD write\n");
////// MUST implement!!
//// CMD_CONFIG 0x00
// MIC 0x01
// TOUCH 0x02
//// CMD_PIN 0x01
// SET_OUTPUT 0x01
// SET_PWM 0x02
// SET_SERVO 0x03
// SET_PULL 0x04
// SET_EVENT 0x05
std::string value = pCharacteristic->getValue();
log_i("CMD len:%d\n", value.length());
log_i("%s\n", value.c_str());
const char *cmd_str = value.c_str();
log_i("%s\n", cmd_str);
char cmd = (cmd_str[0] >> 5);
switch (cmd) {
case 0x01:
// CMD_PIN
log_i("CMD pin\n");
cmd_pin(cmd_str);
break;
case 0x02:
//// CMD_DISPLAY
log_i("CMD display\n");
cmd_display(cmd_str);
break;
case 0x03:
//// CMD_AUDIO
log_i("CMD audio\n");
cmd_audio(cmd_str);
break;
case 0x04:
//// CMD_DATA (only v2)
log_i("CMD data\n");
cmd_data(cmd_str);
break;
}
}
};
// for state
class StateCallbacks : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic *pCharacteristic) {
float temp = 0;
int r0 = 0, r1 = 0;
// GPIO input from PIN0 & PIN1.
if (pin_mode[0] == PIN_ANALOG_INPUT) {
r0 = analogRead(pin[0]);
}
if (pin_mode[1] == PIN_ANALOG_INPUT) {
r1 = analogRead(pin[1]);
}
state[0] = 0;
if (r0 >= 2048) {
state[0] |= 0b01;
}
if (r1 >= 2048) {
state[0] |= 0b10;
}
if (myBoard == m5gfx::board_M5StickC || myBoard == m5gfx::board_M5StickCPlus || myBoard == m5gfx::board_M5StickCPlus2) {
state[6] = ((int)map(soundLevel, 0, 1024, 0, 255) & 0xff);
} else {
state[6] = (random(256) & 0xff); // Random sensor value for soundlevel
}
M5.Imu.getTemp(&temp); // get temperature from IMU
state[4] = (random(256) & 0xff); // Random sensor value for lightlevel
state[5] = ((int)(temp + 128) & 0xff); // temperature(+128)
log_i("STATE read %s", (char *)state);
pCharacteristic->setValue(state, 7);
}
};
// for accelerometer related values
#define ACC_MULT 512
#if !defined(RAD_TO_DEG)
#define RAD_TO_DEG 57.324
#endif
float ax, ay, az;
int16_t iax, iay, iaz;
float gx, gy, gz;
float pitch, roll, yaw;
void updateIMU() {
M5.Imu.getAccel(&ax, &ay, &az); // get accel
M5.Imu.getGyro(&gx, &gy, &gz); // get gyro
iax = (int16_t)(ax * ACC_MULT);
iay = (int16_t)(ay * ACC_MULT);
iaz = (int16_t)(az * ACC_MULT);
}
class MotionCallbacks : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic *pCharacteristic) {
updateIMU();
motion[0] = ((int)(pitch * ACC_MULT) & 0xff);
motion[1] = (((int)(pitch * ACC_MULT) >> 8) & 0xff);
motion[2] = ((int)(roll * ACC_MULT) & 0xff);
motion[3] = (((int)(roll * ACC_MULT) >> 8) & 0xff);
motion[4] = (iax & 0xff);
motion[5] = ((iax >> 8) & 0xff);
motion[6] = (iay & 0xff);
motion[7] = ((iay >> 8) & 0xff);
motion[8] = (-iaz & 0xff);
motion[9] = ((-iaz >> 8) & 0xff);
pCharacteristic->setValue(motion, 20);
// debug print
char msg[256] = { 0 };
for (int i = 0; i < sizeof(motion); i++) {
sprintf(&msg[i * 3], "%02x,", motion[i], sizeof(motion) * 3 - 3 * i);
}
log_i("MOTION read: %s\n", msg);
}
};
// for button
class ActionCallbacks : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic *pCharacteristic) {
log_i("BTN read\n");
pCharacteristic->setValue("Read me!!"); // dummy data
}
};
// for Analog pin
class AnalogPinCallback0 : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic *pCharacteristic) {
int r = 0;
r = map(analogRead(pin[0]), 0, 4095, 0, 1023);
log_i("Analog Pin0 Read:%d\n", r);
analog[0] = (r & 0xff);
analog[1] = ((r >> 8) & 0xff);
pCharacteristic->setValue(analog, 2);
}
};
class AnalogPinCallback1 : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic *pCharacteristic) {
int r = 0;
r = map(analogRead(pin[1]), 0, 4095, 0, 1023);
log_i("Analog Pin1 Read:%d\n", r);
analog[0] = (r & 0xff);
analog[1] = ((r >> 8) & 0xff);
pCharacteristic->setValue(analog, 2);
}
};
class AnalogPinCallback2 : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic *pCharacteristic) {
int r = 0;
r = map(analogRead(pin[2]), 0, 4095, 0, 1023);
log_i("Analog Pin2 Read:%d\n", r);
analog[0] = (r & 0xff);
analog[1] = ((r >> 8) & 0xff);
pCharacteristic->setValue(analog, 2);
}
};
void setup_M5Stack() {
// Init M5Stack.
auto cfg = M5.config();
M5.begin(cfg);
// for support BinsPack-for-StackChan-Core2 https://github.com/NoRi-230401/BinsPack-for-StackChan-Core2
if (m5gfx::board_M5StackCore2) {
checkSDUpdater(SD, MENU_BIN, 2000);
}
M5.Display.init();
// Init speaker.
auto spk_cfg = M5.Speaker.config();
M5.Speaker.config(spk_cfg);
M5.Speaker.begin();
myBoard = M5.getBoard();
// Setup M5Dial
if (myBoard == m5gfx::board_M5Dial) {
M5Dial.begin(cfg, true, false);
}
#if !defined(CONFIG_IDF_TARGET_ESP32S3)
// Init FastLED(NeoPixel).
if (myBoard == m5gfx::board_M5Atom) {
FastLED.addLeds<WS2811, LED_DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(20);
}
#endif
// for Mic input
if (myBoard == m5gfx::board_M5StickC || myBoard == m5gfx::board_M5StickCPlus || myBoard == m5gfx::board_M5StickCPlus2) {
i2sInit();
xTaskCreate(mic_record_task, "mic_record_task", 2048, NULL, 1, NULL);
}
}
void setup_pins() {
//// GPIO
// Dirty hack for CoreS3, M5Dial and StampS3
#if !defined(GPIO_NUM_22)
#define GPIO_NUM_22 22
#endif
#if !defined(GPIO_NUM_25)
#define GPIO_NUM_25 25
#endif
switch (myBoard) {
case m5gfx::board_M5Atom:
case m5gfx::board_M5AtomU:
case m5gfx::board_M5AtomPsram:
pin[0] = GPIO_NUM_32;
pin[1] = GPIO_NUM_26;
break;
case m5gfx::board_M5Stack:
pin[0] = GPIO_NUM_36; // Port.B
pin[1] = GPIO_NUM_26; // Port.B
pin[2] = GPIO_NUM_22; // Port.A
pin[8] = GPIO_NUM_21; // Port.A
break;
case m5gfx::board_M5StackCore2:
case m5gfx::board_M5Tough:
pin[0] = GPIO_NUM_33; // Port.A
pin[1] = GPIO_NUM_32; // Port.A
pin[2] = GPIO_NUM_36; // Port.B
pin[8] = GPIO_NUM_26; // Port.B
break;
case m5gfx::board_M5StickC:
case m5gfx::board_M5StickCPlus:
case m5gfx::board_M5StickCPlus2:
case m5gfx::board_M5StackCoreInk:
pin[0] = GPIO_NUM_33; // Port.A (Universal)
pin[1] = GPIO_NUM_32; // Port.A (Universal)
break;
case m5gfx::board_M5Paper:
pin[0] = GPIO_NUM_32; // Port.A
pin[1] = GPIO_NUM_25; // Port.A
pin[2] = GPIO_NUM_33; // Port.B
pin[8] = GPIO_NUM_26; // Port.B
break;
case m5gfx::board_M5StackCoreS3:
pin[0] = GPIO_NUM_1; // Port.A
pin[1] = GPIO_NUM_2; // Port.A
pin[2] = GPIO_NUM_8; // Port.B
pin[8] = GPIO_NUM_9; // Port.B
break;
case m5gfx::board_M5Dial:
pin[0] = GPIO_NUM_1; // Port.A
pin[1] = GPIO_NUM_2; // Port.A
pin[2] = GPIO_NUM_15; // Port.B
pin[8] = GPIO_NUM_13; // Port.B
break;
case m5gfx::board_M5AtomS3:
case m5gfx::board_M5Cardputer:
pin[0] = GPIO_NUM_1; // Port.A (Universal)
pin[1] = GPIO_NUM_2; // Port.A (Universal)
break;
default:
break;
}
}
void setup_BLE() {
// Create MAC address base fixed ID
uint8_t mac0[6] = { 0 };
// Create random mac address for avoid conflict ID.
esp_efuse_mac_get_default(mac0);
randomSeed(analogRead(analogRead(pin[0])));
for (int i = 0; i < sizeof(mac0); i++) {
mac0[i] = random(256);
}
String ID;
for (int i = 0; i < 6; i++) {
char ID_char = (((mac0[i] - 0x61) & 0b0011110) >> 1) + 0x61;
ID += ID_char;
}
log_i("ID char:%s\n", ID.c_str());
char adv_str[32] = { 0 };
String("BBC micro:bit [" + ID + "]").toCharArray(adv_str, sizeof(adv_str));
// Start up screen
fillScreen(TFT_BLUE);
Draw.setTextSize(2);
Draw.setCursor(0, 0);
Draw.print("Welcome to\nM5bit Less!!\nPlease connect to\n");
Draw.println(adv_str);
log_i("BLE start.\n");
log_i("%s\n", adv_str);
BLEDevice::init(adv_str);
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
BLEService *pService = pServer->createService(BLEUUID(MBIT_MORE_SERVICE), 27);
// CMD
pCharacteristic[0] = pService->createCharacteristic(
MBIT_MORE_CH_COMMAND,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
pCharacteristic[0]->setCallbacks(new CmdCallbacks());
pCharacteristic[0]->addDescriptor(new BLE2902());
// STATE
pCharacteristic[1] = pService->createCharacteristic(
MBIT_MORE_CH_STATE,
BLECharacteristic::PROPERTY_READ);
pCharacteristic[1]->setCallbacks(new StateCallbacks());
pCharacteristic[1]->addDescriptor(new BLE2902());
// MOTION
pCharacteristic[2] = pService->createCharacteristic(
MBIT_MORE_CH_MOTION,
BLECharacteristic::PROPERTY_READ);
pCharacteristic[2]->setCallbacks(new MotionCallbacks());
pCharacteristic[2]->addDescriptor(new BLE2902());
pCharacteristic[3] = pService->createCharacteristic(
MBIT_MORE_CH_PIN_EVENT,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
pCharacteristic[3]->setCallbacks(new DummyCallbacks());
pCharacteristic[3]->addDescriptor(new BLE2902());
// ACTION
pCharacteristic[4] = pService->createCharacteristic(
MBIT_MORE_CH_ACTION_EVENT,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
pCharacteristic[4]->setCallbacks(new ActionCallbacks());
pCharacteristic[4]->addDescriptor(new BLE2902());
// PINS
pCharacteristic[5] = pService->createCharacteristic(
MBIT_MORE_CH_ANALOG_IN_P0,
BLECharacteristic::PROPERTY_READ);
pCharacteristic[5]->setCallbacks(new AnalogPinCallback0());
pCharacteristic[5]->addDescriptor(new BLE2902());
pCharacteristic[6] = pService->createCharacteristic(
MBIT_MORE_CH_ANALOG_IN_P1,
BLECharacteristic::PROPERTY_READ);
pCharacteristic[6]->setCallbacks(new AnalogPinCallback1());
pCharacteristic[6]->addDescriptor(new BLE2902());
pCharacteristic[7] = pService->createCharacteristic(
MBIT_MORE_CH_ANALOG_IN_P2,
BLECharacteristic::PROPERTY_READ);
pCharacteristic[7]->setCallbacks(new AnalogPinCallback2());
pCharacteristic[7]->addDescriptor(new BLE2902());
// MESSAGE (only for v2)
pCharacteristic[8] = pService->createCharacteristic(
MBIT_MORE_CH_MESSAGE,
BLECharacteristic::PROPERTY_READ);
pCharacteristic[8]->setCallbacks(new DummyCallbacks());
pCharacteristic[8]->addDescriptor(new BLE2902());
pService->start();
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->start();
}
void setup() {
Serial.begin(115200);
Serial2.begin(115200);
setup_M5Stack();
screen_w = M5.Lcd.width();
screen_h = M5.Lcd.height();
setup_pins();
setup_BLE();
}
void sendBtn(uint8_t btnID, uint8_t btn, uint8_t btn_status, uint8_t prev) {
memset((char *)(action), 0, 20); // clear action buffer
action[0] = 0x01; // for Button event
action[19] = 0x12; // ACTION_EVENT
action[1] = btnID; // btnID 0x01:BtnA, 0x02:BtnB, 121:BtnC(LOGO)
// Set TimeStamp (Little Endian)
uint32_t time = (uint32_t)millis();
action[4] = (time & 0xff);
action[5] = (time >> 8) & 0xff;
action[6] = (time >> 16) & 0xff;
action[7] = (time >> 24) & 0xff;
if (btn) {
// Button CLICK
log_i(" button clicked!\n");
action[3] = 0x03;
pCharacteristic[4]->setValue(action, 20);
pCharacteristic[4]->notify();
}
if (btn_status == 0 && prev == 1) {
// Button Up
log_i(" button up!\n");
action[3] = 0x02;
pCharacteristic[4]->setValue(action, 20);
pCharacteristic[4]->notify();
} else if (btn_status == 1 && prev == 0) {
// Button Down
log_i(" button down!\n");
action[3] = 0x01;
pCharacteristic[4]->setValue(action, 20);
pCharacteristic[4]->notify();
}
}
// Previous button state
uint8_t prevA = 0, prevB = 0, prevC = 0;
uint32_t old_label_time = 0;
void loop() {
// Board status update.
M5.update();
if (myBoard == m5gfx::board_M5Dial) {
M5Dial.update();
}
if (deviceConnected) {
// Send notify data for button A, B and C(LOGO).
uint8_t btnA = 0, btnB = 0, btnC = 0,
btn_statusA = 0, btn_statusB = 0, btn_statusC = 0;
// Get all button status
btnA = M5.BtnA.wasPressed();
btn_statusA = M5.BtnA.isPressed();
btnB = M5.BtnB.wasPressed();
btn_statusB = M5.BtnB.isPressed();
btnC = M5.BtnC.wasPressed();
btn_statusC = M5.BtnC.isPressed();
#define BUTTON_DELAY 50
//// Button A
action[1] = 0x01;
sendBtn(0x01, btnA, btn_statusA, prevA);
prevA = btn_statusA;
delay(BUTTON_DELAY);
//// Button B
action[1] = 0x02;
sendBtn(0x02, btnB, btn_statusB, prevB);
prevB = btn_statusB;
delay(BUTTON_DELAY);
//// Button C (LOGO)
action[1] = 121; // LOGO 121
sendBtn(121, btnC, btn_statusC, prevC);
prevC = btn_statusC;
delay(BUTTON_DELAY);
updateGesture();
uint32_t label_time = (uint32_t)millis();
if (label_time - old_label_time > 50) {
// Send dummy data label='a' data=random('a'-'z') every 50ms
memset((char *)(action), 0, 20); // clear action buffer
action[19] = DATA_TEXT;
action[0] = 'a'; // Label 'a'
action[1] = 0;
action[8] = 'a' + random(26); // 'a-z'
action[9] = 0;
pCharacteristic[4]->setValue(action, 20);
pCharacteristic[4]->notify();
//// Send touch panel information
// Get touch panel data.
float tx;
float ty;
if (myBoard == m5gfx::board_M5Dial) {
auto t = M5Dial.Touch.getDetail();
tx = t.x;
ty = t.y;
} else {
auto t = M5.Touch.getDetail();
tx = t.x;
ty = t.y;
}
// Send X axis as 'tx'
cd.f = (float)tx;
memset((char *)(action), 0, 20); // clear action buffer
action[19] = DATA_NUMBER;
action[0] = 't'; // Label 'tx'
action[1] = 'x';
action[8] = cd.b[0];
action[9] = cd.b[1];
action[10] = cd.b[2];
action[11] = cd.b[3];
pCharacteristic[4]->setValue(action, 20);
pCharacteristic[4]->notify();
// Send Y axis as 'ty'
cd.f = (float)ty;
memset((char *)(action), 0, 20); // clear action buffer
action[19] = DATA_NUMBER;
action[0] = 't'; // Label 'ty'
action[1] = 'y';
action[8] = cd.b[0];
action[9] = cd.b[1];
action[10] = cd.b[2];
action[11] = cd.b[3];
pCharacteristic[4]->setValue(action, 20);
pCharacteristic[4]->notify();
//// Encoder for M5Dial
if (myBoard == m5gfx::board_M5Dial) {
cd.f = (float)M5Dial.Encoder.read();
memset((char *)(action), 0, 20); // clear action buffer
action[19] = DATA_NUMBER;
action[0] = 'e'; // Label 'e'
action[8] = cd.b[0];
action[9] = cd.b[1];
action[10] = cd.b[2];
action[11] = cd.b[3];
pCharacteristic[4]->setValue(action, 20);
pCharacteristic[4]->notify();
}
if (myBoard == m5gfx::board_M5Stack) {
// keyboard input for M5Stack Faces
if (digitalRead(5) == LOW) {
Wire.requestFrom(0x08, 1); // 0x08 means FACES_KEYBOARD_I2C_ADDR.
while (Wire.available()) {
char c = Wire.read(); // receive a byte as character
Serial.printf("Key:%c\n", c); // print the character
memset((char *)(action), 0, 20); // clear action buffer
action[19] = DATA_TEXT;
action[0] = 'K'; // Label 'Key'
action[1] = 'e';
action[2] = 'y';
action[3] = 0x00;
action[8] = c; // Key character
action[9] = 0;
delay(50); // Wait 50ms
pCharacteristic[4]->setValue(action, 20);
pCharacteristic[4]->notify();
}
}
}
old_label_time = label_time;
}
}
//// Get data from Spresense via Serial2.
int av = Serial2.available();
// When serial2 data is available...
while (av > 0) {
String label = Serial2.readStringUntil(':');
String value = Serial2.readStringUntil('\n');
av = Serial2.available();
memset((char *)(action), 0, 20); // clear action buffer
const char *label_str = label.c_str();
strncpy((char *)action, label_str, 7);
// These data is passed as String(TEXT).
if (label.startsWith("Date") || label.startsWith("Fix")) {
action[19] = DATA_TEXT; // Data send as text.
// The data buffer can handle 18 - 8 characters.
const char *buf = value.c_str();
for (int i = 0; i < av && i + 8 < 19; i++) {
action[i + 8] = buf[i];
}
} else {
// Other data is passed as float.
action[19] = DATA_NUMBER; // Data send as number.
cd.f = value.toFloat();
action[8] = cd.b[0];
action[9] = cd.b[1];
action[10] = cd.b[2];
action[11] = cd.b[3];
}
// Send data to Scratch with label 'GPS'.
pCharacteristic[4]->setValue(action, 20);
pCharacteristic[4]->notify();
}
};
SPRESENSE.ino
のコードは、SPRESENSE上でGPSデータを取得してScratchに送ることと、Scratchから指定されたLEDの情報に応じてLEDの点滅を操作することが実現されています。
SPRESENSE.ino
#include <GNSS.h>
#define STRING_BUFFER_SIZE 128
#define RESTART_CYCLE (60 * 5) // Every 5min
static SpGnss Gnss;
int led_pins[] = { PIN_LED0, PIN_LED1, PIN_LED2, PIN_LED3 };
void setup() {
int result;
Serial.begin(115200);
Serial2.begin(115200); // Connect to M5Stack via Serial2.
ledOn(PIN_LED0); // Turn on LED0 to indicate initialize
Gnss.setDebugMode(PrintInfo); // Set Debug mode to Info
if (Gnss.begin() != 0) {
Serial.println("Gnss begin error!!");
ledOn(PIN_LED1);
exit(0);
}
// Select all GPS mode.
Gnss.select(GPS);
Gnss.select(GLONASS);
Gnss.select(QZ_L1CA);
// Start positioning
if (Gnss.start(COLD_START) != 0) {
Serial.println("Gnss start error!!");
ledOn(PIN_LED2);
exit(0);
}
ledOff(PIN_LED0); /// Turn off LED0 :Setup done.
}
// Print received data Serial for debug and Serial2 for M5Stack
static void print_with_debug(char *strbuf) {
Serial.print(strbuf);
Serial2.print(strbuf);
}
static void print_pos(SpNavData *pNavData) {
char StringBuffer[STRING_BUFFER_SIZE];
// print date & time
snprintf(StringBuffer, STRING_BUFFER_SIZE, "Date:%04d/%02d/%02d\n", pNavData->time.year, pNavData->time.month, pNavData->time.day);
print_with_debug(StringBuffer);
snprintf(StringBuffer, STRING_BUFFER_SIZE, "Time:%02d%02d%02d.%02d\n", pNavData->time.hour, pNavData->time.minute, pNavData->time.sec, int(pNavData->time.usec / 10000));
print_with_debug(StringBuffer);
// print satellites count
snprintf(StringBuffer, STRING_BUFFER_SIZE, "numSat:%2d\n", pNavData->numSatellites);
print_with_debug(StringBuffer);
snprintf(StringBuffer, STRING_BUFFER_SIZE, "numSatCalc:%2d\n", pNavData->numSatellitesCalcPos);
print_with_debug(StringBuffer);
// HDOP
snprintf(StringBuffer, STRING_BUFFER_SIZE, "HDOP:%.1f\n", pNavData->hdop);
print_with_debug(StringBuffer);
// altitude
snprintf(StringBuffer, STRING_BUFFER_SIZE, "alt:%.1f\n", pNavData->altitude);
print_with_debug(StringBuffer);
// print position data
if (pNavData->posFixMode == FixInvalid) {
print_with_debug("Fix:No-Fix\n");
} else {
print_with_debug("Fix:Fix\n");
}
if (pNavData->posDataExist == 0) {
Serial.print("Post:No Position\n");
} else {
snprintf(StringBuffer, STRING_BUFFER_SIZE, "Lat:%f\nLon:%f\n", pNavData->latitude, pNavData->longitude);
print_with_debug(StringBuffer);
}
}
static void print_condition(SpNavData *pNavData) {
char StringBuffer[STRING_BUFFER_SIZE];
unsigned long cnt;
// Print satellite count.
snprintf(StringBuffer, STRING_BUFFER_SIZE, "numSat:%2d\n", pNavData->numSatellites);
print_with_debug(StringBuffer);
for (cnt = 0; cnt < pNavData->numSatellites; cnt++) {
const char *pType = "GPS";
SpSatelliteType sattype = pNavData->getSatelliteType(cnt);
// Get print conditions.
unsigned long Id = pNavData->getSatelliteId(cnt);
unsigned long Elv = pNavData->getSatelliteElevation(cnt);
unsigned long Azm = pNavData->getSatelliteAzimuth(cnt);
float sigLevel = pNavData->getSatelliteSignalLevel(cnt);
// Print satellite condition.
snprintf(StringBuffer, STRING_BUFFER_SIZE, "[%2ld] Type:%s, Id:%2ld, Elv:%2ld, Azm:%3ld, CN0:", cnt, pType, Id, Elv, Azm);
Serial.print(StringBuffer);
Serial.println(sigLevel, 6);
}
}
String getStrValue(String data, String pattern) {
data.replace(pattern.c_str(), "");
return (data);
}
float getFloatValue(String data, String pattern) {
data.replace(pattern.c_str(), "");
return (data.toFloat());
}
int getIntValue(String data, String pattern) {
data.replace(pattern.c_str(), "");
return (data.toInt());
}
void loop() {
static int LoopCount = 0;
static int LastPrintMin = 0;
int av = Serial2.available();
while (av > 0) {
String line = Serial2.readStringUntil('\n');
av = Serial2.available();
if (line.startsWith("LED")) {
line.replace("LED", "");
int led_num = (int)line.charAt(0) - 48; // 48 means ascii code of '0'
line.replace(String(led_num) + ":", "");
if (strcmp(line.c_str(), "on") == 0) {
ledOn(led_pins[led_num]);
} else {
ledOff(led_pins[led_num]);
}
}
}
// Check update.
if (Gnss.waitUpdate(-1)) {
// Get NaviData.
SpNavData NavData;
Gnss.getNavData(&NavData);
// Print satellite information to Serial every minute.
if (NavData.time.minute != LastPrintMin) {
print_condition(&NavData);
LastPrintMin = NavData.time.minute;
}
// Send position information via Serial2
print_pos(&NavData);
} else {
// Not update.
Serial.println("data not update");
}
// Check loop count.
LoopCount++;
if (LoopCount >= RESTART_CYCLE) {
int error_flag = 0;
// Restart GNSS.
if (Gnss.stop() != 0) {
Serial.println("Gnss stop error!!");
error_flag = 1;
} else if (Gnss.end() != 0) {
Serial.println("Gnss end error!!");
error_flag = 1;
} else {
Serial.println("Gnss stop OK.");
}
if (Gnss.begin() != 0) {
Serial.println("Gnss begin error!!");
error_flag = 1;
} else if (Gnss.start(HOT_START) != 0) {
Serial.println("Gnss start error!!");
error_flag = 1;
} else {
Serial.println("Gnss restart OK.");
}
// If error on LED3 and halt.
if (error_flag == 1) {
ledOn(PIN_LED3);
exit(0);
}
LoopCount = 0;
}
}
デモ
ここでは、システムを外に持ち出して歩いてみて、Scratch上にGPSのトラッキング情報を表示してみます。
動作の様子は以下の通りです。
Geo Scratchを使うことで、現在位置の地図情報だけでなく、県名や市名が表示できるようになっています。
LEDはScratchからランダムに点滅させています。
TODO
これからやりたいことを書いてみたいと思います。
Bluetooth UART接続による有線シリアル接続の排除
現状、SPRESENSEとM5Stackは有線によるシリアルで接続されています。
この構成では、SPRESENSEとM5Stackは近くにある必要があり、システム構成の自由度が低くなっています。
SPRESENSE Bluetooth UARTボード(SPRESENSE用 BLEベースボード)が手元にあるため、これを利用して、SPRESENSEとM5Stackを接続します。こうすることで、SPRESENSEとCore2を離して配置することが可能となるため、配置の自由度が大きくなると考えています。
SPRESENSEから直接Scratchと通信を行う
SPRESENSE用 BLEベースボードは、BLEモジュールISP1507を使っているということで、ファームウエアを更新することで、UART以外にも利用できるようです。これを使うと、M5Stackを介さずに、直接SPRESENSEからScratchに接続できるようになります。
M5Stackに元から付いている加速度センサーなどの情報は利用できなくなりますが、SPRESENSE拡張ボードの多彩な機能がよりダイレクトに利用できることが期待されます。ちなみに、もうすぐSPRESENSE用のIMU拡張ボードも提供開始されるということなので、とても期待しています。
おわりに
SPRESENSEのGPS機能やLED機能をScratchから利用可能にすることができました。
これで、Scratch上でGPSトラッカーを作ったり、地図情報と組み合わせて様々なアプリケーションを作成できるようになりました。
何を作るかは、あなたの好奇心次第。
Enjoy your SPRESENSE life with Scratch and M5Stack!!
投稿者の人気記事
-
610t
さんが
2024/12/11
に
編集
をしました。
(メッセージ: 初版)
-
610t
さんが
2024/12/11
に
編集
をしました。
(メッセージ: 諸々、更新しました。)
-
610t
さんが
2024/12/11
に
編集
をしました。
(メッセージ: ソースコードとデモ部分の記述追加)
-
610t
さんが
2024/12/12
に
編集
をしました。
-
610t
さんが
2024/12/12
に
編集
をしました。
-
610t
さんが
2024/12/12
に
編集
をしました。
-
610t
さんが
2024/12/29
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/02
に
編集
をしました。
-
610t
さんが
2025/01/04
に
編集
をしました。
-
610t
さんが
2025/01/04
に
編集
をしました。
-
610t
さんが
2025/01/04
に
編集
をしました。
-
610t
さんが
2025/01/04
に
編集
をしました。
-
610t
さんが
2025/01/04
に
編集
をしました。
(メッセージ: ソースコードを追加)
-
610t
さんが
2025/01/05
に
編集
をしました。
-
610t
さんが
2025/01/05
に
編集
をしました。
-
610t
さんが
2025/01/05
に
編集
をしました。
-
610t
さんが
2025/01/05
に
編集
をしました。
-
610t
さんが
2025/01/05
に
編集
をしました。
ログインしてコメントを投稿する