ゆるく楽しむプログラミング

Seeeduino XIAOで「学校のチャイム」をタイマー制御してみる:スケッチ

スケッチ書くのメンドクサイ (;´Д`)

Seeeduino XIAOをターゲットにしています。

使用したライブラリ
OLED SSD1306表示用 ACROBOTIC_SSD1306 ArduinoIDEのライブラリ管理からインストール 
FlasROM アクセス  FlashStorage  GitHub 
【参考】Seeeduino XIAO の使い方

UIは4個のボタンを▲図のようにわりあてています。
モードは 時刻表示 / 時刻設定 / チャイム設定 / Config の4種類
チャイムの時刻は各々で変更確定を押さないと変わりません。
Config から[Save Chime]を実行しないとFlashROMに保存されません。
※保存しない場合、電源を切ると元の設定に戻ってしまいます。

チャイムは外部接続した【学校のチャイム】とスピーカー(自前で演奏)が選べます。
ディフォルトは外部接続した【学校のチャイム】です。
チャイムの選択もConfig から[Save Chime]を実行しないとFlashROMに保存されません。

ぶっちゃけ、UI作るの面倒になってヤッツケで作ってますのでいい加減です。
※必要になったら後で直せば良いや…と思うとどうにも (´・ω・`)

文字フォントを一部変更

\Arduino\libraries\ACROBOTIC_SSD1306\fonts\font8x8.h の一部を変更しています。
//  0x00,0x08,0x08,0x76,0x42,0x42,0x00,0x00,  // {
//  0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,  // |
//  0x00,0x42,0x42,0x76,0x08,0x08,0x00,0x00,  // }
//  0x00,0x00,0x04,0x02,0x04,0x02,0x00,0x00,  // ~
  0x03,0x0F,0x3F,0xFF,0x3F,0x0F,0x03,0x00,  // { ▼
  0xFF,0xFF,0x7E,0x7E,0x3C,0x18,0x18,0x00,  // | >
  0xC0,0xF0,0xFC,0xFF,0xFC,0xF0,0xC0,0x00,  // } ▲
  0x18,0x18,0x3C,0x7E,0x7E,0xFF,0xFF,0x00,  // ~ <

