<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>ロボット玩具の自作</title>
<link>https://ameblo.jp/shinai15/</link>
<atom:link href="https://rssblog.ameba.jp/shinai15/rss20.xml" rel="self" type="application/rss+xml" />
<atom:link rel="hub" href="http://pubsubhubbub.appspot.com" />
<description>ロボット玩具の自作を始めましたが、細かなことをすぐ忘れてしまうので制作過程を自分の忘備録として残すことにしました。</description>
<language>ja</language>
<item>
<title>無線通信モジュール（ｎRF24L01) の動作確認</title>
<description>
<![CDATA[ <p>&nbsp;</p><p><span style="font-size:1.4em;"><b style="font-weight:bold;">無線通信機能の動作確認を行う</b></span></p><p>&nbsp;</p><p>・無線通信には無線モジュール <b style="font-weight:bold;">nRF24L01 </b>を使用する。</p><p>・無線モジュールは<b style="font-weight:bold;">SPI</b>インターフェイスで<b style="font-weight:bold;">Raspbery Pi Pico</b> と接続する。</p><p>&nbsp;</p><p>・配線の簡素化と電圧変換のためにアダプタも使用する。</p><p>　（nRF24L01 は通常3.3Vを使用するが、アダプタを介せばアダプタが</p><p>&nbsp; &nbsp; &nbsp; 3.3Vを供給してくれるので Pico からは５Vで接続できる）</p><p>&nbsp;</p><p>&nbsp; 　　<a href="https://stat.ameba.jp/user_images/20251214/10/shinai15/51/66/j/o2489205915729905888.jpg"><img alt="" contenteditable="inherit" height="300" src="https://stat.ameba.jp/user_images/20251214/10/shinai15/51/66/j/o2489205915729905888.jpg" width="363"></a></p><p>&nbsp;</p><p>◆主な仕様と特徴</p><ul data-processed="true" jscontroller="mPWODf" jsuid="ZtEXsd_x"><li data-hveid="CAMQAA" data-processed="true" jscontroller="vsuOFb" jsuid="ZtEXsd_y"><strong data-processed="true" jscontroller="zYmgkd" jsuid="ZtEXsd_10">動作周波数</strong>: 2.4GHz ISM帯</li><li data-hveid="CAMQAQ" data-processed="true" jscontroller="vsuOFb" jsuid="ZtEXsd_11"><strong data-processed="true" jscontroller="zYmgkd" jsuid="ZtEXsd_13">変調方式</strong>: GFSK</li><li data-hveid="CAMQAg" data-processed="true" jscontroller="vsuOFb" jsuid="ZtEXsd_14"><strong data-processed="true" jscontroller="zYmgkd" jsuid="ZtEXsd_16">データレート</strong>: 最大2Mbps (250Kbps, 1Mbps, 2Mbps)</li><li data-hveid="CAMQAw" data-processed="true" jscontroller="vsuOFb" jsuid="ZtEXsd_17"><strong data-processed="true" jscontroller="zYmgkd" jsuid="ZtEXsd_19">通信距離</strong>: モジュールによる（PA/LNAなしで数十m〜100m）</li><li data-hveid="CAMQBA" data-processed="true" jscontroller="vsuOFb" jsuid="ZtEXsd_1a"><strong data-processed="true" jscontroller="zYmgkd" jsuid="ZtEXsd_1c">電源電圧</strong>: DC 1.9V～3.6V</li><li data-hveid="CAMQBQ" data-processed="true" jscontroller="vsuOFb" jsuid="ZtEXsd_1d"><strong data-processed="true" jscontroller="zYmgkd" jsuid="ZtEXsd_1f">インターフェース</strong>: SPI (Serial Peripheral Interface)</li><li data-hveid="CAMQBg" data-processed="true" jscontroller="vsuOFb" jsuid="ZtEXsd_1g"><strong data-processed="true" jscontroller="zYmgkd" jsuid="ZtEXsd_1i">チャネル数</strong>: 125 RFチャネル</li></ul><p>&nbsp;</p><p>◆回路接続</p><p>&nbsp;</p><p>　・使用する信号ピンは <b style="font-weight:bold;">CE、CSN、SCK、MOSI、MISO</b>&nbsp;の５本です。</p><p>　・電源ピンは VCC５V、GND の２本</p><p>　・IRQ信号は今回使用しない。</p><p>　・Raspbery Pi との接続は上記の計７本をアダプタに接続する。</p><p>&nbsp;</p><p>&nbsp;　　　　　　　<a href="https://stat.ameba.jp/user_images/20251214/08/shinai15/66/80/j/o2169311415729869447.jpg"><img alt="" contenteditable="inherit" height="200" src="https://stat.ameba.jp/user_images/20251214/08/shinai15/66/80/j/o2169311415729869447.jpg" width="139"></a></p><p>&nbsp;</p><p>　・Pico でSPI接続に使えるピンはいくつかの系統があるが、</p><p>　　今回のテストではSPI0の信号ペアを使用することにする。</p><p>&nbsp;</p><p>　&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp; &nbsp; &nbsp; &nbsp;SCK&nbsp; &nbsp; &nbsp;&nbsp; &nbsp;|&nbsp; &nbsp; TX(MOSI)&nbsp; &nbsp; &nbsp;|&nbsp; &nbsp; &nbsp;RX(MISO)&nbsp; &nbsp; &nbsp;</p><p>　　---------------------------------------------------------------</p><p>　&nbsp; &nbsp;<b style="font-weight:bold;"> SPI0&nbsp; &nbsp; &nbsp; |&nbsp; &nbsp; &nbsp; GP2&nbsp; 　&nbsp; |&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;GP3&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp; &nbsp; &nbsp; &nbsp; GP4&nbsp;</b> &nbsp;&nbsp;</p><p>　&nbsp; &nbsp; SPI1&nbsp; &nbsp; &nbsp; |&nbsp; &nbsp; &nbsp; GP18&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; |&nbsp; &nbsp; &nbsp; &nbsp; GP19&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;|&nbsp; &nbsp; &nbsp; &nbsp;GP16</p><p>&nbsp;</p><p>　・CE、CSN信号は任意なので今回はGP0、GP1を使用。</p><p>&nbsp;</p><p>　　　<a href="https://stat.ameba.jp/user_images/20251214/10/shinai15/28/91/j/o2333278315729901018.jpg"><img alt="" contenteditable="inherit" height="300" src="https://stat.ameba.jp/user_images/20251214/10/shinai15/28/91/j/o2333278315729901018.jpg" width="251"></a></p><p>　</p><p>◆ テストプログラム</p><p>&nbsp;</p><p>　・使用ライブラリは定番の <strong data-end="248" data-start="226">TMRh20版 RF24 ライブラリ</strong> を使う。</p><p>&nbsp;</p><p>　🔳送信側スケッチは</p><p data-end="1525" data-start="1488">　　　・文字列 <code data-end="1516" data-start="1492">"Hello nRF24L01 #カウンタ"</code> を1秒おきに送信</p><p data-end="1546" data-start="1528">　　　・アドレス <code data-end="1542" data-start="1533">"NODE1"</code> へ送信</p><p data-end="1572" data-start="1549">　　　・デバッグ用にシリアルモニタにも送信データを表示</p><p data-end="1572" data-start="1549">&nbsp;</p><p>　🔳受信側スケッチは</p><p data-end="1525" data-start="1488">　　　・送信側と同じアドレス <code data-end="2808" data-start="2799">"NODE1"</code> を読み取り口として設定</p><p data-end="1525" data-start="1488">　　　・受信した文字列をシリアルモニタに表示</p><p>　</p><p>　🔳注意点</p><p>　　　・アドレスは送信側と受信側で同じであることが必須</p><p>　　　・周波数が被ってノイズが多い場合は、<code data-end="4023" data-start="4006">setChannelの値を変える</code></p><p><code data-end="4023" data-start="4006">　　　・</code>nRF24L01のVCC-GND間に10μF～の電解コンデンサを付ける</p><p>&nbsp;</p><p>◆ 送信側スケッチ</p><p>---------------------------------------------------------------------------------------------</p><p>#include &lt;SPI.h&gt;<br>#include &lt;RF24.h&gt;<br><br>RF24 radio(0, 1); &nbsp;// CE=GP0, CSN=GP1<br><br>const byte address[6] = "NODE1";<br><br>void setup() {<br>&nbsp; Serial.begin(115200);<br>&nbsp; delay(2000);<br>&nbsp; Serial.println("nRF24L01 Sender start");<br><br>&nbsp; // SPI0 のデフォルトは SCK=18, MOSI=19, MISO=16<br>&nbsp; // 明示するならこう書いてもOK（なくてもOK）<br>&nbsp; SPI.setSCK(2); &nbsp; // SCK → GP2<br>&nbsp; SPI.setTX(3); &nbsp; &nbsp;// MOSI → GP3<br>&nbsp; SPI.setRX(4); &nbsp; &nbsp;// MISO → GP4<br><br>&nbsp; if (!radio.begin()) {<br>&nbsp; &nbsp; Serial.println("radio.begin() 失敗。配線や電源を確認してください。");<br>&nbsp; &nbsp; while (1);<br>&nbsp; }<br><br>&nbsp; radio.openWritingPipe(address);<br><br>&nbsp; // AutoAck を ONにする&nbsp;<br>&nbsp; radio.setAutoAck(true);<br><br>&nbsp; radio.setDataRate(RF24_1MBPS);<br>&nbsp; radio.setPALevel(RF24_PA_LOW);<br>&nbsp; radio.setChannel(76);<br><br>&nbsp; radio.stopListening();<br>}<br><br>void loop() {<br>&nbsp; static unsigned long counter = 0;<br>&nbsp; char text[32];<br>&nbsp; snprintf(text, sizeof(text), "Hello nRF24L01 #%lu", counter++);<br><br>&nbsp; bool ok = radio.write(&amp;text, sizeof(text));<br><br>&nbsp; if (ok) {<br>&nbsp; &nbsp; Serial.print("送信 (write OK): ");<br>&nbsp; } else {<br>&nbsp; &nbsp; Serial.print("送信 (write NG): ");<br>&nbsp; }<br>&nbsp; Serial.println(text);<br>&nbsp; delay(1000);<br>}</p><p>----------------------------------------------------------------------------------</p><p>&nbsp;</p><p>&nbsp;</p><p>&nbsp;</p><p>◆ 受信側スケッチ</p><p>----------------------------------------------------------------------------------</p><p>#include &lt;SPI.h&gt;<br>#include &lt;RF24.h&gt;<br><br>RF24 radio(0, 1); &nbsp;// CE=GP0, CSN=GP1<br><br>const byte address[6] = "NODE1";<br><br>void setup() {<br>&nbsp; Serial.begin(115200);<br>&nbsp; delay(2000);<br>&nbsp; Serial.println("nRF24L01 Receiver start");<br><br>&nbsp; SPI.setSCK(2); &nbsp; // SCK → GP2<br>&nbsp; SPI.setTX(3); &nbsp; &nbsp;// MOSI → GP3<br>&nbsp; SPI.setRX(4); &nbsp; &nbsp;// MISO → GP4<br><br>&nbsp; if (!radio.begin()) {<br>&nbsp; &nbsp; Serial.println("radio.begin() 失敗。配線や電源を確認してください。");<br>&nbsp; &nbsp; while (1);<br>&nbsp; }<br><br>&nbsp; radio.openReadingPipe(1, address);<br><br>&nbsp; //&nbsp;AutoAck を ONにする&nbsp;<br>&nbsp; radio.setAutoAck(true);<br><br>&nbsp; radio.setDataRate(RF24_1MBPS);<br>&nbsp; radio.setPALevel(RF24_PA_LOW);<br>&nbsp; radio.setChannel(76);<br><br>&nbsp; radio.startListening();<br>}<br><br>void loop() {<br>&nbsp; if (radio.available()) {<br>&nbsp; &nbsp; char text[32] = {0};<br>&nbsp; &nbsp; radio.read(&amp;text, sizeof(text));<br><br>&nbsp; &nbsp; Serial.print("受信: ");<br>&nbsp; &nbsp; Serial.println(text);<br>&nbsp; }<br>}</p><p>----------------------------------------------------------------------------------</p><p>&nbsp;</p><p>◆実行結果</p><div><div><p>&nbsp;</p><p>　正常に送受信ができているので、受信側のシリアルモニタに</p><p>　以下のメッセージが表示されている。</p><p>　　　　　　　・　　<!--StartFragment --></p><p>　　　受信: Hello nRF24L01 #38</p><p>　　　受信: Hello nRF24L01 #39</p><p>　　　受信: Hello nRF24L01 #40</p><p>　　　　　　　・</p><p><!--EndFragment --></p><div>　　　　　　　・<p>&nbsp;</p></div><p>&nbsp;</p></div><p>&nbsp;</p></div><p>&nbsp;</p><p>&nbsp;</p>
]]>
</description>
<link>https://ameblo.jp/shinai15/entry-12950004107.html</link>
<pubDate>Sun, 14 Dec 2025 07:00:35 +0900</pubDate>
</item>
<item>
<title>時刻発声と鳩の動きの動作確認</title>
<description>
<![CDATA[ <p>&nbsp;</p><p><span style="font-size:1.4em;"><b style="font-weight:bold;">時刻発声と鳩の動きの動作確認</b></span></p><p>&nbsp;</p><p>◆動作の仕様</p><p>&nbsp;</p><p>　・朝7時から夜の12時の間で時刻を音声で告知する。</p><p>　　例えば3時には「サンジー　サンジー」と音声を発生して</p><p>　　窓から鳩が顔を出すようにする。</p><p>　・音声発声用のICはATP3012を使用する。</p><p>　　ATP3012 についての詳細は「<a href="https://ameblo.jp/shinai15/entry-12887950688.html">音声生成IC の動作確認</a>」を参照</p><p>　・朝7時と夜0時は時報の代わりに音楽を流す。</p><p>&nbsp;</p><p>◆ テストプログラムの仕様</p><p>&nbsp;</p><p>　・Pico-1に実装されているRTCから時刻を入手し、</p><p>　　ちょうどの時刻情報をPico-2 にSoftSerial で送信する。</p><p>　　（ xx時 00分 00秒 を検出して xx の値を送信する）</p><p>　・Pico-2では送られてきた時刻情報に基づいて対応する音声を発生する</p><p>　　または対応する音楽を鳴らす。</p><p>&nbsp;</p><p>　　</p><p>◆ テストプログラム</p><p>&nbsp;</p><p><b style="font-weight:bold;">　Pico-1 (時刻送信側）</b></p><p>　　プログラムの中の <b style="font-weight:bold;">drawNormal()</b> ルーチンの中に時報送信に関する</p><p>　　下記のコードを追加する。</p><p>&nbsp;</p><p>&nbsp; // 時刻チェック</p><p>&nbsp; DateTime n = rtc.now();</p><p>&nbsp; if ( n.minute() == 0 &amp;&amp; n.second() == 0 ) {</p><p>&nbsp; &nbsp; // 夜中の1時から6時は時報を鳴らさない</p><p>&nbsp; &nbsp; if (!( n.hour() &gt;= 1 &amp;&amp; n.hour() &lt;&gt;= 7 )) {</p><p>&nbsp; &nbsp; &nbsp; mySerial.println( n.hour());</p><p>&nbsp; &nbsp; &nbsp; Serial.println( n.hour());</p><p>&nbsp; &nbsp; &nbsp; delay(600); &nbsp;//二重送信防止</p><p>&nbsp; &nbsp; }</p><p>&nbsp; }</p><p>&nbsp;</p><p>Pico-1 送信側の全体プログラム</p><p>時刻と温度の表示、時刻の設定、アラームの設定等を含む</p><p>---------------------------------------------------------------------------------------------</p><p>&nbsp;</p><p>#include &lt;Wire.h&gt;</p><p>#include &lt;hd44780.h&gt;</p><p>#include &lt;hd44780ioClass/hd44780_I2Cexp.h&gt;</p><p>#include &lt;RTClib.h&gt;</p><p>#include &lt;SoftwareSerial.h&gt;</p><p>&nbsp;</p><p>SoftwareSerial mySerial(6, 7); &nbsp;// RX,TXの割り当て</p><p>&nbsp;</p><p>// ==== このスケッチ固有の型定義を置く====</p><p>struct TimeParts { int y, mo, d, h, mi, s; };</p><p>struct AlarmParts { int mo, d, h, mi; };</p><p>&nbsp;</p><p>// ===== 設定値 =====</p><p>#define LCD_COLS 20</p><p>#define LCD_ROWS 4</p><p>#define I2C_SDA_PIN 16</p><p>#define I2C_SCL_PIN 17</p><p>&nbsp;</p><p>// Buttons (Active-Low)</p><p>#define PIN_BTN_MODE &nbsp; 0 &nbsp; // GP0: モード切替（通常→時刻設定→アラーム設定→…）</p><p>#define PIN_BTN_SEL &nbsp; &nbsp;1 &nbsp; // GP1: 項目選択</p><p>#define PIN_BTN_INC &nbsp; &nbsp;2 &nbsp; // GP2: +1（長押しリピート）</p><p>#define PIN_BTN_OK &nbsp; &nbsp; 3 &nbsp; // GP3: 確定</p><p>&nbsp;</p><p>// 表示カラム（固定）</p><p>#define COL_DATE &nbsp; 2 &nbsp; // 1行目：日付(3カラム目)</p><p>#define COL_TIME &nbsp; 6 &nbsp; // 2行目：時刻(7カラム目)</p><p>#define COL_TEMP &nbsp; 3 &nbsp; // 4行目：温度(4カラム目)</p><p>&nbsp;</p><p>// オートリピート（増加ボタン）</p><p>#define AR_DELAY_MS &nbsp; &nbsp; 300 &nbsp; // 押してからリピート開始まで</p><p>#define AR_INTERVAL_MS &nbsp; 80 &nbsp; // リピート間隔</p><p>&nbsp;</p><p>// ブザー（とりあえずの発報先）</p><p>#define BUZZER_PIN &nbsp; &nbsp; &nbsp;10 &nbsp; &nbsp;// アクティブブザー想定（LOW=OFF/HIGH=ON）</p><p>&nbsp;</p><p>// ===== グローバル =====</p><p>hd44780_I2Cexp lcd;</p><p>RTC_DS3231 rtc;</p><p>&nbsp;</p><p>// --- モード管理 ---</p><p>enum Mode { MODE_TIME = 0, MODE_TIMESET, MODE_ALARMSET };</p><p>Mode mode = MODE_TIME;</p><p>&nbsp;</p><p>// --- ボタン（デバウンス＆立下り検出） ---</p><p>struct Button {</p><p>&nbsp; uint8_t pin;</p><p>&nbsp; bool lastStable; &nbsp;// true=High(未押), false=Low(押)</p><p>&nbsp; bool lastRead;</p><p>&nbsp; unsigned long lastChangeMs;</p><p>};</p><p>Button btnMode{PIN_BTN_MODE, true, true, 0};</p><p>Button btnSel {PIN_BTN_SEL , true, true, 0};</p><p>Button btnInc {PIN_BTN_INC , true, true, 0};</p><p>Button btnOK &nbsp;{PIN_BTN_OK &nbsp;, true, true, 0};</p><p>const unsigned long DEBOUNCE_MS = 30;</p><p>&nbsp;</p><p>bool updateButton(Button &amp;b){</p><p>&nbsp; bool raw = digitalRead(b.pin);</p><p>&nbsp; if (raw != b.lastRead){ b.lastRead = raw; b.lastChangeMs = millis(); }</p><p>&nbsp; if ((millis() - b.lastChangeMs) &gt; DEBOUNCE_MS){</p><p>&nbsp; &nbsp; if (b.lastStable != raw){</p><p>&nbsp; &nbsp; &nbsp; b.lastStable = raw;</p><p>&nbsp; &nbsp; &nbsp; if (raw == LOW) return true; // 押した瞬間</p><p>&nbsp; &nbsp; }</p><p>&nbsp; }</p><p>&nbsp; return false;</p><p>}</p><p>&nbsp;</p><p>// --- 増加ボタン：長押し状態管理 ---</p><p>bool incHeld = false;</p><p>unsigned long incHoldStartMs = 0;</p><p>unsigned long incLastRepMs &nbsp; = 0;</p><p>&nbsp;</p><p>// --- 曜日（英語3文字） ---</p><p>const char* wdENG3(uint8_t d){</p><p>&nbsp; static const char* t[7] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};</p><p>&nbsp; return t[d % 7];</p><p>}</p><p>&nbsp;</p><p>// --- 2桁ゼロ埋め ---</p><p>static inline String two(uint8_t v){ return (v&lt;10)?("0"+String(v)):String(v); }</p><p>&nbsp;</p><p>// --- 行操作 ---</p><p>void printAtClearToEOL(uint8_t row, uint8_t col, const String &amp;text){</p><p>&nbsp; lcd.setCursor(col, row);</p><p>&nbsp; lcd.print(text);</p><p>&nbsp; int printed = col + text.length();</p><p>&nbsp; if (printed &lt; LCD_COLS){</p><p>&nbsp; &nbsp; for (int i = 0; i &lt; (LCD_COLS - printed); i++) lcd.print(' ');</p><p>&nbsp; }</p><p>}</p><p>void clearLine(uint8_t row){</p><p>&nbsp; lcd.setCursor(0, row);</p><p>&nbsp; for (int i=0; i&lt;LCD_COLS; i++) lcd.print(' ');</p><p>}</p><p>&nbsp;</p><p>// --- カレンダ補助（うるう年・月末日数） ---</p><p>bool isLeap(int y){ return ( (y%4==0 &amp;&amp; y%100!=0) || (y%400==0) ); }</p><p>int daysInMonth(int y, int m){</p><p>&nbsp; static const int d[12] = {31,28,31,30,31,30,31,31,30,31,30,31};</p><p>&nbsp; if (m==2) return d[1] + (isLeap(y) ? 1 : 0);</p><p>&nbsp; return d[m-1];</p><p>}</p><p>&nbsp;</p><p>// --- 通常表示用 ---</p><p>int lastSecond = -1;</p><p>&nbsp;</p><p>// --- 時刻設定（年/月/日/時/分） ---</p><p>enum FieldTime { FT_YEAR=0, FT_MON, FT_DAY, FT_HOUR, FT_MIN, FT_COUNT };</p><p>FieldTime selTime = FT_YEAR;</p><p>TimeParts edit; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 時刻設定用 作業値</p><p>int yearMin = 2025, yearMax = 2030;</p><p>&nbsp;</p><p>// --- アラーム設定（"M D H M"） ---</p><p>enum FieldAlm { FA_MON=0, FA_DAY, FA_HOUR, FA_MIN, FA_COUNT };</p><p>FieldAlm selAlm = FA_MON;</p><p>AlarmParts alarmEdit; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 編集中</p><p>AlarmParts alarmSet; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 確定値</p><p>bool alarmEnabled = false;</p><p>&nbsp;</p><p>// 同じ分での多重発報防止</p><p>int lastTrigY = -1, lastTrigMo = -1, lastTrigD = -1, lastTrigH = -1, lastTrigMi = -1;</p><p>&nbsp;</p><p>// ===== ユーティリティ =====</p><p>void loadEditFromRTC(){</p><p>&nbsp; DateTime n = rtc.now();</p><p>&nbsp; edit.y &nbsp;= n.year(); if (edit.y &lt; yearMin) edit.y = yearMin; if (edit.y &gt; yearMax) edit.y = yearMax;</p><p>&nbsp; edit.mo = n.month();</p><p>&nbsp; edit.d &nbsp;= n.day();</p><p>&nbsp; edit.h &nbsp;= n.hour();</p><p>&nbsp; edit.mi = n.minute();</p><p>&nbsp; edit.s &nbsp;= 0;</p><p>&nbsp; int md = daysInMonth(edit.y, edit.mo);</p><p>&nbsp; if (edit.d &gt; md) edit.d = md;</p><p>}</p><p>String fmtYMDHM0(const TimeParts&amp; t){</p><p>&nbsp; return String(t.y) + "/" + two(t.mo) + "/" + two(t.d) + " " +</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;two(t.h) + ":" + two(t.mi) + ":00";</p><p>}</p><p>String fmtALM(const AlarmParts&amp; a){</p><p>&nbsp; return String("ALM: ") + two(a.mo) + "/" + two(a.d) + " " +</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;two(a.h) + ":" + two(a.mi);</p><p>}</p><p>&nbsp;</p><p>// --- ラベル表示（時刻設定 Y M D H M） ---</p><p>void showLabelsTime(){</p><p>&nbsp; String s = "Y M D H M";</p><p>&nbsp; int pos = 0; &nbsp;// 0,2,4,6,8</p><p>&nbsp; switch(selTime){</p><p>&nbsp; &nbsp; case FT_YEAR: pos = 0; break;</p><p>&nbsp; &nbsp; case FT_MON: &nbsp;pos = 2; break;</p><p>&nbsp; &nbsp; case FT_DAY: &nbsp;pos = 4; break;</p><p>&nbsp; &nbsp; case FT_HOUR: pos = 6; break;</p><p>&nbsp; &nbsp; case FT_MIN: &nbsp;pos = 8; break;</p><p>&nbsp; }</p><p>&nbsp; String out;</p><p>&nbsp; for (int i=0; i&lt;(int)s.length(); ++i){</p><p>&nbsp; &nbsp; if (i == pos) { out += '['; out += s[i]; out += ']'; }</p><p>&nbsp; &nbsp; else &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{ out += s[i]; }</p><p>&nbsp; }</p><p>&nbsp; printAtClearToEOL(1, 0, out);</p><p>}</p><p>&nbsp;</p><p>// --- ラベル表示（アラーム設定 M D H M） ---</p><p>void showLabelsAlm(){</p><p>&nbsp; String s = "M D H M";</p><p>&nbsp; int pos = 0; &nbsp;// 0,2,4,6</p><p>&nbsp; switch(selAlm){</p><p>&nbsp; &nbsp; case FA_MON: &nbsp;pos = 0; break;</p><p>&nbsp; &nbsp; case FA_DAY: &nbsp;pos = 2; break;</p><p>&nbsp; &nbsp; case FA_HOUR: pos = 4; break;</p><p>&nbsp; &nbsp; case FA_MIN: &nbsp;pos = 6; break;</p><p>&nbsp; }</p><p>&nbsp; String out;</p><p>&nbsp; for (int i=0; i&lt;(int)s.length(); ++i){</p><p>&nbsp; &nbsp; if (i == pos) { out += '['; out += s[i]; out += ']'; }</p><p>&nbsp; &nbsp; else &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{ out += s[i]; }</p><p>&nbsp; }</p><p>&nbsp; printAtClearToEOL(1, 0, out);</p><p>}</p><p>&nbsp;</p><p>// --- インクリメント（時刻設定） ---</p><p>void incrFieldTime(){</p><p>&nbsp; switch(selTime){</p><p>&nbsp; &nbsp; case FT_YEAR:</p><p>&nbsp; &nbsp; &nbsp; edit.y++; if (edit.y &gt; yearMax) edit.y = yearMin;</p><p>&nbsp; &nbsp; &nbsp; if (edit.d &gt; daysInMonth(edit.y, edit.mo)) edit.d = daysInMonth(edit.y, edit.mo);</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case FT_MON:</p><p>&nbsp; &nbsp; &nbsp; edit.mo++; if (edit.mo &gt; 12) edit.mo = 1;</p><p>&nbsp; &nbsp; &nbsp; if (edit.d &gt; daysInMonth(edit.y, edit.mo)) edit.d = daysInMonth(edit.y, edit.mo);</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case FT_DAY:{</p><p>&nbsp; &nbsp; &nbsp; int md = daysInMonth(edit.y, edit.mo);</p><p>&nbsp; &nbsp; &nbsp; edit.d++; if (edit.d &gt; md) edit.d = 1;</p><p>&nbsp; &nbsp; } break;</p><p>&nbsp; &nbsp; case FT_HOUR:</p><p>&nbsp; &nbsp; &nbsp; edit.h++; if (edit.h &gt; 23) edit.h = 0;</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case FT_MIN:</p><p>&nbsp; &nbsp; &nbsp; edit.mi++; if (edit.mi &gt; 59) edit.mi = 0;</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; }</p><p>}</p><p>&nbsp;</p><p>// --- インクリメント（アラーム設定） ---</p><p>void incrFieldAlm(){</p><p>&nbsp; switch(selAlm){</p><p>&nbsp; &nbsp; case FA_MON:</p><p>&nbsp; &nbsp; &nbsp; alarmEdit.mo++; if (alarmEdit.mo &gt; 12) alarmEdit.mo = 1;</p><p>&nbsp; &nbsp; &nbsp; { int y = rtc.now().year();</p><p>&nbsp; &nbsp; &nbsp; &nbsp; int md = daysInMonth(y, alarmEdit.mo);</p><p>&nbsp; &nbsp; &nbsp; &nbsp; if (alarmEdit.d &gt; md) alarmEdit.d = md;</p><p>&nbsp; &nbsp; &nbsp; }</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case FA_DAY:{</p><p>&nbsp; &nbsp; &nbsp; int y = rtc.now().year();</p><p>&nbsp; &nbsp; &nbsp; int md = daysInMonth(y, alarmEdit.mo);</p><p>&nbsp; &nbsp; &nbsp; alarmEdit.d++; if (alarmEdit.d &gt; md) alarmEdit.d = 1;</p><p>&nbsp; &nbsp; } break;</p><p>&nbsp; &nbsp; case FA_HOUR:</p><p>&nbsp; &nbsp; &nbsp; alarmEdit.h++; if (alarmEdit.h &gt; 23) alarmEdit.h = 0;</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case FA_MIN:</p><p>&nbsp; &nbsp; &nbsp; alarmEdit.mi++; if (alarmEdit.mi &gt; 59) alarmEdit.mi = 0;</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; }</p><p>}</p><p>&nbsp;</p><p>// --- ブザー：簡易ビープ（後日、音楽に差し替え予定） ---</p><p>void triggerAlarm(){</p><p>&nbsp; /*</p><p>&nbsp; for (int i=0; i&lt;3; ++i){</p><p>&nbsp; &nbsp; digitalWrite(BUZZER_PIN, HIGH); delay(150);</p><p>&nbsp; &nbsp; digitalWrite(BUZZER_PIN, LOW); &nbsp;delay(120);</p><p>&nbsp; }</p><p>&nbsp; */</p><p>}</p><p>&nbsp;</p><p>// ===== セットアップ =====</p><p>void setup(){</p><p>&nbsp; Serial.begin(9600);</p><p>&nbsp; mySerial.begin(9600); &nbsp; // ソフトウェアシリアル通信</p><p>&nbsp; delay(200);</p><p>&nbsp;</p><p>&nbsp; // I2C</p><p>&nbsp; Wire.setSDA(I2C_SDA_PIN);</p><p>&nbsp; Wire.setSCL(I2C_SCL_PIN);</p><p>&nbsp; Wire.begin();</p><p>&nbsp;</p><p>&nbsp; // LCD</p><p>&nbsp; int status = lcd.begin(LCD_COLS, LCD_ROWS);</p><p>&nbsp; if (status){</p><p>&nbsp; &nbsp; Serial.print("LCD init error: "); Serial.println(status);</p><p>&nbsp; &nbsp; while(1) delay(10);</p><p>&nbsp; }</p><p>&nbsp; lcd.clear(); lcd.noCursor(); lcd.noBlink();</p><p>&nbsp; Serial.print("lcd setup");</p><p>&nbsp; // RTC</p><p>&nbsp; if (!rtc.begin()){</p><p>&nbsp; &nbsp; lcd.setCursor(0,0); lcd.print("RTC not found!");</p><p>&nbsp; &nbsp; while(1) delay(10);</p><p>&nbsp; }</p><p>&nbsp; if (rtc.lostPower()){</p><p>&nbsp; &nbsp; rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));</p><p>&nbsp; &nbsp; Serial.println("RTC adjusted to compile time.");</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; // Buttons</p><p>&nbsp; pinMode(PIN_BTN_MODE, INPUT_PULLUP);</p><p>&nbsp; pinMode(PIN_BTN_SEL , INPUT_PULLUP);</p><p>&nbsp; pinMode(PIN_BTN_INC , INPUT_PULLUP);</p><p>&nbsp; pinMode(PIN_BTN_OK &nbsp;, INPUT_PULLUP);</p><p>&nbsp;</p><p>&nbsp; // Buzzer</p><p>&nbsp; pinMode(BUZZER_PIN, OUTPUT);</p><p>&nbsp; digitalWrite(BUZZER_PIN, LOW);</p><p>&nbsp;</p><p>&nbsp; // 初期画面クリア</p><p>&nbsp; for (int r=0; r&lt;LCD_ROWS; r++) clearLine(r);</p><p>&nbsp;</p><p>&nbsp; // アラーム初期値</p><p>&nbsp; alarmEdit = {1, 1, 0, 0};</p><p>&nbsp; alarmSet &nbsp;= alarmEdit;</p><p>&nbsp; alarmEnabled = false;</p><p>}</p><p>&nbsp;</p><p>// ===== 通常表示 =====</p><p>void drawNormal(){</p><p>&nbsp; DateTime now = rtc.now();</p><p>&nbsp; if (now.second() != lastSecond){</p><p>&nbsp; &nbsp; lastSecond = now.second();</p><p>&nbsp;</p><p>&nbsp; &nbsp; clearLine(0);</p><p>&nbsp; &nbsp; clearLine(1);</p><p>&nbsp; &nbsp; clearLine(2);</p><p>&nbsp; &nbsp; clearLine(3);&nbsp; &nbsp;</p><p>&nbsp;</p><p>&nbsp; &nbsp; String dateStr = String(now.year()) + "/" + two(now.month()) + "/" + two(now.day());</p><p>&nbsp; &nbsp; String wd = String(" (") + wdENG3(now.dayOfTheWeek()) + String(")");</p><p>&nbsp; &nbsp; printAtClearToEOL(0, COL_DATE, dateStr + wd);</p><p>&nbsp;</p><p>&nbsp; &nbsp; char sep = ':';</p><p>&nbsp; &nbsp; String timeStr = two(now.hour()) + String(sep) + two(now.minute()) + String(sep) + two(now.second());</p><p>&nbsp; &nbsp; printAtClearToEOL(1, COL_TIME, timeStr);</p><p>&nbsp;</p><p>&nbsp; &nbsp; // 4行目：温度</p><p>&nbsp; &nbsp; float tc = rtc.getTemperature();</p><p>&nbsp; &nbsp; String line4 = "Temp: " + String(tc, 1) + " C";</p><p>&nbsp; &nbsp; printAtClearToEOL(3, COL_TEMP, line4);</p><p>&nbsp;</p><p>&nbsp; &nbsp; // アラーム設定インジケータ</p><p>&nbsp; &nbsp; lcd.setCursor(19, 3);</p><p>&nbsp; &nbsp; lcd.print(alarmEnabled ? (char)255 : ' ');</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; // 時刻チェック</p><p>&nbsp; DateTime n = rtc.now();</p><p>&nbsp; if ( n.minute() == 0 &amp;&amp; n.second() == 0 ) {</p><p>&nbsp; &nbsp; // 夜中の1時から6時は時報を鳴らさない</p><p>&nbsp; &nbsp; if (!( n.hour() &gt;= 1 &amp;&amp; n.hour() &lt;&gt;= 7 )) {</p><p>&nbsp; &nbsp; &nbsp; mySerial.println( n.hour());</p><p>&nbsp; &nbsp; &nbsp; Serial.println( n.hour());</p><p>&nbsp; &nbsp; &nbsp; delay(600); &nbsp;//二重送信防止</p><p>&nbsp; &nbsp; }</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; // 発報チェック</p><p>&nbsp; if (alarmEnabled){</p><p>&nbsp; &nbsp; DateTime n = rtc.now();</p><p>&nbsp; &nbsp; if ( n.month() &nbsp;== alarmSet.mo &amp;&amp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;n.day() &nbsp; &nbsp;== alarmSet.d &nbsp;&amp;&amp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;n.hour() &nbsp; == alarmSet.h &nbsp;&amp;&amp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;n.minute() == alarmSet.mi &amp;&amp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;n.second() == 0 ) {</p><p>&nbsp; &nbsp; &nbsp; if (!(lastTrigY==n.year() &amp;&amp; lastTrigMo==n.month() &amp;&amp; lastTrigD==n.day() &amp;&amp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; lastTrigH==n.hour() &amp;&amp; lastTrigMi==n.minute())){</p><p>&nbsp; &nbsp; &nbsp; &nbsp; lastTrigY = n.year(); lastTrigMo = n.month(); lastTrigD = n.day();</p><p>&nbsp; &nbsp; &nbsp; &nbsp; lastTrigH = n.hour(); lastTrigMi = n.minute();</p><p>&nbsp; &nbsp; &nbsp; &nbsp; triggerAlarm();</p><p>&nbsp; &nbsp; &nbsp; }</p><p>&nbsp; &nbsp; }</p><p>&nbsp; }</p><p>&nbsp; delay(500);</p><p>}</p><p>&nbsp;</p><p>// ===== 時刻設定画面 =====</p><p>void drawTimeSet(){</p><p>&nbsp; printAtClearToEOL(0, 0, "Mode:Time Set");</p><p>&nbsp; showLabelsTime();</p><p>&nbsp;</p><p>&nbsp; clearLine(2);</p><p>&nbsp; switch(selTime){</p><p>&nbsp; &nbsp; case FT_YEAR: printAtClearToEOL(2, 0, String(edit.y)); &nbsp;break;</p><p>&nbsp; &nbsp; case FT_MON: &nbsp;printAtClearToEOL(2, 0, String(edit.mo)); break;</p><p>&nbsp; &nbsp; case FT_DAY: &nbsp;printAtClearToEOL(2, 0, String(edit.d)); &nbsp;break;</p><p>&nbsp; &nbsp; case FT_HOUR: printAtClearToEOL(2, 0, String(edit.h)); &nbsp;break;</p><p>&nbsp; &nbsp; case FT_MIN: &nbsp;printAtClearToEOL(2, 0, String(edit.mi)); break;</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; printAtClearToEOL(3, 0, fmtYMDHM0(edit));</p><p>}</p><p>&nbsp;</p><p>// ===== アラーム設定画面 =====</p><p>void drawAlarmSet(){</p><p>&nbsp; printAtClearToEOL(0, 0, "Mode:Alarm Set");</p><p>&nbsp; showLabelsAlm();</p><p>&nbsp;</p><p>&nbsp; clearLine(2);</p><p>&nbsp; switch(selAlm){</p><p>&nbsp; &nbsp; case FA_MON: &nbsp;printAtClearToEOL(2, 0, String(alarmEdit.mo)); break;</p><p>&nbsp; &nbsp; case FA_DAY: &nbsp;printAtClearToEOL(2, 0, String(alarmEdit.d)); &nbsp;break;</p><p>&nbsp; &nbsp; case FA_HOUR: printAtClearToEOL(2, 0, String(alarmEdit.h)); &nbsp;break;</p><p>&nbsp; &nbsp; case FA_MIN: &nbsp;printAtClearToEOL(2, 0, String(alarmEdit.mi)); break;</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; printAtClearToEOL(3, 0, fmtALM(alarmEdit));</p><p>}</p><p>&nbsp;</p><p>// ===== メインループ =====</p><p>void loop(){</p><p>&nbsp; bool modePressed = updateButton(btnMode);</p><p>&nbsp; bool selPressed &nbsp;= updateButton(btnSel);</p><p>&nbsp; bool incPressed &nbsp;= updateButton(btnInc);</p><p>&nbsp; bool okPressed &nbsp; = updateButton(btnOK);</p><p>&nbsp;</p><p>&nbsp; // 長押し管理</p><p>&nbsp; if (incPressed){ incHeld = true; incHoldStartMs = millis(); incLastRepMs = millis(); }</p><p>&nbsp; if (btnInc.lastStable == HIGH) incHeld = false;</p><p>&nbsp; bool incRepeat = false;</p><p>&nbsp; if (incHeld &amp;&amp; btnInc.lastStable == LOW){</p><p>&nbsp; &nbsp; unsigned long nowMs = millis();</p><p>&nbsp; &nbsp; if (nowMs - incHoldStartMs &gt;= AR_DELAY_MS){</p><p>&nbsp; &nbsp; &nbsp; if (nowMs - incLastRepMs &gt;= AR_INTERVAL_MS){</p><p>&nbsp; &nbsp; &nbsp; &nbsp; incLastRepMs = nowMs; incRepeat = true;</p><p>&nbsp; &nbsp; &nbsp; }</p><p>&nbsp; &nbsp; }</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; // モード切替：通常 → 時刻設定 → アラーム設定 → 通常 …</p><p>&nbsp; if (modePressed){</p><p>&nbsp; &nbsp; if (mode == MODE_TIME){</p><p>&nbsp; &nbsp; &nbsp; mode = MODE_TIMESET; loadEditFromRTC();</p><p>&nbsp; &nbsp; } else if (mode == MODE_TIMESET){</p><p>&nbsp; &nbsp; &nbsp; mode = MODE_ALARMSET; alarmEdit = alarmSet;</p><p>&nbsp; &nbsp; } else {</p><p>&nbsp; &nbsp; &nbsp; mode = MODE_TIME;</p><p>&nbsp; &nbsp; }</p><p>&nbsp; &nbsp; lastSecond = -1;</p><p>&nbsp; &nbsp; for (int r=0; r&lt;LCD_ROWS; r++) clearLine(r);</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; if (mode == MODE_TIME){</p><p>&nbsp; &nbsp; drawNormal();</p><p>&nbsp;</p><p>&nbsp; } else if (mode == MODE_TIMESET){</p><p>&nbsp; &nbsp; if (selPressed) selTime = (FieldTime)((selTime + 1) % FT_COUNT);</p><p>&nbsp; &nbsp; if (incPressed || incRepeat) incrFieldTime();</p><p>&nbsp; &nbsp; if (okPressed){</p><p>&nbsp; &nbsp; &nbsp; rtc.adjust(DateTime(edit.y, edit.mo, edit.d, edit.h, edit.mi, 0)); // 秒=0で確定</p><p>&nbsp; &nbsp; &nbsp; mode = MODE_TIME; lastSecond = -1;</p><p>&nbsp; &nbsp; &nbsp; for (int r=0; r&lt;LCD_ROWS; r++) clearLine(r);</p><p>&nbsp; &nbsp; }</p><p>&nbsp; &nbsp; drawTimeSet();</p><p>&nbsp;</p><p>&nbsp; } else { // MODE_ALARMSET</p><p>&nbsp; &nbsp; if (selPressed) selAlm = (FieldAlm)((selAlm + 1) % FA_COUNT);</p><p>&nbsp; &nbsp; if (incPressed || incRepeat) incrFieldAlm();</p><p>&nbsp; &nbsp; if (okPressed){</p><p>&nbsp; &nbsp; &nbsp; alarmSet = alarmEdit; alarmEnabled = true;</p><p>&nbsp; &nbsp; &nbsp; mode = MODE_TIME; lastSecond = -1;</p><p>&nbsp; &nbsp; &nbsp; for (int r=0; r&lt;LCD_ROWS; r++) clearLine(r);</p><p>&nbsp; &nbsp; }</p><p>&nbsp; &nbsp; drawAlarmSet();</p><p>&nbsp; }</p><p>&nbsp; delay(20);</p><p>}</p><p>&nbsp;</p><p>&nbsp;</p><p><b style="font-weight:bold;">&nbsp;Pico-2 (時刻受信側）</b></p><p>----------------------------------------------------------------------------------</p><p>//********************************************************</p><p>// &nbsp;受信した番号が 7（7時）の場合は目覚めの音楽を演奏</p><p>// &nbsp;受信した番号が 0 (0時）の場合はおやすみの音楽を演奏</p><p>// &nbsp;受信した番号が 8～23(8時～23時) の場合は音声で時報を通知</p><p>// &nbsp;受信した番号が 24 の場合はアラーム通知の音楽を演奏</p><p>// &nbsp;受信した番号が 25～28 の場合は好みの音楽を演奏</p><p>//********************************************************</p><p>#include &lt;Servo.h&gt;</p><p>#include &lt;Wire.h&gt;</p><p>#include &lt;DFRobotDFPlayerMini.h&gt;</p><p>#include &lt;SoftwareSerial.h&gt;</p><p>&nbsp;</p><p>DFRobotDFPlayerMini myDFPlayer;</p><p>Servo myServo;</p><p>&nbsp;</p><p>// ===== 設定値 =====</p><p>#define I2C_SDA_PIN 16</p><p>#define I2C_SCL_PIN 17</p><p>&nbsp;</p><p>SoftwareSerial mySerial(27, 28); &nbsp; &nbsp;// RX,TXの割り当て</p><p>SoftwareSerial dfSerial(20, 21); &nbsp; &nbsp;// DFPlayer</p><p>&nbsp;</p><p>int cnum = 0; // 受信した番号</p><p>&nbsp;</p><p>// --- 時報音声 ---</p><p>const char* jihou(uint8_t d){</p><p>&nbsp; static const char* t[24] = {</p><p>&nbsp; &nbsp; "reiji-","ichiji-","niji-","sanji-","yoji-",</p><p>&nbsp; &nbsp; "goji-","rokuji-","hichiji-","hachiji-","kuji-",</p><p>&nbsp; &nbsp; "jyuuji-","jyuuichiji-","jyuuniji-","ichiji-","niji-",</p><p>&nbsp; &nbsp; "sanji-","yoji-","goji-","rokuji-","hichiji-",</p><p>&nbsp; &nbsp; "hachiji-","kuji-","jyuuji-","jyuuichiji-"};</p><p>&nbsp; return t[d];</p><p>}</p><p>&nbsp;</p><p>void voiceOut(const char *voice) {</p><p>&nbsp; // ATP3012とはI2C通信で接続する</p><p>&nbsp; // ATP3012のI2Cアドレスは0x2E</p><p>&nbsp; Wire.beginTransmission(0x2E); // transmit to device 0x2E</p><p>&nbsp; Wire.write(voice);</p><p>&nbsp; Wire.write(0x0D); &nbsp;</p><p>&nbsp; Wire.endTransmission(); &nbsp; &nbsp;// stop transmitting</p><p>&nbsp; delay(1000);</p><p>}</p><p>&nbsp;</p><p>void setup() {</p><p>&nbsp; // I2C</p><p>&nbsp; Wire.setSDA(I2C_SDA_PIN);</p><p>&nbsp; Wire.setSCL(I2C_SCL_PIN);</p><p>&nbsp; Wire.begin();</p><p>&nbsp;</p><p>&nbsp; // Serial</p><p>&nbsp; mySerial.begin(9600);</p><p>&nbsp; dfSerial.begin(9600); &nbsp;</p><p>&nbsp; Serial.begin(9600);</p><p>&nbsp;</p><p>&nbsp; // Servo</p><p>&nbsp; myServo.attach(22, 500, 2400); &nbsp;</p><p>&nbsp; myServo.write(60); &nbsp;</p><p>&nbsp;</p><p>&nbsp; if (!myDFPlayer.begin(dfSerial)) {</p><p>&nbsp; &nbsp; Serial.println("DFPlayer Mini が見つかりません。配線またはSDカードを確認してください。");</p><p>&nbsp; &nbsp; while (true); // 停止</p><p>&nbsp; }</p><p>&nbsp; myDFPlayer.volume(10); // 音量設定（0～30）</p><p>&nbsp;</p><p>}</p><p>&nbsp;</p><p>void loop() {</p><p>&nbsp; if (mySerial.available()) {</p><p>&nbsp; &nbsp; String s = mySerial.readStringUntil('\n'); &nbsp;// 改行まで読む</p><p>&nbsp; &nbsp; // 送信側は print() ではなくprintln() で送信する</p><p>&nbsp; &nbsp;</p><p>&nbsp; &nbsp; if (s.length() == 0) return;</p><p>&nbsp;</p><p>&nbsp; &nbsp; cnum = s.toInt(); &nbsp; // int に変換</p><p>&nbsp;</p><p>&nbsp; &nbsp; Serial.print("受信した値 = ");</p><p>&nbsp; &nbsp; Serial.println(cnum);</p><p>&nbsp; &nbsp;</p><p>&nbsp; &nbsp; if ( cnum == 7 ) {</p><p>&nbsp; &nbsp; &nbsp; // 目覚めの音楽を演奏</p><p>&nbsp; &nbsp; &nbsp; myDFPlayer.play(1); // 曲番号1 を演奏</p><p>&nbsp;</p><p>&nbsp; &nbsp; } else if ( cnum == 0 ) { &nbsp;</p><p>&nbsp; &nbsp; &nbsp; // おやすみの音楽を演奏</p><p>&nbsp; &nbsp; &nbsp; myDFPlayer.play(2); // 曲番号2 を演奏</p><p>&nbsp;</p><p>&nbsp; &nbsp; } else if ( cnum &gt;= 1 &amp;&amp; cnum &lt;= 23 ) {</p><p>&nbsp; &nbsp; &nbsp; myServo.write(110); &nbsp; &nbsp;// 鳩を出す</p><p>&nbsp; &nbsp; &nbsp; delay(100);</p><p>&nbsp; &nbsp; &nbsp; voiceOut(jihou(cnum)); // 時報を発声</p><p>&nbsp; &nbsp; &nbsp; delay(100);</p><p>&nbsp; &nbsp; &nbsp; voiceOut(jihou(cnum)); // 2回続けて発声</p><p>&nbsp; &nbsp; &nbsp; delay(599);</p><p>&nbsp; &nbsp; &nbsp; myServo.write(60); &nbsp; &nbsp; // 鳩を仕舞う</p><p>&nbsp; &nbsp; &nbsp;</p><p>&nbsp; &nbsp; } else if ( &nbsp;cnum &gt;= 24 &amp;&amp; cnum &lt;= 26 ) {</p><p>&nbsp; &nbsp; &nbsp; // 音楽を演奏</p><p>&nbsp; &nbsp; &nbsp; myDFPlayer.play( cnum - 22); // 曲番号 3～5 を演奏</p><p>&nbsp; &nbsp; }</p><p>&nbsp; }</p><p>}</p><p>&nbsp;</p><p>◆実行結果</p><div><p>&nbsp;</p><p>テスト録音が小さくてよく聞こえないが、時報を発生している</p><div><p>&nbsp;</p><iframe allowfullscreen frameborder="0" height="210" scrolling="no" src="https://static.blog-video.jp/?v=MC1F9iHdz6PiuTA0SElEAgoJOn" width="420"></iframe><p>&nbsp;</p></div><p>&nbsp;</p></div><p>&nbsp;</p><p>&nbsp;</p>
]]>
</description>
<link>https://ameblo.jp/shinai15/entry-12948004487.html</link>
<pubDate>Tue, 25 Nov 2025 18:46:34 +0900</pubDate>
</item>
<item>
<title>複数のRaspbery Pi Pico どうしの連携の動作確認</title>
<description>
<![CDATA[ <p>&nbsp;</p><p><span style="font-size:1.4em;"><b style="font-weight:bold;">Raspbery Pi Pico 間の連携の動作確認</b></span></p><p>&nbsp;</p><p>&nbsp; &nbsp;&nbsp;<a href="https://stat.ameba.jp/user_images/20251120/09/shinai15/18/43/j/o1080069915721100517.jpg"><img alt="image" height="272" src="https://stat.ameba.jp/user_images/20251120/09/shinai15/18/43/j/o1080069915721100517.jpg" width="420"></a></p><p>&nbsp;</p><p>◆Pico 間の連携の動作確認方法</p><p>&nbsp;</p><p>　・pico 間の連携はシリアル通信で行う<br>　・シリアル通信はソフトシリアルを使う<br>　・Pico-1（送信側）はコマンド（数値１～５）を順に送る<br>　・Pico-2（受信側）はコマンドを受けて対応する音楽を再生する<br>&nbsp;</p><p>◆回路接続</p><p>&nbsp;</p><p>&nbsp; &nbsp; ・Pico-1基板の回路図</p><p>&nbsp; &nbsp; &nbsp;&nbsp;<a href="https://stat.ameba.jp/user_images/20251119/21/shinai15/41/27/j/o1544093615720987509.jpg"><img alt="" height="255" src="https://stat.ameba.jp/user_images/20251119/21/shinai15/41/27/j/o1544093615720987509.jpg" width="420"></a></p><p>&nbsp;</p><p>&nbsp; &nbsp; ・Pico-2基板の回路図</p><p>&nbsp; &nbsp; &nbsp;&nbsp;<a href="https://stat.ameba.jp/user_images/20251120/09/shinai15/45/a2/j/o1466092215721107062.jpg"><img alt="" height="264" src="https://stat.ameba.jp/user_images/20251120/09/shinai15/45/a2/j/o1466092215721107062.jpg" width="420"></a></p><p>&nbsp;</p><p>　・Pico-1 では GP06、GP07 をSoftSerial で使用</p><p>　・Pico-2 では GP27、GP28 をSoftSerial で使用</p><p>　　</p><p>&nbsp;</p><p>◆ テストプログラム</p><p>&nbsp;</p><p>　・送信側 Pico-1 では音楽番号として1から5を順番に送る</p><p>　・受信側 Pico-2 では送られてきた番号の音楽を再生する</p><p>&nbsp;</p><p>---------------------------------------------------------------------------------------------</p><p>Pico-1（送信側）プログラム</p><p>&nbsp;</p><p>#include &lt;SoftwareSerial.h&gt;</p><p>SoftwareSerial mySerial(6, 7); &nbsp;// RX,TXの割り当て</p><p>int n = 1;</p><p>&nbsp;</p><p>void setup() {</p><p>&nbsp; mySerial.begin(9600); &nbsp; // ソフトウェアシリアル通信</p><p>}</p><p>&nbsp;</p><p>void loop(){</p><p>&nbsp; delay(6000);</p><p>&nbsp; mySerial.print(n);</p><p>&nbsp; n++;</p><p>&nbsp; if ( n &gt; 5) {n = 0;}</p><p>}</p><p>----------------------------------------------------------------------------------</p><p>Pico-2（受信側）プログラム</p><p>&nbsp;</p><p>#include &lt;SoftwareSerial.h&gt;</p><p>#include "DFRobotDFPlayerMini.h"</p><p>#define FPSerial softSerial</p><p>&nbsp;</p><p>SoftwareSerial mySerial(27, 28); &nbsp; &nbsp;// Pico-1との通信</p><p>SoftwareSerial dfSerial(20, 21); &nbsp; &nbsp;// DFPlayerとの通信</p><p>DFRobotDFPlayerMini myDFPlayer;</p><p>&nbsp;</p><p>void setup() {</p><p>&nbsp; &nbsp;mySerial.begin(9600);&nbsp; &nbsp;// 各初期化</p><p>&nbsp; &nbsp;Serial.begin(9600);</p><p>&nbsp; &nbsp;dfSerial.begin(9600);</p><p>&nbsp;</p><p>&nbsp; if (!myDFPlayer.begin(dfSerial)) {</p><p>&nbsp; &nbsp; &nbsp;Serial.println("DFPlayer Mini が見つかりません");</p><p>&nbsp; &nbsp; &nbsp;while (true); // 停止</p><p>&nbsp; }</p><p>&nbsp; myDFPlayer.volume(20);&nbsp; &nbsp;//Set volume value.&nbsp;</p><p>}</p><p>&nbsp;</p><p>void loop(){</p><p>&nbsp; &nbsp;while(mySerial.available()&gt;0){</p><p>&nbsp; &nbsp; &nbsp;int n = mySerial.read(); &nbsp; &nbsp; // Pico-1から受信したデータを読み込む</p><p>&nbsp; &nbsp; &nbsp;n = n-'0';&nbsp; &nbsp; // 数値変換</p><p>&nbsp; &nbsp; &nbsp;//Serial.print("read data ="); Serial.println(n);</p><p>&nbsp; &nbsp; &nbsp;myDFPlayer.play(n); // 曲を再生</p><p>&nbsp; &nbsp; &nbsp;delay(5000);</p><p>&nbsp; &nbsp;}</p><p>}</p><p>&nbsp;</p><p>void loop() {</p><p>&nbsp; // 何もしない</p><p>}</p><p>&nbsp;</p><p>&nbsp;</p><p>◆実行結果</p><div><div><p>&nbsp;</p><p>&nbsp;</p><div>&nbsp;<p>&nbsp;</p></div><p>&nbsp;</p></div><p>&nbsp;</p></div><p>&nbsp;</p><p>&nbsp;</p>
]]>
</description>
<link>https://ameblo.jp/shinai15/entry-12947305608.html</link>
<pubDate>Wed, 19 Nov 2025 12:13:21 +0900</pubDate>
</item>
<item>
<title>RTC （DS3231) に時刻設定機能を追加</title>
<description>
<![CDATA[ <p>&nbsp;</p><p><span style="font-size:1.4em;"><b style="font-weight:bold;">RRTC （DS3231) に時刻設定機能を追加する</b></span></p><p>&nbsp;</p><p>◆プログラムの仕様</p><p>&nbsp;</p><div><a href="https://stat.ameba.jp/user_images/20251103/12/shinai15/26/61/j/o1080060815708505984.jpg"><img alt="" border="0" height="225" src="https://stat.ameba.jp/user_images/20251103/12/shinai15/26/61/j/o1080060815708505984.jpg" width="400"></a></div><p>&nbsp;</p><p>&nbsp;</p><p>・ボタンSWを4つ用意します。各ボタンはVCCにプルアップされて　<br>　押し下げるとLowになり、それをプログラムで検出します<br>・ボタン1はモード切替SWで<b style="font-weight:bold;">時刻表示モード</b>、<b style="font-weight:bold;">時刻設定モード</b>、<br>　<b style="font-weight:bold;">アラーム設定モード</b>をトグルで切り替えます。<br>・モードSWを一度押すと1行目に <b style="font-weight:bold;">Mode:Time Set</b> と表示し、さらに押すと<br>　<b style="font-weight:bold;">Mode:Alarm Set</b> と表示、さらに押すと通常の時刻表示モードに戻る<br>・時刻設定モードになったときは2行目に　Y M D H M S と表示して<br>　最初はYが点滅する<br>・ボタン2は何を設定するかを<b style="font-weight:bold;">選択するボタン</b>で押すたびに<br>　Y-&gt; M-&gt; D-&gt; H-&gt; M-&gt; S と順番に点滅させる<br>・3行目にはそれぞれの初期値を表示する<br>　例えばボタン2がY（Year）だったら初期値は2025で最大値はとりあえず</p><p>　2030 までとし、2030の次は2025に戻る<br>・以下、Mなら１～１２、Dなら１～３１のようにする　<br>・ボタン３は<b style="font-weight:bold;">数値の増加ボタン</b>で押すたびに3行目の数値を１づつ増加させる</p><p>・ボタンの<b style="font-weight:bold;">長押しでオートリピート</b>させる<br>・ボタン４は<b style="font-weight:bold;">確定ボタン</b>で、それまでに設定した値を4行目に表示し、<br>　もう一度押したら設定値をもとに時刻表示モードに戻る<br>・アラーム設定は時刻設定モードと同じ操作方法だが、秒の設定は不要とする。</p><p>・アラームがヒットした時点で音楽を鳴らすか音声で知らせるか、</p><p>　詳細の仕様は後日検討する。<br>・4つのボタンはGP00～GP03に接続して押下を検出する。</p><p>&nbsp;</p><p>&nbsp;</p><p>◆ テストプログラム</p><p>&nbsp;</p><p>---------------------------------------------------------------------------------------------</p><p>// ==== まず #include を最上段に ====</p><p>#include &lt;Wire.h&gt;</p><p>#include &lt;hd44780.h&gt;</p><p>#include &lt;hd44780ioClass/hd44780_I2Cexp.h&gt;</p><p>#include &lt;RTClib.h&gt;</p><p>&nbsp;</p><p>// ==== つぎに、このスケッチ固有の型定義を置く（以降の関数で安全に使える）====</p><p>struct TimeParts { int y, mo, d, h, mi, s; };</p><p>struct AlarmParts { int mo, d, h, mi; };</p><p>&nbsp;</p><p>// ===== 設定値 =====</p><p>#define LCD_COLS 20</p><p>#define LCD_ROWS 4</p><p>#define I2C_SDA_PIN 16</p><p>#define I2C_SCL_PIN 17</p><p>&nbsp;</p><p>// Buttons (Active-Low)</p><p>#define PIN_BTN_MODE &nbsp; 0 &nbsp; // GP0: モード切替（通常→時刻設定→アラーム設定→…）</p><p>#define PIN_BTN_SEL &nbsp; &nbsp;1 &nbsp; // GP1: 項目選択</p><p>#define PIN_BTN_INC &nbsp; &nbsp;2 &nbsp; // GP2: +1（長押しリピート）</p><p>#define PIN_BTN_OK &nbsp; &nbsp; 3 &nbsp; // GP3: 確定</p><p>&nbsp;</p><p>// 表示カラム（固定）</p><p>#define COL_DATE &nbsp; 2 &nbsp; // 1行目：日付(3カラム目)</p><p>#define COL_TIME &nbsp; 6 &nbsp; // 2行目：時刻(7カラム目)</p><p>#define COL_TEMP &nbsp; 3 &nbsp; // 4行目：温度(4カラム目)</p><p>&nbsp;</p><p>// オートリピート（増加ボタン）</p><p>#define AR_DELAY_MS &nbsp; &nbsp; 300 &nbsp; // 押してからリピート開始まで</p><p>#define AR_INTERVAL_MS &nbsp; 80 &nbsp; // リピート間隔</p><p>&nbsp;</p><p>// ブザー（とりあえずの発報先）</p><p>#define BUZZER_PIN &nbsp; &nbsp; &nbsp;10 &nbsp; &nbsp;// アクティブブザー想定（LOW=OFF/HIGH=ON）</p><p>&nbsp;</p><p>// ===== グローバル =====</p><p>hd44780_I2Cexp lcd;</p><p>RTC_DS3231 rtc;</p><p>&nbsp;</p><p>// --- モード管理 ---</p><p>enum Mode { MODE_TIME = 0, MODE_TIMESET, MODE_ALARMSET };</p><p>Mode mode = MODE_TIME;</p><p>&nbsp;</p><p>// --- ボタン（デバウンス＆立下り検出） ---</p><p>struct Button {</p><p>&nbsp; uint8_t pin;</p><p>&nbsp; bool lastStable; &nbsp;// true=High(未押), false=Low(押)</p><p>&nbsp; bool lastRead;</p><p>&nbsp; unsigned long lastChangeMs;</p><p>};</p><p>Button btnMode{PIN_BTN_MODE, true, true, 0};</p><p>Button btnSel {PIN_BTN_SEL , true, true, 0};</p><p>Button btnInc {PIN_BTN_INC , true, true, 0};</p><p>Button btnOK &nbsp;{PIN_BTN_OK &nbsp;, true, true, 0};</p><p>const unsigned long DEBOUNCE_MS = 30;</p><p>&nbsp;</p><p>bool updateButton(Button &amp;b){</p><p>&nbsp; bool raw = digitalRead(b.pin);</p><p>&nbsp; if (raw != b.lastRead){ b.lastRead = raw; b.lastChangeMs = millis(); }</p><p>&nbsp; if ((millis() - b.lastChangeMs) &gt; DEBOUNCE_MS){</p><p>&nbsp; &nbsp; if (b.lastStable != raw){</p><p>&nbsp; &nbsp; &nbsp; b.lastStable = raw;</p><p>&nbsp; &nbsp; &nbsp; if (raw == LOW) return true; // 押した瞬間</p><p>&nbsp; &nbsp; }</p><p>&nbsp; }</p><p>&nbsp; return false;</p><p>}</p><p>&nbsp;</p><p>// --- 増加ボタン：長押し状態管理 ---</p><p>bool incHeld = false;</p><p>unsigned long incHoldStartMs = 0;</p><p>unsigned long incLastRepMs &nbsp; = 0;</p><p>&nbsp;</p><p>// --- 曜日（英語3文字） ---</p><p>const char* wdENG3(uint8_t d){</p><p>&nbsp; static const char* t[7] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};</p><p>&nbsp; return t[d % 7];</p><p>}</p><p>&nbsp;</p><p>// --- 2桁ゼロ埋め ---</p><p>static inline String two(uint8_t v){ return (v&lt;10)?("0"+String(v)):String(v); }</p><p>&nbsp;</p><p>// --- 行操作 ---</p><p>void printAtClearToEOL(uint8_t row, uint8_t col, const String &amp;text){</p><p>&nbsp; lcd.setCursor(col, row);</p><p>&nbsp; lcd.print(text);</p><p>&nbsp; int printed = col + text.length();</p><p>&nbsp; if (printed &lt; LCD_COLS){</p><p>&nbsp; &nbsp; for (int i = 0; i &lt; (LCD_COLS - printed); i++) lcd.print(' ');</p><p>&nbsp; }</p><p>}</p><p>void clearLine(uint8_t row){</p><p>&nbsp; lcd.setCursor(0, row);</p><p>&nbsp; for (int i=0; i&lt;LCD_COLS; i++) lcd.print(' ');</p><p>}</p><p>&nbsp;</p><p>// --- カレンダ補助（うるう年・月末日数） ---</p><p>bool isLeap(int y){ return ( (y%4==0 &amp;&amp; y%100!=0) || (y%400==0) ); }</p><p>int daysInMonth(int y, int m){</p><p>&nbsp; static const int d[12] = {31,28,31,30,31,30,31,31,30,31,30,31};</p><p>&nbsp; if (m==2) return d[1] + (isLeap(y) ? 1 : 0);</p><p>&nbsp; return d[m-1];</p><p>}</p><p>&nbsp;</p><p>// --- 通常表示用 ---</p><p>int lastSecond = -1;</p><p>&nbsp;</p><p>// --- 時刻設定（年/月/日/時/分） ---</p><p>enum FieldTime { FT_YEAR=0, FT_MON, FT_DAY, FT_HOUR, FT_MIN, FT_COUNT };</p><p>FieldTime selTime = FT_YEAR;</p><p>TimeParts edit; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 時刻設定用 作業値</p><p>int yearMin = 2025, yearMax = 2030;</p><p>&nbsp;</p><p>// --- アラーム設定（"M D H M"） ---</p><p>enum FieldAlm { FA_MON=0, FA_DAY, FA_HOUR, FA_MIN, FA_COUNT };</p><p>FieldAlm selAlm = FA_MON;</p><p>AlarmParts alarmEdit; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 編集中</p><p>AlarmParts alarmSet; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 確定値</p><p>bool alarmEnabled = false;</p><p>&nbsp;</p><p>// 同じ分での多重発報防止</p><p>int lastTrigY = -1, lastTrigMo = -1, lastTrigD = -1, lastTrigH = -1, lastTrigMi = -1;</p><p>&nbsp;</p><p>// ===== ユーティリティ =====</p><p>void loadEditFromRTC(){</p><p>&nbsp; DateTime n = rtc.now();</p><p>&nbsp; edit.y &nbsp;= n.year(); if (edit.y &lt; yearMin) edit.y = yearMin; if (edit.y &gt; yearMax) edit.y = yearMax;</p><p>&nbsp; edit.mo = n.month();</p><p>&nbsp; edit.d &nbsp;= n.day();</p><p>&nbsp; edit.h &nbsp;= n.hour();</p><p>&nbsp; edit.mi = n.minute();</p><p>&nbsp; edit.s &nbsp;= 0;</p><p>&nbsp; int md = daysInMonth(edit.y, edit.mo);</p><p>&nbsp; if (edit.d &gt; md) edit.d = md;</p><p>}</p><p>String fmtYMDHM0(const TimeParts&amp; t){</p><p>&nbsp; return String(t.y) + "/" + two(t.mo) + "/" + two(t.d) + " " +</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;two(t.h) + ":" + two(t.mi) + ":00";</p><p>}</p><p>String fmtALM(const AlarmParts&amp; a){</p><p>&nbsp; return String("ALM: ") + two(a.mo) + "/" + two(a.d) + " " +</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;two(a.h) + ":" + two(a.mi);</p><p>}</p><p>&nbsp;</p><p>// --- ラベル表示（時刻設定 Y M D H M） ---</p><p>void showLabelsTime(){</p><p>&nbsp; String s = "Y M D H M";</p><p>&nbsp; int pos = 0; &nbsp;// 0,2,4,6,8</p><p>&nbsp; switch(selTime){</p><p>&nbsp; &nbsp; case FT_YEAR: pos = 0; break;</p><p>&nbsp; &nbsp; case FT_MON: &nbsp;pos = 2; break;</p><p>&nbsp; &nbsp; case FT_DAY: &nbsp;pos = 4; break;</p><p>&nbsp; &nbsp; case FT_HOUR: pos = 6; break;</p><p>&nbsp; &nbsp; case FT_MIN: &nbsp;pos = 8; break;</p><p>&nbsp; }</p><p>&nbsp; String out;</p><p>&nbsp; for (int i=0; i&lt;(int)s.length(); ++i){</p><p>&nbsp; &nbsp; if (i == pos) { out += '['; out += s[i]; out += ']'; }</p><p>&nbsp; &nbsp; else &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{ out += s[i]; }</p><p>&nbsp; }</p><p>&nbsp; printAtClearToEOL(1, 0, out);</p><p>}</p><p>&nbsp;</p><p>// --- ラベル表示（アラーム設定 M D H M） ---</p><p>void showLabelsAlm(){</p><p>&nbsp; String s = "M D H M";</p><p>&nbsp; int pos = 0; &nbsp;// 0,2,4,6</p><p>&nbsp; switch(selAlm){</p><p>&nbsp; &nbsp; case FA_MON: &nbsp;pos = 0; break;</p><p>&nbsp; &nbsp; case FA_DAY: &nbsp;pos = 2; break;</p><p>&nbsp; &nbsp; case FA_HOUR: pos = 4; break;</p><p>&nbsp; &nbsp; case FA_MIN: &nbsp;pos = 6; break;</p><p>&nbsp; }</p><p>&nbsp; String out;</p><p>&nbsp; for (int i=0; i&lt;(int)s.length(); ++i){</p><p>&nbsp; &nbsp; if (i == pos) { out += '['; out += s[i]; out += ']'; }</p><p>&nbsp; &nbsp; else &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{ out += s[i]; }</p><p>&nbsp; }</p><p>&nbsp; printAtClearToEOL(1, 0, out);</p><p>}</p><p>&nbsp;</p><p>// --- インクリメント（時刻設定） ---</p><p>void incrFieldTime(){</p><p>&nbsp; switch(selTime){</p><p>&nbsp; &nbsp; case FT_YEAR:</p><p>&nbsp; &nbsp; &nbsp; edit.y++; if (edit.y &gt; yearMax) edit.y = yearMin;</p><p>&nbsp; &nbsp; &nbsp; if (edit.d &gt; daysInMonth(edit.y, edit.mo)) edit.d = daysInMonth(edit.y, edit.mo);</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case FT_MON:</p><p>&nbsp; &nbsp; &nbsp; edit.mo++; if (edit.mo &gt; 12) edit.mo = 1;</p><p>&nbsp; &nbsp; &nbsp; if (edit.d &gt; daysInMonth(edit.y, edit.mo)) edit.d = daysInMonth(edit.y, edit.mo);</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case FT_DAY:{</p><p>&nbsp; &nbsp; &nbsp; int md = daysInMonth(edit.y, edit.mo);</p><p>&nbsp; &nbsp; &nbsp; edit.d++; if (edit.d &gt; md) edit.d = 1;</p><p>&nbsp; &nbsp; } break;</p><p>&nbsp; &nbsp; case FT_HOUR:</p><p>&nbsp; &nbsp; &nbsp; edit.h++; if (edit.h &gt; 23) edit.h = 0;</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case FT_MIN:</p><p>&nbsp; &nbsp; &nbsp; edit.mi++; if (edit.mi &gt; 59) edit.mi = 0;</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; }</p><p>}</p><p>&nbsp;</p><p>// --- インクリメント（アラーム設定） ---</p><p>void incrFieldAlm(){</p><p>&nbsp; switch(selAlm){</p><p>&nbsp; &nbsp; case FA_MON:</p><p>&nbsp; &nbsp; &nbsp; alarmEdit.mo++; if (alarmEdit.mo &gt; 12) alarmEdit.mo = 1;</p><p>&nbsp; &nbsp; &nbsp; { int y = rtc.now().year();</p><p>&nbsp; &nbsp; &nbsp; &nbsp; int md = daysInMonth(y, alarmEdit.mo);</p><p>&nbsp; &nbsp; &nbsp; &nbsp; if (alarmEdit.d &gt; md) alarmEdit.d = md;</p><p>&nbsp; &nbsp; &nbsp; }</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case FA_DAY:{</p><p>&nbsp; &nbsp; &nbsp; int y = rtc.now().year();</p><p>&nbsp; &nbsp; &nbsp; int md = daysInMonth(y, alarmEdit.mo);</p><p>&nbsp; &nbsp; &nbsp; alarmEdit.d++; if (alarmEdit.d &gt; md) alarmEdit.d = 1;</p><p>&nbsp; &nbsp; } break;</p><p>&nbsp; &nbsp; case FA_HOUR:</p><p>&nbsp; &nbsp; &nbsp; alarmEdit.h++; if (alarmEdit.h &gt; 23) alarmEdit.h = 0;</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case FA_MIN:</p><p>&nbsp; &nbsp; &nbsp; alarmEdit.mi++; if (alarmEdit.mi &gt; 59) alarmEdit.mi = 0;</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; }</p><p>}</p><p>&nbsp;</p><p>// --- ブザー：簡易ビープ（後日、音楽に差し替え予定） ---</p><p>void triggerAlarm(){</p><p>&nbsp; for (int i=0; i&lt;3; ++i){</p><p>&nbsp; &nbsp; digitalWrite(BUZZER_PIN, HIGH); delay(150);</p><p>&nbsp; &nbsp; digitalWrite(BUZZER_PIN, LOW); &nbsp;delay(120);</p><p>&nbsp; }</p><p>}</p><p>&nbsp;</p><p>// ===== セットアップ =====</p><p>void setup(){</p><p>&nbsp; Serial.begin(9600);</p><p>&nbsp; delay(200);</p><p>&nbsp;</p><p>&nbsp; // I2C</p><p>&nbsp; Wire.setSDA(I2C_SDA_PIN);</p><p>&nbsp; Wire.setSCL(I2C_SCL_PIN);</p><p>&nbsp; Wire.begin();</p><p>&nbsp;</p><p>&nbsp; // LCD</p><p>&nbsp; int status = lcd.begin(LCD_COLS, LCD_ROWS);</p><p>&nbsp; if (status){</p><p>&nbsp; &nbsp; Serial.print("LCD init error: "); Serial.println(status);</p><p>&nbsp; &nbsp; while(1) delay(10);</p><p>&nbsp; }</p><p>&nbsp; lcd.clear(); lcd.noCursor(); lcd.noBlink();</p><p>&nbsp;</p><p>&nbsp; // RTC</p><p>&nbsp; if (!rtc.begin()){</p><p>&nbsp; &nbsp; lcd.setCursor(0,0); lcd.print("RTC not found!");</p><p>&nbsp; &nbsp; while(1) delay(10);</p><p>&nbsp; }</p><p>&nbsp; if (rtc.lostPower()){</p><p>&nbsp; &nbsp; rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));</p><p>&nbsp; &nbsp; Serial.println("RTC adjusted to compile time.");</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; // Buttons</p><p>&nbsp; pinMode(PIN_BTN_MODE, INPUT_PULLUP);</p><p>&nbsp; pinMode(PIN_BTN_SEL , INPUT_PULLUP);</p><p>&nbsp; pinMode(PIN_BTN_INC , INPUT_PULLUP);</p><p>&nbsp; pinMode(PIN_BTN_OK &nbsp;, INPUT_PULLUP);</p><p>&nbsp;</p><p>&nbsp; // Buzzer</p><p>&nbsp; pinMode(BUZZER_PIN, OUTPUT);</p><p>&nbsp; digitalWrite(BUZZER_PIN, LOW);</p><p>&nbsp;</p><p>&nbsp; // 初期画面クリア</p><p>&nbsp; for (int r=0; r&lt;LCD_ROWS; r++) clearLine(r);</p><p>&nbsp;</p><p>&nbsp; // アラーム初期値</p><p>&nbsp; alarmEdit = {1, 1, 0, 0};</p><p>&nbsp; alarmSet &nbsp;= alarmEdit;</p><p>&nbsp; alarmEnabled = false;</p><p>}</p><p>&nbsp;</p><p>// ===== 通常表示 =====</p><p>void drawNormal(){</p><p>&nbsp; DateTime now = rtc.now();</p><p>&nbsp; if (now.second() != lastSecond){</p><p>&nbsp; &nbsp; lastSecond = now.second();</p><p>&nbsp;</p><p>&nbsp; &nbsp; clearLine(0);</p><p>&nbsp; &nbsp; clearLine(1);</p><p>&nbsp; &nbsp; clearLine(2);</p><p>&nbsp; &nbsp; clearLine(3); &nbsp; // ★ご要望どおり3行目も毎秒クリア</p><p>&nbsp;</p><p>&nbsp; &nbsp; String dateStr = String(now.year()) + "/" + two(now.month()) + "/" + two(now.day());</p><p>&nbsp; &nbsp; String wd = String(" (") + wdENG3(now.dayOfTheWeek()) + String(")");</p><p>&nbsp; &nbsp; printAtClearToEOL(0, COL_DATE, dateStr + wd);</p><p>&nbsp;</p><p>&nbsp; &nbsp; char sep = ':';</p><p>&nbsp; &nbsp; String timeStr = two(now.hour()) + String(sep) + two(now.minute()) + String(sep) + two(now.second());</p><p>&nbsp; &nbsp; printAtClearToEOL(1, COL_TIME, timeStr);</p><p>&nbsp;</p><p>&nbsp; &nbsp; // 4行目：温度</p><p>&nbsp; &nbsp; float tc = rtc.getTemperature();</p><p>&nbsp; &nbsp; String line4 = "Temp: " + String(tc, 1) + " C";</p><p>&nbsp; &nbsp; printAtClearToEOL(3, COL_TEMP, line4);</p><p>&nbsp;</p><p>&nbsp; &nbsp; /*</p><p>&nbsp; &nbsp; // アラーム設定インジケータ（最右：列19、0xFFを●代わりに）</p><p>&nbsp; &nbsp; lcd.setCursor(19, 3);</p><p>&nbsp; &nbsp; lcd.print(alarmEnabled ? (char)255 : ' ');</p><p>&nbsp; &nbsp; */</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; // 発報チェック</p><p>&nbsp; if (alarmEnabled){</p><p>&nbsp; &nbsp; DateTime n = rtc.now();</p><p>&nbsp; &nbsp; if ( n.month() &nbsp;== alarmSet.mo &amp;&amp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;n.day() &nbsp; &nbsp;== alarmSet.d &nbsp;&amp;&amp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;n.hour() &nbsp; == alarmSet.h &nbsp;&amp;&amp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;n.minute() == alarmSet.mi &amp;&amp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;n.second() == 0 ) {</p><p>&nbsp; &nbsp; &nbsp; if (!(lastTrigY==n.year() &amp;&amp; lastTrigMo==n.month() &amp;&amp; lastTrigD==n.day() &amp;&amp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; lastTrigH==n.hour() &amp;&amp; lastTrigMi==n.minute())){</p><p>&nbsp; &nbsp; &nbsp; &nbsp; lastTrigY = n.year(); lastTrigMo = n.month(); lastTrigD = n.day();</p><p>&nbsp; &nbsp; &nbsp; &nbsp; lastTrigH = n.hour(); lastTrigMi = n.minute();</p><p>&nbsp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; triggerAlarm();</p><p>&nbsp; &nbsp; &nbsp; }</p><p>&nbsp; &nbsp; }</p><p>&nbsp; }</p><p>}</p><p>&nbsp;</p><p>// ===== 時刻設定画面 =====</p><p>void drawTimeSet(){</p><p>&nbsp; printAtClearToEOL(0, 0, "Mode:Time Set");</p><p>&nbsp; showLabelsTime();</p><p>&nbsp;</p><p>&nbsp; clearLine(2);</p><p>&nbsp; switch(selTime){</p><p>&nbsp; &nbsp; case FT_YEAR: printAtClearToEOL(2, 0, String(edit.y)); &nbsp;break;</p><p>&nbsp; &nbsp; case FT_MON: &nbsp;printAtClearToEOL(2, 0, String(edit.mo)); break;</p><p>&nbsp; &nbsp; case FT_DAY: &nbsp;printAtClearToEOL(2, 0, String(edit.d)); &nbsp;break;</p><p>&nbsp; &nbsp; case FT_HOUR: printAtClearToEOL(2, 0, String(edit.h)); &nbsp;break;</p><p>&nbsp; &nbsp; case FT_MIN: &nbsp;printAtClearToEOL(2, 0, String(edit.mi)); break;</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; printAtClearToEOL(3, 0, fmtYMDHM0(edit));</p><p>}</p><p>&nbsp;</p><p>// ===== アラーム設定画面 =====</p><p>void drawAlarmSet(){</p><p>&nbsp; printAtClearToEOL(0, 0, "Mode:Alarm Set");</p><p>&nbsp; showLabelsAlm();</p><p>&nbsp;</p><p>&nbsp; clearLine(2);</p><p>&nbsp; switch(selAlm){</p><p>&nbsp; &nbsp; case FA_MON: &nbsp;printAtClearToEOL(2, 0, String(alarmEdit.mo)); break;</p><p>&nbsp; &nbsp; case FA_DAY: &nbsp;printAtClearToEOL(2, 0, String(alarmEdit.d)); &nbsp;break;</p><p>&nbsp; &nbsp; case FA_HOUR: printAtClearToEOL(2, 0, String(alarmEdit.h)); &nbsp;break;</p><p>&nbsp; &nbsp; case FA_MIN: &nbsp;printAtClearToEOL(2, 0, String(alarmEdit.mi)); break;</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; printAtClearToEOL(3, 0, fmtALM(alarmEdit));</p><p>}</p><p>&nbsp;</p><p>// ===== メインループ =====</p><p>void loop(){</p><p>&nbsp; bool modePressed = updateButton(btnMode);</p><p>&nbsp; bool selPressed &nbsp;= updateButton(btnSel);</p><p>&nbsp; bool incPressed &nbsp;= updateButton(btnInc);</p><p>&nbsp; bool okPressed &nbsp; = updateButton(btnOK);</p><p><br>&nbsp;</p><p>&nbsp; // 長押し管理</p><p>&nbsp; if (incPressed){ incHeld = true; incHoldStartMs = millis(); incLastRepMs = millis(); }</p><p>&nbsp; if (btnInc.lastStable == HIGH) incHeld = false;</p><p>&nbsp; bool incRepeat = false;</p><p>&nbsp; if (incHeld &amp;&amp; btnInc.lastStable == LOW){</p><p>&nbsp; &nbsp; unsigned long nowMs = millis();</p><p>&nbsp; &nbsp; if (nowMs - incHoldStartMs &gt;= AR_DELAY_MS){</p><p>&nbsp; &nbsp; &nbsp; if (nowMs - incLastRepMs &gt;= AR_INTERVAL_MS){</p><p>&nbsp; &nbsp; &nbsp; &nbsp; incLastRepMs = nowMs; incRepeat = true;</p><p>&nbsp; &nbsp; &nbsp; }</p><p>&nbsp; &nbsp; }</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; // モード切替：通常 → 時刻設定 → アラーム設定 → 通常 …</p><p>&nbsp; if (modePressed){</p><p>&nbsp; &nbsp; if (mode == MODE_TIME){</p><p>&nbsp; &nbsp; &nbsp; mode = MODE_TIMESET; loadEditFromRTC();</p><p>&nbsp; &nbsp; } else if (mode == MODE_TIMESET){</p><p>&nbsp; &nbsp; &nbsp; mode = MODE_ALARMSET; alarmEdit = alarmSet;</p><p>&nbsp; &nbsp; } else {</p><p>&nbsp; &nbsp; &nbsp; mode = MODE_TIME;</p><p>&nbsp; &nbsp; }</p><p>&nbsp; &nbsp; lastSecond = -1;</p><p>&nbsp; &nbsp; for (int r=0; r&lt;LCD_ROWS; r++) clearLine(r);</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; if (mode == MODE_TIME){</p><p>&nbsp; &nbsp; drawNormal();</p><p>&nbsp;</p><p>&nbsp; } else if (mode == MODE_TIMESET){</p><p>&nbsp; &nbsp; if (selPressed) selTime = (FieldTime)((selTime + 1) % FT_COUNT);</p><p>&nbsp; &nbsp; if (incPressed || incRepeat) incrFieldTime();</p><p>&nbsp; &nbsp; if (okPressed){</p><p>&nbsp; &nbsp; &nbsp; rtc.adjust(DateTime(edit.y, edit.mo, edit.d, edit.h, edit.mi, 0)); // 秒=0で確定</p><p>&nbsp; &nbsp; &nbsp; mode = MODE_TIME; lastSecond = -1;</p><p>&nbsp; &nbsp; &nbsp; for (int r=0; r&lt;LCD_ROWS; r++) clearLine(r);</p><p>&nbsp; &nbsp; }</p><p>&nbsp; &nbsp; drawTimeSet();</p><p>&nbsp;</p><p>&nbsp; } else { // MODE_ALARMSET</p><p>&nbsp; &nbsp; if (selPressed) selAlm = (FieldAlm)((selAlm + 1) % FA_COUNT);</p><p>&nbsp; &nbsp; if (incPressed || incRepeat) incrFieldAlm();</p><p>&nbsp; &nbsp; if (okPressed){</p><p>&nbsp; &nbsp; &nbsp; alarmSet = alarmEdit; alarmEnabled = true;</p><p>&nbsp; &nbsp; &nbsp; mode = MODE_TIME; lastSecond = -1;</p><p>&nbsp; &nbsp; &nbsp; for (int r=0; r&lt;LCD_ROWS; r++) clearLine(r);</p><p>&nbsp; &nbsp; }</p><p>&nbsp; &nbsp; drawAlarmSet();</p><p>&nbsp; }</p><p>&nbsp;</p><p>&nbsp; delay(20);</p><p>}</p><p>----------------------------------------------------------------------------------</p><p>&nbsp;</p><p>◆実行結果</p><div><div><p>&nbsp;</p><p>OK!</p><p>&nbsp;</p><div>&nbsp;<p>&nbsp;</p></div><p>&nbsp;</p></div><p>&nbsp;</p></div><p>&nbsp;</p><p>&nbsp;</p>
]]>
</description>
<link>https://ameblo.jp/shinai15/entry-12942374350.html</link>
<pubDate>Fri, 31 Oct 2025 21:54:10 +0900</pubDate>
</item>
<item>
<title>RTC リアルタイムクロック（DS3231) の動作確認</title>
<description>
<![CDATA[ <p>&nbsp;</p><p><span style="font-size:1.4em;"><b style="font-weight:bold;">RTC (DS3231) の動作確認を行う</b></span></p><p>&nbsp;</p><p><a href="https://stat.ameba.jp/user_images/20251031/20/shinai15/7d/f0/j/o0400025215706914710.jpg"><img alt="" height="252" src="https://stat.ameba.jp/user_images/20251031/20/shinai15/7d/f0/j/o0400025215706914710.jpg" width="400"></a></p><p>&nbsp;</p><p>◆主な仕様と特徴</p><p>&nbsp;</p><p>　・AM/PMインジケータ付き24/12時制フォーマット<br>　・低消費電力<br>　・時刻アラームx2<br>　・インターフェイス：I2C（400kHz）、7bitアドレス：0x68<br>　・寸法：25mm x 25mm x 8mm (4.2g)<br>　・６ピンソケットでラズベリーパイと直接接続</p><p>&nbsp;</p><p>◆回路接続</p><p>&nbsp;</p><p>　・使用するピンは <b style="font-weight:bold;">VCC、GND、SDL、SCL</b>&nbsp;の４本です。</p><p>　・Raspbery Pi との接続は上記４本のみ。</p><p>　　</p><p>◆ テストプログラム</p><p>&nbsp;</p><p>　・時刻の表示は2004Aを使います。</p><p>　・1行目に「YYYY/MM/DD」、2行目に「HH:MM:SS」（24時間表記）、</p><p>　　曜日は英語3文字で、温度は摂氏温度をリアルタイム表示する。<br data-end="117" data-start="114">　・初回起動時（またはRTCの電源喪失時）は自動で“コンパイル時刻”を</p><p>　　RTCに書き込み、以後はRTCを使って進みます。</p><p>&nbsp;</p><p>---------------------------------------------------------------------------------------------</p><p>#include &lt;Wire.h&gt;<br>#include &lt;hd44780.h&gt;<br>#include &lt;hd44780ioClass/hd44780_I2Cexp.h&gt;<br>#include &lt;RTClib.h&gt;<br><br>#define LCD_COLS 20<br>#define LCD_ROWS 4<br>#define I2C_SDA_PIN 16<br>#define I2C_SCL_PIN 17<br><br>hd44780_I2Cexp lcd;<br>RTC_DS3231 rtc;<br><br>int lastSecond = -1;<br><br>// 2桁ゼロ埋め<br>static inline String two(uint8_t v){ return (v&lt;10)?("0"+String(v)):String(v); }<br><br>// 英語3文字の曜日（RTClib: 0=Sun..6=Sat）<br>const char* wdENG3(uint8_t d){<br>&nbsp; static const char* t[7] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};<br>&nbsp; return t[d % 7];<br>}<br><br>// 指定位置に表示し、行末まで空白で消す（上書きのゴミ防止）<br>void printAtClearToEOL(uint8_t row, uint8_t col, const String &amp;text){<br>&nbsp; lcd.setCursor(col, row);<br>&nbsp; lcd.print(text);<br>&nbsp; // 残りを空白で塗る<br>&nbsp; int printed = col + text.length();<br>&nbsp; if (printed &lt; LCD_COLS){<br>&nbsp; &nbsp; for (int i = 0; i &lt; (LCD_COLS - printed); i++) lcd.print(' ');<br>&nbsp; }<br>}<br><br>// 行全体を空白でクリア<br>void clearLine(uint8_t row){<br>&nbsp; lcd.setCursor(0, row);<br>&nbsp; for(int i=0;i&lt;LCD_COLS;i++) lcd.print(' ');<br>}<br><br>void setup(){<br>&nbsp; Serial.begin(115200);<br>&nbsp; delay(200);<br><br>&nbsp; Wire.setSDA(I2C_SDA_PIN);<br>&nbsp; Wire.setSCL(I2C_SCL_PIN);<br>&nbsp; Wire.begin();<br><br>&nbsp; int status = lcd.begin(LCD_COLS, LCD_ROWS);<br>&nbsp; if(status){<br>&nbsp; &nbsp; Serial.print("LCD init error: "); Serial.println(status);<br>&nbsp; &nbsp; while(1) delay(10);<br>&nbsp; }<br>&nbsp; lcd.clear();<br>&nbsp; lcd.noCursor();<br>&nbsp; lcd.noBlink();<br><br>&nbsp; if(!rtc.begin()){<br>&nbsp; &nbsp; lcd.setCursor(0,0); lcd.print("RTC not found!");<br>&nbsp; &nbsp; while(1) delay(10);<br>&nbsp; }<br><br>&nbsp; if(rtc.lostPower()){<br>&nbsp; &nbsp; rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));<br>&nbsp; &nbsp; Serial.println("RTC adjusted to compile time.");<br>&nbsp; }<br><br>&nbsp; // 初期表示を軽くクリア<br>&nbsp; for(int r=0;r&lt;LCD_ROWS;r++) clearLine(r);<br>}<br><br>void loop(){<br>&nbsp; DateTime now = rtc.now();<br><br>&nbsp; if(now.second() != lastSecond){<br>&nbsp; &nbsp; lastSecond = now.second();<br><br>&nbsp; &nbsp; // 1行目：col=2(3カラム目)から 日付 + スペース + (英3曜日)<br>&nbsp; &nbsp; String dateStr = String(now.year()) + "/" + two(now.month()) + "/" + two(now.day());<br>&nbsp; &nbsp; String wd = String(" (") + wdENG3(now.dayOfTheWeek()) + String(")");<br>&nbsp; &nbsp; printAtClearToEOL(0, 2, dateStr + wd);<br><br>&nbsp; &nbsp; // 2行目：col=6(7カラム目)から 時刻（コロン点滅）<br>&nbsp; &nbsp; bool blinkOn = (now.second() % 2 == 0);<br>&nbsp; &nbsp; char sep = blinkOn ? ':' : ' ';<br>&nbsp; &nbsp; String timeStr = two(now.hour()) + String(sep) + two(now.minute()) + String(sep) + two(now.second());<br>&nbsp; &nbsp; printAtClearToEOL(1, 6, timeStr);<br><br>&nbsp; &nbsp; // 3行目：空白<br>&nbsp; &nbsp; clearLine(2);<br><br>&nbsp; &nbsp; // 4行目：col=3(4カラム目)から 摂氏のみ 小数1桁<br>&nbsp; &nbsp; float tc = rtc.getTemperature();<br>&nbsp; &nbsp; String line4 = "Temp: " + String(tc, 1) + " C";<br>&nbsp; &nbsp; printAtClearToEOL(3, 3, line4);<br>&nbsp; }<br><br>&nbsp; delay(40); // 秒変化を素早く検出<br>}</p><p>----------------------------------------------------------------------------------</p><p>&nbsp;</p><p>◆実行結果</p><div><div><p>&nbsp;</p><p>&nbsp;</p><div><iframe allow="fullscreen" frameborder="0" height="276" scrolling="no" src="https://static.blog-video.jp/?v=MCOvWQn0Zf18oYFpL1yNC4NW5W" width="276"></iframe><p>&nbsp;</p></div><p>&nbsp;</p></div><p>&nbsp;</p></div><p>&nbsp;</p><p>&nbsp;</p>
]]>
</description>
<link>https://ameblo.jp/shinai15/entry-12942359899.html</link>
<pubDate>Fri, 31 Oct 2025 20:05:30 +0900</pubDate>
</item>
<item>
<title>LCD 表示器（2004A) の動作確認</title>
<description>
<![CDATA[ <p><b style="font-weight:bold;"><span style="font-size:1.4em;"></span></b></p><p><b style="font-weight:bold;">LCD 表示器（2004A) の動作確認を行う</b></p><p></p><p>&nbsp;</p><p><a href="https://stat.ameba.jp/user_images/20251031/07/shinai15/43/4f/p/o0512019415706445115.png"><img alt="" height="159" src="https://stat.ameba.jp/user_images/20251031/07/shinai15/43/4f/p/o0512019415706445115.png" width="420"></a>　　　<a href="https://stat.ameba.jp/user_images/20251031/07/shinai15/2e/ac/p/o0350023415706445119.png"><img alt="" height="234" src="https://stat.ameba.jp/user_images/20251031/07/shinai15/2e/ac/p/o0350023415706445119.png" width="350"></a>　パラレル/I2C変換モジュール</p><p>&nbsp;</p><p>&nbsp;</p><p>◆主な仕様と特徴</p><p>&nbsp;</p><p>　・<a href="https://aitendo3.sakura.ne.jp/aitendo_data/product_img/ic/interface/HLF8574T/HLF8574T.pdf">HLF8574T</a>が搭載されたパラレル/I2C変換モジュール付き</p><p>　・20文字x4行液晶モジュール</p><p>　・接続インターフェース：I2C</p><p>　・2.54mmピッチ4Pピンヘッダ</p><p>　・バックライト付き</p><p>　・動作電源：5V</p><p>&nbsp;</p><p>◆回路接続</p><p>&nbsp;</p><p>　・使用するピンは <b style="font-weight:bold;">VCC、GND、SDL、SCL</b>&nbsp;の４本です。</p><p>　・Raspbery Pi との接続は上記４本のみ。</p><p>　　</p><p>◆ テストプログラム</p><p>&nbsp;</p><p>　・Bill Perry さんの <strong data-end="110" data-start="99">hd44780</strong> ライブラリを使用しました。</p><p>　　（Arduino IDEで&nbsp;<strong data-end="249" data-start="222">“hd44780 by Bill Perry”</strong> をインストール）</p><p>　・別の“LiquidCrystal_I2C” ライブラリとの競合？でコンパイルエラーが</p><p>　　出たので LiquidCrystal_I2C は削除</p><p>&nbsp;</p><p>　・テストプログラムの内容は&nbsp;<code data-end="1400" data-start="1384">hd44780_I2Cexp</code> で初期化し、</p><p>　　４行すべてに文字を出力して、バックライトON/OFFを行う。</p><p>---------------------------------------------------------------------------------------------</p><p>#include &lt;Wire.h&gt;<br>#include &lt;hd44780.h&gt;<br>#include &lt;hd44780ioClass/hd44780_I2Cexp.h&gt;<br><br>#define SDA_PIN 16<br>#define SCL_PIN 17<br>#define LCD_COLS 20<br>#define LCD_ROWS 4<br><br>hd44780_I2Cexp lcd;<br><br>void setup() {<br>&nbsp; Wire.setSDA(SDA_PIN);<br>&nbsp; Wire.setSCL(SCL_PIN);<br>&nbsp; Wire.begin();<br><br>&nbsp; delay(100); // 電源安定待ち<br>&nbsp; int status = lcd.begin(LCD_COLS, LCD_ROWS);<br>&nbsp; if (status) {<br>&nbsp; &nbsp; // 初期化失敗時はここに来る（配線や電源見直し）<br>&nbsp; &nbsp; for(;;);<br>&nbsp; }<br><br>&nbsp; lcd.clear();<br>&nbsp; lcd.backlight(); &nbsp; // バックライトON（反応しなければI2C未接続の可能性）<br>&nbsp; lcd.noCursor();<br>&nbsp; lcd.noBlink();<br><br>&nbsp; lcd.setCursor(0,0); lcd.print("HD44780 I2C TEST");<br>&nbsp; lcd.setCursor(0,1); lcd.print("Line2: 0123456789012345");<br>&nbsp; lcd.setCursor(0,2); lcd.print("Line3: abcdefghijklmnop");<br>&nbsp; lcd.setCursor(0,3); lcd.print("Line4: counter");<br><br>&nbsp; delay(1500);<br>}<br><br>void loop() {<br>&nbsp; static unsigned long t0 = millis();<br>&nbsp; static int cnt = 0;<br><br>&nbsp; // 4行目右側にカウンタ上書き（残像消し）<br>&nbsp; lcd.setCursor(15, 3);<br>&nbsp; lcd.print(" &nbsp; &nbsp;");<br>&nbsp; lcd.setCursor(15, 3);<br>&nbsp; lcd.print(cnt++);<br><br>&nbsp; // 2秒ごとにバックライトをトグル<br>&nbsp; if (millis() - t0 &gt; 2000) {<br>&nbsp; &nbsp; static bool on = true;<br>&nbsp; &nbsp; on ? lcd.noBacklight() : lcd.backlight();<br>&nbsp; &nbsp; on = !on;<br>&nbsp; &nbsp; t0 = millis();<br>&nbsp; }<br>&nbsp; delay(250);<br>}</p><p>----------------------------------------------------------------------------------</p><p>&nbsp;</p><p>◆実行結果</p><div><p><br></p><p></p><div><iframe src="https://static.blog-video.jp/?v=MCjQHf2tI063qtRh1Z6wMgXMEz" width="276" height="276" frameborder="0" scrolling="no" allow="fullscreen"></iframe><p><br></p></div><p>&nbsp;</p></div><p>&nbsp;</p><p>&nbsp;</p>
]]>
</description>
<link>https://ameblo.jp/shinai15/entry-12942192979.html</link>
<pubDate>Fri, 31 Oct 2025 07:10:12 +0900</pubDate>
</item>
<item>
<title>Arduino IDE 書き込みエラー</title>
<description>
<![CDATA[ <p>Arduino IDE 書き込みエラーで書き込みができない</p><p>&nbsp;</p><p>🔳 Raspbery Pi Pico に Arduino IDE からプログラムを書き込みすると、</p><p>　 出力に　"Failed uploading: uploading error: exit Status 1" と表示される。</p><p>&nbsp;</p><p>&nbsp;　◆解決策</p><p>　　１．Raspbery Pi Pico からUSBを抜く</p><p>　　２．<b style="font-weight:bold;">BOOTSELボタン</b>を押しながらUSBを接続（数秒押したまま）</p><p>　　３．ツール &gt; ポートに <b style="font-weight:bold;">UF2_Board </b>が表示されるので選択</p><p>　　４．スケッチ &gt; 書き込みで書き込み成功</p><p>　　５．ツール &gt; ポートには本来のCOMポートが表示されるので選択</p><p>&nbsp;</p><p>🔳&nbsp;プログラム中にシリアルポートを使っている場合には上記の方法でも</p><p>　　次のエラーメッセージが表示されて解決できない場合がある。</p><p>　　"Port monitor error: command 'open'failed：<b style="font-weight:bold;">Serial port busy</b>.&nbsp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Could not connect to COM10 serial port"</p><p>&nbsp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp;詳細のメッセージは次のような詳細のメッセージも表示される。</p><p>　&nbsp; "Resetting COM10 Converting to uf2, output size: 781312,&nbsp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; start address: 0x2000 Scanning for RP2040 devices <b style="font-weight:bold;">No drive to deploy</b>.&nbsp;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; Failed uploading: uploading error: exit status 1</p><p>&nbsp;</p><p>&nbsp; &nbsp; &nbsp; ChatGPT によると、</p><p>　&nbsp; "<b style="font-weight:bold;">No drive to deploy"</b>エラーの意味は「UF2を書き込むための<b style="font-weight:bold;">“RPI-RP2”</b></p><p><b style="font-weight:bold;">&nbsp; &nbsp; &nbsp; ドライブ</b>が見つからない」ということで、RP2040系は書き込み時は</p><p><b style="font-weight:bold;">　&nbsp; UF2のUSBメモリ(RPI-RP2)</b>が必要になります。</p><p>&nbsp; &nbsp; &nbsp;&nbsp;</p><p>&nbsp; &nbsp; ◆解決策</p><p>　　１．Raspbery Pi Pico からUSBを抜く</p><p>　　２．<b style="font-weight:bold;">BOOTSELボタン</b>を押しながらUSBを接続（数秒押したまま）</p><p>　　３．エクスプローラに <strong data-end="262" data-start="251">RPI-RP2</strong> というリムーバブルドライブが出るか確認</p><p>　　　・出ればOK。　Arduino IDEでそのまま書き込みを実行</p><p data-end="423" data-start="334">　　　・それでも失敗するなら、<strong data-end="374" data-start="345"><code data-end="372" data-start="347">スケッチ &gt; コンパイル済みバイナリを</code></strong></p><p data-end="423" data-start="334"><strong data-end="374" data-start="345"><code data-end="372" data-start="347">　　　　エクスポート でできた</code></strong>&nbsp;<strong data-end="391" data-start="383">.uf2</strong> を、RPI-RP2ドライブへ<strong data-end="419" data-start="406">ドラッグ＆ドロップ</strong></p><p data-end="423" data-start="334">　　　・.uf2 ファイルはプロジェクトディレクトリ配下（検索で見つける）</p><p data-end="423" data-start="334">　　　・ドライブが自動で消え、Picoが再起動します（正常）</p><p data-end="423" data-start="334">&nbsp;</p><p>&nbsp;</p>
]]>
</description>
<link>https://ameblo.jp/shinai15/entry-12941659360.html</link>
<pubDate>Tue, 28 Oct 2025 13:44:09 +0900</pubDate>
</item>
<item>
<title>DFPlayer mini (DFR0229) の動作確認</title>
<description>
<![CDATA[ <p>音楽再生モジュール <b style="font-weight:bold;">DFPlayer mini (DFR0229)&nbsp;</b>の動作確認を行う</p><p>&nbsp;</p><p>　　　<a href="https://stat.ameba.jp/user_images/20251027/08/shinai15/c2/8c/j/o0308027815704032253.jpg"><img alt="" height="278" src="https://stat.ameba.jp/user_images/20251027/08/shinai15/c2/8c/j/o0308027815704032253.jpg" width="308"></a></p><p>&nbsp;</p><p>◆主な仕様と特徴</p><p><a href="https://stat.ameba.jp/user_images/20251027/08/shinai15/94/75/p/o0641037915704032250.png"><img alt="" height="248" src="https://stat.ameba.jp/user_images/20251027/08/shinai15/94/75/p/o0641037915704032250.png" width="420"></a></p><p><a href="https://stat.ameba.jp/user_images/20251027/08/shinai15/2b/9e/p/o0697058515704032666.png"><img alt="" height="353" src="https://stat.ameba.jp/user_images/20251027/08/shinai15/2b/9e/p/o0697058515704032666.png" width="420"></a></p><p>&nbsp;</p><p>◆制御ピン接続</p><p>&nbsp;</p><p>　・使用するピンは <b style="font-weight:bold;">VCC、RX、TX、DAC_R、DAC_ｌ、GND</b> の6本です。</p><p>　・Speaker を直接つなぐこともできますが出力が3Wということなので、</p><p>　　別途アンプ（<b style="font-weight:bold;">GF1002</b>) に接続することにします。</p><p>&nbsp;</p><p><a href="https://stat.ameba.jp/user_images/20251027/09/shinai15/0d/68/p/o0209013915704054051.png"><img alt="" height="139" src="https://stat.ameba.jp/user_images/20251027/09/shinai15/0d/68/p/o0209013915704054051.png" width="209"></a><a href="https://stat.ameba.jp/user_images/20251027/09/shinai15/ff/75/j/o0522057615704055307.jpg"><img alt="" contenteditable="inherit" height="243" src="https://stat.ameba.jp/user_images/20251027/09/shinai15/ff/75/j/o0522057615704055307.jpg" width="220"></a></p><p>　　　</p><p>◆再生音楽の準備</p><p>&nbsp;</p><p>　・ DFPlayer に入れるSDカードに好みの楽曲を書き込みます。</p><p>　　ー 楽曲ファイルは <b style="font-weight:bold;">mp3</b> フォーマットで用意します。</p><p>　　ー ルートディレクトリに<b style="font-weight:bold;"> mp3 フォルダ</b>を作成します。</p><p>　　ー mp3 フォルダの下に楽曲ファイルを入れます。</p><p>　　ー楽曲ファイルの名前の頭に４桁の数値を入れます。</p><p>&nbsp;</p><p>◆ 回路接続</p><p>&nbsp;</p><p>&nbsp;　　<a href="https://stat.ameba.jp/user_images/20251027/10/shinai15/f4/62/p/o1216088915704063726.png"><img alt="" height="307" src="https://stat.ameba.jp/user_images/20251027/10/shinai15/f4/62/p/o1216088915704063726.png" width="420"></a></p><p>&nbsp;</p><p>◆ DFPlayer の制御プログラム</p><p>&nbsp;</p><p>　・ライブラリは <b style="font-weight:bold;">DFRobotDFPlayerMini </b>を使用します。</p><p>　　ー"https://github.com/DFRobot/DFRobotDFPlayerMini"</p><p>　　ー上記から .zip ファイルをダウンロードし&nbsp;Arduino IDE にインストールする。</p><p>&nbsp;</p><p>◆ テストプログラム（１）</p><p>&nbsp;</p><p>　・available() 関数はイベントを取得する関数</p><p>　・readType() が DFPlayerPlayFinished のとき、</p><p>　　その曲番号が read() で返される。</p><p>　・これにより曲の最後から次の曲までがスムースにつなげられる。</p><p>　・テストでは５曲をSDカードに入れて動作を確認</p><p>-------------------------------------------------------------------------------------------</p><p>#include &lt;SoftwareSerial.h&gt;<br>#include &lt;DFRobotDFPlayerMini.h&gt;<br>SoftwareSerial mySerial(20, 21); // RX=20, TX=21<br>DFRobotDFPlayerMini myDFPlayer;<br><br>void setup() {<br>&nbsp; Serial.begin(9600);<br>&nbsp; mySerial.begin(9600);<br>&nbsp; Serial.println();<br>&nbsp; Serial.println("=== DFPlayer Mini 初期化中 ===");<br><br>&nbsp; if (!myDFPlayer.begin(mySerial)) {<br>&nbsp; &nbsp; Serial.println("DFPlayer Mini が見つかりません。配線またはSDカードを確認してください。");<br>&nbsp; &nbsp; while (true); // 停止<br>&nbsp; }<br>&nbsp; Serial.println("DFPlayer Mini 検出成功！");<br>&nbsp; myDFPlayer.volume(20);&nbsp; // 音量設定（0～30）<br><br>&nbsp; // 曲を順に再生（1～5）<br>&nbsp; for (int track = 1; track &lt;= 5; track++) {<br>&nbsp; &nbsp; Serial.print("♪ 曲 ");<br>&nbsp; &nbsp; Serial.print(track);<br>&nbsp; &nbsp; Serial.println(" を再生します。");<br>&nbsp; &nbsp; myDFPlayer.play(track); // 曲を再生<br><br>&nbsp; &nbsp; // 再生完了イベントを待つ<br>&nbsp; &nbsp; bool finished = false;<br>&nbsp; &nbsp; while (!finished) {<br>&nbsp; &nbsp; &nbsp; if (myDFPlayer.available()) {<br>&nbsp; &nbsp; &nbsp; &nbsp; uint8_t type = myDFPlayer.readType();<br>&nbsp; &nbsp; &nbsp; &nbsp; int value = myDFPlayer.read();<br><br>&nbsp; &nbsp; &nbsp; &nbsp; if (type == DFPlayerPlayFinished) {<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.print("曲 ");<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.print(value);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.println(" の再生が完了しました。");<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; finished = true;<br>&nbsp; &nbsp; &nbsp; &nbsp; } else if (type == DFPlayerError) {<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.print("エラー発生: ");<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.println(value);<br>&nbsp; &nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp; // 5秒間の休憩<br>&nbsp; &nbsp; Serial.println("5秒間休憩中...");<br>&nbsp; &nbsp; delay(5000);<br>&nbsp; }<br>&nbsp; Serial.println("=== 全5曲の再生が完了しました。プログラムを終了します。 ===");<br>}<br>void loop() {<br>&nbsp; // 何もしない<br>}<br>------------------------------------------------------------------------------</p><p>&nbsp;</p><p>◆ テストプログラム（２）</p><p>&nbsp;</p><p>　・曲と曲の間隔を delay() 関数で制御するプログラム</p><p>&nbsp;</p><p>#include "Arduino.h"</p><p>#include "DFRobotDFPlayerMini.h"</p><p>#include &lt;SoftwareSerial.h&gt;</p><p>SoftwareSerial softSerial(20,21);</p><p>#define FPSerial softSerial</p><p>&nbsp;</p><p>DFRobotDFPlayerMini myDFPlayer;</p><p>void printDetail(uint8_t type, int value);</p><p>&nbsp;</p><p>void setup()</p><p>{</p><p>&nbsp; FPSerial.begin(9600);</p><p>&nbsp; Serial.begin(115200);</p><p>&nbsp; Serial.println();</p><p>&nbsp; Serial.println(F("DFRobot DFPlayer Mini Demo"));</p><p>&nbsp; Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));</p><p>&nbsp;</p><p>&nbsp; if (!myDFPlayer.begin(FPSerial, /*isACK = */true, /*doReset = */true)) {&nbsp;</p><p>　 // mp3とのコミュニケーションをシリアルで行う</p><p>&nbsp; &nbsp; Serial.println(F("Unable to begin:"));</p><p>&nbsp; &nbsp; Serial.println(F("1.Please recheck the connection!"));</p><p>&nbsp; &nbsp; Serial.println(F("2.Please insert the SD card!"));</p><p>&nbsp; &nbsp; while(true){</p><p>&nbsp; &nbsp; &nbsp; delay(0); // Code to compatible with ESP8266 watch dog.</p><p>&nbsp; &nbsp; }</p><p>&nbsp; }</p><p>&nbsp; Serial.println(F("DFPlayer Mini online."));</p><p>&nbsp;</p><p>&nbsp; myDFPlayer.volume(20);&nbsp;&nbsp;</p><p>&nbsp; myDFPlayer.play(1);&nbsp; 　//最初のmp3を再生</p><p>&nbsp; delay(2000);</p><p>}</p><p>&nbsp;</p><p>void loop()</p><p>{</p><p>&nbsp; static unsigned long timer = millis();</p><p>&nbsp; if (millis() - timer &gt; 10000) {</p><p>&nbsp; &nbsp; timer = millis();</p><p>&nbsp; &nbsp; myDFPlayer.next();&nbsp; 　//10秒ごとに次の曲を再生する</p><p>&nbsp; }</p><p>&nbsp; if (myDFPlayer.available()) {</p><p>&nbsp; &nbsp; printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states.</p><p>&nbsp; }</p><p>}</p><p>/*エラー情報を出力</p><p>void printDetail(uint8_t type, int value){</p><p>&nbsp; switch (type) {</p><p>&nbsp; &nbsp; case TimeOut:</p><p>&nbsp; &nbsp; &nbsp; Serial.println(F("Time Out!"));</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case WrongStack:</p><p>&nbsp; &nbsp; &nbsp; Serial.println(F("Stack Wrong!"));</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case DFPlayerCardInserted:</p><p>&nbsp; &nbsp; &nbsp; Serial.println(F("Card Inserted!"));</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case DFPlayerCardRemoved:</p><p>&nbsp; &nbsp; &nbsp; Serial.println(F("Card Removed!"));</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case DFPlayerCardOnline:</p><p>&nbsp; &nbsp; &nbsp; Serial.println(F("Card Online!"));</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case DFPlayerUSBInserted:</p><p>&nbsp; &nbsp; &nbsp; Serial.println("USB Inserted!");</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case DFPlayerUSBRemoved:</p><p>&nbsp; &nbsp; &nbsp; Serial.println("USB Removed!");</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case DFPlayerPlayFinished:</p><p>&nbsp; &nbsp; &nbsp; Serial.print(F("Number:"));</p><p>&nbsp; &nbsp; &nbsp; Serial.print(value);</p><p>&nbsp; &nbsp; &nbsp; Serial.println(F(" Play Finished!"));</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; case DFPlayerError:</p><p>&nbsp; &nbsp; &nbsp; Serial.print(F("DFPlayerError:"));</p><p>&nbsp; &nbsp; &nbsp; switch (value) {</p><p>&nbsp; &nbsp; &nbsp; &nbsp; case Busy:</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.println(F("Card not found"));</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; case Sleeping:</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.println(F("Sleeping"));</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; case SerialWrongStack:</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.println(F("Get Wrong Stack"));</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; case CheckSumNotMatch:</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.println(F("Check Sum Not Match"));</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; case FileIndexOut:</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.println(F("File Index Out of Bound"));</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; case FileMismatch:</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.println(F("Cannot Find File"));</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; case Advertise:</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Serial.println(F("In Advertise"));</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; &nbsp; &nbsp; default:</p><p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; &nbsp; }</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; &nbsp; default:</p><p>&nbsp; &nbsp; &nbsp; break;</p><p>&nbsp; }</p><p>*/　</p><p>}</p><p>&nbsp;</p><p>◆実行結果</p><p>&nbsp;</p><div><p><a href="https://stat.ameba.jp/user_images/20251027/13/shinai15/a4/d8/j/o2711175715704129407.jpg"><img alt="" contenteditable="inherit" height="227" src="https://stat.ameba.jp/user_images/20251027/13/shinai15/a4/d8/j/o2711175715704129407.jpg" width="350"></a></p><p>&nbsp;</p><p>最初の数秒のみ動画でアップ</p><p><iframe allow="fullscreen" frameborder="0" height="276" scrolling="no" src="https://static.blog-video.jp/?v=MCF1zEtRkkV6TX1fAcfGJiDOiJ" width="276"></iframe></p></div><p>&nbsp;</p><p>&nbsp;</p>
]]>
</description>
<link>https://ameblo.jp/shinai15/entry-12941327824.html</link>
<pubDate>Mon, 27 Oct 2025 08:09:05 +0900</pubDate>
</item>
<item>
<title>鳩時計の制作準備</title>
<description>
<![CDATA[ <p><b style="font-weight:bold;"><span style="font-size:1.96em;">鳩時計の制作</span></b><br><br>作成する鳩時計の仕様検討<br><br>■時刻の取得、設定</p><p>　インターネットから時刻情報を取得、または<a href="https://ameblo.jp/shinai15/entry-12942359899.html" rel="noopener noreferrer" target="_blank">クロックモジュールを使う</a>。</p><p><br>■時刻の表示</p><p>　ステッピングモータと歯車で時計の針を動かす、</p><p>　または<a href="https://ameblo.jp/shinai15/entry-12942192979.html" rel="noopener noreferrer" target="_blank">LCD(2004A)にデジタル表示する</a>。</p><p><br>■時刻発声と鳩の動きの動作確認</p><p>　音声発声は<a href="https://ameblo.jp/shinai15/entry-12887950688.html" rel="noopener noreferrer" target="_blank">ロボット制作に使用したATP3012 IC</a>を使用する。</p><p>　時刻によって<a href="https://ameblo.jp/shinai15/entry-12948004487.html" rel="noopener noreferrer" target="_blank">音声で時報を流すかまたは音楽を流す</a>。</p><p>　時報と同時に鳩を窓から出し入れする。</p><p><br>■アラーム機能を付ける</p><p>　アラーム時刻の設定は<a href="https://ameblo.jp/shinai15/entry-12942374350.html" rel="noopener noreferrer" target="_blank">4つのボタンＳＷを使って設定</a>する。</p><p>　アラームがヒットしたら音声又は音楽で知らせる。</p><p><br>■音楽はあらかじめ .MP3フォーマットで用意して、</p><p>　SDカードに入れたものを <a href="https://ameblo.jp/shinai15/entry-12941327824.html" rel="noopener noreferrer" target="_blank">DFPlayer(DFR0229) で再生</a>する</p><p>　<br>■各機能は複数の Raspbery Pi Pico (W) で制御する</p><p>　時刻の表示、時刻の設定、アラームの設定、LCD表示は１台目、</p><p>　音声の発生、音楽の再生、鳩の動き、ドアの開閉などの機構制御は</p><p>　２台目のPicoで行う。</p><p>　<a href="https://ameblo.jp/shinai15/entry-12947305608.html" rel="noopener noreferrer" target="_blank">Picoどうしの連携はシリアル接続で</a>行う。</p><p><br>■鳩を窓から出し入れする</p><p>　サーボモータで動きをコントロールする。</p><p>&nbsp;</p><p>■鳩時計をリモコンで操作できるようにする</p><p>　時刻の設定、アラームの設定、音楽の再生をリモコンで行う。</p><p>　リモコンは<a href="https://ameblo.jp/shinai15/entry-12950004107.html" rel="noopener noreferrer" target="_blank">無線通信機能</a>を使って行う。</p><p><br>■外観はプラスチックで構成し３Dプリンター（Creality）で作成</p><p>■機構の設計はFusion360 を使用</p><p>■電気回路とプリント基板の設計はKiCad を使用</p><p>■プリント基板の作成は<a href="https://jlcpcb.com/JPV" rel="noopener noreferrer" target="_blank">JLCPCB に外注</a>する。<br>■プログラムの開発は「Arduino IDE」を使用</p><p>■プログラムの検討、コーディングは ChatGPT を使用<br>■デバックは<a href="https://ameblo.jp/shinai15/entry-12887633198.html" rel="noopener noreferrer" target="_blank">OLED ディスプレイ SSD1306</a> とシリアルモニタを随時使用する。</p><p>&nbsp;</p><p>&nbsp;</p><p>&nbsp;</p><p>&nbsp;</p>
]]>
</description>
<link>https://ameblo.jp/shinai15/entry-12939368642.html</link>
<pubDate>Sat, 18 Oct 2025 20:22:14 +0900</pubDate>
</item>
<item>
<title>(13) ロボットの外観の構成とパーツの取り付け</title>
<description>
<![CDATA[ <p>(13) ロボットの外観の構成とパーツの取り付け</p><p>&nbsp;</p><p>◆ ロボットの外観の作成方法について</p><p>&nbsp;</p><p>　・デザインは AUTODESK社の <b style="font-weight:bold;">Fusion 360 CAD</b> を使って行う。</p><p>　・Fusion から設計したデータを <b style="font-weight:bold;">.stl </b>ファイルとして出力する。</p><p>　・出力した .stl ファイルは<b style="font-weight:bold;">３Dプリンター（Creality）</b>でプリントする。</p><p>&nbsp;</p><div>◆ ロボットの体の分割と実装するパーツについて</div><div>&nbsp;</div><div>　🔳体は大きく分けて顔、胴体、腕、スカート、台座に分割する。</div><div>&nbsp;</div><div>　(1) 顔の部分はさらに3分割<br><a href="https://stat.ameba.jp/user_images/20250807/18/shinai15/dc/3d/p/o0300030015647845252.png"><img alt="" height="300" src="https://stat.ameba.jp/user_images/20250807/18/shinai15/dc/3d/p/o0300030015647845252.png" width="300"></a><a href="https://stat.ameba.jp/user_images/20250807/17/shinai15/6d/69/p/o0300030015647844348.png"><img alt="" height="300" src="https://stat.ameba.jp/user_images/20250807/17/shinai15/6d/69/p/o0300030015647844348.png" width="300"></a></div><div><div>　　・顔の前部分にはLED（音声の発生時に点滅）を取り付ける。</div><div>　　・顔の中心部分には首を付けて胴体と接続する。</div><div>　　・顔の後部分にはスピーカとアンプを取り付ける。</div></div><div><p>&nbsp;</p><div>　<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/9e/4d/j/o0991141315647890757.jpg"><img alt="" border="0" contenteditable="inherit" height="285" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/9e/4d/j/o0991141315647890757.jpg" width="200"></a></div><p>&nbsp;</p></div><div>　(2) 胴体はさらに2つに分割</div><div><a href="https://stat.ameba.jp/user_images/20250807/18/shinai15/20/9a/p/o0300030015647846721.png"><img alt="" contenteditable="inherit" height="300" src="https://stat.ameba.jp/user_images/20250807/18/shinai15/20/9a/p/o0300030015647846721.png" width="300"></a><a href="https://stat.ameba.jp/user_images/20250807/18/shinai15/6a/39/p/o0300030015647847645.png"><img alt="" contenteditable="inherit" height="300" src="https://stat.ameba.jp/user_images/20250807/18/shinai15/6a/39/p/o0300030015647847645.png" width="300"></a></div><div>　　・胴体の前部分には OLED ディスプレイ (<b style="font-weight:bold;">SSD1306</b>) を取り付ける。</div><div><p>&nbsp;</p><div>&nbsp;&nbsp;<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/c5/03/j/o1080132615647890762.jpg"><img alt="" border="0" contenteditable="inherit" height="320" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/c5/03/j/o1080132615647890762.jpg" width="261"></a>&nbsp; &nbsp;<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/83/ee/j/o1080143915647890765.jpg"><img alt="" border="0" contenteditable="inherit" height="320" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/83/ee/j/o1080143915647890765.jpg" width="240"></a></div><p>&nbsp;</p></div><div>　　・胴体の後部分には腕を駆動するサーボ (<b style="font-weight:bold;">SG90</b>) を取り付ける。</div><div>　　・胴体の後部分には首を駆動するサーボ (<b style="font-weight:bold;">SG92R</b>) を取り付ける。</div><div><p>&nbsp;</p><p>&nbsp;</p></div><div>&nbsp;</div><div>　(3) 腕はさらに2つに分割</div><div><a href="https://stat.ameba.jp/user_images/20250807/18/shinai15/3f/2f/p/o0300030015647848806.png"><img alt="" height="300" src="https://stat.ameba.jp/user_images/20250807/18/shinai15/3f/2f/p/o0300030015647848806.png" width="300"></a><a href="https://stat.ameba.jp/user_images/20250807/18/shinai15/42/18/p/o0300030015647848808.png"><img alt="" height="300" src="https://stat.ameba.jp/user_images/20250807/18/shinai15/42/18/p/o0300030015647848808.png" width="300"></a></div><div>　　・上腕は胴体のサーボに接続する。</div><div>　　・前腕の肘には上腕とつなぐサーボを取り付ける</div><div><p>&nbsp;</p><div>&nbsp;&nbsp;<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/71/61/j/o1080062115647890769.jpg"><img alt="" border="0" height="230" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/71/61/j/o1080062115647890769.jpg" width="400"></a></div><p>&nbsp;</p></div><div>　(4) スカート部分はさらに４つに分割</div><div><a href="https://stat.ameba.jp/user_images/20250807/18/shinai15/a5/66/p/o0300030015647853288.png"><img alt="" height="300" src="https://stat.ameba.jp/user_images/20250807/18/shinai15/a5/66/p/o0300030015647853288.png" width="300"></a><a href="https://stat.ameba.jp/user_images/20250807/18/shinai15/2d/c9/p/o0300030015647853290.png"><img alt="" height="300" src="https://stat.ameba.jp/user_images/20250807/18/shinai15/2d/c9/p/o0300030015647853290.png" width="300"></a></div><div>　　・前の部分にモーションセンサを取り付ける。</div><div><p>&nbsp;</p><div>&nbsp; &nbsp;<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/1e/eb/j/o1080254815647890772.jpg"><img alt="" border="0" contenteditable="inherit" height="472" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/1e/eb/j/o1080254815647890772.jpg" width="200"></a>&nbsp;　<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/9a/79/j/o1080228515647890776.jpg"><img alt="" border="0" contenteditable="inherit" height="470" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/9a/79/j/o1080228515647890776.jpg" width="222"></a></div><p>&nbsp;</p></div><div>　　・前の部分に超音波センサのための穴をあける。</div><div>　　・後ろの部分にテールのサーボを取り付ける。</div><div>　　・後ろの部分に電源SWを取り付ける。</div><div><p>　　・後ろの部分にプログラムロードの為のUSBソケットを付ける。</p></div><div>　　・左側にモード切替SWを付ける。　　</div><div><p>&nbsp;</p><div>&nbsp; &nbsp;<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/e9/08/j/o1080091415647890780.jpg"><img alt="" border="0" contenteditable="inherit" height="212" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/e9/08/j/o1080091415647890780.jpg" width="250"></a></div><p>&nbsp;</p></div><div>　　・右側に特に取り付けパーツはなし。</div><div>&nbsp;</div><div>　(5) 台座部分には残りのパーツを取り付ける</div><div><a href="https://stat.ameba.jp/user_images/20250807/18/shinai15/25/bd/p/o0400025015647854889.png"><img alt="" height="250" src="https://stat.ameba.jp/user_images/20250807/18/shinai15/25/bd/p/o0400025015647854889.png" width="400"></a><a href="https://stat.ameba.jp/user_images/20250809/15/shinai15/42/65/j/o2671291915648794458.jpg"><img alt="" contenteditable="inherit" height="327" src="https://stat.ameba.jp/user_images/20250809/15/shinai15/42/65/j/o2671291915648794458.jpg" width="300"></a></div><div>　　・プリント基板を取り付ける。</div><div><p>&nbsp;</p><div>&nbsp;<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/40/1b/j/o1080101915647890788.jpg"><img alt="" border="0" contenteditable="inherit" height="270" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/40/1b/j/o1080101915647890788.jpg" width="287"></a>&nbsp;&nbsp;<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/08/e4/j/o1080126015647890808.jpg"><img alt="" border="0" contenteditable="inherit" height="270" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/08/e4/j/o1080126015647890808.jpg" width="231"></a></div><p>&nbsp;</p></div><div>　　・バッテリを収納するパーツを取り付ける。</div><div>　　・超音波センサーを取り付ける。</div><div>　　・DC モーターを取り付ける。</div><div>　　・DCモータを制御する基板を取り付ける。</div><div>　　・タイヤを取り付ける。</div><div>　　・姿勢を安定させるベアリングを取り付ける。</div><div><p>&nbsp;</p><div>&nbsp; &nbsp;<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/72/ed/j/o1080104315647890816.jpg"><img alt="" border="0" contenteditable="inherit" height="250" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/72/ed/j/o1080104315647890816.jpg" width="259">&nbsp; &nbsp;&nbsp;</a><a href="https://stat.ameba.jp/user_images/20250809/15/shinai15/cb/13/j/o2525248915648794436.jpg"><img alt="" contenteditable="inherit" height="247" src="https://stat.ameba.jp/user_images/20250809/15/shinai15/cb/13/j/o2525248915648794436.jpg" width="250"></a></div><p>&nbsp;</p></div><div>&nbsp;</div><div>(6) 全体像</div><div>&nbsp;</div><div>　<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/c7/85/j/o2027364615647896193.jpg"><img alt="" contenteditable="inherit" height="500" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/c7/85/j/o2027364615647896193.jpg" width="278"></a>　　<a href="https://stat.ameba.jp/user_images/20250807/20/shinai15/95/b2/j/o1801364615647897499.jpg"><img alt="" contenteditable="inherit" height="500" src="https://stat.ameba.jp/user_images/20250807/20/shinai15/95/b2/j/o1801364615647897499.jpg" width="247"></a></div>
]]>
</description>
<link>https://ameblo.jp/shinai15/entry-12921375817.html</link>
<pubDate>Thu, 07 Aug 2025 11:21:58 +0900</pubDate>
</item>
</channel>
</rss>
