Button-controlled LED
The button is a commonly used input component and widely used in a light switch, remote control, keyboard, etc, to control the current flow in the circuit. Now it’s your turn to build a LED light control using a button.
Learning goals
- Know about digital input.
- Understand how to use a button in your circuit.
- Learn about the pull-up and pull-down resistor.
- Get a rough idea of interrupt and debounce.
🔸Background​
What is digital input?​
The microcontroller could not only send output voltage but also receive signals. You learned about digital output in the previous tutorial, and now you will discover how digital input works.
Like digital output, digital input has two states: high and low. When an input signal comes in, its voltage will be compared with specific thresholds. If the voltage higher than the threshold, it’s high, and if it’s lower, it’s low. You can regard it as a multimeter used to measure voltage, but only has two results.
According to the input value, you could easily know the exact state of the external devices, for example, check if the button is pressed or not.
As mentioned before, the digital pins on the board act as both input and output, you need to initialize the pin as input in your code.
🔸New component​
Pushbutton​
Pushbutton is known as a momentary switch. In the simplified circuit below, by default, the circuit inside the button is disconnected, therefore block the current flow. As you press it, the internal metals comes into contact, so the current can flow successfully through the circuit. Once you release it, the button will go back to its original state and the current is blocked again.

Symbol:
This kind of button usually has four legs. Each two of them are shorted, like 1 and 3, 2 and 4 in the following diagram. If you connect the two legs that are shorted internally, like 1 and 3, the button is of no use since the current flows all the time.
To make things easier, it is a good idea to connect the two legs on a diagonal, like 1 and 4, 2 and 3.
Pull resistor​
As you have known, the input will always be either high or low. But if the input pin is connected to nothing, what will the input value be? High or low?
That's hard to say. The state of that pin will be uncertain and change randomly between high and low states. This state is called floating. To ensure a stable state, a pull-up or pull-down resistor is needed.
Pull-up resistor
The pull-up resistor connects the input pin to power. In this case, the button should connect to the input pin and ground.
By default, when the button is not pressed, the input pin reads actually the power voltage which is high. If the button is pressed, the current flows from power directly to the ground and the pin reads low level.
Pull-down resistor
The pull-down resistor connects the input pin to ground. If so, the button should connect to the power and input pin.
By default, the pin connects directly to the ground, so the pin keeps in a low state. And if you press the button, the pin reads power voltage which is high.

In this way, the input pin is always at a determined state.
You would usually need pull-up or pull-down resistor with a button. The SwiftIO Feather board has internal pull-up and pull-down resistors. By default, the pull-down resistor is chosen. You could change it according to actual usage.

This is a simplified version for better understanding.
🔸Circuit - button module​
There are two buttons on your kit. They are connected respectively to D1 and D21.


The circuits above are simplified for your reference.
🔸Preparation​
Let’s see the new class you are going to use in this project.
Class
DigitalIn
- this class allows to get current state of a specified input pin.
Method | Explanation |
---|---|
init(_:mode:) | Initialize the digital input pin. - id : the id of the digital pin. - mode : set the pull resistors, pullDown by default. |
read() | Read voltage level from an input pin. - Return value - the voltage represented as true or false . |
setInterrupt(_: enable:callback:) | It allows you to define the interrupt mechanism in your project. - mode : set the type of edge to trigger the interrupt: .rising , .falling , .bothEdges . - enable : decide whether to enable the interrupt once you set it. By default, it is true (enabled). - callback : pass in a function as the paramter. The function should needs no parameter and returns nothing: () -> void. |
🔸Projects​
1. LED switch​
Let’s start to build a simple LED switch. When you press the button, the LED turns on. When you release the button, the LED turns off.

