In this codelab, you will create an interactive Flutter app that runs on a Raspberry Pi using the custom embedder flutter-pi. This app will demonstrate how to control hardware components like LEDs and buttons through GPIO pins, dim an LED using PWM, and read the time from an RTC module using I2C.
How to run a flutter app on your Pi using flutter-pi.
How to use GPIO in flutter/dart.
How to use PWM in flutter/dart.
How to use I2C in flutter/dart.
This codelab is focused on flutter and flutter-pi. Non-relevant concepts and code blocks are glossed over and are provided for you to simply copy and paste.
If this is your first time setting up a Pi see this on how to use ssh for remote control of your pi.
Setup flutter-pi
Configure your Pi by following these instructions:
sudo raspi-config
Switch to console mode System Options -> Boot / Auto Login then pick Console or Console Autologin
Advanced Options -> GL Driver -> GL (Fake KMS) (You can skip this if you're on Raspberry Pi 4 with Raspbian Bullseye)
Configure the GPU memory Performance Options -> GPU Memory and enter 64.
Leave raspi-config.
Give the pi permission to use 3D acceleration. (NOTE: potential security hazard. If you don't want to do this, launch flutter-pi using sudo instead.) sudo usermod -a -G render pi
if no explicit --id=<device id> parameter is specified, the id will be the IP address or hostname.
example: flutterpi_tool devices add pi@pi5
Then run flutterpi_tool run -d <device-id>.
3. Control an LED with GPIO
GPIO (General-purpose input/output) refers to pins on a microcontroller or computer board that can be configured and controlled by the user. These pins can be set to either a high (voltage) state or a low (ground) state, or can be read to detect input signals. In this context, we will be using GPIO pins to control and light up an LED.
Connect the LED to your Pi
First you need to connect the LED to your Pi’s GPIO Header. You can have a look at the documentation to find a GPIO pin that will work for you.
40-Pin Header
For this example I will be using GPIO23 but you can use another GPIO pin. You should add a resistor to limit the amount of current flowing through the LED. In this example I’m using a 330-ohm resistor in series with the LED.
Add flutter_gpiod to your app with: flutter pub add flutter_gpiod
Import flutter_gpiod in your main.dart import 'package:flutter_gpiod/flutter_gpiod.dart';
flutter_gpiod has a very useful way to find the correct GPIO chip. You can list all available GPIO chips along with their GPIO lines/pins by running the following code in your main method:
final chips = FlutterGpiod.instance.chips;
for (final chip in chips) {
print("chip name: ${chip.name}, chip label: ${chip.label}");
for (final line in chip.lines) {
print(" line: $line");
}
}
When starting your app you can expect the above code to print something like this:
Taking a look at the output from this command it is easy to see that GPIO23 is connected to gpiochip0. Define two global variablesgpioChipName andledGpioLineName in your main.dart.
/// The name of the GPIO chip that the LED is connected to.
const gpioChipName = 'gpiochip0';
/// The name of the GPIO line that the LED is connected to.
const ledGpioLineName = 'GPIO23';
Define two new variables in MyHomePage_chip and _ledLine.
/// The GPIO chip that the LED is connected to.
late final GpioChip _chip;
/// The GPIO line that the LED is connected to.
late final GpioLine _ledLine;
Now in your init state you want to assign the chip and line to the variables.
@override
void initState() {
super.initState();
// Retrieve a list of GPIO chips attached to the system.
final chips = FlutterGpiod.instance.chips;
// Find the GPIO chip with the label _gpioChipLabel.
_chip = chips.singleWhere((chip) {
return chip.name == gpioChipName;
});
// Find the GPIO line with the name _ledGpioLineName.
_ledLine = _chip.lines.singleWhere((line) {
return line.info.name == ledGpioLineName;
});
}
Following flutter_gpiod’s example for controlling a GPIO line you first need to request ownership of it, as we will be switching an led on/off we need to request it as an output, you can do this by calling the requestOutput() method on the _ledLine in initState().
The consumer parameter is in essence just a debug label that you can use to identify who is using the GPIO line.
The initialValue parameter is the initial value of the GPIO line, in this case, we want the LED to be off initially so we set it to false.
// Request control of the GPIO line as an output.
_ledLine.requestOutput(
consumer: 'flutterpi_codelab',
initialValue: false,
);
You also need to release ownership of the _ledLine when you are done using it, so use the release() method on the _ledLine in the dispose() method.
// Release control of the GPIO line.
_ledLine.release();
Now create a SwitchListTile to turn the button on/off. For this you will need to create a boolean _ledState so the SwitchListTile knows in what state the LED is in.
/// The state of the LED. (true = on, false = off)
bool _ledState = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Pi Codelab')),
body: ListView(
children: <Widget>[
SwitchListTile(
title: const Text('LED Switch'),
value: _ledState,
onChanged: (value) {
setState(() {
// Update the state of the LED.
_ledState = value;
});
// Set the value of the GPIO line to the new state.
_ledLine.setValue(value);
},
),
],
),
);
}
Now you can start your app and turn the LED on/off by toggling the SwitchListTile.
Due to issues with flutter_gpiod and hot-restart you will need to do a full restart of the app. On hot-restart_ledLine.release is never executed, so the LED line will be seen as in-use. During a hot-restart flutter_gpiod loses its internal state and needs to regain control of the_ledLine, but the request is denied because the line is still in use.
Because you will be turning the led on/off later in this codelab, extract the onChanged() method into a separate function.
void _updateLED(value) {
setState(() {
// Update the state of the LED.
_ledState = value;
});
// Set the value of the GPIO line to the new state.
_ledLine.setValue(value);
}
Also update the requestOutput() method to set the initial value of the LED.
Decide which GPIO pin you want to use for the button, in this example GPIO24 will be used, and connect your button to it as shown in the schematic below.
Button Schematic
If you decide to use a different gpio pin with this circuit you will need to check thespecifications, if the default pull is high you will need to addbias: Bias.pullDown to therequestInput() method. This is because the circuit requires the GPIO pin to be pulled low when the button is not pressed.
Detect the button press
Start by adding a new global variable buttonGpioLineName that matches the name of the GPIO pin you want to use.
/// The name of the [GpioLine] that the button is connected to.
const buttonGpioLineName = 'GPIO24';
Define a new variable _buttonLine in MyHomePage.
/// The GPIO line that the button is connected to.
late final GpioLine _buttonLine;
In the initState() method assign the chip and line to the variables.
// Find the GPIO line with the name _buttonGpioLineName.
_buttonLine = _chip.lines.singleWhere((line) {
return line.info.name == buttonGpioLineName;
});
Next you need to request ownership of it, in this case you are going to use it as an input, and request ownership with the requestInput() method on the _buttonLine. The request input has a triggers parameter, this is what will determine the type of event(s) the _buttonLine.onEvent stream will emit. You have the choice of one or both of the following triggers:
// Rising means that the voltage on the line has risen from low to high.
SignalEdge.rising;
// Falling means that the voltage on the line has dropped from high to low.
SignalEdge.falling;
You can now request the GPIOline as an input in your initState() method with the following code:
// Request control of the _buttonLine. (Because we are using the line as an input use the requestInput method.)
_buttonLine.requestInput(
consumer: 'flutterpi_codelab',
triggers: {
SignalEdge.rising,
SignalEdge.falling,
},
);
And release the line in the dispose() method of the MyHomePage.
_buttonLine.release();
You can now listen to the _buttonLine.onEvent and print the event in the console, by adding this to your initState() method:
// Listen for signal events on the _buttonLine.
_buttonLine.onEvent.listen(
(event) => print(event),
);
Make sure that you are detecting the button press by restarting the app and pressing the button, you should see the event being printed in the console. (If not make sure the button is connected correctly).
signal event SignalEvent(edge: falling, timestamp: 0:37:03.476893, time: 2024-06-19 14:39:20.301019)
signal event SignalEvent(edge: rising, timestamp: 0:37:03.564660, time: 2024-06-19 14:39:20.388289)
signal event SignalEvent(edge: falling, timestamp: 0:37:04.507935, time: 2024-06-19 14:39:21.331576)
Now that you are detecting the button press you can use it to turn the LED on/off. You can do this by updating the _buttonLine.onEvent.listen() method to toggle the LED on/off when the button is pressed.
// Listen for signal events on the _buttonLine.
_buttonLine.onEvent.listen((event) {
switch (event.edge) {
case SignalEdge.rising:
_updateLED(true);
case SignalEdge.falling:
_updateLED(false);
}
});
Now restart your app and you should now be able to turn the LED on and off using the physical button.
If you are concerned aboutdebouncing you will need to manually implement a delay to counteract this, a future version of flutter_gpiod might support this.
5. Dim a LED with PWM
PWM (Pulse-width modulation) is a technique for representing a signal as a rectangular wave with a varying duty cycle and, in some cases, a varying period. By adjusting the duty cycle, the proportion of time the signal is high versus low, you are going to control the amount of power delivered to a LED.
Configuring your Pi
To use PWM you will need to add some lines to the config.txt
1. Open up the config.txt: sudo nano /boot/firmware/config.txt
2. Adding this line enables PWM support
dtoverlay=pwm,pin=18,func=2
3. Save and exit the file.
4. Open up 99-com.rules: sudo nano /etc/udev/rules.d/99-com.rules
5. Adding these lines, allows root:gpio to own the PWM pins.
git clone https://github.com/vsergeev/c-periphery.git
cd c-periphery
mkdir build
cd build
cmake -DBUILD_SHARED_LIBS=ON ..
make
sudo make install
This will install the c-periphery on your pi, take note of where it has placed libperiphery.so, in the case of the example it has been placed here /usr/local/lib/libperiphery.so.
Connect the LED to your Pi
Now you can wire up the led to ground and the PWM enabled pin. In this example pin GPIO18 is used.
PWM Schematic
Dim the LED
Start off by pointing dart_periphery to the library you installed earlier.
// Set the libperiphery.so path
setCustomLibrary("/usr/local/lib/libperiphery.so");
Create two new identifiers for the PWM chip and the PWM channel:
/// The [PWM] chip number.
const pwmChip = 0;
/// The [PWM] channel number.
const pwmChannel = 0;
Next add a variable _pwm to MyHomePage:
late final PWM _pwm;
And assign a PWM instance to the variable in the initState() method:
_pwm = PWM(pwmChip, pwmChannel);
Create two variables to define the behavior of the PWM signal:
/// The period of the PWM signal in seconds.
final double _periodSeconds = 0.01;
/// The duty cycle, this is the amount of time the signal is high for the given period.
double _dutyCycle = 0.05; // 5%
Now set up the PWM instance to use the period and duty cycle, in your initState() method:
// Set the period of the PWM signal.
_pwm.setPeriod(_periodSeconds);
// Set the duty cycle of the PWM signal.
_pwm.setDutyCycle(_dutyCycle);
// Enable the PWM signal.
_pwm.enable();
Remember to dispose of the PWM instance.
_pwm.dispose();
Start up the app and you should see that the LED is not as bright as it usually is. (If not make sure the LED is connected to the PWM pin).
You can now add a slider to adjust the brightness of the LED:
Restart the app and you are now be able to adjust the brightness of the LED using the slider.
6. Read the time from an RTC module (DS1307) using I2C
I2C (Inter-Integrated Circuit) is a communication bus used to connect lower-speed peripheral devices to processors and microcontrollers. It allows multiple devices to communicate with each other over short distances on the same board.
You can now connect your tiny rtc module according to the schematic below.
RTC Schematic
Finding the I2C adaptor
Finding the correct i2c adapter run the following command on your pi i2cdetect -l this will return a list of all the I2C adaptors on your device.
i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter
You can find out if any I2C devices are connected to an adapter by running i2cdetect -y <channel number>, on raspberry pi you would generally use the I2C adaptor 1 which is connected to the GPIO header.
Running i2cdetect -y 1 will result in something like this:
Check that there is something at address 0x68 as this is the default address of the DS1307 chip.
Reading the time
Because dart_periphery does not support the DS1307 chip (yet), you can use this minimal dart implementation to read/adjust the time on the DS1307 chip.
Create a new file ds1307.dart in the lib/src directory.
After restarting the app you are now be able to read/adjust the time on the DS1307 chip.
Conclusion
Congratulations! You've successfully completed the codelab and built a Flutter app that interfaces with hardware components on a Raspberry Pi. Throughout this project, you've learned how to:
Run a Flutter app on a Raspberry Pi using the custom embedder flutter-pi.
Control an LED and read a button press through GPIO pins.
Dim an LED using PWM for more nuanced control over your hardware.
Leave a Comment
Your Email address will not be published
KDAB is committed to ensuring that your privacy is protected.
For more information about our Privacy Policy, please read our privacy policy