Introduce a new demo sketch to visulize the
attenuation and offset constraints of the input cv.
This commit is contained in:
@ -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
|
||||
|
||||
171
examples/calibrate_analog2/calibrate_analog2.ino
Normal file
171
examples/calibrate_analog2/calibrate_analog2.ino
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user