Compare commits
9 Commits
8bb89a5f4b
...
gravity-r4
| Author | SHA1 | Date | |
|---|---|---|---|
| c6ce5b1309 | |||
| acd028846c | |||
| ed625e75fc | |||
| b60dcc0e68 | |||
| 909d589609 | |||
| 330f5e6ceb | |||
| 87dacd869b | |||
| 64f467d6ac | |||
| 84cafe2387 |
118
examples/test_r4/encoder.h
Normal file
118
examples/test_r4/encoder.h
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* @file encoder.h
|
||||||
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
|
* @brief Class for interacting with encoders.
|
||||||
|
* @version 2.0.0
|
||||||
|
* @date 2025-08-17
|
||||||
|
*
|
||||||
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef ENCODER_DIR_H
|
||||||
|
#define ENCODER_DIR_H
|
||||||
|
|
||||||
|
#include <RotaryEncoder.h>
|
||||||
|
|
||||||
|
#include "button.h"
|
||||||
|
#include "peripherials.h"
|
||||||
|
|
||||||
|
class Encoder {
|
||||||
|
protected:
|
||||||
|
typedef void (*CallbackFunction)(void);
|
||||||
|
typedef void (*RotateCallbackFunction)(int val);
|
||||||
|
CallbackFunction on_press;
|
||||||
|
RotateCallbackFunction on_press_rotate;
|
||||||
|
RotateCallbackFunction on_rotate;
|
||||||
|
int change;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Encoder() : encoder_(ENCODER_PIN1, ENCODER_PIN2, RotaryEncoder::LatchMode::FOUR3),
|
||||||
|
button_(ENCODER_SW_PIN) {
|
||||||
|
_instance = this;
|
||||||
|
}
|
||||||
|
~Encoder() {}
|
||||||
|
|
||||||
|
// Set to true if the encoder read direction should be reversed.
|
||||||
|
void SetReverseDirection(bool reversed) {
|
||||||
|
reversed_ = reversed;
|
||||||
|
}
|
||||||
|
void AttachPressHandler(CallbackFunction f) {
|
||||||
|
on_press = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachRotateHandler(RotateCallbackFunction f) {
|
||||||
|
on_rotate = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachPressRotateHandler(RotateCallbackFunction f) {
|
||||||
|
on_press_rotate = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process() {
|
||||||
|
encoder_.tick();
|
||||||
|
// Get encoder position change amount.
|
||||||
|
int encoder_rotated = _rotate_change() != 0;
|
||||||
|
bool button_pressed = button_.On();
|
||||||
|
button_.Process();
|
||||||
|
|
||||||
|
// Handle encoder position change and button press.
|
||||||
|
if (button_pressed && encoder_rotated) {
|
||||||
|
rotated_while_held_ = true;
|
||||||
|
if (on_press_rotate != NULL) on_press_rotate(change);
|
||||||
|
} else if (!button_pressed && encoder_rotated) {
|
||||||
|
if (on_rotate != NULL) on_rotate(change);
|
||||||
|
} else if (button_.Change() == Button::CHANGE_RELEASED && !rotated_while_held_) {
|
||||||
|
if (on_press != NULL) on_press();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset rotate while held state.
|
||||||
|
if (button_.Change() == Button::CHANGE_RELEASED && rotated_while_held_) {
|
||||||
|
rotated_while_held_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void isr() {
|
||||||
|
// If the instance has been created, call its tick() method.
|
||||||
|
if (_instance) {
|
||||||
|
_instance->encoder_.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Encoder* _instance;
|
||||||
|
|
||||||
|
int previous_pos_;
|
||||||
|
bool rotated_while_held_;
|
||||||
|
bool reversed_ = false;
|
||||||
|
RotaryEncoder encoder_;
|
||||||
|
Button button_;
|
||||||
|
|
||||||
|
// Return the number of ticks change since last polled.
|
||||||
|
int _rotate_change() {
|
||||||
|
int position = encoder_.getPosition();
|
||||||
|
unsigned long ms = encoder_.getMillisBetweenRotations();
|
||||||
|
|
||||||
|
// Validation (TODO: add debounce check).
|
||||||
|
if (previous_pos_ == position) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update state variables.
|
||||||
|
change = position - previous_pos_;
|
||||||
|
previous_pos_ = position;
|
||||||
|
|
||||||
|
// Encoder rotate acceleration.
|
||||||
|
if (ms < 16) {
|
||||||
|
change *= 3;
|
||||||
|
} else if (ms < 32) {
|
||||||
|
change *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reversed_) {
|
||||||
|
change = -(change);
|
||||||
|
}
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
76
examples/test_r4/test_r4.ino
Normal file
76
examples/test_r4/test_r4.ino
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
|
||||||
|
#include "peripherials.h"
|
||||||
|
|
||||||
|
#include "encoder.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include <U8g2lib.h>
|
||||||
|
U8G2_SSD1306_128X64_NONAME_1_HW_I2C display(U8G2_R2, SCL, SDA, U8X8_PIN_NONE);
|
||||||
|
|
||||||
|
|
||||||
|
Encoder encoder;
|
||||||
|
|
||||||
|
const int OUTPUT_COUNT = 6;
|
||||||
|
int outputs[OUTPUT_COUNT] = {
|
||||||
|
OUT_CH1,
|
||||||
|
OUT_CH2,
|
||||||
|
OUT_CH3,
|
||||||
|
OUT_CH4,
|
||||||
|
OUT_CH5,
|
||||||
|
OUT_CH6,
|
||||||
|
};
|
||||||
|
|
||||||
|
volatile int idx = 0;
|
||||||
|
|
||||||
|
// the setup function runs once when you press reset or power the board
|
||||||
|
void setup() {
|
||||||
|
// initialize digital pin LED_BUILTIN as an output.
|
||||||
|
pinMode(LED_BUILTIN, OUTPUT);
|
||||||
|
for (int i = 0; i < OUTPUT_COUNT; i++) {
|
||||||
|
pinMode(outputs[i], OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.AttachRotateHandler(rotateEncoder);
|
||||||
|
encoder.AttachPressHandler(press);
|
||||||
|
|
||||||
|
display.begin();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void rotateEncoder(int val) {
|
||||||
|
idx = (val > 0)
|
||||||
|
? constrain(idx + 1, 0 , OUTPUT_COUNT)
|
||||||
|
: constrain(idx - 1, 0 , OUTPUT_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the loop function runs over and over again forever
|
||||||
|
void loop() {
|
||||||
|
encoder.Process();
|
||||||
|
UpdateDisplay();
|
||||||
|
|
||||||
|
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
|
||||||
|
digitalWrite(outputs[idx], HIGH); // turn the LED on (HIGH is the voltage level)
|
||||||
|
delay(500); // wait for a second
|
||||||
|
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
|
||||||
|
digitalWrite(outputs[idx], LOW); // turn the LED on (LOW is the voltage level)
|
||||||
|
delay(500); // wait for a second
|
||||||
|
}
|
||||||
|
|
||||||
|
void press() {
|
||||||
|
for (int i = 0; i < OUTPUT_COUNT; i++) {
|
||||||
|
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
|
||||||
|
digitalWrite(outputs[i], HIGH); // turn the LED on (HIGH is the voltage level)
|
||||||
|
delay(50); // wait for a second
|
||||||
|
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
|
||||||
|
digitalWrite(outputs[i], LOW); // turn the LED on (LOW is the voltage level)
|
||||||
|
delay(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UpdateDisplay() {
|
||||||
|
display.firstPage();
|
||||||
|
do {
|
||||||
|
display.drawStr(0, 0, "Hello");
|
||||||
|
} while (display.nextPage());
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@
|
|||||||
* @file Gravity.ino
|
* @file Gravity.ino
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @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
|
* @version v1.0 - August 2023 Oleksiy H - Initial release
|
||||||
* @date 2025-07-04
|
* @date 2025-07-04
|
||||||
*
|
*
|
||||||
@ -227,9 +227,17 @@ void HandleEncoderPressed() {
|
|||||||
case PARAM_MAIN_LOAD_DATA:
|
case PARAM_MAIN_LOAD_DATA:
|
||||||
if (app.selected_sub_param < StateManager::MAX_SAVE_SLOTS) {
|
if (app.selected_sub_param < StateManager::MAX_SAVE_SLOTS) {
|
||||||
app.selected_save_slot = app.selected_sub_param;
|
app.selected_save_slot = app.selected_sub_param;
|
||||||
|
// Load pattern data into app state.
|
||||||
stateManager.loadData(app, app.selected_save_slot);
|
stateManager.loadData(app, app.selected_save_slot);
|
||||||
|
// 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);
|
InitGravity(app);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case PARAM_MAIN_FACTORY_RESET:
|
case PARAM_MAIN_FACTORY_RESET:
|
||||||
if (app.selected_sub_param == 0) { // Erase
|
if (app.selected_sub_param == 0) { // Erase
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file app_state.h
|
* @file app_state.h
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file channel.h
|
* @file channel.h
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file display.h
|
* @file display.h
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file euclidean.h
|
* @file euclidean.h
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file save_state.cpp
|
* @file save_state.cpp
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @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) {}
|
StateManager::StateManager() : _isDirty(false), _lastChangeTime(0) {}
|
||||||
|
|
||||||
bool StateManager::initialize(AppState& app) {
|
bool StateManager::initialize(AppState& app) {
|
||||||
|
noInterrupts();
|
||||||
|
bool success = false;
|
||||||
if (_isDataValid()) {
|
if (_isDataValid()) {
|
||||||
// Load global settings.
|
// Load global settings.
|
||||||
_loadMetadata(app);
|
_loadMetadata(app);
|
||||||
// Load app data from the transient slot.
|
// Load app data from the transient slot.
|
||||||
_loadState(app, TRANSIENT_SLOT);
|
_loadState(app, TRANSIENT_SLOT);
|
||||||
return true;
|
success = true;
|
||||||
}
|
}
|
||||||
// EEPROM does not contain save data for this firmware & version.
|
// EEPROM does not contain save data for this firmware & version.
|
||||||
else {
|
else {
|
||||||
// Erase EEPROM and initialize state. Save default pattern to all save slots.
|
// Erase EEPROM and initialize state. Save default pattern to all save slots.
|
||||||
factoryReset(app);
|
factoryReset(app);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
interrupts();
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StateManager::loadData(AppState& app, byte slot_index) {
|
bool StateManager::loadData(AppState& app, byte slot_index) {
|
||||||
// Check if slot_index is within max range + 1 for transient.
|
// Check if slot_index is within max range + 1 for transient.
|
||||||
if (slot_index >= MAX_SAVE_SLOTS + 1) return false;
|
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.
|
// Load the state data from the specified EEPROM slot and update the app state save slot.
|
||||||
_loadState(app, slot_index);
|
_loadState(app, slot_index);
|
||||||
app.selected_save_slot = slot_index;
|
app.selected_save_slot = slot_index;
|
||||||
// Persist this change in the global metadata.
|
// Persist this change in the global metadata on next update.
|
||||||
_saveMetadata(app);
|
_isDirty = true;
|
||||||
|
|
||||||
|
interrupts();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save app state to user specified save slot.
|
// Save app state to user specified save slot.
|
||||||
void StateManager::saveData(const AppState& app) {
|
void StateManager::saveData(const AppState& app) {
|
||||||
|
noInterrupts();
|
||||||
// Check if slot_index is within max range + 1 for transient.
|
// Check if slot_index is within max range + 1 for transient.
|
||||||
if (app.selected_save_slot >= MAX_SAVE_SLOTS + 1) return;
|
if (app.selected_save_slot >= MAX_SAVE_SLOTS + 1) return;
|
||||||
|
|
||||||
_saveState(app, app.selected_save_slot);
|
_saveState(app, app.selected_save_slot);
|
||||||
_saveMetadata(app);
|
_saveMetadata(app);
|
||||||
_isDirty = false;
|
_isDirty = false;
|
||||||
|
interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save transient state if it has changed and enough time has passed since last save.
|
// Save transient state if it has changed and enough time has passed since last save.
|
||||||
void StateManager::update(const AppState& app) {
|
void StateManager::update(const AppState& app) {
|
||||||
if (_isDirty && (millis() - _lastChangeTime > SAVE_DELAY_MS)) {
|
if (_isDirty && (millis() - _lastChangeTime > SAVE_DELAY_MS)) {
|
||||||
|
noInterrupts();
|
||||||
_saveState(app, TRANSIENT_SLOT);
|
_saveState(app, TRANSIENT_SLOT);
|
||||||
_saveMetadata(app);
|
_saveMetadata(app);
|
||||||
_isDirty = false;
|
_isDirty = false;
|
||||||
|
interrupts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::reset(AppState& app) {
|
void StateManager::reset(AppState& app) {
|
||||||
|
noInterrupts();
|
||||||
|
|
||||||
AppState default_app;
|
AppState default_app;
|
||||||
app.tempo = default_app.tempo;
|
app.tempo = default_app.tempo;
|
||||||
app.selected_param = default_app.selected_param;
|
app.selected_param = default_app.selected_param;
|
||||||
@ -98,6 +110,7 @@ void StateManager::reset(AppState& app) {
|
|||||||
_loadMetadata(app);
|
_loadMetadata(app);
|
||||||
|
|
||||||
_isDirty = false;
|
_isDirty = false;
|
||||||
|
interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::markDirty() {
|
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.
|
// Check if slot_index is within max range + 1 for transient.
|
||||||
if (app.selected_save_slot >= MAX_SAVE_SLOTS + 1) return;
|
if (app.selected_save_slot >= MAX_SAVE_SLOTS + 1) return;
|
||||||
|
|
||||||
noInterrupts();
|
|
||||||
static EepromData save_data;
|
static EepromData save_data;
|
||||||
|
|
||||||
save_data.tempo = app.tempo;
|
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));
|
int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData));
|
||||||
EEPROM.put(address, save_data);
|
EEPROM.put(address, save_data);
|
||||||
interrupts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::_loadState(AppState& app, byte slot_index) {
|
void StateManager::_loadState(AppState& app, byte slot_index) {
|
||||||
// Check if slot_index is within max range + 1 for transient.
|
// Check if slot_index is within max range + 1 for transient.
|
||||||
if (slot_index >= MAX_SAVE_SLOTS + 1) return;
|
if (slot_index >= MAX_SAVE_SLOTS + 1) return;
|
||||||
|
|
||||||
noInterrupts();
|
|
||||||
static EepromData load_data;
|
static EepromData load_data;
|
||||||
int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData));
|
int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData));
|
||||||
EEPROM.get(address, load_data);
|
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.setCv1Dest(static_cast<CvDestination>(saved_ch_state.cv1_dest));
|
||||||
ch.setCv2Dest(static_cast<CvDestination>(saved_ch_state.cv2_dest));
|
ch.setCv2Dest(static_cast<CvDestination>(saved_ch_state.cv2_dest));
|
||||||
}
|
}
|
||||||
interrupts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::_saveMetadata(const AppState& app) {
|
void StateManager::_saveMetadata(const AppState& app) {
|
||||||
noInterrupts();
|
|
||||||
Metadata current_meta;
|
Metadata current_meta;
|
||||||
strcpy(current_meta.sketch_name, SKETCH_NAME);
|
strcpy(current_meta.sketch_name, SKETCH_NAME);
|
||||||
strcpy(current_meta.version, SEMANTIC_VERSION);
|
strcpy(current_meta.version, SEMANTIC_VERSION);
|
||||||
@ -215,14 +223,12 @@ void StateManager::_saveMetadata(const AppState& app) {
|
|||||||
current_meta.rotate_display = app.rotate_display;
|
current_meta.rotate_display = app.rotate_display;
|
||||||
|
|
||||||
EEPROM.put(METADATA_START_ADDR, current_meta);
|
EEPROM.put(METADATA_START_ADDR, current_meta);
|
||||||
interrupts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::_loadMetadata(AppState& app) {
|
void StateManager::_loadMetadata(AppState& app) {
|
||||||
noInterrupts();
|
|
||||||
Metadata metadata;
|
Metadata metadata;
|
||||||
EEPROM.get(METADATA_START_ADDR, metadata);
|
EEPROM.get(METADATA_START_ADDR, metadata);
|
||||||
app.selected_save_slot = metadata.selected_save_slot;
|
app.selected_save_slot = metadata.selected_save_slot;
|
||||||
app.encoder_reversed = metadata.encoder_reversed;
|
app.encoder_reversed = metadata.encoder_reversed;
|
||||||
interrupts();
|
app.rotate_display = metadata.rotate_display;
|
||||||
}
|
}
|
||||||
@ -2,8 +2,8 @@
|
|||||||
* @file save_state.h
|
* @file save_state.h
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @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.
|
// This struct holds the data that identifies the firmware version.
|
||||||
struct Metadata {
|
struct Metadata {
|
||||||
char sketch_name[12];
|
char sketch_name[16];
|
||||||
char version[5];
|
char version[16];
|
||||||
// Additional global/hardware settings
|
// Additional global/hardware settings
|
||||||
byte selected_save_slot;
|
byte selected_save_slot;
|
||||||
bool encoder_reversed;
|
bool encoder_reversed;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name=libGravity
|
name=libGravity
|
||||||
version=2.0.0
|
version=2.0.1
|
||||||
author=Adam Wonak
|
author=Adam Wonak
|
||||||
maintainer=awonak <github.com/awonak>
|
maintainer=awonak <github.com/awonak>
|
||||||
sentence=Hardware abstraction library for Sitka Instruments Gravity eurorack module
|
sentence=Hardware abstraction library for Sitka Instruments Gravity eurorack module
|
||||||
@ -7,4 +7,4 @@ category=Other
|
|||||||
license=MIT
|
license=MIT
|
||||||
url=https://github.com/awonak/libGravity
|
url=https://github.com/awonak/libGravity
|
||||||
architectures=avr
|
architectures=avr
|
||||||
depends=uClock,RotaryEncoder,U8g2
|
depends=uClock,RotaryEncoder,U8g2,NeoHWSerial
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file analog_input.h
|
* @file analog_input.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Class for interacting with analog inputs.
|
* @brief Class for interacting with analog inputs.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-05-23
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file button.h
|
* @file button.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Wrapper class for interacting with trigger / gate inputs.
|
* @brief Wrapper class for interacting with trigger / gate inputs.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-20
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
58
src/clock.h
58
src/clock.h
@ -2,8 +2,8 @@
|
|||||||
* @file clock.h
|
* @file clock.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Wrapper Class for clock timing functions.
|
* @brief Wrapper Class for clock timing functions.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-05-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
@ -12,20 +12,12 @@
|
|||||||
#ifndef CLOCK_H
|
#ifndef CLOCK_H
|
||||||
#define CLOCK_H
|
#define CLOCK_H
|
||||||
|
|
||||||
#include <NeoHWSerial.h>
|
|
||||||
|
|
||||||
#include "peripherials.h"
|
#include "peripherials.h"
|
||||||
#include "uClock/uClock.h"
|
#include "uClock/uClock.h"
|
||||||
|
|
||||||
// MIDI clock, start, stop, and continue byte definitions - based on MIDI 1.0 Standards.
|
|
||||||
#define MIDI_CLOCK 0xF8
|
|
||||||
#define MIDI_START 0xFA
|
|
||||||
#define MIDI_STOP 0xFC
|
|
||||||
#define MIDI_CONTINUE 0xFB
|
|
||||||
|
|
||||||
typedef void (*ExtCallback)(void);
|
typedef void (*ExtCallback)(void);
|
||||||
static ExtCallback extUserCallback = nullptr;
|
static ExtCallback extUserCallback = nullptr;
|
||||||
static void serialEventNoop(uint8_t msg, uint8_t status) {}
|
|
||||||
|
|
||||||
class Clock {
|
class Clock {
|
||||||
public:
|
public:
|
||||||
@ -49,19 +41,11 @@ class Clock {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void Init() {
|
void Init() {
|
||||||
NeoSerial.begin(31250);
|
|
||||||
|
|
||||||
// Initialize the clock library
|
// Initialize the clock library
|
||||||
uClock.init();
|
uClock.init();
|
||||||
uClock.setClockMode(uClock.INTERNAL_CLOCK);
|
uClock.setClockMode(uClock.INTERNAL_CLOCK);
|
||||||
uClock.setOutputPPQN(uClock.PPQN_96);
|
uClock.setOutputPPQN(uClock.PPQN_96);
|
||||||
uClock.setTempo(DEFAULT_TEMPO);
|
uClock.setTempo(DEFAULT_TEMPO);
|
||||||
|
|
||||||
// MIDI events.
|
|
||||||
uClock.setOnClockStart(sendMIDIStart);
|
|
||||||
uClock.setOnClockStop(sendMIDIStop);
|
|
||||||
uClock.setOnSync24(sendMIDIClock);
|
|
||||||
|
|
||||||
uClock.start();
|
uClock.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,10 +64,6 @@ class Clock {
|
|||||||
void SetSource(Source source) {
|
void SetSource(Source source) {
|
||||||
bool was_playing = !IsPaused();
|
bool was_playing = !IsPaused();
|
||||||
uClock.stop();
|
uClock.stop();
|
||||||
// If we are changing the source from MIDI, disable the serial interrupt handler.
|
|
||||||
if (source_ == SOURCE_EXTERNAL_MIDI) {
|
|
||||||
NeoSerial.attachInterrupt(serialEventNoop);
|
|
||||||
}
|
|
||||||
source_ = source;
|
source_ = source;
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case SOURCE_INTERNAL:
|
case SOURCE_INTERNAL:
|
||||||
@ -102,9 +82,6 @@ class Clock {
|
|||||||
uClock.setInputPPQN(uClock.PPQN_1);
|
uClock.setInputPPQN(uClock.PPQN_1);
|
||||||
break;
|
break;
|
||||||
case SOURCE_EXTERNAL_MIDI:
|
case SOURCE_EXTERNAL_MIDI:
|
||||||
uClock.setClockMode(uClock.EXTERNAL_CLOCK);
|
|
||||||
uClock.setInputPPQN(uClock.PPQN_24);
|
|
||||||
NeoSerial.attachInterrupt(onSerialEvent);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (was_playing) {
|
if (was_playing) {
|
||||||
@ -160,37 +137,6 @@ class Clock {
|
|||||||
private:
|
private:
|
||||||
Source source_ = SOURCE_INTERNAL;
|
Source source_ = SOURCE_INTERNAL;
|
||||||
|
|
||||||
static void onSerialEvent(uint8_t msg, uint8_t status) {
|
|
||||||
// Note: uClock start and stop will echo to MIDI.
|
|
||||||
switch (msg) {
|
|
||||||
case MIDI_CLOCK:
|
|
||||||
if (extUserCallback) {
|
|
||||||
extUserCallback();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MIDI_STOP:
|
|
||||||
uClock.stop();
|
|
||||||
sendMIDIStop();
|
|
||||||
break;
|
|
||||||
case MIDI_START:
|
|
||||||
case MIDI_CONTINUE:
|
|
||||||
uClock.start();
|
|
||||||
sendMIDIStart();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sendMIDIStart() {
|
|
||||||
NeoSerial.write(MIDI_START);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sendMIDIStop() {
|
|
||||||
NeoSerial.write(MIDI_STOP);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sendMIDIClock(uint32_t tick) {
|
|
||||||
NeoSerial.write(MIDI_CLOCK);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
197
src/clock_midi.h
Normal file
197
src/clock_midi.h
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
/**
|
||||||
|
* @file clock.h
|
||||||
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
|
* @brief Wrapper Class for clock timing functions.
|
||||||
|
* @version 2.0.0
|
||||||
|
* @date 2025-08-17
|
||||||
|
*
|
||||||
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CLOCK_H
|
||||||
|
#define CLOCK_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <NeoHWSerial.h>
|
||||||
|
|
||||||
|
#include "peripherials.h"
|
||||||
|
#include "uClock/uClock.h"
|
||||||
|
|
||||||
|
// MIDI clock, start, stop, and continue byte definitions - based on MIDI 1.0 Standards.
|
||||||
|
#define MIDI_CLOCK 0xF8
|
||||||
|
#define MIDI_START 0xFA
|
||||||
|
#define MIDI_STOP 0xFC
|
||||||
|
#define MIDI_CONTINUE 0xFB
|
||||||
|
|
||||||
|
typedef void (*ExtCallback)(void);
|
||||||
|
static ExtCallback extUserCallback = nullptr;
|
||||||
|
static void serialEventNoop(uint8_t msg, uint8_t status) {}
|
||||||
|
|
||||||
|
class Clock {
|
||||||
|
public:
|
||||||
|
static constexpr int DEFAULT_TEMPO = 120;
|
||||||
|
|
||||||
|
enum Source {
|
||||||
|
SOURCE_INTERNAL,
|
||||||
|
SOURCE_EXTERNAL_PPQN_24,
|
||||||
|
SOURCE_EXTERNAL_PPQN_4,
|
||||||
|
SOURCE_EXTERNAL_PPQN_1,
|
||||||
|
SOURCE_EXTERNAL_MIDI,
|
||||||
|
SOURCE_LAST,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Pulse {
|
||||||
|
PULSE_NONE,
|
||||||
|
PULSE_PPQN_24,
|
||||||
|
PULSE_PPQN_4,
|
||||||
|
PULSE_PPQN_1,
|
||||||
|
PULSE_LAST,
|
||||||
|
};
|
||||||
|
|
||||||
|
void Init() {
|
||||||
|
NeoSerial.begin(31250);
|
||||||
|
|
||||||
|
// Initialize the clock library
|
||||||
|
uClock.init();
|
||||||
|
uClock.setClockMode(uClock.INTERNAL_CLOCK);
|
||||||
|
uClock.setOutputPPQN(uClock.PPQN_96);
|
||||||
|
uClock.setTempo(DEFAULT_TEMPO);
|
||||||
|
|
||||||
|
// MIDI events.
|
||||||
|
uClock.setOnClockStart(sendMIDIStart);
|
||||||
|
uClock.setOnClockStop(sendMIDIStop);
|
||||||
|
uClock.setOnSync24(sendMIDIClock);
|
||||||
|
|
||||||
|
uClock.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle external clock tick and call user callback when receiving clock trigger (PPQN_4, PPQN_24, or MIDI).
|
||||||
|
void AttachExtHandler(void (*callback)()) {
|
||||||
|
extUserCallback = callback;
|
||||||
|
attachInterrupt(digitalPinToInterrupt(EXT_PIN), callback, RISING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal PPQN96 callback for all clock timer operations.
|
||||||
|
void AttachIntHandler(void (*callback)(uint32_t)) {
|
||||||
|
uClock.setOnOutputPPQN(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the source of the clock mode.
|
||||||
|
void SetSource(Source source) {
|
||||||
|
bool was_playing = !IsPaused();
|
||||||
|
uClock.stop();
|
||||||
|
// If we are changing the source from MIDI, disable the serial interrupt handler.
|
||||||
|
if (source_ == SOURCE_EXTERNAL_MIDI) {
|
||||||
|
NeoSerial.attachInterrupt(serialEventNoop);
|
||||||
|
}
|
||||||
|
source_ = source;
|
||||||
|
switch (source) {
|
||||||
|
case SOURCE_INTERNAL:
|
||||||
|
uClock.setClockMode(uClock.INTERNAL_CLOCK);
|
||||||
|
break;
|
||||||
|
case SOURCE_EXTERNAL_PPQN_24:
|
||||||
|
uClock.setClockMode(uClock.EXTERNAL_CLOCK);
|
||||||
|
uClock.setInputPPQN(uClock.PPQN_24);
|
||||||
|
break;
|
||||||
|
case SOURCE_EXTERNAL_PPQN_4:
|
||||||
|
uClock.setClockMode(uClock.EXTERNAL_CLOCK);
|
||||||
|
uClock.setInputPPQN(uClock.PPQN_4);
|
||||||
|
break;
|
||||||
|
case SOURCE_EXTERNAL_PPQN_1:
|
||||||
|
uClock.setClockMode(uClock.EXTERNAL_CLOCK);
|
||||||
|
uClock.setInputPPQN(uClock.PPQN_1);
|
||||||
|
break;
|
||||||
|
case SOURCE_EXTERNAL_MIDI:
|
||||||
|
uClock.setClockMode(uClock.EXTERNAL_CLOCK);
|
||||||
|
uClock.setInputPPQN(uClock.PPQN_24);
|
||||||
|
NeoSerial.attachInterrupt(onSerialEvent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (was_playing) {
|
||||||
|
uClock.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if the current selected source is externl (PPQN_4, PPQN_24, or MIDI).
|
||||||
|
bool ExternalSource() {
|
||||||
|
return uClock.getClockMode() == uClock.EXTERNAL_CLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if the current selected source is the internal master clock.
|
||||||
|
bool InternalSource() {
|
||||||
|
return uClock.getClockMode() == uClock.INTERNAL_CLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the current BPM tempo.
|
||||||
|
int Tempo() {
|
||||||
|
return uClock.getTempo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the clock tempo to a int between 1 and 400.
|
||||||
|
void SetTempo(int tempo) {
|
||||||
|
return uClock.setTempo(tempo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record an external clock tick received to process external/internal syncronization.
|
||||||
|
void Tick() {
|
||||||
|
uClock.clockMe();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the internal clock.
|
||||||
|
void Start() {
|
||||||
|
uClock.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop internal clock clock.
|
||||||
|
void Stop() {
|
||||||
|
uClock.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset all clock counters to 0.
|
||||||
|
void Reset() {
|
||||||
|
uClock.resetCounters();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the clock is not running.
|
||||||
|
bool IsPaused() {
|
||||||
|
return uClock.clock_state == uClock.PAUSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Source source_ = SOURCE_INTERNAL;
|
||||||
|
|
||||||
|
static void onSerialEvent(uint8_t msg, uint8_t status) {
|
||||||
|
// Note: uClock start and stop will echo to MIDI.
|
||||||
|
switch (msg) {
|
||||||
|
case MIDI_CLOCK:
|
||||||
|
if (extUserCallback) {
|
||||||
|
extUserCallback();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MIDI_STOP:
|
||||||
|
uClock.stop();
|
||||||
|
sendMIDIStop();
|
||||||
|
break;
|
||||||
|
case MIDI_START:
|
||||||
|
case MIDI_CONTINUE:
|
||||||
|
uClock.start();
|
||||||
|
sendMIDIStart();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sendMIDIStart() {
|
||||||
|
NeoSerial.write(MIDI_START);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sendMIDIStop() {
|
||||||
|
NeoSerial.write(MIDI_STOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sendMIDIClock(uint32_t tick) {
|
||||||
|
NeoSerial.write(MIDI_CLOCK);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -2,8 +2,8 @@
|
|||||||
* @file digital_output.h
|
* @file digital_output.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Class for interacting with trigger / gate outputs.
|
* @brief Class for interacting with trigger / gate outputs.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-17
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* @file encoder_dir.h
|
* @file encoder.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Class for interacting with encoders.
|
* @brief Class for interacting with encoders.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-19
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file libGravity.cpp
|
* @file libGravity.cpp
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Library for building custom scripts for the Sitka Instruments Gravity module.
|
* @brief Library for building custom scripts for the Sitka Instruments Gravity module.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-19
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
@ -33,6 +33,7 @@ void Gravity::initInputs() {
|
|||||||
cv1.Init(CV1_PIN);
|
cv1.Init(CV1_PIN);
|
||||||
cv2.Init(CV2_PIN);
|
cv2.Init(CV2_PIN);
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_AVR)
|
||||||
// Pin Change Interrupts for Encoder.
|
// Pin Change Interrupts for Encoder.
|
||||||
// Thanks to https://dronebotworkshop.com/interrupts/
|
// Thanks to https://dronebotworkshop.com/interrupts/
|
||||||
|
|
||||||
@ -42,6 +43,14 @@ void Gravity::initInputs() {
|
|||||||
PCMSK2 |= B00010000;
|
PCMSK2 |= B00010000;
|
||||||
// Select PCINT11 Bit3 (Pin D17/A3)
|
// Select PCINT11 Bit3 (Pin D17/A3)
|
||||||
PCMSK1 |= B00001000;
|
PCMSK1 |= B00001000;
|
||||||
|
#endif
|
||||||
|
#if defined(ARDUINO_NANO_R4)
|
||||||
|
pinMode(ENCODER_PIN1, INPUT_PULLDOWN);
|
||||||
|
pinMode(ENCODER_PIN2, INPUT_PULLDOWN);
|
||||||
|
attachInterrupt(digitalPinToInterrupt(ENCODER_PIN1), Encoder::isr, CHANGE);
|
||||||
|
attachInterrupt(digitalPinToInterrupt(ENCODER_PIN2), Encoder::isr, CHANGE);
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gravity::initOutputs() {
|
void Gravity::initOutputs() {
|
||||||
@ -74,6 +83,7 @@ void Gravity::Process() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_AVR)
|
||||||
// Pin Change Interrupt on Port D (D4).
|
// Pin Change Interrupt on Port D (D4).
|
||||||
ISR(PCINT2_vect) {
|
ISR(PCINT2_vect) {
|
||||||
Encoder::isr();
|
Encoder::isr();
|
||||||
@ -82,6 +92,8 @@ ISR(PCINT2_vect) {
|
|||||||
ISR(PCINT1_vect) {
|
ISR(PCINT1_vect) {
|
||||||
Encoder::isr();
|
Encoder::isr();
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// Global instance
|
// Global instance
|
||||||
Gravity gravity;
|
Gravity gravity;
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file libGravity.h
|
* @file libGravity.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Library for building custom scripts for the Sitka Instruments Gravity module.
|
* @brief Library for building custom scripts for the Sitka Instruments Gravity module.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-19
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file peripherials.h
|
* @file peripherials.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Arduino pin definitions for the Sitka Instruments Gravity module.
|
* @brief Arduino pin definitions for the Sitka Instruments Gravity module.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-19
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
@ -24,8 +24,8 @@
|
|||||||
|
|
||||||
// Clock and CV Inputs
|
// Clock and CV Inputs
|
||||||
#define EXT_PIN 2
|
#define EXT_PIN 2
|
||||||
#define CV1_PIN A7
|
#define CV1_PIN 21 // A7
|
||||||
#define CV2_PIN A6
|
#define CV2_PIN 20 // A6
|
||||||
#define PULSE_OUT_PIN 3
|
#define PULSE_OUT_PIN 3
|
||||||
|
|
||||||
// Button pins
|
// Button pins
|
||||||
|
|||||||
72
src/uClock/platforms/renesas.h
Normal file
72
src/uClock/platforms/renesas.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file nano_r4.h
|
||||||
|
* @author Gemini (Based on the uClock AVR implementation)
|
||||||
|
* @brief uClock platform support for the Arduino Nano R4 (Renesas RA4M1).
|
||||||
|
*
|
||||||
|
* This file implements the timer initialization and control functions
|
||||||
|
* required by uClock using the FspTimer library, which provides a high-level
|
||||||
|
* interface to the General PWM Timers (GPT) on the Renesas RA4M1
|
||||||
|
* microcontroller. This approach replaces the direct register manipulation
|
||||||
|
* used for AVR platforms.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <FspTimer.h>
|
||||||
|
|
||||||
|
// ATOMIC macro for defining critical sections where interrupts are disabled.
|
||||||
|
#define ATOMIC(X) noInterrupts(); X; interrupts();
|
||||||
|
|
||||||
|
// Forward declaration of the uClock's main handler function. This function
|
||||||
|
// must be defined in the main uClock library code and will be called by the timer interrupt.
|
||||||
|
void uClockHandler();
|
||||||
|
|
||||||
|
// Create an FspTimer instance for uClock.
|
||||||
|
// We use GPT channel 6, as it is less likely to conflict with the default
|
||||||
|
// analogWrite() (PWM) functionality on the Nano R4's pins.
|
||||||
|
FspTimer uClockTimer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the hardware timer for uClock.
|
||||||
|
*
|
||||||
|
* This function configures and starts a hardware timer (GPT6) to fire
|
||||||
|
* periodically. It attaches the uClockHandler as the interrupt service routine.
|
||||||
|
* The initial tempo is set to a default of 120 BPM (48 Hz tick rate).
|
||||||
|
*
|
||||||
|
* @param init_clock This parameter is unused on this platform but is kept
|
||||||
|
* for API compatibility with other uClock platforms.
|
||||||
|
*/
|
||||||
|
void initTimer(uint32_t init_clock)
|
||||||
|
{
|
||||||
|
ATOMIC(
|
||||||
|
// Configure the timer to be a periodic interrupt source.
|
||||||
|
// The frequency/period arguments here are placeholders, as the actual
|
||||||
|
// period is set precisely with the setPeriod() call below.
|
||||||
|
uClockTimer.begin(TIMER_MODE_PERIODIC, GPT_TIMER, 6, 1.0f, STANDARD_PWM_FREQ_HZ);
|
||||||
|
|
||||||
|
// Set the timer's period to the provided BPM period in microseconds.
|
||||||
|
uClockTimer.set_period(init_clock);
|
||||||
|
|
||||||
|
// Start the timer to begin generating ticks.
|
||||||
|
uClockTimer.start();
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the timer's interval in microseconds.
|
||||||
|
*
|
||||||
|
* This function dynamically updates the timer's period to match the specified
|
||||||
|
* interval, which effectively changes the clock's tempo. The FspTimer library
|
||||||
|
* automatically handles the complex low-level prescaler and counter adjustments.
|
||||||
|
*
|
||||||
|
* @param us_interval The desired interval between clock ticks in microseconds.
|
||||||
|
*/
|
||||||
|
void setTimer(uint32_t us_interval)
|
||||||
|
{
|
||||||
|
// Atomically update the timer's period. The FspTimer library abstracts
|
||||||
|
// away the manual prescaler math required on AVR platforms.
|
||||||
|
ATOMIC(
|
||||||
|
uClockTimer.set_period(us_interval);
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -32,7 +32,13 @@
|
|||||||
* DEALINGS IN THE SOFTWARE.
|
* DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "uClock.h"
|
#include "uClock.h"
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_AVR)
|
||||||
#include "platforms/avr.h"
|
#include "platforms/avr.h"
|
||||||
|
#endif
|
||||||
|
#if defined(ARDUINO_NANO_R4)
|
||||||
|
#include "platforms/renesas.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
//
|
//
|
||||||
// Platform specific timer setup/control
|
// Platform specific timer setup/control
|
||||||
|
|||||||
Reference in New Issue
Block a user