ITで遊ぶ

M5StickCを時計に

IoTとか電子工作がにぎやかな今日このごろです。

とはいえ基本的なところから始めると、いろんなことを学ばねばならず結構ハードルは高いです。

しかし電子機器のほうも人に優しい機器が結構出てきています。

私が「これ、イイね!」と思ったのがこれ。

約5センチx2.5センチx1.5センチの小さい箱でディスプレイ、スイッチ、USB端子が目立ちますね。
AmazonなどでM5Stic Cという名前で2,000円ちょっとで売られています。

いい点ははんだ付けやら手を汚さなくて、これとパソコンだけで遊べる点です。

ちょっとこれで遊んでみたいと思います。

概要

中身はあなどれず、

  • 0.96インチ 80×160 TFTディスプレイ
  • USB Type-C
  • ESP32ベースCPU
  • 4 MBフラッシュ+ 520 K RAM
  • 6軸センサー(MPU6886)
  • 赤色LED
  • 赤外線トランスミッタ
  • マイク
  • 2ボタン、1リセット
  • 2.4 G WiFi
  • 80 mAh LiPoバッテリー
  • 拡張可能なソケット
  • Grove用のポート
  • ウェアラブル&ウォールマウントの周辺パーツ
  • 開発プラットフォーム:Arduino利用可能
  • 旧姓 ESP-32 Pico

です。
もちろん、それほど大きな電源をもっているわけではないので、ポケットに入れる時計にする手順を書いてみたいと思います。

Arduinoの開発環境を利用する

環境作成は定型的な仕事なので、知らない人は詳しく書いてくださっているこちらを御覧ください

なお、Mac Catalinaではうまくいきません。
私がやった対策は、
1. CP210 USBドライバーを最新にする
2. G0とGndを直結して強制的にダウンロードモードでリブートする(ケーブルのない人はゼムピンを曲げたり、ホッチキスの針がちょうどG0とGndに届くのでやってみてください)

Windows環境ではあまり問題は起きません。ハードウェアの開発はWindowsをオススメします。
私もこの記事はMacで書いていますが、内容はWindows10でやっています。

時計のソースコード

時計にするソースはこちらを借用しました。
WristWatch_M5
機能の要約は、

  • バッテリーを電源管理IC AXP192で行っているため電力が小さいながら管理可能でディスプレイには残量がでます。
  • 時計なのでできるだけDeep Sleep状態で動きます。
  • スリープ中は10マイクロアンペア未満しか電力を消費しません。
  • アナログ時計です
  • リセットスイッチで起動します。
  • M5_Home_Buttonを押すとSleepします。
  • 起動中は内蔵の加速度計を使用して、腕につけて腕をあげるとスクリーンランプが自動的にオンになります。
  • 腕を下げると、ライトは自動的にオフになります。
  • 動作から10秒なにもしないとオフになります。

ただ、このソースコードは時間を自分でセットすることになっています。
それはイヤなので、ここからWiFiを利用してNTPサーバーから時間をもってくるようにしました。
起動するたびに時間をもってきます。

さらに、自分の名前が出るようにしました。
ソースコード内の日本語だけ変更すれば動作すると思います。
他はいじっていないので、なにか問題があっても自分で解析してくださいね。

#include <M5StickC.h>
#include <WiFi.h>
#include “time.h”

RTC_TimeTypeDef RTC_TimeStruct;
RTC_DateTypeDef RTC_DateStruct;
// WIFI setup
const char* ssid = “あなたのSSID”;
const char* password = “あなたのパスワード”;
const char* ntpServer = “ntp.jst.mfeed.ad.jp”;
const char* myname =”あなたの名前(英数字)”;
uint8_t led_count = 15;
long brightnessTime, tiltTime = 0, tiltTime2 = 0;
float b, c = 0;
int battery = 0;
float accX = 0;
float accY = 0;
float accZ = 0;
#define TFT_GREY 0x5AEB
float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0; // Saved H, M, S x & y multipliers
float sdeg = 0, mdeg = 0, hdeg = 0;
uint16_t osx = 120, osy = 120, omx = 120, omy = 120, ohx = 120, ohy = 120; // Saved H, M, S x & y coords
uint16_t x0 = 0, x1 = 0, yy0 = 0, yy1 = 0;
uint32_t targetTime = 0; // for next 1 second timeout
static uint8_t conv2d(const char* p); // Forward declaration needed for IDE 1.6.x
uint8_t hh, mm, ss, YY, MM, DD, dd;
boolean initial = 1;

