• 車種別
  • パーツ
  • 整備手帳
  • ブログ
  • みんカラ+

まう@朱理のブログ一覧

2026年08月28日 イイね!

ジムカーナタイム測定機改6

alt

8/15の練習会でサンプル取得
有線とのタイム差ほとんどないのでいいんじゃないですか。

Posted at 2026/08/28 01:46:37 | コメント(0) | トラックバック(0) | ジムカーナ | 日記
2026年08月13日 イイね!

ジムカーナタイム測定機改5

基板に半田して完成。
夏休みの工作で取りあえず動くように箱に入れました。

Xの動画
https://x.com/i/status/2087573773310738612






子機
```cpp
#include <esp_now.h>
#include <wifi.h>
#include <esp_wifi.h>
#include <esp_timer.h>

// ==========================================
// デバッグ設定 (1にするとシリアルモニタ出力)
// ==========================================
#define DEBUG 0

#if DEBUG
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif

// ==========================================
// アンテナ制御設定 (XIAO ESP32C6 専用)
// 1: 外部アンテナ(U.FL) / 0: 内蔵アンテナ
// ※送信出力はどちらのモードでも常に最大(20dBm)です
// ==========================================
#define USE_EXTERNAL_ANTENNA 1

#define PIN_RF_PWR 3 // RFスイッチ電源制御ピン (GPIO 3)
#define PIN_RF_SEL 14 // アンテナ選択ピン (GPIO 14: HIGH=外部, LOW=内蔵)

// ==========================================
// ピン設定 (XIAO ESP32C6 の D0 ピン = GPIO 0)
// ==========================================
#define SENSOR_PIN D0
const bool SENSOR_ACTIVE_LEVEL = LOW;

// 親機(受信側)のMACアドレス
uint8_t masterAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// ==========================================
// 通信データ構造体・グローバル変数
// ==========================================
typedef struct __attribute__((packed)) {
char mode; // 'S': スタート, 'G': ゴール, 'R': リセット, 'H': ハートビート, 'A': ACK
uint64_t time; // マイクロ秒単位の経過時間
} struct_message;

struct_message sendData;
struct_message recvData;
esp_now_peer_info_t peerInfo;

enum SlaveState {
STATE_READY, // スタート待ち
STATE_RUNNING, // 計測中
STATE_GOAL // ゴール完了(リセット待ち)
};
SlaveState currentState = STATE_READY;

int64_t startTimeUs = 0;

volatile bool ackReceived = false;
unsigned long lastHeartbeatTime = 0;
const unsigned long HEARTBEAT_INTERVAL_MS = 2000;

// デバウンス・チャタリング防止用
bool lastDebouncedSensorState = true; // 安定したセンサー状態
bool rawSensorState = true; // 前回の生の入力状態
unsigned long lastStateChangeTimeMs = 0; // 状態が変化した時刻
const unsigned long DEBOUNCE_DELAY_MS = 50; // チャタリング吸収時間 (50ms)

unsigned long lastTriggerTimeMs = 0; // 車体再検知防止用 (2000ms = 2秒)
const unsigned long RE_TRIGGER_INTERVAL_MS = 2000;

// ==========================================
// アンテナ・送信出力初期化関数
// ==========================================
void setupAntenna() {
pinMode(PIN_RF_PWR, OUTPUT);
pinMode(PIN_RF_SEL, OUTPUT);

// 送信出力は内蔵・外部を問わず常に最大(20dBm = 100mW相当)に設定
WiFi.setTxPower(WIFI_POWER_20dBm);

#if USE_EXTERNAL_ANTENNA
// 外部アンテナ (U.FLコネクタ)
digitalWrite(PIN_RF_PWR, LOW); // LOWでRFスイッチ有効化 (正)
digitalWrite(PIN_RF_SEL, HIGH); // HIGHで外部アンテナ選択
#else
// 内蔵アンテナ (オンボード)
digitalWrite(PIN_RF_PWR, LOW); // LOWでRFスイッチ有効化 (正)
digitalWrite(PIN_RF_SEL, LOW); // LOWで内蔵アンテナ選択
#endif

delay(10); // 切替後の信号安定化ウェイト
}

// ==========================================
// ESP-NOW コールバック
// ==========================================
void OnDataSent(const wifi_tx_info_t *tx_info, esp_now_send_status_t status) {}

void OnDataRecv(const esp_now_recv_info_t *recv_info, const uint8_t *incomingDataRaw, int len) {
if (len != sizeof(struct_message)) return;

memcpy(&recvData, incomingDataRaw, sizeof(recvData));

// 親機からのACK受信
if (recvData.mode == 'A') {
ackReceived = true;
}
// 親機からのリセット信号受信
else if (recvData.mode == 'R') {
currentState = STATE_READY;
startTimeUs = 0;
}
}

// ACK応答付きデータ送信処理(最大10回再送)
bool sendDataWithAck(char mode, uint64_t timeVal) {
sendData.mode = mode;
sendData.time = timeVal;

for (int retry = 0; retry < 10; retry++) {
ackReceived = false;
esp_err_t result = esp_now_send(masterAddress, (uint8_t *)&sendData, sizeof(sendData));

if (result == ESP_OK) {
unsigned long waitStart = millis();
while (millis() - waitStart < 50) {
if (ackReceived) {
return true; // ACK受信成功
}
delay(1);
}
}
delay(10);
}

return false; // 再送失敗
}

// ==========================================
// Setup
// ==========================================
void setup() {
pinMode(SENSOR_PIN, (SENSOR_ACTIVE_LEVEL == LOW) ? INPUT_PULLUP : INPUT_PULLDOWN);

// Wi-Fi / アンテナ初期化
WiFi.disconnect(true);
delay(100);
WiFi.mode(WIFI_STA);

setupAntenna(); // アンテナ切替 & 出力最大化 (20dBm)

esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE);

// ESP-NOW 初期化
if (esp_now_init() != ESP_OK) {
return;
}

esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);

memcpy(peerInfo.peer_addr, masterAddress, 6);
peerInfo.channel = 1;
peerInfo.encrypt = false;
esp_now_add_peer(&peerInfo);

// ピンの初期状態を取得
bool initRead = digitalRead(SENSOR_PIN);
rawSensorState = initRead;
lastDebouncedSensorState = initRead;
}

// ==========================================
// Loop
// ==========================================
void loop() {
unsigned long currentTimeMs = millis();
int64_t currentTimeUs = esp_timer_get_time();
bool currentReading = digitalRead(SENSOR_PIN);

// ----------------------------------------
// 1. デバウンス処理(安定した入力判定)
// ----------------------------------------
// ピンの状態に変化があったらタイマーリセット
if (currentReading != rawSensorState) {
lastStateChangeTimeMs = currentTimeMs;
rawSensorState = currentReading;
}

// 指定時間(50ms)以上、状態が安定していたら確定値として採用
if ((currentTimeMs - lastStateChangeTimeMs) > DEBOUNCE_DELAY_MS) {
if (currentReading != lastDebouncedSensorState) {
lastDebouncedSensorState = currentReading;

// --- ON(通過・遮断)になった「瞬間」のみトリガー処理を実行 ---
if (lastDebouncedSensorState == SENSOR_ACTIVE_LEVEL) {

// 車体再検知防止用インターバルのチェック (2秒)
if (currentTimeMs - lastTriggerTimeMs > RE_TRIGGER_INTERVAL_MS) {

// --- READY状態: スタート計測開始 ---
if (currentState == STATE_READY) {
startTimeUs = currentTimeUs;
currentState = STATE_RUNNING;

sendDataWithAck('S', 0);
lastTriggerTimeMs = currentTimeMs;
}
// --- RUNNING状態: ゴール計測完了 ---
else if (currentState == STATE_RUNNING) {
uint64_t elapsedTimeUs = (uint64_t)(currentTimeUs - startTimeUs);
currentState = STATE_GOAL; // ゴール状態へ移行

sendDataWithAck('G', elapsedTimeUs);
lastTriggerTimeMs = currentTimeMs;
}
}
}
}
}

// ----------------------------------------
// 2. 定期生存信号 (Heartbeat) の送信
// ----------------------------------------
if (currentTimeMs - lastHeartbeatTime > HEARTBEAT_INTERVAL_MS) {
sendData.mode = 'H';
sendData.time = 0;
esp_now_send(masterAddress, (uint8_t *)&sendData, sizeof(sendData));
lastHeartbeatTime = currentTimeMs;
}
}
```

