/*
IO PinOut
SSD1306 OLED Display SPI PinOut for Teensy 3.2
D0 SCK 13
D1 MOSI 11
RES RST 8
DC AO 6
CS SS 7
Bouton TTP223 12
WS2812 RGB LED Ring = 5
CAN Controller is Internal to Teensy
CAN TX 3
CAN RX 4
CAN transceiver is Texas Instr. SN65HVD230
*/
const int buttonPin = 12;
bool buttonState, prevButtonState, timeToParty;
int page = 0;
long scanTime, lastUpdate, maxUpdate, pressTime, PBduration;
int nbCAN_Totalmsg, nbCAN_Tachmsg, nbCAN_Gearmsg, nbCAN_Wheelmsg;
// --------------- Chrono Instances
#include <Chrono.h> // https://github.com/SofaPirate/Chrono
Chrono ChronoFlashLent; // pour flasher quand moteur éteint, ou pas de CAN BUS
Chrono ChronoUpdateLCD; // pour refresh du LCD
Chrono ChronoUpdateScroll; // pour refresh du texte scrollant
Chrono ChronoUpdateFastLed; // pour refresh du Tach avec des leds RGB
Chrono ChronoCANStatRefresh; // pour refresh du calcul de statistique de frames/secondes
// --------------- SSD1306 Display variables
#include <Arduino.h>
#include <U8x8lib.h> // https://github.com/olikraus/u8g2
// U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE); // en I2c
U8X8_SSD1306_128X64_NONAME_4W_HW_SPI u8x8(7/*CS*/, 6/*DC*/, 8/*RES*/ ); // En SPI
String Title = " BRZ Tuning 2018/07/16 ";
String espace = " ";
String temp(4);
byte span = Title.length() -16; // nb de caractere qui dépasse le display
byte x = 0, step = 1;
// --------------- CAN BUS variables
#include <FlexCAN.h> // https://github.com/collin80/FlexCAN_Library
bool CANstatus, prevCANstatus;
long CANupdate, CANmsg_TimeStamp, elapseTime;
// --------------- FAST LED variables
#include <WS2812Serial.h> // https://github.com/PaulStoffregen/WS2812Serial Non blocking LED library
#define USE_WS2812SERIAL
#include "FastLED.h" // https://github.com/FastLED/FastLED
#define NUM_LEDS 16
#define DATA_PIN 5
CRGB leds[NUM_LEDS];
CRGB Rleds[NUM_LEDS]; // Reverse leds array
bool FlasherRapide, FlasherLent;
// --------------- BRZ speed variables
int EngSpd;
byte Gear, Throttle, AccPedal;
String GearStr(4); // String de 4 caracteres
int AvGa, AvDr, ArGa, ArDr, Steering;
int wheelSpd [4], deltaSpd, maxSpd, minSpd;
bool driftStatus, prevDriftStatus;
long driftStartTime, driftDuration;
//--------------------------------------------------------------------
void sort(int a[], int size) {
for(int i=0; i<(size-1); i++) {
for(int o=0; o<(size-(i+1)); o++) {
if(a[o] > a[o+1]) {
int t = a[o];
a[o] = a[o+1];
a[o+1] = t;
}
}
}
}
//---------------------------------------------------------------
void fadeall() { for(int i = 0; i < NUM_LEDS; i++) { leds[i].nscale8(250); } }
//--------------------------------------------------------------------
void cylon() {
static uint8_t hue = 0;
for(int i = 0; i < NUM_LEDS; i++) {
Rleds[i] = CHSV(hue, 255, 96);
hue = hue + 7;
FastLED.show();
fadeall();
delay(10);
}
for(int i = (NUM_LEDS)-1; i >= 0; i--) {
Rleds[i] = CHSV(hue, 255, 96);
hue = hue + 7;
FastLED.show();
fadeall();
delay(10);
}
}
//--------------------------------------------------------------------
String int_to_str (int x) { // conversion int a string de 4 caracteres
String inStr;
inStr = String(x);
while (inStr.length() < 4)
{
inStr = " " + inStr;
}
return inStr;
}
//--------------------------------------------------------------------
String int_to_str3D1D (int x) { // conversion int a string de 3 chiffres + 1 décimale
String inStr;
inStr = String(x);
while (inStr.length() < 4)
{
inStr = " " + inStr;
}
String outStr = inStr.substring(0,3) + "." + inStr.substring(3,4);
return outStr;
}
//--------------------------------------------------------------------
void reverseLeds() { // Reverse order of led array for clockwise tach effect
uint8_t left = 0;
uint8_t right = NUM_LEDS-1;
while (left < NUM_LEDS) {
Rleds [left++] = leds[right--];
}
}
void defaultDisplay(){ // draw default display (page 0)
u8x8.clearDisplay();
u8x8.drawString(0,1," CANBUS TACH ");
u8x8.drawString(0,2,"Gear");
u8x8.drawString(0,3,"Tach(rpm)");
u8x8.drawString(0,4,"Av Gauche");
u8x8.drawString(0,5,"Av Droite");
u8x8.drawString(0,6,"Ar Gauche");
u8x8.drawString(0,7,"Ar Droite");
}
void CANBusAnalyserDisplay(){ // draw alternate display (page 1)
u8x8.clearDisplay();
u8x8.drawString(0,1,"CANBUS ANALYSER");
//u8x8.drawString(0,2,"Update ms");
u8x8.drawString(0,3,"CAN BUS fr/sec");
u8x8.drawString(0,4,"Total frame");
u8x8.drawString(0,5,"Tach");
u8x8.drawString(0,6,"Gear");
u8x8.drawString(0,7,"Wheel Speed");
}
void DriftDisplay1(){ // draw drift display (page 2)
u8x8.clearDisplay();
u8x8.drawString(0,1," Drift Timer ");
//u8x8.drawString(0,2,"");
u8x8.drawString(0,3,"Time");
u8x8.drawString(0,4,"Speed");
u8x8.drawString(0,5,"Tach");
u8x8.drawString(0,6,"Gear");
u8x8.drawString(0,7,"Steering");
}
void DriftDisplay2(){ // draw drift display (page 3)
u8x8.clearDisplay();
u8x8.drawString(0,1,"Drift Statistics");
u8x8.drawString(0,2,"Delta Speed");
u8x8.drawString(0,3,"Max Speed");
u8x8.drawString(0,4,"Min Speed");
u8x8.drawString(0,5,"Drift Time");
u8x8.drawString(0,6,"Throttle");
u8x8.drawString(0,7,"AccPedal");
}
//---------------------------------------------------------------
class ExampleClass : public CANListener
{
public:
void printFrame(CAN_message_t &frame, int mailbox);
bool frameHandler(CAN_message_t &frame, int mailbox, uint8_t controller); //overrides the parent version so we can actually do something
};
void ExampleClass::printFrame(CAN_message_t &frame, int mailbox)
{
nbCAN_Totalmsg++;
CANmsg_TimeStamp = millis();
switch (frame.id) {
case 0x140: // filtre sur la vitesse moteur
nbCAN_Tachmsg++;
AccPedal = frame.buf[0] * 0.392157; // Acceleration Pedal 0-100%
//Serial.println(AccPedal);
EngSpd = ((frame.buf[3] & 63) * 256) + frame.buf[2];
//Serial.println(EngSpd); // pour Arduino IDE Serial Plotter
Throttle = frame.buf[6] * 0.392157; // Throttle 0-100%
break;
case 0x141: // filtre sur la transmission
nbCAN_Gearmsg++;
Gear = (frame.buf[6] & 15);
if ((Gear > 0) & (Gear < 7)) {
GearStr = int_to_str(Gear);
}
else {GearStr = " N"; }
break;
case 0x0D0: // filtre sur la position du volant en degree
Steering = ((frame.buf[1] * 256 + frame.buf[0])*0.1); // Steering position en degrés *100
break;
case 0x0D4: // filtre sur les vitesses des roues
nbCAN_Wheelmsg++;
AvGa = ((frame.buf[1] * 256 + frame.buf[0])*0.5748);// vitesse en km/h X 10
AvDr = ((frame.buf[3] * 256 + frame.buf[2])*0.5748);// vitesse en km/h X 10
ArGa = ((frame.buf[5] * 256 + frame.buf[4])*0.5748);// vitesse en km/h X 10
ArDr = ((frame.buf[7] * 256 + frame.buf[6])*0.5748);// vitesse en km/h X 10
if ((page == 3) | (page == 2)) {
wheelSpd [0] = AvGa;
wheelSpd [1] = AvDr;
wheelSpd [2] = ArGa;
wheelSpd [3] = AvDr;
sort(wheelSpd,4); //Trier les vitesses des roues par ordre croissant
maxSpd = wheelSpd [3];
minSpd = wheelSpd [0];
deltaSpd = maxSpd-minSpd;
if (deltaSpd >= 30 ) {
driftStatus = 1;
driftDuration = millis() - driftStartTime;
}
else driftStatus = 0;
if ((driftStatus == 1) & (prevDriftStatus == 0)) {
driftStartTime = millis();
}
prevDriftStatus = driftStatus;
}
break;
}
}
bool ExampleClass::frameHandler(CAN_message_t &frame, int mailbox, uint8_t controller)
{
printFrame(frame, mailbox);
return true;
}
ExampleClass exampleClass;
//--------------------------------------------------------------------
void setup() {
pinMode(buttonPin, INPUT); // Touch button input
LEDS.addLeds<WS2812SERIAL,DATA_PIN,BRG>(Rleds,NUM_LEDS);
LEDS.setBrightness(84);
u8x8.begin();
u8x8.setPowerSave(0);
u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
defaultDisplay();
Serial.begin(115200); // For debug use
Can0.begin(500000);
Can0.attachObj(&exampleClass);
exampleClass.attachGeneralHandler();
}
//---------------------------------------------------------------------
void loop()
{
scanTime = millis() - lastUpdate;
lastUpdate = millis();
if (scanTime > maxUpdate) maxUpdate = scanTime;
buttonState = digitalRead(buttonPin); // toggle page a chaque fois que le bouton est activé
if ((buttonState == HIGH) & (prevButtonState == 0)) { // one scan only - detection de front montant
pressTime = millis();
page++;
if ((page == 0) | (page == 4)) { // go to regular CAN Data page
page = 0;
defaultDisplay();
}
else if (page == 1) { // go to CAN frame rate analysis page
CANBusAnalyserDisplay();
nbCAN_Totalmsg = 0;
nbCAN_Tachmsg = 0;
nbCAN_Gearmsg = 0;
nbCAN_Wheelmsg = 0;
}
else if (page == 2) { // go to regular CAN Data page
DriftDisplay1();
}
else if (page == 3) { // go to 2nd CAN Data page
DriftDisplay2();
}
}
prevButtonState = buttonState;
if (buttonState == HIGH) {
PBduration = millis() - pressTime;
if (PBduration > 2500) {
cylon();
timeToParty = 1;
}
}
else {timeToParty = 0;}
if (ChronoUpdateScroll.hasPassed(500) == 1) { // scroll aller-retour du titre au 500ms
ChronoUpdateScroll.restart();
u8x8.setCursor(0, 0);
u8x8.print(Title.substring(x, x+16));
x = x + step;
if (x == span || x == 0) {
step = step * -1 ;
}
}
// CAN bus live status detection; 0 = No CAN Data present, 1 = CAN Data incoming
CANupdate = millis() - CANmsg_TimeStamp;
if ( CANupdate < 100 ) {
CANstatus = 1;
if (prevCANstatus == 0) u8x8.drawString(0,1,"Live CANBUS Data") ;
}
else {
CANstatus = 0;
u8x8.drawString(0,1,"No CAN BUS Data ") ;
}
prevCANstatus = CANstatus;
// -------------------------- SD1306 128x64 OLED Display page 1 when CANstatus == 1
if ((CANstatus == 1) & (page == 1) & (ChronoCANStatRefresh.hasPassed(1000) == 1)) {
elapseTime = ChronoCANStatRefresh.elapsed();
ChronoCANStatRefresh.restart();
temp = int_to_str(scanTime); // nd de scan en 1 seconde = rate
u8x8.setCursor(12, 2);
//u8x8.print(temp);
maxUpdate = 0;
nbCAN_Totalmsg = nbCAN_Totalmsg * (1000.0 / elapseTime);
temp = int_to_str(nbCAN_Totalmsg);
nbCAN_Totalmsg = 0,
u8x8.setCursor(12, 4);
u8x8.print(temp);
nbCAN_Tachmsg = nbCAN_Tachmsg * (1000.0 / elapseTime);
temp = int_to_str(nbCAN_Tachmsg);
nbCAN_Tachmsg = 0,
u8x8.setCursor(12, 5);
u8x8.print(temp);
nbCAN_Gearmsg = nbCAN_Gearmsg * (1000.0 / elapseTime);
temp = int_to_str(nbCAN_Gearmsg);
nbCAN_Gearmsg = 0,
u8x8.setCursor(12, 6);
u8x8.print(temp);
nbCAN_Wheelmsg = nbCAN_Wheelmsg * (1000.0 / elapseTime);
temp = int_to_str(nbCAN_Wheelmsg);
nbCAN_Wheelmsg = 0;
u8x8.setCursor(12, 7);
u8x8.print(temp);
}
// ---------------------------- Refresh SD1306 128x64 OLED Display page 0 when CANstatus == 1
if (ChronoUpdateLCD.hasPassed(250) == 1) { // Update LCD au 250ms
ChronoUpdateLCD.restart();
if ((CANstatus == 1) & (page == 0)) { // refresh page au 100msec
long LCDrefreshTime = millis();
u8x8.setCursor(12, 2);
u8x8.print(GearStr); // Gear
u8x8.setCursor(12, 3);
temp = int_to_str(EngSpd); // Revolution
u8x8.print(temp);
u8x8.setCursor(11, 4);
temp = int_to_str3D1D(AvGa); // Roue Avant Gauche
u8x8.print(temp);
u8x8.setCursor(11, 5);
temp = int_to_str3D1D(AvDr); // Roue Avant Droite
u8x8.print(temp);
u8x8.setCursor(11, 6);
temp = int_to_str3D1D(ArGa); // Roue Arriere Gauche
u8x8.print(temp);
u8x8.setCursor(11, 7);
temp = int_to_str3D1D(ArDr); // Roue Arriere Droite
u8x8.print(temp);
LCDrefreshTime = millis() - LCDrefreshTime;
// Serial.println(LCDrefreshTime); // mesure du temps de refresh (I2C Vs SPI)
// égal 23msec en I2C, égal 5msec en SPI
}
if ((CANstatus == 1) & (page == 2)) { // refresh page au 100msec
u8x8.setCursor(12, 3);
temp = int_to_str(driftDuration); //
u8x8.print(temp);
u8x8.setCursor(11, 4);
temp = int_to_str3D1D(minSpd); //
u8x8.print(temp); //
u8x8.setCursor(12, 5);
temp = int_to_str(EngSpd); //
u8x8.print(temp);
u8x8.setCursor(12, 6);
u8x8.print(GearStr);
if (Steering < -20 ) {
u8x8.setCursor(9, 7);
u8x8.print("Ga");
}
else if (Steering > 20 ) {
u8x8.setCursor(9, 7);
u8x8.print("Dr");
}
else {
u8x8.setCursor(9, 7);
u8x8.print(" ");
}
u8x8.setCursor(11, 7);
temp = int_to_str3D1D(Steering); //
u8x8.print(temp);
}
if ((CANstatus == 1) & (page == 3)) { // refresh page au 100msec
u8x8.setCursor(11, 2);
temp = int_to_str3D1D(deltaSpd); //
u8x8.print(temp); //
u8x8.setCursor(11, 3);
temp = int_to_str3D1D(maxSpd); //
u8x8.print(temp);
u8x8.setCursor(11, 4);
temp = int_to_str3D1D(minSpd); //
u8x8.print(temp);
u8x8.setCursor(12, 5);
//temp = int_to_str(driftDuration); //
u8x8.print(driftDuration);
u8x8.setCursor(12, 6);
temp = int_to_str(Throttle); //
u8x8.print(temp);
u8x8.setCursor(12, 7);
temp = int_to_str(AccPedal); //
u8x8.print(temp);
}
// ---------------------------- Refresh SD1306 128x64 OLED Display page 0 when CANstatus == 0
else if (CANstatus == 0) { // erase display when no CAN data available
u8x8.setCursor(12, 2);
u8x8.print(espace);
u8x8.setCursor(12, 3);
u8x8.print(espace);
u8x8.setCursor(12, 4);
u8x8.print(espace);
u8x8.setCursor(12, 5);
u8x8.print(espace);
u8x8.setCursor(12, 6);
u8x8.print(espace);
u8x8.setCursor(12, 7);
u8x8.print(espace);
}
}
//---------------------------------------------------------------
// WS2812 LEDs update
if (ChronoFlashLent.hasPassed(800) == 1) { // toggle au 800msec
ChronoFlashLent.restart();
if (FlasherLent == HIGH) FlasherLent = LOW;
else FlasherLent = HIGH;
}
if (CANstatus == 1) {
if ((ChronoUpdateFastLed.hasPassed(200) == 1) & (timeToParty == 0)){ // refresh au 200msec
ChronoUpdateFastLed.restart();
if (FlasherRapide == HIGH) FlasherRapide = LOW;
else FlasherRapide = HIGH;
int numLedsToLight = map(EngSpd, 0, 8001, 0, NUM_LEDS);
long WS2812RefreshTime = micros();
for(int i = 0; i < NUM_LEDS; i++) {
if (i < numLedsToLight) { leds[i] = CHSV(160, 255, 128); } // Blue 1/2 brightness
if ((i == numLedsToLight) & (FlasherRapide == 1)) { leds[i] = CRGB(48, 48, 48); } // White 3/8 brightness
else if ((i == numLedsToLight) & (FlasherRapide == 0)) { leds[i] = CRGB(0, 0, 0); } // Black
if ((i >= numLedsToLight) & (EngSpd >= 7400)) { leds[i] = CHSV(0, 255, 128); } // Red 1/2 brightness
if (i > numLedsToLight) { leds[i] = CHSV(160, 255, 0); } // Black
// First LED flash Green when engine in idle
if ((i == 0) & (EngSpd < 500) & (FlasherLent == 1)) { leds[i] = CHSV(96, 255, 128);} // Green 1/2 brightness
else if ((i == 0) & (EngSpd < 500) & (FlasherLent == 0)) { leds[i] = CHSV(96, 255, 0);} // Black
if ((i > 0) & (EngSpd < 500)) { leds[i] = CRGB(0, 0, 0); } // Black
}
reverseLeds();
FastLED.show();
WS2812RefreshTime = micros() - WS2812RefreshTime;
//Serial.println(WS2812RefreshTime);
}
}
else {
// First LED flash Red when no CAN BUS communication
for(int i = 0; i < NUM_LEDS; i++) {
if ((i == 0) & (FlasherLent == 1)) { leds[i] = CHSV(0, 255, 128);} // Red 1/2 brightness
if ((i == 0) & (FlasherLent == 0)) { leds[i] = CHSV(0, 255, 0);} // Black
if (i > 0 ) { leds[i] = CRGB(0, 0, 0); } // Black
}
reverseLeds();
FastLED.show();
}
}
//--------------------------------------------------------------