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.