diff --git a/analog_input.h b/analog_input.h index fb44018..7838653 100644 --- a/analog_input.h +++ b/analog_input.h @@ -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 diff --git a/examples/calibrate_analog2/calibrate_analog2.ino b/examples/calibrate_analog2/calibrate_analog2.ino new file mode 100644 index 0000000..ac5d772 --- /dev/null +++ b/examples/calibrate_analog2/calibrate_analog2.ino @@ -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); + } + } +}