AK47m Project Part 1 (MIDI interface and Keypad interface)

This is a proof of concept project to produce a Roland BK7m replacement with a similar piece of kit that costs under £50. (which is the price an engineer would charge to look at a faulty BK7m.) This was outlined in an “up yours Roland” post before. A suitable name for this is the AK47m and you can work it out yourself why I chose it !

The first part of the project is to develop an interface to a MIDI instrument. The proper way to interface to MIDI input is to use an opto-isolator to avoid any direct electronic connections that could cause earth loops and to filter out any other nasty transients.

My first prototype used a general purpose opto-isolator and this failed miserably. It produced the appropriate changes in logic levels in response to an input MIDI message, but it was not fast enough. When I looked at the output from the opto it had a rounded return to +3.5v and the level was about half way at the bit sampling point, so it was neither up or down. So I need to order a faster device.

In order to make progress, I used a direct connection to the MIDI instrument and this worked good enough for my prototype, however I shall modify it to use an opto in due course.

I have used a PIC 16F628 microcontroller to handle the MIDI input and also to handle a 3×4 matrix keypad. The PIC outputs MIDI messages from its internal UART which are directed to any number of Raspberry PI computers. The PIC scans the keypad and debounces key presses. These are then translated into MIDI system common messages. e.g. a key press of 5 results in three MIDI bytes of

F1 00 05

An additional 4 press switches will be included later and these will be driven by the A3 output (A0, A1 & A2 drive the keypad, B4 ,B5, B6 and B7 sense the keypad)

 

Messages received from the instrument are relayed onwards. This introduces a slight amount of latency but that’s ok.

The PIC operates at 4 MHz and has a ceramic resonator to implement the clock, however it works just as well using the internal clock oscillator. The MIDI message baud rate is 31250 and so the bit time is 1000,000 / 31,250 =  32 uS. The internal UART for the PIC divides down the CPU oscillator to produce a UART clock and the divisor is a pure integer so the UART clock error is neglible.

The PIC firmware is written in PASCAL and compiled using the excellent MikroPascal IDE described previously. The firmware sets up the internal UART to receive and transmit at 31250 baud. It implements an interrupt handler that is activated when a single MIDI byte is received. The byte is stored in a FIFO buffer. The main process checks for received bytes and transmits them if present. It also scans the keypad matrix and debounces the key presses. If a key press is detected, a system message is transmitted with the keypad value. When the * key is pressed it transmits a MIDI message sequence that plays the three notes G,Ab,A as a test routine. A copy of the firmware is included later.

This is the circuit diagram – Note the opto isolater is shown seperately and the main circuit shows a direct connection

These are the connections to a 5 way DIN connector shown from the rear.

 

This is the keypad and the pinout

This shows the connections to the PI Zero . From left to right, RXD, Ground, 3.5v. The PI powers the PIC on the 3.5v supply line.

 

This is everything lashed up :

 

This is the PIC 16F628 firmware

program PI ;

{ Declarations section }
var fifo : array [0..63] of byte;
var inix, outix : byte;
var i : word;
var tmp : byte;


procedure interrupt(); iv 0x0004; ics ICS_AUTO;
begin
 if TestBit (PIR1, RCIF) then
 begin
 // tmp := RCSTA; // clear interrupt
 fifo[inix] := RCREG;


 if TestBit (RCSTA,FERR) or TestBit (RCSTA,OERR) then
 RCSTA := $90;

inix := (inix + 1) and $1F;

ClearBit(PIR1, RCIF);
 end;
end;

procedure delay ( pDelay : word) ;
//delay ms
var
 n : word;
 i : word;
begin
 for n := 1 to pDelay do
 for i := 1 to 76 do;
end;

procedure transmit ( pByte : BYTE) ;
begin
 TXREG := pByte;

//wait tx empty
 while true do
 begin
 if ((TXSTA and $02) = $02) then break;
 end;

end;

procedure sendTestMessage;
var
 i : byte;
