Coming from a world of pure software development, I had always taken it for granted just how high-level most of my code was. All the functions that I was using were predefined by someone else who did so much of the heavy lifting that I never really needed to understand what was actually happening underneath the hood.
But it isn’t just the imported libraries that I was taking for granted, it was also the mindset a software programmer employs. Take, for example, the simple implementation of a button in Python using the PyQT5 library, as shown below.
b1 = QtWidgets.QPushButton(win)
There isn’t a whole lot to pick apart from this snippet, but we see that an instance of a button object is created, and a boolean function that checks if the instance has been clicked by the user or not is tied to it. If it is clicked, then the button is connected to another function explicitly defined somewhere in the program above this snippet of code that responds to a “clicked” event.
That’s essentially all that’s happening here. But, what exactly does it mean to “click” a “button”? Click the left mouse button with the cursor on top of the software button? Or, to touch the part of the screen that has the button if the user is using a touchscreen? Or a mousepad click? It’s time to remove all assumptions and start from scratch. Time to approach this using first principles.
I took my first embedded systems course for my electrical engineering program during the Fall semester of ’21. As part of the first lab assignment, we were tasked to create a door lock system that would accept a sequence of button presses to check if the user is authorized to enter or not. The door lock had three buttons and we were free to pick which element of the sequence corresponded to which button. So it went something like this…
I’m not going to get into how I implemented the entire system just yet. Instead, there’s a very important shift in mindset that’s needed in order to approach a physical pushbutton that has some functionality attached to it. And if you don’t understand the underlying mechanics of how one button’s supposed to work, you can forget about ever implementing three.
What Does It Mean To Push a Button?
The essence of an input to a microcontroller (the brain of an embedded system) is a closed circuit. A microcontroller, such as the ATtiny2313A used in some of my projects, has several pins that can be configured as either input or output pins.
The way to configure a pin as input or output is by using a Data Direction Register (DDRx). A microcontroller will have several ports denoted by letters such as A, B, etc. Each of these ports will contain about 8 pins. The first step will be to pick a pin for your input and another for your output. Let’s use Pin 1 of Port B as output, and Pin 4 of Port D as input. As you can see, this choice is completely arbitrary so long as everything is consistent in your firmware code.
The type of operation happening here is a bitwise operation where you move a 0 (~1) to a register that corresponds to the pin to let the microcontroller know that the pin is to accept input, and a 1 to the register to let the microcontroller know that the pin is for output. Why 0 for input and 1 for input? This is according to the datasheet of the ATtiny2313A datasheet.
With a simple schematic of a circuit like the one shown above in mind, we should be able to implement the rest of the logic. We can check for the input and depending on the boolean value, send a 1 (closed circuit) to the output pin. The input function should look something like this…
And the output function, something like this…
In the input function, I’ve decided to return “1” to give an arbitrary pushbutton some form of representation or alias. A pushbutton is pressed, causing the circuit to be complete and a current to flow into the pin, and a high voltage to be read (high = 1, low = 0). The while loop ensures that 1 is only returned after the button has been released and not while the button is still being pressed.
In the output function, we see that once the program detects the press of “button 1”, the output port can be flipped allowing for a current to pass through the LED and into the ground.
But (there’s always a but) there is a problem with this implementation. And it has to do with what’s called a floating pin. In reality, this is what’s happening in the simple schematic shown above.
What you’re seeing is noise from the surroundings that interfere with the circuit causing it to be in the open and close state at random. I remember implementing this code without accounting for noise and showing it to my roommate, and how the circuit would start acting differently as soon as he’d walk into the room. It wasn’t like the behavior of the circuit wasn’t unpredictable before that, but the influx of noise that was caused by another person entering the room would temporarily make the circuit go haywire.
To combat noise, microcontrollers come with what’s known as a pull-up resistor. This is what it looks like (sort of…)
When the button isn’t being pushed, the pin is at a voltage of 5V since there isn’t any current across the pull-up resistor, and the pin is at a logic state of high. But when the button is pushed, the current takes the path of least resistance and travels to the ground, causing the pin to now be at a voltage of 0V, a low logic state. This means “button press = 0; else 1” which goes against our initial intuition, but something that can be accounted for programmatically, pretty easily.
The following changes need to be made in order to make use of the pull-up resistor.
And that wraps it up! This is what it takes to implement a button in hardware programming. One thing you’ll have probably noticed is that the main function has an infinite loop inside. The reason for this is that these programs need to be running until the main power is switched off. Once all the initializations are taken care of and the program can graduate to its main functionality, it needs to be in there — looking for new input signals and making decisions all in real-time — until the hardware itself is turned off. This, too, was another key difference I noticed between software and firmware programming.
The next post will be an extension of this, where I show how I implemented a door lock system that makes use of three buttons. This is exactly why it was so important to get this first basic firmware right, as it will set the basis for everything to be built on top of. If you don’t understand how one button works, you can forget about making use of three.