The Breadboard-TS

BTSscope

The Breadboard TS is a development platform used to breadboard new synth ideas.

Features:

  • 830 point breadboard with built in 3 volt rails
  • 2 Octave touch keyboard
  • MIDI, CV/Gate and SQR-oscillator outputs
  • MIDI to CV/Gate input
  • 3.5mm Audio jack output
  • Built-in Oscilloscope
  • Powered by 2 AA-size 1.5v batteries
  • Measures 180x120x40mm
  • Comes with 65 jumper cables

It is built with a 830 point breadboard and the mini-TS found here.

BreadboardSynth

 

The Breadboard-TS can be bought prebuilt here:
Order the Breadboard-TS $149 including shipping

The code

The firmware for the BTS is open-source and is divide in 4 blocks:

  • Touch keyboard scanner
  • Built-In test square wave oscillator
  • MIDI2CV
  • OLED Display & Oscilloscope

The keyboard is a capacitive touch keyboard and because it has 25 keys there aren’t enough GPIO’s to assign for each key. So the keyboard is arranged in a matrix of 5 columns and 5 rows.

Each key on the keyboard have 2 touch pads, 1 from a column and 1 from a row. That totals to 50 touch pads!

They are also cleverly arranged to minimize keyrollover.

This is the code for scanning and finding a keypress:

uint8_t capsenseCOLbase = 8; //Set capacitive touch Column sensitivity
uint8_t capsenseROWbase = 6; //Set capacitive touch Row sensitivity

//———- Capacitive Touch sensing —————————–

uint8_t capsensePORTD(uint8_t mask) {
   if ((mask&1)||(mask&2)) return 0; //Dont measure our MIDI pins
   PORTD &= 0x03; //Ground the PCB surface
   DDRD  |= 0xFC;
   asm(“nop”);
   cli();
   DDRD &= ~(mask); //Turn selected pin to input
   PORTD |= mask;   //With pullup
   uint8_t cycles = 0;
   if      (PIND & mask) { cycles =  0;}
   else if (PIND & mask) { cycles =  1;}
   else if (PIND & mask) { cycles =  2;}
   else if (PIND & mask) { cycles =  3;}
   else if (PIND & mask) { cycles =  4;}
   else if (PIND & mask) { cycles =  5;}
   else if (PIND & mask) { cycles =  6;}
   else if (PIND & mask) { cycles =  7;}
   else if (PIND & mask) { cycles =  8;}
   else if (PIND & mask) { cycles =  9;}
   else if (PIND & mask) { cycles = 10;}
   else if (PIND & mask) { cycles = 11;}
   else if (PIND & mask) { cycles = 12;}
   else if (PIND & mask) { cycles = 13;}
   else if (PIND & mask) { cycles = 14;}
   else if (PIND & mask) { cycles = 15;}
   else if (PIND & mask) { cycles = 16;}
   sei();
   DDRD  |= 0xFC;
   PORTD &= 0x03; //Ground the PCB surface
   return cycles; //Return measured cycles
}

uint8_t capsensePORTB(uint8_t mask) {
   if ((mask&2)||(mask&8)||(mask&16)||(mask&32)) return 0; //Dont measure our Audio and CV pins
   PORTB &= 0x3A; //Ground the PCB surface
   DDRB  |= 0xC5;
   asm(“nop”);
   cli();
   DDRB &= ~(mask); //Turn selected pin to input
   PORTB |= mask;   //With pullup
   uint8_t cycles = 0;
   if      (PINB & mask) { cycles =  0;}
   else if (PINB & mask) { cycles =  1;}
   else if (PINB & mask) { cycles =  2;}
   else if (PINB & mask) { cycles =  3;}
   else if (PINB & mask) { cycles =  4;}
   else if (PINB & mask) { cycles =  5;}
   else if (PINB & mask) { cycles =  6;}
   else if (PINB & mask) { cycles =  7;}
   else if (PINB & mask) { cycles =  8;}
   else if (PINB & mask) { cycles =  9;}
   else if (PINB & mask) { cycles = 10;}
   else if (PINB & mask) { cycles = 11;}
   else if (PINB & mask) { cycles = 12;}
   else if (PINB & mask) { cycles = 13;}
   else if (PINB & mask) { cycles = 14;}
   else if (PINB & mask) { cycles = 15;}
   else if (PINB & mask) { cycles = 16;}
   sei();
   DDRB  |= 0xC5;
   PORTB &= 0x3A; //Ground the PCB surface
   return cycles; //Return measured cycles
}

//—————————————————————–

