Re-doing my Hammond MIDI controller: ideas and suggestion needed

Status
Not open for further replies.

garubi

Well-known member
Hallo,
sorry for the long post, but I'd love to have your suggestion on my next project.

A premise
Last year I finished my MIDI drawbars Controller based on Teensy LC and since then I used it on a numbers of gigs without any problem
3105491541770680221.jpg801101541770665395.jpg
(for the whole project see here).
I also built a bunch of smaller midi controller in foot controller / pedalboard form factor for my musican friends.

Motivated from that "success" I'm now planning to convert/port to Teensy another DIY controller that I have built some years ago: a dual manual Hammond "clone".
wpid-20150510_224227.jpg
(detailed project log here)
At the time I based it on a Livid Instruments "Brain" but it requires a PC to run and moreover it hasn't ever been very reliable.

The plan is to replace the "brain" wth a Teensy and use the whole "clone" as a MIDI controller for an external sound module hooked up via MIDI (namely the Crumar "Gemini" module) keeping much of the already wired and soldered electronics, a part from the Drawbars set that I'll describe later.

Let me describe the current controller implementation:

The control panel
The controller has a panel with 7 analog potentiometers, that I'll attach to 7 analog inputs.
There are 28 momentary buttons and 28 leds already wired as a matrix with diodes and resistors (2 4x8 matrix, see below).
View attachment existing-panel-schematic.pdf

The drawbars set
Then there is a set of 9 + 2 + 9 Drawbars, coming from an original Hammond organ. They aren't the "contemporary" ones that are just linear potentiometers disguised. No, they are made of busses of rigid wires where the sliding Drawbars make contacts (the usual Drawbars thing, I attach some pictures from the bottom of one of them).
drawbars_howto.jpg.png

The drawbars deserve a few more words
In the current implementation of my controller I soldered an array of resistors across the busses, making the sliders actually behave like potentiometers. Then just attached them to 20 analog inputs.
This is how the vast majority of DIY projects that readjust "original" organ Drawbars do.
But in my experience this solution isn't very reliable: often there are spurious value, little jumps, etc, I think because the long wire busses are shared between all of the "sliders" so any movement of one slider from one contact to another causes voltage fluctation, noises etc that propagate to all of them.
(yes, capacitors are in place, and the things with them are a little better, but anyway not as thight as I would)

Having said that, my **new** plan is to revert back the Drawbars set to the original state (by removing all the resistors array) then transform it in a "button matrix" (sorta of). It will be a matrix of 9x20...
It should make sense: the busses are like rows and the sliders can be the columns: when I push/pull a slider and it closes the contact with one of the bus it's the same if I were pushing a button. Isn't it?
View attachment drawbar-matrix-idea.pdf
There used to be a repository on GitHub from someone who done something similar but now isn't accessible anymore.
I think that this implementation would be a lot more reliable and precise than the previous one based on the resistors array.

The two keyboards
The controller has two identical 61 keys keyboards. They are 2 old MAUDIO keyboard with the case removed. They have MIDI out both via usb and via MIDI DIN port so I plan to connect them to the Teensy making two MIDI in ports for them (or maybe using USB host on Teensy?).

The outputs
The controller has to send the Midi stream both via MIDI DIN out and MIDI on usb. But this is the "easy part" ;-)

My questions
At the very end it will be just a very big MIDI controller.
I'm not worried about the performaces: MIDI is a relatively slow protocol and all the buttons and the drawbars changes their state not so often.
But it will be anyway 3 big matrices to poll and it's required that any change in the buttons state will be detected and transmitted in something around 5/7ms for a good user experience (unnoticeable latency).

I see a bunch of options for implement all of that:
1) Using only one Teensy with a huge number of digital input (which one?).
2) a single smaller Teensy and some multiplexer to handle the matrices
3) use a second board (Teensy LC or Arduino) to poll for the Drawbars Matrix, and connect it via serial/MIDI to a "main" Teensy that handle all the rest

What do you suggest? what are your ideas about the whole thing? Are there other issue that you see and that I overlooked?

Thanks for your time and your help

Stefano
 
Last edited:
3.5, 3.6, and 4.1 all have enough pins to directly read out 8 rows / 20 columns AND 2X Serial ins to do MIDI merging from the two keyboards if you desire. 3.6 and 4.1 can USB host.

A decade counter or 3 ->8 decoder could strobe through the buss bars and leave enough pins to just pull off column reads with a smaller Teensy. I’ve got a set of drawbars around somewhere and was reticent to do the resistor hack, so I’m interested to see where you land.