Posted at 2026/08/13 02:20:15 | コメント(0) | トラックバック(0) | ジムカーナ | 日記
2026年08月12日 イイね!

ジムカーナタイム測定機改4

プログラムはGoogle Geminiにこねくり回して貰いました。

Xの動画
https://x.com/i/status/2084622576526184793

画面レイアウト変更(大画面 0.96→2.42インチ)
BEEPを追加
BLEキーボードでスマホにタイム出力
キーボードと子機接続表示
外部アンテナ使用
その他安定化を追加







親機
```cpp
#include <esp_now.h>
#include <wifi.h>
#include <esp_wifi.h>
#include <wire.h>
#include <u8g2lib.h>
#include <hijelhid_blekeyboard.h>
#include <esp_timer.h>


// ==========================================
// デバッグ設定 (1にするとシリアルモニタ出力)
// ==========================================
#define DEBUG 0

#if DEBUG
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif

// ==========================================
// ディスプレイ設定 (U8g2)
// ==========================================
// ★2.42 / 2.54 インチ (SSD1309) の場合:
U8G2_SSD1309_128X64_NONAME0_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// ★0.96 インチ (SSD1306) で使用する場合は上の行を有効化してください:
// U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// ==========================================
// アンテナ制御設定 (XIAO ESP32C6 専用)
// 1: 外部アンテナ(U.FL) / 0: 内蔵アンテナ
// ==========================================
#define USE_EXTERNAL_ANTENNA 1

#define PIN_RF_PWR 3 // RFスイッチ電源制御ピン (GPIO 3)
#define PIN_RF_SEL 14 // アンテナ選択ピン (GPIO 14: HIGH=外部, LOW=内蔵)

// ==========================================
// ピン・ハードウェア設定
// ==========================================
#define I2C_SDA 22
#define I2C_SCL 23
#define PIN_RESET 1 // リセット / ストップボタン
#define PIN_BUZZER 18 // 圧電ブザー接続ピン (D10 / GPIO 18)

const bool ACTIVE_LEVEL = LOW;

// BLEキーボード
HijelHID_BLEKeyboard keyboard("Gymkhana_Android_KB", "Seeed", 100);

// ==========================================
// 定数設定(タイムアウト・間隔)
// ==========================================
const unsigned long SENSOR_TIMEOUT_MS = 6000; // 子機切断とみなす時間 (ms)
const unsigned long BTN_LONG_PRESS_MS = 500; // 長押し判定時間 (ms)
const unsigned long RESET_SIGNAL_DUR_MS = 300; // 子機へのリセット信号送信期間 (ms)
const unsigned long RESET_SIGNAL_INT_MS = 50; // リセット信号送信間隔 (ms)
const unsigned long DISPLAY_UPDATE_INT_MS = 50; // 走行中の画面描画間隔 (ms)

// ==========================================
// 状態定義・グローバル変数
// ==========================================
enum State {
STATE_READY,
STATE_RUNNING,
STATE_GOAL,
STATE_TX_ERROR,
STATE_INIT_ERROR
};
State currentState = STATE_READY;

// 高精度計測(マイクロ秒: us)
int64_t localStartTimeUs = 0;
uint64_t elapsedTimeUs = 0;

// ボタン処理用
bool lastBtnState = true;
unsigned long btnPressedTime = 0;
bool isLongPressedProcessed = false;

// 子機リセット処理用
unsigned long signalActiveStartTime = 0;
unsigned long lastResetSendTime = 0;
bool isAfterRunActive = false;

// 生存チェック & 電波強度用
unsigned long lastHeartbeatTime = 0;
bool isSensorConnected = false;
bool lastSensorConnectedState = false;
int lastRssi = -100; // 直近の電波強度 (dBm)

// 画面更新フラグ
bool updateDisplayRequest = true;

// ==========================================
// ESP-NOW 通信設定(ユニキャスト & パッキング)
// ==========================================
uint8_t slaveAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

typedef struct __attribute__((packed)) {
char mode; // 'S': Start, 'G': Goal, 'R': Reset, 'H': Heartbeat, 'A': ACK
uint64_t time; // マイクロ秒
} struct_message;

struct_message incomingData;
struct_message myData;
esp_now_peer_info_t peerInfo;

// ==========================================
// ビープ音再生処理 (D10 / BZ-01A 用)
// ==========================================
enum BuzzerSound { SND_START, SND_GOAL, SND_RESET, SND_ERROR };

void playBuzzer(BuzzerSound sound) {
switch (sound) {
case SND_START:
// スタート:ピッ!(高音・短め)
tone(PIN_BUZZER, 2000, 100);
break;

case SND_GOAL:
// ゴール:ファンファーレ音
tone(PIN_BUZZER, 1500, 100); delay(100);
tone(PIN_BUZZER, 2000, 100); delay(100);
tone(PIN_BUZZER, 2500, 250);
break;

case SND_RESET:
// リセット / 起動:ピッ、ピッ
tone(PIN_BUZZER, 2400, 50); delay(80);
tone(PIN_BUZZER, 2400, 50);
break;

case SND_ERROR:
// エラー:ブッ、ブッ(低音)
tone(PIN_BUZZER, 500, 200); delay(250);
tone(PIN_BUZZER, 500, 200);
break;
}
}

// ==========================================
// アンテナ・送信出力初期化関数
// ==========================================
void setupAntenna() {
pinMode(PIN_RF_PWR, OUTPUT);
pinMode(PIN_RF_SEL, OUTPUT);

WiFi.setTxPower(WIFI_POWER_20dBm);

#if USE_EXTERNAL_ANTENNA
digitalWrite(PIN_RF_PWR, LOW);
digitalWrite(PIN_RF_SEL, HIGH); // 外部アンテナ
#else
digitalWrite(PIN_RF_PWR, LOW);
digitalWrite(PIN_RF_SEL, LOW); // 内蔵アンテナ
#endif

delay(10);
}

// ==========================================
// ユーティリティ関数
// ==========================================
String formatTimeUs(uint64_t us) {
uint64_t ms = us / 1000;

unsigned long totalSec = ms / 1000;
unsigned long milliSec = ms % 1000;
unsigned long seconds = totalSec % 60;
unsigned long minutes = totalSec / 60;

char buf[16];
snprintf(buf, sizeof(buf), "%02lu'%02lu\"%03lu", minutes, seconds, milliSec);
return String(buf);
}

String getStatusStr() {
switch (currentState) {
case STATE_READY: return "READY";
case STATE_RUNNING: return "RUNNING";
case STATE_GOAL: return "GOAL!!";
case STATE_TX_ERROR: return "R-ERR";
default: return "ERROR";
}
}

// ==========================================
// オープニング画面描画関数 (テキスト大型表示)
// ==========================================
void showOpeningScreen() {
u8g2.clearBuffer();

// "GYMKHANA" を大きな太字フォントで中央描画
u8g2.setFont(u8g2_font_helvB14_tr);
int strWidth1 = u8g2.getStrWidth("GYMKHANA");
u8g2.drawStr((128 - strWidth1) / 2, 26, "GYMKHANA");

// "TIMER SYSTEM" を中型フォントで中央描画
u8g2.setFont(u8g2_font_helvB10_tr);
int strWidth2 = u8g2.getStrWidth("TIMER SYSTEM");
u8g2.drawStr((128 - strWidth2) / 2, 50, "TIMER SYSTEM");

u8g2.sendBuffer();

// 起動音を再生(ピッ、ピッ)
playBuzzer(SND_RESET);

// 2秒間オープニングテキストを表示して待機
delay(2000);
}

// ==========================================
// 画面描画 & 外部出力 (U8g2版・文字大型化)
// ==========================================
void displayTimeMax(uint64_t us) {
u8g2.clearBuffer();

// 1. ヘッダー情報(右上:[B] と [S] を最小フォントで配置してスペース確保)
u8g2.setFont(u8g2_font_5x7_tf);

// BLE接続状態 [B] / [-]
if (keyboard.isPaired()) {
u8g2.drawStr(92, 7, "[B]");
} else {
u8g2.drawStr(92, 7, "[-]");
}

// 子機センサー状態 [S] / [!] / [NC]
if (!isSensorConnected) {
u8g2.drawStr(108, 7, "[NC]");
} else if (lastRssi < -80) {
u8g2.drawStr(113, 7, "[!]");
} else {
u8g2.drawStr(113, 7, "[S]");
}

// 2. ステータス・エラー表示(限界サイズの超大型極太フォント)
u8g2.setFont(u8g2_font_helvB18_tr);

String st = getStatusStr();
int xPos = 0;

// 各ステータス・エラーに応じた最適位置(中央寄せ)の計算
if (st == "READY") xPos = 18;
else if (st == "RUNNING") xPos = 0; // 画面横幅いっぱい
else if (st == "GOAL!!") xPos = 14;
else if (st == "R-ERR") xPos = 18; // リセット/送信エラー時
else if (st == "ERROR") xPos = 15; // 初期化エラー時
else {
int strWidth = u8g2.getStrWidth(st.c_str());
xPos = (128 - strWidth) / 2;
if (xPos < 0) xPos = 0;
}

u8g2.drawStr(xPos, 28, st.c_str());

// 3. タイム表示(下段:巨大数字フォント)
uint64_t ms = us / 1000;
unsigned long totalSec = ms / 1000;
unsigned long milliSec = ms % 1000;
unsigned long seconds = totalSec % 60;
unsigned long minutes = totalSec / 60;

char timeBuf[16];
snprintf(timeBuf, sizeof(timeBuf), "%lu:%02lu.%03lu", minutes, seconds, milliSec);

u8g2.setFont(u8g2_font_logisoso28_tn);
u8g2.drawStr(0, 62, timeBuf);

u8g2.sendBuffer();
}

// スマホへタイムを出力 (BLE Keyboard)
void sendTimeToSmartphone(uint64_t us, const char *prefix = nullptr) {
if (!keyboard.isPaired()) return;

if (prefix != nullptr && *prefix != '\0') {
keyboard.print(prefix);
}
keyboard.print(formatTimeUs(us));
keyboard.println("");
keyboard.releaseAll();
}

// ==========================================
// ESP-NOW コールバック処理
// ==========================================
void OnDataSent(const wifi_tx_info_t *tx_info, esp_now_send_status_t status) {}

void OnDataRecv(const esp_now_recv_info_t *recv_info, const uint8_t *incomingDataRaw, int len) {
if (len != sizeof(struct_message)) {
DEBUG_PRINT("Len Mismatch: ");
DEBUG_PRINTLN(len);
return;
}

memcpy(&incomingData, incomingDataRaw, sizeof(incomingData));

// 生存タイマーとRSSIの更新
lastHeartbeatTime = millis();
isSensorConnected = true;
lastRssi = recv_info->rx_ctrl->rssi;

DEBUG_PRINT("Recv Mode: ");
DEBUG_PRINTLN(incomingData.mode);

// ACK(受信確認)の返送
if (incomingData.mode == 'S' || incomingData.mode == 'G') {
struct_message ackMsg;
ackMsg.mode = 'A';
ackMsg.time = 0;
esp_now_send(slaveAddress, (uint8_t *)&ackMsg, sizeof(ackMsg));
}

// 受信シグナルに応じた状態遷移
// 【スタート処理】
if (currentState == STATE_READY || currentState == STATE_TX_ERROR) {
if (incomingData.mode == 'S') {
localStartTimeUs = esp_timer_get_time();
currentState = STATE_RUNNING;
updateDisplayRequest = true;

playBuzzer(SND_START); // ★スタート音再生
}
}
// 【ゴール処理】
else if (currentState == STATE_RUNNING) {
if (incomingData.mode == 'G' || incomingData.mode == 'S') {

if (incomingData.mode == 'G' && incomingData.time > 0) {
elapsedTimeUs = incomingData.time;
} else {
int64_t currentTimeUs = esp_timer_get_time();
elapsedTimeUs = (uint64_t)(currentTimeUs - localStartTimeUs);
}

currentState = STATE_GOAL;
updateDisplayRequest = true;

playBuzzer(SND_GOAL); // ★ゴール音再生

// スマホ(BLE)へ確定タイムを送信
sendTimeToSmartphone(elapsedTimeUs);

// ゴール後、子機へリセットシグナル('R')を連送する
signalActiveStartTime = millis();
isAfterRunActive = true;
}
}
}

// ==========================================
// セットアップ
// ==========================================
void setup() {
#if DEBUG
Serial.begin(115200);
#endif

// ピン初期化
pinMode(PIN_RESET, (ACTIVE_LEVEL == LOW) ? INPUT_PULLUP : INPUT_PULLDOWN);
pinMode(PIN_BUZZER, OUTPUT);

Wire.begin(I2C_SDA, I2C_SCL);

// U8g2 OLED表示初期化
u8g2.begin();

// ★オープニングロゴ画面の表示処理
showOpeningScreen();

// BLE起動
keyboard.begin();

// Wi-Fi & アンテナ & ESP-NOW 初期化 (チャンネル1指定)
WiFi.disconnect(true);
delay(100);
WiFi.mode(WIFI_STA);

setupAntenna(); // アンテナ切替 & 出力最大化 (20dBm)

esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE);

if (esp_now_init() != ESP_OK) {
currentState = STATE_INIT_ERROR;
updateDisplayRequest = true;
playBuzzer(SND_ERROR); // ★エラー音
return;
}

esp_now_register_recv_cb(OnDataRecv);
esp_now_register_send_cb(OnDataSent);

// ピア登録(子機MACアドレス指定)
memcpy(peerInfo.peer_addr, slaveAddress, 6);
peerInfo.channel = 1;
peerInfo.encrypt = false;

if (esp_now_add_peer(&peerInfo) != ESP_OK) {
DEBUG_PRINTLN("Failed to add peer (Slave)");
}

lastHeartbeatTime = millis();
updateDisplayRequest = true;

DEBUG_PRINTLN("Master Setup Complete");
}

// ==========================================
// メインループ
// ==========================================
void loop() {
unsigned long currentTime = millis();
int64_t currentTimeUs = esp_timer_get_time();
bool currentBtnState = digitalRead(PIN_RESET);

// 初期化エラー時
if (currentState == STATE_INIT_ERROR) {
if (updateDisplayRequest) {
updateDisplayRequest = false;
displayTimeMax(0);
}
return;
}

// 1. 子機の生存確認
if (currentTime - lastHeartbeatTime > SENSOR_TIMEOUT_MS) {
isSensorConnected = false;
}

if (isSensorConnected != lastSensorConnectedState) {
updateDisplayRequest = true;
lastSensorConnectedState = isSensorConnected;
}

// 2. リセット/ストップボタン処理
if (currentBtnState == ACTIVE_LEVEL && lastBtnState != ACTIVE_LEVEL) {
btnPressedTime = currentTime;
isLongPressedProcessed = false;
}

// 長押し判定 (リセット)
if (currentBtnState == ACTIVE_LEVEL) {
if (!isLongPressedProcessed && (currentTime - btnPressedTime > BTN_LONG_PRESS_MS)) {
currentState = STATE_READY;
elapsedTimeUs = 0;
updateDisplayRequest = true;
isLongPressedProcessed = true;

signalActiveStartTime = currentTime;
isAfterRunActive = true;

playBuzzer(SND_RESET); // ★リセット操作音
}
}

// ボタンを離したときの処理 (短押し = 手動ストップ)
if (currentBtnState != ACTIVE_LEVEL && lastBtnState == ACTIVE_LEVEL) {
if (!isLongPressedProcessed) {
if (currentState == STATE_RUNNING) {
elapsedTimeUs = (uint64_t)(currentTimeUs - localStartTimeUs);
currentState = STATE_GOAL;
updateDisplayRequest = true;

signalActiveStartTime = currentTime;
isAfterRunActive = true;

playBuzzer(SND_GOAL); // ★手動ストップ時のゴール音
sendTimeToSmartphone(elapsedTimeUs, "M-");
}
}
isLongPressedProcessed = false;
}
lastBtnState = currentBtnState;

// 3. 子機へのリセット要求送信処理 (連送)
if (isAfterRunActive) {
if (currentTime - signalActiveStartTime < RESET_SIGNAL_DUR_MS) {
if (currentTime - lastResetSendTime > RESET_SIGNAL_INT_MS) {
memset(&myData, 0, sizeof(myData));
myData.mode = 'R';
myData.time = 0;
esp_now_send(slaveAddress, (uint8_t *)&myData, sizeof(myData));
lastResetSendTime = currentTime;
}
} else {
isAfterRunActive = false;
}
}

// 4. 走行中のリアルタイム表示更新
if (currentState == STATE_RUNNING) {
elapsedTimeUs = (uint64_t)(currentTimeUs - localStartTimeUs);
static unsigned long lastDisplayUpdateTime = 0;

if (currentTime - lastDisplayUpdateTime > DISPLAY_UPDATE_INT_MS) {
updateDisplayRequest = true;
lastDisplayUpdateTime = currentTime;
}
}

// 5. Bluetooth接続状態の変化検出
static bool lastBleConnected = false;
bool currentBleConnected = keyboard.isPaired();
if (currentBleConnected != lastBleConnected) {
updateDisplayRequest = true;
lastBleConnected = currentBleConnected;
}

// 6. 画面描画の実行
if (updateDisplayRequest) {
updateDisplayRequest = false;
displayTimeMax(elapsedTimeUs);
}
}
```


