#include <Arduino.h>
#include <esp_adc_cal.h>
#include <SPI.h>
#include <FS.h> // Font files are stored in SPIFFS, so load the library
const int joyX = 39; //pin number
const int joyY = 36;
const int joyR = 35;
const int buttonA = 16;
const int buttonB = 21;
const int touchSensor = 32; //pin number
const int touchThresh = 15;
const int stepperSpeed[] = {4, 16, 50, 100}; // 4 different speeds, used for acceleration
const int servoSpeed[] = {10, 35, 70, 140};
const int joyXYThresh[] = {0, 50, 530, 850, 1500, 00000, 2200, 2700, 3100, 3800}; //12-bit ADC: there is a +/-300 null space in the middle (1885) of the joystick range
const int joyRThresh[] = {0, 50, 530, 850, 1500, 00000, 2200, 2700, 3100, 3800}; //12-bit ADC: there is a +/-300 null space in the middle (1885) of the joystick range
int joyXVal;
int joyYVal;
int joyRVal;
int L_XposReq = 0;
int L_YposReq = 0;
int L_RposReq = 1500;
bool L_touch;
int prevXval;
int prevYval;
int prevRval;
int stepperSpeedMode;
int servoSpeedMode;
int XposMin = 0; //! These 6 need to be read from calibration/last-position data (SPIFFS)
int YposMin = 0;
int RposMin = 500;
int XposMax = 10000;
int YposMax = 5000;
int RposMax = 2500;
// Graphics-related declarations
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI(); // Use hardware SPI
#define AA_FONT_SMALL "NotoSansBold15"
#define AA_FONT_LARGE "NotoSansBold36"
#define BGCOLOUR TFT_WHITE
TFT_eSprite micPointArrow = TFT_eSprite(&tft); // Create Sprite object "micPointArrow" with pointer to "tft" object. The pointer is used with *.pushSprite() to push it onto the TFT
const int micPointArrow_colour = TFT_RED;
const int micPointArrow_padding = 2;
const int micPointArrow_w = 18 + 2 * micPointArrow_padding;
const int micPointArrow_h = 28 + 2 * micPointArrow_padding;
const int resolution_x = 480; //^ Set screen resolution
const int resolution_y = 320;
const int physicalTravelX_mm = 450; //^ Physical travel of axis in millimetres
const int physicalTravelY_mm = 200;
const int positionGraph_w = 340;
const int positionGraph_h = (float)physicalTravelY_mm / physicalTravelX_mm * positionGraph_w;
const int positionGraph_x = (resolution_x - positionGraph_w - micPointArrow_w - 3);
const int positionGraph_y = 55;
const int graphBoxThickness = 2;
//^ **************** SETUP STARTS HERE ****************
void setup()
{
/*=========================================================================
Physical I/O
=========================================================================*/
pinMode(joyX, INPUT);
pinMode(joyY, INPUT);
pinMode(joyR, INPUT);
pinMode(buttonA, INPUT); //using resistor for pull-up.
pinMode(buttonB, INPUT); //using resistor for pull-up.
//pinMode(touchSensor, INPUT); //? not necessary?
/*=========================================================================
SPIFFS setup
=========================================================================*/
if (!SPIFFS.begin())
{
Serial.println("SPIFFS initialisation failed!");
while (1)
yield(); // Stay here twiddling thumbs waiting
}
Serial.println("\r\nSPIFFS available!");
// ESP32 will crash if any of the fonts are missing
bool font_missing = false;
if (SPIFFS.exists("/NotoSansBold15.vlw") == false)
font_missing = true;
if (SPIFFS.exists("/NotoSansBold36.vlw") == false)
font_missing = true;
if (font_missing)
{
Serial.println("\r\nFont missing in SPIFFS, did you upload it? (See Font_Notes.ino for details)");
while (1)
yield();
}
else
Serial.println("\r\nFonts found OK.");
/*=========================================================================
TFT graphics display setup
=========================================================================*/
tft.begin(); //? Maybe tft.init() ?? Begin seems to work
tft.setRotation(3);
tft.fillScreen(BGCOLOUR);
tft.setTextColor(TFT_BLACK, BGCOLOUR); // Set the font colour AND the background colour so the anti-aliasing works
tft.loadFont(AA_FONT_SMALL); // Must load the font first
tft.setCursor(0, 0); // Set cursor at top left of screen
Serial.begin(115200); // debug only
Serial.println("serial monitoring enabled"); // debug only
Serial.println("Graphics library started");
}
//^ ******************************************************************
//^ **************** MAIN FUNCTION STARTS HERE ****************
//^ ******************************************************************
void loop()
{
stepperXControl(); //run this routine
stepperYControl();
servoControl();
updateDisplayDynamic();
}
//^ **************** STEPPER X Control STARTS HERE ****************
//^ *****************************************************************
void stepperXControl()
{
joyXVal = analogRead(joyX);
{
if (L_XposReq > XposMin) //This block applies acceleration based on joystick position
{
if (joyXVal <= joyXYThresh[1]) //decreasing fast
{
L_XposReq -= stepperSpeed[3];
stepperSpeedMode = -4;
}
if (joyXVal > joyXYThresh[1] && joyXVal <= joyXYThresh[2]) //decreasing medium
{
L_XposReq -= stepperSpeed[2];
stepperSpeedMode = -3;
}
if (joyXVal > joyXYThresh[2] && joyXVal <= joyXYThresh[3]) //decreasing fine
{
L_XposReq -= stepperSpeed[1];
stepperSpeedMode = -2;
}
if (joyXVal > joyXYThresh[3] && joyXVal <= joyXYThresh[4]) //decreasing super-fine
{
L_XposReq -= stepperSpeed[0];
stepperSpeedMode = -1;
}
}
if (joyXVal > joyXYThresh[4] && joyXVal < joyXYThresh[6]) //centre position
{
stepperSpeedMode = 0;
}
if (L_XposReq < XposMax)
{
if (joyXVal >= joyXYThresh[6] && joyXVal < joyXYThresh[7]) //increasing super-fine
{
L_XposReq += stepperSpeed[0];
stepperSpeedMode = 1;
}
if (joyXVal >= joyXYThresh[7] && joyXVal < joyXYThresh[8]) //increasing fine
{
L_XposReq += stepperSpeed[1];
stepperSpeedMode = 2;
}
if (joyXVal >= joyXYThresh[8] && joyXVal < joyXYThresh[9]) //increasing medium
{
L_XposReq += stepperSpeed[2];
stepperSpeedMode = 3;
}
if (joyXVal >= joyXYThresh[9]) //increasing fast
{
L_XposReq += stepperSpeed[3];
stepperSpeedMode = 4;
}
}
if (abs(stepperSpeedMode) == 1) //effectively adds extra precision
{
delay(100);
}
else if (abs(stepperSpeedMode) == 2)
{
delay(30);
}
else
{
delay(10);
}
}
// Enforce min/max limits
if (L_XposReq < XposMin)
{
L_XposReq = XposMin;
}
if (L_XposReq > XposMax)
{
L_XposReq = XposMax;
}
// stepperX.write(L_XposReq); //¡ The business.
}
//^ **************** STEPPER Y Control STARTS HERE ****************
//^ *****************************************************************
void stepperYControl()
{
joyYVal = analogRead(joyY);
{
if (joyYVal <= joyXYThresh[1]) //decreasing fast
{
L_YposReq -= stepperSpeed[3];
stepperSpeedMode = -4;
}
if (joyYVal > joyXYThresh[1] && joyYVal <= joyXYThresh[2]) //decreasing medium
{
L_YposReq -= stepperSpeed[2];
stepperSpeedMode = -3;
}
if (joyYVal > joyXYThresh[2] && joyYVal <= joyXYThresh[3]) //decreasing fine
{
L_YposReq -= stepperSpeed[1];
stepperSpeedMode = -2;
}
if (joyYVal > joyXYThresh[3] && joyYVal <= joyXYThresh[4]) //decreasing super-fine
{
L_YposReq -= stepperSpeed[0];
stepperSpeedMode = -1;
}
if (joyYVal > joyXYThresh[4] && joyYVal < joyXYThresh[6]) //centre position
{
stepperSpeedMode = 0;
}
if (joyYVal >= joyXYThresh[6] && joyYVal < joyXYThresh[7]) //increasing super-fine
{
L_YposReq += stepperSpeed[0];
stepperSpeedMode = 1;
}
if (joyYVal >= joyXYThresh[7] && joyYVal < joyXYThresh[8]) //increasing fine
{
L_YposReq += stepperSpeed[1];
stepperSpeedMode = 2;
}
if (joyYVal >= joyXYThresh[8] && joyYVal < joyXYThresh[9]) //increasing medium
{
L_YposReq += stepperSpeed[2];
stepperSpeedMode = 3;
}
if (joyYVal >= joyXYThresh[9]) //increasing fast
{
L_YposReq += stepperSpeed[3];
stepperSpeedMode = 4;
}
if (abs(stepperSpeedMode) == 1) //effectively adds extra precision
{
delay(100);
}
else if (abs(stepperSpeedMode) == 2)
{
delay(30);
}
else
{
delay(10);
}
}
// Enforce min/max limits
if (L_YposReq < YposMin)
{
L_YposReq = YposMin;
}
if (L_YposReq > YposMax)
{
L_YposReq = YposMax;
}
// stepperY.write(L_YposReq); //¡ The business.
}
//^ **************** SERVO Control STARTS HERE ****************
//^ *****************************************************************
void servoControl()
{
joyRVal = analogRead(joyR);
{
if (joyRVal <= joyRThresh[1]) //decreasing fast
{
L_RposReq -= servoSpeed[3];
servoSpeedMode = -4;
}
if (joyRVal > joyRThresh[1] && joyRVal <= joyRThresh[2]) //decreasing medium
{
L_RposReq -= servoSpeed[2];
servoSpeedMode = -3;
}
if (joyRVal > joyRThresh[2] && joyRVal <= joyRThresh[3]) //decreasing fine
{
L_RposReq -= servoSpeed[1];
servoSpeedMode = -2;
}
if (joyRVal > joyRThresh[3] && joyRVal <= joyRThresh[4]) //decreasing super-fine
{
L_RposReq -= servoSpeed[0];
servoSpeedMode = -1;
}
if (joyRVal > joyRThresh[4] && joyRVal < joyRThresh[6]) //centre position
{
servoSpeedMode = 0;
}
if (joyRVal >= joyRThresh[6] && joyRVal < joyRThresh[7]) //increasing super-fine
{
L_RposReq += servoSpeed[0];
servoSpeedMode = 1;
}
if (joyRVal >= joyRThresh[7] && joyRVal < joyRThresh[8]) //increasing fine
{
L_RposReq += servoSpeed[1];
servoSpeedMode = 2;
}
if (joyRVal >= joyRThresh[8] && joyRVal < joyRThresh[9]) //increasing medium
{
L_RposReq += servoSpeed[2];
servoSpeedMode = 3;
}
if (joyRVal >= joyRThresh[9]) //increasing fast
{
L_RposReq += servoSpeed[3];
servoSpeedMode = 4;
}
if (abs(servoSpeedMode) == 1) //effectively adds extra precision
{
delay(100);
}
else if (abs(servoSpeedMode) == 2)
{
delay(30);
}
else
{
delay(10);
}
}
// Enforce min/max limits
if (L_RposReq < RposMin)
{
L_RposReq = RposMin;
}
if (L_RposReq > RposMax)
{
L_RposReq = RposMax;
}
// servo.write(L_RposReq); //¡ The business.
}
//^ **************** Dynamic display function STARTS HERE ****************
//^ ***********************************************************************
void updateDisplayDynamic()
{
//- Map variables to graph bounds
int plotX = map(L_XposReq, XposMin, XposMax, positionGraph_x, positionGraph_x + positionGraph_w); // variable, fromLow, fromHigh, toLow, toHigh
int plotY = map(L_YposReq, YposMin, YposMax, positionGraph_y + positionGraph_h, positionGraph_y); //^ inverted
int plotR = map(L_RposReq, RposMin, RposMax, -45, 45);
//- Clear mic arrow pointer sprite
micPointArrow.fillSprite(BGCOLOUR); //Since sprite hasn't yet been created, these 2 lines should do nothing until after the mic pointer arrow is first drawn (below)
micPointArrow.pushRotated(plotR);
//- Draw mic arrow pointer sprite
micPointArrow.setColorDepth(8);
micPointArrow.createSprite(micPointArrow_w, micPointArrow_h); // Create an 8 bit sprite 20*30 pixels (uses 600 bytes of RAM)
tft.setPivot(plotX, plotY); //anchor point on LCD
uint16_t micPointpiv_x = micPointArrow.width() / 2; // Define sprite pivot point. x pivot of Sprite (middle).
uint16_t micPointpiv_y = 0 + micPointArrow_padding; // y pivot of Sprite (0 = top row of pixels)
micPointArrow.setPivot(micPointpiv_x, micPointpiv_y); // Set pivot point of this Sprite
micPointArrow.fillSprite(BGCOLOUR);
micPointArrow.fillTriangle(9 + micPointArrow_padding, 1 + micPointArrow_padding, 0 + micPointArrow_padding, 28 + micPointArrow_padding, 18 + micPointArrow_padding, 28 + micPointArrow_padding, micPointArrow_colour); //Apex X,Y , Left bottom X,Y , Right bottom X,Y
micPointArrow.pushRotated(plotR, BGCOLOUR);
micPointArrow.deleteSprite(); // Delete it to free-up memory
//- Draw graph bounding box [STATIC ELEMENT, redrawn on each refresh]
//^ Mitred line T,B,L,R method
for (int i = 0; i <= (graphBoxThickness - 1); i++)
{
tft.drawLine(positionGraph_x - i, positionGraph_y - i, positionGraph_x + positionGraph_w + i, positionGraph_y - i, TFT_LIGHTGREY); // top
tft.drawLine(positionGraph_x - i, positionGraph_y + positionGraph_h + i, positionGraph_x + positionGraph_w + i, positionGraph_y + positionGraph_h + i, TFT_LIGHTGREY); //bottom
tft.drawLine(positionGraph_x - i, positionGraph_y - i, positionGraph_x - i, positionGraph_y + positionGraph_h + i, TFT_LIGHTGREY); //left
tft.drawLine(positionGraph_x + positionGraph_w + i, positionGraph_y - i, positionGraph_x + positionGraph_w + i, positionGraph_y + positionGraph_h + i, TFT_LIGHTGREY); //right
}
}