//—————— Key scanner —————————–
  uint8_t keydown=0;
  uint8_t keydownC=0;
  uint8_t keydownR=0;
  uint8_t keycol=0;
  uint8_t keyrow=0;
  for (uint8_t column=0;column<5;column++) {
   if ((column==0)&&(capsensePORTD(4)>capsenseCOLbase)) {
      keycol=column;
      keydownC=1;
    }
    if ((column==1)&&(capsensePORTD(8)>capsenseCOLbase)) {
      keycol=column;
      keydownC=1;
    }
    if ((column==2)&&(capsensePORTD(16)>capsenseCOLbase)) {
      keycol=column;
      keydownC=1;
    }
    if ((column==3)&&(capsensePORTB(64)>capsenseCOLbase)) {
      keycol=column;
      keydownC=1;
    }
    if ((column==4)&&(capsensePORTB(128)>capsenseCOLbase)) {
      keycol=column;
      keydownC=1;
    } 
  }
  for (uint8_t row=0;row<5;row++) {
   if ((row==0)&&(capsensePORTD(32)>capsenseROWbase)) {
      keyrow=row;
      keydownR=1;
    }
    if ((row==1)&&(capsensePORTD(64)>capsenseROWbase)) {
      keyrow=row;
      keydownR=1;
    }
    if ((row==2)&&(capsensePORTD(128)>capsenseROWbase)) {
      keyrow=row;
      keydownR=1;
    }
    if ((row==3)&&(capsensePORTB(1)>capsenseROWbase)) {
      keyrow=row;
      keydownR=1;
    }
    if ((row==4)&&(capsensePORTB(4)>capsenseROWbase)) {
      keyrow=row;
      keydownR=1;
    } 
  }
 
  if ((keydownC)&&(keydownR)) {
    key=(keyrow*5)+keycol;
    keydown=1;
  }

The square wave test oscillator is a simple DDS job.
It only outputs when there is a gate from the built-in keyboard or from MIDI:

//——————– DCO block ——————————————
if (!(flip+=4)) {
phacc+=FREQ;
if ((phacc&0x8000)&&(TRIG)) digitalWrite(11,HIGH); //OCR2A = 255;
if ((phacc&0x8000)&&(MIDITRIG)) digitalWrite(11,HIGH); //OCR2A = 255;
if (!(phacc&0x8000)) digitalWrite(11,LOW); //OCR2A = 0;
}
//—————————————————————————

MIDI2CV uses Timer1 as PWM DAC for 11 bits resolution.
The 0-Vcc range is divided into 5 octaves or 256 voltage steps per octave:

TCCR1A =
(1 << COM1A1)  |
// Fast PWM mode.
(1 << WGM11);
TCCR1B =
// Fast PWM mode.
(1 << WGM12) | (1 << WGM13) |
// No clock prescaling (fastest possible
// freq).
(1 << CS11);
OCR1A = 0;
TIMSK1=0;
OCR1AH = 0; //Set initial CV to 0 volts.
OCR1AL = 0;

The display is a standard 128×32 graphics i2c OLED. It is connected to the ATmega328 SCL/SDA i2c pins.

In the code I’m using the SSD1306xled library.

The scope is sampled with 9600sps and the waveform is then drawn to the display.
The rotary encoder makes small changes to the scan rate for waveform sync.

//——————— Scope block —————————————–
scale+=sync;
if (scale&0x80) {
scale&=0x7F;

if (!(ADCSRA & 64)) {
scopewave[scopeCNT++]=63-((ADCL+(ADCH<<8))>>4);
ADMUX = 64 | 3; //Select MUX
sbi(ADCSRA, ADSC); //start next conversation
}
}
//—————————————————————————

void ssd1306_drawwave() {
uint8_t m,n,wave,sp;
for (m = 0; m < 4; m++)
{
ssd1306_send_command(0xb0 + m); // page0 – page1
ssd1306_send_command(0x00);   // low column start address
ssd1306_send_command(0x10);   // high column start address
ssd1306_send_data_start();
for (n = 0; n < 128; n++)
{
if ((scopewave[n]>>4)==m) {
sp=(1<<((scopewave[n]>>2)&0x07));
if (((m==2)||(m==4)||(m==6))&&((n&3)==1)) sp|=1;
if ((n&15)==8) sp|=16;
ssd1306_send_byte(sp);
}
else {
sp=0;
if (((m==2)||(m==4)||(m==6))&&((n&3)==1)) sp|=1;
if ((n&15)==8) sp|=16;
ssd1306_send_byte(sp);
}
}
ssd1306_send_data_stop();
}
}

//————— Draw Oscilloscope ————————————-
ssd1306_drawwave();
sync+=read_encoder();
if (sync>128) sync=128;
if (sync<8) sync=8;
}
//——————————————————————–

The firmware

Here is the full code for the firmware of the built-in ATmega328:

Download BreadboardTS.INO

 

Advertisements

%d bloggers like this: