Introduce a new demo sketch to visulize the

attenuation and offset constraints of the input
cv.
This commit is contained in:
2025-06-08 11:39:27 -07:00
parent d9106c6951
commit 1a13fbff5f
2 changed files with 201 additions and 15 deletions

View File

@ -4,25 +4,29 @@
* @brief Class for interacting with analog inputs.
* @version 0.1
* @date 2025-05-23
*
*
* @copyright Copyright (c) 2025
*
*
*/
#ifndef ANALOG_INPUT_H
#define ANALOG_INPUT_H
const int MAX_INPUT = (1 << 10) - 1; // Max 10 bit analog read resolution.
// estimated default calibration value
const int CALIBRATED_LOW = -566;
const int CALIBRATED_HIGH = 512;
class AnalogInput {
public:
AnalogInput() {}
~AnalogInput() {}
/**
* Initializes a analog input object.
*
* @param pin gpio pin for the analog input.
*/
* Initializes a analog input object.
*
* @param pin gpio pin for the analog input.
*/
void Init(uint8_t pin) {
pinMode(pin, INPUT);
pin_ = pin;
@ -30,33 +34,43 @@ class AnalogInput {
/**
* Read the value of the analog input and set instance state.
*
*
*/
void Process() {
old_read_ = read_;
int raw = analogRead(pin_);
read_ = map(raw, offset_, MAX_INPUT, low_, high_);
read_ = map(raw, 0, MAX_INPUT, low_, high_);
read_ = constrain(read_ - offset_, -512, 512);
if (inverted_) read_ = -read_;
}
// Set calibration values.
void AdjustCalibrationLow(int amount) { low_ += amount; }
void AdjustCalibrationOffset(int amount) { offset_ -= amount; }
void AdjustCalibrationHigh(int amount) { high_ += amount; }
void SetOffset(float percent) { offset_ = -(percent)*512; }
void SetAttenuation(float percent) {
low_ = abs(percent) * CALIBRATED_LOW;
high_ = abs(percent) * CALIBRATED_HIGH;
inverted_ = percent < 0;
}
/**
* Get the current value of the analog input within a range of +/-512.
*
*
* @return read value within a range of +/-512.
*
*
*/
inline int16_t Read() { return read_; }
/**
* Return the analog read value as voltage.
*
*
* @return A float representing the voltage (-5.0 to +5.0).
*
*
*/
inline float Voltage() { return ((read_ / 512.0) * 5.0); }
@ -66,8 +80,9 @@ class AnalogInput {
uint16_t old_read_;
// calibration values.
int offset_ = 0;
int low_ = -512;
int high_ = 512;
int low_ = CALIBRATED_LOW;
int high_ = CALIBRATED_HIGH;
bool inverted_ = false;
};
#endif

View File

@ -0,0 +1,171 @@
/**
* Analog Input Attenuate and Offset Script
*
* This script provides a demonstration of representing cv input on a 270
* degree arc (similar to the range of a potentiometer) with 12 o'clock
* representing 0v, ~5 o'clock representing 5v and ~7 o'clock representing 0v.
*
* The input voltage can be attenuated/attenuverted and offset. The arc will
* shrink and rotate according to the attenuate/offset values and the input cv
* will be displayed within the configured boundaries.
*
* Note: drawing an arc is expensive and there are a lot of arcs in this
* sketch, so the refresh rate is pretty slow.
*
*/
#include "gravity.h"
#define TEXT_FONT u8g2_font_profont11_tf
byte selected_param = 0;
int offset = 0;
int attenuate = 100;
// Initialize the gravity library and attach your handlers in the setup method.
void setup() {
gravity.Init();
gravity.encoder.AttachRotateHandler(CalibrateCV);
gravity.encoder.AttachPressHandler(NextCalibrationPoint);
}
// The loop method must always call `gravity.Process()` to read any peripherial state changes.
void loop() {
gravity.Process();
UpdateDisplay();
}
void NextCalibrationPoint() {
selected_param = (selected_param + 1) % 2;
}
void CalibrateCV(Direction dir, int val) {
// AnalogInput* cv = (selected_param > 2) ? &gravity.cv2 : &gravity.cv1;
AnalogInput* cv = &gravity.cv1;
switch (selected_param % 2) {
case 0:
attenuate = constrain(attenuate + val, -100, 100);
cv->SetAttenuation(float(attenuate) / 100.0f);
break;
case 1:
offset = constrain(offset + val, -100, 100);
cv->SetOffset(float(offset) / 100.0f);
break;
}
}
void UpdateDisplay() {
gravity.display.firstPage();
do {
// Set default font mode and color for each page draw
gravity.display.setFontMode(0); // Transparent font background
gravity.display.setDrawColor(1); // Draw with color 1 (ON)
gravity.display.setFont(TEXT_FONT);
DisplayCalibrationArc(&gravity.cv1, 0);
} while (gravity.display.nextPage());
}
void DisplayCalibrationArc(AnalogInput* cv, int index) {
gravity.display.setFont(TEXT_FONT);
int text_ascent = gravity.display.getAscent();
int inputValue = cv->Read();
// Print param label, param value, and internal value.
gravity.display.setCursor(0, 64);
switch (selected_param) {
case 0:
gravity.display.print("attenuate: ");
if (attenuate >= 0) gravity.display.print(" ");
gravity.display.print(attenuate);
gravity.display.print("%");
break;
case 1:
gravity.display.print("offset: ");
if (offset >= 0) gravity.display.print(" ");
gravity.display.print(offset);
gravity.display.print("%");
break;
}
gravity.display.setCursor(100, 64);
if (inputValue >= 0) gravity.display.print(" ");
gravity.display.print(inputValue);
// If attenuate is 0, do not draw arc.
if (attenuate == 0) {
return;
}
// Arc drawing parameters.
const int arc_cx = 64;
const int arc_cy = 32;
const int outter_radius = 28;
const int inner_radius = 12;
const int arc_dist = outter_radius - inner_radius; // (28 - 12) = 16
const int arc_north = 64; // Approx (90.0 / 360.0) * 255.0
const int half_arc = 96; // Approx (135.0 / 360.0) * 255.0
const int max_start = 223; // map(360-45, 0, 360, 0, 255); // 315 -> 223
const int max_end = 159; // map(270-45, 0, 360, 0, 255); // 225 -> 159
int start = max_start;
int end = max_end;
// Modify the cv arc frame start/end according to the attenuate/offset values.
if (attenuate != 100) {
float attenuation_factor = abs(float(attenuate) / 100.0f);
int attenuate_amount = round((float)half_arc * (1.0f - attenuation_factor));
start += attenuate_amount;
end -= attenuate_amount;
}
if (offset != 0) {
float offset_factor = float(offset) / 100.0f;
int offset_amount = round((float)(half_arc) * (offset_factor));
// check attenuation if the offset should be flipped.
if (attenuate > 0) {
start = max(start - offset_amount, max_start);
end = min(end - offset_amount, max_end);
} else {
start = max(start + offset_amount, max_start);
end = min(end + offset_amount, max_end);
}
}
// Draw the cv arc frame and end cap lines.
gravity.display.drawArc(arc_cx, arc_cy, outter_radius, start, end);
gravity.display.drawArc(arc_cx, arc_cy, inner_radius, start, end);
// Use drawArc to draw lines connecting the ends of the arc to close the frame.
for (int i = 0; i < arc_dist; i++) {
gravity.display.drawArc(arc_cx, arc_cy, inner_radius + i, start, start + 1);
gravity.display.drawArc(arc_cx, arc_cy, inner_radius + i, end, end + 1);
}
int fill_arc_start;
int fill_arc_end;
if (inputValue >= 0) {
// For positive values (0 to 512), fill clockwise from North
// map inputValue (0 to 512) to angle_offset_units (0 to half_arc)
long mapped_angle_offset = map(inputValue, 0, 512, 0, half_arc);
fill_arc_start = (arc_north - mapped_angle_offset + 256) % 256;
fill_arc_end = arc_north + 1;
} else { // Negative values (-512 to -1)
// For negative values, fill counter-clockwise from North
long mapped_angle_offset = map(abs(inputValue), 0, 512, 0, half_arc); // abs(inputValue) is 1 to 512
fill_arc_start = arc_north - 1;
fill_arc_end = (arc_north + mapped_angle_offset) % 256;
}
// Draw the filled portion of the arc by drawing multiple concentric arcs
// The step for 'i' determines the density of the fill.
// i+=1 for solid fill, i+=4 for a coarser, faster fill.
int fill_step = 4;
// Only draw if there's an actual arc segment to fill (inputValue != 0)
if (inputValue != 0) {
for (int i = fill_step; i < arc_dist - 1; i += fill_step) {
gravity.display.drawArc(arc_cx, arc_cy, inner_radius + i, fill_arc_start, fill_arc_end);
}
}
}