diff --git a/.gitignore b/.gitignore index 89cc49c..5203348 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +/logs diff --git a/include/Button.h b/include/Button.h index d4db6b3..f0e57ee 100644 --- a/include/Button.h +++ b/include/Button.h @@ -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); }; \ No newline at end of file diff --git a/include/CircularBuffer.h b/include/CircularBuffer.h index 371108b..9412026 100644 --- a/include/CircularBuffer.h +++ b/include/CircularBuffer.h @@ -4,7 +4,7 @@ template class CircularBuffer { private: - T buffer[size]; + T _buffer[size]; size_t _head = 0; size_t _tail = 0; bool _full = false; @@ -19,7 +19,7 @@ class CircularBuffer { _full = _head == _tail; } - bool pop (&T item) { + bool pop (T &item) { if (isEmpty()) { return false; } diff --git a/include/Logger.h b/include/Logger.h index 527c620..c342e18 100644 --- a/include/Logger.h +++ b/include/Logger.h @@ -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 _logBuffer; diff --git a/include/MpuController.h b/include/MpuController.h index beaed81..acb0ad5 100644 --- a/include/MpuController.h +++ b/include/MpuController.h @@ -1,3 +1,4 @@ +#pragma once #include "config.h" #include "Logger.h" #include "Nextion.h" diff --git a/include/Nextion.h b/include/Nextion.h index 4230395..3d55506 100644 --- a/include/Nextion.h +++ b/include/Nextion.h @@ -1,3 +1,4 @@ +#pragma once #include "config.h" // Nextion connected to Serial7 (TX7/RX7 on Teensy 4.1) @@ -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 @@ -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); }; diff --git a/include/Pedal.h b/include/Pedal.h new file mode 100644 index 0000000..86a8c32 --- /dev/null +++ b/include/Pedal.h @@ -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(); +}; \ No newline at end of file diff --git a/include/config.h b/include/config.h index b68a5bb..44ccd3a 100644 --- a/include/config.h +++ b/include/config.h @@ -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 @@ -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 \ No newline at end of file +#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; \ No newline at end of file diff --git a/src/Logger.cpp b/src/Logger.cpp index bb7ac0e..58f914c 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -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 @@ -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++; } diff --git a/src/Nextion.cpp b/src/Nextion.cpp index 3824925..8abb855 100644 --- a/src/Nextion.cpp +++ b/src/Nextion.cpp @@ -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); } @@ -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); @@ -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); } \ No newline at end of file diff --git a/src/Pedal.cpp b/src/Pedal.cpp new file mode 100644 index 0000000..a174cae --- /dev/null +++ b/src/Pedal.cpp @@ -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; +} + +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; + } + + 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) { + pct = 0.0f; + } + pct = std::min(pct, (float)MAX_ACCEL_PERCENT); + + return (int16_t)(TORQUE_MAX * (pct / 100.0f)); +} diff --git a/src/main.cpp b/src/main.cpp index 4d3cea6..0f150ac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,21 +9,40 @@ Adafruit_MPU6050 MPU; MpuController mpuController(MPU); Button driveButton(BUTTON_PIN); DashStatus dashStatus; +FlexCAN_T4 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!"); + } + 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. + */ }