Also since it’s MIDI you could put the keyboards on different channels and chain one into the other to save pins or avoid USB hub.
 
Thanks for your feedback!

A decade counter or 3 ->8 decoder could strobe through the buss bars and leave enough pins to just pull off column reads with a smaller Teensy..

I have problems understending the above sentece, it could be my bad English or my bad electronic knowledge :eek:
Could you elaboarate on it?

Also since it’s MIDI you could put the keyboards on different channels and chain one into the other to save pins or avoid USB hub.
Unfortunately the keyboards only have MDI OUT, not IN nor any merge function...

Thank you again!
Stefano
 
OK, I missed the switches and LEDs PDF in your first post, so this is just a drawbar reading solution. If you use a large Teensy, you can omit an external chip and just cycle through pulling one of the 8 or 9 buss bars high and read the 20 drawbars with input pins. Next buss high, read 20, repeat. Since I broached them, two other possibilities:

Decade counter approach: http://www.learningaboutelectronics.com/Articles/Decade-counter-circuit-with-4017.php
Instead of LEDS in that reference circuit, you would be pulling one buss bar high and reading state of all 20 of the drawbar pins to see which switches along that buss were down. Similar to the above.
Then pulse the clock pin and first buss goes low, the second buss bar goes high. Read 20 pins, pulse clock of 4017, repeat, eventually cycles back to 0 buss. You probably need a hard reference to which buss is active by reading back one of the lines*, so...two pins are needed to run the 4017 + 20 to read drawbars.

3 of 8 decoder approach**: http://www.learningaboutelectronics.com/Articles/74HC238-3-to-8-decoder-demultiplexer-circuit.php
In this approach, you would specify the buss bar (1-8) in binary on three lines and the chip decodes it to one of eight output pins. Again, you would be pulling a single buss bar high and reading state of all 20 of the drawbar pins to see which switches were down. So three address pins + 20 to read drawbars.

Teensy 3.2 has 25 I/O pins on the periphery, including A14 on the end. A10 and A11 are also available without resorting to funky pads on the bottom. So you have around what you need for either "external chip" approach including two pins for serial MIDI in, one for MIDI out, plus maybe a button or two. The code size and speed needs are simple enough you could squeeze it into a Teensy LC, but you will need another processor to read those buttons and display the LEDs.



John

*State of counter outs at power-up is not always as expected, so cycling to a known starting position is good practice.

**You said it was a 20 x 9 matrix but the PDF only shows 8 rows. The 3 of 8 decoder approach won't work if there's 9 busses.
 
You can use 1x 74hc147/cd40147b (10to4 line priority encoder) for the rows, and 3x cd4051 (1of8 analog MUX for the columns)
Also you need 9 pull up resistors on each row-"output"
With this only 10pins is required from the MCU/teensy.
 
schematic:
dragbar matrix.png

truth-table for col-select:
dragbars_colSelTruthtable.png

truth-table for row "output" (taken from 74hc147 datasheet)
dragbars_rowOutputTruthtable.png
 
there is the alternative to use drjohns approach with 1x 4017 (it have a reset input that when activated put the output to a known state)
combined with 3x 74hc165 (latch input, 8bit in, serial out) shiftregisters.
This is only requiring 5 IO from the teensy

dragbar_minimal_IO.png

edit.
I forgot to tie all INH (pin15) to GND in the schematic
also the SER input of IC5 should be tied to GND to make the logic ic stable.
also I reversed the serial data output so the first ROW input is the first bit out.
 
Last edited:
update

* the E-H inputs of the last shift-reg should be tied to gnd

