Software Update 4: Sequencer Block

Sequencer Klasse eingebaut
  Überprüfung noch ausständig
This commit is contained in:
Erik Tóth
2025-11-13 17:03:04 +01:00
parent 3ad41e6ba5
commit b3d66fdfd8
12 changed files with 641 additions and 234 deletions

View File

@@ -1,5 +0,0 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View File

@@ -1,10 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

View File

@@ -1,37 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -1,16 +0,0 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32-s3-devkitm-1]
platform = espressif32
board = esp32-s3-devkitm-1
framework = arduino
build_flags = -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_JTAG_ON_BOOT=1
lib_deps = adafruit/Adafruit MCP4728@^1.0.10

View File

@@ -1,82 +0,0 @@
#include <Wire.h>
#include <Adafruit_MCP4728.h>
#define BAUDRATE 115200
#define I2C_SDA 15
#define I2C_SCL 16
#define I2C_FREQ 400000
#define DAC_MAX 4095
#define DAC_MIN 0
#define VREF_mV 2048
#define VREF_CONF_IN_USE MCP4728_VREF_INTERNAL
#define PIN_BTN 40
Adafruit_MCP4728 mcp;
static uint16_t value_mV = VREF_mV;
bool button_state_old = false;
bool button_state_now = false;
uint16_t voltage(uint16_t mV)
{
return map(mV, 0, VREF_mV, DAC_MIN, DAC_MAX);
}
void setup()
{
Serial.begin(BAUDRATE);
delay(1000);
Serial.print("\n\rMCP4728 DAC Test");
Wire.begin(I2C_SDA, I2C_SCL);
Wire.setClock(I2C_FREQ);
pinMode(PIN_BTN, INPUT);
Serial.print("\n\rInitialisiere MCP4728...");
uint8_t attempts = 0;
while (!mcp.begin())
{
if(attempts > 20)
{
Serial.print("\n\rError: Es konnte nach 20 Versuchen kein IC gefunden werden. Überprüfe die Verkabelung und starte den ESP neu!");
for(;;);
}
Serial.print(".");
delay(100);
attempts++;
}
Serial.print("\n\rErfolgreich verbunden!");
mcp.setChannelValue(MCP4728_CHANNEL_A, 4095, VREF_CONF_IN_USE);
Serial.print("\n\rDAC-Kanal A auf 0 gesetzt");
delay(1000);
}
void loop()
{
button_state_now = digitalRead(PIN_BTN);
if (button_state_now != button_state_old)
{
delay(10); // debounce delay
button_state_now = digitalRead(PIN_BTN);
if (button_state_now != button_state_old)
{
button_state_old = button_state_now;
if (button_state_now == HIGH)
{
if(value_mV < 2000) value_mV += (1000/12);
if(value_mV >= 2000) value_mV = 0;
if (mcp.setChannelValue(MCP4728_CHANNEL_A, voltage(value_mV), VREF_CONF_IN_USE)) Serial.printf("DAC A = %u -> mV: %u\r\n", voltage(value_mV), value_mV);
else Serial.println("Error: Neuer MCP4728 Wert konnte nicht gesetzt werden!");
}
}
}
}

View File

@@ -1,11 +0,0 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html

View File

