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

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 の一部を変更しています。
  1. // 0x00,0x08,0x08,0x76,0x42,0x42,0x00,0x00, // {
  2. // 0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00, // |
  3. // 0x00,0x42,0x42,0x76,0x08,0x08,0x00,0x00, // }
  4. // 0x00,0x00,0x04,0x02,0x04,0x02,0x00,0x00, // ~
  5. 0x03,0x0F,0x3F,0xFF,0x3F,0x0F,0x03,0x00, // { ▼
  6. 0xFF,0xFF,0x7E,0x7E,0x3C,0x18,0x18,0x00, // | >
  7. 0xC0,0xF0,0xFC,0xFF,0xFC,0xF0,0xC0,0x00, // } ▲
  8. 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 --  
  1.  
  2. #include Wire.h>
  3. #include ACROBOTIC_SSD1306.h>
  4. #include "FlashStorage.h"
  5.  
  6. #define SPK_PIN 2
  7. #define CHIME_PIN 1
  8. #define BTN0_PIN 7
  9. #define BTN1_PIN 8
  10.  
  11. #define MODE_DISP 0
  12. #define MODE_CLOCK 1
  13. #define MODE_ALERM 2
  14. #define MODE_INIT 3
  15.  
  16. // RTCのレジスタテーブル(8byte)
  17. int RegTbl[8];
  18.  
  19. // RTC デバイスアドレス(スレーブ)
  20. byte RTC_ADDRESS = 0x68; // 2進数 1101000
  21.  
  22. // 表示用Buffer
  23. char dispStr[20] ;
  24.  
  25. // 状態変位用変数
  26. uint8_t sec ; // 秒
  27. uint8_t secPre ; // 秒 - 前回取得時
  28. uint8_t mnt ; // 分
  29. uint8_t mntPre ; // 分 - 前回取得時
  30. uint8_t how ; // 時
  31.  
  32. int brlinkMode ; // 画面反転モード 0~F
  33. uint8_t nextChimeH ; // チャイム(時): 88-OFF
  34. uint8_t nextChimeM ; // チャイム(分): 88-OFF
  35. unsigned char buttonStat ; // ボタンの状態: BTN1=0x01 BTN2=0x02 BTN3=0x10 BTN4=0x20
  36. unsigned char buttonStatPre ; // ボタンの状態 - 前回取得時
  37. unsigned char btn ; // ボタンが押された 1-4
  38. unsigned char mode1 ; // 動作モード 0:時計表示 1:時刻設定 2:チャイム設定 3:初期化
  39. unsigned char mode2 ; // 動作状態
  40. // MODE1=1: 0:時間1 1:時間2 3:分1 4:分2
  41. // MODE1=2: 0:チャイムNo 1:ON/OFF 2:時間1 3:時間2 4:分1 5:分2
  42. unsigned char mode4 ; // 作業用データ:チャイム番号
  43. short int mode3 ; // 作業用データ:時刻設定数値
  44.  
  45. // 2進化10進数(BCD)を10進数に変換
  46. byte BCDtoDec(byte value) {
  47. return ((value >> 4) * 10) + (value & 0x0F) ;
  48. }
  49.  
  50. // チャイム初期値
  51. #define MAXCHIMENUM 10
  52. const short int chimeFlash[MAXCHIMENUM] PROGMEM = {
  53. 845 , 1200 , 1300 , 1730 , 8888 , 8888 , 8888 , 8888 , 8888 , 8888
  54. } ;
  55.  
  56. const int melody[][2] PROGMEM = {
  57. {698,800},{880,800},{784,800},{523,2300},{0,100},
  58. {698,800},{784,800},{880,800},{698,2300},{0,100},
  59. {880,800},{698,800},{784,800},{523,2300},{0,100},
  60. {523,800},{784,800},{880,800},{698,2300},{0,100},
  61. {0,0} } ;
  62.  
  63. unsigned short int chime[MAXCHIMENUM] ;
  64. unsigned char chimeType = 0 ; // 0:外付けチャイム 1:スピーカー
  65. unsigned char playChimeFlag = false ;
  66.  
  67. // Flash DATA
  68. typedef struct {
  69. unsigned char chimeType;
  70. unsigned short int chime[MAXCHIMENUM] ;
  71. } CHIMEDATA ;
  72.  
  73. FlashStorage(flash_store, CHIMEDATA) ;
  74. CHIMEDATA chimeData ;
  75.  
  76. // 時刻設定
  77. void setTime(int h,int m) {
  78. int setH = (h/10) * 16 + (h % 10) ;
  79. int setM = (m/10) * 16 + (m % 10) ;
  80. // I2Cスレーブに対して送信処理を開始する
  81. Wire.beginTransmission(RTC_ADDRESS);
  82. // レジスタのアドレスを指定する(0-63の内、先頭から)
  83. Wire.write(0x00);
  84. // ---------------------------------
  85. // タイマーキーパーレジスタ
  86. // ---------------------------------
  87. // 2進化10進数(BCD)で指定します。(03以外)
  88. // [00]Seconds(00秒)
  89. Wire.write(0x0);
  90. // [01]Minutes(m分)
  91. Wire.write(setM);
  92. // [02]Hours(h時)
  93. Wire.write(setH);
  94.  
  95. // [03]Day(月)
  96. // 1:日 2:月 3:火 4:水 5:木 6:金 7:土
  97. Wire.write(0x01);
  98. // [04]Date(31日)
  99. Wire.write(0x31);
  100. // [05]Month(12月)
  101. Wire.write(0x12);
  102. // [06]Years(2021年)
  103. Wire.write(0x21);
  104. // I2Cスレーブへの送信完了
  105. Wire.endTransmission();
  106. }
  107.  
  108. // 時刻取得
  109. // 4桁の10進数 hhmm
  110. int getTime( ) {
  111. // レジスタのアドレスを先頭にする
  112. Wire.beginTransmission(RTC_ADDRESS);
  113. Wire.write(0x00);
  114. Wire.endTransmission();
  115. // I2Cスレーブに8byteのレジスタデータを要求する
  116. Wire.requestFrom(RTC_ADDRESS, 8);
  117. // 8byteのデータを取得する
  118. int i;
  119. for (i = 0; i < 8; i++) {
  120. while (Wire.available() == 0 ) {}
  121. RegTbl[i] = Wire.read();
  122. }
  123. // 現在日時 : グローバル変数にセット
  124. how = BCDtoDec(RegTbl[2] & 0x3F) ;
  125. mnt = BCDtoDec(RegTbl[1] & 0x7F) ;
  126. sec = BCDtoDec(RegTbl[0] & 0x7F) ;
  127.  
  128. if (mntPre != mnt) { // 分 - 前回取得時
  129. getNextChime( ) ;
  130. mntPre = mnt ;
  131. }
  132.  
  133. return ( how * 10000 + mnt * 100 ) ; // リターン値として返す
  134. }
  135.  
  136. // Nextチャイム時刻取得
  137. int getNextChime( ) {
  138. int n = how * 100 + mnt ; // 現在時刻
  139. int nc = 0 ;
  140. for (int i=0;i= 2) ? mode2 +1 : mode2 ;
  141. sprintf(dispStr," ") ;
  142. dispStr[p] = '}' ;
  143. oled.setTextXY(4,2);
  144. oled.putString(dispStr);
  145. dispStr[p] = '{' ;
  146. oled.setTextXY(6,2);
  147. oled.putString(dispStr);
  148. }
  149. if (btn == 3) {
  150. char data[6] ;
  151. data[0] = (mode3 / 100) / 10 ;
  152. data[1] = (mode3 / 100 )% 10 ;
  153. data[2] = ':' ;
  154. data[3] = (mode3 % 100) / 10 ;
  155. data[4] = mode3 % 10 ;
  156. data[5] = 0 ;
  157. switch(mode2) {
  158. case 0:
  159. data[0] = (data[0] + 1) % 3 ;
  160. if (data[0] == 2) {
  161. if (data[1] > 4) {
  162. data[1] = 0 ;
  163. }
  164. }
  165. break ;
  166. case 1:
  167. if (data[0] != 2) {
  168. data[1] = (data[1] + 1) % 10 ;
  169. } else {
  170. data[1] = (data[1] + 1) % 4 ;
  171. }
  172. break ;
  173. case 2:
  174. data[3] = (data[3] + 1) % 6 ;
  175. break ;
  176. case 3:
  177. data[4] = (data[4] + 1) % 10 ;
  178. break ;
  179. }
  180. mode3 = (data[0] * 10 + data[1]) * 100 + (data[3] * 10 + data[4]) ;
  181. data[0] += '0' ;
  182. data[1] += '0' ;
  183. data[3] += '0' ;
  184. data[4] += '0' ;
  185. oled.setTextXY(5,2);
  186. oled.putString(data);
  187. }
  188. if (btn == 4) {
  189. setTime(mode3/100, mode3%100) ;
  190. mode1 = 0 ;
  191. initDisp() ;
  192. return ;
  193. }
  194. }
  195. // ----- MODE-3 - チャイム設定 -----
  196. void setChime() {
  197. if (btn == 4) {
  198. chime[mode4] = mode3 ;
  199. mode2 = 0 ;
  200. dispChimeCursor() ;
  201. getNextChime( ) ;
  202. }
  203. if (btn == 3) {
  204. if (mode2 >= 2) {
  205. char data[6] ;
  206. data[0] = (mode3 / 100) / 10 ;
  207. data[1] = (mode3 / 100 )% 10 ;
  208. data[2] = ':' ;
  209. data[3] = (mode3 % 100) / 10 ;
  210. data[4] = mode3 % 10 ;
  211. data[5] = 0 ;
  212. switch(mode2) {
  213. case 2:
  214. data[0] = (data[0] + 1) % 3 ;
  215. if (data[0] == 2) {
  216. if (data[1] > 4) {
  217. data[1] = 0 ;
  218. }
  219. }
  220. break ;
  221. case 3:
  222. if (data[0] != 2) {
  223. data[1] = (data[1] + 1) % 10 ;
  224. } else {
  225. data[1] = (data[1] + 1) % 4 ;
  226. }
  227. break ;
  228. case 4:
  229. data[3] = (data[3] + 1) % 6 ;
  230. break ;
  231. case 5:
  232. data[4] = (data[4] + 1) % 10 ;
  233. break ;
  234. }
  235. mode3 = (data[0] * 10 + data[1]) * 100 + (data[3] * 10 + data[4]) ;
  236. data[0] += '0' ;
  237. data[1] += '0' ;
  238. data[3] += '0' ;
  239. data[4] += '0' ;
  240. oled.setTextXY(5,9);
  241. oled.putString(data);
  242. } else {
  243. switch(mode2) {
  244. case 0:
  245. mode4 = (mode4 + 1) % 10 ;
  246. mode3 = chime[mode4] ;
  247. dispChime( ) ;
  248. break ;
  249. case 1:
  250. mode3 = (mode3 == 8888) ? 1200 : 8888 ;
  251. dispChime( ) ;
  252. break ;
  253. }
  254. }
  255. }
  256. if (btn == 2) {
  257. switch(mode2) {
  258. case 0: mode2 = 1 ; break ;
  259. case 1: mode2 = (mode3 == 8888) ? 0 : 2 ; break ;
  260. case 2: mode2 = 3 ; break ;
  261. case 3: mode2 = 4 ; break ;
  262. case 4: mode2 = 5 ; break ;
  263. case 5: mode2 = 0 ; break ;
  264. }
  265. dispChimeCursor() ;
  266. }
  267. }
  268. // チャイムEDITの時刻表示
  269. void dispChime( ) {
  270. if (mode3 == 8888) {
  271. sprintf(dispStr,"%02d OFF %02d:%02d", mode4 + 1 , mode3 / 100 , mode3 % 100) ;
  272. } else {
  273. sprintf(dispStr,"%02d ON %02d:%02d", mode4 + 1 , mode3 / 100 , mode3 % 100) ;
  274. }
  275. oled.setTextXY(5,2);
  276. oled.putString(dispStr);
  277. }
  278. // チャイムEditのカーソル表示
  279. void chimeCursor(int pos,int num) {
  280. sprintf(dispStr," ") ;
  281. memset (&dispStr[pos], '}' , num) ;
  282. oled.setTextXY(4,2);
  283. oled.putString(dispStr);
  284. memset (&dispStr[pos], '{' , num) ;
  285. oled.setTextXY(6,2);
  286. oled.putString(dispStr);
  287. }
  288. void dispChimeCursor() {
  289. switch(mode2) {
  290. case 0:
  291. chimeCursor(0,2) ;
  292. break ;
  293. case 1:
  294. chimeCursor(3,3) ;
  295. break ;
  296. case 2:
  297. case 3:
  298. chimeCursor(7+mode2-2,1) ;
  299. break ;
  300. case 4:
  301. case 5:
  302. chimeCursor(10+mode2-4,1) ;
  303. break ;
  304. }
  305. }
  306. // ----- MODE-4 - CONFIG -----
  307. void setConfig() {
  308. if (btn == 4) {
  309. switch(mode2) {
  310. case 0: // Reset Clock
  311. setTime(12,0) ;
  312. break ;
  313. case 1: // Reset Chime
  314. if (mode3 == 0) {
  315. initChime( ) ;
  316. } else {
  317. saveChime( ) ;
  318. mode1 = 0 ;
  319. initDisp( ) ;
  320. }
  321. break ;
  322. case 2: // ChimeType
  323. chimeType = mode4 ;
  324. // チャイム実行
  325. if (chimeType == 0) {
  326. externalChime( ) ;
  327. } else {
  328. internalChime( ) ;
  329. }
  330. break ;
  331. }
  332. }
  333. if (btn == 2) {
  334. mode2 = (mode2 + 1) % 3 ;
  335. oled.setTextXY(5,1);
  336. switch(mode2) {
  337. case 0: // Reset Clock
  338. sprintf(dispStr," Reset Clock ");
  339. break ;
  340. case 1: // Reset Chime
  341. dispChimeMode( ) ;
  342. break ;
  343. case 2: // External Test
  344. dispChimeType(mode4) ;
  345. break ;
  346. }
  347. oled.putString(dispStr);
  348. }
  349. if (btn == 3) {
  350. if (mode2 == 1) {
  351. mode3 = (mode3 == 0) ? 1 : 0 ;
  352. dispChimeMode( ) ;
  353. oled.setTextXY(5,1);
  354. oled.putString(dispStr);
  355. }
  356. if (mode2 == 2) {
  357. mode4 = (mode4 == 0) ? 1 : 0 ;
  358. dispChimeType(mode4) ;
  359. oled.setTextXY(5,1);
  360. oled.putString(dispStr);
  361. }
  362. }
  363. }
  364. // --- チャイムCongigMode表示 ---
  365. void dispChimeMode( ) {
  366. if (mode3 == 0) {
  367. sprintf(dispStr," Reset Chime ");
  368. } else {
  369. sprintf(dispStr," Save Chime ");
  370. }
  371. }
  372. // --- チャイムタイプ表示 ---
  373. void dispChimeType(unsigned char ct) {
  374. if (ct == 0) {
  375. sprintf(dispStr," Ext Chime ");
  376. } else {
  377. sprintf(dispStr," Speaker ");
  378. }
  379. }
  380. // ボタンの状態をチェック
  381. void checkButton( ) {
  382. int button0Stat ; // ボタン1・2の状態
  383. int button1Stat ; // ボタン3・4の状態
  384. buttonStat = 0 ;
  385. button0Stat = analogRead(BTN0_PIN) / 100 ;
  386. button1Stat = analogRead(BTN1_PIN) / 100 ;
  387. if (button0Stat == 6) buttonStat |= 0x01 ;
  388. if (button0Stat == 8) buttonStat |= 0x02 ;
  389. if (button0Stat == 5) buttonStat |= 0x03 ;
  390. if (button1Stat == 6) buttonStat |= 0x10 ;
  391. if (button1Stat == 8) buttonStat |= 0x20 ;
  392. if (button1Stat == 5) buttonStat |= 0x30 ;
  393. btn = 0 ;
  394. if ((buttonStat & 0x20) != 0 && (buttonStatPre & 0x20) == 0) {
  395. btn = 4 ;
  396. }
  397. if ((buttonStat & 0x10) != 0 && (buttonStatPre & 0x10) == 0) {
  398. btn = 3 ;
  399. }
  400. if ((buttonStat & 0x02) != 0 && (buttonStatPre & 0x02) == 0) {
  401. btn = 2 ;
  402. }
  403. if ((buttonStat & 0x01) != 0 && (buttonStatPre & 0x01) == 0) {
  404. btn = 1 ;
  405. }
  406. buttonStatPre = buttonStat ;
  407. }
  408. // --- Init Chime ---
  409. void initChime( ) {
  410. // チャイム時刻テーブル設定
  411. memcpy(chime,chimeFlash,sizeof(short int) * MAXCHIMENUM) ;
  412. for (int i1=0;i1 chime[i2]) {
  413. int w = chime[i1] ;
  414. chime[i1] = chime[i2] ;
  415. chime[i2] = w ;
  416. }
  417. }
  418. }
  419. }
  420. // --- Save Chime ---
  421. void saveChime( ) {
  422. chimeData.chimeType = chimeType ;
  423. for (int i1=0;i1 時刻合わせ
  424. case 1: mode1 = 2 ; break ; // 時刻合わせ -> チャイム設定
  425. case 2: mode1 = 3 ; break ; // チャイム設定 -> 設定
  426. case 3:
  427. default:
  428. mode1 = 0 ; break ; // チャイム設定 -> 時刻表示
  429. }
  430. initDisp() ;
  431. }
  432. // Loop Process
  433. switch(mode1) {
  434. case 0: dispTime() ; break ;
  435. case 1: setClocl() ; break ;
  436. case 2: setChime() ; break ;
  437. case 3: setConfig() ; break ;
  438. }
  439. // ----- Loop Term -----
  440. delay(100);
  441. }