* for extra stability there should also be (pulldowns + capacitors(optional)) on each of the 20 inputs
(they are not connected in the schematic, I don't have the energy to make all those connections)

* shiftregister input pins are in reverse order H = LSB, A = MSH (who did this stupid mistake)

new updated fixed schematic:
View attachment 22459

some code:
Code:
char bars[20];

// IO defines (pin-numbers is just an example)
#define IN_RESET 0
#define IN_CLK 1
#define OUT_CLK 2
#define OUT_LOAD_SHIFT 3
#define SERIAL_DATA_OUT 4

void setup()
{
    pinMode(IN_RESET, OUTPUT);
    pinMode(IN_CLK, OUTPUT);
    pinMode(OUT_CLK, OUTPUT);
    pinMode(OUT_LOAD_SHIFT, OUTPUT);
    pinMode(SERIAL_DATA_OUT, INPUT);

    // inital states
    digitalWrite(IN_RESET, LOW);
    digitalWrite(IN_CLK, LOW);
    digitalWrite(OUT_CLK, LOW);
    digitalWrite(OUT_LOAD_SHIFT, HIGH); // normal state

}

void loop()
{
    doBarsRead();
    if (bars[0] == 1) // first drawbar equals one
    {
        // do something
    }
    else if (bars[0] == 2) // first drawbar equals two
    {
        // do something
    }
    // ...

    if (bars[19] == 1) // last drawbar equals one
    {
        // do something
    }
    else if (bars[19] == 2) // last drawbar equals two
    {
        // do something
    }
}
void doBarsRead()
{
    digitalWrite(IN_RESET, HIGH);
    // minimum 260nS delay
    digitalWrite(IN_RESET, LOW);
    
    for (int ri = 1; ri <= 10; ri++)
    {
        // LATCH data
        digitalWrite(OUT_LOAD_SHIFT, LOW); 
        // minimum 100nS delay
        digitalWrite(OUT_LOAD_SHIFT, HIGH);
        
        for (int ci = 0; ci < 20; ci++)
        {
            if (digitalRead(SERIAL_DATA_OUT) == HIGH)
                bars[ci] = ri;
                
            // next col clock input
            digitalWrite(OUT_CLK, HIGH);
            // minimum 100nS delay
            digitalWrite(OUT_CLK, LOW);
        }
        
        // select next row
        digitalWrite(IN_CLK, HIGH);
        // minimum 100nS delay
        digitalWrite(IN_CLK, LOW);
    }
    // now each bars[i] have the selected value
}
 
Last edited:
Jannik and John,
thank you so much for your suggestions and for all the hard work you have put in it.
Now I have something to study for the next days :p ...

I can dedicate only few horus a week to this hobby, I'll be back to you as soon as I have something tested

Stefano


p.s. :
new updated fixed schematic:
View attachment 22459
Unfortunately It says it can't find the attachment, but I think I'll can go without it...
 
Last edited:
I found some time today to "study" your suggestions (You know how it happens: click one link, read that article, then follow that other link, read there, but wait what's that? search Google...e tc etc... and now it's evening...)
I like the solution of the last schematic, it's neat and logic.

Now I'm wondering if I could use the unused inputs of IC5 and another daisy-chained 74HC165 to read the button matrix of the control panel, maybe driv9ing the rows of the button matrix with onoter chip, maybe a 595 (or it can be another 4017N)?
Or do you think it's better to wire the button matrix indipendently with its own 74HC165 and for the rows [a to-be-identified chip]?

the existent button matrix is wired as follows:
Immagine 2020-11-17 173836.jpeg

...next there will be the LED matrix... but a step at a time ;-)
 
I haven't check the forum so much today, been busy with the new Design Tool and House maintaining
I think you can use the current 4017 for the button matrix as well because you have used diodes they will prevent
false readings.
and you can combine the LD/SHIFT + OUT_CLK and separate out the SERIAL_DATA_OUT from the added 74HC165

now in the last minute I did some calculations on the pulldown resistors they should be minimum 33k each,
because at worst case scenario all 20+8 bars + buttons will be connected to one row and that parallel connects all resistors to GND
which will be 33k/28 =1.1k => 3mA @ 3v3

also you should use the faster and "newer" model of the 4017 => 74HC4017E (74HC4017E datasheet specifies the timings down to 2V)


new schematic (explains much better):
dragbar_minimal_IO_ver2.jpg
code for that:
Code:
#define NOP3 "nop\n\t""nop\n\t""nop\n\t"
#define NOP4 "nop\n\t""nop\n\t""nop\n\t""nop\n\t"
#define NOP5 "nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"
#define NOP6 "nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"
#define NOP15 NOP4 NOP6 NOP5
#define NOP30 NOP10 NOP10 NOP10
// @ F_CPU @ 600Mhz 60NOP = 100nS
//                  90NOP = 150nS
#define P150ns __asm__(NOP30 NOP30 NOP30)
#define P300ns __asm__(NOP30 NOP30 NOP30 NOP30 NOP30 NOP30)

// IO defines (pin-numbers is just an example)
#define IN_RESET 0
#define IN_CLK 1
#define OUT_CLK 2
#define OUT_LOAD_SHIFT 3
#define SERIAL_DATA_OUT 4
#define SERIAL_DATA2_OUT 5

#define BARCOUNT 20
#define BARVALUECOUNT 9
#define MATRIXROWCOUNT 4