Posted at 2026/08/13 02:16:03 | コメント(0) | トラックバック(0) | ジムカーナ | 日記
2026年08月01日 イイね!

ジムカーナタイム測定機改3

EPS32C6を購入してプログラムを作成しました。
親機
LCDタイム表示
タイムリセットボタン
子機
ボタンは光電管のスイッチ
スタート2秒間切れないようにチャタリング対応
親機からリセットでリセット
簡単なプログラムです

Xの動画
https://x.com/i/status/2075165905936670995

ここからは生成AIに頑張ってもらいます。
Posted at 2026/08/01 03:40:44 | コメント(0) | トラックバック(0) | ジムカーナ | 日記
2026年07月18日 イイね!

ジムカーナタイム測定機改2

こんな感じで確認
右が光電管に直接入ります(5V-12VのDC-DCコンバータで光電管は動作させます)

Posted at 2026/07/18 01:53:06 | コメント(0) | トラックバック(0) | ジムカーナ | 日記

プロフィール

まう@朱理です。よろしくお願いします。
みんカラ新規会員登録

ユーザー内検索

<< 2026/8 >>

       1
2345678
91011 12 131415
16171819202122
2324252627 2829
3031     

リンク・クリップ

NDロードスターのリヤバンパーの穴の件 
カテゴリ:その他(カテゴリ未設定)
2021/01/10 00:36:03

