├── .gitignore ├── .vscode └── extensions.json ├── README.md ├── include └── README ├── lib └── README ├── platformio.ini ├── src └── main.cpp └── test └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LightBox 2 | A personal Arduino / ESP32 Platform.io project for controlling lights on an rc drift car based on receiver values. Could easily be adapted to suit any surface vehicle. 3 | The controller code contained in src/main.cpp is intended to be very readable and self-documenting. Feel free to use/modify or create a pull request/ form if you want to use this elsewhere. 4 | 5 | This project is designed to run on an Arduino device connected to a string of WS2811/12 LEDS from a single pin. Lamps are defined by their address in that led chain as well as their purpose (Always on, responds to signal, etc). The project also uses up to 4 serial ports on the Arduino device to read incoming receiver values. Typically you'd connect the receiver to serial pots using a Y adapter off of each servo signal wire (throttle, turn, aux). An enterprising person could change this to read values from a composite signal stream (S.Bus, I.Bus, PPM) 6 | 7 | Some features: 8 | 9 | - Running lights 10 | - Brake lights 11 | - Turn Signals 12 | - Reverse signal 13 | - Throttle based exhaust backfire effect. 14 | - Configurable light colors 15 | - Configurable light arrangement 16 | - Global car state and receiver state objects 17 | - Configurable car state logic 18 | - Uses FastLED for LED control 19 | - Uses ArduinoTimer for blinker intervals 20 | 21 | 22 | ## TODO 23 | 24 | - Mode switching based on Aux channel 25 | - Additional 'Light show' modes. (using WS2812FX if feasible). 26 | 27 | ## Wish List 28 | 29 | 30 | - Gyro based car states / effects. 31 | - Engine sound simulator. Grain table synth? 32 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:wemos_d1_mini32] 12 | platform = espressif32 13 | board = wemos_d1_mini32 14 | framework = arduino 15 | lib_deps = 16 | dmadison/ServoInput@^1.0.1 17 | fastled/FastLED@^3.4.0 18 | arkhipenko/TaskScheduler@^3.2.2 19 | contrem/arduino-timer@^2.3.0 20 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #define FASTLED_INTERNAL //Supress FastLED pragma warning 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define LED_COUNT 17 8 | #define LED_PIN 15 9 | 10 | #define THROTTLE_RC_PIN 4 11 | #define STEERING_RC_PIN 5 12 | 13 | CRGB leds[LED_COUNT]; 14 | 15 | CHSV headlightColor = CHSV(70, 45, 128); 16 | CHSV highbeamColor = CHSV(193, 115, 255); 17 | CHSV turnSignalColor = CHSV(64, 255, 145); 18 | CHSV fogLightColor = CHSV(70, 250, 255); 19 | CHSV brakelightColor = CHSV(0, 255, 255); 20 | CHSV brakelightDimColor = CHSV(0, 255, 55); 21 | CHSV exhaustLampColor = CHSV(150, 255, 50); 22 | CHSV offColor = CHSV(0, 0, 0); 23 | 24 | enum RCChannels 25 | { 26 | STEERING, 27 | THROTTLE, 28 | AUX 29 | }; 30 | 31 | struct ReceiverStates 32 | { 33 | int steeringValue; 34 | int throttleValue; 35 | int auxValue; 36 | }; 37 | 38 | float steeringAngle; 39 | int throttlePercent; 40 | 41 | enum CarLamps 42 | { 43 | LF_TURN, 44 | LF_HEADLIGHT, 45 | LF_HIGHBEAM, 46 | LF_FOG, 47 | RF_HIGHBEAM, 48 | RF_HEADLIGHT, 49 | RF_TURN, 50 | RF_FOG, 51 | RR_BRAKE, 52 | RR_TURN, 53 | LR_TURN, 54 | LR_BRAKE, 55 | C_RUNNING, 56 | RR_LICENSE_PLATE, 57 | LR_LICENSE_PLATE, 58 | EXHAUST_1, 59 | EXHAUST_2 60 | }; 61 | 62 | struct LampConfig 63 | { 64 | CarLamps carLamp; 65 | CHSV activeColor; 66 | CHSV inactiveColor; 67 | }; 68 | 69 | LampConfig lampsConfig[] = { 70 | {LF_TURN, turnSignalColor, offColor}, 71 | {LF_HEADLIGHT, headlightColor, offColor}, 72 | {LF_HIGHBEAM, highbeamColor, offColor}, 73 | {LF_FOG, fogLightColor, offColor}, 74 | {RF_HIGHBEAM, highbeamColor, offColor}, 75 | {RF_HEADLIGHT, headlightColor, offColor}, 76 | {RF_TURN, turnSignalColor, offColor}, 77 | {RF_FOG, fogLightColor, offColor}, 78 | {RR_TURN, turnSignalColor, offColor}, 79 | {RR_BRAKE, brakelightColor, brakelightDimColor}, 80 | {LR_BRAKE, brakelightColor, brakelightDimColor}, 81 | {LR_TURN, turnSignalColor, offColor}, 82 | {C_RUNNING, brakelightColor, brakelightDimColor}, 83 | {RR_LICENSE_PLATE, brakelightColor, offColor}, 84 | {LR_LICENSE_PLATE, headlightColor, offColor}, 85 | {EXHAUST_1, exhaustLampColor, offColor}, 86 | {EXHAUST_2, exhaustLampColor, offColor}}; 87 | 88 | LampConfig getLampConfig(CarLamps carLamp) 89 | { 90 | for (int i = 0; i < LED_COUNT; i++) 91 | { 92 | if (lampsConfig[i].carLamp == carLamp) 93 | { 94 | return lampsConfig[i]; 95 | } 96 | } 97 | }; 98 | 99 | enum CarStates 100 | { 101 | DRIVING, 102 | TURBO, 103 | BRAKING, 104 | REVERSING, 105 | IDLING, 106 | TURNING_LEFT, 107 | TURNING_RIGHT, 108 | NOT_TURNING 109 | }; 110 | 111 | CarStates carState[2]; 112 | 113 | const int SteeringSignalPin = STEERING_RC_PIN; // MUST be interrupt-capable! 114 | const int SteeringPulseMin = 1000; // microseconds (us) 115 | const int SteeringPulseMax = 2000; // Ideal values for your servo can be found with the "Calibration" example 116 | 117 | ServoInputPin steering(SteeringPulseMin, SteeringPulseMax); 118 | 119 | // Throttle Setup 120 | const int ThrottleSignalPin = THROTTLE_RC_PIN; // MUST be interrupt-capable! 121 | const int ThrottlePulseMin = 1000; // microseconds (us) 122 | const int ThrottlePulseMax = 2000; // Ideal values for your servo can be found with the "Calibration" example 123 | 124 | ServoInputPin throttle(ThrottlePulseMin, ThrottlePulseMax); 125 | 126 | const int blinkerIntervalMs = 1500; 127 | auto timer = timer_create_default(); 128 | 129 | void updateSteeringState() 130 | { 131 | if (steeringAngle < -30.0) 132 | { 133 | carState[STEERING] = TURNING_LEFT; 134 | } 135 | else if (steeringAngle > 30) 136 | { 137 | carState[STEERING] = TURNING_RIGHT; 138 | } 139 | else 140 | { 141 | carState[STEERING] = NOT_TURNING; 142 | } 143 | }; 144 | 145 | void updateThrottleState() 146 | { 147 | if (throttlePercent < -75) 148 | { 149 | carState[THROTTLE] = REVERSING; 150 | } 151 | else if (throttlePercent < -5) 152 | { 153 | carState[THROTTLE] = BRAKING; 154 | } 155 | else if (throttlePercent <= 10) 156 | { 157 | carState[THROTTLE] = DRIVING; 158 | } 159 | else if (throttlePercent >= 33) 160 | { 161 | carState[THROTTLE] = TURBO; 162 | } 163 | else 164 | { 165 | carState[THROTTLE] = IDLING; 166 | } 167 | }; 168 | 169 | void updateCarStates() 170 | { 171 | updateSteeringState(); 172 | updateThrottleState(); 173 | }; 174 | 175 | void updateRCValues() 176 | { 177 | steeringAngle = (90.0 - steering.getAngle()) * -1; // returns 0 - 180, subtracting from 90 to center at "0" and invert for "normal" steering 178 | throttlePercent = throttle.map(-100, 100); // remap to a percentage both forward and reverse 179 | }; 180 | 181 | 182 | bool blinkerOn = false; 183 | 184 | bool serviceSteeringSignalLamps(void *) 185 | { 186 | CHSV toggleColor; 187 | if(!blinkerOn){ 188 | toggleColor = turnSignalColor; 189 | blinkerOn = true; 190 | } else { 191 | toggleColor = offColor; 192 | blinkerOn = false; 193 | } 194 | switch(carState[STEERING]){ 195 | case TURNING_LEFT: 196 | leds[LF_TURN] = toggleColor; 197 | leds[LR_TURN] = toggleColor; 198 | leds[RF_TURN] = offColor; 199 | leds[RR_TURN] = offColor; 200 | break; 201 | 202 | case TURNING_RIGHT: 203 | leds[RF_TURN] = toggleColor; 204 | leds[RR_TURN] = toggleColor; 205 | leds[LF_TURN] = offColor; 206 | leds[LR_TURN] = offColor; 207 | break; 208 | 209 | default: 210 | leds[LF_TURN] = offColor; 211 | leds[LR_TURN] = offColor; 212 | leds[RF_TURN] = offColor; 213 | leds[RR_TURN] = offColor; 214 | break; 215 | }; 216 | return true; 217 | } 218 | 219 | 220 | 221 | void initRunningMode() 222 | { 223 | //Default light states for standard running mode 224 | leds[LF_HEADLIGHT] = getLampConfig(LF_HEADLIGHT).activeColor; 225 | leds[RF_HEADLIGHT] = getLampConfig(RF_HEADLIGHT).activeColor; 226 | leds[LR_BRAKE] = getLampConfig(LR_BRAKE).inactiveColor; 227 | leds[RR_BRAKE] = getLampConfig(RR_BRAKE).inactiveColor; 228 | leds[LR_LICENSE_PLATE] = getLampConfig(LR_LICENSE_PLATE).inactiveColor; 229 | leds[RR_LICENSE_PLATE] = getLampConfig(LR_LICENSE_PLATE).inactiveColor; 230 | leds[C_RUNNING] = getLampConfig(C_RUNNING).inactiveColor; 231 | leds[EXHAUST_1] = getLampConfig(EXHAUST_1).inactiveColor; 232 | leds[EXHAUST_2] = getLampConfig(EXHAUST_2).inactiveColor; 233 | } 234 | 235 | 236 | 237 | void serviceTurboMode(){ 238 | 239 | int randomValue = random(0, throttlePercent * exhaustLampColor.val); 240 | CHSV tempColor = CHSV(exhaustLampColor.hue, exhaustLampColor.sat - (exhaustLampColor.sat * throttlePercent), randomValue); 241 | leds[EXHAUST_1] = tempColor; 242 | leds[EXHAUST_2] = tempColor; 243 | Serial.println(randomValue); 244 | } 245 | 246 | void serviceThrottleSignalLamps() 247 | { 248 | switch (carState[THROTTLE]) 249 | { 250 | case DRIVING: 251 | //move driving mode init here from main loop. 252 | break; 253 | 254 | case TURBO: 255 | serviceTurboMode(); 256 | break; 257 | 258 | case BRAKING: 259 | leds[RR_BRAKE] = getLampConfig(RR_BRAKE).activeColor; 260 | leds[LR_BRAKE] = getLampConfig(LR_BRAKE).activeColor; 261 | leds[C_RUNNING] = getLampConfig(C_RUNNING).activeColor; 262 | break; 263 | case REVERSING: 264 | leds[LR_LICENSE_PLATE] = getLampConfig(LR_LICENSE_PLATE).activeColor; 265 | leds[RR_LICENSE_PLATE] = getLampConfig(RR_LICENSE_PLATE).activeColor; 266 | break; 267 | case IDLING: 268 | //todo countdown to lights off 269 | break; 270 | } 271 | }; 272 | 273 | void serviceSignalLamps() 274 | { 275 | initRunningMode(); 276 | serviceThrottleSignalLamps(); 277 | }; 278 | 279 | void setup() 280 | { 281 | 282 | Serial.begin(9600); 283 | FastLED.addLeds(leds, LED_COUNT); 284 | while (!ServoInput.available()) 285 | { // wait for all signals to be ready 286 | //Serial.println("Waiting for servo signals..."); 287 | delay(500); 288 | } 289 | timer.every(1000, serviceSteeringSignalLamps); 290 | 291 | }; 292 | 293 | void loop() 294 | { 295 | updateRCValues(); 296 | updateCarStates(); 297 | serviceSignalLamps(); 298 | timer.tick(); 299 | FastLED.show(); 300 | } 301 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | --------------------------------------------------------------------------------