byte bars[BARCOUNT];
byte btnMatrix[MATRIXROWCOUNT];
byte btnMatrixCurrent;

void setup()
{
    pinMode(IN_RESET, OUTPUT);
    pinMode(IN_CLK, OUTPUT);
    pinMode(OUT_CLK, OUTPUT);
    pinMode(OUT_LOAD_SHIFT, OUTPUT);
    pinMode(SERIAL_DATA_OUT, INPUT);

    // inital states
    digitalWrite(IN_RESET, LOW);
    digitalWrite(IN_CLK, LOW);
    digitalWrite(OUT_CLK, LOW);
    digitalWrite(OUT_LOAD_SHIFT, HIGH); // normal state

}

void loop()
{
    doBarsRead();
    if (bars[0] == 1) // first drawbar equals one
    {
        // do something
    }
    else if (bars[0] == 2) // first drawbar equals two
    {
        // do something
    }
    // ...

    if (bars[19] == 1) // last drawbar equals one
    {
        // do something
    }
    else if (bars[19] == 2) // last drawbar equals two
    {
        // do something
    }
}
void doBarsRead()
{
    digitalWrite(IN_RESET, HIGH);
	P300ns; // min time @ 2V = 230nS using fast common model 74HC4017E
    digitalWrite(IN_RESET, LOW);
    P300ns; // extra delay to be on the safe side
	
	int btnMatrixIndex = 0;
    for (int ri = 1; ri <= BARVALUECOUNT; ri++)
    {
		
        // LATCH data
        digitalWrite(OUT_LOAD_SHIFT, LOW); 
		P150ns; // max @ 2v 74HC165 (it must be HC model because it have VCC 2-6V) HCT is 4.5-5.5v only
        digitalWrite(OUT_LOAD_SHIFT, HIGH);
        P150ns; // extra delay to be on the safe side
		btnMatrixCurrent = 0;
        for (int ci = 0; ci < BARCOUNT; ci++)
        {
            if (digitalRead(SERIAL_DATA_OUT) == HIGH)
                bars[ci] = ri;
            if (ci < 8)
			{
				if (digitalRead(SERIAL_DATA2_OUT) == HIGH)
					btnMatrixCurrent |= 0x01;
				btnMatrixCurrent = btnMatrixCurrent << 1;
			}
            // next col clock input
            digitalWrite(OUT_CLK, HIGH);
			P150ns;
            digitalWrite(OUT_CLK, LOW);
			P150ns; // extra delay to be on the safe side
			
			btnMatrix
        }
		if (btnMatrixIndex < MATRIXROWCOUNT)
			btnMatrix[btnMatrixIndex++] = btnMatrixCurrent;
        
        // select next row
        digitalWrite(IN_CLK, HIGH);
        P300ns; // min time @ 2V = 230nS using fast common model 74HC4017E
        digitalWrite(IN_CLK, LOW);
		P300ns; // extra delay to be on the safe side
    }
	// some calculations of the different delays in nS give
	// 300*2 + (150*2 + 300*2 + 150*20)*9 = 35.7uS => ~28011 in refresh-rate
	
    // now each bars[i] have the selected value
}

if you reverse the button matrix diodes
you can do this:
dragbar_minimal_IO_ver2.5.png
and the little different code: (note that I shift each btnMatrix[ci] for each in bit read, this is very efficient)
Code:
#define NOP3 "nop\n\t""nop\n\t""nop\n\t"
#define NOP4 "nop\n\t""nop\n\t""nop\n\t""nop\n\t"
#define NOP5 "nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"
#define NOP6 "nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"
#define NOP15 NOP4 NOP6 NOP5
#define NOP30 NOP10 NOP10 NOP10
// @ F_CPU @ 600Mhz 60NOP = 100nS
//                  90NOP = 150nS
#define P150ns __asm__(NOP30 NOP30 NOP30)
#define P300ns __asm__(NOP30 NOP30 NOP30 NOP30 NOP30 NOP30)

// IO defines (pin-numbers is just an example)
#define IN_RESET 0
#define IN_CLK 1
#define OUT_CLK 2
#define OUT_LOAD_SHIFT 3
#define SERIAL_DATA_OUT 4
#define SERIAL_DATA2_OUT 5

#define BARCOUNT 20
#define BARVALUECOUNT 9
#define MATRIXROWCOUNT 8
#define MATRIXCOLCOUNT 4

byte bars[BARCOUNT];
byte btnMatrix[MATRIXCOLCOUNT];

