Positron8 - Reading Multiple Buttons using a Single Pin on the microcontroller

Started by top204, Feb 26, 2024, 03:28 PM

Previous topic - Next topic

top204

A simple method for reading multiple buttons with only a single pin, is to use an analogue method. Where each button will give a different value to an ADC input when it is pressed, and whatever value is read can be translated to a certain button pressed. I've used this method many times, and on a PIC device that does not contain an ADC, the RCIn command can be used instead.

I've just written a simple demonstration that shows exactly how this works, and it is listed below for a PIC16F628A device. I guessed at the resistor values, but made them approx double resistance to each other so the values returned from the RCIn command would have large differences for each button.

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Wait for button presses and transmit what button was pressed to a serial terminal
' The three buttons are attached to a single pin via series resistors of different values,
' so each button will give a different reding to the RCin command.
'
' Written for the Positron8 compiler by Les Johnson.
'
    Device       = 16F628A                                      ' Tell the compiler what device to compile for
    Declare Xtal = 20                                           ' Tell the compiler what frequency the device will be operating at (in MHz)
'
' Setup USART1
'
    Declare Hserial1_Baud = 9600                                ' Set the Baud rate to 9600
    Declare HRSOut1_Pin   = PORTB.2                             ' Set the TX pin

    Symbol Buttons_Pin = PORTA.0                                ' The pin to use for the Buttons
'
' Create any global variables fro the demo here
'
    Dim bButtonPressed As Byte                                  ' Holds the button pressed value

'-------------------------------------------------------------------------
' The main program starts here
' Wait for button presses and transmit what button was pressed to a serial terminal
'
Main:
    HRsoutLn "Press a Button"
    Do                                                          ' Create a loop
        bButtonPressed = Button_Get()                           ' Read the buttons
        If bButtonPressed = cUp_Button Then                     ' Was the Up Button pressed?
            HRsoutLn "Up Button Pressed"                        ' Yes. So transmit to a serial terminal what button was pressed
        ElseIf bButtonPressed = cMenu_Button Then               ' Was the Menu Button pressed?
            HRsoutLn "Menu Button Pressed"                      ' Yes. So transmit to a serial terminal what button was pressed
        ElseIf bButtonPressed = cDown_Button Then               ' Was the Down Button pressed?
            HRsoutLn "Down Button Pressed"                      ' Yes. So transmit to a serial terminal what button was pressed
        EndIf
        DelayMs 50
    Loop                                                        ' Do it forever

'-------------------------------------------------------------------------
' Read a button press from an analogue sequence of buttons connected to a single pin
' Input     : Buttons_Pin holds the Port.Pin used for the buttons
' Output    : Returns a value that represents what button is pressed (0 to 3)
' Notes     : Only allows a single button press, until it is released.
'           : This stops multiple repeats if the button is pressed for too long, and acts as a form of debounce.
'
Proc Button_Get(), Byte
Global Symbol cNo_Button   = 0                                  ' The return value for No Button pressed
Global Symbol cUp_Button   = 1                                  ' The return value for the Up Button pressed
Global Symbol cMenu_Button = 2                                  ' The return value for the Menu Button pressed
Global Symbol cDown_Button = 3                                  ' The return value for the Down Button pressed

Static Dim bPrevButton As Byte = $FF                            ' Holds the previous button value
    Dim wRCTime As Word                                         ' Holds the value returned from the RCin command
    Dim bButton As wRCTime.Byte0                                ' Holds the button value (aliased)
'
' Read the buttons
'
    PinHigh Buttons_Pin
    DelayMs 1                                                   ' Wait for 1 ms
    wRCTime = RCin Buttons_Pin, High                            ' Measure the RC charge time
    'HRsoutLn Dec wRCTime                                   ' Uncomment to see the RC Time value for each button pressed
'
' Choose what button was pressed by the value returned from RCin
'
    Result = cNo_Button                                         ' Default to no button pressed
    Select wRCTime                                              ' Do comparisons on the value held in wRCTime
        Case 550 To 700                                         ' Does wRCTime holds the values between 500 To 700?
            bButton = cUp_Button                                ' Yes. So that is the Up Button pressed
        Case 280 To 400                                         ' Does wRCTime holds the values between 280 To 400?
            bButton = cMenu_Button                              ' Yes. So that is the Menu Button pressed
        Case 100 To 220                                         ' Does wRCTime holds the values between 100 To 220?
            bButton = cDown_Button                              ' Yes. So that is the Down Button pressed
        Case Else                                               ' Otherwise...
            bButton = cNo_Button                                ' No Button pressed
    EndSelect
    If bButton <> bPrevButton Then                              ' Is the button the same as the previous button pressed?
        Result = bButton                                        ' No. So place the button pressed value in the result
        bPrevButton = bButton                                   ' Update the previous button press variable
    EndIf
EndProc

Running it in the Proteus simulator gives good results, and a screenshot of it operating inside Proteus is shown below. To get the values returned from the RCin command for particular resistors used, uncomment a line in the code and it will transmit the values received for a button press to a serial terminal, then use that value for a particular button choice within the Button_Get() procedure, then re-comment the line when they are chosen. The Button_Get() procedure also has a simple mechanism for a one-shot button press to stop repeats if the button is left pressed down. It also acts as a simple form of debounce. :-)

The Positron8 source code and the proteus project files are also attached below. It is named "Analogue_Buttons.zip".

Analogue_Buttons_1_PIC16F268A.jpg

SeanG_65

I did something very similar years ago, using comparators to triger different IRQ lines on multiple micro controllers. Each had a specific task and some took a long time, hence the need for multiple processors. Worked very reliably.

Fanie

Clever way to get away without an A/D converter.

top204

It is not using the ADC peripheral, it is using the RCIn command. Which is, in the layout, measuring the length of time it takes to discharge the capacitor. Hence the different values of resistors that will discharge it at different rates.

I wrote it for users of the old 14-bit core devices that did not have an ADC peripheral on them.