arduino: TSV telemetry protocol with mock RPM/gear

- null-terminated TSV frame: V_bat, IMU (9 fields), RPM, gear
- mock RPM ramps 800-8000, gear derived from RPM bands
- voltage calibration offset
- PROTOCOL.md documents wire format

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2026-02-01 17:00:14 +09:00
parent 4e68dcef5f
commit f1ed809c71
10 changed files with 186 additions and 51 deletions

View File

@@ -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(": ");

View File

@@ -2,6 +2,7 @@
#define COMMS_H
#include <Arduino.h>
#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);

19
arduino/main/gear.cpp Normal file
View File

@@ -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;
}

7
arduino/main/gear.h Normal file
View File

@@ -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

View File

@@ -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);
}

19
arduino/main/rpm.cpp Normal file
View File

@@ -0,0 +1,19 @@
#include "rpm.h"
#include <Arduino.h>
// 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;
}

8
arduino/main/rpm.h Normal file
View File

@@ -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

View File

@@ -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;
}