3
\$\begingroup\$

I am rewiring a cheap old organ for digital and want to use shift registers to read all the keys. Is it possible to debounce the keys after the shift registers, or do I need to do it before? Either way, is there anything I need to keep in mind?

\$\endgroup\$
  • \$\begingroup\$ You effectively asked a few new questions. In general you should not do that, it makes a perfect answer to your original question seem sloppy for nor answering the new parts. Better open a new question, or ask a (small) follow-up in a comment to the answer. \$\endgroup\$ – Wouter van Ooijen Feb 24 '15 at 14:34
  • \$\begingroup\$ Sorry, rolled back. People just wrote more general answers than I expected. Michael's answer gave me enough confidence to order the components, and they're cheap enough that I don't risk much by finding the rest of my answers via experimentation. \$\endgroup\$ – Karl Bielefeldt Feb 24 '15 at 14:47
5
\$\begingroup\$

It is perfectly legitimate to strobe all the key present states into a series of shift registers and bring those states into the software realm. All you need to do is to sample the key states on a periodic basis and at each sample do some "vertical bit math" on the current bit stream as compared to the previous copy of the bit stream.

The neat thing about this approach is that you can operate on the vectors of all the input stream data a byte at a time and effectively process the debounce and current state logic on eight keys at a time!

I have used this approach in a number of commercial products over the years and have been able to support large numbers of key inputs without consuming a high percentage of the processing bandwidth if the MCU.

To give an idea of how the vertical binary math works I will share some code here that processes a vector of input signals. In the example the inputs were gathered from six pins coming in from a port. However it can be extended to as wide of vector as you need with successive bytes of data coming in from your shift registers.

First some definitions from a header file. Note that this input filter scheme was designed to be called once each 10 msec with four sample periods to provide for switch debouncing over a 40 msec period.

#ifndef _INPUTS_H
#define _INPUTS_H

/* setup the number of input poll samples to filter for stable state */
#define INPUT_POLL_COUNT    4

/* define the states of the INPUT bits in the input status byte */
/* the inputs from port 4 are gathered and shifted right two bits */
#define INP_1           0x01    /* P4.2 - input 1 */
#define INP_2           0x02    /* P4.3 - input 2 */
#define INP_3           0x04    /* P4.4 - input 3 */
#define INP_4           0x08    /* P4.5 - input 4 */
#define INP_5           0x10    /* P4.6 - input 5 */
#define INP_6           0x20    /* P4.7 - input 6 */

#define INP_CNT     6           /* number of logical inputs */

/* global data declarations */
extern unsigned char code inp_table[INP_CNT];
extern unsigned char xdata inp_filter[INPUT_POLL_COUNT];
extern unsigned char xdata inp_status;
extern unsigned char xdata inp_previous;
extern unsigned char xdata inp_true;
extern unsigned char xdata inp_false;

/* input function prototypes */
extern void inp_init(void);
extern void inp_scan(void);
extern unsigned char inp_state(unsigned char inp_bit);
extern void inp_clear(unsigned char inp_bit);
extern unsigned char inp_true_test(unsigned char inp_bit);
extern unsigned char inp_true_get(unsigned char inp_bit);
extern unsigned char inp_false_test(unsigned char inp_bit);
extern unsigned char inp_false_get(unsigned char inp_bit);

#endif /* _INPUTS_H */

Here is the global data definition for the input debounce scanner.

/* array of defined INP acess masks ordered by index number */
unsigned char code inp_table[INP_CNT] = {
                                         INP_1,      /* P4.2 - input 1 / power LED */
                                         INP_2,      /* P4.3 - input 2 / green stat LED */
                                         INP_3,      /* P4.4 - input 3 / amber stat LED */
                                         INP_4,      /* P4.5 - input 4 / blue ID LED */
                                         INP_5,      /* P4.6 - input 5 / aux in A */
                                         INP_6       /* P4.7 - input 6 / aux in B */
                                        };

/* array of bit flags for inputs filter */
unsigned char xdata inp_filter[INPUT_POLL_COUNT];
unsigned char xdata inp_status;          /* current filtered input state */
unsigned char xdata inp_previous;        /* previous filtered input state */
unsigned char xdata inp_true;            /* saved input true transition */
unsigned char xdata inp_false;           /* saved input false transition */

