Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
/logs
21 changes: 10 additions & 11 deletions include/Button.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
#include "config.h"

class Button {
public:
Button(int pin);
bool isPressed();
bool isReleased();
bool heldFor(uint16_t duration_ms);

private:
uint8_t _pin;
bool _lastState;
uint64_t _lastEdgeTime;
uint64_t _holdStartTime;
private:
uint8_t _pin;
bool _lastState;
uint64_t _lastEdgeTime;
uint64_t _holdStartTime;
public:
Button(int pin);
bool isPressed();
bool isReleased();
bool heldFor(uint16_t duration_ms);
};
4 changes: 2 additions & 2 deletions include/CircularBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
template <typename T, size_t size>
class CircularBuffer {
private:
T buffer[size];
T _buffer[size];
size_t _head = 0;
size_t _tail = 0;
bool _full = false;
Expand All @@ -19,7 +19,7 @@ class CircularBuffer {
_full = _head == _tail;
}

bool pop (&T item) {
bool pop (T &item) {
if (isEmpty()) {
return false;
}
Expand Down
8 changes: 4 additions & 4 deletions include/Logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
#include "config.h"
#include "CircularBuffer.h"

enum LogLevel { DEBUG, ERROR, INFO, WARNING };
enum LogLevel { NONE, ERROR, WARNING, INFO, DEBUG };


// In C++, this is the cleaner way to define a fixed-size char array type
struct LogEntry {
char data[MAX_LOG_LEN];
};

class Logger {
private:
static SdFs sd; // The SD filesystem object
static FsFile logFile; // The log file object
static SdFs sd;
static FsFile logFile;

public:
static CircularBuffer<LogEntry, MAX_BUF> _logBuffer;
Expand Down
1 change: 1 addition & 0 deletions include/MpuController.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma once
#include "config.h"
#include "Logger.h"
#include "Nextion.h"
Expand Down
9 changes: 6 additions & 3 deletions include/Nextion.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma once
#include "config.h"

// Nextion connected to Serial7 (TX7/RX7 on Teensy 4.1)
Expand All @@ -16,11 +17,12 @@
#define NX_DRIVE_SPEED "n_speed" // number: vehicle speed, integer km/h
#define NX_DRIVE_RPM "n_rpm" // number: motor RPM
#define NX_DRIVE_TORQUE "n_torque" // number: torque command, 0-100%
#define NX_DRIVE_DCBUS "n_dcbus" // number: DC bus voltage, whole volts
#define NX_DRIVE_DCBUS "n_dc_bus" // number: DC bus voltage, whole volts
#define NX_DRIVE_FAULT "t_fault" // text: "OK" or "FAULT"
#define NX_DRIVE_STATE "t_drive" // text: "DRIVE: ON" or "DRIVE: OFF"
#define NX_DRIVE_MOTOR_TEMP "n_mtemp" // number: motor temperature, °C
#define NX_DRIVE_INVERTER_TEMP "n_itemp" // number: inverter temperature, °C
#define NX_DRIVE_MOTOR_TEMP "n_motor_temp" // number: motor temperature, °C
#define NX_DRIVE_INVERTER_TEMP "n_inv_temp" // number: inverter temperature, °C
#define NX_DRIVE_SPEED_BAR "j_throttle" // progress bar: throttle command, 0-100%

struct DashStatus {
int16_t speed; // km/h
Expand All @@ -43,4 +45,5 @@ class Nextion {
static void sendNumber(const char *component, int16_t value);
static void bootStatus(const char *phase, const char *detail);
static void updateDash(DashStatus dashStatus);
static void sendNumberValue(const char *component, int16_t value);
};
14 changes: 14 additions & 0 deletions include/Pedal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once
#include "config.h"

class Pedal {
private:
float appsPercent(int raw, int rest, int full);
bool fault = false;
uint8_t plausibilityCount = 0;
public:
Pedal();
int16_t apps1Raw = 0;
int16_t apps2Raw = 0;
int16_t read();
};
25 changes: 23 additions & 2 deletions include/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@


// --------- LOGGING ----------
#define LOG_SD true
#define SERIAL_LOG_LEVEL LogLevel::NONE // threshold for logging to serial (NONE to disable)
#define LOG_SD false
#define LOG_SERIAL false
#define MAX_BUF 16
#define MAX_LOG_LEN 128
Expand All @@ -18,7 +19,27 @@ extern FsFile logFile;
#define DEBOUNCE_MS 50
#define BUTTON_PIN 2

// ---------- APPS (pedal sensor) config ----------
// Set REST to ADC reading with pedal physically released.
// Set FULL to ADC reading at maximum pedal travel.
// Formula handles both rising and falling sensor directions.
#define APPS1_PIN A0
#define APPS2_PIN A1
#define APPS1_REST 2884 // calibrate: ADC at physical zero
#define APPS1_FULL 1835 // calibrate: ADC at full pedal
#define APPS2_REST 2910 // calibrate: ADC at physical zero
#define APPS2_FULL 1845 // calibrate: ADC at full pedal
#define PEDAL_DEADBAND_PERCENT 3
#define PEDAL_PLAUSIBILITY_PERCENT 10
#define MAX_ACCEL_PERCENT 100
#define TORQUE_MAX 32767

// ---------- Adafruit MPU -----------
#define MPU_ACCEL_RANGE MPU6050_RANGE_8_G
#define MPU_GYRO_RANGE MPU6050_RANGE_500_DEG
#define MPU_FILTER_BW MPU6050_BAND_21_HZ
#define MPU_FILTER_BW MPU6050_BAND_21_HZ

// ---------- BAMOCAR ----------
#define BAMOCAR_RX_ID 0x201 // Teensy → Bamocar
#define BAMOCAR_TX_ID 0x181 // Bamocar → Teensy
extern FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> Can1;
6 changes: 5 additions & 1 deletion src/Logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ void Logger::log(LogLevel level, const char* module, const char* msg) {
LogEntry entry;
snprintf(entry.data, MAX_LOG_LEN, "[%s] %s: %s", levelToStr(level), module, msg);

if (SERIAL_LOG_LEVEL >= level) {
Serial.println(entry.data);
}

_logBuffer.push(entry);
//TODO: consider having mode for nextion to display logs in real time
// serial writing is better done in smaller bursts, so use different buffer
Expand All @@ -43,7 +47,7 @@ void Logger::process() {
uint8_t count = 0;

// Pop and write in small bursts
while (_logBuffer.pop(&entry) && count < 5) {
while (_logBuffer.pop(entry) && count < 5) {
logFile.println(entry.data);
count++;
}
Expand Down
18 changes: 14 additions & 4 deletions src/Nextion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ void Nextion::sendCmd(const char *cmd) {

void Nextion::begin() {
NEXTION_SERIAL.begin(NEXTION_BAUD);
delay(100);
delay(100);
sendCmd(""); // clear RX buffer
sendCmd("bkcmd=1"); // enable Nextion command feedback
sendCmd("page 0");
}

void Nextion::page(uint8_t pageNumber) {
char cmd[8];
char cmd[16];
snprintf(cmd, sizeof(cmd), "page %d", pageNumber);
sendCmd(cmd);
}
Expand All @@ -29,6 +29,14 @@ void Nextion::sendText(const char *component, const char *text) {
}

void Nextion::sendNumber(const char *component, int16_t value) {
char cmd[32];
char buf[8];
itoa(value, buf, 10);
snprintf(cmd, sizeof(cmd), "%s.txt=\"%s\"", component, buf);
sendCmd(cmd);
}

void Nextion::sendNumberValue(const char *component, int16_t value) {
char cmd[32];
snprintf(cmd, sizeof(cmd), "%s.val=%d", component, value);
sendCmd(cmd);
Expand All @@ -44,8 +52,10 @@ void Nextion::updateDash(DashStatus dashStatus) {
sendNumber(NX_DRIVE_RPM, dashStatus.rpm);
sendNumber(NX_DRIVE_TORQUE, dashStatus.torque);
sendNumber(NX_DRIVE_DCBUS, dashStatus.dcBusV);
sendText(NX_DRIVE_FAULT, dashStatus.fault ? "FAULT" : "OK");
sendText(NX_DRIVE_STATE, dashStatus.driveOn ? "DRIVE: ON" : "DRIVE: OFF");
//sendText(NX_DRIVE_FAULT, dashStatus.fault ? "FAULT" : "OK");
//sendText(NX_DRIVE_STATE, dashStatus.driveOn ? "DRIVE: ON" : "DRIVE: OFF");
// TODO: update fault and drive state when we have that data from CAN
sendNumber(NX_DRIVE_MOTOR_TEMP, dashStatus.motorTemp);
sendNumber(NX_DRIVE_INVERTER_TEMP, dashStatus.inverterTemp);
sendNumberValue(NX_DRIVE_SPEED_BAR, dashStatus.torque);
}
44 changes: 44 additions & 0 deletions src/Pedal.cpp

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this where brake pedal pressure sensor inputs will be handled in the future too? if yes, add placeholders to ensure we do not forget to implement the following
EV2.3.1 — if brakes are mechanically actuated AND APPS signals >25% pedal travel (or >5kW, whichever is lower) simultaneously for more than 500ms, commanded torque must be 0 Nm.
EV2.3.2 — once that condition triggers, torque must stay at 0 Nm until APPS drops below 5% and 0 Nm is commanded — regardless of whether brakes are still on.

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "pedal.h"

float Pedal::appsPercent(int raw, int rest, int full) {
float pct = (float)(rest - raw) * 100.0f / (float)(rest - full);
pct = std::clamp(pct, 0.0f, 100.0f);
return pct;
}
Comment thread
swhelan123 marked this conversation as resolved.

int16_t Pedal::read() {
apps1Raw = analogRead(APPS1_PIN);
apps2Raw = analogRead(APPS2_PIN);

float pct1 = appsPercent(apps1Raw, APPS1_REST, APPS1_FULL);
float pct2 = appsPercent(apps2Raw, APPS2_REST, APPS2_FULL);

// Plausibility: sensors must agree within PEDAL_PLAUSIBILITY_PERCENT.
// Fault only latches after 3 consecutive bad readings (~60ms) to reject
// single noisy samples. Zero torque immediately on any bad reading.
if (fabsf(pct1 - pct2) > PEDAL_PLAUSIBILITY_PERCENT) {
plausibilityCount++;
if (plausibilityCount >= 3) fault = true;
return 0;
}
Comment on lines +16 to +23

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ T11.8.9 - implausibility is defined as >10 percentage points deviation between any two APPS sensors.
❌ T11.8.8 - if an implausibility persists for more than 100ms, power to the motor(s) must be immediately shut down completely. Shutting down via the motor controller is sufficient - you don't have to open the SDC, but the inverter must cut power.

11.8.9 is implemented fine with the macro, 11.8.8 should be implemented to comply completely with rules


plausibilityCount = 0;

// reset fault when pedal is released
if (fault) {
if (pct1 < PEDAL_DEADBAND_PERCENT && pct2 < PEDAL_DEADBAND_PERCENT) {
fault = false;
} else {
return 0;
}
}
// TODO: cut power if fault persistent for 100ms

float pct = (pct1 + pct2) * 0.5f;
if (pct < PEDAL_DEADBAND_PERCENT) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you do what you did below doing pct = std::max(0, PEDAL_DEADBAND_PERCENT?

i suppose that would mirror the same logic as what you've got here, but does that mean as you increase pedal it goes 0...0...3 all of a sudden?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the reason for the deadband in the old code is that when you configure rest to say 2000 and full to 1000, a bit of noise may make rest go to like 1956 or 2083 or something so rather than throwing errors if raw reading goes outside set range (drops to 1956) or send a small percentage torque request (goes to 2083) a small deadband is set around rest so this noise can be dealt with. would still obviously like to preserve a full 0-100 operational window though

pct = 0.0f;
}
pct = std::min(pct, (float)MAX_ACCEL_PERCENT);

return (int16_t)(TORQUE_MAX * (pct / 100.0f));
}
19 changes: 19 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,40 @@ Adafruit_MPU6050 MPU;
MpuController mpuController(MPU);
Button driveButton(BUTTON_PIN);
DashStatus dashStatus;
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> Can1;

void setup() {
Nextion::begin();
Nextion::bootStatus("INITIALISATION", "System booting up, initialising components");

Logger::begin();
if (SERIAL_LOG_LEVEL != LogLevel::NONE) {
Serial.begin(115200);
while (!Serial);
Serial.println("System Ready!");
Comment thread
swhelan123 marked this conversation as resolved.
}

Logger::log(LogLevel::INFO, "Main", "System booting up, initialising components");

mpuController.begin();

Nextion::bootStatus("INITIALISATION", "Initialisation complete, starting main loop");
Logger::log(LogLevel::INFO, "Main", "System initialised, starting main loop");

Nextion::page(NX_PAGE_DRIVE);
}

void loop() {
mpuController.logTelemetry();
//TODO: update dashStatus from CAN



//TODO: Implement brake and APPS signal checking
/* EV2.3.1 — if brakes are mechanically actuated AND APPS signals >25% pedal travel
(or >5kW, whichever is lower) simultaneously for more than 500ms, commanded torque
must be 0 Nm.
EV2.3.2 — once that condition triggers, torque must stay at 0 Nm until APPS drops
below 5% and 0 Nm is commanded — regardless of whether brakes are still on.
*/
}