Seeeduino XIAOにはEEPROMがなかった件 (;´Д`)

設定(チャイムを鳴らす時刻)を不揮発メモリーに保存しないと、電源を切る毎に初期化されてしまいます。
この設定をEEPROMに保存する予定だったのですが・・・Seeeduino XIAOにはEEPROMがなかったのでした。

代替えとしてFlashROMに設定データを書き込みます。
FlashROMの寿命が縮みますが、設定変更の度にスケッチを書き直す事を考えれば書込み回数は変わらないので問題ないでしょう。
書込み回数を最低限にするためにUIを変更しました。
チャイムの時刻は[CHIME EDIT] で変更できますがFlashROMへの保存はCONFIGから明示的に[Save Chime]を実行しないとお行われません。

-- CHIME.ino --  

#include <Wire.h>
#include <ACROBOTIC_SSD1306.h>
#include "FlashStorage.h"

#define SPK_PIN 2
#define CHIME_PIN 1
#define BTN0_PIN 7
#define BTN1_PIN 8

#define MODE_DISP 0
#define MODE_CLOCK 1
#define MODE_ALERM 2
#define MODE_INIT 3

// RTCのレジスタテーブル(8byte)
int RegTbl[8];

// RTC デバイスアドレス(スレーブ)
byte RTC_ADDRESS = 0x68;  // 2進数 1101000

// 表示用Buffer
char dispStr[20] ;

// 状態変位用変数
uint8_t sec ;     // 秒
uint8_t secPre ;  // 秒 - 前回取得時
uint8_t mnt ;     // 分
uint8_t mntPre ;  // 分 - 前回取得時
uint8_t how ;     // 時

int brlinkMode ;  // 画面反転モード 0~F
uint8_t nextChimeH ; // チャイム(時): 88-OFF
uint8_t nextChimeM ; // チャイム(分): 88-OFF
unsigned char buttonStat ;    // ボタンの状態: BTN1=0x01 BTN2=0x02 BTN3=0x10 BTN4=0x20 
unsigned char buttonStatPre ; // ボタンの状態 - 前回取得時
unsigned char btn ; // ボタンが押された 1-4
unsigned char mode1 ; // 動作モード 0:時計表示 1:時刻設定 2:チャイム設定 3:初期化
unsigned char mode2 ; // 動作状態
                      //   MODE1=1: 0:時間1 1:時間2 3:分1 4:分2
                      //   MODE1=2: 0:チャイムNo 1:ON/OFF 2:時間1 3:時間2 4:分1 5:分2
unsigned char mode4 ; // 作業用データ:チャイム番号
short int mode3 ;     // 作業用データ:時刻設定数値                  

// 2進化10進数(BCD)を10進数に変換
byte BCDtoDec(byte value) {
  return ((value >> 4) * 10) + (value & 0x0F) ;
}

// チャイム初期値
#define MAXCHIMENUM 10
const short int chimeFlash[MAXCHIMENUM] PROGMEM = {
  845 , 1200 , 1300 , 1730 , 8888 , 8888 , 8888 , 8888 , 8888 , 8888 
} ;

const int melody[][2] PROGMEM = {
    {698,800},{880,800},{784,800},{523,2300},{0,100},
    {698,800},{784,800},{880,800},{698,2300},{0,100},
    {880,800},{698,800},{784,800},{523,2300},{0,100},
    {523,800},{784,800},{880,800},{698,2300},{0,100},
    {0,0} } ;

unsigned short int chime[MAXCHIMENUM] ;
unsigned char chimeType = 0 ; // 0:外付けチャイム 1:スピーカー
unsigned char playChimeFlag = false ;

// Flash DATA
typedef struct {
  unsigned char chimeType;
  unsigned short int chime[MAXCHIMENUM] ;
} CHIMEDATA ;

FlashStorage(flash_store, CHIMEDATA) ;
CHIMEDATA chimeData ;

// 時刻設定
void setTime(int h,int m) {
  int setH = (h/10) * 16 + (h % 10) ;
  int setM = (m/10) * 16 + (m % 10) ;
  
  // I2Cスレーブに対して送信処理を開始する
  Wire.beginTransmission(RTC_ADDRESS);
 
  // レジスタのアドレスを指定する(0-63の内、先頭から)
  Wire.write(0x00);
  
  // ---------------------------------
  //  タイマーキーパーレジスタ
  // ---------------------------------
  // 2進化10進数(BCD)で指定します。(03以外)
  
  // [00]Seconds(00秒)
  Wire.write(0x0);
  // [01]Minutes(m分)
  Wire.write(setM);
  // [02]Hours(h時)
  Wire.write(setH);

  // [03]Day(月)
  // 1:日 2:月 3:火 4:水 5:木 6:金 7:土
  Wire.write(0x01);
  // [04]Date(31日)
  Wire.write(0x31);
  // [05]Month(12月)
  Wire.write(0x12);
  // [06]Years(2021年)
  Wire.write(0x21);
 
  // I2Cスレーブへの送信完了
  Wire.endTransmission();
}

// 時刻取得
// 4桁の10進数 hhmm
int getTime( ) {
  // レジスタのアドレスを先頭にする
  Wire.beginTransmission(RTC_ADDRESS);
  Wire.write(0x00);
  Wire.endTransmission();
 
  // I2Cスレーブに8byteのレジスタデータを要求する
  Wire.requestFrom(RTC_ADDRESS, 8);
 
  // 8byteのデータを取得する
  int i;
  for (i = 0; i < 8; i++) {
    while (Wire.available() == 0 ) {}
    RegTbl[i] = Wire.read();
  }
 
  // 現在日時 : グローバル変数にセット
  how = BCDtoDec(RegTbl[2] & 0x3F) ;
  mnt = BCDtoDec(RegTbl[1] & 0x7F) ;
  sec = BCDtoDec(RegTbl[0] & 0x7F) ;

  if (mntPre != mnt) {  // 分 - 前回取得時
    getNextChime( ) ;
    mntPre = mnt ;
  }

  return ( how * 10000 + mnt * 100 ) ;   // リターン値として返す
}

// Nextチャイム時刻取得
int getNextChime( ) {
  int n = how * 100 + mnt ; // 現在時刻
  int nc = 0 ;
  for (int i=0;i= 2) ? mode2 +1 : mode2 ;
    sprintf(dispStr,"     ") ;
    dispStr[p] = '}' ;
    oled.setTextXY(4,2);
    oled.putString(dispStr);
    dispStr[p] = '{' ;
    oled.setTextXY(6,2);
    oled.putString(dispStr);
  }
  if (btn == 3) {
    char data[6] ;
    data[0] = (mode3 / 100) / 10 ;
    data[1] = (mode3 / 100 )% 10 ;
    data[2] = ':' ;
    data[3] = (mode3 % 100) / 10 ;
    data[4] = mode3 % 10 ;
    data[5] = 0 ;
    switch(mode2) {
      case 0:
        data[0] = (data[0] + 1) % 3 ; 
        if (data[0] == 2) {
          if (data[1] > 4) {
            data[1] = 0 ;
          }
        }
        break ;
  
      case 1:
        if (data[0] != 2) {
          data[1] = (data[1] + 1) % 10 ; 
        } else {
          data[1] = (data[1] + 1) % 4 ; 
        }
        break ;
  
      case 2:
        data[3] = (data[3] + 1) % 6 ; 
        break ;
 
      case 3:
        data[4] = (data[4] + 1) % 10 ; 
        break ;
    }
    mode3 = (data[0] * 10 + data[1]) * 100 + (data[3] * 10 + data[4]) ;
    data[0] += '0' ;
    data[1] += '0' ;
    data[3] += '0' ;
    data[4] += '0' ;
    oled.setTextXY(5,2);
    oled.putString(data);
  }
  if (btn == 4) {
    setTime(mode3/100, mode3%100) ;
    mode1 = 0 ;
    initDisp() ;
    return ;
  }
}

// ----- MODE-3 - チャイム設定 -----
void setChime() {
  if (btn == 4) {
    chime[mode4] = mode3 ;
    mode2 = 0 ;
    dispChimeCursor() ;  
    getNextChime( ) ;
  }
  if (btn == 3) {
    if (mode2 >= 2) {
      char data[6] ;
      data[0] = (mode3 / 100) / 10 ;
      data[1] = (mode3 / 100 )% 10 ;
      data[2] = ':' ;
      data[3] = (mode3 % 100) / 10 ;
      data[4] = mode3 % 10 ;
      data[5] = 0 ;
      switch(mode2) {
        case 2:
          data[0] = (data[0] + 1) % 3 ; 
          if (data[0] == 2) {
            if (data[1] > 4) {
              data[1] = 0 ;
            }
          }
          break ;
    
        case 3:
          if (data[0] != 2) {
            data[1] = (data[1] + 1) % 10 ; 
          } else {
            data[1] = (data[1] + 1) % 4 ; 
          }
          break ;
    
        case 4:
          data[3] = (data[3] + 1) % 6 ; 
          break ;
   
        case 5:
          data[4] = (data[4] + 1) % 10 ; 
          break ;
      }
      mode3 = (data[0] * 10 + data[1]) * 100 + (data[3] * 10 + data[4]) ;
      data[0] += '0' ;
      data[1] += '0' ;
      data[3] += '0' ;
      data[4] += '0' ;
      oled.setTextXY(5,9);
      oled.putString(data);
    } else {
      switch(mode2) {
        case 0:
          mode4 = (mode4 + 1) % 10 ;
          mode3 = chime[mode4] ;
          dispChime( ) ;
          break ;
  
        case 1:
          mode3 = (mode3 == 8888) ? 1200 : 8888 ;
          dispChime( ) ;
          break ;
      }
    }
  }
  if (btn == 2) {
    switch(mode2) {
      case 0: mode2 = 1 ; break ;
      case 1: mode2 = (mode3 == 8888) ? 0 : 2 ; break ;
      case 2: mode2 = 3 ; break ;
      case 3: mode2 = 4 ; break ;
      case 4: mode2 = 5 ; break ;
      case 5: mode2 = 0 ; break ;
    }
    dispChimeCursor() ;      
  }
}

// チャイムEDITの時刻表示
void dispChime( ) {
  if (mode3 == 8888) {
    sprintf(dispStr,"%02d OFF %02d:%02d", mode4 + 1 , mode3 / 100 , mode3 % 100) ;
   } else {
     sprintf(dispStr,"%02d ON  %02d:%02d", mode4 + 1 , mode3 / 100 , mode3 % 100) ;
   }
    oled.setTextXY(5,2);
    oled.putString(dispStr);
}

// チャイムEditのカーソル表示
void chimeCursor(int pos,int num) {
  sprintf(dispStr,"               ") ;
  memset (&dispStr[pos], '}' , num) ;
  oled.setTextXY(4,2);
  oled.putString(dispStr);
  memset (&dispStr[pos], '{' , num) ;
  oled.setTextXY(6,2);
  oled.putString(dispStr);
}

void dispChimeCursor() {
  switch(mode2) {
    case 0:
      chimeCursor(0,2) ;
      break ;
  
    case 1:
      chimeCursor(3,3) ;
      break ;
  
    case 2:
    case 3:
      chimeCursor(7+mode2-2,1) ;
      break ;
  
    case 4:
    case 5:
      chimeCursor(10+mode2-4,1) ;
      break ;
  }
}

// ----- MODE-4 - CONFIG -----
void setConfig() {
  if (btn == 4) {
    switch(mode2) {
      case 0: // Reset Clock
        setTime(12,0) ;
        break ;
      case 1: // Reset Chime
        if (mode3 == 0) {
          initChime( ) ;
        } else {
          saveChime( ) ;
          mode1 = 0 ;
          initDisp( ) ;
        }
        break ;
      case 2: // ChimeType
        chimeType = mode4 ;
        // チャイム実行
        if (chimeType == 0) {
          externalChime( ) ;
        } else {
          internalChime( ) ;
        }
        break ;
    }
  }
  if (btn == 2) {
    mode2 = (mode2 + 1) % 3 ;
    oled.setTextXY(5,1);
    switch(mode2) {
      case 0: // Reset Clock
        sprintf(dispStr," Reset Clock ");
        break ;

      case 1: // Reset Chime
        dispChimeMode( ) ;
        break ;

      case 2: // External Test
        dispChimeType(mode4) ; 
        break ;
    }
    oled.putString(dispStr);
  }
  if (btn == 3) {
    if (mode2 == 1) {
      mode3 = (mode3 == 0) ? 1 : 0 ;
      dispChimeMode( ) ;
      oled.setTextXY(5,1);
      oled.putString(dispStr);
    }
    if (mode2 == 2) {
      mode4 = (mode4 == 0) ? 1 : 0 ;
      dispChimeType(mode4) ; 
      oled.setTextXY(5,1);
      oled.putString(dispStr);
    }
  }
}
// --- チャイムCongigMode表示 ---
void dispChimeMode( ) {
  if (mode3 == 0) {
    sprintf(dispStr," Reset Chime ");
  } else {
    sprintf(dispStr," Save Chime  ");
  }
}

// --- チャイムタイプ表示 ---
void dispChimeType(unsigned char ct) {
  if (ct == 0) {
    sprintf(dispStr," Ext Chime   ");
  } else {
    sprintf(dispStr," Speaker     ");
  }
}

// ボタンの状態をチェック
void checkButton( ) {
  int button0Stat ; // ボタン1・2の状態
  int button1Stat ; // ボタン3・4の状態

  buttonStat = 0 ;
  button0Stat = analogRead(BTN0_PIN) / 100 ;
  button1Stat = analogRead(BTN1_PIN) / 100 ;
  if (button0Stat == 6) buttonStat |= 0x01 ;
  if (button0Stat == 8) buttonStat |= 0x02 ;
  if (button0Stat == 5) buttonStat |= 0x03 ;
  if (button1Stat == 6) buttonStat |= 0x10 ;
  if (button1Stat == 8) buttonStat |= 0x20 ;
  if (button1Stat == 5) buttonStat |= 0x30 ;

  btn = 0 ;
  if ((buttonStat & 0x20) != 0 && (buttonStatPre & 0x20) == 0) {
    btn = 4 ; 
  }
  if ((buttonStat & 0x10) != 0 && (buttonStatPre & 0x10) == 0) {
    btn = 3 ; 
  }
  if ((buttonStat & 0x02) != 0 && (buttonStatPre & 0x02) == 0) {
    btn = 2 ; 
  }
  if ((buttonStat & 0x01) != 0 && (buttonStatPre & 0x01) == 0) {
    btn = 1 ; 
  }
  buttonStatPre = buttonStat ;
}

// --- Init Chime ---
void initChime( ) {
  // チャイム時刻テーブル設定
  memcpy(chime,chimeFlash,sizeof(short int) * MAXCHIMENUM) ;
  for (int i1=0;i1 chime[i2]) {
        int w = chime[i1] ;
        chime[i1] = chime[i2] ;
        chime[i2] = w ;
      }
    }
  }
}

// --- Save Chime ---
void saveChime( ) {
  chimeData.chimeType = chimeType ;
  for (int i1=0;i1 時刻合わせ
      case 1: mode1 = 2 ; break ; // 時刻合わせ -> チャイム設定
      case 2: mode1 = 3 ; break ; // チャイム設定 -> 設定
      case 3:
      default:
        mode1 = 0 ; break ; // チャイム設定 -> 時刻表示
    }
    initDisp() ;
  }

  // Loop Process
  switch(mode1) {
    case 0:  dispTime() ; break ;
    case 1:  setClocl() ; break ;
    case 2:  setChime() ; break ;
    case 3:  setConfig() ; break ;
  }

  // ----- Loop Term -----
  delay(100);
}