@@ -18,6 +18,7 @@
#define MS_DEBOUNCE 20
#define N_MAX_DAC_CH 4
#define N_MAX_SEQUENCE_STEPS 128
struct Key
{
@@ -25,6 +26,12 @@ struct Key
int col;
};
struct voltageDurationPair
{
uint16_t voltage;
uint16_t duration;
};
const Key NOT_A_KEY = {-1, -1};
bool isNotKey(Key k);
@@ -83,4 +90,58 @@ class CV
uint8_t _getKeyToVoltageIndex(Key k);
};
class SequencerBlock
{
public:
SequencerBlock(uint16_t maxDurationMS, uint16_t timeoutMS);
// Aufnahme-Funktionen
void startRecord();
void stopRecord();
void addStep(uint16_t voltage);
bool isRecording();
// Wiedergabe-Funktionen
void startPlay();
void stopPlay();
void update(); // Muss regelmäßig aufgerufen werden
bool isPlaying();
// Sequenz-Verwaltung
void clear();
void setLoop(bool loop);
// Status-Abfragen
bool timeLimitReached();
uint8_t getStepCount();
uint16_t getCurrentVoltage();
uint16_t getTotalDuration();
private:
// Sequenz-Speicher
voltageDurationPair _sequence[N_MAX_SEQUENCE_STEPS];
uint8_t _stepCount;
uint8_t _currentStep;
// Zeitverwaltung
uint16_t _maxDurationMS;
uint16_t _timeoutMS;
unsigned long _recordStartTime;
unsigned long _lastStepTime;
unsigned long _playStartTime;
unsigned long _stepStartTime;
// Status-Flags
bool _isRecording;
bool _isPlaying;
bool _loop;
// Letzte aufgenommene Spannung
uint16_t _lastVoltage;
// Hilfsfunktionen
void _finishCurrentStep();
bool _canAddStep();
};
#endif

View File

@@ -14,6 +14,7 @@
#define N_KEYBOARD_ROW 4
#define N_KEYBOARD_COL 3
#define N_CV_GATES 2
#define N_SB 2
#define BAUDRATE 115200
// PIN DEFENTITIONS
// I2C PINS
@@ -24,7 +25,15 @@
#define PIN_K_R1 8
#define PIN_K_R2 9
#define PIN_K_R3 10
#define PIN_K_R4 // NOT IN USE
#define PIN_K_C0 1
#define PIN_K_C1 2
#define PIN_K_C2 4
#define PIN_K_C3 // NOT IN USE
#define PIN_K_C4 // NOT IN USE
// SEQUENCER BUTTON PINS
#define PIN_SB_1_REC 0
#define PIN_SB_1_PLAY 0
#define PIN_SB_2_REC 0
#define PIN_SB_2_PLAY 0
#endif

View File

