Arduino frameworks
- AltSoftSerial UART with WT61 IMU - UART with Pi on TX0/RX0 (and USB)
This commit is contained in:
@@ -11,7 +11,58 @@ Sensor interface running on Arduino Nano, communicating with Pi via UART.
|
|||||||
## Current Capabilities
|
## Current Capabilities
|
||||||
|
|
||||||
- Battery voltage monitoring (voltage divider on A0)
|
- Battery voltage monitoring (voltage divider on A0)
|
||||||
- Serial output at 9600 baud, 1Hz update rate
|
- WT61 IMU/gyro via AltSoftSerial (9-axis: accel, gyro, euler angles)
|
||||||
|
- Duplex UART to Pi at 115200 baud, 10Hz telemetry output
|
||||||
|
- Simple text-based protocol for easy debugging
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Install via Arduino Library Manager:
|
||||||
|
- **AltSoftSerial** by Paul Stoffregen - for WT61 IMU serial
|
||||||
|
|
||||||
|
## Pin Assignments
|
||||||
|
|
||||||
|
| Pin | Function |
|
||||||
|
|-----|----------|
|
||||||
|
| A0 | Battery voltage (via divider) |
|
||||||
|
| D0 (RX) | Pi UART RX ← Arduino TX |
|
||||||
|
| D1 (TX) | Pi UART TX → Arduino RX |
|
||||||
|
| D8 | WT61 IMU RX (AltSoftSerial) |
|
||||||
|
| D9 | WT61 IMU TX (unused, AltSoftSerial fixed pin) |
|
||||||
|
| D13 | Status LED (heartbeat) |
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
- **MCU**: Arduino Nano (ATmega328P)
|
||||||
|
- **Pi Connection**: UART at 115200 baud (TX→RX, RX→TX, common GND)
|
||||||
|
- **IMU**: WT61 module at 9600 baud, 20Hz output
|
||||||
|
- **Voltage sensing**: Resistor divider (100k/47k) scaled for 0-20V input
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
## Planned
|
## Planned
|
||||||
|
|
||||||
@@ -19,18 +70,3 @@ Sensor interface running on Arduino Nano, communicating with Pi via UART.
|
|||||||
- Engine temperature (thermocouple/NTC)
|
- Engine temperature (thermocouple/NTC)
|
||||||
- Gear position indicator
|
- Gear position indicator
|
||||||
- Turn signal / high beam status
|
- Turn signal / high beam status
|
||||||
|
|
||||||
## Hardware
|
|
||||||
|
|
||||||
- **MCU**: Arduino Nano (ATmega328P)
|
|
||||||
- **Connection**: UART to Pi GPIO (TX→RX, RX→TX, common GND)
|
|
||||||
- **Voltage sensing**: Resistor divider scaled for 0-20V input range
|
|
||||||
|
|
||||||
## Protocol
|
|
||||||
|
|
||||||
Simple text-based for now:
|
|
||||||
```
|
|
||||||
V_bat: 12.45V
|
|
||||||
```
|
|
||||||
|
|
||||||
Future: structured binary or JSON for multiple sensors.
|
|
||||||
|
|||||||
70
arduino/main/comms.cpp
Normal file
70
arduino/main/comms.cpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#include "comms.h"
|
||||||
|
|
||||||
|
// Pi communication uses hardware Serial (pins 0/1)
|
||||||
|
// Baud rate - 115200 is reasonable for duplex with Pi
|
||||||
|
static const long BAUD_RATE = 115200;
|
||||||
|
|
||||||
|
// Command buffer
|
||||||
|
static const int CMD_BUF_SIZE = 64;
|
||||||
|
static char cmdBuf[CMD_BUF_SIZE];
|
||||||
|
static int cmdIndex = 0;
|
||||||
|
static bool cmdReady = false;
|
||||||
|
|
||||||
|
// Connection tracking
|
||||||
|
static unsigned long lastRxTime = 0;
|
||||||
|
|
||||||
|
void comms_init() {
|
||||||
|
Serial.begin(BAUD_RATE);
|
||||||
|
cmdIndex = 0;
|
||||||
|
cmdReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool comms_update() {
|
||||||
|
while (Serial.available()) {
|
||||||
|
char c = Serial.read();
|
||||||
|
lastRxTime = millis();
|
||||||
|
|
||||||
|
if (c == '\n' || c == '\r') {
|
||||||
|
if (cmdIndex > 0) {
|
||||||
|
cmdBuf[cmdIndex] = '\0';
|
||||||
|
cmdReady = true;
|
||||||
|
cmdIndex = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (cmdIndex < CMD_BUF_SIZE - 1) {
|
||||||
|
cmdBuf[cmdIndex++] = c;
|
||||||
|
}
|
||||||
|
// else: overflow, silently drop extra chars
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void comms_send(const char* key, float value, int decimals) {
|
||||||
|
Serial.print(key);
|
||||||
|
Serial.print(": ");
|
||||||
|
Serial.println(value, decimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
void comms_send(const char* key, int value) {
|
||||||
|
Serial.print(key);
|
||||||
|
Serial.print(": ");
|
||||||
|
Serial.println(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void comms_send(const char* key, const char* value) {
|
||||||
|
Serial.print(key);
|
||||||
|
Serial.print(": ");
|
||||||
|
Serial.println(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* comms_get_command() {
|
||||||
|
if (cmdReady) {
|
||||||
|
cmdReady = false;
|
||||||
|
return cmdBuf;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool comms_is_connected(unsigned long timeout_ms) {
|
||||||
|
return (millis() - lastRxTime) < timeout_ms;
|
||||||
|
}
|
||||||
25
arduino/main/comms.h
Normal file
25
arduino/main/comms.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef COMMS_H
|
||||||
|
#define COMMS_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// Initialize Pi serial communication (call in setup)
|
||||||
|
void comms_init();
|
||||||
|
|
||||||
|
// Process incoming commands from Pi - call in loop
|
||||||
|
// Returns true if a complete command was received
|
||||||
|
bool comms_update();
|
||||||
|
|
||||||
|
// Send telemetry line to Pi
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Get last received command (empty if none)
|
||||||
|
// Command buffer is cleared after reading
|
||||||
|
const char* comms_get_command();
|
||||||
|
|
||||||
|
// Check if connected (received any data recently)
|
||||||
|
bool comms_is_connected(unsigned long timeout_ms = 5000);
|
||||||
|
|
||||||
|
#endif
|
||||||
115
arduino/main/imu.cpp
Normal file
115
arduino/main/imu.cpp
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include "imu.h"
|
||||||
|
#include <AltSoftSerial.h>
|
||||||
|
|
||||||
|
// AltSoftSerial uses fixed pins on ATmega328P:
|
||||||
|
// RX = Pin 8, TX = Pin 9 (TX not used for WT61)
|
||||||
|
static AltSoftSerial imuSerial;
|
||||||
|
|
||||||
|
// WT61 packet structure:
|
||||||
|
// Byte 0: 0x55 (header)
|
||||||
|
// Byte 1: Packet type (0x51=accel, 0x52=gyro, 0x53=angle)
|
||||||
|
// Bytes 2-9: Data (4x int16_t, little-endian)
|
||||||
|
// Byte 10: Checksum (sum of bytes 0-9, lower 8 bits)
|
||||||
|
static const uint8_t PACKET_HEADER = 0x55;
|
||||||
|
static const uint8_t PACKET_ACCEL = 0x51;
|
||||||
|
static const uint8_t PACKET_GYRO = 0x52;
|
||||||
|
static const uint8_t PACKET_ANGLE = 0x53;
|
||||||
|
static const int PACKET_SIZE = 11;
|
||||||
|
|
||||||
|
// Receive buffer
|
||||||
|
static uint8_t rxBuf[PACKET_SIZE];
|
||||||
|
static int rxIndex = 0;
|
||||||
|
|
||||||
|
// Latest data
|
||||||
|
static ImuData currentData = {0};
|
||||||
|
|
||||||
|
// Scale factors from WT61 datasheet
|
||||||
|
// Accel: raw / 32768 * 16g
|
||||||
|
// Gyro: raw / 32768 * 2000 deg/s
|
||||||
|
// Angle: raw / 32768 * 180 deg
|
||||||
|
static const float ACCEL_SCALE = 16.0 / 32768.0;
|
||||||
|
static const float GYRO_SCALE = 2000.0 / 32768.0;
|
||||||
|
static const float ANGLE_SCALE = 180.0 / 32768.0;
|
||||||
|
|
||||||
|
static int16_t parseI16(uint8_t lo, uint8_t hi) {
|
||||||
|
return (int16_t)((hi << 8) | lo);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool validateChecksum() {
|
||||||
|
uint8_t sum = 0;
|
||||||
|
for (int i = 0; i < PACKET_SIZE - 1; i++) {
|
||||||
|
sum += rxBuf[i];
|
||||||
|
}
|
||||||
|
return sum == rxBuf[PACKET_SIZE - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void processPacket() {
|
||||||
|
if (!validateChecksum()) {
|
||||||
|
return; // Bad packet, ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t type = rxBuf[1];
|
||||||
|
int16_t v0 = parseI16(rxBuf[2], rxBuf[3]);
|
||||||
|
int16_t v1 = parseI16(rxBuf[4], rxBuf[5]);
|
||||||
|
int16_t v2 = parseI16(rxBuf[6], rxBuf[7]);
|
||||||
|
// v3 at bytes 8-9 is temperature, ignored for now
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case PACKET_ACCEL:
|
||||||
|
currentData.ax = v0 * ACCEL_SCALE;
|
||||||
|
currentData.ay = v1 * ACCEL_SCALE;
|
||||||
|
currentData.az = v2 * ACCEL_SCALE;
|
||||||
|
break;
|
||||||
|
case PACKET_GYRO:
|
||||||
|
currentData.gx = v0 * GYRO_SCALE;
|
||||||
|
currentData.gy = v1 * GYRO_SCALE;
|
||||||
|
currentData.gz = v2 * GYRO_SCALE;
|
||||||
|
break;
|
||||||
|
case PACKET_ANGLE:
|
||||||
|
currentData.roll = v0 * ANGLE_SCALE;
|
||||||
|
currentData.pitch = v1 * ANGLE_SCALE;
|
||||||
|
currentData.yaw = v2 * ANGLE_SCALE;
|
||||||
|
currentData.lastUpdate = millis();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void imu_init() {
|
||||||
|
imuSerial.begin(9600);
|
||||||
|
rxIndex = 0;
|
||||||
|
currentData = {0};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool imu_update() {
|
||||||
|
bool gotPacket = false;
|
||||||
|
|
||||||
|
while (imuSerial.available()) {
|
||||||
|
uint8_t c = imuSerial.read();
|
||||||
|
|
||||||
|
// State machine: look for header, then collect packet
|
||||||
|
if (rxIndex == 0) {
|
||||||
|
if (c == PACKET_HEADER) {
|
||||||
|
rxBuf[rxIndex++] = c;
|
||||||
|
}
|
||||||
|
// else: discard, wait for sync
|
||||||
|
} else {
|
||||||
|
rxBuf[rxIndex++] = c;
|
||||||
|
|
||||||
|
if (rxIndex >= PACKET_SIZE) {
|
||||||
|
processPacket();
|
||||||
|
rxIndex = 0;
|
||||||
|
gotPacket = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gotPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImuData& imu_get_data() {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool imu_is_fresh(unsigned long timeout_ms) {
|
||||||
|
return (millis() - currentData.lastUpdate) < timeout_ms;
|
||||||
|
}
|
||||||
31
arduino/main/imu.h
Normal file
31
arduino/main/imu.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef IMU_H
|
||||||
|
#define IMU_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// WT61 IMU data structure
|
||||||
|
struct ImuData {
|
||||||
|
// Acceleration (g)
|
||||||
|
float ax, ay, az;
|
||||||
|
// Angular velocity (deg/s)
|
||||||
|
float gx, gy, gz;
|
||||||
|
// Euler angles (degrees)
|
||||||
|
float roll, pitch, yaw;
|
||||||
|
// Timestamp of last valid packet (millis)
|
||||||
|
unsigned long lastUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize IMU serial (call in setup)
|
||||||
|
void imu_init();
|
||||||
|
|
||||||
|
// Process incoming bytes - call frequently in loop
|
||||||
|
// Returns true if new complete packet was parsed
|
||||||
|
bool imu_update();
|
||||||
|
|
||||||
|
// Get latest IMU data
|
||||||
|
const ImuData& imu_get_data();
|
||||||
|
|
||||||
|
// Check if IMU data is fresh (updated within timeout_ms)
|
||||||
|
bool imu_is_fresh(unsigned long timeout_ms = 200);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -2,24 +2,77 @@
|
|||||||
// Motorcycle telemetry hub
|
// Motorcycle telemetry hub
|
||||||
|
|
||||||
#include "voltage.h"
|
#include "voltage.h"
|
||||||
|
#include "imu.h"
|
||||||
|
#include "comms.h"
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
static const unsigned long TELEMETRY_INTERVAL_MS = 100; // 10Hz telemetry
|
||||||
|
static unsigned long lastTelemetryTime = 0;
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(9600);
|
|
||||||
pinMode(LED_BUILTIN, OUTPUT);
|
pinMode(LED_BUILTIN, OUTPUT);
|
||||||
|
|
||||||
voltage_init();
|
voltage_init();
|
||||||
|
imu_init(); // AltSoftSerial on pins 8(RX)/9(TX)
|
||||||
|
comms_init(); // Hardware Serial to Pi at 115200
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
// Report battery voltage
|
// Always poll IMU - it's streaming at 20Hz
|
||||||
Serial.print("V_bat: ");
|
imu_update();
|
||||||
Serial.print(voltage_read(), 2);
|
|
||||||
Serial.println("V");
|
|
||||||
|
|
||||||
// Heartbeat blink
|
// Process any commands from Pi
|
||||||
digitalWrite(LED_BUILTIN, HIGH);
|
if (comms_update()) {
|
||||||
delay(50);
|
const char* cmd = comms_get_command();
|
||||||
digitalWrite(LED_BUILTIN, LOW);
|
// Future: handle commands like "PING", "SET_RATE", etc.
|
||||||
|
// For now, echo back as acknowledgment
|
||||||
delay(1000); // 1Hz update rate
|
if (cmd[0] != '\0') {
|
||||||
|
comms_send("ACK", cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send telemetry at fixed interval
|
||||||
|
unsigned long now = millis();
|
||||||
|
if (now - lastTelemetryTime >= TELEMETRY_INTERVAL_MS) {
|
||||||
|
lastTelemetryTime = now;
|
||||||
|
sendTelemetry();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heartbeat - quick blink if IMU fresh, slow blink if stale
|
||||||
|
static unsigned long lastBlink = 0;
|
||||||
|
unsigned long blinkInterval = imu_is_fresh() ? 500 : 2000;
|
||||||
|
if (now - lastBlink >= blinkInterval) {
|
||||||
|
lastBlink = now;
|
||||||
|
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendTelemetry() {
|
||||||
|
// Battery voltage
|
||||||
|
comms_send("V_bat", voltage_read());
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
// flip LED to indicate main cycle
|
||||||
|
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user