diff --git a/arduino/PROTOCOL.md b/arduino/PROTOCOL.md new file mode 100644 index 0000000..6f83c33 --- /dev/null +++ b/arduino/PROTOCOL.md @@ -0,0 +1,56 @@ +# Arduino-Pi Communication Protocol + +Telemetry protocol for Arduino → Pi communication over UART at 115200 baud. + +## Design Rationale + +- **ASCII-based**: Human-debuggable, digits are self-bounding (no accidental header spoofing) +- **TSV format**: Tab delimiter, predictable field count, trivial to parse, survives floating decimals +- **Null-terminated**: `\0` (0x00) is unambiguous end-of-line, avoids CRLF headaches +- **10Hz rate**: ~50 bytes/line × 10Hz = 500 B/s, well under 115200 baud capacity (~4% utilization) + +## Telemetry Frame (Arduino → Pi) + +``` +field0\tfield1\tfield2\t...\tfieldN\0 +``` + +### Fields + +| Index | Name | Unit | Description | +|-------|--------|---------|--------------------------------| +| 0 | V_bat | V | Battery voltage | +| 1 | Ax | g | Acceleration X | +| 2 | Ay | g | Acceleration Y | +| 3 | Az | g | Acceleration Z | +| 4 | Gx | deg/s | Angular velocity X | +| 5 | Gy | deg/s | Angular velocity Y | +| 6 | Gz | deg/s | Angular velocity Z | +| 7 | Roll | deg | Euler angle roll | +| 8 | Pitch | deg | Euler angle pitch | +| 9 | Yaw | deg | Euler angle yaw | +| 10 | RPM | RPM | Engine RPM | +| 11 | Gear | - | Gear position (0=N, 1-6) | + +### Example + +``` +12.45\t0.02\t-0.01\t1.00\t0.50\t-0.25\t0.10\t2.35\t-1.20\t45.80\t3500\t3\0 +``` + +## Stale Data Handling + +When IMU data is stale, empty fields are sent to preserve field count: +``` +12.45\t\t\t\t\t\t\t\t\t\0 +``` +Backend parses empty fields as null/NaN. + +## Commands (Pi → Arduino) + +TBD: Command structure for configuration, calibration triggers, etc. + +## Versioning + +Protocol changes should bump a version field or use a different frame header. +Currently unversioned (v0 / development). diff --git a/arduino/README.md b/arduino/README.md index ed247db..ef29b15 100644 --- a/arduino/README.md +++ b/arduino/README.md @@ -40,33 +40,16 @@ Install via Arduino Library Manager: ## Protocol -Simple text lines, one per sensor reading: -``` -V_bat: 12.45 -Ax: 0.02 -Ay: -0.01 -Az: 1.00 -Gx: 0.50 -Gy: -0.25 -Gz: 0.10 -Roll: 2.35 -Pitch: -1.20 -Yaw: 45.80 -``` - -If IMU data is stale (no valid packets for 200ms): -``` -IMU: STALE -``` - -Commands from Pi are echoed back: -``` -ACK: PING -``` +TSV (tab-separated), null-terminated frames at 10Hz. See [PROTOCOL.md](PROTOCOL.md) for full specification. ## Planned - RPM sensing (pulse counting from ignition coil) -- Engine temperature (thermocouple/NTC) -- Gear position indicator -- Turn signal / high beam status +- Gear position indicator + +### Not planned + +- Engine temperature (thermocouple/NTC) + *Borderline do not want to do, simple but not really useful* +- Turn signal / high beam status + *No need to do something the dash already does* diff --git a/arduino/main/comms.cpp b/arduino/main/comms.cpp index 4db3b6c..029a09e 100644 --- a/arduino/main/comms.cpp +++ b/arduino/main/comms.cpp @@ -39,6 +39,49 @@ bool comms_update() { return false; } +void comms_send_telemetry(float voltage, const ImuData& imu, bool imu_valid, int rpm, int gear) { + // Field 0: voltage + Serial.print(voltage, 2); + Serial.write('\t'); + + if (imu_valid) { + // Fields 1-3: acceleration + Serial.print(imu.ax, 2); + Serial.write('\t'); + Serial.print(imu.ay, 2); + Serial.write('\t'); + Serial.print(imu.az, 2); + Serial.write('\t'); + + // Fields 4-6: angular velocity + Serial.print(imu.gx, 2); + Serial.write('\t'); + Serial.print(imu.gy, 2); + Serial.write('\t'); + Serial.print(imu.gz, 2); + Serial.write('\t'); + + // Fields 7-9: euler angles + Serial.print(imu.roll, 2); + Serial.write('\t'); + Serial.print(imu.pitch, 2); + Serial.write('\t'); + Serial.print(imu.yaw, 2); + } else { + // Empty fields for stale IMU (9 tabs for 9 empty fields) + Serial.print(F("\t\t\t\t\t\t\t\t")); + } + + // Fields 10-11: RPM and gear + Serial.write('\t'); + Serial.print(rpm); + Serial.write('\t'); + Serial.print(gear); + + // Null terminator (no newline) + Serial.write('\0'); +} + void comms_send(const char* key, float value, int decimals) { Serial.print(key); Serial.print(": "); diff --git a/arduino/main/comms.h b/arduino/main/comms.h index 8bcb9e0..23b8cef 100644 --- a/arduino/main/comms.h +++ b/arduino/main/comms.h @@ -2,6 +2,7 @@ #define COMMS_H #include +#include "imu.h" // Initialize Pi serial communication (call in setup) void comms_init(); @@ -10,7 +11,12 @@ void comms_init(); // Returns true if a complete command was received bool comms_update(); -// Send telemetry line to Pi +// Send complete telemetry frame (TSV format, null-terminated) +// Format: V_bat\tAx\tAy\tAz\tGx\tGy\tGz\tRoll\tPitch\tYaw\tRPM\tGear\0 +// If imu_valid is false, IMU fields are empty (but tabs preserved) +void comms_send_telemetry(float voltage, const ImuData& imu, bool imu_valid, int rpm, int gear); + +// Send key:value line (for debug/ACK, newline-terminated) void comms_send(const char* key, float value, int decimals = 2); void comms_send(const char* key, int value); void comms_send(const char* key, const char* value); diff --git a/arduino/main/gear.cpp b/arduino/main/gear.cpp new file mode 100644 index 0000000..34703a4 --- /dev/null +++ b/arduino/main/gear.cpp @@ -0,0 +1,19 @@ +#include "gear.h" + +// Mock gear: derived from RPM bands +// Real sensor would read position switch + +void gear_init() { + // Nothing to init for mock +} + +int gear_get(int rpm) { + // Simulate gear based on RPM + // N < 1000, 1st < 2500, 2nd < 4000, 3rd < 5500, 4th < 7000, 5th+ + if (rpm < 1000) return 0; // Neutral + if (rpm < 2500) return 1; + if (rpm < 4000) return 2; + if (rpm < 5500) return 3; + if (rpm < 7000) return 4; + return 5; +} diff --git a/arduino/main/gear.h b/arduino/main/gear.h new file mode 100644 index 0000000..8eb4bb8 --- /dev/null +++ b/arduino/main/gear.h @@ -0,0 +1,7 @@ +#ifndef GEAR_H +#define GEAR_H + +void gear_init(); +int gear_get(int rpm); // Returns gear 0-6 (0=neutral) + +#endif diff --git a/arduino/main/main.ino b/arduino/main/main.ino index 1f15c98..b06ba96 100644 --- a/arduino/main/main.ino +++ b/arduino/main/main.ino @@ -3,6 +3,8 @@ #include "voltage.h" #include "imu.h" +#include "rpm.h" +#include "gear.h" #include "comms.h" // Timing @@ -21,6 +23,10 @@ void setup() { imu_init(); // AltSoftSerial on pins 8(RX)/9(TX) Serial.println(F("[INIT] imu ok")); + rpm_init(); + gear_init(); + Serial.println(F("[INIT] rpm/gear ok")); + // Let IMU warm up a bit before calibrating // (WT61 needs a moment to stabilize after power-on) delay(500); @@ -36,6 +42,9 @@ void loop() { // Always poll IMU - it's streaming at 20Hz imu_update(); + // Update mock RPM (ramping) + rpm_update(); + // Process any commands from Pi if (comms_update()) { const char* cmd = comms_get_command(); @@ -63,28 +72,12 @@ void loop() { } void sendTelemetry() { - // Battery voltage - comms_send("V_bat", voltage_read()); + // Send all telemetry in a single TSV frame + float voltage = voltage_read(); + const ImuData& imu = imu_get_data(); + bool imu_valid = imu_is_fresh(); + int rpm = rpm_get(); + int gear = gear_get(rpm); - // IMU data (only if we have fresh data) - if (imu_is_fresh()) { - const ImuData& imu = imu_get_data(); - - // Acceleration (g) - comms_send("Ax", imu.ax); - comms_send("Ay", imu.ay); - comms_send("Az", imu.az); - - // Angular velocity (deg/s) - comms_send("Gx", imu.gx); - comms_send("Gy", imu.gy); - comms_send("Gz", imu.gz); - - // Euler angles (degrees) - comms_send("Roll", imu.roll); - comms_send("Pitch", imu.pitch); - comms_send("Yaw", imu.yaw); - } else { - comms_send("IMU", "STALE"); - } + comms_send_telemetry(voltage, imu, imu_valid, rpm, gear); } diff --git a/arduino/main/rpm.cpp b/arduino/main/rpm.cpp new file mode 100644 index 0000000..2a8e220 --- /dev/null +++ b/arduino/main/rpm.cpp @@ -0,0 +1,19 @@ +#include "rpm.h" +#include + +// Mock RPM: ramps up/down between idle and redline +static int _rpm = 800; + +void rpm_init() { + _rpm = 800; +} + +void rpm_update() { + // ~100ms per call at 10Hz = takes ~7s to sweep range + _rpm += 10; + if (_rpm >= 8000) { _rpm = 800;} +} + +int rpm_get() { + return _rpm; +} diff --git a/arduino/main/rpm.h b/arduino/main/rpm.h new file mode 100644 index 0000000..3d4be49 --- /dev/null +++ b/arduino/main/rpm.h @@ -0,0 +1,8 @@ +#ifndef RPM_H +#define RPM_H + +void rpm_init(); +void rpm_update(); // Call in loop +int rpm_get(); // Returns current RPM (0 if invalid) + +#endif diff --git a/arduino/main/voltage.cpp b/arduino/main/voltage.cpp index 8dbea2d..4cf37d5 100644 --- a/arduino/main/voltage.cpp +++ b/arduino/main/voltage.cpp @@ -10,6 +10,7 @@ static const int PIN_VBAT = A0; static const float DIVIDER_RATIO = 47.0 / (100.0 + 47.0); // ~0.3197 static const float ADC_REF = 5.0; static const int ADC_MAX = 1023; +static const float OFFSET = 0.2; // calib void voltage_init() { // analogRead doesn't need explicit pinMode, but here for future config @@ -23,5 +24,5 @@ int voltage_read_raw() { float voltage_read() { int raw = voltage_read_raw(); float vDivider = (raw / (float)ADC_MAX) * ADC_REF; - return vDivider / DIVIDER_RATIO; + return vDivider / DIVIDER_RATIO + OFFSET; }