前の記事の続き。
この記事では、USB接続の有無でCPUクロックの精度が変わることを確認した。これの補足というか追加試験を実施。
その記事の実験の前半で使っていたPWMで1MHzを出力しつつLEDの点滅に、LCDにカウントアップ表示を追加。これでストップウォッチでの確認をしやすくする。LCD表示なのでUSBケーブル接続も不要。ソースコードは後ほど。
早速、結果。

ストップウォッチでの計測では、やや進む程度(ストップウォッチに対して)。表示の上段が単純なカウントアップ、下段はそれをHH:MM:SS形式で表示したもの。
なお、開始時は、5 4 3 2 1 0 1 2 3 …のように、5からカウントダウンして0からカウントアップする。これでストップウォッチのスタートタイミングを取りやすい。
続いて1MHzの出力を周波数カウンタで測定。USBケーブルは非接続。


結構ふらつくが、まぁ、1000.2kHz程度かな?
念のため、USBケーブルを接続した状態。

予想通り、1000.008kHzくらい。やはり、USBケーブルをつながないと周波数が上がる。+0.2%くらい。
念のため、CPUクロック出力に入れ替えて測定(前の実験のソフト)。48MHz出力。

上はUSBケーブル接続状態。想定通りの周波数。
続いて、USBケーブ非接続。

こちらも前回の実験と同様の結果。
ついでに、LOCO(RTCに使われるクロック)も、USBケーブル接続の有無で測定(前回は測定しなかったので)。
USBケーブル接続。

USBケーブル非接続。

こちらはほぼ変化なし。やはり、LOCOはUSBクロックでの補正はしていないようだ。
最後にソースコード。生成AIに指示して作らせた。
/*
Arduino UNO R4用
- D11ピンからPWM信号を出力
- I2C接続の1602 LCDにカウンタ値とHH:MM:SS形式を表示
- D8-GND間のボタンスイッチでカウント開始・停止・リセット制御
- カウント中は内蔵LEDを2:8の比率で点滅
- 起動時はLCDに"Ready"を表示し、ボタン押下でカウント開始
- カウント開始時は5から0までダウンカウント、その後はカウントアップ
*/
#include "pwm.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define PWM_FREQ 1000000.0f // PWMの出力周波数(1MHz)
#define LED 13 // 内蔵LEDピン番号
#define PWM_PIN 11 // PWM出力ピン番号
#define BUTTON_PIN 8 // ボタン入力ピン番号
#define DEBOUNCE_DELAY 10 // デバウンス対策用待ち時間(ms)
#define START_DOWNCOUNT 5 // ダウンカウント開始値
LiquidCrystal_I2C lcd(0x27, 16, 2); // LCDのI2Cアドレス(必要に応じて変更)
PwmOut pwm(PWM_PIN);
// 動作状態:READY(待機), RUNNING(カウント中), STOPPED(一時停止)
enum State { READY, RUNNING, STOPPED };
State state = READY; // 初期状態はREADY
unsigned long lastBlinkMillis = 0; // 最後にLEDを切り替えた時刻
int counter = 0; // 現在のカウント値(アップカウント時は増加、ダウン時は減少)
bool ledOn = false; // LEDの点灯状態
bool lastButtonState = HIGH; // 前回のボタン状態
bool debounceLock = false; // デバウンス制御用フラグ
bool stopToReset = false; // STOPPED→リセットフラグ
bool isDownCount = false; // ダウンカウント中かどうか
void setup() {
pinMode(LED, OUTPUT); // 内蔵LEDを出力に設定
pinMode(BUTTON_PIN, INPUT_PULLUP); // ボタンはプルアップ入力
pwm.begin(PWM_FREQ, 50.0f); // PWM出力開始(周波数、デューティ比)
lcd.init(); // LCD初期化
lcd.backlight(); // バックライトON
// 起動時に"Ready"をLCDに表示(そのまま保持)
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Ready");
}
void loop() {
handleButton(); // ボタン入力処理
if (state == RUNNING) {
handleLedBlink(); // LED点滅・カウントアップ/ダウン処理
} else {
// カウント停止時はLED消灯
digitalWrite(LED, LOW);
ledOn = false;
}
}
// ボタン入力を監視し、状態遷移を管理
void handleButton() {
bool buttonState = digitalRead(BUTTON_PIN); // 現在のボタン状態読み取り
// デバウンス処理
if (buttonState != lastButtonState) {
delay(DEBOUNCE_DELAY);
}
// ボタンが離された瞬間(立ち上がり検出)で状態遷移
if (buttonState == HIGH && lastButtonState == LOW && !debounceLock) {
debounceLock = true; // 連続検出防止
switch (state) {
case READY:
// READY→RUNNING、ダウンカウント開始、LCD表示をカウンタに切り替え
counter = START_DOWNCOUNT;
isDownCount = true;
state = RUNNING;
lastBlinkMillis = millis();
lcd.clear(); // クリアはカウント開始時のみ
displayCounter();
stopToReset = false;
break;
case RUNNING:
state = STOPPED; // 動作中→停止
stopToReset = true; // 次回ボタンでリセット
break;
case STOPPED:
if (stopToReset) {
// 停止中に再度押されたら再びダウンカウントからRUNNING
counter = START_DOWNCOUNT;
isDownCount = true;
state = RUNNING;
lastBlinkMillis = millis();
lcd.clear(); // クリアはカウント再開時のみ
displayCounter();
stopToReset = false;
}
break;
default:
// 不定状態はREADYに戻す
state = READY;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Ready");
stopToReset = false;
break;
}
}
// ボタンが押された瞬間(立ち下がり検出)でデバウンス解除
else if (buttonState == LOW && lastButtonState == HIGH) {
debounceLock = false;
}
lastButtonState = buttonState; // 状態更新
}
// LED点滅とカウントアップ/ダウン処理
void handleLedBlink() {
unsigned long now = millis();
if (!ledOn && now - lastBlinkMillis >= 800) { // 消灯800ms後に点灯
digitalWrite(LED, HIGH);
ledOn = true;
lastBlinkMillis = now;
// LED点灯のタイミングでカウント処理
if (isDownCount) {
counter--;
if (counter < 0) {
counter = 1; // 0の次は1からアップカウント
isDownCount = false;
}
} else {
counter++;
}
displayCounter();
} else if (ledOn && now - lastBlinkMillis >= 200) { // 点灯200ms後に消灯
digitalWrite(LED, LOW);
ledOn = false;
lastBlinkMillis = now;
}
}
// LCDにカウント値と時分秒形式を表示(lcd.clear()は呼ばない)
void displayCounter() {
// 1行目:カウンタ値(そのまま表示)
lcd.setCursor(0, 0);
lcd.print(counter);
// 2行目:HH:MM:SS形式に変換して表示
int absCounter = counter;
if (absCounter < 0) absCounter = 0; // 負値は0で表示
int sec = absCounter % 60;
int min = (absCounter / 60) % 60;
int hour = absCounter / 3600;
lcd.setCursor(0, 1);
char buf[16];
sprintf(buf, "%02d:%02d:%02d", hour, min, sec);
lcd.print(buf);
}
コメント