diff --git a/README.md b/README.md index c1acac4..a2088c4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # ProMicro-for-Zynthian +More info here: +https://discourse.zynthian.org/t/arduino-instead-of-mcp23017/4050/52 \ No newline at end of file diff --git a/zynthian-i2c-encoders_usb-to-serial-midi/zynthian-i2c-encoders_usb-to-serial-midi.ino b/zynthian-i2c-encoders_usb-to-serial-midi/zynthian-i2c-encoders_usb-to-serial-midi.ino new file mode 100644 index 0000000..aff7690 --- /dev/null +++ b/zynthian-i2c-encoders_usb-to-serial-midi/zynthian-i2c-encoders_usb-to-serial-midi.ino @@ -0,0 +1,313 @@ +#include +#include +#include + +#define COMMAND_HEADER 0 // Value of I2C command message header +#define CMD_RESET 0 // Reset command +#define DEBOUNCE_TIME 50 // Time in ms to ignore switch value change after previous change +#define ROT_THRESHOLD 10 // Threshold at which to change rotational scaling +#define ROT_FAST_SCALE 10 // Factor to multiply rotational change rate for fast scrolling + +enum CONTROLLER_TYPE +{ + CONTROLLER_TYPE_SWITCH, + CONTROLLER_TYPE_ENCODER +}; + +struct Controller +{ + bool dirty = false; // True if value has changed since last I2C read + int16_t value = 0; // Value + uint8_t count = 0; // Used to filter controller + uint8_t code = 0; // Used to filter encoder controller + uint8_t type; // Controller type (see CONTROLLER_TYPE) + uint32_t time; // Time of last update (ms since boot) + int16_t getValue() // Get the controller value with any required processing + { + int nValue = value; + switch(type) + { + case CONTROLLER_TYPE_ENCODER: + nValue = value; + value = 0; // Reset relative position + break; + case CONTROLLER_TYPE_SWITCH: + nValue = value; + } + return nValue; + } +}; + +static const uint8_t anValid[] = {0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0}; // Table of valid encoder states +static const uint8_t SwitchInputPins[] = {10,16,14,15}; +static const uint8_t EncoderInputPins[] = {4,5,6,7,18,19,20,21}; +static const uint8_t InterruptPin = 9; +static const uint8_t MAX_CONTROLLERS = sizeof(SwitchInputPins) + sizeof(EncoderInputPins) / 2; // Quantity of controllers +static const uint8_t SWITCH_REG_START = 65; // Offset of on / off switch I2C register +static const uint8_t ENCODER_REG_START = 115; // Offset of rotary encoder I2C register +static const uint8_t SWITCH_START = 0; // Offset of on / off switch controls in controller table +static const uint8_t ENCODER_START = sizeof(SwitchInputPins); // Offset of rotary encoder controls in controller table +Controller g_anControllers[MAX_CONTROLLERS]; // Array of controller objects + +uint8_t g_nI2Cregister = 0; // Address of I2C register requested by I2C master (index of controller - 1 based) +uint8_t g_nLastRead = 0; // The index of the controller last read + +MIDI_CREATE_DEFAULT_INSTANCE(); + + +void setup() { + MIDI.begin(MIDI_CHANNEL_OFF); + pinMode(LED_BUILTIN_TX,INPUT); //swithc rx and tx leds of, so they don't blink on midi + pinMode(LED_BUILTIN_RX,INPUT); + + //Serial.stop; + /* + Serial.begin(9600); + Serial.println("Starting"); + Serial.print("Controllers: "); + Serial.println(MAX_CONTROLLERS); + Serial.print("Encoders start at "); + Serial.println(ENCODER_START); + */ + + for(uint8_t i = 0; i < sizeof(SwitchInputPins); ++i) + pinMode(SwitchInputPins[i], INPUT_PULLUP); + for(uint8_t i = 0; i < sizeof(EncoderInputPins); ++i) + pinMode(EncoderInputPins[i], INPUT_PULLUP); + + pinMode(InterruptPin, OUTPUT); + + for(int i = 0; i < ENCODER_START; ++i) + g_anControllers[i].type = CONTROLLER_TYPE_SWITCH; + for(int i = ENCODER_START; i < MAX_CONTROLLERS; ++i) + g_anControllers[i].type = CONTROLLER_TYPE_ENCODER; + + Wire.begin(8); // I2C address is 8 + Wire.onRequest(onI2Crequest); + Wire.onReceive(onI2Creceive); + +} + +/** @brief Get the next controller who's values has changed since last I2C request + * @retval uint8_t Index of controller (1 based) or 0 for none + * @note Avoids one controller hogging bus by starting scan after last read controller + */ +uint8_t getDirty() +{ + // Iterate through all controllers looking for changed, starting at controller after last read then wrapping round + for(uint8_t i = g_nLastRead; i < MAX_CONTROLLERS; ++i) + { + if(g_anControllers[i].dirty) + { + digitalWrite(InterruptPin, LOW); + if(i < ENCODER_START) { + return(i + SWITCH_REG_START); + } + else { + return(i - ENCODER_START + ENCODER_REG_START); + } + } + } + for(uint8_t i = 0; i < g_nLastRead; ++i) + { + if(g_anControllers[i].dirty) + { + digitalWrite(InterruptPin, LOW); + if(i < ENCODER_START) { + return(i + SWITCH_REG_START); + } + else { + return(i - ENCODER_START + ENCODER_REG_START); + } + } + } + digitalWrite(InterruptPin, HIGH); + return 0; +} + +/** @brief Reset all controller values + * @todo Should we set values to current readings? + */ +void reset() +{ + for(uint8_t i = 0; i < MAX_CONTROLLERS; ++i) + { + g_anControllers[i].value = 0; + g_anControllers[i].dirty = false; + } + g_nLastRead = 0; + digitalWrite(InterruptPin, HIGH); + //Serial.println("RESET"); +} + +void sendReg(uint8_t nReg) +{ + //Wire.flush(); + uint16_t nValue = 0; + uint8_t data[2]; + if(nReg >= SWITCH_REG_START && nReg < SWITCH_REG_START + sizeof(SwitchInputPins)) + { + nValue = g_anControllers[nReg - SWITCH_REG_START].value; + g_anControllers[nReg - SWITCH_REG_START].dirty = false; + g_nLastRead = nReg - SWITCH_REG_START + 1; + } + else if(nReg >= ENCODER_REG_START && nReg < ENCODER_REG_START + sizeof(EncoderInputPins) / 2) + { + nValue = g_anControllers[nReg - ENCODER_REG_START + ENCODER_START].value; + g_anControllers[nReg - ENCODER_REG_START + ENCODER_START].dirty = false; + g_anControllers[nReg - ENCODER_REG_START + ENCODER_START].value = 0; + g_nLastRead = nReg - ENCODER_REG_START + ENCODER_START + 1; + } + if(g_nLastRead >= MAX_CONTROLLERS) + g_nLastRead = 0; + data[0] = nValue & 0xFF; + data[1] = (nValue & 0xFF00) >> 8; + Wire.write(data, 2); + getDirty(); + /* + Serial.println(); + Serial.print("I2C Tx reg: "); + Serial.print(nReg); + Serial.print(" value: "); + Serial.print(nValue); + Serial.print(":"); + Serial.print(g_anControllers[nValue].value); + Serial.print(":"); + Serial.print(data[0]); + Serial.print(data[1]); + Serial.println(); + */ +} + +void onI2Crequest() { + if(g_nI2Cregister) + { + // Send value of requested controller + sendReg(g_nI2Cregister); + //Serial.println(g_nI2Cregister); + g_nI2Cregister = 0; + return; + } + // Send index of first changed controller or zero if all clean + //Wire.flush(); // Seem to need to flush I2C to ensure single byte of data is sent correctly + Wire.write(getDirty()); +} + +void onI2Creceive(int nBytes) { + if(nBytes < 1) + return; + int nValue = Wire.read(); + if(nValue == COMMAND_HEADER && nBytes > 1) + { + nValue = Wire.read(); + switch(nValue) + { + case CMD_RESET: + reset(); + break; + } + } + else + { + g_nI2Cregister = nValue; + } + while(Wire.available()) + Wire.read(); // Clear buffer of unexpected bytes +} + +void readEncoder() { + int8_t nDir = 0; // Direction of rotation [-1, 0, +1] + + for(uint8_t n = 0; n < sizeof(EncoderInputPins) / 2; ++n) { + int nEncoder = n + ENCODER_START; + int nClk = digitalRead(EncoderInputPins[n*2]); + if(!nClk && !g_anControllers[nEncoder].code) + continue; // Ignore encoder if clock not asserted or not mid-decode + int nData = digitalRead(EncoderInputPins[n*2+1]); + g_anControllers[nEncoder].code <<= 2; + if(nData) + g_anControllers[nEncoder].code |= 0x02; + if(nClk) + g_anControllers[nEncoder].code |= 0x01; + g_anControllers[nEncoder].code &= 0x0f; + // If valid then add to 8-bit history validate rotation codes and process + if(anValid[g_anControllers[nEncoder].code]) { + g_anControllers[nEncoder].count <<= 4; + g_anControllers[nEncoder].count |= g_anControllers[nEncoder].code; + if(g_anControllers[nEncoder].count == 0xd4) + nDir = +1; + else if(g_anControllers[nEncoder].count == 0x17) + nDir = -1; + if(nDir) + { + //Inhibit fast rotation as it doesn't really work well + //uint32_t lNow = millis(); + //if(lNow >= g_anControllers[nEncoder].time + ROT_THRESHOLD) + g_anControllers[nEncoder].value += nDir; //Slow rotation + //else + //g_anControllers[nEncoder].value += nDir * ROT_FAST_SCALE; //Fast rotation + //g_anControllers[nEncoder].time = lNow; + g_anControllers[nEncoder].code = 0; + g_anControllers[nEncoder].dirty = true; + /* + Serial.print("Encoder "); + Serial.print(nEncoder); + Serial.print(":"); + Serial.print(g_anControllers[nEncoder].value); + Serial.println(""); + */ + } + } + } +} + +void readSwitch() { + for(uint8_t n = 0; n < sizeof(SwitchInputPins); ++n) { + int nSwitch = n; + int nValue = !digitalRead(SwitchInputPins[n]);//*(-1))+1; + if(nValue != g_anControllers[nSwitch].value && millis() > g_anControllers[nSwitch].time + DEBOUNCE_TIME) + { + // Note: Initial testing suggests that debounce is not required but it is added to handle noisy switches + g_anControllers[nSwitch].value = nValue; + g_anControllers[nSwitch].time = millis(); + g_anControllers[nSwitch].dirty = true; + /* + Serial.print("Switch "); + Serial.print(SWITCH_START + nSwitch); + Serial.print(":"); + Serial.print(g_anControllers[SWITCH_START + nSwitch].value); + Serial.println(""); + */ + } + } +} + +void loop() { + readSwitch(); + readEncoder(); + getDirty(); + USBtoMIDI(); +} + +void USBtoMIDI() { + midiEventPacket_t rx; + do { + rx = MidiUSB.read(); + if (rx.header != 0) { + MIDI.send(rx.byte1, rx.byte2, rx.byte3, 0x01); + + /* + Serial.print("Received: "); + Serial.print(rx.header, HEX); + Serial.print("-"); + Serial.print(rx.byte1, DEC); + Serial.print("-"); + Serial.print(rx.byte2, DEC); + Serial.print("-"); + Serial.println(rx.byte3, DEC); + Serial.println(rx.byte1-(rx.header << 4), DEC); //Channel + */ + + } + } while (rx.header != 0); +}