begin
 for i := 0 to 2 do
 begin
 //note on
 transmit ($90);
 transmit (55 + i);
 transmit (80);

delay(150);

//note off
 transmit ($80);
 transmit (55 + i);
 transmit (0);
 end;
end;


procedure sendPerformanceMessage (performance : word);
const
 CONTROLCHANGE = $B0;
 PROGRAMCHANGE = $C0;
var
 cc32 , pc : word ;
begin
 cc32 := performance div 128;
 pc := performance mod 128;

transmit ($F1);
 transmit (cc32);
 transmit (pc);
end;


procedure busyDelay (delayMs : word);
var
 midiByte : byte;
 counter : dword;
begin
 // receive midi byte
 counter := delayMs * 30;
 while true do
 begin
 while (inix <> outix) do
 begin
 midiByte := fifo[outix];
 outix := (outix + 1) and $1F;
 transmit (midiByte);
 end;

if counter = 0 then break;
 counter := counter - 1;
 end;

end;

function scanKeypad : byte;
{

1 2 3 -- B7
 4 5 6 -- B4
 7 8 9 -- B5
 * 0 # -- B6

| | |
 | | |
 A0 A1 A2

* = 10
# = 11
}
var
 scan : byte;
begin
 scan := 255;

// OUT A2
 PORTA := $04;
 case (PORTB and $F0) of
 16 : scan := 6;
 32 : scan := 9;
 64 : scan := 11;
 128 : scan := 3;
 end;
 busyDelay(0);

// OUT A1
 PORTA := $02;
 case (PORTB and $F0) of
 16 : scan := 5;
 32 : scan := 8;
 64 : scan := 0;
 128 : scan := 2;
 end;
 busyDelay(0);

// OUT A0
 PORTA := $01;
 case (PORTB and $F0) of
 16 : scan := 4;
 32 : scan := 7;
 64 : scan := 10;
 128 : scan := 1;
 end;
 busyDelay(0);

scanKeypad := scan;
end;


function debounceKey : byte;
var
 key : byte;
 counter : word;
 presses : byte;
begin

// check for three consective valid presses 10ms check time
 counter := 0;
 presses := 0;
 while true do
 begin
 key := scanKeypad ;
 if (key <> 255) then
 presses := presses + 1
 else
 presses := 0;

if (presses = 3) then
 break; //valid key

busyDelay(10);
 counter := counter + 1;

if counter > 100 then
 begin
 key := 255;
 break; // timeout
 end;
 end;

// check for a release time of 50 ms
 counter := 0;
 presses := 0;
 if (key <> 255) then
 while true do
 begin
 if (scanKeypad = 255) then
 presses := presses + 1
 else
 presses := 0;

if (presses = 5) then
 break; //valid off time

busyDelay(10);
 counter := counter + 1;

if counter > 100 then
 begin
 key := 255;
 break; // timeout
 end;
 end;

debounceKey := key;
end;

begin
 { Main program }

// init usart
 TRISA := $FF; //isolate
 TRISB := $FF; // usart pins must be inputs exept B4
 CMCON := $07; // disable comparators


 //enable uart receiver and fifo
 RCSTA := $90;
 TXSTA := $24; // configure hi speed @ 4mhz
 SPBRG := 7; // MIDI baud rate 31250 ( 4000000 / 16 * BR) - 1

inix := 0;
 outix := 0;

//enable receiver interrupts
 SetBit (PIE1, RCIE);
 SetBit (INTCON, PEIE);
 SetBit (INTCON, GIE);

// output bit 0,1,2 and 4
 // 0,1,2 are for keypad scan, 4 is to control LED
 TRISA := $E8; // bit set is an input
 busyDelay(100);

inix := 0;
 outix := 0;

while true do
 begin
 busyDelay(0);

tmp := debounceKey; //times out after 1 second
 if (tmp <> 255) then
 begin
 if (tmp = 11) then 
 sendTestMessage
 else
 sendPerformanceMessage (tmp);
 end;

//PORTA := inix mod 3;
 end;

end.

Leave a Reply