#include "Freenove_WS2812_Lib_for_ESP32.h" /*********************************************************************************/ // Constants // Pin 21 is GPIO only #define PIN_BUTTON 21 // Calibration button // Apparently there is a dedicated 2812 controller in the ESP32-S3 that lives on this pin // and this pin only. #define PIN_2812 48 // Data bus for the 2812 LED // These pins are shared with the second ADC unit. We're not using that unit in this project // but for future projects as my boards become more dense I need to remember that each pin // choice is a tradeoff. At a certain point we run out of available pins for adding stuff on, // and have to start getting creative about how we use the pins we have left. #define PIN_LED_X 16 // Red X axis LED #define PIN_LED_Y 18 // Green Y axis LED #define PIN_LED_Z 15 // Yellow Z axis LED (button) #define PWM_FREQUENCY 1000 #define PWM_BITWIDTH 12 // Pins 1 and 2 are on ADC channel 0 and 1. #define PIN_JOY_X 1 #define PIN_JOY_Y 2 // Pin 47 is GPIO only #define PIN_JOY_Z 47 // But Pin 12 is where the tutorial has it //#define PIN_JOY_Z 12 #define BUTTON_STABLETIME 100 #define JOYSTICK_STABLETIME 3000 // Button states #define BUTTON_STATE_BOUNCING (1 << 0) #define BUTTON_STATE_DOWN (1 << 1) #define BUTTON_STATE_UP (1 << 2) #define BUTTON_STATE_HELD (1 << 3) // Joystick states #define JOYSTICK_STATE_CALIBRATING (1 << 0) #define JOYSTICK_STATE_CALIBRATED (1 << 1) #define JOYSTICK_STATE_READING (1 << 2) #define JOYSTICK_STATE_READY (1 << 3) // These aren't pins, they're just defines that help us identify the lights on the 2812 unit #define LED_2812_LEFT 0 // 2812 LEDs are ordered from connection point at left #define LED_2812_TOPLEFT 1 // and go clockwise #define LED_2812_TOP 2 #define LED_2812_TOPRIGHT 3 #define LED_2812_RIGHT 4 #define LED_2812_BOTTOMRIGHT 5 #define LED_2812_BOTTOM 6 #define LED_2812_BOTTOMLEFT 7 // Error flags #define ERRNO_SUCCESS 0 #define ERRNO_NULLPOINTER 1 #define ERRNO_IO 2 #define ERRNO_TIMEOUT 3 #define ERRNO_CALIBRATION 4 #define ERROR(x) errno=x; return x; // Uses the Freenove WS2812 Lib for ESP32@2.0.1 /**********************************************************************************/ // Interface structures typedef struct Calibration { // When did calibration start uint32_t start_time; // Low calibration range uint32_t low; // High calibration range uint32_t high; // How long we should sample values on this joystick uint32_t stable_time; } Calibration; typedef struct Button { // What pin is this button on uint8_t pin; // What is the state of this button (bitmask of BUTTON_STATE_*) uint8_t state; // Is this button pressed when it is LOW or HIGH? uint32_t pressedvalue; // How long do we wait when debouncing this button uint32_t debouncetime; // When did we start debouncing uint32_t debounce_start_time; } Button; typedef struct JoystickAxis { // What ADC pin is this joystick axis connected to uint8_t pin; // Calibration data Calibration calibration; // Position as of the last read time. uint32_t position; } JoystickAxis; typedef struct Joystick { // X axis data JoystickAxis x; // Y axis data JoystickAxis y; // This is the Z axis switch on the joystick control Button button; // What is the state of this joystick (bitmask of JOYSTICK_STATE_*) uint8_t state; } Joystick; /*******************************************************************************/ // Global Variables int errno; Button calibration_button; Joystick js; alignas(Freenove_ESP32_WS2812) uint8_t strip_storage[sizeof(Freenove_ESP32_WS2812)]; Freenove_ESP32_WS2812 *strip; /******************************************************************************/ // Initialization Functions void initSerial() { Serial.begin(115200); } int init2812(Freenove_ESP32_WS2812 **strip) { Freenove_ESP32_WS2812 *obj = new(strip_storage) Freenove_ESP32_WS2812(8, PIN_2812, 0, TYPE_GRB); if ( obj == NULL ) { ERROR(ERRNO_NULLPOINTER); } obj->setBrightness(10); obj->begin(); *strip = obj; return ERRNO_SUCCESS; } int initJoystick(Joystick *js) { if ( js == NULL ) { ERROR(ERRNO_NULLPOINTER); } js->button.pin = PIN_JOY_Z; js->button.debouncetime = BUTTON_STABLETIME; js->button.pressedvalue = LOW; pinMode(js->button.pin, INPUT_PULLUP); js->x.pin = PIN_JOY_X; js->x.calibration.stable_time = JOYSTICK_STABLETIME; js->y.pin = PIN_JOY_Y; js->y.calibration.stable_time = JOYSTICK_STABLETIME; return ERRNO_SUCCESS; } int initButton(Button *button) { pinMode(button->pin, INPUT); return ERRNO_SUCCESS; } int initLEDs() { // Attach LEDC to the joystick status LED pins ledcAttach(PIN_LED_X, PWM_FREQUENCY, PWM_BITWIDTH); ledcAttach(PIN_LED_Y, PWM_FREQUENCY, PWM_BITWIDTH); pinMode(PIN_LED_Z, OUTPUT); return ERRNO_SUCCESS; } /****************************************************************************************/ // IO Routines // Calibrate a given joystick int calibrateJoystick(Joystick *js) { uint32_t x = 0; uint32_t y = 0; uint32_t curmillis = 0; if ( js == NULL ) { ERROR(ERRNO_NULLPOINTER); } curmillis = millis(); if ( js->x.calibration.start_time == 0 || js->y.calibration.start_time == 0 ) { // Starting a new calibration cycle Serial.printf("Starting a new calibration cycle\n"); js->x.calibration.low = 65535; js->x.calibration.high = 0; js->y.calibration.low = 65535; js->y.calibration.high = 0; js->x.calibration.start_time = curmillis; js->y.calibration.start_time = curmillis; return ERRNO_SUCCESS; } if ( (curmillis - js->x.calibration.start_time) >= js->x.calibration.stable_time ) { js->state = JOYSTICK_STATE_CALIBRATED; js->x.calibration.start_time = 0; js->y.calibration.start_time = 0; Serial.printf("Calibrated joystick to x: <%d, %d> y <%d, %d>\n", js->x.calibration.low, js->x.calibration.high, js->y.calibration.low, js->y.calibration.high); return ERRNO_SUCCESS; } x = analogRead(js->x.pin); if ( x < js->x.calibration.low ) { js->x.calibration.low = x; } else if ( x > js->x.calibration.high ) { js->x.calibration.high = x; } y = analogRead(js->y.pin); if ( y < js->y.calibration.low ) { js->y.calibration.low = y; } else if ( y > js->y.calibration.high ) { js->y.calibration.high = y; } return ERRNO_SUCCESS; } // Read the state of a given button int readButton(Button *button) { // We could improve this method further. It doesn't *really* debounce. It just introduces a delay between the // point of first contact and the time when the value is read. A true debouncing would wait until the state was // *stable* for a given period of time. I may come back and change it to work this way later. uint32_t pinvalue; uint32_t curmillis; uint32_t buttonvalue; if ( button == NULL ) { ERROR(ERRNO_NULLPOINTER); } curmillis = millis(); buttonvalue = digitalRead(button->pin); if ( (button->state & BUTTON_STATE_BOUNCING) == BUTTON_STATE_BOUNCING ) { if ( (curmillis - button->debounce_start_time) >= button->debouncetime ) { if ( buttonvalue == button->pressedvalue ) { button->state = BUTTON_STATE_DOWN; } else { button->state = BUTTON_STATE_UP; } Serial.printf("Button on pin %d has debounced state %d\n", button->pin, button->state); button->debounce_start_time = 0; return ERRNO_SUCCESS; } return ERRNO_SUCCESS; } if ( buttonvalue != button->pressedvalue && ( (button->state & BUTTON_STATE_DOWN) == BUTTON_STATE_DOWN || (button->state & BUTTON_STATE_HELD) == BUTTON_STATE_HELD)){ // We are beginning to release. Debounce. Serial.printf("Debouncing button on pin %d for release\n", button->pin); button->state = button->state | BUTTON_STATE_BOUNCING; button->debounce_start_time = curmillis; } if ( buttonvalue == button->pressedvalue && ( (button->state & BUTTON_STATE_DOWN) != BUTTON_STATE_DOWN && (button->state & BUTTON_STATE_HELD) != BUTTON_STATE_HELD)){ // We are beginning to press. Debounce. Serial.printf("Debouncing button on pin %d for press\n", button->pin); button->state = button->state | BUTTON_STATE_BOUNCING; button->debounce_start_time = curmillis; } return ERRNO_SUCCESS; } int readJoystick(Joystick *js) { uint32_t adcvalue = 0; if ( js == NULL ) { ERROR(ERRNO_NULLPOINTER); } js->state = js->state | JOYSTICK_STATE_READING; adcvalue = analogRead(js->x.pin); if ( adcvalue < js->x.calibration.low || adcvalue > js->x.calibration.high ) { js->x.position = adcvalue; } else { // Snap to center js->x.position = 2048; } adcvalue = analogRead(js->y.pin); if ( adcvalue < js->y.calibration.low || adcvalue > js->y.calibration.high ) { js->y.position = adcvalue; } else { // Snap to center js->y.position = 2048; } //Serial.printf("Joystick x %d y %d\n", js->x.position, js->y.position); readButton(&js->button); js->state = js->state ^ JOYSTICK_STATE_READING; if ( errno != ERRNO_SUCCESS ) { Serial.printf("Failed to read joystick button : %d\n", errno); return errno; } js->state = js->state | JOYSTICK_STATE_READY; return ERRNO_SUCCESS; } int displayLEDs(Joystick *js) { if ( js == NULL ) { ERROR(ERRNO_NULLPOINTER); } // Is the joystick fully ready? Display state on the LEDs if ( ( js->state & JOYSTICK_STATE_READY ) == JOYSTICK_STATE_READY ) { ledcWrite(PIN_LED_X, js->x.position); ledcWrite(PIN_LED_Y, js->y.position); if ( ( js->button.state & BUTTON_STATE_DOWN ) == BUTTON_STATE_DOWN ) { digitalWrite(PIN_LED_Z, HIGH); } else { digitalWrite(PIN_LED_Z, LOW); } return ERRNO_SUCCESS; } // Is the joystick calibrating? Turn all the LEDs off if ( js->state == JOYSTICK_STATE_CALIBRATING ) { ledcWrite(PIN_LED_X, 1); ledcWrite(PIN_LED_Y, 1); digitalWrite(PIN_LED_Z, LOW); } return ERRNO_SUCCESS; } int display2812(Joystick *js, Freenove_ESP32_WS2812 *strip) { if ( js == NULL ) { ERROR(ERRNO_NULLPOINTER); } strip->setLedColorData(LED_2812_LEFT, 0, 0, 0); strip->setLedColorData(LED_2812_TOPLEFT, 0, 0, 0); strip->setLedColorData(LED_2812_TOP, 0, 0, 0); strip->setLedColorData(LED_2812_TOPRIGHT, 0, 0, 0); strip->setLedColorData(LED_2812_RIGHT, 0, 0, 0); strip->setLedColorData(LED_2812_BOTTOMRIGHT, 0, 0, 0); strip->setLedColorData(LED_2812_BOTTOM, 0, 0, 0); strip->setLedColorData(LED_2812_BOTTOMLEFT, 0, 0, 0); // Is the joystick fully ready? Display direction on the 2812 if ( ( js->state & JOYSTICK_STATE_READY ) == JOYSTICK_STATE_READY ) { // Light up the correct LED_2812_* depending on the value of the X/Y axes on the joystick if ( js->x.position < 1024 && (js->y.position < 3072 && js->y.position > 1024) ) { strip->setLedColorData(LED_2812_LEFT, 128, 128, 128); } else if ( js->x.position < 1024 && js->y.position < 1024 ) { strip->setLedColorData(LED_2812_TOPLEFT, 128, 128, 128); } else if ( ( js->x.position > 1024 && js->x.position < 3072 ) && js->y.position < 1024 ) { strip->setLedColorData(LED_2812_TOP, 128, 128, 128); } else if ( js->x.position > 3072 && js->y.position < 1024 ) { strip->setLedColorData(LED_2812_TOPRIGHT, 128, 128, 128); } else if ( js->x.position > 3072 && ( js->y.position < 3072 && js->y.position > 1024) ) { strip->setLedColorData(LED_2812_RIGHT, 128, 128, 128); } else if ( js->x.position > 3072 && js->y.position > 3072 ) { strip->setLedColorData(LED_2812_BOTTOMRIGHT, 128, 128, 128); } else if ( ( js->x.position > 1024 && js->x.position < 3072 ) && js->y.position > 3072 ) { strip->setLedColorData(LED_2812_BOTTOM, 128, 128, 128); } else if ( js->x.position < 1024 && js->y.position > 3072 ) { strip->setLedColorData(LED_2812_BOTTOMLEFT, 128, 128, 128); } } strip->show(); return ERRNO_SUCCESS; } /**********************************************************************************************/ // Setup and Loop void setup() { errno = ERRNO_SUCCESS; memset(&js, 0x00, sizeof(Joystick)); memset(&calibration_button, 0x00, sizeof(Button)); calibration_button.pin = PIN_BUTTON; calibration_button.debouncetime = BUTTON_STABLETIME; calibration_button.pressedvalue = LOW; initSerial(); if ( init2812(&strip) != ERRNO_SUCCESS ) Serial.printf("Failed to initialize 2812 LED array : %d\n", errno); if ( initLEDs() != ERRNO_SUCCESS ) Serial.printf("Failed to initialize LED GPIO pins : %d\n", errno); if ( initJoystick(&js) != ERRNO_SUCCESS ) Serial.printf("Failed to initialized Joystick datastructure : %d\n", errno); if ( initButton(&calibration_button) != ERRNO_SUCCESS ) Serial.printf("Failed to initialize Calibration button and GPIO pin : %d\n", errno); } void loop() { if ( readButton(&calibration_button) != ERRNO_SUCCESS ) Serial.printf("Failed to read calibration button : %d\n", errno); if ( calibration_button.state == BUTTON_STATE_DOWN && js.state != JOYSTICK_STATE_CALIBRATING ) { // A calibrating joystick cannot be in any other state, so don't mask Serial.printf("Calibrating joystick\n"); calibration_button.state = BUTTON_STATE_HELD; js.state = JOYSTICK_STATE_CALIBRATING; } if ( js.state == JOYSTICK_STATE_CALIBRATING ) { if ( calibrateJoystick(&js) != ERRNO_SUCCESS ) { Serial.printf("Failed to calibrate joystick : %d\n", errno); } } if ( ( js.state & JOYSTICK_STATE_CALIBRATED) == JOYSTICK_STATE_CALIBRATED ) { if ( readJoystick(&js) != ERRNO_SUCCESS ) { Serial.printf("Failed to read joystick state : %d\n", errno); } } if ( displayLEDs(&js) != ERRNO_SUCCESS ) { Serial.printf("Failed to display LEDs : %d\n", errno); } if ( display2812(&js, strip) != ERRNO_SUCCESS ) { Serial.printf("Failed to display 2812 LEDs : %d\n", errno); } }