The goal of this project was to add MIDI connectivity to the Korg/Littlebits Synth Kit, so that I could use it with a full sized keyboard and any other MIDI devices and software. Currently I've implemented note on, note off and pitch bend to control the oscillators with CV from an digital-to-analog converter(DAC) and trigger the envelope and sequencer with a digital signal.

I set out to buy a DAC (MCP4821) and a JST SH 3-wire connector to connect the Littlebits Wire module to an Arduino + MIDI shield. Getting hold of the connector in Helsinki proved impossible, so I had to flatten some jumper cable to fit in the tiny connector. (Meanwhile, I ordered the real connectors from Sparkfun.)

After making an experiment by controlling the voltage and reading the frequency in Max, I concluded that the CV protocol of the oscillator is of the V/Octave variant, unlike Hz/V used in some of the other Korg synths, such as MS-20 (Mini).

With the tune knob on the oscillator bit, the pitch range could be adjusted accurately so that each semitone is represented by 1000/12 or 83 and 1/3 milliVolts. My master keyboard begins at C2, or MIDI note 36, so I adjusted the pitch of the oscillator so that the first audible tone matched to that.

I found out that the trigger on the envelope bit was activated by a rising edge, or a signal that connects from ground to a higher voltage. Surprisingly, connecting the signal connector of the trigger inlet of the envelope bit to one of Arduino's digital pins and writing LOW and HIGH to it did not work. I solved the issue by adding a 15KOhm pull-down resistor from that pin to ground.

Finally, the Arduino code was simple enough, thanks to Kerry Wong's MCP4821 example and the excellent Arduino MIDI library.

#include <MIDI.h>
#include <SPI.h>
 
#define PIN_CS 10
#define GAIN_1 0x1
#define GAIN_2 0x0
#define DAC_MAX 4095

#define TRIG_PIN 3
#define SEMITONE 83.333 //millivolts per semitone
#define PITCH_BEND 833.33
#define ZERONOTE 33 //zero volts midi note
#define MAXNOTE 82

byte lastNoteOn = 0;
int lastPitchBend = 0;

void setup()
{
  pinMode(PIN_CS, OUTPUT);
  SPI.begin();  
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  
  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.setHandleNoteOn(HandleNoteOn);
  MIDI.setHandlePitchBend(HandlePitchBend);
}
 
//assuming single channel, gain=2
void setOutput(unsigned int val)
{
  byte lowByte = val & 0xff;
  byte highByte = ((val >> 8) & 0xff) | 0x10;
   
  PORTB &= 0xfb;
  SPI.transfer(highByte);
  SPI.transfer(lowByte);
  PORTB |= 0x4;
}

void loop() {
  MIDI.read();
}

void HandleNoteOn(byte channel, byte pitch, byte velocity) {
  if(pitch >= ZERONOTE && pitch <= MAXNOTE) {
    if(velocity > 0) {
      lastNoteOn = pitch;
      int freq = round(SEMITONE*(lastNoteOn-ZERONOTE) + PITCH_BEND*(lastPitchBend/8192.0));
      freq = max(min(freq, DAC_MAX), 0);
      setOutput(freq);
      digitalWrite(TRIG_PIN, HIGH);
    }
    else if(pitch == lastNoteOn) {
      digitalWrite(TRIG_PIN, LOW);
    }
  }
}

void HandlePitchBend(byte channel, int bend) {
  lastPitchBend = bend;
  int freq = round(SEMITONE*(lastNoteOn-ZERONOTE) + PITCH_BEND*(lastPitchBend/8192.0));
  freq = max(min(freq, DAC_MAX), 0);
  setOutput(freq);
}