Add GPIO12 startup/activity heartbeat LED behavior

This commit is contained in:
devdesk
2026-02-21 23:32:28 +02:00
parent 25dc34c6e3
commit fc9bbd213f
4 changed files with 108 additions and 15 deletions

11
include/heartbeat.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef HEARTBEAT_H
#define HEARTBEAT_H
#include <Arduino.h>
void setupHeartbeat();
void startupHeartbeatBlink(uint8_t count = 5);
void triggerHeartbeatBlink();
void updateHeartbeat();
#endif

71
src/heartbeat.cpp Normal file
View File

@@ -0,0 +1,71 @@
#include <Arduino.h>
#include "heartbeat.h"
namespace {
constexpr uint8_t HEARTBEAT_PIN = 12;
constexpr uint16_t STARTUP_ON_MS = 120;
constexpr uint16_t STARTUP_OFF_MS = 120;
constexpr uint16_t ACTIVITY_ON_MS = 45;
constexpr uint16_t ACTIVITY_OFF_MS = 70;
constexpr uint16_t TRIGGER_THROTTLE_MS = 35;
constexpr uint8_t MAX_PENDING_BLINKS = 6;
uint8_t pendingBlinks = 0;
uint32_t phaseStartMs = 0;
uint32_t lastTriggerMs = 0;
bool ledOn = false;
}
void setupHeartbeat() {
pinMode(HEARTBEAT_PIN, OUTPUT);
digitalWrite(HEARTBEAT_PIN, LOW);
ledOn = false;
phaseStartMs = millis();
}
void startupHeartbeatBlink(uint8_t count) {
for (uint8_t i = 0; i < count; ++i) {
digitalWrite(HEARTBEAT_PIN, HIGH);
delay(STARTUP_ON_MS);
digitalWrite(HEARTBEAT_PIN, LOW);
delay(STARTUP_OFF_MS);
}
}
void triggerHeartbeatBlink() {
const uint32_t now = millis();
if (now - lastTriggerMs < TRIGGER_THROTTLE_MS) {
return;
}
lastTriggerMs = now;
if (pendingBlinks < MAX_PENDING_BLINKS) {
pendingBlinks++;
}
}
void updateHeartbeat() {
const uint32_t now = millis();
if (pendingBlinks == 0) {
if (ledOn) {
ledOn = false;
digitalWrite(HEARTBEAT_PIN, LOW);
}
return;
}
if (!ledOn && (now - phaseStartMs >= ACTIVITY_OFF_MS)) {
ledOn = true;
phaseStartMs = now;
digitalWrite(HEARTBEAT_PIN, HIGH);
return;
}
if (ledOn && (now - phaseStartMs >= ACTIVITY_ON_MS)) {
ledOn = false;
phaseStartMs = now;
digitalWrite(HEARTBEAT_PIN, LOW);
pendingBlinks--;
}
}

View File

@@ -1,6 +1,7 @@
#include <Arduino.h>
#include <WiFi.h>
#include "config.h"
#include "heartbeat.h"
#include "motor.h"
#include "webserver.h"
@@ -36,6 +37,9 @@ void setupWiFi() {
void setup() {
Serial.begin(115200);
delay(1000);
setupHeartbeat();
startupHeartbeatBlink(5);
Serial.println("\n=============================");
Serial.println(" Dual BTS7960 Motor Controller");
@@ -61,5 +65,6 @@ void loop() {
handleWebServer();
motor1.update(); // Update current sensing and logging for motor 1
motor2.update(); // Update current sensing and logging for motor 2
updateHeartbeat();
delay(1);
}

View File

@@ -1,10 +1,16 @@
#include <Arduino.h>
#include <WebServer.h>
#include "heartbeat.h"
#include "motor.h"
#include "config.h"
WebServer server(HTTP_PORT);
static void sendWithHeartbeat(int code, const char* contentType, const String& content) {
triggerHeartbeatBlink();
server.send(code, contentType, content);
}
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
@@ -585,74 +591,74 @@ void handleRoot() {
void handleMotor1Drive() {
if (!server.hasArg("value")) {
server.send(400, "text/plain", "Missing value");
sendWithHeartbeat(400, "text/plain", "Missing value");
return;
}
applyDriveValue(motor1, server.arg("value").toInt());
server.send(200, "text/plain", "OK");
sendWithHeartbeat(200, "text/plain", "OK");
}
void handleMotor2Drive() {
if (!server.hasArg("value")) {
server.send(400, "text/plain", "Missing value");
sendWithHeartbeat(400, "text/plain", "Missing value");
return;
}
applyDriveValue(motor2, server.arg("value").toInt());
server.send(200, "text/plain", "OK");
sendWithHeartbeat(200, "text/plain", "OK");
}
void handleMotor1Speed() {
if (server.hasArg("value")) {
motor1.setSpeed(server.arg("value").toInt());
server.send(200, "text/plain", "OK");
sendWithHeartbeat(200, "text/plain", "OK");
} else {
server.send(400, "text/plain", "Missing value");
sendWithHeartbeat(400, "text/plain", "Missing value");
}
}
void handleMotor1Direction() {
if (server.hasArg("value")) {
motor1.setDirection(server.arg("value").toInt());
server.send(200, "text/plain", "OK");
sendWithHeartbeat(200, "text/plain", "OK");
} else {
server.send(400, "text/plain", "Missing value");
sendWithHeartbeat(400, "text/plain", "Missing value");
}
}
void handleMotor1Stop() {
motor1.stop();
server.send(200, "text/plain", "OK");
sendWithHeartbeat(200, "text/plain", "OK");
}
void handleMotor2Speed() {
if (server.hasArg("value")) {
motor2.setSpeed(server.arg("value").toInt());
server.send(200, "text/plain", "OK");
sendWithHeartbeat(200, "text/plain", "OK");
} else {
server.send(400, "text/plain", "Missing value");
sendWithHeartbeat(400, "text/plain", "Missing value");
}
}
void handleMotor2Direction() {
if (server.hasArg("value")) {
motor2.setDirection(server.arg("value").toInt());
server.send(200, "text/plain", "OK");
sendWithHeartbeat(200, "text/plain", "OK");
} else {
server.send(400, "text/plain", "Missing value");
sendWithHeartbeat(400, "text/plain", "Missing value");
}
}
void handleMotor2Stop() {
motor2.stop();
server.send(200, "text/plain", "OK");
sendWithHeartbeat(200, "text/plain", "OK");
}
void handleStop() {
motor1.stop();
motor2.stop();
server.send(200, "text/plain", "OK");
sendWithHeartbeat(200, "text/plain", "OK");
}
void handleSpeed() {