愛車一覧

マツダ ロードスター マツダ ロードスター
2020/04/01納車 マシーングレープレミアムメタリック
マツダ デミオ マツダ デミオ
12/4納車されました。 2020/04/01乗り換え 155003km
ホンダ CR-Z ホンダ CR-Z
α Blacklabel 9月18日納車でした
ホンダ シビックタイプR ホンダ シビックタイプR
初購入の車でした。 間瀬サーキット 1'17.792 Best

過去のブログ

2026年
01月02月03月04月05月06月
07月08月09月10月11月12月
2021年
01月02月03月04月05月06月
07月08月09月10月11月12月
2020年
01月02月03月04月05月06月
07月08月09月10月11月12月
2019年
01月02月03月04月05月06月
07月08月09月10月11月12月
2018年
01月02月03月04月05月06月
07月08月09月10月11月12月
2017年
01月02月03月04月05月06月
07月08月09月10月11月12月
2016年
01月02月03月04月05月06月
07月08月09月10月11月12月
2015年
01月02月03月04月05月06月
07月08月09月10月11月12月
2014年
01月02月03月04月05月06月
07月08月09月10月11月12月
2013年
01月02月03月04月05月06月
07月08月09月10月11月12月
2012年
01月02月03月04月05月06月
07月08月09月10月11月12月
2011年
01月02月03月04月05月06月
07月08月09月10月11月12月
ヘルプ利用規約サイトマップ
© LY Corporation