├── Dash.JPG ├── ClassStuff.zip ├── FSAE Dash v0.2.pdf ├── Enclosure ├── Dash Backplate.SLDPRT ├── Dash Faceplate.SLDPRT ├── Dash Faceplate Version 2.SLDPRT ├── Dash v0.2 Backplate Version 2.STL ├── Dash v0.2 Backplate - Formlabs Clear Resin.STL └── Dash v0.2 Faceplate - Formlabs Clear Resin.STL ├── .gitattributes ├── KS3E_2560 ├── Tasks.cpp ├── Tasker.cpp ├── Tasker.h ├── 2560Lib.h ├── 2560Lib.cpp └── KS3E_2560.ino ├── .gitignore ├── Readme.md ├── Dash0.2_328P_Code └── Dash0.2_328P_Code.ino ├── KS3E_328P └── KS3E_328P.ino ├── LICENSE.txt └── Dash0.2_2560_Code └── Dash0.2_2560_Code.ino /Dash.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClintonFlowers/FSAE-Dashboard/HEAD/Dash.JPG -------------------------------------------------------------------------------- /ClassStuff.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClintonFlowers/FSAE-Dashboard/HEAD/ClassStuff.zip -------------------------------------------------------------------------------- /FSAE Dash v0.2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClintonFlowers/FSAE-Dashboard/HEAD/FSAE Dash v0.2.pdf -------------------------------------------------------------------------------- /Enclosure/Dash Backplate.SLDPRT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClintonFlowers/FSAE-Dashboard/HEAD/Enclosure/Dash Backplate.SLDPRT -------------------------------------------------------------------------------- /Enclosure/Dash Faceplate.SLDPRT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClintonFlowers/FSAE-Dashboard/HEAD/Enclosure/Dash Faceplate.SLDPRT -------------------------------------------------------------------------------- /Enclosure/Dash Faceplate Version 2.SLDPRT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClintonFlowers/FSAE-Dashboard/HEAD/Enclosure/Dash Faceplate Version 2.SLDPRT -------------------------------------------------------------------------------- /Enclosure/Dash v0.2 Backplate Version 2.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClintonFlowers/FSAE-Dashboard/HEAD/Enclosure/Dash v0.2 Backplate Version 2.STL -------------------------------------------------------------------------------- /Enclosure/Dash v0.2 Backplate - Formlabs Clear Resin.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClintonFlowers/FSAE-Dashboard/HEAD/Enclosure/Dash v0.2 Backplate - Formlabs Clear Resin.STL -------------------------------------------------------------------------------- /Enclosure/Dash v0.2 Faceplate - Formlabs Clear Resin.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClintonFlowers/FSAE-Dashboard/HEAD/Enclosure/Dash v0.2 Faceplate - Formlabs Clear Resin.STL -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | 19 | -------------------------------------------------------------------------------- /KS3E_2560/Tasks.cpp: -------------------------------------------------------------------------------- 1 | #include "Tasker.h" 2 | 3 | class HandleCan : public TaskClass { 4 | public: 5 | void execute() override { 6 | int packetSize = CAN.parsePacket(); 7 | 8 | if (packetSize) { 9 | // received a packet 10 | showText("rxd"); 11 | 12 | if (CAN.packetExtended()) { 13 | 14 | } 15 | 16 | if (CAN.packetRtr()) { 17 | // Remote transmission request, packet contains no data 18 | 19 | } 20 | 21 | 22 | if (CAN.packetRtr()) { 23 | 24 | } else { 25 | 26 | 27 | // only print packet data for non-RTR packets 28 | while (CAN.available()) { 29 | // Serial.print((char)CAN.read()); 30 | } 31 | } 32 | } 33 | } 34 | 35 | private: 36 | volatile unsigned int getExecutionDelay() override { return 1000000; } 37 | }; 38 | 39 | class PrintLad : public TaskClass { 40 | public: 41 | void execute() override { 42 | // Serial.println("lad"); 43 | } 44 | 45 | private: 46 | volatile unsigned int getExecutionDelay() override { return 6000100; } 47 | }; 48 | -------------------------------------------------------------------------------- /KS3E_2560/Tasker.cpp: -------------------------------------------------------------------------------- 1 | #include "Tasker.h" 2 | #include "Tasks.cpp" 3 | 4 | void Tasker::init() { 5 | // We can set pin modes here 6 | 7 | if (!Serial) { 8 | Serial.begin(115200); // We do not wait for Serial to be initialized 9 | } 10 | 11 | // Declare tasks which should be executed 12 | tasks[0] = new HandleCan(); 13 | tasks[1] = new PrintLad(); 14 | 15 | // Default variable initialization 16 | // Sensor input pins 17 | this->maxAdcValue = pow(2, ADC_READ_RESOLUTION); 18 | this->loopsCompleted = 0; 19 | } 20 | 21 | void Tasker::taskLoop() { 22 | for (int i = 0; i < TASKS_ARRAY_SIZE; i++) { 23 | volatile long currentTime = micros(); // Putting this inside the for loop may make sense 24 | if (tasks[i]->shouldExecute(currentTime)) { 25 | tasks[i]->execute(); 26 | } 27 | } 28 | this->loopsCompleted++; 29 | } 30 | 31 | long Tasker::getLoopsCompleted() { 32 | long result = this->loopsCompleted; 33 | this->loopsCompleted = 0; 34 | return result; 35 | } 36 | 37 | float Tasker::mapf(float x, float in_min, float in_max, float out_min, float out_max) { 38 | return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 39 | } 40 | 41 | bool Tasker::strContains(const String &outer, const String &inner) { 42 | return outer.indexOf(inner) >= 0; 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore list for Eagle, a PCB layout tool 2 | 3 | # Backup files 4 | *.s#? 5 | *.b#? 6 | *.l#? 7 | 8 | # Eagle project file 9 | # It contains a serial number and references to the file structure 10 | # on your computer. 11 | # comment the following line if you want to have your project file included. 12 | eagle.epf 13 | 14 | # Autorouter files 15 | *.pro 16 | *.job 17 | 18 | # CAM files 19 | *.$$$ 20 | *.cmp 21 | *.ly2 22 | *.l15 23 | *.sol 24 | *.plc 25 | *.stc 26 | *.sts 27 | *.crc 28 | *.crs 29 | 30 | *.dri 31 | *.drl 32 | *.gpi 33 | *.pls 34 | 35 | *.drd 36 | *.drd.* 37 | 38 | *.info 39 | 40 | *.eps 41 | 42 | # file locks introduced since 7.x 43 | *.lck 44 | 45 | 46 | # ========================= 47 | # Operating System Files 48 | # ========================= 49 | 50 | # OSX 51 | # ========================= 52 | 53 | .DS_Store 54 | .AppleDouble 55 | .LSOverride 56 | 57 | # Thumbnails 58 | ._* 59 | 60 | # Files that might appear in the root of a volume 61 | .DocumentRevisions-V100 62 | .fseventsd 63 | .Spotlight-V100 64 | .TemporaryItems 65 | .Trashes 66 | .VolumeIcon.icns 67 | 68 | # Directories potentially created on remote AFP share 69 | .AppleDB 70 | .AppleDesktop 71 | Network Trash Folder 72 | Temporary Items 73 | .apdisk 74 | 75 | # Windows 76 | # ========================= 77 | 78 | # Windows image file caches 79 | Thumbs.db 80 | ehthumbs.db 81 | 82 | # Folder config file 83 | Desktop.ini 84 | 85 | # Recycle Bin used on file shares 86 | $RECYCLE.BIN/ 87 | 88 | # Windows Installer files 89 | *.cab 90 | *.msi 91 | *.msm 92 | *.msp 93 | 94 | # Windows shortcuts 95 | *.lnk 96 | 97 | -------------------------------------------------------------------------------- /KS3E_2560/Tasker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * General-purpose task scheduler class 3 | * Clinton Flowers, 2019-6-2 4 | */ 5 | 6 | #ifndef TASKER_H 7 | #define TASKER_H 8 | 9 | #include 10 | 11 | #define TASKS_ARRAY_SIZE 2 // This must match the number of tasks that are defined in the init method. Ideally, a linked list would obviate the need for this, but that is added complexity. 12 | #define ADC_READ_RESOLUTION 10 // For Teensy. Default is 10. Above 12, read time increases drastically. 13 | 14 | #include 15 | #include "2560Lib.h" 16 | 17 | 18 | class TaskClass { 19 | protected: 20 | friend class Tasker; 21 | 22 | virtual void execute() = 0; 23 | 24 | bool shouldExecute(volatile unsigned long currentTime) { 25 | if (currentTime > lastExecutionTime + getExecutionDelay()) { 26 | lastExecutionTime = currentTime; 27 | return true; 28 | } else { 29 | return false; 30 | } 31 | }; 32 | 33 | TaskClass() = default;; 34 | ~TaskClass() = default;; 35 | 36 | private: 37 | virtual volatile unsigned int getExecutionDelay() = 0; 38 | volatile unsigned int lastExecutionTime = 0; 39 | }; 40 | 41 | class Tasker { 42 | public: 43 | void init(); 44 | 45 | void taskLoop(); // Call this in the main loop to evaluate and execute tasks 46 | 47 | // Debug information 48 | long getLoopsCompleted(); // Not idempotent 49 | int getTasksSize(); // TODO: Unused? 50 | 51 | // Utility methods 52 | static float mapf(float x, float in_min, float in_max, float out_min, float out_max); 53 | static bool strContains(const String & superset, const String & subset); 54 | 55 | protected: 56 | // Friend classes (usually VCUTask classes) which can access the protected VCU variables here 57 | //friend class DoSpecificDebugThing; 58 | 59 | // Debug variables 60 | int loopsCompleted{}; 61 | 62 | // Other operational variables 63 | TaskClass **tasks = new TaskClass *[TASKS_ARRAY_SIZE]; 64 | int maxAdcValue{}; 65 | }; 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /KS3E_2560/2560Lib.h: -------------------------------------------------------------------------------- 1 | #ifndef MEGA_LIB 2 | #define MEGA_LIB 3 | 4 | #include 5 | 6 | #define NUMBER_OF_ALPHANUMERICS 3 7 | 8 | // These arrays are used to map the alphanumeric LED's to the associated code character. 9 | // Each bit position corresponds to a segment of the LED; adding the correct bits together and then shifting that byte into the driver will light up the segments for that character 10 | // Since there are 15 positions per digit, we use 2 8-bit bytes, so a Top and a Bottom 11 | const char characterList[] = {' ', '$', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; 12 | const byte characterBottoms[] = {0x00, 0x65, 0xe2, 0x20, 0xc1, 0x61, 0x21, 0x61, 0xe1, 0x20, 0xe1, 0x21, 0xa1, 0x65, 0xc0, 0x64, 0xc1, 0x81, 0xe0, 0xa1, 0x44, 0xe0, 0x89, 0xc0, 0xa0, 0xa8, 0xe0, 0x81, 0xe8, 0x89, 0x60, 0x04, 0xe0, 0x82, 0xaa, 0x0a, 0x05, 0x42}; 13 | const byte characterTops[] = {0x00, 0x6a, 0x56, 0x14, 0x0e, 0x0e, 0x4c, 0x4a, 0x4a, 0x06, 0x4e, 0x4e, 0x4e, 0x2e, 0x42, 0x26, 0x42, 0x42, 0x4a, 0x4c, 0x22, 0x04, 0x50, 0x40, 0x55, 0x45, 0x46, 0x4e, 0x46, 0x4e, 0x0b, 0x22, 0x44, 0x50, 0x44, 0x11, 0x4c, 0x12}; 14 | 15 | // Various control variables for the TLC59281RGER Constant Current LED Driver 16 | const int blankPin = 65; 17 | const int latchPin = 64; 18 | const int clockPin = 63; 19 | const int dataPin = 62; 20 | // Pins for the joystick on the rear of the device, accounting for improper hardware placement. Unused in current code. 21 | const int centerPin = PE6; // Center in schematic, J_Common as installed. 22 | const int rightPin = 19; // J_Left in schematic 23 | const int leftPin = PE7; // J_Right in schematic 24 | const int downPin = 2; // J_Up in schematic 25 | const int upPin = 3; // J_Down in schematic 26 | // How many (14-segment + dp) alphanumeric displays are connected 27 | 28 | extern byte dataOne, dataTwo, dataThree, dataFour, dataFive, dataSix; 29 | 30 | extern void shiftOut(int myDataPin, int myClockPin, byte myDataOut); 31 | extern void showText (String whatToWrite); 32 | 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # KSU Motorsports Dashboard & Data Logger 2 | 3 | ![KS-2 Dashboard](/Dash.JPG "KS-2 Dashboard") 4 | 5 | This is a custom dashboard / shift-light module created for the Kennesaw State University Motorsports student-built race car, the KS-2. 6 | In a 5" x 1.25" x 0.5" footprint, it has: 7 | 8 | * 20 individually-addressable RGB LED's (Neopixels) 9 | * 3 14-segment alphanumeric LED elements for string output via scrolling text 10 | * CAN bus communication with solderable termination jumper 11 | * Onboard 12V to 5V switching converter plus overvoltage, transient, overcurrent, and reverse-voltage protection (i.e., Partially Student Resistant) 12 | * A 9-DOF (accelerometer/gyro/magnetometer) inertial measurement unit with integrated sensor fusion 13 | * A battery-backed Realtime Clock 14 | * A Micro SD Card slot 15 | * A 5-way Tactile Switch/Joystick 16 | * Expansion capability via a 0.1" header at the bottom left, designed for a 16x2 character matrix or any I2C device 17 | 18 | It's based on a combination of a Atmega328P (Arduino Uno) and a Atmega2560 (Arduino Mega) to ensure team members can program it into the future. 19 | 20 | I've included a PDF to view the schematic without EagleCAD, and the models (STL/SLDPRT) of the enclosure pieces. The version 1 faceplate and backplate were designed for Formlabs clear resin, as the backplate's integrated cable-tie strain-relief loops may break more easily on FDM prints (due to Z-axis layer adhesion). Newer designs (version 2+) use a thin waterjet polycarbonate faceplate for simplicity. 21 | 22 | Feel free to contact me with any questions, 23 | 24 | Clinton Flowers 25 | 26 | clintonflowers222@gmail.com 27 | 28 | Copyright 2017 Clinton Flowers 29 | 30 | Licensed under the Apache License, Version 2.0 (the "License"); 31 | you may not use this file except in compliance with the License. 32 | You may obtain a copy of the License at 33 | 34 | http://www.apache.org/licenses/LICENSE-2.0 35 | 36 | Unless required by applicable law or agreed to in writing, software 37 | distributed under the License is distributed on an "AS IS" BASIS, 38 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 39 | See the License for the specific language governing permissions and 40 | limitations under the License. 41 | -------------------------------------------------------------------------------- /Dash0.2_328P_Code/Dash0.2_328P_Code.ino: -------------------------------------------------------------------------------- 1 | //----------------------- UNO/328P 2 | // KSU Motorsports/FSAE KS-2 Dashboard ATmega328P Firmware 3 | // Clinton Flowers, 2017 4 | 5 | #include 6 | 7 | #include // Get this from the Arduino IDE Library Manager 8 | #ifdef __AVR__ 9 | #include 10 | #endif 11 | 12 | // Which pin on the Arduino is connected to the NeoPixels? 13 | #define PIN 8 14 | 15 | // How many NeoPixels are attached to the Arduino? 16 | #define NUMPIXELS 20 17 | #define numRows 2 18 | 19 | Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); 20 | 21 | int delayval = 500; // delay for half a second 22 | int8_t temp = 50; 23 | int unoRpms = 0; // Local storage of engine RPM's 24 | int megaAddress = 6; 25 | int unoAddress = 5; 26 | int ledArgs[7]; 27 | 28 | void setup() { 29 | pixels.begin(); // This initializes the NeoPixel library. 30 | Wire.begin(unoAddress); // Join the I2C bus 31 | Wire.setClock(400000L); 32 | Wire.onReceive(receiveRpmHandler); 33 | } // End setup() 34 | 35 | void loop() { 36 | delay(1); // The latest code version is entirely based on I2C-initiated interrupts; see receiveRpmHandler 37 | } // End loop() 38 | 39 | void setPixelGroup(int beginPixels, int endPixels, int delayTime, int red, int green, int blue, int mode){ 40 | // Multiple "modes" for different driver preferences on how the lights light up 41 | if(mode == 0){ 42 | for(int i=beginPixels-1;i 0){ 47 | pixels.show(); // This sends the updated pixel color to the hardware. 48 | delay(delayTime); // Delay for a period of time (in milliseconds). 49 | } 50 | } 51 | }else if(mode == 1){ 52 | for(int i=beginPixels-1;i 0){ 59 | pixels.show(); // This sends the updated pixel color to the hardware. 60 | delay(delayTime); // Delay for a period of time (in milliseconds). 61 | } 62 | } 63 | } 64 | pixels.show(); // This sends the updated pixel color to the hardware. 65 | } // End setPixelGroup(int, int, int, int, int, int, int) 66 | 67 | // Function that executes whenever data is received from the I2C master 2560 uC 68 | // This function is registered as an event/ISR, see setup() 69 | void receiveRpmHandler(int howMany) { 70 | for(int i = 0; i < sizeof(ledArgs); i++){ 71 | ledArgs[i] = Wire.read(); 72 | } 73 | pixels.clear(); 74 | setPixelGroup(ledArgs[0], ledArgs[1], ledArgs[2], ledArgs[3], ledArgs[4], ledArgs[5], ledArgs[6]); 75 | } // End receiveRpmHandler() 76 | 77 | -------------------------------------------------------------------------------- /KS3E_328P/KS3E_328P.ino: -------------------------------------------------------------------------------- 1 | //----------------------- UNO/328P 2 | // KSU Motorsports/FSAE KS-2 Dashboard ATmega328P Firmware 3 | // Clinton Flowers, 2017 4 | 5 | #include 6 | #include 7 | 8 | #include // Get this from the Arduino IDE Library Manager 9 | #ifdef __AVR__ 10 | #include 11 | #endif 12 | 13 | // Which pin on the Arduino is connected to the NeoPixels? 14 | #define PIN 8 15 | 16 | // How many NeoPixels are attached to the Arduino? 17 | #define NUMPIXELS 20 18 | #define numRows 2 19 | 20 | Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); 21 | 22 | int delayval = 500; // delay for half a second 23 | int8_t temp = 50; 24 | int unoRpms = 0; // Local storage of engine RPM's 25 | int megaAddress = 6; 26 | int unoAddress = 5; 27 | int ledArgs[7]; 28 | 29 | void setup() { 30 | wdt_enable(WDTO_120MS); 31 | pixels.begin(); // This initializes the NeoPixel library. 32 | Wire.begin(unoAddress); // Join the I2C bus 33 | Wire.setClock(400000L); 34 | Wire.onReceive(receiveRpmHandler); 35 | } // End setup() 36 | 37 | void loop() { 38 | wdt_reset(); 39 | delay(1); // The latest code version is entirely based on I2C-initiated interrupts; see receiveRpmHandler 40 | } // End loop() 41 | 42 | void setPixelGroup(int beginPixels, int endPixels, int delayTime, int red, int green, int blue, int mode){ 43 | // Multiple "modes" for different driver preferences on how the lights light up 44 | if(mode == 0){ 45 | for(int i=beginPixels-1;i 0){ 50 | pixels.show(); // This sends the updated pixel color to the hardware. 51 | delay(delayTime); // Delay for a period of time (in milliseconds). 52 | } 53 | } 54 | }else if(mode == 1){ 55 | for(int i=beginPixels-1;i 0){ 62 | pixels.show(); // This sends the updated pixel color to the hardware. 63 | delay(delayTime); // Delay for a period of time (in milliseconds). 64 | } 65 | } 66 | } 67 | pixels.show(); // This sends the updated pixel color to the hardware. 68 | } // End setPixelGroup(int, int, int, int, int, int, int) 69 | 70 | // Function that executes whenever data is received from the I2C master 2560 uC 71 | // This function is registered as an event/ISR, see setup() 72 | void receiveRpmHandler(int howMany) { 73 | for(int i = 0; i < sizeof(ledArgs); i++){ 74 | ledArgs[i] = Wire.read(); 75 | } 76 | pixels.clear(); 77 | setPixelGroup(ledArgs[0], ledArgs[1], ledArgs[2], ledArgs[3], ledArgs[4], ledArgs[5], ledArgs[6]); 78 | } // End receiveRpmHandler() 79 | -------------------------------------------------------------------------------- /KS3E_2560/2560Lib.cpp: -------------------------------------------------------------------------------- 1 | #include "2560Lib.h" 2 | 3 | // Vars for data passed to shifting function 4 | byte dataOne, dataTwo, dataThree, dataFour, dataFive, dataSix; 5 | 6 | // The method that actually shifts out the raw data to the shift registers/LED drivers 7 | void shiftOut(int myDataPin, int myClockPin, byte myDataOut) { 8 | // This shifts 8 bits out MSB first, on the rising edge of the clock, clock idles low 9 | // Internal function setup 10 | int i=0; 11 | int pinState; 12 | // Clear everything out just in case to prepare shift register for bit shifting 13 | digitalWrite(myDataPin, 0); 14 | digitalWrite(myClockPin, 0); 15 | //For each bit in the byte myDataOut notice that we are counting down in our for loop 16 | //This means that %00000001 or "1" will go through such that it will be pin Q0 that lights. 17 | for (i = 7; i >= 0; i--) { 18 | digitalWrite(myClockPin, 0); 19 | // If the value passed to myDataOut and a bitmask result true then... so if we are at i=6 and our value is %11010100 it would the code compares it to %01000000 and proceeds to set pinState to 1. 20 | if ( myDataOut & (1< NUMBER_OF_ALPHANUMERICS){ 39 | // The string still needs to be scrolled out. 40 | return; 41 | } 42 | 43 | // For this version we shift everything to uppercase (until lowercase values are added to the arrays above) 44 | whatToActuallyWrite.toUpperCase(); // "As of 1.0, toUpperCase() modifies the string in place rather than returning a new one." 45 | 46 | // At this point, the string can (probably) be displayed on 3 characters, so do that. 47 | // Loop for each of the 3 digits 48 | byte topDatas[] = {dataTwo, dataFour, dataSix}; 49 | byte bottomDatas[] = {dataOne, dataThree, dataFive}; 50 | // Load the input sequence into the given characters 51 | // i is the loop for each of the (nominally 3) alphanumeric characters 52 | for(int i = 0; i < NUMBER_OF_ALPHANUMERICS; i++){ 53 | for(int j = 0; j < sizeof(characterList); j++){ 54 | if(whatToActuallyWrite[i] == characterList[j]){ 55 | // The first character is the same as the current character. 56 | // Load that into the top and bottom bytes. 57 | bottomDatas[i] = characterBottoms[j]; 58 | topDatas[i] = characterTops[j]; 59 | } 60 | } 61 | if(decimals[i] == '.'){ 62 | bottomDatas[i] |= 0x10; // Append the decimal to this character 63 | } 64 | } 65 | 66 | // Ground latchPin and hold low for as long as you are transmitting 67 | digitalWrite(latchPin, 0); 68 | // Shift the bytes out 69 | for(int i = NUMBER_OF_ALPHANUMERICS - 1; i >= 0; i--){ 70 | shiftOut(dataPin, clockPin, topDatas[i]); 71 | shiftOut(dataPin, clockPin, bottomDatas[i]); 72 | } 73 | // Return the latch pin high to signal chip that it no longer needs to listen for information 74 | digitalWrite(latchPin, 1); 75 | return true; 76 | } // End show() 77 | 78 | 79 | // Scroll the text from left to right across the displays. 80 | // This is done by calling show() on one set of characters, 81 | // shifting left by one character, and then repeating. 82 | void scrollText(String whatToScroll, int delayTime, int addedPadding){ 83 | int decimalCount = 0; 84 | String whatToActuallyWrite; 85 | String decimals; 86 | // Add padding to the scrolled text based on input request 87 | for(int i = 0; i < addedPadding; i++){ 88 | whatToScroll = " " + whatToScroll + " "; 89 | } 90 | for(int i = 0; i < whatToScroll.length(); i++){ 91 | // Append the character to the portion displayed, dealing with decimals 92 | decimals += ' '; 93 | if(whatToScroll[i] == '.'){ 94 | // Any time this loop finds a decimal, go to the last valid char and "place" one 95 | // (Note: This implementation discards any leading or standalone decimals) 96 | decimals[whatToActuallyWrite.length() - 1] = '.'; 97 | }else{ 98 | whatToActuallyWrite += whatToScroll[i]; 99 | } 100 | 101 | // Send the latest group of 3 alphanumerics to be shown, and remove the earliest character. 102 | if(whatToActuallyWrite.length() >= NUMBER_OF_ALPHANUMERICS){ 103 | show(whatToActuallyWrite, decimals); 104 | decimals.remove(0,1); 105 | whatToActuallyWrite.remove(0,1); 106 | delay(delayTime); // This delay will cause the main loop to stop operating (and thus updating LED's) if text that's too long is being shifted out. Interrupts might be a better choice. 107 | } 108 | } 109 | } // End scrollText(String, int, int) 110 | 111 | 112 | // Default method for scrolled text 113 | void scrollText(String input){ 114 | scrollText(input, 150, NUMBER_OF_ALPHANUMERICS); 115 | } // End scrollText(String) 116 | 117 | 118 | // A method to simplify showing text on the 3 alphanumerics 119 | void showText (String whatToWrite){ 120 | int decimalCount = 0; 121 | String whatToActuallyWrite; // Used after decimal points have been accounted for and merged 122 | String decimals; 123 | 124 | if(whatToWrite.length() > NUMBER_OF_ALPHANUMERICS*2){ 125 | // The string definitely needs to be scrolled out since there can only be 1 decimal per alphanumeric character 126 | scrollText(whatToWrite); 127 | return; 128 | } 129 | // Deal with decimals (e.g., printing "12.4" can fit on 3 alphanumeric displays, so merge the decimal) 130 | for(int i = 0; i < whatToWrite.length(); i++){ 131 | // Append the character to the portion displayed, dealing with decimals 132 | decimals += ' '; 133 | if(whatToWrite[i] == '.'){ 134 | // Any time this loop finds a decimal, go to the last 'valid' char and append one 135 | // (This implementation discards any leading decimals) 136 | decimals[whatToActuallyWrite.length() - 1] = '.'; 137 | }else{ 138 | whatToActuallyWrite += whatToWrite[i]; 139 | } 140 | } 141 | if(whatToActuallyWrite.length() <= NUMBER_OF_ALPHANUMERICS){ 142 | show(whatToActuallyWrite, decimals); 143 | return; 144 | }else{ 145 | scrollText(whatToWrite); 146 | return; 147 | } 148 | } // End showText() 149 | 150 | // PWM the alphanumeric blank pin to dim it, via interrupts. Otherwise the alphanumeric LED's get fairly (or very) hot with the chosen current set resistor. 151 | SIGNAL(TIMER0_COMPA_vect) { 152 | PORTK ^= B00001000; // Toggle K3 aka blankPin 153 | } // End SIGNAL() interrupt 154 | -------------------------------------------------------------------------------- /KS3E_2560/KS3E_2560.ino: -------------------------------------------------------------------------------- 1 | //----------------------- MEGA/2560 2 | 3 | /* 4 | KSU Motorsports/FSAE KS-3E Dashboard ATmega2560 Firmware 5 | Clinton Flowers, 2018 6 | For programming via ISP, see https://learn.sparkfun.com/tutorials/installing-an-arduino-bootloader/connecting-the-programmer 7 | Don't forget the capacitor on the reset pin of the programmer Arduino, and don't forget to use "Upload using Programmer" 8 | 9 | If you're using this for a student project (e.g., a Formula SAE car) and need help with it feel free to contact me @ clintonflowers222@gmail.com 10 | 11 | Remember, a design goal of this dash was to display only critical information the driver needs to know; K.I.S.S. applies. If this changes, consider Waveshare panels. 12 | 13 | MSCan_Sniffer was used and is Copyright (c) 2013 David Will - davidwill@gmail.com The MIT License (MIT) 14 | */ 15 | 16 | // Note - CAN data is transmitted in the blind. 17 | // Communications with Megasquirt can only be verified by *RECEIVED* data. 18 | #define MS_REQUEST 19 | 20 | #include "2560Lib.h" 21 | #include 22 | #include "Tasker.h" 23 | #include 24 | #include 25 | #include "RTClib.h" // Adafruit RTCLib Library, from Library Manager 26 | #include 27 | // IMU Includes 28 | #include // Adafruit Unified Sensor library, from Library Manager 29 | #include // Adafruit BNO055 library, from Library Manager 30 | #include 31 | //#define BNO055_SAMPLERATE_DELAY_MS (10) // 1s / 10ms = 100Hz 32 | 33 | #include // Watchdog timer for possible lock-ups 34 | 35 | #define DEBUG 36 | #ifdef DEBUG 37 | #define debugShow(x) show(x) 38 | #else 39 | #define debugShow(x) 40 | #endif 41 | 42 | // Pin definitions specific to how the MCP2515 is wired up. 43 | // If this code is being breadboarded off of the actual PCB: Pin 10 for SparkFun canbus shield 44 | #define CS_PIN 36 45 | #define CAN_INT_PIN 18 // INTERRUPT != PIN. 46 | /* 47 | Board int.0 int.1 int.2 int.3 int.4 int.5 48 | Uno, Ethernet 2 3 49 | Mega2560 2 3 21 20 19 18 50 | Leonardo 3 2 0 1 7 51 | */ 52 | 53 | Tasker tasker; 54 | 55 | 56 | 57 | 58 | // Some more global vars 59 | // Received CAN data 60 | String canText = ""; 61 | int canRed = 0; 62 | int canGreen = 20; 63 | int canBlue = 5; 64 | int canDisplayedSegments = 0; 65 | // Interrupt and flow control variables 66 | byte dataready = 0; 67 | long lastISR = 0; 68 | long lastParse = 0; 69 | volatile long lastMillisInt = 5000; // don't PWM the screen before this many ms 70 | volatile bool lastMillisIntState = LOW; 71 | volatile bool displayingText = true; 72 | long lastShow = 0; 73 | 74 | // One better way to do this would be object-oriented, where the CAN fields are instantiated with name, address, etc, and add themselves to an array of CAN field objects 75 | 76 | // Clock (DS3231), IMU (BNO055) Setup 77 | RTC_DS3231 rtc; 78 | Adafruit_BNO055 bno = Adafruit_BNO055(); 79 | imu::Vector<3> accel; 80 | int latAccel = 0; 81 | // I2C Addresses 82 | const int unoAddress = 5; 83 | const int megaAddress = 6; 84 | 85 | void setup() { 86 | // Enable Watchdog Timer 87 | wdt_enable(WDTO_8S); // 8 seconds to allow for programming on ...most... bootloaders. :contingency: 88 | // TODO: For when the dash reboots mid-race due to any reason, maybe make this ask the other MCU its millis() over I2C to see if it should say "hooty hoo" etc, or jump right into displaying RPM 89 | 90 | tasker.init(); 91 | 92 | // pinMode(CS_PIN, OUTPUT); 93 | // SPI.setClockDivider(SPI_CLOCK_DIV2); 94 | // SPI.setDataMode(SPI_MODE0); 95 | // SPI.setBitOrder(MSBFIRST); 96 | // SPI.begin(); 97 | delay(1); 98 | 99 | delay(1); 100 | Wire.begin(megaAddress); // Join I2C bus with the address. See 328p code to guarantee its address if uncertain. 101 | Wire.setClock(400000L); 102 | 103 | // Config for shiftOut, used for LED drivers 104 | pinMode(clockPin, OUTPUT); 105 | pinMode(dataPin, OUTPUT); 106 | pinMode(latchPin, OUTPUT); 107 | pinMode(blankPin, OUTPUT); 108 | 109 | // Timer interrupt on millis. IIRC this is used for TIMER0_COMPA_vect, to dim the alphanumerics by putting them on a ~50% duty cycle. 110 | OCR0A = 0xAF; 111 | TIMSK0 |= _BV(OCIE0A); 112 | 113 | // IMU Stuff 114 | if(!bno.begin()){ 115 | showText("NIM"); // No BNO/IMU Detected. Hardware problem! 116 | delay(200); 117 | } 118 | delay(2); 119 | bno.setExtCrystalUse(true); 120 | delay(2); 121 | 122 | // DS3231 Clock Stuff 123 | if (! rtc.begin()) { 124 | showText("NCK"); // The Clock doesn't work. Hardware problem! 125 | delay(200); 126 | } 127 | 128 | if (rtc.lostPower()) { 129 | //rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); 130 | // TODO: Make a clock synchronization procedure 131 | } 132 | delay(1); 133 | DateTime now = rtc.now(); 134 | delay(1); 135 | //showText("T " + String(now.hour()) + "." + String(now.minute()) + "." + String(now.second())); // Display the time (uncomment when setting time) 136 | delay(1); 137 | 138 | // SD Card / Datalogging Stuff 139 | // See the IR Tire Temp Sensor code on how to create files and handle appending/creating CSVs. Ensure the clocks are synced (DS3231 drifts ~5 seconds/month) 140 | // if (!SD.begin(A8)) { // The SD card CS line is on pin PC0/Arduino 37/A8 on Dash v0.2 141 | // showText("NSD"); // No SD card. User or hardware problem. 142 | // delay(200); 143 | // }else{ 144 | // showText("SD."); 145 | // delay(200); 146 | // } 147 | 148 | //showText("Hooty Hoo 1.9"); // Firmware version number. 149 | // delay(100); 150 | 151 | // CAN Interrupt setup and CAN stuff 152 | pinMode(CAN_INT_PIN, INPUT_PULLUP); // Maybe not necessary, but shouldn't hurt 153 | if (!CAN.begin(500E3)) { 154 | showText("NCN"); // The CAN doesn't work. Hardware problem! 155 | delay(200); 156 | } 157 | //attachInterrupt(digitalPinToInterrupt(CAN_INT_PIN), ISR_can, FALLING); 158 | // CAN.onReceive(ISR_can); 159 | // CAN.onReceive(rxd); 160 | // for(int i = 0; i < 3; i++){ 161 | // MS_Parse(); 162 | // delay(1); 163 | // } 164 | 165 | 166 | } // End setup() 167 | 168 | void rxd(int packetSize){ 169 | noInterrupts(); 170 | showText("RXD"); 171 | interrupts(); 172 | } 173 | 174 | void loop() { 175 | // Clear watchdog, delaying forced resets. Sometimes called "kicking" the watchdog 176 | // showText("z01"); 177 | wdt_reset(); 178 | // showText("z02"); 179 | tasker.taskLoop(); 180 | // showText("z03"); 181 | updateNeopixels(); // Update LEDs of the 328P via I2C 182 | // showText("z04"); 183 | delay(1); 184 | accel = bno.getVector(Adafruit_BNO055::VECTOR_ACCELEROMETER); 185 | // showText("z05"); 186 | delay(1); // Allow time for fetching data from the IMU 187 | latAccel = accel.y(); 188 | // showText("z06"); 189 | if(latAccel > 10){ // Display lateral G's if turning hard enough (over 5 m/s^2) 190 | float latGs = accel.y() / 9.81; 191 | showText(String(latGs)); 192 | }else if(millis() % 5000 < 2500){ 193 | // If the modulo of the time the dash has been on is < 2.5s, show voltage, otherwise show coolant temp. 194 | // (That's the programming way of saying alternate showing Batt and CLT every 2.5s) 195 | showText(String(canText)); 196 | // showText("z07"); 197 | } 198 | 199 | // if (dataready == 1) { 200 | //MS_Parse(); // Deal with data if it's arrived (shouldn't be necessary since ISR_CAN is working) 201 | //dataready = 0; 202 | // } 203 | } // End loop() 204 | 205 | // I2C Send RPM - When called, transmits the current RPM to the I2C Slaved 328P 206 | void updateNeopixels(){ 207 | // ledArgs format: setPixelGroup(int beginPixels, int endPixels, int delayTime, int red, int green, int blue, int mode). See 328p code for most up-to-date format. 208 | int ledArgs[7] = {1, 1, 0, canRed, canGreen, canBlue, 0}; 209 | // showText("a01"); 210 | Wire.beginTransmission(unoAddress); 211 | if(canText == ""){ // The dash has not received a message from the VCU 212 | // showText("a02"); 213 | ledArgs[1] = (millis() / 1000) % 10; 214 | if(abs(latAccel) > 5.0){ 215 | // Might as well display the lateral acceleration or something so we know the dash isn't broken when debugging on a bench. 216 | ledArgs[1] = abs(latAccel); 217 | } 218 | // showText("a03"); 219 | }else{ 220 | // Display the RPM value 221 | ledArgs[3] = canRed; // Red 222 | ledArgs[4] = canGreen; // Green 223 | ledArgs[5] = canBlue; // Blue 224 | ledArgs[1] = canDisplayedSegments; 225 | // showText("a04"); 226 | } 227 | // showText("a05"); 228 | // Send the LED values to the 328P for display 229 | for(int i = 0; i < 7; i++){ 230 | Wire.write(ledArgs[i]); 231 | } 232 | // showText("a06"); 233 | Wire.endTransmission(); 234 | // showText("a07"); 235 | } // End updateNeopixels() 236 | 237 | 238 | // CAN Interrupt Service Routine Handler 239 | // Any time the MCP2515 pings the interrupt pin of the 2560, indicating a message is ready in the CAN buffer, this is run 240 | // Handle new CAN messages 241 | void ISR_can(int packetSize) { 242 | 243 | if (CAN.packetExtended()) { 244 | showText(" X "); 245 | } 246 | 247 | if (CAN.packetRtr()) { 248 | showText("R "); 249 | // Remote transmission request, packet contains no data 250 | } 251 | 252 | int currentByte = 0; 253 | byte bytes [packetSize]; 254 | 255 | if (CAN.packetRtr()) { 256 | showText(" R"); 257 | } else { 258 | 259 | // only print packet data for non-RTR packets 260 | while (CAN.available()) { 261 | bytes[currentByte] = ((byte)CAN.read()); 262 | currentByte++; 263 | } 264 | } 265 | 266 | if(bytes[0] == (byte)0x01){ 267 | showText("SD."); 268 | char ch[]={bytes[1],bytes[2],bytes[3]}; 269 | String toDisplay(ch); 270 | canText = toDisplay; 271 | showText(toDisplay); 272 | }else{ 273 | showText(" ."); 274 | } 275 | 276 | // MS_Parse(); 277 | } // END ISR_can() 278 | 279 | // Parse new CAN messages 280 | void MS_Parse() { 281 | 282 | dataready = 0; 283 | lastParse = millis(); 284 | } // End MS_Parse() 285 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /Dash0.2_2560_Code/Dash0.2_2560_Code.ino: -------------------------------------------------------------------------------- 1 | //----------------------- MEGA/2560 2 | 3 | /* 4 | KSU Motorsports/FSAE KS-2 Dashboard ATmega2560 Firmware 5 | Clinton Flowers, 2017 6 | For programming via ISP, see https://learn.sparkfun.com/tutorials/installing-an-arduino-bootloader/connecting-the-programmer 7 | Don't forget the capacitor on the reset pin of the programmer Arduino, and don't forget to use "Upload using Programmer" 8 | 9 | If you're using this for a student project (e.g., a Formula SAE car) and need help with it feel free to contact me @ clintonflowers222@gmail.com 10 | 11 | Remember, a design goal of this dash was to display only critical information the driver needs to know; K.I.S.S. applies. If this changes, consider Waveshare panels. 12 | 13 | MSCan_Sniffer was used and is Copyright (c) 2013 David Will - davidwill@gmail.com The MIT License (MIT) 14 | */ 15 | 16 | // Note - CAN data is transmitted in the blind. 17 | // Communications with Megasquirt can only be verified by *RECEIVED* data. 18 | #define MS_REQUEST 19 | 20 | #include 21 | #include 22 | #include "RTClib.h" // Adafruit RTCLib Library, from Library Manager 23 | #include 24 | // IMU Includes 25 | #include // Adafruit Unified Sensor library, from Library Manager 26 | #include // Adafruit BNO055 library, from Library Manager 27 | #include 28 | //#define BNO055_SAMPLERATE_DELAY_MS (10) // 1s / 10ms = 100Hz 29 | 30 | #define WATCHDOG 31 | #ifdef WATCHDOG 32 | #include // Watchdog timer for possible lock-ups 33 | #endif 34 | 35 | #define DEBUG 36 | #ifdef DEBUG 37 | #define debugShow(x) show(x) 38 | #else 39 | #define debugShow(x) 40 | #endif 41 | 42 | // Pin definitions specific to how the MCP2515 is wired up. 43 | // If this code is being breadboarded off of the actual PCB: Pin 10 for SparkFun canbus shield 44 | #define CS_PIN 36 45 | #define CAN_INT_PIN 18 // INTERRUPT != PIN. 46 | /* 47 | Board int.0 int.1 int.2 int.3 int.4 int.5 48 | Uno, Ethernet 2 3 49 | Mega2560 2 3 21 20 19 18 50 | Leonardo 3 2 0 1 7 51 | */ 52 | 53 | // These arrays are used to map the alphanumeric LED's to the associated code character. 54 | // Each bit position corresponds to a segment of the LED; adding the correct bits together and then shifting that byte into the driver will light up the segments for that character 55 | // Since there are 15 positions per digit, we use 2 8-bit bytes, so a Top and a Bottom 56 | const char characterList[] = {' ', '$', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; 57 | const byte characterBottoms[] = {0x00, 0x65, 0xe2, 0x20, 0xc1, 0x61, 0x21, 0x61, 0xe1, 0x20, 0xe1, 0x21, 0xa1, 0x65, 0xc0, 0x64, 0xc1, 0x81, 0xe0, 0xa1, 0x44, 0xe0, 0x89, 0xc0, 0xa0, 0xa8, 0xe0, 0x81, 0xe8, 0x89, 0x60, 0x04, 0xe0, 0x82, 0xaa, 0x0a, 0x05, 0x42}; 58 | const byte characterTops[] = {0x00, 0x6a, 0x56, 0x14, 0x0e, 0x0e, 0x4c, 0x4a, 0x4a, 0x06, 0x4e, 0x4e, 0x4e, 0x2e, 0x42, 0x26, 0x42, 0x42, 0x4a, 0x4c, 0x22, 0x04, 0x50, 0x40, 0x55, 0x45, 0x46, 0x4e, 0x46, 0x4e, 0x0b, 0x22, 0x44, 0x50, 0x44, 0x11, 0x4c, 0x12}; 59 | // Various control variables for the TLC59281RGER Constant Current LED Driver 60 | const int blankPin = 65; 61 | const int latchPin = 64; 62 | const int clockPin = 63; 63 | const int dataPin = 62; 64 | // Pins for the joystick on the rear of the device, accounting for improper hardware placement. Unused in current code. 65 | const int centerPin = PE6; // Center in schematic, J_Common as installed. 66 | const int rightPin = 19; // J_Left in schematic 67 | const int leftPin = PE7; // J_Right in schematic 68 | const int downPin = 2; // J_Up in schematic 69 | const int upPin = 3; // J_Down in schematic 70 | // How many (14-segment + dp) alphanumeric displays are connected 71 | const int numberOfAlphanumerics = 3; 72 | // Vars for data passed to shifting function 73 | byte dataOne, dataTwo, dataThree, dataFour, dataFive, dataSix; 74 | 75 | //MCP2515 definitions, from the MSCAN_Sniffer Code 76 | // May be improved by throwing all of this into an external included file 77 | #define CAN_READ 0x03 78 | #define CAN_WRITE 0x02 79 | #define CANINTE 0x2B 80 | #define CANINTF 0x2C 81 | #define BFPCTRL 0x0C 82 | #define CANCTRL 0x0F 83 | #define CANSTAT 0x0E 84 | 85 | #define CNF1 0x2A 86 | #define CNF2 0x29 87 | #define CNF3 0x28 88 | #define RXB0CTRL 0x60 89 | #define RXB1CTRL 0x70 90 | 91 | // TX Buffer 0 92 | #define TXB0CTRL 0x30 93 | #define TXB0SIDH 0x31 94 | #define TXB0SIDL 0x32 95 | #define TXB0EID8 0x33 96 | #define TXB0EID0 0x34 97 | #define TXB0DLC 0x35 98 | #define TXB0D0 0x36 99 | #define TXB0D1 0x37 100 | #define TXB0D2 0x38 101 | #define TXB0D3 0x39 102 | #define TXB0D4 0x3A 103 | #define TXB0D5 0x3B 104 | #define TXB0D6 0x3C 105 | #define TXB0D7 0x3D 106 | // RX Buffer 0 107 | #define RXB0CTRL 0x60 108 | #define RXB0SIDH 0x61 109 | #define RXB0SIDL 0x62 110 | #define RXB0EID8 0x63 111 | #define RXB0EID0 0x64 112 | #define RXB0DLC 0x65 113 | #define RXB0D0 0x66 114 | #define RXB0D1 0x67 115 | #define RXB0D2 0x68 116 | #define RXB0D3 0x69 117 | #define RXB0D4 0x6A 118 | #define RXB0D5 0x6B 119 | #define RXB0D6 0x6C 120 | #define RXB0D7 0x6D 121 | // RX Buffer 1 122 | #define RXB1CTRL 0x70 123 | #define RXB1SIDH 0x71 124 | #define RXB1SIDL 0x72 125 | #define RXB1EID8 0x73 126 | #define RXB1EID0 0x74 127 | #define RXB1DLC 0x75 128 | #define RXB1D0 0x76 129 | #define RXB1D1 0x77 130 | #define RXB1D2 0x78 131 | #define RXB1D3 0x79 132 | #define RXB1D4 0x7A 133 | #define RXB1D5 0x7B 134 | #define RXB1D6 0x7C 135 | #define RXB1D7 0x7D 136 | 137 | // Global vars aren't ideal but they simplify the coding process, especially with a substandard IDE or on a microcontroller. 138 | byte SIDH, SIDL, EID8, EID0, DLC; 139 | byte databuffer[7]; 140 | unsigned int data; 141 | byte block, canintf; 142 | unsigned int offset; 143 | byte dataready = 0; 144 | long lastISR = 0; 145 | long lastParse = 0; 146 | volatile long lastMillisInt = 5000; // don't PWM the screen before this many ms 147 | volatile bool lastMillisIntState = LOW; 148 | volatile bool displayingText = true; 149 | long lastShow = 0; 150 | 151 | // Fields stored as received over CAN bus. Append new variables of interest here. 152 | // After declaring the variable storage here, add code to request the associated address in the loop, and handle it in the ISR 153 | // One better way to do this would be object-oriented, where the CAN fields are instantiated with name, address, etc, and add themselves to an array of CAN field objects 154 | unsigned int canRPM = 0; 155 | unsigned const int shiftRPM = 12000; 156 | String canVoltage = "0"; 157 | int canTPS = 0; 158 | int canGear = 10; // Zeroeth gear = maybe neutral 159 | int canCELStatus = 0; // CANbus Check Engine Light status 160 | int canTPSADC = 0; 161 | int canCELErrorCode = 0; 162 | int canCLT = 0; 163 | int canOLP = -1; // Oil pressure 164 | 165 | // Clock (DS3231), IMU (BNO055) Setup 166 | RTC_DS3231 rtc; 167 | Adafruit_BNO055 bno = Adafruit_BNO055(); 168 | imu::Vector<3> accel; 169 | int latAccel = 0; 170 | // I2C Addresses 171 | const int unoAddress = 5; 172 | const int megaAddress = 6; 173 | 174 | void setup() { 175 | // Enable Watchdog Timer 176 | wdt_enable(WDTO_8S); // 8 seconds to allow for programming on ...most... bootloaders. :contingency: 177 | // TODO: For when the dash reboots mid-race due to any reason, maybe make this ask the other MCU its millis() over I2C to see if it should say "hooty hoo" etc, or jump right into displaying RPM 178 | 179 | pinMode(CS_PIN, OUTPUT); 180 | SPI.setClockDivider(SPI_CLOCK_DIV2); 181 | SPI.setDataMode(SPI_MODE0); 182 | SPI.setBitOrder(MSBFIRST); 183 | SPI.begin(); 184 | delay(1); 185 | // Check MCP2515 whitepaper for option definitions. These work for Megasquirt. 186 | CANWrite(CANCTRL, B10000111); // Set MCP2515 to config mode 187 | CANWrite(CNF1, 0x00); // CNF1 b00000000 188 | CANWrite(CNF2, 0xA4); // CNF2 b10100100 189 | CANWrite(CNF3, 0x84); // CNF3 b10000100 190 | CANWrite(CANCTRL, B00000111); // Set MCP2515 to normal mode 191 | delay(1); 192 | CANWrite(RXB0CTRL, B01101000); //RXB0CTRL clear receive buffers 193 | CANWrite(RXB1CTRL, B01101000); //RXB1CTRL clear receive buffers 194 | CANWrite(CANINTE, B00000011); // Enable interrupt on RXB0, RXB1 195 | CANWrite(BFPCTRL, B00001111); // setting interrupts 196 | 197 | // Joystick interrupts. 198 | // pinMode(centerPin, OUTPUT); 199 | // pinMode(upPin, INPUT_PULLUP); 200 | // pinMode(downPin, INPUT_PULLUP); 201 | // pinMode(leftPin, INPUT_PULLUP); 202 | // pinMode(rightPin, INPUT_PULLUP); 203 | // digitalWrite(centerPin, LOW); // Ground the "common" pin (as installed) 204 | // attachInterrupt(digitalPinToInterrupt(downPin), resetFunction, FALLING); 205 | // attachInterrupt(digitalPinToInterrupt(upPin), upFunc, FALLING); 206 | // attachInterrupt(digitalPinToInterrupt(downPin), downFunc, FALLING); 207 | // attachInterrupt(digitalPinToInterrupt(leftPin), leftFunc, FALLING); 208 | // attachInterrupt(digitalPinToInterrupt(rightPin), rightFunc, FALLING); 209 | 210 | delay(1); 211 | Wire.begin(megaAddress); // Join I2C bus with the address. See 328p code to guarantee its address if uncertain. 212 | Wire.setClock(400000L); 213 | 214 | // Config for shiftOut, used for LED drivers 215 | pinMode(clockPin, OUTPUT); 216 | pinMode(dataPin, OUTPUT); 217 | pinMode(latchPin, OUTPUT); 218 | pinMode(blankPin, OUTPUT); 219 | 220 | // Timer interrupt on millis. IIRC this is used for TIMER0_COMPA_vect, to dim the alphanumerics by putting them on a ~50% duty cycle. 221 | OCR0A = 0xAF; 222 | TIMSK0 |= _BV(OCIE0A); 223 | 224 | // IMU Stuff 225 | if(!bno.begin()){ 226 | showText("NIM"); // No BNO/IMU Detected. Hardware problem! 227 | delay(200); 228 | } 229 | delay(2); 230 | bno.setExtCrystalUse(true); 231 | delay(2); 232 | 233 | // DS3231 Clock Stuff 234 | if (! rtc.begin()) { 235 | showText("NCK"); // The Clock doesn't work. Hardware problem! 236 | delay(200); 237 | } 238 | 239 | if (rtc.lostPower()) { 240 | //rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); 241 | // TODO: Make a clock synchronization procedure 242 | } 243 | delay(1); 244 | DateTime now = rtc.now(); 245 | delay(1); 246 | //showText("T " + String(now.hour()) + "." + String(now.minute()) + "." + String(now.second())); // Display the time (uncomment when setting time) 247 | delay(1); 248 | 249 | // SD Card / Datalogging Stuff 250 | // See the IR Tire Temp Sensor code on how to create files and handle appending/creating CSVs. Ensure the clocks are synced (DS3231 drifts ~5 seconds/month) 251 | // if (!SD.begin(A8)) { // The SD card CS line is on pin PC0/Arduino 37/A8 on Dash v0.2 252 | // showText("NSD"); // No SD card. User or hardware problem. 253 | // delay(200); 254 | // }else{ 255 | // showText("SD."); 256 | // delay(200); 257 | // } 258 | 259 | showText("Hooty Hoo 1.9"); // Firmware version number. 260 | delay(100); 261 | 262 | // CAN Interrupt setup and CAN stuff 263 | pinMode(CAN_INT_PIN, INPUT_PULLUP); // Maybe not necessary, but shouldn't hurt 264 | attachInterrupt(digitalPinToInterrupt(CAN_INT_PIN), ISR_can, FALLING); 265 | for(int i = 0; i < 3; i++){ 266 | MS_Parse(); 267 | delay(1); 268 | } 269 | } // End setup() 270 | 271 | void loop() { 272 | // Clear watchdog, delaying forced resets. Sometimes called "kicking" the watchdog 273 | #ifdef WATCHDOG 274 | wdt_reset(); 275 | #endif 276 | // Check engine light test code. This is commented out since I couldn't get the ECU to actually report check engine light status from its CAN address(es)... Maybe it's bugged? 277 | // if(canCELStatus > 0){ // If there's a CEL status other than 0, display that fact 278 | // showText("CHK."); 279 | // delay(100); 280 | // showText(String(canCELStatus)); 281 | // delay(100); 282 | // } else if(canCELErrorCode > 0){ // Check for error codes from the other CAN address 283 | // showText("CEL"); 284 | // delay(100); 285 | // showText(String(canCELErrorCode)); 286 | // delay(100); 287 | // } else{ 288 | // } 289 | sendRpm(); // Send RPM to the 328P via I2C 290 | delay(1); 291 | accel = bno.getVector(Adafruit_BNO055::VECTOR_ACCELEROMETER); 292 | delay(1); // Allow time for fetching data from the IMU 293 | latAccel = accel.y(); 294 | if(latAccel > 5){ // Display lateral G's if turning hard enough (over 5 m/s^2) 295 | float latGs = accel.y() / 9.81; 296 | showText(String(latGs)); 297 | }else if(millis() % 5000 < 2500){ 298 | // If the modulo of the time the dash has been on is < 2.5s, show voltage, otherwise show coolant temp. 299 | // (That's the programming way of saying alternate showing Batt and CLT every 2.5s) 300 | showText(String(canVoltage)); 301 | }else{ 302 | showText(String(canCLT)); // Coolant Temp 303 | //showText(String(canOLP)); // Oil Pressure 304 | } 305 | 306 | 307 | if (dataready == 1) { 308 | //MS_Parse(); // Deal with data if it's arrived (shouldn't be necessary since ISR_CAN is working) 309 | //dataready = 0; 310 | }else{ 311 | #ifdef MS_REQUEST 312 | // See http://www.msextra.com/doc/pdf/Megasquirt_CAN_Broadcast.pdf for addresses 313 | MSrequest(7, 24, 2); // MS3 1.3.2 Firmware TPS - block 7, offset 24, 2 byte request 314 | delay(1); // Don't request stuff over CAN before it can be transmitted, received, and processed. Usually happens way under 1 ms--see oscilloscope traces. 315 | MSrequest(7, 26, 2); // MS3 1.3.2 Firmware batt - block 7, offset 26, 2 byte request 316 | delay(1); 317 | MSrequest(7, 6, 2); // MS3 1.3.2 Firmware RPM - block 7, offset 6, 2 byte request 318 | delay(1); 319 | MSrequest(7, 22, 2); // MS3 1.3.2 Firmware CLT - block 7, offset 22, 2 byte request 320 | delay(1); 321 | //MSrequest(7, 106, 2); // MS3 1.3.2 Firmware Generic In (Oil Pressure!(?)) - block 7, offset 106, 2 byte request 322 | //delay(1); 323 | //MSrequest(7, 270, 1); // MS3 1.3.2 Firmware gear - block 7, offset 270, 1 byte request 324 | //delay(1); 325 | //MSrequest(7, 434, 2); // MS3 1.3.2 Firmware CEL Status - block 7, offset 434, 2 byte request 326 | //delay(1); 327 | //MSrequest(7, 226, 2); // MS3 1.3.2 Firmware TPSADC - block 7, offset 226, 2 byte request 328 | //delay(1); 329 | //MSrequest(7, 415, 1); // MS3 1.3.2 Firmware CEL Error Code - block 7, offset 415, 1 byte request 330 | //delay(1); 331 | #endif 332 | } 333 | } // End loop() 334 | 335 | void resetFunction() { // Software reset - first, try the watchdog timer 336 | wdt_disable(); 337 | wdt_enable(WDTO_15MS); 338 | delay(16000); // Try to suicide via watchdog 339 | while(1){}; 340 | } // End reset function 341 | 342 | // For debugging/testing the joystick 343 | void upFunc(){ 344 | showText("U"); 345 | delay(100); 346 | } 347 | void downFunc(){ 348 | showText("D"); 349 | delay(100); 350 | } 351 | void leftFunc(){ 352 | showText("L"); 353 | delay(100); 354 | } 355 | void rightFunc(){ 356 | showText("R"); 357 | delay(100); 358 | } 359 | 360 | // I2C RequestEvent - Any time the 328P (assumed) requests data from the 2560 over I2C, respond with data 361 | // In the later versions, this is not used, since the 2560 sends the 328P the RPM on its own time. Could delete. 362 | void requestEvent(){ 363 | if(canRPM == 0){ // The RPM is reading zero. Display TPS! 364 | Wire.write(map(canTPS, 0, 1050, 0, 255)); 365 | }else{ 366 | // Return the RPM value 367 | Wire.write(map(canRPM, 0, shiftRPM, 0, 255)); 368 | } 369 | } // End requestEvent() 370 | 371 | // I2C Send RPM - When called, transmits the current RPM to the I2C Slaved 328P 372 | void sendRpm(){ 373 | // ledArgs format: setPixelGroup(int beginPixels, int endPixels, int delayTime, int red, int green, int blue, int mode). See 328p code for most up-to-date format. 374 | int ledArgs[7] = {1, 1, 0, 0, 40, 10, 0}; 375 | Wire.beginTransmission(unoAddress); 376 | if(canRPM == 0){ // The RPM is reading zero - The engine is ostensibly off. Display TPS or something! 377 | ledArgs[1] = map(canTPS, 0, 1050, 0, 10); // Display a TPS reading 378 | if(canTPS == 0){ 379 | // The TPS and RPM are both exactly zero, so CAN probably isn't working or the ECU is off. 380 | // Might as well display the lateral acceleration or something so we know the dash isn't broken when debugging on a bench. 381 | ledArgs[1] = abs(latAccel); 382 | if(latAccel > 8){ 383 | showText("ACC"); // Accelerometer/Acceleration 384 | delay(200); 385 | } 386 | } 387 | }else{ 388 | // Display the RPM value 389 | ledArgs[3] = 128; // Red 390 | ledArgs[4] = 50; // Green 391 | ledArgs[5] = 0; // Blue 392 | ledArgs[1] = map(canRPM, 0, shiftRPM, 0, 10); 393 | if(canRPM > shiftRPM){ // We're over 'n'k RPM, display bright blue/green. Don't use bright red since that should only indicate errors. 394 | ledArgs[3] = 64; 395 | ledArgs[4] = 128; 396 | ledArgs[5] = 255; 397 | }else if(canRPM > (shiftRPM * 2)){ 398 | resetFunction(); // Something's broke, or breaking 399 | } 400 | } 401 | // Send the LED values to the 328P for display 402 | for(int i = 0; i < 7; i++){ 403 | Wire.write(ledArgs[i]); 404 | } 405 | Wire.endTransmission(); 406 | } // End sendRpm() 407 | 408 | 409 | // Main CAN Requester Function, from David Will's MSCan library 410 | void MSrequest(byte block, unsigned int offset, byte req_bytes) { 411 | /* Request data in Megasquirt format */ 412 | byte SIDH, SIDL, EID8, EID0, DLC, D0, D1, D2; 413 | // 414 | SIDH = lowByte(offset >> 3); 415 | // var_offset<2:0> SRR IDE msg_type <3:0> 416 | SIDL = (lowByte((offset << 5)) | B0001000); //set IDE bit 417 | // MFFFFTTT msg_type, From, To 418 | EID8 = B10011000; //:7 msg_req, from id 3 (4:3) 419 | // TBBBBBSS To, Block, Spare 420 | EID0 = ( ( block & B00001111) << 3); // last 4 bits, move them to 6:3 421 | EID0 = ((( block & B00010000) >> 2) | EID0); // bit 5 goes to :2 422 | // 423 | DLC = B00000011; 424 | D0=(block); 425 | D1=(offset >> 3); 426 | D2=(((offset & B00000111) << 5) | req_bytes); // shift offset 427 | // Disable interrupts to prevent locking up during SPI transfers 428 | noInterrupts(); 429 | // digitalWrite(CS_PIN,LOW); 430 | PORTC &= B11111101; // Set CS pin (port C1, pin 36) low, starting a write 431 | SPI.transfer(0x40); // Push bits starting at 0x31 (RXB0SIDH) 432 | SPI.transfer(SIDH); //0x31 433 | SPI.transfer(SIDL); //0x32 434 | SPI.transfer(EID8); //0x33 435 | SPI.transfer(EID0); //0x34 436 | SPI.transfer(DLC); //0x35 437 | SPI.transfer(D0); // 0x36 TXB0D0 my_varblk 438 | SPI.transfer(D1); // 0x37 TXB0D1 my_offset 439 | SPI.transfer(D2); // 0x38 TXB0D2 - request 8 bytes(?) from MS3 440 | // digitalWrite(CS_PIN,HIGH); // end write 441 | PORTC |= B00000010; // Set CS pin (port C1, pin 36) high, ending the write 442 | 443 | // RTS - Send this buffer down the wire 444 | // digitalWrite(CS_PIN,LOW); 445 | PORTC &= B11111101; // Set CS pin (port C1, pin 36) low, starting a write 446 | SPI.transfer(B10000001); 447 | // digitalWrite(CS_PIN,HIGH); 448 | PORTC |= B00000010; // Set CS pin (port C1, pin 36) high, ending the write 449 | interrupts(); // *Don't* move this below CANWrite. Though maybe remove the redundant CANWrite. 450 | CANWrite(CANINTF,0x00); // Reenabling interrupts above this makes it freeze occasionally, 451 | // but enabling them afterwards below slows loops down a lot 452 | } // End MSrequest() 453 | 454 | 455 | // CAN Interrupt Service Routine Handler 456 | // Any time the MCP2515 pings the interrupt pin of the 2560, indicating a message is ready in the CAN buffer, this is run 457 | void ISR_can() { 458 | //Buffer variables are global. 459 | byte temp; 460 | lastISR = millis(); 461 | 462 | canintf=CANRead(CANINTF); // Reading Interrupt to determine which buffer is full. 463 | 464 | if (canintf & B00000001) { 465 | SIDH=CANRead(RXB0SIDH); 466 | SIDL=CANRead(RXB0SIDL); 467 | EID8=CANRead(RXB0EID8); 468 | EID0=CANRead(RXB0EID0); 469 | DLC=CANRead(RXB0DLC); 470 | databuffer[0]=CANRead(RXB0D0); 471 | databuffer[1]=CANRead(RXB0D1); 472 | databuffer[2]=CANRead(RXB0D2); 473 | databuffer[3]=CANRead(RXB0D3); 474 | databuffer[4]=CANRead(RXB0D4); 475 | databuffer[5]=CANRead(RXB0D5); 476 | databuffer[6]=CANRead(RXB0D6); 477 | databuffer[7]=CANRead(RXB0D7); 478 | } 479 | else if (canintf & B00000010) { 480 | SIDH=CANRead(RXB1SIDH); 481 | SIDL=CANRead(RXB1SIDL); 482 | EID8=CANRead(RXB1EID8); 483 | EID0=CANRead(RXB1EID0); 484 | DLC=CANRead(RXB0DLC); 485 | databuffer[0]=CANRead(RXB1D0); 486 | databuffer[1]=CANRead(RXB1D1); 487 | databuffer[2]=CANRead(RXB1D2); 488 | databuffer[3]=CANRead(RXB1D3); 489 | databuffer[4]=CANRead(RXB1D4); 490 | databuffer[5]=CANRead(RXB1D5); 491 | databuffer[6]=CANRead(RXB1D6); 492 | databuffer[7]=CANRead(RXB1D7); 493 | } 494 | 495 | block=((B01111000 & EID0) >> 3); 496 | temp=0x00; 497 | temp=((B00000100 & EID0) << 3); 498 | block=block | temp; 499 | 500 | offset=SIDH; 501 | temp=((SIDL & B11100000) >> 5); 502 | offset=((offset << 3) | temp); 503 | 504 | dataready = 1; // set flag to run parser in loop 505 | CANWrite(CANINTF, 0x00); // clear interrupt 506 | 507 | MS_Parse(); 508 | } // END ISR_can() 509 | 510 | 511 | void MS_Parse() { 512 | dataready = 0; 513 | lastParse = millis(); 514 | 515 | data=databuffer[0]; 516 | data=((data << 8) | databuffer[1]); 517 | 518 | byte datalength=(DLC & 0x0F); 519 | 520 | byte temp; 521 | int var_offset; 522 | var_offset=SIDH; 523 | temp=((SIDL & B11100000) >> 5); 524 | var_offset=((var_offset << 3 )| temp); 525 | 526 | int msg_type; 527 | msg_type=((B00000011 & SIDL) << 1); 528 | temp=0x00; 529 | temp=((B10000000 & EID8) >> 7); 530 | msg_type=msg_type | temp; 531 | 532 | byte from_id, to_id; 533 | from_id=((B01111000 & EID8) >> 3); 534 | to_id=((B00000111 & EID8) << 1); 535 | temp=0x00; 536 | temp=((B10000000 & EID0) >> 7); 537 | to_id=to_id | temp; 538 | 539 | int var_block; 540 | var_block=((B01111000 & EID0) >> 3); 541 | temp=0x00; 542 | temp=((B00000100 & EID0) << 3); 543 | var_block=var_block | temp; 544 | 545 | // Determine what data was received and update the associated (global) variable 546 | // This code is, how you say... inelegant 547 | if(var_offset == 6){ // RPM 548 | canRPM = data; 549 | }else if(var_offset == 24){ // TPS 550 | canTPS = data; 551 | }else if(var_offset == 26){ // Battery Voltage 552 | canVoltage = String(((double)data)/10).substring(0,4); 553 | }else if(var_offset == 270){ // Gear 554 | canGear = databuffer[0]; 555 | }else if(var_offset == 434){ // CEL Status(?) 556 | canCELStatus = data; 557 | }else if(var_offset == 226){ // TPSADC 558 | canTPSADC = data; 559 | }else if(var_offset == 22){ // CLT 560 | canCLT = data / 10; 561 | } 562 | // if(var_offset == 106){ // Oil Pressure 563 | // canOLP = data / 10; 564 | // } 565 | // if(canCELErrorCode == 415){ // CEL Error Code 566 | // canCELErrorCode = databuffer[0]; 567 | // } 568 | 569 | 570 | if(String(data).length() < 5){ 571 | //showText(String(data)); 572 | }else{ 573 | showText(" . . ."); 574 | } 575 | } // End MS_Parse() 576 | 577 | 578 | void CANWrite(byte addr, byte data) { 579 | digitalWrite(CS_PIN,LOW); 580 | noInterrupts(); // Necessary to avoid freezing during SPI transfer (due to MS_Parse and ISR_can both clearing the interrupt flag) 581 | SPI.transfer(CAN_WRITE); 582 | SPI.transfer(addr); 583 | SPI.transfer(data); 584 | interrupts(); 585 | digitalWrite(CS_PIN,HIGH); 586 | } // End CANWrite 587 | 588 | 589 | byte CANRead(byte addr) { 590 | byte data; 591 | digitalWrite(CS_PIN,LOW); 592 | SPI.transfer(CAN_READ); 593 | SPI.transfer(addr); 594 | data=SPI.transfer(0x00); 595 | digitalWrite(CS_PIN,HIGH); 596 | return data; 597 | } // End CANRead 598 | 599 | 600 | // A method to simplify showing text on the 3 alphanumerics 601 | void showText (String whatToWrite){ 602 | int decimalCount = 0; 603 | String whatToActuallyWrite; // Used after decimal points have been accounted for and merged 604 | String decimals; 605 | 606 | if(whatToWrite.length() > numberOfAlphanumerics*2){ 607 | // The string definitely needs to be scrolled out since there can only be 1 decimal per alphanumeric character 608 | scrollText(whatToWrite); 609 | return; 610 | } 611 | // Deal with decimals (e.g., printing "12.4" can fit on 3 alphanumeric displays, so merge the decimal) 612 | for(int i = 0; i < whatToWrite.length(); i++){ 613 | // Append the character to the portion displayed, dealing with decimals 614 | decimals += ' '; 615 | if(whatToWrite[i] == '.'){ 616 | // Any time this loop finds a decimal, go to the last 'valid' char and append one 617 | // (This implementation discards any leading decimals) 618 | decimals[whatToActuallyWrite.length() - 1] = '.'; 619 | }else{ 620 | whatToActuallyWrite += whatToWrite[i]; 621 | } 622 | } 623 | if(whatToActuallyWrite.length() <= numberOfAlphanumerics){ 624 | show(whatToActuallyWrite, decimals); 625 | return; 626 | }else{ 627 | scrollText(whatToWrite); 628 | return; 629 | } 630 | } // End showText() 631 | 632 | 633 | // Default method for scrolled text 634 | void scrollText(String input){ 635 | scrollText(input, 150, numberOfAlphanumerics); 636 | } // End scrollText(String) 637 | 638 | 639 | // Scroll the text from left to right across the displays. 640 | // This is done by calling show() on one set of characters, 641 | // shifting left by one character, and then repeating. 642 | void scrollText(String whatToScroll, int delayTime, int addedPadding){ 643 | int decimalCount = 0; 644 | String whatToActuallyWrite; 645 | String decimals; 646 | // Add padding to the scrolled text based on input request 647 | for(int i = 0; i < addedPadding; i++){ 648 | whatToScroll = " " + whatToScroll + " "; 649 | } 650 | for(int i = 0; i < whatToScroll.length(); i++){ 651 | // Append the character to the portion displayed, dealing with decimals 652 | decimals += ' '; 653 | if(whatToScroll[i] == '.'){ 654 | // Any time this loop finds a decimal, go to the last valid char and "place" one 655 | // (Note: This implementation discards any leading or standalone decimals) 656 | decimals[whatToActuallyWrite.length() - 1] = '.'; 657 | }else{ 658 | whatToActuallyWrite += whatToScroll[i]; 659 | } 660 | 661 | // Send the latest group of 3 alphanumerics to be shown, and remove the earliest character. 662 | if(whatToActuallyWrite.length() >= numberOfAlphanumerics){ 663 | show(whatToActuallyWrite, decimals); 664 | decimals.remove(0,1); 665 | whatToActuallyWrite.remove(0,1); 666 | delay(delayTime); // This delay will cause the main loop to stop operating (and thus updating LED's) if text that's too long is being shifted out. Interrupts might be a better choice. 667 | } 668 | } 669 | } // End scrollText(String, int, int) 670 | 671 | 672 | // Time-based method to show text 673 | void show(String whatToActuallyWrite, String decimals){ 674 | // Example Inputs: "123", "1.234", "1.23", "Hello World" 675 | if(whatToActuallyWrite.length() > numberOfAlphanumerics){ 676 | // The string still needs to be scrolled out. 677 | return; 678 | } 679 | 680 | // For this version we shift everything to uppercase (until lowercase values are added to the arrays above) 681 | whatToActuallyWrite.toUpperCase(); // "As of 1.0, toUpperCase() modifies the string in place rather than returning a new one." 682 | 683 | // At this point, the string can (probably) be displayed on 3 characters, so do that. 684 | // Loop for each of the 3 digits 685 | byte topDatas[] = {dataTwo, dataFour, dataSix}; 686 | byte bottomDatas[] = {dataOne, dataThree, dataFive}; 687 | // Load the input sequence into the given characters 688 | // i is the loop for each of the (nominally 3) alphanumeric characters 689 | for(int i = 0; i < numberOfAlphanumerics; i++){ 690 | for(int j = 0; j < sizeof(characterList); j++){ 691 | if(whatToActuallyWrite[i] == characterList[j]){ 692 | // The first character is the same as the current character. 693 | // Load that into the top and bottom bytes. 694 | bottomDatas[i] = characterBottoms[j]; 695 | topDatas[i] = characterTops[j]; 696 | } 697 | } 698 | if(decimals[i] == '.'){ 699 | bottomDatas[i] |= 0x10; // Append the decimal to this character 700 | } 701 | } 702 | 703 | // Ground latchPin and hold low for as long as you are transmitting 704 | digitalWrite(latchPin, 0); 705 | // Shift the bytes out 706 | for(int i = numberOfAlphanumerics - 1; i >= 0; i--){ 707 | shiftOut(dataPin, clockPin, topDatas[i]); 708 | shiftOut(dataPin, clockPin, bottomDatas[i]); 709 | } 710 | // Return the latch pin high to signal chip that it no longer needs to listen for information 711 | digitalWrite(latchPin, 1); 712 | return true; 713 | } // End show() 714 | 715 | 716 | // The method that actually shifts out the raw data to the shift registers/LED drivers 717 | void shiftOut(int myDataPin, int myClockPin, byte myDataOut) { 718 | // This shifts 8 bits out MSB first, on the rising edge of the clock, clock idles low 719 | // Internal function setup 720 | int i=0; 721 | int pinState; 722 | // Clear everything out just in case to prepare shift register for bit shifting 723 | digitalWrite(myDataPin, 0); 724 | digitalWrite(myClockPin, 0); 725 | //For each bit in the byte myDataOut notice that we are counting down in our for loop 726 | //This means that %00000001 or "1" will go through such that it will be pin Q0 that lights. 727 | for (i = 7; i >= 0; i--) { 728 | digitalWrite(myClockPin, 0); 729 | // If the value passed to myDataOut and a bitmask result true then... so if we are at i=6 and our value is %11010100 it would the code compares it to %01000000 and proceeds to set pinState to 1. 730 | if ( myDataOut & (1<