Next here is the routine called at startup to initialize the debounce filter arrays and current state vectors.

/*
**
** routine to initialize the inp scan port logic to the inactive
** state. 
**
*/

void inp_init(void)
{
    unsigned char i;

    for(i=0; i<INPUT_POLL_COUNT; i++)
    {
        inp_filter[i] = 0;
    }
    inp_status = 0;
    inp_previous = 0;
    inp_true = 0;
    inp_false = 0;
}

This is the routine called once each 10 msec from a timer interrupt to fetch the current input data and process it through the bit vectors using vertical bit math.

/*
** 
** routine to poll the inp lines and filter the current state of inputs
** (note that this is called from the timer interrupt function)
**
*/

void inp_scan(void) using 1
{
    unsigned char inp_tmp;
    unsigned char inp_or;
    unsigned char inp_cur;
    unsigned char i;

    inp_cur = P4 >> 2;                      /* read inputs from P4.2 -> P4.7 */

    /* loop to shift filter up by one position and perform */
    /* bit wise equal comparison */
    inp_or = 0;                             /* this holds 1 for bits that are */  
    for(i=0; i<INPUT_POLL_COUNT; i++)       /* changing in span of filter table */
    {
        inp_or |= inp_cur ^ inp_filter[i];  /* adjacent pair != so set or */
        inp_tmp = inp_filter[i];            /* swap so to slide table up */
        inp_filter[i] = inp_cur;
        inp_cur = inp_tmp;
    }

    /* produce bit pattern for current stable input data */
    /* if a input goes true its previous false state data is */
    /* automatically cleared and as well if a input goes false */
    /* its previous true going status is cleared */
            /* no chg where toggles  */   /* mask present status where stable */
    inp_status = (inp_status & inp_or) | (inp_filter[0] & ~inp_or);

    inp_tmp = inp_status & (inp_status ^ inp_previous);
    inp_true |= inp_tmp;
    inp_false &= ~inp_tmp;

    inp_tmp = inp_previous & (inp_status ^ inp_previous);
    inp_false |= inp_tmp;
    inp_true &= ~inp_tmp;

    inp_previous = inp_status;
}

Finally here is a collection of subroutines that can be called by mainline code to see what the current status of any given key is.

/*
**
** function to return the current filtered and stable state
** of a specific inp input. returns 0 if the inp is
** inactive and 1 if the input is active. the input argument 
** is the bitmask for the requested inp.
**
*/

unsigned char inp_state(unsigned char inp_bit)
{
    if(inp_status & inp_bit)
    {
        return(1);
    }
    return(0);
}

/*
**
** function to flush the inp input
** true/false status for a bit and make that inp 
** look inactive. the input argument 
** is the bitmask for the requested inp.
**
*/

void inp_clear(unsigned char inp_bit)
{
    inp_true &= ~inp_bit;
    inp_false &= ~inp_bit;
}

/*
**
** function to check for specific inp status 
** indicating a queued TRUE transition of a inp. 
** if so then to return a 1 value, else
** return a 0 value. the input argument 
** is the bitmask for the requested inp.
**
*/

unsigned char inp_true_test(unsigned char inp_bit)
{
    if(inp_true & inp_bit)
    {
        return(1);
    }
    return(0);
}

/*
**
** function to get input status for a inp bit 
** indicating a queued TRUE transition of a inp 
** and then clear the queued true status. if the inp
** is queued true then return a 1 value, else return 
** a 0 value. the input argument is the bitmask 
** for the requested inp.
**
*/

unsigned char inp_true_get(unsigned char inp_bit)
{
    if(inp_true & inp_bit)
    {
        inp_true &= ~inp_bit;  /* clear the status */
        return(1);
    }
    return(0);
}

/*
**
** function to check for specific input status 
** indicating a queued FALSE transition of a  
** inp. if so then to return a 1 value, else 
** return a 0 value. the input argument is the 
** bitmask for the requested inp.
**
*/

unsigned char inp_false_test(unsigned char inp_bit)
{
    if(inp_false & inp_bit)
    {
        return(1);
    }
    return(0);
}