void setup() {
//COMMENT next line after first upload
M5.begin();
Wire.begin(32, 33);
pinMode(M5_BUTTON_HOME, INPUT);
pinMode(M5_BUTTON_RST, INPUT);
pinMode(M5_BUTTON_HOME, INPUT_PULLUP);
esp_sleep_enable_ext0_wakeup(GPIO_NUM_37, 0); //1 = High, 0 = Low
//COMMENT next 12 lines after first upload
//hh = conv2d(__TIME__), mm = conv2d(__TIME__ + 3), ss = conv2d(__TIME__ + 6); // Get H, M, S from compile time
// Ntp
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED){
delay(500);
}
configTime(9*3600, 0, ntpServer);
struct tm timeInfo;
if (getLocalTime(&timeInfo)){
RTC_TimeTypeDef TimeStruct;
TimeStruct.Hours = timeInfo.tm_hour;
TimeStruct.Minutes = timeInfo.tm_min;
TimeStruct.Seconds = timeInfo.tm_sec;
M5.Rtc.SetTime(&TimeStruct);

RTC_DateTypeDef DateStruct;
DateStruct.WeekDay = timeInfo.tm_wday;
DateStruct.Month = timeInfo.tm_mon + 1;
DateStruct.Date = timeInfo.tm_mday;
DateStruct.Year = timeInfo.tm_year + 1900;
M5.Rtc.SetData(&DateStruct);
}
//disconnect wifi
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);

//COMMENT UPTO HERE
M5.Rtc.GetTime(&RTC_TimeStruct);
hh = RTC_TimeStruct.Hours;
mm = RTC_TimeStruct.Minutes;
ss = RTC_TimeStruct.Seconds;

M5.Rtc.GetData(&RTC_DateStruct);
YY = RTC_DateStruct.Year;
MM = RTC_DateStruct.Month;
DD = RTC_DateStruct.Date;
dd = RTC_DateStruct.WeekDay;
M5.Lcd.setRotation(3);
M5.Lcd.fillScreen(TFT_GREY);

M5.Lcd.setTextColor(TFT_WHITE, TFT_GREY); // Adding a background colour erases previous text automatically

// Draw clock face
M5.Lcd.fillCircle(40, 40, 40, TFT_BLACK);
M5.Lcd.fillCircle(40, 40, 36, TFT_BLACK);

// Draw 12 lines
for (int i = 0; i < 360; i += 30) {
sx = cos((i – 90) * 0.0174532925);
sy = sin((i – 90) * 0.0174532925);
x0 = sx * 38 + 40;
yy0 = sy * 38 + 40;
x1 = sx * 32 + 40;
yy1 = sy * 32 + 40;

M5.Lcd.drawLine(x0, yy0, x1, yy1, TFT_WHITE);
}

// Draw 60 dots
for (int i = 0; i < 360; i += 6) {
sx = cos((i – 90) * 0.0174532925);
sy = sin((i – 90) * 0.0174532925);
x0 = sx * 34 + 40;
yy0 = sy * 34 + 40;
// Draw minute markers
M5.Lcd.drawPixel(x0, yy0, TFT_WHITE);

// Draw main quadrant dots
if (i == 0 || i == 180) M5.Lcd.fillCircle(x0, yy0, 2, TFT_WHITE);
if (i == 90 || i == 270) M5.Lcd.fillCircle(x0, yy0, 2, TFT_WHITE);
}
M5.Lcd.fillCircle(40, 40, 2, TFT_WHITE);
M5.Lcd.drawCentreString(myname, 120, 260, 2);
targetTime = millis() + 1000;
M5.Axp.ScreenBreath(9);
M5.MPU6886.Init();
}

void brightnessT() {
M5.MPU6886.getAccelData(&accX, &accY, &accZ);
accX *= 1000;
accY *= 1000;
if ((accX < (0 – 200) && (accX > -900)) && ((accY > 0 – 300) && (accY < 300)))
{
if (millis() > (tiltTime + 500)) {
M5.Axp.ScreenBreath(12);
brightnessTime = millis();
// while (accX < (0 – 500) && ((accY > 0) && (accY < 100)));
}
}
else
tiltTime = millis();
if (brightnessTime < millis() – 2000)
{
M5.Axp.ScreenBreath(7);
brightnessTime = 0;
}
}

void wristWatch() {
if (targetTime < millis()) {
targetTime += 1000;
ss++; // Advance second
if (ss == 60) {
ss = 0;
mm++; // Advance minute
if (mm > 59) {
mm = 0;
hh++; // Advance hour
if (hh > 23) {
hh = 0;
}
}
}
// Pre-compute hand degrees, x & y coords for a fast screen update
sdeg = ss * 6; // 0-59 -> 0-354
mdeg = mm * 6 + sdeg * 0.01666667; // 0-59 -> 0-360 – includes seconds
hdeg = hh * 30 + mdeg * 0.0833333; // 0-11 -> 0-360 – includes minutes and seconds
hx = cos((hdeg – 90) * 0.0174532925);
hy = sin((hdeg – 90) * 0.0174532925);
mx = cos((mdeg – 90) * 0.0174532925);
my = sin((mdeg – 90) * 0.0174532925);
sx = cos((sdeg – 90) * 0.0174532925);
sy = sin((sdeg – 90) * 0.0174532925);

if (ss == 0 || initial) {
initial = 0;
// Erase hour and minute hand positions every minute
M5.Lcd.drawLine(ohx, ohy, 40, 40, TFT_BLACK);
ohx = hx * 15 + 40;
ohy = hy * 15 + 40;
M5.Lcd.drawLine(omx, omy, 40, 40, TFT_BLACK);
omx = mx * 20 + 40;
omy = my * 20 + 40;
}
// Redraw new hand positions, hour and minute hands not erased here to avoid flicker
M5.Lcd.drawLine(osx, osy, 40, 40, TFT_BLACK);
osx = sx * 25 + 40;
osy = sy * 25 + 40;
M5.Lcd.drawLine(osx, osy, 40, 40, TFT_RED);
M5.Lcd.drawLine(ohx, ohy, 40, 40, TFT_WHITE);
M5.Lcd.drawLine(omx, omy, 40, 40, TFT_WHITE);
M5.Lcd.drawLine(osx, osy, 40, 40, TFT_RED);
M5.Lcd.fillCircle(40, 40, 2, TFT_RED);
}
dd = RTC_DateStruct.WeekDay;
weekDay();
Date();
}

void weekDay() {
M5.Lcd.setCursor(110, 12, 2);
M5.Lcd.setTextColor(WHITE, TFT_GREY);
// M5.Lcd.print(dd);
static const char *wd[7] = {“Sun”,”Mon”,”Tue”,”Wed”,”Thr”,”Fri”,”Sat”};
M5.Lcd.print(wd[dd]);
}

void Date() {
M5.Lcd.setCursor(110, 26, 2);
M5.Lcd.setTextColor(WHITE, TFT_GREY);
M5.Lcd.print(MM);
M5.Lcd.print(‘/’);
M5.Lcd.print(DD);
}

void batteryLevel() {
M5.Lcd.setCursor(110, 3, 1);
c = M5.Axp.GetVapsData() * 1.4 / 1000;
b = M5.Axp.GetVbatData() * 1.1 / 1000;
// M5.Lcd.print(b);
battery = ((b – 3.0) / 1.2) * 100;
if (c >= 4.5) {
M5.Lcd.setTextColor(TFT_YELLOW, TFT_GREY);
M5.Lcd.print(“CHG:”);
}
else {
M5.Lcd.setTextColor(TFT_GREEN, TFT_GREY);
M5.Lcd.print(“BAT:”);
}
if (battery > 100)
battery = 100;
else if (battery < 100 && battery > 9)
M5.Lcd.print(” “);
else if (battery < 9)
M5.Lcd.print(” “);
if (battery < 10)
M5.Axp.DeepSleep();
if (digitalRead(M5_BUTTON_HOME) == LOW) {
while (digitalRead(M5_BUTTON_HOME) == LOW);
M5.Axp.DeepSleep(SLEEP_SEC(1));
}
M5.Lcd.print(battery);
M5.Lcd.print(“%”);
}

void batterySaver() {
M5.MPU6886.getAccelData(&accX, &accY, &accZ);
accX *= 1000;
accY *= 1000;
if (!((accX < (0 – 200) && (accX > -900)) && ((accY > 0 – 300) && (accY < 300))))
{
if (millis() > (tiltTime2 + 10000)) {
M5.Axp.DeepSleep();
// while (accX < (0 – 500) && ((accY > 0) && (accY < 100)));
}
}
else
tiltTime2 = millis();
}
void loop() {
brightnessT();
wristWatch();
batteryLevel();
batterySaver();
}

static uint8_t conv2d(const char* p) {
uint8_t v = 0;
if (‘0’ <= *p && *p <= ‘9’)
v = *p – ‘0’;
return 10 * v + *++p – ‘0’;
}

以上です。

関連記事

  1. スマートフォン開発環境

  2. CodeIgniter アップデート用View

  3. ハードディスクレコーダ

  4. マクセルの危険な製品

  5. 自炊(自分で本をデジタル化)を悪とする出版社?

  6. 物悲しいSAMSUNGの新製品

  7. リチウムイオン電池でバッテリーパックを作る

  8. 製品を作るおじさんの独り言

記事をプリント