15 Commits

21 changed files with 263 additions and 104 deletions

View File

@ -1,6 +1,18 @@
# Sitka Instruments Gravity Firmware Abstraction
This library helps make writing firmware easier by abstracting away the initialization and peripheral interactions. Now your firmware code can just focus on the logic and behavior of the app, and keep the low level code neatly tucked away in this library.
This library helps make writing firmware for the [Sitka Instruments Gravity](https://sitkainstruments.com/gravity/) eurorack module easier by abstracting away the initialization and peripheral interactions. Now your firmware code can just focus on the logic and behavior of the app, and keep the low level code neatly tucked away in this library.
The latest releases of all Sitka Instruments Gravity firmware builds can be found on the [Updater](https://sitkainstruments.com/gravity/updater/) page. You can use this page to flash the latest build directly to the Arduino Nano on the back of your module.
## Project Code Layout
* [`src/`](src/) - **libGravity**: This is the hardware abstraction library used to simplify the creation of new Gravity module firmware by providing common reusable wrappers around the module peripherials like [DigitalOutput](src/digital_output.h#L18) providing methods like [`Update(uint8_t state)`](src/digital_output.h#L45) which allow you to set that output channel voltage high or low, and common module behavior like [Clock](src/clock.h#L30) which provides handlers like [`AttachExtHandler(callback)`](src/clock.h#L69) which takes a callback function to handle external clock tick behavior when receiving clock trigger.
* [`firmware/Gravity`](firmware/Gravity/) - **Alt Gravity**: This is the implementation of the default 6-channel trigger/gate clock modulation firmware. This is a full rewrite of the original firmware designed to use `libGravity` with a focus on open source friendlines.
* `firmware/GridSeq` - **GridSeq**: Comming Soon.
* [`examples/skeleton`](examples/skeleton/skeleton.ino) - **Skeleton**: This is the bare bones scaffloding for a `libGravity` firmware app.
## Installation
@ -17,13 +29,14 @@ Common directory locations:
* [uClock](https://github.com/midilab/uClock) [MIT] - (Included with this repo) Handle clock tempo, external clock input, and internal clock timer handler.
* [RotateEncoder](https://github.com/mathertel/RotaryEncoder) [BSD] - Library for reading and interpreting encoder rotation.
* [U8g2](https://github.com/olikraus/u8g2/) [MIT] - Graphics helper library.
* [NeoHWSerial](https://github.com/SlashDevin/NeoHWSerial) [GPL] - Hardware serial library with attachInterrupt.
## Example
Here's a trivial example showing some of the ways to interact with the library. This script rotates the active clock channel according to the set tempo. The encoder can change the temo or rotation direction. The play/pause button will toggle the clock activity on or off. The shift button will freeze the clock from advancing the channel rotation.
```cpp
#include "gravity.h"
#include "libGravity.h"
byte idx = 0;
bool reversed = false;
@ -75,11 +88,11 @@ void HandlePlayPressed() {
}
}
void HandleRotate(Direction dir, int val) {
void HandleRotate(int val) {
if (selected_param == 0) {
gravity.clock.SetTempo(gravity.clock.Tempo() + val);
} else if (selected_param == 1) {
reversed = (dir == DIRECTION_DECREMENT);
reversed = (val < 0);
}
}
@ -111,8 +124,16 @@ void UpdateDisplay() {
}
```
**Building New Firmware Using libGravity**
When starting a new firmware sketch you can use the [skeleton](examples/skeleton/skeleton.ino) app as a place to start.
**Building New Firmware from scratch**
If you do not want to use the libGravity hardware abstraction library and want to roll your own vanilla firmware, take a look at the [peripherials.h](src/peripherials.h) file for the pinout definitions used by the module.
### Build for release
```
$ arduino-cli compile -v -b arduino:avr:nano ./firmware/Gravity/Gravity.ino -e --output-dir=./build/
```
```

View File

@ -17,7 +17,7 @@
* TODO: Store the calibration value in EEPROM.
*/
#include "gravity.h"
#include "libGravity.h"
#define TEXT_FONT u8g2_font_profont11_tf
#define INDICATOR_FONT u8g2_font_open_iconic_arrow_1x_t
@ -43,7 +43,7 @@ void NextCalibrationPoint() {
selected_param = (selected_param + 1) % 6;
}
void CalibrateCV(Direction dir, int val) {
void CalibrateCV(int val) {
AnalogInput* cv = (selected_param > 2) ? &gravity.cv2 : &gravity.cv1;
switch (selected_param % 3) {
case 0:

View File

@ -14,7 +14,7 @@
*
*/
#include "gravity.h"
#include "libGravity.h"
#define TEXT_FONT u8g2_font_profont11_tf
@ -39,7 +39,7 @@ void NextCalibrationPoint() {
selected_param = (selected_param + 1) % 2;
}
void CalibrateCV(Direction dir, int val) {
void CalibrateCV(int val) {
// AnalogInput* cv = (selected_param > 2) ? &gravity.cv2 : &gravity.cv1;
AnalogInput* cv = &gravity.cv1;
switch (selected_param % 2) {

View File

@ -1,4 +1,4 @@
#include "gravity.h"
#include "libGravity.h"
byte idx = 0;
bool reversed = false;
@ -33,28 +33,28 @@ void IntClock(uint32_t tick) {
if (tick % 12 == 0 && ! freeze) {
gravity.outputs[idx].Low();
if (reversed) {
idx = (idx == 0) ? OUTPUT_COUNT - 1 : idx - 1;
idx = (idx == 0) ? Gravity::OUTPUT_COUNT - 1 : idx - 1;
} else {
idx = (idx + 1) % OUTPUT_COUNT;
idx = (idx + 1) % Gravity::OUTPUT_COUNT;
}
gravity.outputs[idx].High();
}
}
void HandlePlayPressed() {
gravity.clock.Pause();
gravity.clock.Stop();
if (gravity.clock.IsPaused()) {
for (int i = 0; i < OUTPUT_COUNT; i++) {
for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) {
gravity.outputs[i].Low();
}
}
}
void HandleRotate(Direction dir, int val) {
void HandleRotate(int val) {
if (selected_param == 0) {
gravity.clock.SetTempo(gravity.clock.Tempo() + val);
} else if (selected_param == 1) {
reversed = (dir == DIRECTION_DECREMENT);
reversed = (val < 0);
}
}
@ -80,7 +80,7 @@ void UpdateDisplay() {
gravity.display.print("Direction: ");
gravity.display.print((reversed) ? "Backward" : "Forward");
gravity.display.drawChar(0, selected_param * 10, 0x10, 1, 0, 1);
gravity.display.drawStr(0, selected_param * 10, "x");
gravity.display.display();
}

View File

@ -0,0 +1,118 @@
/**
* @file skeleton.ino
* @author YOUR_NAME (<url>)
* @brief Skeleton app for Sitka Instruments Gravity.
* @version vX.Y.Z - MONTH YEAR YOUR_NAME
* @date YYYY-MM-DD
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*
* Skeleton app for basic structure of a new firmware for Sitka Instruments
* Gravity using the libGravity library.
*
* ENCODER:
* Press: change between selecting a parameter and editing the parameter.
* Hold & Rotate: change current selected output channel.
*
* BTN1:
* Play/pause - start or stop the internal clock.
*
* BTN2:
* Shift - hold and rotate encoder to change current selected output channel.
*
* EXT:
* External clock input. When Gravity is set to INTERNAL or MIDI clock
* source, this input is used to reset clocks.
*
* CV1:
* External analog input used to provide modulation to any channel parameter.
*
* CV2:
* External analog input used to provide modulation to any channel parameter.
*
*/
#include <libGravity.h>
// Global state for settings and app behavior.
struct AppState {
int tempo = Clock::DEFAULT_TEMPO;
Clock::Source selected_source = Clock::SOURCE_INTERNAL;
// Add app specific state variables here.
};
AppState app;
//
// Arduino setup and loop.
//
void setup() {
// Start Gravity.
gravity.Init();
// Clock handlers.
gravity.clock.AttachIntHandler(HandleIntClockTick);
gravity.clock.AttachExtHandler(HandleExtClockTick);
// Encoder rotate and press handlers.
gravity.encoder.AttachPressHandler(HandleEncoderPressed);
gravity.encoder.AttachRotateHandler(HandleRotate);
gravity.encoder.AttachPressRotateHandler(HandlePressedRotate);
// Button press handlers.
gravity.play_button.AttachPressHandler(HandlePlayPressed);
}
void loop() {
// Process change in state of inputs and outputs.
gravity.Process();
// Non-ISR loop behavior.
}
//
// Firmware handlers for clocks.
//
void HandleIntClockTick(uint32_t tick) {
bool refresh = false;
for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) {
// Process each output tick handlers.
}
}
void HandleExtClockTick() {
switch (app.selected_source) {
case Clock::SOURCE_INTERNAL:
case Clock::SOURCE_EXTERNAL_MIDI:
// Use EXT as Reset when not used for clock source.
gravity.clock.Reset();
break;
default:
// Register EXT cv clock tick.
gravity.clock.Tick();
}
}
//
// UI handlers for encoder and buttons.
//
void HandlePlayPressed() {
}
void HandleEncoderPressed() {
}
void HandleRotate(int val) {
}
void HandlePressedRotate(int val) {
}
//
// Application logic goes here.
//

View File

@ -2,7 +2,7 @@
* @file Gravity.ino
* @author Adam Wonak (https://github.com/awonak/)
* @brief Alt firmware version of Gravity by Sitka Instruments.
* @version v2.0.0 - June 2025 awonak - Full rewrite
* @version v2.0.0 - August 2025 awonak - Full rewrite
* @version v1.0 - August 2023 Oleksiy H - Initial release
* @date 2025-07-04
*
@ -89,6 +89,13 @@ void loop() {
// Check if cv run or reset is active and read cv.
CheckRunReset(gravity.cv1, gravity.cv2);
// Process clock pulses.
for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) {
if (app.channel[i].isCvModActive()) {
app.channel[i].recalculatePulses();
}
}
// Check for dirty state eligible to be saved.
stateManager.update(app);
@ -227,8 +234,16 @@ void HandleEncoderPressed() {
case PARAM_MAIN_LOAD_DATA:
if (app.selected_sub_param < StateManager::MAX_SAVE_SLOTS) {
app.selected_save_slot = app.selected_sub_param;
// Load pattern data into app state.
stateManager.loadData(app, app.selected_save_slot);
InitGravity(app);
// Load global performance settings if they have changed.
if (gravity.clock.Tempo() != app.tempo) {
gravity.clock.SetTempo(app.tempo);
}
// Load global settings only clock is not active.
if (gravity.clock.IsPaused()) {
InitGravity(app);
}
}
break;
case PARAM_MAIN_FACTORY_RESET:
@ -273,6 +288,7 @@ void HandleRotate(int val) {
void HandlePressedRotate(int val) {
updateSelection(app.selected_channel, val, Gravity::OUTPUT_COUNT + 1);
app.selected_param = 0;
app.editing_param = false;
stateManager.markDirty();
app.refresh_screen = true;
}

View File

@ -2,8 +2,8 @@
* @file app_state.h
* @author Adam Wonak (https://github.com/awonak/)
* @brief Alt firmware version of Gravity by Sitka Instruments.
* @version 2.0.1
* @date 2025-07-04
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*

View File

@ -2,8 +2,8 @@
* @file channel.h
* @author Adam Wonak (https://github.com/awonak/)
* @brief Alt firmware version of Gravity by Sitka Instruments.
* @version 2.0.1
* @date 2025-07-04
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*
@ -77,16 +77,16 @@ class Channel {
pattern.Init(DEFAULT_PATTERN);
// Calcule the clock mod pulses on init.
_recalculatePulses();
recalculatePulses();
}
bool isCvModActive() const { return cv1_dest != CV_DEST_NONE || cv2_dest != CV_DEST_NONE; }
bool inline isCvModActive() const { return cv1_dest != CV_DEST_NONE || cv2_dest != CV_DEST_NONE; }
// Setters (Set the BASE value)
void setClockMod(int index) {
base_clock_mod_index = constrain(index, 0, MOD_CHOICE_SIZE - 1);
_recalculatePulses();
recalculatePulses();
}
void setProbability(int prob) {
@ -95,16 +95,17 @@ class Channel {
void setDutyCycle(int duty) {
base_duty_cycle = constrain(duty, 1, 99);
_recalculatePulses();
recalculatePulses();
}
void setOffset(int off) {
base_offset = constrain(off, 0, 99);
_recalculatePulses();
recalculatePulses();
}
void setSwing(int val) {
base_swing = constrain(val, 50, 95);
_recalculatePulses();
recalculatePulses();
}
// Euclidean
@ -117,11 +118,11 @@ class Channel {
void setCv1Dest(CvDestination dest) {
cv1_dest = dest;
_recalculatePulses();
recalculatePulses();
}
void setCv2Dest(CvDestination dest) {
cv2_dest = dest;
_recalculatePulses();
recalculatePulses();
}
CvDestination getCv1Dest() const { return cv1_dest; }
CvDestination getCv2Dest() const { return cv2_dest; }
@ -194,26 +195,22 @@ class Channel {
return;
}
if (isCvModActive()) _recalculatePulses();
int cv1 = gravity.cv1.Read();
int cv2 = gravity.cv2.Read();
int cvmod_clock_mod_index = getClockModIndexWithMod(cv1, cv2);
int cvmod_probability = getProbabilityWithMod(cv1, cv2);
const uint16_t mod_pulses = pgm_read_word_near(&CLOCK_MOD_PULSES[cvmod_clock_mod_index]);
int cvmod_probability = base_probability;
if (cv1_dest == CV_DEST_PROB || cv2_dest == CV_DEST_PROB) {
cvmod_probability = getProbabilityWithMod(gravity.cv1.Read(), gravity.cv2.Read());
}
// Conditionally apply swing on down beats.
uint16_t swing_pulses = 0;
if (_swing_pulse_amount > 0 && (tick / mod_pulses) % 2 == 1) {
swing_pulses = _swing_pulse_amount;
if (_swing_pulses > 0 && (tick / _mod_pulses) % 2 == 1) {
swing_pulses = _swing_pulses;
}
// Duty cycle high check logic
const uint32_t current_tick_offset = tick + _offset_pulses + swing_pulses;
if (!output.On()) {
// Step check
if (current_tick_offset % mod_pulses == 0) {
if (current_tick_offset % _mod_pulses == 0) {
bool hit = cvmod_probability >= random(0, 100);
// Euclidean rhythm hit check
switch (pattern.NextStep()) {
@ -232,11 +229,31 @@ class Channel {
// Duty cycle low check
const uint32_t duty_cycle_end_tick = tick + _duty_pulses + _offset_pulses + swing_pulses;
if (duty_cycle_end_tick % mod_pulses == 0) {
if (duty_cycle_end_tick % _mod_pulses == 0) {
output.Low();
}
}
void recalculatePulses() {
int cv1 = gravity.cv1.Read();
int cv2 = gravity.cv2.Read();
int clock_mod_index = getClockModIndexWithMod(cv1, cv2);
int duty_cycle = getDutyCycleWithMod(cv1, cv2);
int offset = getOffsetWithMod(cv1, cv2);
int swing = getSwingWithMod(cv1, cv2);
_mod_pulses = pgm_read_word_near(&CLOCK_MOD_PULSES[clock_mod_index]);
_duty_pulses = max((long)((_mod_pulses * (100L - duty_cycle)) / 100L), 1L);
_offset_pulses = (long)((_mod_pulses * (100L - offset)) / 100L);
// Calculate the down beat swing amount.
if (swing > 50) {
int shifted_swing = swing - 50;
_swing_pulses = (long)((_mod_pulses * (100L - shifted_swing)) / 100L);
} else {
_swing_pulses = 0;
}
}
private:
int _calculateMod(CvDestination dest, int cv1_val, int cv2_val, int min_range, int max_range) {
int mod1 = (cv1_dest == dest) ? map(cv1_val, -512, 512, min_range, max_range) : 0;
@ -244,26 +261,6 @@ class Channel {
return mod1 + mod2;
}
void _recalculatePulses() {
int cv1 = gravity.cv1.Read();
int cv2 = gravity.cv2.Read();
int clock_mod_index = getClockModIndexWithMod(cv1, cv2);
int duty_cycle = getDutyCycleWithMod(cv1, cv2);
int offset = getOffsetWithMod(cv1, cv2);
int swing = getSwingWithMod(cv1, cv2);
const uint16_t mod_pulses = pgm_read_word_near(&CLOCK_MOD_PULSES[clock_mod_index]);
_duty_pulses = max((long)((mod_pulses * (100L - duty_cycle)) / 100L), 1L);
_offset_pulses = (long)((mod_pulses * (100L - offset)) / 100L);
// Calculate the down beat swing amount.
if (swing > 50) {
int shifted_swing = swing - 50;
_swing_pulse_amount = (long)((mod_pulses * (100L - shifted_swing)) / 100L);
} else {
_swing_pulse_amount = 0;
}
}
// User-settable base values.
byte base_clock_mod_index;
byte base_probability;
@ -282,9 +279,10 @@ class Channel {
bool mute;
// Pre-calculated pulse values for ISR performance
uint16_t _mod_pulses;
uint16_t _duty_pulses;
uint16_t _offset_pulses;
uint16_t _swing_pulse_amount;
uint16_t _swing_pulses;
};
#endif // CHANNEL_H

View File

@ -2,8 +2,8 @@
* @file display.h
* @author Adam Wonak (https://github.com/awonak/)
* @brief Alt firmware version of Gravity by Sitka Instruments.
* @version 2.0.1
* @date 2025-07-04
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*

View File

@ -2,8 +2,8 @@
* @file euclidean.h
* @author Adam Wonak (https://github.com/awonak/)
* @brief Alt firmware version of Gravity by Sitka Instruments.
* @version 2.0.1
* @date 2025-07-04
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*

View File

@ -2,8 +2,8 @@
* @file save_state.cpp
* @author Adam Wonak (https://github.com/awonak/)
* @brief Alt firmware version of Gravity by Sitka Instruments.
* @version 2.0.1
* @date 2025-07-04
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*
@ -33,54 +33,66 @@ const int StateManager::EEPROM_DATA_START_ADDR = sizeof(StateManager::Metadata);
StateManager::StateManager() : _isDirty(false), _lastChangeTime(0) {}
bool StateManager::initialize(AppState& app) {
noInterrupts();
bool success = false;
if (_isDataValid()) {
// Load global settings.
_loadMetadata(app);
// Load app data from the transient slot.
_loadState(app, TRANSIENT_SLOT);
return true;
success = true;
}
// EEPROM does not contain save data for this firmware & version.
else {
// Erase EEPROM and initialize state. Save default pattern to all save slots.
factoryReset(app);
return false;
}
interrupts();
return success;
}
bool StateManager::loadData(AppState& app, byte slot_index) {
// Check if slot_index is within max range + 1 for transient.
if (slot_index >= MAX_SAVE_SLOTS + 1) return false;
noInterrupts();
// Load the state data from the specified EEPROM slot and update the app state save slot.
_loadState(app, slot_index);
app.selected_save_slot = slot_index;
// Persist this change in the global metadata.
_saveMetadata(app);
// Persist this change in the global metadata on next update.
_isDirty = true;
interrupts();
return true;
}
// Save app state to user specified save slot.
void StateManager::saveData(const AppState& app) {
noInterrupts();
// Check if slot_index is within max range + 1 for transient.
if (app.selected_save_slot >= MAX_SAVE_SLOTS + 1) return;
_saveState(app, app.selected_save_slot);
_saveMetadata(app);
_isDirty = false;
interrupts();
}
// Save transient state if it has changed and enough time has passed since last save.
void StateManager::update(const AppState& app) {
if (_isDirty && (millis() - _lastChangeTime > SAVE_DELAY_MS)) {
noInterrupts();
_saveState(app, TRANSIENT_SLOT);
_saveMetadata(app);
_isDirty = false;
interrupts();
}
}
void StateManager::reset(AppState& app) {
noInterrupts();
AppState default_app;
app.tempo = default_app.tempo;
app.selected_param = default_app.selected_param;
@ -98,6 +110,7 @@ void StateManager::reset(AppState& app) {
_loadMetadata(app);
_isDirty = false;
interrupts();
}
void StateManager::markDirty() {
@ -134,7 +147,6 @@ void StateManager::_saveState(const AppState& app, byte slot_index) {
// Check if slot_index is within max range + 1 for transient.
if (app.selected_save_slot >= MAX_SAVE_SLOTS + 1) return;
noInterrupts();
static EepromData save_data;
save_data.tempo = app.tempo;
@ -165,14 +177,12 @@ void StateManager::_saveState(const AppState& app, byte slot_index) {
int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData));
EEPROM.put(address, save_data);
interrupts();
}
void StateManager::_loadState(AppState& app, byte slot_index) {
// Check if slot_index is within max range + 1 for transient.
if (slot_index >= MAX_SAVE_SLOTS + 1) return;
noInterrupts();
static EepromData load_data;
int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData));
EEPROM.get(address, load_data);
@ -200,11 +210,9 @@ void StateManager::_loadState(AppState& app, byte slot_index) {
ch.setCv1Dest(static_cast<CvDestination>(saved_ch_state.cv1_dest));
ch.setCv2Dest(static_cast<CvDestination>(saved_ch_state.cv2_dest));
}
interrupts();
}
void StateManager::_saveMetadata(const AppState& app) {
noInterrupts();
Metadata current_meta;
strcpy(current_meta.sketch_name, SKETCH_NAME);
strcpy(current_meta.version, SEMANTIC_VERSION);
@ -215,14 +223,12 @@ void StateManager::_saveMetadata(const AppState& app) {
current_meta.rotate_display = app.rotate_display;
EEPROM.put(METADATA_START_ADDR, current_meta);
interrupts();
}
void StateManager::_loadMetadata(AppState& app) {
noInterrupts();
Metadata metadata;
EEPROM.get(METADATA_START_ADDR, metadata);
app.selected_save_slot = metadata.selected_save_slot;
app.encoder_reversed = metadata.encoder_reversed;
interrupts();
app.rotate_display = metadata.rotate_display;
}

View File

@ -2,8 +2,8 @@
* @file save_state.h
* @author Adam Wonak (https://github.com/awonak/)
* @brief Alt firmware version of Gravity by Sitka Instruments.
* @version 2.0.1
* @date 2025-07-04
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*
@ -52,8 +52,8 @@ class StateManager {
// This struct holds the data that identifies the firmware version.
struct Metadata {
char sketch_name[12];
char version[5];
char sketch_name[16];
char version[16];
// Additional global/hardware settings
byte selected_save_slot;
bool encoder_reversed;

View File

@ -1,5 +1,5 @@
name=libGravity
version=2.0.0
version=2.0.1
author=Adam Wonak
maintainer=awonak <github.com/awonak>
sentence=Hardware abstraction library for Sitka Instruments Gravity eurorack module
@ -7,4 +7,4 @@ category=Other
license=MIT
url=https://github.com/awonak/libGravity
architectures=avr
depends=uClock,RotaryEncoder,U8g2
depends=uClock,RotaryEncoder,U8g2,NeoHWSerial

View File

@ -2,8 +2,8 @@
* @file analog_input.h
* @author Adam Wonak (https://github.com/awonak)
* @brief Class for interacting with analog inputs.
* @version 0.1
* @date 2025-05-23
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*

View File

@ -2,8 +2,8 @@
* @file button.h
* @author Adam Wonak (https://github.com/awonak)
* @brief Wrapper class for interacting with trigger / gate inputs.
* @version 0.1
* @date 2025-04-20
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*

View File

@ -2,8 +2,8 @@
* @file clock.h
* @author Adam Wonak (https://github.com/awonak)
* @brief Wrapper Class for clock timing functions.
* @version 0.1
* @date 2025-05-04
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*

View File

@ -2,8 +2,8 @@
* @file digital_output.h
* @author Adam Wonak (https://github.com/awonak)
* @brief Class for interacting with trigger / gate outputs.
* @version 0.1
* @date 2025-04-17
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*

View File

@ -2,8 +2,8 @@
* @file encoder_dir.h
* @author Adam Wonak (https://github.com/awonak)
* @brief Class for interacting with encoders.
* @version 0.1
* @date 2025-04-19
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*

View File

@ -2,8 +2,8 @@
* @file libGravity.cpp
* @author Adam Wonak (https://github.com/awonak)
* @brief Library for building custom scripts for the Sitka Instruments Gravity module.
* @version 0.1
* @date 2025-04-19
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*

View File

@ -2,8 +2,8 @@
* @file libGravity.h
* @author Adam Wonak (https://github.com/awonak)
* @brief Library for building custom scripts for the Sitka Instruments Gravity module.
* @version 0.1
* @date 2025-04-19
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*

View File

@ -2,8 +2,8 @@
* @file peripherials.h
* @author Adam Wonak (https://github.com/awonak)
* @brief Arduino pin definitions for the Sitka Instruments Gravity module.
* @version 0.1
* @date 2025-04-19
* @version 2.0.0
* @date 2025-08-17
*
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
*