Example code
// Import the SwiftIO library to control input and output and the MadBoard to use the id of the pins.
import SwiftIO
import MadBoard
@main
public struct C01S02ButtonLED {
public static func main() {
// Initialize the input pin for the button and output pin for the LED.
let led = DigitalOut(Id.D19)
let button = DigitalIn(Id.D1)
// Keep repeating the following steps.
while true {
// Read the input pin to check the button state.
let value = button.read()
// If the value is true which means the button is pressed, turn on the LED. Otherwise, turn off the LED.
if value == true {
led.write(true)
} else {
led.write(false)
}
}
}
}
Code analysis
let button = DigitalIn(Id.D1)
The button is an instance created for the DigitalIn
class. As usual, the id of the pin is necessary. It’s the pin the button connects: D1.
And there is an optional parameter mode
with a default value pullDown
, which means a pull-down resistor connects to the pin. There are two more options: pullUp
(pull-up resistor), pullNone
(no pull resistors).
button.read()
Use the instance method read()
to get the state from the pin. The return value tells you if it is high or low voltage. The value true
means the button is being pressed.
if value == true {
led.write(true)
} else {
led.write(false)
}
The microcontroller will judge the value read from the pin. When the value equals true, it means the button is pressed, so make the board output a high voltage to turn on the LED.
You might notice two similar symbols are so confusing: = and ==.
Well, = is to assign value to a constant or variable, while == compares if values are equal.
2. LED switch using interrupt​
In the previous project, the microcontroller is doing nothing but checking the input value over and over again to wait button press. But what if you want to do something else in the loop? When should the microcontroller check the pin state? It’s hard to decide.
So there comes another important mechanism - interrupt for the digital input.
Interrupt
The interrupt allows the microcontroller to respond quickly to a specified event. How does it work?
- Normally the microcontroller executes its main program.
- Once the interrupt occurs, it will suspend the normal execution and then start the task with higher priority, called interrupt handler or ISR (Interrupt Service Routine).
- After finished ISR, the microcontroller goes back to where it stopped and continues the main program until another interrupt.
There are two points about the ISR:
- Generally, the ISR should be done as fast as possible, usually in nanoseconds.
- The functions don't need any parameters and return anything.
In short, it's better to change a value or toggle digital output for the ISR. And you should not add print()
which take about several milliseconds. Or your program may go wrong.
An interrupt may come from different sources. Now you are dealing with an interrupt triggered by the state change. There are three conditions in total:
- Rising edge: when the signal changes from low to high.
- Falling edge: when the signal changes from high to low.
- Both of them.
So when setting the interrupt, you need to tell the microcontroller the specific condition to trigger it: rising edge, falling edge, or both edges. Then once the specified edge is detected, the interrupt happens.
With interrupt mechanism, the microcontroller can
- respond instantly to what it is supposed to do, which is vital for time-critical events.
- perform other tasks while the interrupt hasn't been triggered, thus increasing the efficiency.
Example code
// Import the SwiftIO library to control input and output and the MadBoard to use the id of the pins.
import SwiftIO
import MadBoard
@main
public struct C01S02ButtonInterrupt {
public static func main() {
// Initialize the input pin for the button and output pin for the LED.
let button = DigitalIn(Id.D1)
let led = DigitalOut(Id.D19)
// Define a new function used to toggle the LED.
func toggleLed() {
led.toggle()
}
// Set the interrupt to detect the rising edge. Once detected, the LED will change its state.
button.setInterrupt(.rising, callback: toggleLed)
// Keep sleeping if the interrupt hasn't been triggered.
while true {
sleep(ms: 1000)
}
}
}
Code analysis
func toggleLed() {
led.toggle()
}
This newly declared function would be passed as parameter for the interrupt. It needs no parameter and don't have return values, which meets the requirement for the ISR.
toggle()
can reverse the output from the current state to the other,from low to high, or from high to low.
Btw, changing the digital output level normally can be finished quickly, so it can be set as ISR.
button.setInterrupt(.rising, callback: toggleLed)
The interrupt will be triggered when a rising edge is detected. The rising edge here corresponds to the moment the button is pressed.
The third parameter callback
needs a specified type of functions: () -> void. It means the function has no parameters and return values.
This parameter calls the function toggleLed
defined above. Thus, once there is a rising edge, the microcontroller will come to this piece of code, then go to that function toggleLed
that callback
calls and finally know it needs to toggle the output. After finished, the microcontroller return to the previous task, in this case, sleep again.
Previously, you invoke them to do some work. Actually, a function can accept another function as its parameters, as long as the passed-in function follows the specified pattern.
Let's see an example!
func add1(_ num: Int) -> Int {
return num + 1
}
func multiply3(_ num: Int) -> Int {
return num * 3
}
func arithmetic(num: Int, formula: (Int) -> Int) -> Int {
return formula(num)
}
var value = 5
// 6
value = arithmetic(num: value, formula: add1)
// 18
value = arithmetic(num: value, formula: multiply3)
The function arithmetic
needs a number and a formula to compute the number. The formula
accepts an integer and returns an integer: (Int) -> Int. The other two functions add1
and multiply3
is in the same pattern. So arithmetic
can accept either them as a parameter, as well as other functions of this pattern. In this way, the function arithmetic
becomes general purpose and can apply different methods of computation.
while true {
sleep(ms: 1000)
}
It makes the board sleep while interrupt hasn't occurred. The sleep time can be a random value. Without it, the board will run extremely quickly again and again but do nothing.
🔸Go further​
Congratulations! Now let’s dive deeper into something more complicated. Don’t be upset if you are confused. This part talks about the signal noise produced by buttons. You could skip this part and go back later as you get more familiar with all knowledge.
Debounce​
When you press or release a button, you might think the button would immediately come to a stable state, closed or open. So the LED should always be on when pressing the button and turn off once the button is released. However, there may be some unexpected situations sometimes.
The button has metals inside it which will move as you press or release it. Due to this mechanical structure, there may be several bounces before the internal metals finally settle down. So once the button is pressed or released, it may change several times between two states: establish the connection and disconnect the circuit before coming to a stable state.
The signal would thus change several times during this short period. It is not visible to your eye. You could observe it by viewing the wave in the oscilloscope. But the microcontroller works much faster. It may read the values during this uncertain period and regard these noises as multiple presses.

So you need debounce methods to avoid this situation. There are many solutions, including hardware debounce and software debounce.
Hardware debounce
It lies in eliminating the influence of bounce when designing the circuit. So does the button module on this kit. Let's talk about one method used for the kit. There are also many other ways, of course.

A capacitor (C1 shown in the image below) is added to smooth the voltage changes. After the button is pressed or released, the voltage will gradually change to another level rather than accidentally change several times between the two levels.
Software debounce
It makes your board ignore the bounce and wait for the real state. Usually, you'll check many times or wait a few milliseconds to skip this unstable period. After this period, the value read from the pin should be the one needed. A fast press would at least last about 20ms. You could have a look at this reference code.
Since the button on the kit uses a hardware debounce method, you will not meet this problem. But it is still an important phenomenon when you DIY some projects using buttons.
🔸More info​
Find out more details in the links below if you are interested: