ここまで、ATtiny1616をベースに省電力化をしたり、フルカラーLEDを光らせたり、音声を再生させたりしてきました。今回はこれらを全部まとめて、「ボタン電池(LR44)x3個を電源としながら、自由に光る・鳴るをプログラムできて、かつ、電源スイッチを都度都度ON/OFFしなくても長く遊べるもの」のサンプルを作りたいと思います。これをベースに改良していくことで、仮面ライダーに出てくる収集アイテム、ガシャットやプログライズキーといった玩具と全く同じものを、自分の手でプログラムして作ることができるようになります。
これまでもオリジナルガシャットなどは作ってはいましたが、その実態はリチウムイオンポリマー充電池を使った力技で、正直遊びにくいし、安全性にも疑問が残るものでした。それが遂に、実際の玩具と同じものを作れるということで、個人的にはずっと実現したかったことでした。
改めて、使用するのは以下の具材です。
すべて秋月電子通商で手に入ります。しかも全部合わせても870円!これまでメインで使っていたSeeeduino XIAOやDFPlayer単体よりも安い!
おさらいですが、ATtiny1616の各ピンは以下の機能を持ちます(※自分の調べた範囲では)。
これを念頭に、各モジュールを以下のように配線します。
配線のポイントは以下です。
- スリープの解除に使用するボタンは、ATtiny1616の外部割り込みに対応したピンに接続する。今回はSW1をA6に接続。
- RGB LEDの各ピンは、ATtiny1616のPWM制御に対応したピンに接続する。今回はA3, A4, A5に接続。
- NSP2340AはI2C接続モードにして、ATtiny1616のB0(SCL)とB1(SDA)に接続。
- I2C接続のためのプルアップ抵抗をB0, B1からそれぞれVDDのラインに繋げる
RGB LEDに接続する抵抗器の大きさについてはこちら、NSP2340AをI2C接続モードで使用するための設定方法についてはこちらの記事をご参照ください。
実際に配線したものが以下になります。
そしてサンプルプログラムですが、以下のような仕様にしました。『仮面ライダーW』のガイアメモリの簡易版のイメージです。
- 初期状態(INIT)でSW1を押すと、準備状態(READY)に遷移して音声1(ジョーカー!)が鳴り、発光→徐々に消灯する
- 準備状態(READY)でSW2を押すと、セット状態(SET)に遷移して音声2(ドライバーセット&変身待機音)が鳴り、徐々に点灯→徐々に消灯を繰り返す
- セット状態(SET)でSW1を押すと、オープン状態(OPEN)に遷移して音声3(ドライバー開音)が鳴り、発光→徐々に消灯する
- オープン状態(OPEN)でSW1を押すと、変身状態(CHANGE)に遷移して音声1(ジョーカー!)が鳴り、発光→徐々に消灯する
- 変身状態(CHANGE)でSW1を押すと、初期状態(INIT)に遷移して音声4(変身音)が鳴る
- 状態に依らず、SW3を押すたびに音声5(BGM)の再生/停止を繰り返す
- SW1/2/3のいずれの操作も行われていない状態が3分続くと、スリープする(=NSP2340Aをスリープさせてから、ATtiny1616もスリープさせる)
- スリープ中にSW1を押すと、スリープから復帰して準備状態(READY)に遷移して音声1(ジョーカー!)が鳴り、発光→徐々に消灯する
これを実装したのが以下になります。今回は、NSP2340Aのコマンドをライブラリ風の別ファイル(”NSP2340A_I2C.h”と”NSP2340A_I2C.cpp”)にしたので、ファイルが3つに分かれています。
#include <avr/sleep.h>
#include "NSP2340A_I2C.h"
#define LOOP_DELAY_MS 20
#define PIN_SW_1 PIN_PA6
#define PIN_SW_2 PIN_PC0
#define PIN_SW_3 PIN_PC1
#define PIN_LED_R PIN_PA3
#define PIN_LED_G PIN_PA4
#define PIN_LED_B PIN_PA5
// PCのシリアルモニタとの接続には、PIN_PB3をRX、PIN_PB2をTXとして使用
// NSP2340A(音声IC)向けのI2C接続には、PIN_PB0をSCL、PIN_PB1をSDAとして使用
#define ON LOW
#define OFF HIGH
uint8_t sw_1 = OFF;
uint8_t sw_2 = OFF;
uint8_t sw_3 = OFF;
uint8_t prev_sw_1 = OFF;
uint8_t prev_sw_2 = OFF;
uint8_t prev_sw_3 = OFF;
#define STATE_INIT 0
#define STATE_READY 1
#define STATE_SET 2
#define STATE_OPEN 3
#define STATE_CHANGE 4
uint8_t state = STATE_INIT;
uint8_t prev_state = STATE_INIT;
void change_state(uint8_t new_state){
state = new_state;
Serial.print(F("State: "));
switch(state){
case STATE_INIT: Serial.println(F("INIT")); break;
case STATE_READY: Serial.println(F("READY")); break;
case STATE_SET: Serial.println(F("SET")); break;
case STATE_OPEN: Serial.println(F("OPEN")); break;
case STATE_CHANGE: Serial.println(F("CHANGE")); break;
default: ;
}
}
///////////////////////// 発光関係 /////////////////////////
#define ANALOG_WRITE_MAX 255
#define INC_ONLY 0
#define DIM_ONLY 1
#define INC_DIM 2
struct color_rgb {
uint8_t r;
uint8_t g;
uint8_t b;
};
color_rgb COLOR_BLACK = { 0, 0, 0};
color_rgb COLOR_RED = {128, 0, 0};
color_rgb COLOR_GREEN = { 0,128, 0};
color_rgb COLOR_BLUE = { 0, 0,128};
color_rgb COLOR_YELLOW = {128,128, 0};
color_rgb COLOR_CYAN = { 0,128,128};
color_rgb COLOR_MAGENTA = {128, 0,128};
color_rgb COLOR_WHITE = {128,128,128};
unsigned long led_pattern_start_point_ms = 0;
unsigned long prev_on_off_point_ms = 0;
unsigned long inc_dim_start_point_ms = 0;
unsigned long prev_range_ms = 0;
uint8_t prev_steps = 0;
bool is_blink_on = false;
bool is_inc = true;
void init_led_pattern_start_point_ms(unsigned long now_ms){
if(prev_state != state){
led_pattern_start_point_ms = now_ms;
inc_dim_start_point_ms = 0;
led_base_pattern_off();
}
}
void init_inc_dim_led_pattern(unsigned long now_ms, unsigned long range_ms, uint8_t steps){
if(inc_dim_start_point_ms == 0 || range_ms != prev_range_ms || steps != prev_steps){
inc_dim_start_point_ms = now_ms;
prev_range_ms = range_ms;
prev_steps = steps;
}
}
// シンク型接続なので、analogWriteの明るさ指定はすべて反転させる
void led_base_pattern_off(){
analogWrite(PIN_LED_R, ANALOG_WRITE_MAX);
analogWrite(PIN_LED_G, ANALOG_WRITE_MAX);
analogWrite(PIN_LED_B, ANALOG_WRITE_MAX);
}
void led_base_pattern_on(struct color_rgb *color){
analogWrite(PIN_LED_R, ANALOG_WRITE_MAX - color->r);
analogWrite(PIN_LED_G, ANALOG_WRITE_MAX - color->g);
analogWrite(PIN_LED_B, ANALOG_WRITE_MAX - color->b);
}
void led_base_pattern_blink(struct color_rgb *color, unsigned long now_ms, unsigned long inverval_ms){
if(now_ms - prev_on_off_point_ms >= inverval_ms){
if(is_blink_on){
led_base_pattern_off();
}else{
led_base_pattern_on(color);
}
is_blink_on = !is_blink_on;
prev_on_off_point_ms = now_ms;
}
}
void led_base_pattern_inc(struct color_rgb *color, unsigned long now_ms, unsigned long total_ms, uint8_t steps){
init_inc_dim_led_pattern(now_ms, total_ms, steps);
int ms_per_step = total_ms / steps;
int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
if(current_step > steps){
current_step = steps;
}
uint8_t r_step = color->r/steps;
uint8_t g_step = color->g/steps;
uint8_t b_step = color->b/steps;
analogWrite(PIN_LED_R, ANALOG_WRITE_MAX - r_step * current_step);
analogWrite(PIN_LED_G, ANALOG_WRITE_MAX - g_step * current_step);
analogWrite(PIN_LED_B, ANALOG_WRITE_MAX - b_step * current_step);
}
void led_base_pattern_dim(struct color_rgb *color, unsigned long now_ms, unsigned long total_ms, uint8_t steps){
init_inc_dim_led_pattern(now_ms, total_ms, steps);
int ms_per_step = total_ms / steps;
int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
if(current_step > steps){
current_step = steps;
}
uint8_t r_step = color->r/steps;
uint8_t g_step = color->g/steps;
uint8_t b_step = color->b/steps;
analogWrite(PIN_LED_R, ANALOG_WRITE_MAX - r_step * (steps - current_step));
analogWrite(PIN_LED_G, ANALOG_WRITE_MAX - g_step * (steps - current_step));
analogWrite(PIN_LED_B, ANALOG_WRITE_MAX - b_step * (steps - current_step));
}
void led_base_pattern_inc_dim_repeat(struct color_rgb *color, unsigned long now_ms, unsigned long interval_ms, uint8_t steps, uint8_t type){
init_inc_dim_led_pattern(now_ms, interval_ms, steps);
int ms_per_step = interval_ms / steps;
int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
if(current_step > steps){
current_step = steps;
}
uint8_t r_step = color->r/steps;
uint8_t g_step = color->g/steps;
uint8_t b_step = color->b/steps;
if(type == INC_ONLY || (type == INC_DIM && is_inc)){
analogWrite(PIN_LED_R, ANALOG_WRITE_MAX - r_step * current_step);
analogWrite(PIN_LED_G, ANALOG_WRITE_MAX - g_step * current_step);
analogWrite(PIN_LED_B, ANALOG_WRITE_MAX - b_step * current_step);
}else{
analogWrite(PIN_LED_R, ANALOG_WRITE_MAX - r_step * (steps - current_step));
analogWrite(PIN_LED_G, ANALOG_WRITE_MAX - g_step * (steps - current_step));
analogWrite(PIN_LED_B, ANALOG_WRITE_MAX - b_step * (steps - current_step));
}
if(now_ms - inc_dim_start_point_ms >= interval_ms){
is_inc = !is_inc;
inc_dim_start_point_ms = 0;
}
}
void control_led(unsigned long now_ms){
// 状態遷移したときの初期化処理
init_led_pattern_start_point_ms(now_ms);
unsigned long passed_ms = now_ms - led_pattern_start_point_ms;
switch(state){
case STATE_INIT:
break;
case STATE_READY:
if(passed_ms < 1000){
led_base_pattern_on(&COLOR_MAGENTA);
}else if(1000 <= passed_ms && passed_ms < 1500){
led_base_pattern_dim(&COLOR_MAGENTA, now_ms, 500, 10);
}else{
led_base_pattern_off();
}
break;
case STATE_SET:
if(passed_ms < 1000){
led_base_pattern_dim(&COLOR_MAGENTA, now_ms, 1000, 20);
}else{
led_base_pattern_inc_dim_repeat(&COLOR_MAGENTA, now_ms, 500, 10, INC_DIM);
}
break;
case STATE_OPEN:
if(passed_ms < 800){
led_base_pattern_dim(&COLOR_MAGENTA, now_ms, 800, 20);
}else{
led_base_pattern_off();
}
break;
case STATE_CHANGE:
if(passed_ms < 1000){
led_base_pattern_on(&COLOR_MAGENTA);
}else if(1000 <= passed_ms && passed_ms < 1500){
led_base_pattern_dim(&COLOR_MAGENTA, now_ms, 500, 10);
}else{
led_base_pattern_off();
}
break;
default:
;
}
}
///////////////////////// 音声関係 /////////////////////////
#define AUDIO_VOLUME 48
#define AUDIO_SEND_CMD_WAIT_MS 500
#define AUDIO_SEND_CMD_RETRY 5
#define SOUND_NAME 1
#define SOUND_SET 2
#define SOUND_OPEN 3
#define SOUND_CHANGE 4
#define SOUND_BGM 5
bool is_bgm_playing = false;
void control_sound(){
switch(prev_state){
case STATE_INIT:
if(state == STATE_READY){
audio_play_sound(AUDIO_CH1, SOUND_NAME);
}
break;
case STATE_READY:
if(state == STATE_SET){
audio_play_sound(AUDIO_CH1, SOUND_SET);
}
break;
case STATE_SET:
if(state == STATE_OPEN){
audio_play_sound(AUDIO_CH1, SOUND_OPEN);
}
break;
case STATE_OPEN:
if(state == STATE_CHANGE){
audio_play_sound(AUDIO_CH1, SOUND_NAME);
}
break;
case STATE_CHANGE:
if(state == STATE_INIT){
audio_play_sound(AUDIO_CH1, SOUND_CHANGE);
}
break;
default:
;
}
}
///////////////////////// 省電力関係 /////////////////////////
#define GO_TO_SLEEP_MS 180000
unsigned long sleep_timer_start_point_ms = 0;
volatile bool wakeup_flag = false;
// 割り込み関数(attachInterrupt用)
void wakeup(){
wakeup_flag = true;
}
void go_to_sleep(){
Serial.println(F("Go to sleep."));
// SW1をスリープ解除ボタンに設定
attachInterrupt(digitalPinToInterrupt(PIN_SW_1), wakeup, FALLING); // HIGH -> LOW変化をトリガーにする
led_base_pattern_off();
// NSP2340Aを確実にスリープさせるため、一定回数リトライさせる
for(uint8_t i=0;i<AUDIO_SEND_CMD_RETRY;i++){
if(audio_power_down() == AUDIO_RTN_OK){
break;
}else{
delay(AUDIO_SEND_CMD_WAIT_MS);
}
}
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // 最も省電力なモード。CPU, クロック, ほぼ全ペリフェラル停止
sleep_enable(); // スリープ許可。次のsleep_cpu()を有効化するフラグ
sleep_cpu(); // CPU停止。復帰後は、次の行から再開
// 復帰後
sleep_disable();
}
///////////////////////// メインプログラム /////////////////////////
void setup(){
// 省電力化のため、不要な部分を停止。動作周波数はArduino IDEの方のClock指定で1MHzに落とす
ADC0.CTRLA &= ~ADC_ENABLE_bm; // ADC(アナログデジタル変換)を停止
SPI0.CTRLA &= ~SPI_ENABLE_bm; // SPI停止
Serial.begin(9600); // デバッグ用シリアル通信有効化
Wire.begin(); // 音声IC用I2C接続有効化
pinMode(PIN_SW_1, INPUT_PULLUP);
pinMode(PIN_SW_2, INPUT_PULLUP);
pinMode(PIN_SW_3, INPUT_PULLUP);
pinMode(PIN_LED_R, OUTPUT);
pinMode(PIN_LED_G, OUTPUT);
pinMode(PIN_LED_B, OUTPUT);
// 省電力化のため、未使用ピンはすべてプルアップする
pinMode(PIN_PA1, INPUT_PULLUP);
pinMode(PIN_PA2, INPUT_PULLUP);
pinMode(PIN_PA7, INPUT_PULLUP);
pinMode(PIN_PB4, INPUT_PULLUP);
pinMode(PIN_PB5, INPUT_PULLUP);
pinMode(PIN_PC2, INPUT_PULLUP);
pinMode(PIN_PC3, INPUT_PULLUP);
led_base_pattern_off();
// 起動直後は通信に失敗する可能性があるので、一定回数リトライさせる
for(uint8_t i=0;i<AUDIO_SEND_CMD_RETRY;i++){
if(audio_set_volume(AUDIO_VOLUME) == AUDIO_RTN_OK){
break;
}else{
delay(AUDIO_SEND_CMD_WAIT_MS);
}
}
// スリープ用タイマーのスタート
sleep_timer_start_point_ms = millis();
}
void loop(){
sw_1 = digitalRead(PIN_SW_1);
sw_2 = digitalRead(PIN_SW_2);
sw_3 = digitalRead(PIN_SW_3);
unsigned long now_ms = millis();
switch(state){
case STATE_INIT:
if(prev_sw_1 == OFF && sw_1 == ON){
change_state(STATE_READY);
}
break;
case STATE_READY:
if(prev_sw_2 == OFF && sw_2 == ON){
change_state(STATE_SET);
}
break;
case STATE_SET:
if(prev_sw_1 == OFF && sw_1 == ON){
change_state(STATE_OPEN);
}
break;
case STATE_OPEN:
if(prev_sw_1 == OFF && sw_1 == ON){
change_state(STATE_CHANGE);
}
break;
case STATE_CHANGE:
if(prev_sw_1 == OFF && sw_1 == ON){
change_state(STATE_INIT);
}
break;
default:
;
}
// BGM再生/停止はSTATEに関係なく実施可能にする
if(prev_sw_3 == OFF && sw_3 == ON){
if(is_bgm_playing){
audio_stop_sound(AUDIO_CH2);
Serial.println("Stop BGM.");
}else{
audio_play_sound(AUDIO_CH2, SOUND_BGM);
Serial.println("Play BGM.");
}
is_bgm_playing = !is_bgm_playing;
}
// ボタン操作があれば、スリープタイマーをリセットする
if(prev_sw_1 != sw_1 || prev_sw_2 != sw_2 || prev_sw_3 != sw_3){
sleep_timer_start_point_ms = now_ms;
}
if(now_ms - sleep_timer_start_point_ms >= GO_TO_SLEEP_MS){
go_to_sleep(); // ここで処理が止まる
}
// スリープ復帰時に呼ばれる割り込み関数でflagがtrueになる
if(wakeup_flag == true){
// 以下、スリープ復帰後に一度だけ実行
Serial.println(F("Wakeup."));
// STATE_INIT -> STATE_READYへ変化した状態から再開させる
sw_1 = ON;
sw_2 = sw_3 = OFF;
prev_sw_1 = prev_sw_2 = prev_sw_3 = OFF;
prev_state = STATE_INIT;
state = STATE_READY;
sleep_timer_start_point_ms = millis();
// NSP2340Aを目覚めさせるためのコマンド送信 兼 音量セット
for(uint8_t i=0;i<AUDIO_SEND_CMD_RETRY;i++){
if(audio_set_volume(AUDIO_VOLUME) == AUDIO_RTN_OK){
break;
}else{
delay(AUDIO_SEND_CMD_WAIT_MS);
}
}
// SW1のスリープ解除ボタン設定を解除
detachInterrupt(digitalPinToInterrupt(PIN_SW_1));
wakeup_flag = false;
}
control_led(now_ms);
control_sound();
prev_sw_1 = sw_1;
prev_sw_2 = sw_2;
prev_sw_3 = sw_3;
prev_state = state;
delay(LOOP_DELAY_MS);
}#ifndef NSP2340A_I2C_h
#define NSP2340A_I2C_h
#include <Arduino.h>
#include <Wire.h>
#define AUDIO_ADDR 0x20
#define AUDIO_CH1 1
#define AUDIO_CH2 2
#define AUDIO_RTN_OK 0xA5
#define AUDIO_RTN_NG 0xFA
uint8_t send_cmd(const uint8_t *cmd, uint8_t len);
uint8_t calc_checksum(const uint8_t *data, uint8_t len);
uint8_t audio_power_down();
uint8_t audio_set_auto_sleep_enable();
uint8_t audio_set_auto_sleep_disable();
uint8_t audio_set_volume(uint8_t vol);
uint8_t audio_play_sound(uint8_t ch, uint16_t num_sound);
uint8_t audio_stop_sound(uint8_t ch);
uint8_t audio_pause_sound(uint8_t ch);
uint8_t audio_resume_sound(uint8_t ch);
#endif#include "NSP2340A_I2C.h"
uint8_t send_cmd(const uint8_t *cmd, uint8_t len) {
Wire.beginTransmission(AUDIO_ADDR);
for (uint8_t i = 0; i < len; i++) {
Wire.write(cmd[i]);
}
Wire.endTransmission();
delay(5);
Wire.requestFrom(AUDIO_ADDR, 1);
if (Wire.available()){
return Wire.read();
}
return 0x00;
}
uint8_t calc_checksum(const uint8_t *data, uint8_t len){
uint16_t sum = 0;
for(uint8_t i=0; i<len; i++){
sum += data[i];
}
return (uint8_t)(~sum);
}
uint8_t audio_power_down(){
uint8_t cmd[2];
cmd[0] = 0x82;
cmd[1] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
return send_cmd(cmd, sizeof(cmd));
}
uint8_t audio_set_auto_sleep_enable(){
uint8_t cmd[2];
cmd[0] = 0x83;
cmd[1] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
return send_cmd(cmd, sizeof(cmd));
}
uint8_t audio_set_auto_sleep_disable(){
uint8_t cmd[2];
cmd[0] = 0x84;
cmd[1] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
return send_cmd(cmd, sizeof(cmd));
}
uint8_t audio_set_volume(uint8_t vol){
if(vol > 128){
vol = 128;
}
uint8_t cmd[3];
cmd[0] = 0xF0;
cmd[1] = vol;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
return send_cmd(cmd, sizeof(cmd));
}
uint8_t audio_play_sound(uint8_t ch, uint16_t num_sound){
uint8_t cmd[5];
cmd[0] = 0xA0;
cmd[1] = ch;
cmd[2] = highByte(num_sound);
cmd[3] = lowByte(num_sound);
cmd[4] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[3]までの4byteのチェックサムを計算
return send_cmd(cmd, sizeof(cmd));
}
uint8_t audio_stop_sound(uint8_t ch){
uint8_t cmd[3];
cmd[0] = 0xA1;
cmd[1] = ch;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
return send_cmd(cmd, sizeof(cmd));
}
uint8_t audio_pause_sound(uint8_t ch){
uint8_t cmd[3];
cmd[0] = 0xA2;
cmd[1] = ch;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
return send_cmd(cmd, sizeof(cmd));
}
uint8_t audio_resume_sound(uint8_t ch){
uint8_t cmd[3];
cmd[0] = 0xA3;
cmd[1] = ch;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
return send_cmd(cmd, sizeof(cmd));
}なお、プログラムを書き込む際に、Arduino IDEの方で動作周波数を1MHzに落としています。設定方法については、こちらの記事の最後の追記をご確認ください。
では、とりあえず電流を測りながら、動作を確認したいと思います。プログラムの書き込み方については、こちらをご参照ください。
なお、最後のボタン操作からスリープに入るまでの時間は”GO_TO_SLEEP_MS”で定義していて、上記のサンプルでは180,000ms(=3分)にしていますが、以下の動作確認では3分待つのは長いので、20,000ms(=20秒)でスリープに入るようにしています。
これより、以下が確認できると思います。
- 紫(=赤+青)でLEDを光らせると瞬間的に20mAぐらいまで届くが、動作自体は問題ない
- 音声再生だけなら13mAぐらい
- 待機中の消費電流は6mAぐらい
- 20秒経過してスリープに入ると、消費電流は1mAより小さくなる
- SW1を押すと、スリープから復帰する
- NSP2340Aで2ch同時に音声を再生しても、めちゃめちゃ消費電流が増えるわけではない
なお、スリープ中の消費電流は、上記の動画では計測レンジを合わせ損ねたせいで「1mAより小さい」ということしかわかりませんが、その後にレンジを変えてきちんと計測したところ、
0.1μAまで落ちていることが確認できました。これなら十分長持ちしそうです。
では最後に、当初の目的であった、「ボタン電池(LR44) x 3個で動く」ということを確認しておきたいと思います。
BGMなし(=1ch再生)版。
BGMあり(=2ch再生)版。
ということで、BGM付きにしても問題なく動作することが確認できました。ただまあ、電池の消耗が激しくなるのは間違いないので、「やろうと思えばできる」ぐらいに思っておくほうが良いと思います。
で、最後の検証として、ボタン電池を新品のものに入れ替えて、「電池をずっと繋いだまま、一日に数回遊ぶか遊ばないかにして、あとは放置」という、実際の玩具にありそうなシチュエーションでどれぐらい電池が持つかを確認しました。
2026/5/7の深夜に検証をスタートさせて、直近1ヶ月ぐらいはほぼ放置状態でしたが、この記事の公開前(2026/7/11)にボタンを押してみたら問題なく動作しました。きちんとスリープできていたということで、大成功と言ってよいでしょう。
ということで、長年やりたかったことをようやく実現することができました。良いサンプルが作れて、過去のライダー玩具の小物を改めてつくってみたい気分になりましたので、ひょっとしたら簡易的なやつを今後何個かつくる。。。かもしれないし、つくらないかもしれません。
コメント