#include <U8glib.h>
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST);
const uint8_t PWM_PIN = 9;
const uint8_t PWM_FEEDBACK_PIN = A0;
const uint8_t JOYSTICK_X_PIN = A1;
const uint16_t CONTROL_UPDATE_INTERVAL_MS = 100;
const uint16_t DISPLAY_UPDATE_INTERVAL_MS = 200;
const uint8_t PWM_SAMPLE_COUNT = 5;
uint8_t pwmControlValue = 127;
uint8_t prevPwmControlValue = 127;
uint8_t measuredPwmValue = 0;
const int JOYSTICK_CENTER = 512;
const int JOYSTICK_DEADZONE = 100;
const uint8_t MAX_PWM_CHANGE_RATE = 5;
const uint8_t GRAPH_X = 5;
const uint8_t GRAPH_Y = 10;
const uint8_t GRAPH_WIDTH = 118;
const uint8_t GRAPH_HEIGHT = 40;
const uint8_t WAVE_PERIOD = 20;
bool isUpdateDue(unsigned long lastUpdate, unsigned long interval)
{
return (millis() - lastUpdate >= interval);
}
void updateLastTime(unsigned long &lastUpdate)
{
lastUpdate = millis();
}
int8_t getJoystickPWMChange()
{
int joystickValue = analogRead(JOYSTICK_X_PIN);
int offset = joystickValue - JOYSTICK_CENTER;
if (abs(offset) < JOYSTICK_DEADZONE)
{
return 0;
}
int8_t changeAmount = map(abs(offset),
JOYSTICK_DEADZONE,
512 - JOYSTICK_DEADZONE,
1,
MAX_PWM_CHANGE_RATE);
return (offset > 0) ? changeAmount : -changeAmount;
}
uint8_t readPWMFeedback()
{
long total = 0;
for (uint8_t i = 0; i < PWM_SAMPLE_COUNT; i++)
{
int reading = analogRead(PWM_FEEDBACK_PIN);
total += reading;
delayMicroseconds(50);
}
int averageReading = total / PWM_SAMPLE_COUNT;
averageReading = constrain(averageReading, 0, 1023);
return map(averageReading, 0, 1023, 0, 255);
}
void outputPWM(uint8_t pin, uint8_t value)
{
analogWrite(pin, value);
}
bool updatePwmSignal()
{
static unsigned long lastControlUpdate = 0;
bool valueChanged = false;
if (isUpdateDue(lastControlUpdate, CONTROL_UPDATE_INTERVAL_MS))
{
prevPwmControlValue = pwmControlValue;
int joystickValue = analogRead(JOYSTICK_X_PIN);
int16_t newValue = map(joystickValue, 0, 1023, 0, 255);
newValue = constrain(newValue, 0, 255);
pwmControlValue = (uint8_t)newValue;
outputPWM(PWM_PIN, pwmControlValue);
valueChanged = (pwmControlValue != prevPwmControlValue);
updateLastTime(lastControlUpdate);
}
return valueChanged;
}
void drawGraphAxes()
{
u8g.drawLine(GRAPH_X, GRAPH_Y, GRAPH_X, GRAPH_Y + GRAPH_HEIGHT);
u8g.drawLine(GRAPH_X, GRAPH_Y + GRAPH_HEIGHT, GRAPH_X + GRAPH_WIDTH, GRAPH_Y + GRAPH_HEIGHT);
}
void drawSquareWave(uint8_t dutyCycle)
{
uint8_t highWidth = map(dutyCycle, 0, 255, 0, WAVE_PERIOD);
uint8_t numPeriods = GRAPH_WIDTH / WAVE_PERIOD;
for (uint8_t i = 0; i < numPeriods; i++)
{
uint8_t startX = GRAPH_X + i * WAVE_PERIOD;
if (highWidth > 0)
{
u8g.drawLine(startX, GRAPH_Y + GRAPH_HEIGHT, startX, GRAPH_Y);
u8g.drawLine(startX, GRAPH_Y, startX + highWidth, GRAPH_Y);
u8g.drawLine(startX + highWidth, GRAPH_Y, startX + highWidth, GRAPH_Y + GRAPH_HEIGHT);
}
u8g.drawLine(startX + highWidth, GRAPH_Y + GRAPH_HEIGHT, startX + WAVE_PERIOD, GRAPH_Y + GRAPH_HEIGHT);
}
}
void drawPwmText()
{
char buf[30];
uint8_t setPercentage = map(pwmControlValue, 0, 255, 0, 100);
uint8_t measuredPercentage = map(measuredPwmValue, 0, 255, 0, 100);
u8g.setFont(u8g_font_6x10);
snprintf(buf, sizeof(buf), "Set: %d", pwmControlValue);
u8g.drawStr(GRAPH_X, GRAPH_Y - 2, buf);
snprintf(buf, sizeof(buf), "(%d%%)", setPercentage);
u8g.drawStr(GRAPH_X + 75, GRAPH_Y - 2, buf);
snprintf(buf, sizeof(buf), "Measure: %d", measuredPwmValue);
u8g.drawStr(GRAPH_X, GRAPH_Y + GRAPH_HEIGHT + 10, buf);
snprintf(buf, sizeof(buf), "(%d%%)", measuredPercentage);
u8g.drawStr(GRAPH_X + 75, GRAPH_Y + GRAPH_HEIGHT + 10, buf);
}
void updateDisplay()
{
u8g.firstPage();
do
{
drawGraphAxes();
drawSquareWave(measuredPwmValue);
drawPwmText();
} while (u8g.nextPage());
}
void setup()
{
pinMode(PWM_PIN, OUTPUT);
pinMode(JOYSTICK_X_PIN, INPUT);
pinMode(PWM_FEEDBACK_PIN, INPUT);
analogReference(DEFAULT);
}
void loop()
{
measuredPwmValue = readPWMFeedback();
bool pwmChanged = updatePwmSignal();
static unsigned long lastDisplayUpdate = 0;
if (pwmChanged || isUpdateDue(lastDisplayUpdate, DISPLAY_UPDATE_INTERVAL_MS))
{
updateDisplay();
updateLastTime(lastDisplayUpdate);
}
}