/*
**
** function to get inp status for a specified bit 
** indicating a queued FALSE transition of a inp
** and then clear the queued false status. if the input
** is queued false then this so then to return a 1 value,
** else to return a 0 value. the entry argument is a bit 
** number 0-7 to look at.
**
*/

unsigned char inp_false_get(unsigned char inp_bit)
{
    if(inp_false & inp_bit)
    {
        inp_false &= ~inp_bit;  /* clear the status */
        return(1);
    }
    return(0);
}
\$\endgroup\$
  • \$\begingroup\$ 40ms is getting to the edge of acceptable delays, though, from a playability standpoint. \$\endgroup\$ – Scott Seidman Feb 24 '15 at 16:39
  • \$\begingroup\$ @ScottSeidman - The code I posted was a sample from some other application. One is free to set the sampling rate as fast as the residual bandwidth of the MCU can support and then set the debounce filter depth accordingly. Each type of switch and application will have its own set of unique requirements. One beauty of the software approach is how easily it can be adjusted to the requirements at hand. \$\endgroup\$ – Michael Karas Feb 24 '15 at 21:13
1
\$\begingroup\$

I am going to assume are reading all of the keys by feeding them in parallel into shift registers, and then clocking them out.

You need to debounce them as they are latched into the shift registers; otherwise if their is noise from a key the wrong state could be latched and then shifted out.

There are lots of circuits around for debouncing switches; this is considered one of the best:

enter image description here

For more information, see "A Guide to Debouncing".

\$\endgroup\$
  • 2
    \$\begingroup\$ Sorry to say but your claim that all the switches must be debounced before loading into the shift register is decidedly not true. \$\endgroup\$ – Michael Karas Feb 24 '15 at 6:51
  • \$\begingroup\$ Oh and by the way. An RC filter switch debouncer can be a suitable method should one decide to use hardware for debouncing switches but it is not one of the best. The best debouncer still uses a two throw switch and a set/reset flip flop. \$\endgroup\$ – Michael Karas Feb 24 '15 at 6:54
  • \$\begingroup\$ @MichaelKaras Whether or not you need to do the debouncing before or afte depends on the scan rate. \$\endgroup\$ – tcrosley Feb 24 '15 at 7:06
  • \$\begingroup\$ @MichaelKaras Re the RC filter, that's why I said it is one of the best. I agree the latch is best, but it uses more hardware. It is the first one mentioned in the document I linked to. \$\endgroup\$ – tcrosley Feb 24 '15 at 7:10
1
\$\begingroup\$

If all key-bounce events happen quickly, there is no harm in feeding the key switches directly to the shift registers and letting software take care of them. Indeed, in cases where switch bounce events will be shorter than the minimum time resolution of interest, slow polling will take care of switch bounce "automatically" [if a key is pressed just before a scan, but switch bounce causes it to be read as not pressed, it will simply be perceived as having been pushed one polling cycle later than it actually was]. For an organ, it's probably desirable to avoid quantizing key events too coarsely, so "vertical bit processing" as described in the other answer may be good.

Something like an organ keyboard, however, may have switch-bounce problems that cannot be resolved purely in software. The difficulty is that some switches may have a resistance which varies irregularly with position; if pushed sufficiently slowly, this process could be spread out over an arbitrarily-long time. If one uses suitably-sized pull-up resistors, this problem may be solved by feeding the switches into Schmidt-trigger buffers in advance of the shift registers. If one uses that approach, it may be good to arrange things so that when driven with full VDD the pull-ups would be a little "stiffer" than desired, but instead of connecting them to VDD connect them to an adjustable voltage.

Alternatively, depending upon the type of organ the keyboard is coming from, it may be desirable to power the switches from a higher-voltage supply, pass them through a resistor, and diode-clamp them to a voltage which is acceptable to the Schmidt-trigger buffer (one would need a passive pulldown in that case). From what I understand, some silver-alloy switches may tarnish so as to have significant resistance, but applying enough voltage to force current through them will help break down the tarnish. Such switches may initially work fine when connected to low-voltage/low-current logic, but performance may degrade with time. Something like an electropneumatic pipe organ keyboard would switch 24 volts or so with significant current; a Conn Minuet electronic (vacuum-tube based) organ keyboard would switch less current, but at 90 volts.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.