// Create potentiometers with markers.
// by Koepel, 3 June 2024, Public Domain
//
// With common UTF-8 characters that most fonts have.
// I used this website: https://symbl.cc/en/unicode-table/#dingbats
//
// Still to do:
// Good description of process to draw a svg file.
//
// Is nullptr preferred over NULL ?
#undef round
#include <Wire.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>
#include <Servo.h>
hd44780_I2Cexp lcd1(0x27);
hd44780_I2Cexp lcd2(0x38);
const int potPinMotorSpeed = A0;
const int potPinVolume = A1;
const int potPinThrottle = A2;
const int potPinSelectNumber = A3;
const int ledPin = 13;
const int servoPin = 8;
int previousMotorSpeed = -1; // force update with impossible value
int previousSelectNumber = -1; // force update with impossible value
int previousVolume = -1;
int previousThrottle = -1;
bool motorOverheating = false;
bool previousMotorOverheating = false;
Servo myServo;
int servoPos = 0; // 0...180
int servoDirection = 1; // positive or negative
int servoStep = 4; // angle step per interval
// The counter for the text ids, for the generators.
int textid = 1; // start number for id
int potid = 1; // start number for id
int servoid = 1; // start number for id
int lcdid = 1; // start number for id
// The location is changed, every time something is generated.
int location = 0; // starting location
// Variables to check the temperature of the motor
unsigned long previousMillis;
const unsigned long interval = 200UL;
float temperature = 20.0;
float roomTemperature = 20.0;
const float maxTemperature = 100.0;
// The cooling in percentage per interval
const float cooling = 10.0;
// There are 10 numbers for "Select Number", 0...9.
// There are 20 characters in a row for the 20x4 display.
// Add one for zero terminator.
char message[10][21] =
{
// 01234567890123456789
{ "Try potentiometers. " }, // 0
{ "An Anode is postive." }, // 1
{ "Cathode is negative." }, // 2
{ "Read wokwi.com Docs " }, // 3
{ "Try \"Logic Analyzer\"" }, // 4
{ "Cool: Custom Chips " }, // 5
{ "The \"R\" rotates. " }, // 6
{ "Use \"G\" for grid. " }, // 7
{ "\"D\" for duplicate. " }, // 8
{ "Shift selects area. " }, // 9
};
// Function prototyping
void GeneratePotentiometer(int numbers = 10, char *pOverdrive = NULL);
void GenerateSlidePotentiometer(bool throttle = false);
void GenerateServo();
void GenerateLCD(int cols, int rows);
void GenerateCircle(int radius = 100);
void setup()
{
Serial.begin(115200);
/*
// The Generator functions can be called more than once.
GeneratePotentiometer(12, "overdrive");
GeneratePotentiometer(9);
GeneratePotentiometer(9);
GeneratePotentiometer(9);
GenerateSlidePotentiometer();
GenerateSlidePotentiometer(true);
GenerateServo();
GenerateLCD(16,2);
GenerateLCD(20,4);
GenerateCircle(50);
GenerateCircle(100);
*/
pinMode(ledPin, OUTPUT);
int status;
status = lcd1.begin(16, 2);
if (status)
{
// hd44780 has a fatalError() routine that blinks an led if possible
// begin() failed so blink error code using the onboard LED if possible
hd44780::fatalError(status); // does not return
}
status = lcd2.begin(20, 4);
if (status)
{
// hd44780 has a fatalError() routine that blinks an led if possible
// begin() failed so blink error code using the onboard LED if possible
hd44780::fatalError(status); // does not return
}
lcd1.clear();
// 0123456789012345
// ----------------
// 0| Motor: Overdrive
// 1| Vol: 10 #9 T: F9
lcd1.setCursor(0, 0);
lcd1.print("Motor: ");
lcd1.setCursor(0, 1);
lcd1.print("Vol: # T: ");
lcd2.clear();
// 01234567890123456789
// --------------------
// 0| Demonstration
// 1| with "wokwi-text"
// 2|
// 3|
lcd2.setCursor(0, 0);
lcd2.print(" Demonstration");
lcd2.setCursor(0, 1);
lcd2.print(" with \"wokwi-text\" ");
myServo.attach(servoPin);
}
void loop()
{
int rawADC;
float step;
unsigned long currentMillis = millis();
// -------------------------------------
// Motor speed
// -------------------------------------
rawADC = analogRead(potPinMotorSpeed);
// There are 13 levels on the potentiometer (0...12).
// There are 1024 levels for the ADC.
// That is not a whole number. Therefor 'float' is used.
step = 1024.0 / 12.0;
int motorSpeed = int(round(float(rawADC) / step));
if(motorSpeed != previousMotorSpeed)
{
lcd1.setCursor(7, 0);
lcd1.print(" ");
lcd1.setCursor(7, 0);
if(motorSpeed == 12)
lcd1.print("Overdrive");
else
lcd1.print(motorSpeed);
previousMotorSpeed = motorSpeed;
}
// -------------------------------------
// Select Number
// -------------------------------------
rawADC = analogRead(potPinSelectNumber);
// There are 10 possible numbers (0...9).
// There are 1024 levels for the ADC.
step = 1024.0 / 9.0;
int selectNumber = int(round(float(rawADC) / step));
if (selectNumber != previousSelectNumber)
{
lcd1.setCursor(9, 1);
lcd1.print(selectNumber);
previousSelectNumber = selectNumber;
// Extra: Show text on display
// Extra Extra: Message for motor overheating has priority.
lcd2.setCursor(0, 3);
if(!motorOverheating)
lcd2.print(message[selectNumber]);
}
// -------------------------------------
// Volume
// -------------------------------------
// The volume from 0 to 10 (inclusive 10)
step = 1024.0 / 10.0;
rawADC = analogRead(potPinVolume);
int volume = int(round(float(rawADC) / step));
if(volume != previousVolume)
{
lcd1.setCursor(5,1);
lcd1.print(" ");
lcd1.setCursor(volume < 10 ? 6 : 5, 1);
lcd1.print(volume);
previousVolume = volume;
}
// -------------------------------------
// Throttle
// -------------------------------------
// In the middle is a neutral.
// Then there are 6 steps up and 6 steps down.
// Text:
// Neutral: "N"
// Forward: "F1" to "F6"
// Reverse: "R1" to "R6"
step = 1024.0 / 12.0;
rawADC = analogRead(potPinThrottle);
int throttle = int(round(float(rawADC) / step)) - 6;
if(throttle != previousThrottle)
{
lcd1.setCursor(14,1);
if(throttle == 0)
{
lcd1.print("N ");
}
else if(throttle > 0)
{
lcd1.print("F");
lcd1.print(throttle);
}
else
{
lcd1.print("R");
lcd1.print(-throttle);
}
previousThrottle = throttle;
}
// -------------------------------------
// Servo
// -------------------------------------
// Makes use of the interval.
// Make the servo speed depend on the volume,
// but not in a obvious way.
servoStep = 4 + int((volume*volume)/10.0);
servoPos += servoDirection * servoStep;
if(servoPos <= 0)
{
servoPos = 0;
servoDirection = -servoDirection;
}
if(servoPos >= 180)
{
servoPos = 180;
servoDirection = -servoDirection;
}
myServo.write(servoPos);
// -------------------------------------
// Check the temperature of the motor
// -------------------------------------
if( currentMillis - previousMillis >= interval)
{
previousMillis = currentMillis;
// Add energy to the temperature
temperature += 10 * (float(motorSpeed) / 12.0);
// Leak away energy with a percentage
// to the room temperature.
temperature -= (temperature - roomTemperature)*cooling/100.0;
// Turn on led if the temperature is too high.
motorOverheating = (temperature > maxTemperature) ? true : false;
if(motorOverheating != previousMotorOverheating)
{
if(motorOverheating)
{
digitalWrite(ledPin, HIGH);
lcd2.setCursor(0, 3);
// 01234567890123456789
lcd2.print("MOTOR IS OVERHEATING");
}
else
{
digitalWrite(ledPin, LOW);
lcd2.setCursor(0, 3);
// 01234567890123456789
lcd2.print(" Thank You ");
}
previousMotorOverheating = motorOverheating;
}
}
delay(50); // slow down the sketch
}
// The function GeneratePotentiometer() can be used to generate
// the combination of parts for the diagram.json file for Wokwi.
// To use it, call it from the setup() function, and the
// serial output can be copied into the "parts"
// section of the diagram.json file.
//
// In the diagram itself, the Shift key plus mouse left button
// can select all the parts to move them.
//
// Parameters:
// numbers, the amount of numbers, 10 or 12 are normal values.
// pOverdrive, optional text for top.
void GeneratePotentiometer(int numbers = 10, char *pOverdrive = NULL)
{
// Overall offset for everything
const int x_offset = 0 + location;
const int y_offset = 0;
location += 300; // for the next time this function is called.
// The radius for the small lines around
// the potentiometer.
const int radius_marker = 65;
const int radius_number = 84;
// The offset for the center of the knob of the potentiometer
int x_knob_center = 33;
int y_knob_center = 18;
bool optionalOverdrive = (pOverdrive != NULL and strlen(pOverdrive) != 0);
// Place the potentiometer
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-potentiometer\",\n");
Serial.print( " \"id\": \"gpot");
Serial.print( potid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y_offset);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x_offset);
Serial.print( ",\n");
Serial.print( " \"rotate\": ");
Serial.print( "0");
Serial.print( ",\n");
Serial.print( " \"attrs\": {}\n");
Serial.print( " },\n");
// Divide by 12 steps.
// Level 0 ... 12 are printed, that are 13 things.
// The potentiometer turns from -135 to +135.
// Smaller lines are printed in between.
// That makes 13 + 12 lines.
for(int i=0; i<=(2*numbers); i++)
{
float angle1 = -135.0 + float(i)*((360.0-90.0)/float(2*numbers));
float x1 = float(x_offset) + float(x_knob_center) + radius_marker * sin(angle1/180.0*M_PI);
float y1 = float(y_offset) + float(y_knob_center) - radius_marker * cos(angle1/180.0*M_PI);
// Possible UTF-8 characters: ┃ ╹ ╻ │ ╵ ╷
char *pLine = "│"; // UTF-8 character, needs more than a byte
if( i%2 == 1)
pLine = "╷";
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-text\",\n");
Serial.print( " \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y1);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x1);
Serial.print( ",\n");
Serial.print( " \"rotate\": ");
Serial.print( angle1);
Serial.print( ",\n");
Serial.print( " \"attrs\": { \"text\": \"");
Serial.print( pLine);
Serial.print( "\"}\n");
Serial.print( " },\n");
}
for(int i=0; i<=numbers; i++)
{
float angle2 = -135.0 + float(i)*((360.0-90.0)/float(numbers));
float x2 = float(x_offset) + float(x_knob_center) + radius_number * sin(angle2/180.0*M_PI);
float y2 = float(y_offset) + float(y_knob_center) - radius_number * cos(angle2/180.0*M_PI);
if(i==numbers and optionalOverdrive)
{
angle2 -= 90.0;
x2 -= 7;
y2 += 17;
}
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-text\",\n");
Serial.print( " \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y2);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x2);
Serial.print( ",\n");
Serial.print( " \"rotate\": ");
Serial.print( angle2);
Serial.print( ",\n");
Serial.print( " \"attrs\": { \"text\": \"");
if(i==numbers and optionalOverdrive)
Serial.print( pOverdrive);
else
Serial.print( i);
Serial.print( "\"}\n");
Serial.print( " },\n");
}
// Wait some time to be sure that everything has been send.
delay(500);
}
// Normally a "volume" of 0...10 is created.
// The "throttle" is for Neutral Forward and Reverse.
void GenerateSlidePotentiometer(bool throttle = false)
{
// Overall offset for everything
const int x_offset = 0 + location;
const int y_offset = 0;
location += 300; // for the next time this function is called.
const char *pSize = "45"; // 45 for volume potentiometer
const char *pInitial = "0"; // default volume 0
if(throttle)
{
pSize = "30"; // size for throttle
pInitial = "512"; // default in the middle
}
// Place the slide potentiometer
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-slide-potentiometer\",\n");
Serial.print( " \"id\": \"gpot");
Serial.print( potid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y_offset);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x_offset);
Serial.print( ",\n");
Serial.print( " \"rotate\": ");
Serial.print( "270");
Serial.print( ",\n");
Serial.print( " \"attrs\": { \"value\":\"");
Serial.print( pInitial);
Serial.print( "\", \"travelLength\": \"");
Serial.print( pSize);
Serial.print( "\" }\n");
Serial.print( " },\n");
if(throttle)
{
// A throttle with Neutral Forward and Reverse.
//
// The number of markers is fixed.
// 7 large markers with small markers in between.
for(int i=0; i<13; i++)
{
float x3 = float(x_offset) + 144.0;
float y3 = float(y_offset) + -15.0 + (115.0 * (float(i)/float(12)));
char *pLine = "─"; // UTF-8 character, needs more than a byte
if( i%2 == 1)
pLine = "╴";
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-text\",\n");
Serial.print( " \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y3);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x3);
Serial.print( ",\n");
Serial.print( " \"rotate\": 0,\n");
Serial.print( " \"attrs\": { \"text\": \"");
Serial.print( pLine);
Serial.print( "\"}\n");
Serial.print( " },\n");
}
// Print the symbols, 5 symbols
for(int i=0; i<5; i++)
{
float x4 = float(x_offset) + 160.0;
float y4 = float(y_offset) + -15.0 + (336.0 * (float(i)/float(12)));
// UTF-8 characters are not bytes, use strings
char *symbol[5] = { "△", "▵", "□", "▿", "▽"};
// For some reasing, the smaller arrays are aligned to the left.
// Those need a little correction.
if(i==1 or i==3)
x4 += 2;
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-text\",\n");
Serial.print( " \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y4);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x4);
Serial.print( ",\n");
Serial.print( " \"rotate\": 0,\n");
Serial.print( " \"attrs\": { \"text\": \"");
Serial.print( symbol[i]);
Serial.print( "\"}\n");
Serial.print( " },\n");
}
}
else
{
// Normal slide potentiometer as a volume control.
// With a volume from 0...10
// To allow that all number are printed, the size is now 45.
//
// The number of markers is fixed, from 0...10 with smaller in between.
// They are printed from the top down.
int n10 = 20;
for(int i=0; i<=n10; i++)
{
float x10 = float(x_offset) + 174.0;
float y10 = float(y_offset) + -42.0 + (168.6 * (float(i)/float(n10)));
char *pLine = "─"; // UTF-8 character, needs more than a byte
if( i%2 == 1)
pLine = "╴";
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-text\",\n");
Serial.print( " \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y10);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x10);
Serial.print( ",\n");
Serial.print( " \"rotate\": 0,\n");
Serial.print( " \"attrs\": { \"text\": \"");
Serial.print( pLine);
Serial.print( "\"}\n");
Serial.print( " },\n");
}
// Print the numbers, all of them from 0...10
// It was easier to print them from the top down,
// starting with number 10.
int n11 = 10;
for(int i=0; i<=n11; i++)
{
float x11 = float(x_offset) + 188.0;
float y11 = float(y_offset) + -42.0 + (168.6 * (float(i)/float(n11)));
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-text\",\n");
Serial.print( " \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y11);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x11);
Serial.print( ",\n");
Serial.print( " \"rotate\": 0,\n");
Serial.print( " \"attrs\": { \"text\": \"");
Serial.print( 10-i);
Serial.print( "\"}\n");
Serial.print( " },\n");
}
}
// Wait some time to be sure that everything has been send.
delay(500);
}
void GenerateServo()
{
// Overall offset for everything
const int x_offset = 0 + location;
const int y_offset = 0;
location += 300; // for the next time this function is called.
// The radius for the small lines around the Servo
const int radius_marker = 90;
// The radius for the degrees text
const int radius_number = 111;
// The offset for the center of the horn of the Servo
int x_horn_center = 80;
int y_horn_center = 40;
// Place the Servo
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-servo\",\n");
Serial.print( " \"id\": \"gservo");
Serial.print( servoid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y_offset);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x_offset);
Serial.print( ",\n");
Serial.print( " \"rotate\": ");
Serial.print( "270");
Serial.print( ",\n");
Serial.print( " \"attrs\": { \"hornColor\":\"Navy\" }\n");
Serial.print( " },\n");
// From 0 to 180 degrees.
// Markers at every 10 degrees with small markers in between.
// That are 36 steps.
int steps = 36;
for(int i=0; i<=steps; i++)
{
float angle1 = -90.0 + float(i)*(180.0/float(steps));
float x1 = float(x_offset) + float(x_horn_center) + radius_marker * sin(angle1/180.0*M_PI);
float y1 = float(y_offset) + float(y_horn_center) - radius_marker * cos(angle1/180.0*M_PI);
// Possible UTF-8 characters: ┃ ╹ ╻ │ ╵ ╷
char *pLine = "│"; // UTF-8 character, needs more than a byte
if( i%2 == 1)
pLine = "╷";
if( i%9 == 0)
pLine = "┃";
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-text\",\n");
Serial.print( " \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y1);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x1);
Serial.print( ",\n");
Serial.print( " \"rotate\": ");
Serial.print( angle1);
Serial.print( ",\n");
Serial.print( " \"attrs\": { \"text\": \"");
Serial.print( pLine);
Serial.print( "\"}\n");
Serial.print( " },\n");
}
int numbers = 4;
for(int i=0; i<=numbers; i++)
{
float angle2 = -90.0 + float(i)*(180.0/float(numbers));
float x2 = float(x_offset) + float(x_horn_center) + radius_number * sin(angle2/180.0*M_PI);
float y2 = float(y_offset) + float(y_horn_center) - radius_number * cos(angle2/180.0*M_PI);
if(i>0)
x2 -= 5; // A small correction.
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-text\",\n");
Serial.print( " \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y2);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x2);
Serial.print( ",\n");
Serial.print( " \"rotate\": ");
Serial.print( "0"); // angle2
Serial.print( ",\n");
Serial.print( " \"attrs\": { \"text\": \"");
Serial.print( i*45);
Serial.print( "\"}\n");
Serial.print( " },\n");
}
// Wait some time to be sure that everything has been send.
delay(500);
}
void GenerateLCD(int cols, int rows)
{
// Overall offset for everything
const int x_offset = 0 + location;
const int y_offset = 0;
location += 500; // for the next time this function is called.
char *part_string;
// Only 20x4 and 16x2 are accepted.
if(cols == 20)
{
part_string = "wokwi-lcd2004";
rows = 4;
}
else
{
part_string = "wokwi-lcd1602";
cols = 16;
rows = 2;
}
// Place the slide potentiometer
Serial.print( " {\n");
Serial.print( " \"type\": \"");
Serial.print( part_string);
Serial.print( "\",\n");
Serial.print( " \"id\": \"glcd");
Serial.print( lcdid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y_offset);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x_offset);
Serial.print( ",\n");
Serial.print( " \"attrs\": { \"pins\": \"i2c\" }\n");
Serial.print( " },\n");
// Print columns text.
//
float xstep = 13.4;
for(int i=0; i<cols; i++)
{
float x7 = float(x_offset) + 48.0 + (float(i)* xstep);
float y7 = float(y_offset) + -2.0;
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-text\",\n");
Serial.print( " \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y7);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x7);
Serial.print( ",\n");
Serial.print( " \"rotate\": 0,\n");
Serial.print( " \"attrs\": { \"text\": \"");
Serial.print( i%10);
Serial.print( "\"}\n");
Serial.print( " },\n");
}
// Print rows text.
float xposition = float(cols)*13.24 + 79.1;
float ystep = 22.8;
for(int i=0; i<rows; i++)
{
float x8 = float(x_offset) + xposition;
float y8 = float(y_offset) + 46.0 + (float(i)*ystep);
Serial.print( " {\n");
Serial.print( " \"type\": \"wokwi-text\",\n");
Serial.print( " \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n");
Serial.print( " \"top\": ");
Serial.print( y8);
Serial.print( ",\n");
Serial.print( " \"left\": ");
Serial.print( x8);
Serial.print( ",\n");
Serial.print( " \"rotate\": 0,\n");
Serial.print( " \"attrs\": { \"text\": \"");
Serial.print( i%10);
Serial.print( "\"}\n");
Serial.print( " },\n");
}
// Wait some time to be sure that everything has been send.
delay(500);
}
// This code is good yet.
// Trying to match the different coordination systems needs some tuning.
//
// The rotation for Wokwi rotates around the center of the text.
// It seems that text is vertically and horizontally is aligned
// around the center in the middle.
// To draw a line from point to point, that center has to
// compensated for. The compensation is half the length
// of the line piece (which is a character).
// Wokwi: rows and columns, to the right and downward.
// rotation: clockwise
// x,y : x-axis to the right, y-axis up.
// rotation: anti clockwise, zero is pointing to x-axis.
void GenerateCircle(int radius = 100)
{
// Possible characters:
// ❘ Light Vertical Bar, U+2758, ❘
// ❙ Medium Vertical Bar, U+2759
// ❚ Heavy Vertical Bar, U+275A
// Serial.println("❘ ❙ ❚");
// Overall offset for everything
location += radius + 10; // extra space on the left
const int x_offset = 0 + location;
const int y_offset = 0;
location += radius + 10; // extra space on the right
float x = float(radius);
float y = 0.0;
float length = 16.0; // length of line of line char.
float circumference = 2.0 * M_PI * radius;
// For a closed circle, the number of characters is rounded upward.
// The line pieces might overlap, but that is better than gaps.
int steps = int(ceil(circumference / length));
float angle = 0; // 90 + 360.0/float(steps);
float distance = circumference / (float(steps));
// During development: put a circle in the middle.
// Serial.print( " { \"type\": \"wokwi-text\", \"id\": \"gtxt");
// Serial.print( textid++);
// Serial.print( "\",\n \"top\": ");
// Serial.print( y_offset);
// Serial.print( ", \"left\": 0,\n \"attrs\": { \"text\": \"⚫\" }\n },\n");
for(int i=0; i<steps; i++)
{
angle += 360.0/float(steps);
// The distance for x and y to the next point.
float x_delta = -distance*sin(angle/180*M_PI);
float y_delta = distance*cos(angle/180*M_PI);
char *pLine = "❘";
Serial.print( " { \"type\": \"wokwi-text\", \"id\": \"gtxt");
Serial.print( textid++);
Serial.print( "\",\n \"top\": ");
Serial.print( y_offset -(y+y_delta/2.0));
Serial.print( ", \"left\": ");
Serial.print( x_offset + x+x_delta/2.0);
Serial.print( ", \"rotate\": ");
Serial.print( 360.0 - angle);
Serial.print( ",\n \"attrs\": { \"text\": \"");
Serial.print( pLine);
Serial.print( "\" }\n },\n");
// Update x and y
x += x_delta;
y += y_delta;
}
// Wait some time to be sure that everything has been send.
delay(500);
}
MOTOR SPEED
SELECT NUMBER
TROTTLE
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
☆
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
❘
┃
╷
│
╷
│
╷
│
╷
│
┃
│
╷
│
╷
│
╷
│
╷
┃
╷
│
╷
│
╷
│
╷
│
┃
│
╷
│
╷
│
╷
│
╷
┃
0
45
90
135
180
─
╴
─
╴
─
╴
─
╴
─
╴
─
╴
─
△
▵
□
▿
▽
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
0
1
2
3
4
5
6
7
8
9
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
╷
│
0
1
2
3
4
5
6
7
8
9
10
11
overdrive
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
0
1
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
VOLUME
─
╴
─
╴
─
╴
─
╴
─
╴
─
╴
─
╴
─
╴
─
╴
─
╴
─
10
9
8
7
6
5
4
3
2
1
0
SERVO