@@ -8,6 +8,8 @@
#include "FIRMWARE.h"
// ==================== Helper-Functions ====================
bool isNotKey(Key k)
{
if((k.row == NOT_A_KEY.row) && (k.col == NOT_A_KEY.col)) return true;
@@ -20,6 +22,8 @@ bool isEqualKey(Key k1, Key k2)
else return false;
}
// ==================== Keyboard ====================
Keyboard::Keyboard(uint8_t nRows, uint8_t nCols, uint8_t *pinsRow, uint8_t *pinsCol)
{
_nRows = nRows;
@@ -171,6 +175,8 @@ void Keyboard::_removeActiveKey(uint8_t row, uint8_t col)
}
}
// ==================== CV ====================
/*!
* @param dac Adafruit_MCP4728 object
* @param wire TwoWire object
@@ -233,3 +239,263 @@ uint8_t CV::_getKeyToVoltageIndex(Key k)
{
return (k.row*_col + k.col);
}
// ==================== SequencerBlock ====================
/*!
* @param maxDurationMS maximum loop duration of recording in milliseconds
* @param timeoutMS stops recording after timeout in milliseconds
* @brief TODO
*/
SequencerBlock::SequencerBlock(uint16_t maxDurationMS, uint16_t timeoutMS)
{
_maxDurationMS = maxDurationMS;
_timeoutMS = timeoutMS;
_stepCount = 0;
_currentStep = 0;
_isRecording = false;
_isPlaying = false;
_loop = false;
_lastVoltage = 0;
_recordStartTime = 0;
_lastStepTime = 0;
_playStartTime = 0;
_stepStartTime = 0;
}
/*!
* @brief starts sequence block recording
*/
void SequencerBlock::startRecord()
{
if(_isPlaying) stopPlay();
clear();
_isRecording = true;
_recordStartTime = millis();
_lastStepTime = _recordStartTime;
_lastVoltage = 0;
}
/*!
* @brief stops sequence block recording and saves it
*/
void SequencerBlock::stopRecord()
{
if(!_isRecording) return;
_finishCurrentStep();
_isRecording = false;
}
/*!
* @brief adds step to sequencer block
* @param voltage voltage step for CV-Gate in millivolts
*/
void SequencerBlock::addStep(uint16_t voltage)
{
if(!_isRecording) return;
if(!_canAddStep()) return;
unsigned long now = millis();
// Wenn sich die Spannung geändert hat, vorherigen Schritt abschließen
if(voltage != _lastVoltage && _stepCount > 0)
{
_finishCurrentStep();
}
// Neuen Schritt beginnen oder vorhandenen aktualisieren
if(voltage != _lastVoltage || _stepCount == 0)
{
if(_canAddStep())
{
_sequence[_stepCount].voltage = voltage;
_sequence[_stepCount].duration = 0;
_lastStepTime = now;
_lastVoltage = voltage;
}
}
else
{
if(_stepCount > 0)
{
_sequence[_stepCount - 1].duration = now - _lastStepTime;
}
}
}
/*!
* @brief checks if sequencer block is recording
* @return true or false
*/
bool SequencerBlock::isRecording()
{
return _isRecording;
}
/*!
* @brief starts playing sequencer block
*/
void SequencerBlock::startPlay()
{
if(_stepCount == 0) return;
if(_isRecording) stopRecord();
_isPlaying = true;
_currentStep = 0;
_playStartTime = millis();
_stepStartTime = _playStartTime;
}
/*!
* @brief stops playing sequencer block
*/
void SequencerBlock::stopPlay()
{
_isPlaying = false;
_currentStep = 0;
}
/*!
* @brief updates sequencer block
* @attention Has to be called every cycle!
*/
void SequencerBlock::update()
{
if(!_isPlaying || _stepCount == 0) return;
unsigned long now = millis();
unsigned long elapsed = now - _stepStartTime;
// Prüfen ob aktueller Schritt abgelaufen ist
if(elapsed >= _sequence[_currentStep].duration)
{
_currentStep++;
// Sequenz-Ende erreicht?
if(_currentStep >= _stepCount)
{
if(_loop)
{
_currentStep = 0;
_stepStartTime = now;
}
else
{
stopPlay();
return;
}
}
else
{
_stepStartTime = now;
}
}
}
/*!
* @brief checks if sequencer block is playing
* @return true or false
*/
bool SequencerBlock::isPlaying()
{
return _isPlaying;
}
/*!
* @brief clears recording of sequencer block
*/
void SequencerBlock::clear()
{
_stepCount = 0;
_currentStep = 0;
_lastVoltage = 0;
for(uint8_t i = 0; i < N_MAX_SEQUENCE_STEPS; i++)
{
_sequence[i].voltage = 0;
_sequence[i].duration = 0;
}
}
/*!
* @brief sets configuation for looping over the recording
* @param loop if set to true, saved recording gets played in a loop
*/
void SequencerBlock::setLoop(bool loop)
{
_loop = loop;
}
/*!
* @brief checks if the recording time limit has been reached
* @return true of false
*/
bool SequencerBlock::timeLimitReached()
{
if(!_isRecording) return false;
unsigned long now = millis();
unsigned long elapsed = now - _recordStartTime;
return (elapsed >= _maxDurationMS);
}
/*!
* @brief returns the currently recoreded steps
* @return uint8_t between 0 and 128
*/
uint8_t SequencerBlock::getStepCount()
{
return _stepCount;
}
/*!
* @brief if sequencer is playing, returns the current voltage level
* @return uint16_t voltage range for CV
*/
uint16_t SequencerBlock::getCurrentVoltage()
{
if(!_isPlaying || _stepCount == 0) return 0;
if(_currentStep >= _stepCount) return 0;
return _sequence[_currentStep].voltage;
}
/*!
* @brief gets the length of the recording in the block
* @return uint16_t time in milliseconds
*/
uint16_t SequencerBlock::getTotalDuration()
{
uint16_t total = 0;
for(uint8_t i = 0; i < _stepCount; i++)
{
total += _sequence[i].duration;
}
return total;
}
void SequencerBlock::_finishCurrentStep()
{
if(_stepCount == 0) return;
unsigned long now = millis();
_sequence[_stepCount - 1].duration = now - _lastStepTime;
// Timeout prüfen - wenn zu lange keine Änderung, Schritt nicht hinzufügen
if(_sequence[_stepCount - 1].duration < _timeoutMS)
{
_stepCount++;
}
}
bool SequencerBlock::_canAddStep()
{
if(_stepCount >= N_MAX_SEQUENCE_STEPS) return false;
if(timeLimitReached()) return false;
return true;
}

