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.
Your app will be able to:
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.
A resistor with a resistance from 330 to 1000 ohms will also work. (This is just to protect the LED).
If you do not have some of the components you can just skip the section of the codelab.
Circuit picture
If this is your first time setting up a Pi see this on how to use ssh for remote control of your pi.
Configure your Pi by following these instructions:
sudo raspi-config
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)Performance Options -> GPU Memory
and enter 64
.raspi-config
.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
sudo reboot
flutter create flutter_pi_codelab
flutter pub add flutterpi_tool
flutterpi_tool devices add [<user>@]<ip / hostname> [--id=<device id>]
--id=<device id>
parameter is specified, the id will be the IP address or hostname.flutterpi_tool devices add pi@pi5
flutterpi_tool run -d <device-id>
.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.
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.
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.
(LED Schematic)
You will be using flutter_gpiod to control the LED.
flutter pub add flutter_gpiod
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:
chip name: gpiochip0, chip label: pinctrl-bcm2835
line: GpioLine(info: LineInfo(name: 'ID_SDA', consumer: '', direction: input, bias: disable, isUsed: false, isRequested: false, isFree: true))
line: GpioLine(info: LineInfo(name: 'ID_SCL', consumer: '', direction: input, bias: disable, isUsed: false, isRequested: false, isFree: true))
line: GpioLine(info: LineInfo(name: 'GPIO2', consumer: '', direction: input, bias: disable, isUsed: false, isRequested: false, isFree: true))
...
line: GpioLine(info: LineInfo(name: 'GPIO23', consumer: '', direction: input, bias: disable, isUsed: false, isRequested: false, isFree: true))
chip name: gpiochip1, chip label: raspberrypi-exp-gpio
line: GpioLine(info: LineInfo(name: 'BT_ON', consumer: 'shutdown', direction: output, bias: disable, isUsed: true, isRequested: false, isFree: false))
line: GpioLine(info: LineInfo(name: 'WL_ON', consumer: '', direction: output, bias: disable, isUsed: false, isRequested: false, isFree: true))
...
Taking a look at the output from this command it is easy to see that GPIO23 is connected to gpiochip0. Define two global variables gpioChipName
and ledGpioLineName
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.
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()
.
consumer
parameter is in essence just a debug label that you can use to identify who is using the GPIO line.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;
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.
_ledLine?.requestOutput(
consumer: 'flutterpi_codelab',
initialValue: _ledState,
);
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 the specifications, if the default pull is high you will need to add bias: Bias.pullDown
to the requestInput()
method. This is because the circuit requires the GPIO pin to be pulled low when the button is not pressed.
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 about debouncing you will need to manually implement a delay to counteract this, a future version of flutter_gpiod might support this.
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.
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.
SUBSYSTEM=="pwm*", PROGRAM="/bin/sh -c '\
chown -R root:gpio /sys/class/pwm && chmod -R 770 /sys/class/pwm;\
chown -R root:gpio /sys/devices/platform/soc/*.pwm/pwm/pwmchip* && chmod -R 770 /sys/devices/platform/soc/*.pwm/pwm/pwmchip*\
'"
6. Save and exit the file.
7. Reboot your Pi:sudo reboot
If this does not work you check out this documentation.
You will be using dart_periphery for this step so add that to you app.
flutter pub add dart_periphery
import 'package:dart_periphery/dart_periphery.dart';
In dart_periphery’s documentation it says that it makes use of the c-periphery library. You will need to use the setCustomLibrary(String absolutePath)
provided by dart_periphery. To install c-periphery as a shared library on your pi. (see: https://github.com/vsergeev/c-periphery#shared-library)
Quick install guide:
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
.
Now you can wire up the led to ground and the PWM enabled pin. In this example pin GPIO18
is used.
PWM Schematic
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:
ListTile(
title: const Text('PWM duty cycle'),
subtitle: Slider(
min: 0,
max: 1,
value: _dutyCycle,
onChanged: (value) {
setState(() {
_dutyCycle = value;
});
_pwm.setDutyCycle(value);
},
),
),
Restart the app and you are now be able to adjust the brightness of the LED using the slider.
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 will need to enable I2C on your Pi.
sudo raspi-config
.Interfacing Options
.I2C
.sudo reboot
.If you are running into issues you can check out this documentation and/or this documentation.
You can now connect your tiny rtc module according to the schematic below.
RTC Schematic
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:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
Check that there is something at address 0x68
as this is the default address of the DS1307 chip.
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.
ds1307.dart
in the lib/src
directory.import 'src/ds1307.dart';
Create a const value to declare the i2cBus number:
/// The bus number of the rtc [I2C] device.
const int i2cBus = 1;
Create variables for the i2c and DS1307 instances in your MyHomePage
:
late final I2C _i2c;
late final DS1307 _rtc;
Then instantiate them in your initState()
method:
// Create a new I2C instance on bus 1.
_i2c = I2C(i2cBus);
// Create a new TinyRTC instance.
_rtc = DS1307(_i2c);
And dispose of the I2C instance in your dispose()
method
// Dispose of the I2C instance.
_i2c.dispose();
You can use this widget to read/adjust the time on the DS1307 chip:
real_time_clock_widget.dart
in the lib/src
directory.Then just add the widget to your MyHomePage
:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Pi Codelab')),
body: ListView(
children: <Widget>[
...
RealTimeClockWidget(rtc: _rtc),
],
),
);
}
After restarting the app you are now be able to read/adjust the time on the DS1307 chip.
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:
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