- Code: Select all
// Name: MIDI_Encoder.ino
// Created: 1/8/2019
// Author: John Kinkennon
#include <arduino.h>
#include <SPI.h>
#include "User.h"
#include "Midi.h"
const int channel = 0; // 0 to 15 for MIDI channel 1 to 16
const int slaveInPin = 10; // chip select for SPI input
const int slaveOutPin = 9; // chip select for SPI output
const int analogPin1 = 14; // potentiometer input pins
const int analogPin2 = 15;
const int analogPin3 = 16;
const int analogPin4 = 17;
const int analogPin5 = 18;
const int analogPin6 = 19;
const int analogPin7 = 20;
const int analogPin8 = 21;
// GPIO pins
const int relay1 = 24; // power relay (available)
const int relay2 = 25; // audio power relay
const int powerLamp = 26; // 12v power lamp (available)
const int audioLamp = 27; // audio power lamp
const int cresc1 = 28; // crescendo stage 1 lamp (for HW cresc)
const int cresc2 = 29; // crescendo stage 2
const int cresc3 = 30; // crescendo stage 3
const int cresc4 = 35; // crescendo stage 4
// set up the speed, mode and endianness device
SPISettings settingsIn(1000000, MSBFIRST, SPI_MODE0);
unsigned char sysex[64]; // Buffer to assemble SysEx message
int sx = 0; // Index into sysex[64]
keyTable8_t keyTable;
keyTableB_t keyBit; // true for keys which are depressed
keyTableB_t keyOn; // true for keys which are on after debounce
keyTableB_t stopOn; // true for stops that are on
spiTable_t spiInput; // buffer variable to store read data
spiTable_t spiOutput; // buffer variable to store write data
uint16_t newPotValue[NUM_POTS];
uint16_t oldPotValue[NUM_POTS];
int keyScanCount; // cycles 0 through 7 to allow slowing some tasks
elapsedMillis sincePending;
elapsedMillis sincePulse;
// Sysex messages require HW output, see General Settings->General->Advanced settings
void mySystemExclusiveChunk(const byte *data, uint16_t length, bool last) {
int i;
for (i = sx; i < length; i++) {
sysex[sx++] = data[i];
}
if (sx == 6) {
switch (sysex[3]) {
case 33: // IsSetterModeOn
// TODO: Turn on a yellow LED
break;
case 38: // IsOrganReady
// Match LED states to relays
if (sysex[4] == 1) {
digitalWrite(relay2, HIGH);
digitalWrite(audioLamp, HIGH);
}
else if (sysex[4] == 0) {
digitalWrite(relay2, LOW);
digitalWrite(audioLamp, LOW);
}
break;
case 39: // IsInErrorState
// TODO: Turn on a red LED
break;
case 47: // Master Crescendo Pedal
digitalWrite(cresc1, LOW);
digitalWrite(cresc2, LOW);
digitalWrite(cresc3, LOW);
digitalWrite(cresc4, LOW);
if (sysex[4] > 0) {
digitalWrite(cresc1, HIGH);
}
if (sysex[4] > 8) {
digitalWrite(cresc2, HIGH);
}
if (sysex[4] > 16) {
digitalWrite(cresc3, HIGH);
}
if (sysex[4] > 24) {
digitalWrite(cresc4, HIGH);
}
break;
case 84: // IsOrganLoaded
// Possible substitute for IsOrganReady
break;
case 85: // IsOrganLoading
// TODO: Turn on a yellow LED
break;
}
}
if (sx == 39) {
switch (sysex[3]) {
case 0:
for (i = 6; i < sx - 1; i++) {
HWSERIAL1.write(sysex[i]);
}
break;
case 1:
// TODO: Write to LCD #2
for (i = 6; i < sx - 1; i++) {
HWSERIAL2.write(sysex[i]);
}
break;
default:
break;
}
}
if (last) {
sx = 0;
for (i = 0; i < 64; i++)
sysex[i] = 0;
}
}
// the setup function runs once when you press reset or power the board
void setup() {
int i, j;
pinMode(slaveInPin, OUTPUT);
pinMode(slaveOutPin, OUTPUT);
pinMode(analogPin1, INPUT);
pinMode(analogPin2, INPUT);
pinMode(analogPin3, INPUT);
pinMode(analogPin4, INPUT);
pinMode(analogPin5, INPUT);
pinMode(analogPin6, INPUT);
pinMode(analogPin7, INPUT);
pinMode(analogPin8, INPUT);
pinMode(relay1, OUTPUT); // SSR_1, Switched Power
pinMode(relay2, OUTPUT); // SSR_2, Organ Ready Power
pinMode(powerLamp, OUTPUT); // Ground to turn on power
pinMode(audioLamp, OUTPUT); // Ground to turn on audio pwr
pinMode(cresc1, OUTPUT); // Ground to turn on cresc1 lamp
pinMode(cresc2, OUTPUT); // Ground to turn on cresc2 lamp
pinMode(cresc3, OUTPUT); // Ground to turn on cresc3 lamp
pinMode(cresc4, OUTPUT); // Ground to turn on cresc4 lamp
HWSERIAL1.begin(9600); // default, reliable baud rate
HWSERIAL2.begin(9600);
delay(500); // wait for LCDs to boot
HWSERIAL1.write(254);
HWSERIAL1.write(01); // Clear displays
HWSERIAL2.write(254);
HWSERIAL2.write(01);
digitalWrite(slaveInPin, HIGH); // HIGH to shift, LOW to load
digitalWrite(slaveOutPin, HIGH); // HIGH edge to move data to outputs
for (i = 0; i < NUM_SWITCHES; i++) {
keyTable.O[i] = 0;
keyBit.O[i] = false;
keyOn.O[i] = false;
stopOn.O[i] = false;
}
for (i = 0; i < NUM_CHANNELS; i++) {
for (j = 0; j < NUM_REGISTERS; j++) {
spiInput.C[i][j] = 0;
spiOutput.C[i][j] = 0;
}
}
for (i = 0; i < NUM_POTS; i++) {
newPotValue[i] = 0;
oldPotValue[i] = 0;
}
keyScanCount = 0;
// initialize SPI:
SPI.begin();
usbMIDI.setHandleNoteOff(myNoteOff);
usbMIDI.setHandleNoteOn(myNoteOn);
usbMIDI.setHandleSystemExclusive(mySystemExclusiveChunk);
// similar callbacks available for other MIDI message types
}
// the loop function runs over and over again until power down or reset
void loop() {
int i, j, k, x;
keyScanCount++;
if (keyScanCount > 7) keyScanCount = 0;
// read and write all SPI devices
SPI.beginTransaction(settingsIn);
// read and write 8 registers
digitalWrite(slaveInPin, HIGH); // enable shifting
digitalWrite(slaveOutPin, LOW); // no effect
for (i = 0; i < NUM_CHANNELS; i++) {
k = (NUM_CHANNELS - 1) - i;
for (j = 0; j < NUM_REGISTERS; j++) {
if (j < 4) x = 3 - j;
else x = 11 - j;
spiInput.C[i][j] = SPI.transfer(spiOutput.C[k][x]);
}
}
digitalWrite(slaveInPin, LOW); // disable shifting
digitalWrite(slaveOutPin, HIGH); // high edge latches data
SPI.endTransaction();
for (i = 0; i < NUM_CHANNELS; i++) {
for (j = 0; j < NUM_REGISTERS; j++) {
uint8_t tempData = spiInput.C[i][j];
// if (i == 4) // pedal is MIDI channel 5, software ch 4
// tempData ^= 0xff;
#ifdef INVERT_SPI_DATA
tempData ^= 0xff;
#endif
int endIndex = (NUM_REGISTERS - j) * 8;
int startIndex = endIndex - 8;
for (k = startIndex; k < endIndex; k++) {
keyBit.C[i][k] = tempData & 0b00000001;
tempData >>= 1;
}
}
for (int key = 0; key < 64; key++) {
uint8_t prevData = keyTable.C[i][key];
bool newKey = keyBit.C[i][key]; // is keyDown now?
bool keyDown = (prevData & 0x80); // was keyDown true on previous scan?
prevData <<= 1; // shift the data left one bit
if (newKey) prevData++; // set newest bit
prevData &= 0x0f; // strip any high bits
if (!keyDown) { // if note is off in keyTable see if it should be on
//if (prevData == 0b00000011) { // if 3 consecutive keyDown
if (prevData > 0) { // this is a fast noteOn for testing
keyDown = true; // turn key on in keyTable
usbMIDI.sendNoteOn(key + 0x24, M_VELOCITY_ON, i + 1, 0);
keyOn.C[i][key] = true;
}
}
else if (keyDown) { // if note is on in keyTable see if it should be off
if (prevData == 0b00000000) { // if 3 consecutive keyUp
keyDown = false; // turn key off in keyTable
usbMIDI.sendNoteOff(key + 0x24, M_VELOCITY_OFF, i + 1, 0);
keyOn.C[i][key] = false;
}
}
if (keyDown) prevData |= 0x80; // store the data
keyTable.C[i][key] = prevData;
}
}
i = keyScanCount;
newPotValue[i] = analogRead(i + analogPin1); // sequential pin #'s, nice
newPotValue[i] >>= 3;
newPotValue[i] = (newPotValue[i] + oldPotValue[i]) >> 1; // average old and new
int diff = newPotValue[i] - oldPotValue[i]; // different value?
if (diff != 0) {
usbMIDI.sendControlChange(M_CC_VOLUME, newPotValue[i], i + 1, 0);
}
oldPotValue[i] = newPotValue[i];
usbMIDI.read(); // prime USB to receive any MIDI packet
}
void myNoteOff(byte ch, byte note, byte velocity) {
if ((ch == (channel + 1)) && (note < 100)) {
int stop = note - 0x24;
setSpiOutput(false, stop);
}
}
void myNoteOn(byte ch, byte note, byte velocity) {
if ((ch == (channel + 1)) && (note < 100)) {
int stop = note - 0x24;
setSpiOutput(true, stop);
}
}
/* see Capture_System code for pulsing SAMs */
void setSpiOutput(bool turnOn, int stop) {
stopOn.C[channel][stop] = turnOn; // it turnOn true then turn on
// stop +=32; // FIX for a single 32-pin output board
int spiRegister = stop / 8;
int spiBit = stop % 8;
if (turnOn) {
spiOutput.O[spiRegister] |= (1 << spiBit); // set bit for this note (stop)
}
else {
spiOutput.O[spiRegister] &= ~(1 << spiBit); // clear bit for this note (stop)
}
}
Adapt as needed or send me a PM or preferably an email to john@kinkennon.com. The outputs, relay1 through cresc4 are all run through the ULN2803 IC. I can also provide a couple of extra files that would be needed, the MIDI definitions in Midi.h and User.h, if the file is to be used as is for a full-blown encoder.