View File

@@ -1,5 +1,13 @@
/*
* Example Code Two
* Example Code Three
* with Sequencer
*
* Bedienung:
* - Keyboard-Tasten: CV-Ausgabe direkt oder Recording
* - PIN_SB_1_REC: Sequencer 1 Record Start/Stop
* - PIN_SB_1_PLAY: Sequencer 1 Play/Stop
* - PIN_SB_2_REC: Sequencer 2 Record Start/Stop
* - PIN_SB_2_PLAY: Sequencer 2 Play/Stop
*/
#include "FIRMWARE_DEF.h"
#include "FIRMWARE.h"
@@ -11,7 +19,7 @@ Keyboard keyboard(N_KEYBOARD_ROW, N_KEYBOARD_COL, pins_keyboard_row, pins_keyboa
Adafruit_MCP4728 MCP4728;
MCP4728_channel_t cvMap[N_CV_GATES] = {MCP4728_CHANNEL_A, MCP4728_CHANNEL_B};
uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = {
uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = { /* 83mV = 1/12V */
1*83, 5*83, 9*83,
2*83, 6*83, 10*83,
3*83, 7*83, 11*83,
@@ -20,41 +28,251 @@ uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = {
CV cv(&MCP4728, &Wire, N_CV_GATES, cvMap, keyToVoltage, N_KEYBOARD_ROW, N_KEYBOARD_COL);
SequencerBlock sb1(30000, 250); // 30 Sekunden max, 250ms Timeout
SequencerBlock sb2(30000, 250);
// Button States
struct ButtonState {
bool current;
bool last;
unsigned long lastDebounceTime;
};
ButtonState btn_sb1_rec;
ButtonState btn_sb1_play;
ButtonState btn_sb2_rec;
ButtonState btn_sb2_play;
const unsigned long DEBOUNCE_DELAY = 50;
// Hilfsfunktion zum Lesen eines Buttons mit Debouncing
bool readButton(byte pin, ButtonState &state)
{
bool reading = digitalRead(pin) == LOW; // LOW = gedrückt (mit Pull-Up)
bool buttonPressed = false;
if(reading != state.last)
{
state.lastDebounceTime = millis();
}
if((millis() - state.lastDebounceTime) > DEBOUNCE_DELAY)
{
if(reading != state.current)
{
state.current = reading;
if(state.current == true) // Button wurde gerade gedrückt
{
buttonPressed = true;
}
}
}
state.last = reading;
return buttonPressed;
}
void initButtons()
{
pinMode(PIN_SB_1_REC, INPUT_PULLUP);
pinMode(PIN_SB_1_PLAY, INPUT_PULLUP);
pinMode(PIN_SB_2_REC, INPUT_PULLUP);
pinMode(PIN_SB_2_PLAY, INPUT_PULLUP);
btn_sb1_rec.current = false;
btn_sb1_rec.last = false;
btn_sb1_rec.lastDebounceTime = 0;
btn_sb1_play.current = false;
btn_sb1_play.last = false;
btn_sb1_play.lastDebounceTime = 0;
btn_sb2_rec.current = false;
btn_sb2_rec.last = false;
btn_sb2_rec.lastDebounceTime = 0;
btn_sb2_play.current = false;
btn_sb2_play.last = false;
btn_sb2_play.lastDebounceTime = 0;
}
void handleSequencerButtons()
{
// Sequencer 1 Record Button
if(readButton(PIN_SB_1_REC, btn_sb1_rec))
{
if(sb1.isRecording())
{
sb1.stopRecord();
Serial.printf("\n\r[SEQ1] Recording stopped. Steps: %i, Duration: %ims",
sb1.getStepCount(), sb1.getTotalDuration());
}
else
{
if(sb1.isPlaying()) sb1.stopPlay();
sb1.startRecord();
Serial.printf("\n\r[SEQ1] Recording started...");
}
}
// Sequencer 1 Play Button
if(readButton(PIN_SB_1_PLAY, btn_sb1_play))
{
if(sb1.isPlaying())
{
sb1.stopPlay();
Serial.printf("\n\r[SEQ1] Playback stopped");
}
else
{
if(sb1.isRecording()) sb1.stopRecord();
sb1.startPlay();
Serial.printf("\n\r[SEQ1] Playback started");
}
}
// Sequencer 2 Record Button
if(readButton(PIN_SB_2_REC, btn_sb2_rec))
{
if(sb2.isRecording())
{
sb2.stopRecord();
Serial.printf("\n\r[SEQ2] Recording stopped. Steps: %i, Duration: %ims",
sb2.getStepCount(), sb2.getTotalDuration());
}
else
{
if(sb2.isPlaying()) sb2.stopPlay();
sb2.startRecord();
Serial.printf("\n\r[SEQ2] Recording started...");
}
}
// Sequencer 2 Play Button
if(readButton(PIN_SB_2_PLAY, btn_sb2_play))
{
if(sb2.isPlaying())
{
sb2.stopPlay();
Serial.printf("\n\r[SEQ2] Playback stopped");
}
else
{
if(sb2.isRecording()) sb2.stopRecord();
sb2.startPlay();
Serial.printf("\n\r[SEQ2] Playback started");
}
}
}
void setup()
{
Serial.begin(BAUDRATE);
keyboard.begin();
cv.begin(PIN_SDA, PIN_SCL);
Serial.begin(BAUDRATE);
keyboard.begin();
cv.begin(PIN_SDA, PIN_SCL);
initButtons();
sb1.setLoop(false);
sb2.setLoop(false);
Serial.printf("\n\r=== Sequencer System Started ===");
Serial.printf("\n\rControls:");
Serial.printf("\n\r PIN_SB_1_REC: SEQ1 Record Start/Stop");
Serial.printf("\n\r PIN_SB_1_PLAY: SEQ1 Play/Stop");
Serial.printf("\n\r PIN_SB_2_REC: SEQ2 Record Start/Stop");
Serial.printf("\n\r PIN_SB_2_PLAY: SEQ2 Play/Stop");
Serial.printf("\n\r================================\n\r");
}
void loop()
{
keyboard.update();
keyboard.update();
handleSequencerButtons();
int n = keyboard.getQueueLength();
// Sequencer Update (für Wiedergabe)
sb1.update();
sb2.update();
if(n > 0)
{
Serial.printf("\n\rCurrent queue length: %i", n);
if(n == 1)
int n = keyboard.getQueueLength();
// Keyboard-Tasten verarbeiten
if(n > 0)
{
cv.setVoltage(0, keyboard.getQueue(0));
cv.setVoltage(1, NOT_A_KEY);
// Alle Keyboard-Tasten für CV-Ausgabe verwenden
int cvIndex = 0;
for(int i = 0; i < n && cvIndex < N_CV_GATES; i++)
{
Key k = keyboard.getQueue(i);
if(!isNotKey(k))
{
uint16_t voltage = keyToVoltage[k.row * N_KEYBOARD_COL + k.col];
// Bei Recording: Spannung aufnehmen
if(sb1.isRecording())
{
sb1.addStep(voltage);
}
if(sb2.isRecording())
{
sb2.addStep(voltage);
}
// Live-Ausgabe nur wenn nicht gerade wiedergegeben wird
if(!sb1.isPlaying() && !sb2.isPlaying())
{
cv.setVoltage(cvIndex++, k);
}
}
}
// Restliche CV-Ausgänge auf 0 setzen wenn live gespielt wird
if(!sb1.isPlaying() && !sb2.isPlaying())
{
for(int i = cvIndex; i < N_CV_GATES; i++)
{
cv.setVoltage(i, 0);
}
}
}
else if(n >= 2)
else
{
cv.setVoltage(0, keyboard.getQueue(0));
cv.setVoltage(1, keyboard.getQueue(1));
// Keine Tasten gedrückt
if(sb1.isRecording())
{
sb1.addStep(0);
}
if(sb2.isRecording())
{
sb2.addStep(0);
}
if(!sb1.isPlaying() && !sb2.isPlaying())
{
cv.clearAll();
}
}
for(int i = 0; (i < N_CV_GATES) && (i < n); i++)
// Sequencer-Wiedergabe auf CV-Ausgänge
if(sb1.isPlaying())
{
Key k = keyboard.getQueue(i);
if(isNotKey(k)) Serial.printf("\n\rQueue position %i: NOT A KEY", i);
else Serial.printf("\n\rQueue position %i: R%iC%i", i, k.row, k.col);
cv.setVoltage(0, sb1.getCurrentVoltage());
}
if(sb2.isPlaying())
{
cv.setVoltage(1, sb2.getCurrentVoltage());
}
}
else cv.clearAll();
delay(50);
// Time-Limit Warnung
if(sb1.isRecording() && sb1.timeLimitReached())
{
sb1.stopRecord();
Serial.printf("\n\r[SEQ1] Time limit reached! Recording stopped.");
}
if(sb2.isRecording() && sb2.timeLimitReached())
{
sb2.stopRecord();
Serial.printf("\n\r[SEQ2] Time limit reached! Recording stopped.");
}
delay(10); // Kürzeres Delay für bessere Sequencer-Auflösung
}

View File

@@ -0,0 +1,60 @@
/*
* Example Code Two
*/
#include "FIRMWARE_DEF.h"
#include "FIRMWARE.h"
static byte pins_keyboard_row[N_KEYBOARD_ROW] = {PIN_K_R0, PIN_K_R1, PIN_K_R2, PIN_K_R3};
static byte pins_keyboard_col[N_KEYBOARD_COL] = {PIN_K_C0, PIN_K_C1, PIN_K_C2};
Keyboard keyboard(N_KEYBOARD_ROW, N_KEYBOARD_COL, pins_keyboard_row, pins_keyboard_col);
Adafruit_MCP4728 MCP4728;
MCP4728_channel_t cvMap[N_CV_GATES] = {MCP4728_CHANNEL_A, MCP4728_CHANNEL_B};
uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = {
1*83, 5*83, 9*83,
2*83, 6*83, 10*83,
3*83, 7*83, 11*83,
4*83, 8*83, 12*83
};
CV cv(&MCP4728, &Wire, N_CV_GATES, cvMap, keyToVoltage, N_KEYBOARD_ROW, N_KEYBOARD_COL);
void setup()
{
Serial.begin(BAUDRATE);
keyboard.begin();
cv.begin(PIN_SDA, PIN_SCL);
}
void loop()
{
keyboard.update();
int n = keyboard.getQueueLength();
if(n > 0)
{
Serial.printf("\n\rCurrent queue length: %i", n);
if(n == 1)
{
cv.setVoltage(0, keyboard.getQueue(0));
cv.setVoltage(1, NOT_A_KEY);
}
else if(n >= 2)
{
cv.setVoltage(0, keyboard.getQueue(0));
cv.setVoltage(1, keyboard.getQueue(1));
}
for(int i = 0; (i < N_CV_GATES) && (i < n); i++)
{
Key k = keyboard.getQueue(i);
if(isNotKey(k)) Serial.printf("\n\rQueue position %i: NOT A KEY", i);
else Serial.printf("\n\rQueue position %i: R%iC%i", i, k.row, k.col);
}
}
else cv.clearAll();
delay(50);
}