void setup()
{
    pinMode(IN_RESET, OUTPUT);
    pinMode(IN_CLK, OUTPUT);
    pinMode(OUT_CLK, OUTPUT);
    pinMode(OUT_LOAD_SHIFT, OUTPUT);
    pinMode(SERIAL_DATA_OUT, INPUT);

    // inital states
    digitalWrite(IN_RESET, LOW);
    digitalWrite(IN_CLK, LOW);
    digitalWrite(OUT_CLK, LOW);
    digitalWrite(OUT_LOAD_SHIFT, HIGH); // normal state

}

void loop()
{
    doBarsRead();
    if (bars[0] == 1) // first drawbar equals one
    {
        // do something
    }
    else if (bars[0] == 2) // first drawbar equals two
    {
        // do something
    }
    // ...

    if (bars[19] == 1) // last drawbar equals one
    {
        // do something
    }
    else if (bars[19] == 2) // last drawbar equals two
    {
        // do something
    }
}
void doBarsRead()
{
    digitalWrite(IN_RESET, HIGH);
    P300ns; // min time @ 2V = 230nS using fast common model 74HC4017E
    digitalWrite(IN_RESET, LOW);
    P300ns; // extra delay to be on the safe side
    
    int btnMatrixIndex = 0;
    for (int ri = 1; ri <= BARVALUECOUNT; ri++)
    {
        // LATCH data
        digitalWrite(OUT_LOAD_SHIFT, LOW); 
        P150ns; // max @ 2v 74HC165 (it must be HC model because it have VCC 2-6V) HCT is 4.5-5.5v only
        digitalWrite(OUT_LOAD_SHIFT, HIGH);
        P150ns; // extra delay to be on the safe side
        // first shift in the 20 PULLBARS
        for (int ci = 0; ci < BARCOUNT; ci++)
        {
            if (digitalRead(SERIAL_DATA_OUT) == HIGH)
                bars[ci] = ri;
        }
        // then shift in the button matrix only the first 8 lines
        if (btnMatrixIndex < MATRIXROWCOUNT)
        {
            for (int ci = 0; ci < MATRIXCOLCOUNT; ci++)
            {
                if (digitalRead(SERIAL_DATA2_OUT) == HIGH)
                    btnMatrix[ci] |= 0x01;
                btnMatrix[ci] = btnMatrix[ci] << 1; // visualize this like the matrix is rotated 90 degrees 

                // next col clock input
                digitalWrite(OUT_CLK, HIGH);
                P150ns;
                digitalWrite(OUT_CLK, LOW);
                P150ns; // extra delay to be on the safe side
            }
            btnMatrixIndex++;
        }
        // select next row
        digitalWrite(IN_CLK, HIGH);
        P300ns; // min time @ 2V = 230nS using fast common model 74HC4017E
        digitalWrite(IN_CLK, LOW);
        P300ns; // extra delay to be on the safe side
    }
    // some calculations of the different delays in nS give
    // 300*2 + (150*2 + 300*2 + 150*20)*9 = 35.7uS => ~28011 in refresh-rate
    
    // now each bars[i] have the selected value
}
 
Yes, it would be better to reverse the button matrix diodes...

For the leds (see the current implementation below) I was thinking to use something like 74HC595 or there are better choices?

Immagine 2020-11-20 175357.jpeg

Thank for your great help!!

Stefano
 
Hi,
I think the 74hc595 will do it + the existing 74hc4017 or a separate if you need a different refresh rate.

schematic (separate 74HC4017):
ledmatrix_minimal_IO_ver2.5.png

schematic (common 74HC4017):
dragbar_minimal_IO_andLEDmatrix.jpg
in the common version I have used diodes to form simple OR-gates
that make sure that when updating the leds they will get the same on-time
(maybe it's not needed, I can't verify the results because I don't have any 4017)


another solution is to use 4x 74hc595 to drive the leds directly without any matrix,
in this mode you only need to send led update when they are changing
 
Code:
#define NOP3 "nop\n\t""nop\n\t""nop\n\t"
#define NOP4 "nop\n\t""nop\n\t""nop\n\t""nop\n\t"
#define NOP5 "nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"
#define NOP6 "nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"
#define NOP15 NOP4 NOP6 NOP5
#define NOP30 NOP10 NOP10 NOP10

You shouldn't use nops.
It is incompatible between teensys, and different F_CPU. With a new, faster Teensy, it will not work anymore.
Better is to use delayNanoseconds(). Ther might be cases where your NOPs are a few nanoseconds faster, I know. But they are not good. ...and certainly not NOP30 or anything like that.
 
Status
Not open for further replies.
Back
Top