0% found this document useful (0 votes)
285 views19 pages

Building A Custom Embedded Linux (Yocto) For The Luckfox Pico Ultra W

This guide details the process of building a custom embedded Linux image for the Luckfox Pico Ultra W board using the Yocto Project, including setting up a Docker-based build environment and integrating OTA update support with RAUC. The document outlines the specifications of the Luckfox Pico Ultra W, its hardware capabilities, and the necessary steps to configure Yocto for this specific board. It assumes a beginner-level familiarity with Linux and provides a comprehensive walkthrough for obtaining and configuring the required Yocto layers and recipes.

Uploaded by

leonino010
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
285 views19 pages

Building A Custom Embedded Linux (Yocto) For The Luckfox Pico Ultra W

This guide details the process of building a custom embedded Linux image for the Luckfox Pico Ultra W board using the Yocto Project, including setting up a Docker-based build environment and integrating OTA update support with RAUC. The document outlines the specifications of the Luckfox Pico Ultra W, its hardware capabilities, and the necessary steps to configure Yocto for this specific board. It assumes a beginner-level familiarity with Linux and provides a comprehensive walkthrough for obtaining and configuring the required Yocto layers and recipes.

Uploaded by

leonino010
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 19

Building a Custom Embedded Linux (Yocto) for the

Luckfox Pico Ultra W


Introduction
This guide provides a step-by-step walkthrough for building a custom embedded Linux image for the
Luckfox Pico Ultra W board using the Yocto Project. We will set up a build environment (using Docker on a
Debian Bookworm base), configure Yocto for the Pico Ultra W’s hardware, integrate RAUC for robust over-
the-air (OTA) updates, and ensure core interfaces (GPIO, I2C, SPI, USB) are supported. The Luckfox Pico Ultra
W is a low-cost micro Linux board based on the Rockchip RV1106 SoC – a single-core ARM Cortex-A7 (32-bit)
with NEON, FPU, and a built-in NPU (up to 1 TOPS) 1 . This board includes multiple interfaces (GPIO,
UART, SPI, I2C, USB, etc.) for expansion 2 , and it typically ships with a Buildroot or Ubuntu 22.04-based
image 2 . Here, we’ll learn to build our own Linux system with Yocto (for flexibility and learning purposes)
and set it up headless (no GUI) with OTA update support. The guide assumes a beginner-level familiarity
with Linux, so each concept is explained along the way.

Board Overview – Luckfox Pico Ultra W


The Luckfox Pico Ultra W is part of the Luckfox Pico RV1106 series. It features:

• Rockchip RV1106G2/G3 SoC – Cortex-A7 @1.2GHz, designed for AI and vision applications 1 . It has
specialized hardware like an ISP (image signal processor) and supports camera input, making it
suitable for IoT/AI projects.
• Memory and Storage – 128MB (Ultra W) or 256MB (Ultra) DDR3L RAM, and an on-board 8GB eMMC
for storage 3 4 . Some variants also support SPI NAND or microSD boot (selectable).
• Wireless – The Ultra W model includes 2.4GHz Wi-Fi 6 and Bluetooth 5.2/BLE connectivity 5 (the
non-W variants lack Wi-Fi).
• Interfaces – 30 GPIO pins, 2x UART, I²C and SPI buses, ADC, PWM, a USB 2.0 OTG/Host port,
10/100M Ethernet (with PoE support on certain models), MIPI-CSI camera interface, and an RGB
display interface 5 . These interfaces allow connecting sensors, controlling devices, and adding
peripherals for various projects.
• Power and USB Ports – The board has both a USB Type-C (used for power and OTG functionality)
and a USB Type-A port for host mode. Note: The Pico Ultra W uses a switch to alternate between
USB-C and USB-A; they cannot be active at the same time 6 . If the USB-C port is providing power,
the USB signals route to USB-C; if not (board powered via other means), the USB-A port becomes
active 6 . This design avoids signal conflicts and power issues.

Boot Options: The Pico Ultra W typically boots from its internal eMMC. It also has a boot ROM supporting
USB recovery and possibly microSD in certain configurations. The Luckfox SDK’s build script suggests one
can target SD card or SPI flash as boot media for some models 7 8 , but for Ultra W the default is
eMMC. We will build an image that can be flashed to eMMC or booted from SD, depending on your needs.

1
Factory Software: Luckfox provides an SDK and pre-built images (Buildroot-based and Ubuntu 22.04) for
this board 2 . The official SDK (available on GitHub) includes build scripts for U-Boot, kernel (Linux 5.10.160
in the latest SDK 9 ), and root filesystems (Buildroot, Debian 12 “Bookworm”, and Yocto 5.0 support) 10
11 . However, in this guide we will use the Yocto Project directly (outside the vendor SDK) to gain a deeper

understanding. This means manually configuring layers and recipes for our board – a valuable learning
experience even if it requires a bit more setup.

Preparing the Build Environment (Using Docker)


Yocto builds are known to be sensitive to the host environment, so it’s recommended to use a Linux system
with all required dependencies. We’ll use a Docker container with Debian Bookworm to ensure
consistency (this avoids cluttering your host OS with packages and works on any OS that supports Docker).
Below are the steps to set up the environment:

1. Install Docker: If not already installed, follow the official instructions for your platform. On Debian/
Ubuntu, for example, you can use APT to install Docker Engine 12 .

2. Launch a Debian Bookworm Container: In a terminal on your host, run a container in interactive
mode with a volume mounted for persistent storage. For instance:

mkdir ~/yocto-project # create a working directory on host


docker run -it --name yocto-build \
-v ~/yocto-project:/workdir \
-w /workdir debian:bookworm /bin/bash

This starts a Debian 12 container, mounting ~/yocto-project into it (so files persist on the host).
Inside the container, you’ll have a prompt in the /workdir directory.

3. Install Yocto Build Dependencies: Yocto requires a number of development tools and libraries on
the build system. Inside the container, update apt and install packages:

apt-get update && apt-get install -y \


git ssh make gcc g++ gawk libc6-dev \
gcc-multilib g++-multilib build-essential \
bison flex gperf texinfo chrpath diffstat cpio rsync \
python3 python-is-python3 python3-pip \
libssl-dev libncurses5-dev sudo wget xz-utils \
tar unzip file bc device-tree-compiler \
qemu-user-static binfmt-support

This list covers compilers, build tools, QEMU (for any emulated runs), and other utilities commonly
needed for Yocto 13 14 . It’s intentionally comprehensive to avoid missing dependencies. (Note: In
the Luckfox SDK docs, they recommend Ubuntu 22.04 and list similar packages 13 . Debian
Bookworm is comparable, so we use the same toolset).

2
4. (Optional) Set Up User Permissions: If you plan on flashing eMMC or using USB OTG from inside
the container, you may need USB device access. You can add your user to groups or run the
container with --privileged for simplicity during development. Alternatively, handle flashing
from the host OS.

With the Docker environment ready, we can proceed to fetch Yocto source code and relevant board support
packages. All subsequent commands will be run inside the Docker container (in the /workdir path).

Obtaining Yocto Source and Board Support Layers


Yocto Project provides a reference distribution called Poky, and hardware-specific support is added via
meta-layers. Since the RV1106 is not (yet) officially supported in upstream Yocto BSP layers 15 , we have two
approaches:

• Using the Luckfox SDK’s Yocto layers: The official SDK contains a yocto/ directory with a Yocto-
based root filesystem (referred to as “Yocto 5.0”) 10 11 . However, the SDK uses a unified build script
( build.sh ) and a manifest to tie together many components, which can be complex for learning
purposes.
• Using a community layer (meta-rockchip-rv1106): A community effort by developers has
produced Yocto support for RV1106. For example, the GitHub repository “gflix/rockchip-rv1106-
dev” provides a BSP layer for RV1106 as found on Luckfox Pico boards 16 . This layer can be
integrated into a Yocto build in the usual way. We will follow this approach to better illustrate manual
Yocto integration.

Yocto Release Selection: Luckfox refers to “Yocto 5.0”, which corresponds to Yocto Project 5.0 (codename
scarthgap) – a relatively recent Yocto release (with Linux kernel 6.6, GCC 13, etc., per Yocto 5.0 release notes
17 ). For stability, you may also choose an LTS release like Yocto 4.0 (kirkstone). In this guide, we’ll assume

Yocto 5.0 (scarthgap) to align with the latest references, but the steps are similar for Kirkstone or others.

1. Download Poky (Yocto) – Clone the Yocto Poky repository for the desired release:

# Inside container, in /workdir


git clone -b scarthgap git://git.yoctoproject.org/poky.git poky-scarthgap

This creates a directory poky-scarthgap containing Yocto’s build system and core metadata.

2. Download OpenEmbedded Core layers – Many extra recipes (for packages, networking, etc.) come from
the OpenEmbedded meta layers. Clone at least meta-openembedded:

git clone -b scarthgap git://git.openembedded.org/meta-openembedded.git

This repository contains multiple layers (meta-oe, meta-python, meta-networking, etc.). We will use at least
meta-oe from it, as needed by some BSPs 18 19 .

3
3. Download the Rockchip RV1106 BSP layer – Clone the community layer for RV1106 boards. Using the
gflix repository:

git clone https://github.com/gflix/rockchip-rv1106-dev.git

Inside this, there is likely a layer named meta-rockchip-rv1106 . Indeed, the README of that repo states
it provides a BSP for RV1106 (Luckfox Pico) and even notes some special considerations (like differing SD
card layout) 20 . We will include this layer to get kernel, bootloader, and image recipes for the RV1106.
(Alternatively, one could start from a similar Rockchip board’s config — the meta-rockchip maintainer suggested
reusing PX3SE (RK3288) settings for a basic RV1106 build 21 — but the community layer saves us that effort.)

4. (Optional) Download meta-rauc – To integrate RAUC, get the official RAUC meta-layer:

git clone -b scarthgap https://github.com/rauc/meta-rauc.git

And also the meta-rauc-community layers if available (they sometimes provide ready examples for certain
boards, though none exists for RV1106 yet). For now, meta-rauc alone suffices to add the RAUC recipes.

After these steps, your /workdir should contain directories like poky-scarthgap/ , meta-
openembedded/ , rockchip-rv1106-dev/ , and meta-rauc/ (plus possibly meta-rauc-community).
Now we’ll configure the Yocto build for our hardware.

Configuring the Yocto Build for Pico Ultra W


Yocto builds are configured by setting up a build environment and editing configuration files:

1. Initialize the Build Environment:


Change into the poky directory and source the environment setup script:

cd poky-scarthgap
source oe-init-build-env ../build

This creates (or enters) a build/ directory and sets environment variables (like BITBAKE_DIR ).
After this, your prompt should reflect the Yocto build environment (usually showing “build$”). All
further bitbake commands will be run from within this build/ directory.

2. Add Meta-Layers to BBLAYERS:


Open conf/bblayers.conf (in build/conf/ ). We need to add the paths of our additional
layers. For example, add lines for meta-openembedded, meta-rockchip-rv1106, and meta-rauc. After
editing, your BBLAYERS might look like:

4
BBLAYERS ?= " \
/workdir/poky-scarthgap/meta \
/workdir/poky-scarthgap/meta-poky \
/workdir/poky-scarthgap/meta-yocto-bsp \
/workdir/meta-openembedded/meta-oe \
/workdir/rockchip-rv1106-dev/meta-rockchip-rv1106 \
/workdir/meta-rauc \
"

(Add any other needed layers, such as meta-openembedded/meta-python if required by recipes).


This tells BitBake where to find all the metadata. Note: Ensure the paths are correct and each layer’s
conf/layer.conf has a compatible LAYERSERIES for scarthgap (most likely they do if using the
correct branches).

3. Set the MACHINE and DISTRO in local.conf:


Open conf/local.conf . We need to specify the target machine and any distro features. The
RV1106 layer should provide a machine configuration (check meta-rockchip-rv1106/conf/
machine/ ). The community layer likely defines a machine name such as "rockchip-rv1106" or
specifically for Luckfox (perhaps luckfox-pico variant). According to gflix’s instructions, the
image output was named rockchip-rv1106 (they mention the SD image path includes
rockchip-rv1106 22 ). We’ll use:

MACHINE ?= "rockchip-rv1106"

in local.conf (or the appropriate machine name if documentation says otherwise). If uncertain, you
can list the machines available in the meta layer (look under meta-rockchip-rv1106/conf/
machine/*.conf ). Use exactly the name of the .conf file (without extension) for MACHINE.

Next, since we want a minimal headless setup, we’ll choose a basic Yocto image. core-image-minimal is
a good starting point (just a bootable Linux with very few utilities). Ensure the EXTRA_IMAGE_FEATURES in
local.conf includes ssh-server-dropbear or similar if you want SSH access out of the box. Because our
board likely needs networking for OTA, enabling an SSH server is useful for headless operation.

Add the following to local.conf:

# Use systemd as the init system (optional, but RAUC works well with systemd)
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
# Add RAUC to the image
IMAGE_INSTALL:append = " rauc"
DISTRO_FEATURES:append = " rauc"

5
These lines ensure we include RAUC in the built image and denote that the system has the “rauc” feature (so
that RAUC’s update service can be configured). We also switch to systemd for init, which is common for
RAUC setups (RAUC provides a systemd service for handling updates on boot). If you prefer SysV init, you
would handle RAUC’s startup differently, but using systemd is straightforward.

We’ll also specify the image type and size. For example, to produce an SD card image (in case we want to
boot from SD or flash eMMC via an image), you might ensure IMAGE_FSTYPES includes wic . The meta-
rockchip layer’s README notes that after a build you get a .wic and an update.img (Rockchip firmware
format) 23 . It even mentions adding INHERIT += "rockchip-image" in local.conf 24 . The community
layer may handle packing the firmware if we include that. For simplicity, ensure:

INHERIT:append = " rockchip-image"

and set an appropriate image name to build (we will use core-image-minimal, which is fine).

Save local.conf after making these changes.

1. Verify U-Boot and Kernel recipes: The RV1106 meta-layer should include recipes for u-boot (the
bootloader) and Linux kernel tailored to this SoC/board. The Luckfox SDK update log indicated they
use U-Boot (with fastboot support for RV1106 and SD card fixes) and a Linux 5.10 kernel 9 . The
Yocto layer likely either uses the vendor-provided kernel source (perhaps a 5.10 or 6.1 branch with
RV1106 support) or a mainline kernel with patches. It’s not crucial to manually configure these if the
layer is well-written, but be aware of them. For a beginner-level approach, we trust the meta-layer
defaults. (If needed, you can customize the kernel by editing the defconfig or adding patches, but
that is an advanced step.)

2. (Optional) Adjust Image Contents: By default, core-image-minimal will have very few utilities. For
development, you might append a few tools in local.conf, for example:

IMAGE_INSTALL:append = " nano i2c-tools spi-tools python3"

3. nano (or vim-tiny ) for editing files on target,


4. i2c-tools (for commands like i2cdetect ),
5. spi-tools or ensure spidev test tools are available,
6. python3 if you plan to run Python scripts (useful for testing interfaces as shown later).
This is optional but can make your life easier when you boot the board.

At this stage, our configuration is in place. We have specified the machine, added needed layers, and
included RAUC and networking basics. Now it’s time to build the image.

Building the Yocto Image


With everything configured, trigger the build using BitBake:

6
bitbake core-image-minimal

This command will fetch all required source code (this may take a while on first run – including downloading
the cross-toolchain, kernel, bootloader, etc.), compile the components, and assemble the Linux image. Be
prepared for the build to take a long time (possibly hours) depending on your CPU and internet speed, as
Yocto builds from source.

During the process, BitBake will compile U-Boot, the Linux kernel, the RAUC tool, and create the root
filesystem with our selected packages. If everything completes successfully, you should find images in
tmp/deploy/images/rockchip-rv1106/ (assuming rockchip-rv1106 is our MACHINE). Key outputs
likely include:

• A bootable SD card image, e.g. core-image-minimal-rockchip-rv1106.wic (or .wic.xz ) –


This can be written to a microSD card or used to flash eMMC.
• A Rockchip firmware bundle update.img 25 – This is a single file that includes loader, u-boot,
kernel, and rootfs. Rockchip devices often use this for flashing via their upgrade_tool or RKDevTool
utility.
• Individual partitions: boot.img (with kernel and maybe device tree), rootfs.img (the root
filesystem as ext4 or squashfs), etc. 26 .

Note: The gflix repository README mentioned a known limitation: at one point, their build only packaged
the bootloader but not the kernel into the SD image 27 . Ensure that your build actually includes the kernel.
If the .wic image is missing a kernel, you might need to integrate the kernel manually or check if the
meta-layer has been updated. Assuming the community or vendor layer is complete, you should have a full
image. If not, you might temporarily use the vendor’s pre-built kernel or their kernel recipe (the Luckfox
kernel source might be needed). For our guide, we’ll assume success with the Yocto build.

Flashing and Booting the Board


Once the image is built, the next step is to boot it on the Pico Ultra W:

Using SD Card (if applicable): If your board supports booting from SD or if the eMMC is blank, you can
write the .wic image to a microSD card. For example, from the Docker host (not inside the container):

# Replace X with the actual letter for the SD card device


sudo dd if=core-image-minimal-rockchip-rv1106.wic of=/dev/sdX bs=4M && sync

Insert the SD card into the board and apply power. The RV1106 BootROM will boot from SD if no valid
bootloader is found in SPI flash/eMMC 28 . On serial console, you should see U-Boot and then the Linux
boot logs.

Flashing eMMC via Rockchip Tool: If booting from eMMC, you may create the Rockchip update.img and
use the Rockchip Android tool or the upgrade_tool . The meta-rockchip README suggests using
upgrade_tool on a host PC 29 . You would connect the board in maskrom mode (usually by pressing a

7
button or shorting a pin while powering up, to force the boot ROM’s USB mode) 30 . Then run:

sudo upgrade_tool uf update.img

This will flash the update image to eMMC. Alternatively, use the Luckfox rkflash.sh script (provided in
SDK) or their provided GUI tool if available. For a beginner, SD card boot is simpler if available.

Initial Boot and Login: On first boot, the image will expand (if using wic with proper settings) and you
should get a login prompt. For core-image-minimal , the default login is typically root (no password). If
network is connected (Ethernet), you can try to obtain an IP (Yocto might use DHCP by default if connman
or networking is configured). Since we included dropbear SSH (via ssh-server-dropbear feature), you
should be able to SSH into the board once you know its IP.

Tip: It’s highly recommended to connect a USB-to-UART serial adapter to the board’s debug UART for the
first boot. That way, you can see bootloader messages and Linux kernel output. The Luckfox Pico Ultra W’s
wiki has instructions for serial debugging and default baud rate 31 32 (likely 1500000 or 115200 bps).
Using a serial console will help in case something goes wrong.

At this point, if all went well, you have a running Yocto-built Linux on the Luckfox board. Next, we’ll set up
RAUC for OTA updates, and then demonstrate using the GPIO, I2C, SPI, and USB interfaces.

Setting Up Over-The-Air Updates with RAUC


RAUC (Robust Auto Update Controller) is a framework for A/B update of embedded devices, ensuring that
updates are applied reliably and can roll back if something fails. It works by maintaining two sets of
bootable partitions (often called slot A and slot B). The device runs from one slot while the other is used to
flash a new update, then the bootloader is instructed to switch slots on next reboot 33 . This guarantees
that if an update fails, the device can revert to the untouched slot, avoiding bricking. RAUC also supports
various bundle formats (it commonly uses a SquashFS bundle) and can do updates via network streams
(HTTPS, etc.) 34 .

In our Yocto build, we already included the RAUC package in the image. To fully integrate RAUC, there are a
few additional steps:

• Partition Layout for A/B: We need to define a partition scheme with redundant root filesystems.
The basic requirement is two rootfs partitions of equal size for slots A and B 35 , plus possibly a
separate boot partition and a persistent data partition. For example, on eMMC we might have:
boot (vfat) , rootfsA (ext4) , rootfsB (ext4) , and maybe a data partition. The
bootloader (U-Boot) would load the kernel from the active rootfs or from boot partition depending
on design. Rockchip’s scheme in the SDK uses parameter.txt to define partitions 26 ; for Yocto,
we handle this via a WIC file or Yocto image recipe. The meta-rauc-community provides example WIC
configurations for A/B setups (e.g., for Beaglebone). You might create a custom .wks file specifying
partitions, and then configure local.conf with WIC_FILE to use it.

8
• U-Boot Integration: U-Boot must be aware of RAUC slots to switch boot targets. Typically, this is
done by having U-Boot environment variables for the current slot and a script that decides which
partition to boot. For example, environment variables like rootfs_part or rauc_slot can
determine if we boot from rootfsA or rootfsB. RAUC updates will mark the inactive slot as “candidate”
and on next boot U-Boot should try that slot. If boot succeeds, RAUC marks it good; if not, U-Boot
can fall back to the previous slot. The Konsulko guide for BeagleBone Black with RAUC outlines
providing a custom U-Boot script for slot switching 36 . For our board, we’d need to craft something
similar. This typically involves enabling U-Boot environment redundancy or a small partition to store
environment, so that variables like rauc.slot can persist.

• Enable Required Kernel Features: Ensure the Linux kernel has SquashFS support (for RAUC bundle
verification) and any necessary features like watchdog or dm-verity if you plan to use those. The
Konsulko example explicitly notes enabling SquashFS and using ext4 for rootfs 37 . In our build, if
using the vendor kernel, SquashFS is likely already enabled (since Android uses it for recovery
images too), but double-check or enable it via kernel config if needed.

• RAUC Configuration ( /etc/rauc/system.conf ): RAUC uses a config file on the target to define
the slots and their mount points. We need to provide this via Yocto. For instance, a system.conf
might look like:

[system]
bootloader = uboot
compatible = "luckfox-pico-ultra"

[slot.rootfs.0]
device = /dev/mmcblk0p2
type = ext4
mountpoint = /
bootname = A

[slot.rootfs.1]
device = /dev/mmcblk0p3
type = ext4
mountpoint = /
bootname = B

(Plus potentially a separate [slot.boot] if using a separate boot partition, and perhaps slots for
recovery, etc.) The compatible string is used to ensure an update bundle matches the device. You
can choose a custom name (like “luckfox-pico-ultra”) and use the same in your update bundle recipe.
This file should be deployed to the target’s /etc/rauc/system.conf via Yocto. The meta-rauc
layer provides classes to help with that.

• Generating Keys: RAUC requires signing of update bundles. You should create an X.509 key pair (a
private key and a certificate). The certificate (public part) will be included in the device (e.g., in
/etc/rauc/keyring.pem ) so the device can verify signatures. The private key is kept on your
development machine to sign the update bundles you create. For testing, you can generate a self-

9
signed key with OpenSSL and include the certificate. RAUC’s documentation and examples show how
to generate these keys 38 .

• Adding RAUC to Boot Sequence: With systemd, RAUC installs a service that runs at boot to check if
an update was marked for installation (it also ensures that after an update, the new slot is marked
good). Make sure the RAUC service is enabled. If we added rauc to the image and the rauc
distro feature, meta-rauc typically sets this up automatically via systemd unit files.

Given all these, integrating RAUC fully is an advanced task. For a beginner-friendly route, start with a
simpler approach: design an update workflow and test it manually before automating everything. For
example:

• Manually partition your storage into A/B and flash the same image to both partitions initially. Update
the system.conf accordingly.
• Use U-Boot console to set environment variables for booting from different partitions (to simulate
the switching logic). This helps verify that you can boot from both A and B.
• Then create a dummy RAUC bundle (a signed update that maybe just toggles an LED or changes a
file) and test the rauc install command on the device to see the process.

The RAUC documentation is very detailed, covering partitioning, bootloader interfacing, and Yocto
integration 39 40 . It emphasizes that you must plan your partition scheme early, as changing it later is
difficult 41 . A simple A/B scheme is usually sufficient. The result of a proper RAUC setup is that you can
deliver a single bundle file (for example via a web server or USB stick), run rauc install update-
x.y.z.raucb on the device (or have it pull from network), and RAUC will handle writing the inactive slot
and swapping boot flags.

For our guide scope, we will not go deeper into RAUC’s custom U-Boot script or automation – those details
can fill an entire document. But remember these key points: - A/B slots with identical sizes are required
35 . - Bootloader cooperation is needed to swap slots safely 36 . - Signed Bundles and Key

Management ensure only authorized updates apply.

With the system prepared, you should be able to integrate RAUC as needed for OTA. Now, let’s focus on
using the hardware interfaces (GPIO, I2C, SPI, USB) on our new Linux system.

Using GPIO Pins (General-Purpose I/O)


One of the first things to try on an embedded board is controlling GPIOs – e.g., to blink an LED or read a
button. The Luckfox Pico Ultra W has 30 GPIO pins exposed 2 . In Linux, GPIOs are typically accessed
either via the legacy sysfs interface or the newer character device ( /dev/gpiochip* with libraries like
libgpiod). For simplicity and because the Luckfox documentation uses it, we’ll demonstrate the sysfs
method.

GPIO Sysfs interface: The Linux kernel’s GPIO sysfs allows exporting a pin to userspace as a file under /
sys/class/gpio/ . Once exported, you can configure direction and read/write values by writing to files in
that directory 42 43 .

10
Steps to toggle a GPIO (for example, GPIO pin number 41 as in Luckfox’s example 44 ):

1. Identify the GPIO number: The board’s pinout documentation provides the mapping of physical
pins to Linux GPIO numbers. Luckfox uses a naming scheme with banks (GPIO0, GPIO1, etc.) and
letters A-D, each letter group has 8 pins 45 . They give a formula: Linux GPIO number = bank *
32 + index 46 . For instance, GPIO1_B1 (bank 1, group B, index 1) calculates to 1*32 + (1*8 +
1) = 41 44 . The wiki’s pin diagram should list the number directly for convenience. Let’s assume
we have a pin we want to blink, and its Linux GPIO number is 41.

2. Export the GPIO to userspace:

echo 41 > /sys/class/gpio/export

This tells the kernel we want to control GPIO41. A directory /sys/class/gpio/gpio41/ will
appear if successful 43 .

3. Configure direction:

echo "out" > /sys/class/gpio/gpio41/direction

(Use "in" for an input pin.) This sets GPIO41 as an output. Now two important files in this
directory are value and direction 47 .

4. Write values:

echo 1 > /sys/class/gpio/gpio41/value # set pin high


echo 0 > /sys/class/gpio/gpio41/value # set pin low

By writing 1 or 0 , you drive the GPIO high (logic 1) or low (logic 0). If an LED is attached to that
pin (with appropriate wiring), it should turn on/off accordingly.

5. Read values (if input): If the pin were configured as an input, you could cat /sys/class/gpio/
gpioXX/value to read its state (it will be “0” or “1”). Input GPIOs can also be configured to trigger
interrupts (edge triggers) by writing "rising" , "falling" , or "both" to the edge file in that
directory, then monitoring the value or poll on it – but that’s beyond our basic scope.

6. Unexport when done (optional):

echo 41 > /sys/class/gpio/unexport

This releases the GPIO back to the kernel control (useful if some driver will use it later or just for
cleanup).

11
The above process can be done from the shell or in a C/C++ program by writing to those sysfs files. The
Luckfox documentation provides a sample program (accessible on their drive) using sysfs calls 42 .

Important: Some of the Pico’s GPIOs may be multiplexed with other functions (e.g., some pins might be
used for the RGB display interface by default). The Luckfox guide notes that if you’re not using certain
features (like the RGB LCD), you should disable them via luckfox-config to free the pins for GPIO use
48 . On your custom Yocto image, the luckfox-config TUI might not be present unless you ported it,
so you need to ensure in the device tree that those pins are configured as GPIOs and not tied up in another
function. In our build, if we started from a default device tree, common interfaces (like the display) might be
enabled. You may want to compile a custom device tree that disables the LCD if you need those GPIOs.

For more modern usage, consider using libgpiod ( gpiod tools). If you include libgpiod-tools in your
image, you can do:

gpioget <chip> <line_offset>


gpioset <chip> <line_offset>=1

However, understanding chip and line offsets requires knowledge of how the GPIO controller is
represented. For now, sysfs is straightforward for a beginner, and it works as described in the Luckfox wiki
42 43 .

I2C Communication
The I2C (Inter-Integrated Circuit) bus is used to connect low-speed peripherals like sensors, RTCs, etc. The
Pico Ultra W has I2C interfaces exposed (likely on certain GPIO pins configured as I2C SCL/SDA lines). Linux
represents I2C buses as devices like /dev/i2c-<N> where <N> is the bus number.

To use I2C on the board:

1. Ensure the I2C bus is enabled: In the kernel device tree for RV1106, the I2C controller should be
enabled and pinned out. Typically, it will show up as e.g. i2c-3 or i2c-4 under /dev (the exact
number depends on how many controllers and the numbering in the SoC – refer to Luckfox docs to
find which is exposed). The Luckfox wiki references /dev/i2c-3 in examples 49 .

2. List I2C buses: You can list I2C adapter devices by:

ls /sys/bus/i2c/devices/

The output might show entries like i2c-4 and also any detected device addresses 50 . For
example, i2c-4 might be an adapter and entries like 4-0030 could be devices at address 0x30
on bus 4.

3. Use i2c-tools for scanning: If you included i2c-tools , run:

12
i2cdetect -y -a 3

(Replace 3 with the I2C bus number you want to scan.) This will probe all addresses and list any
device found 51 . A table will be printed showing addresses where devices acknowledge. For
example, a device at 0x68 would show up at that address.

4. Reading/Writing I2C devices: With i2cget and i2cset you can read/write registers. For
instance, to read register 0x01 from device at address 0x68 on bus 3:

i2cget -f -y 3 0x68 0x01

To write value 0x6F to register 0x01 :

i2cset -f -y 3 0x68 0x01 0x6f

These correspond to examples in the Luckfox documentation 52 . The -y skips confirmation, -f


forces access (useful if the device might be in use by a driver, but caution is advised).

5. Using I2C in programs: You can also use the SMBus interface in Python (via the smbus or
smbus2 library) or use C/C++ with the Linux I2C dev interface. For example, Luckfox shows a
Python snippet scanning the bus by trying write operations to every address 53 . This approach is
fine for testing, but in practice you’d use device-specific drivers or read known registers.

If the board has on-board I2C peripherals (sometimes PMICs or audio codecs are on I2C), they might
already appear in the system (e.g., under /sys/bus/i2c/devices ). Otherwise, you will typically connect
external devices to the I2C pins. Remember to use proper pull-up resistors if not already on the board, as
I2C lines require them.

By following the above, you should be able to interface with any I2C device connected to the Pico Ultra W,
using standard tools or writing simple code. Always double-check the voltage levels and pin mappings
before connecting new hardware to the I2C bus.

SPI Communication
SPI (Serial Peripheral Interface) is a high-speed synchronous bus commonly used for sensors, displays, or
memory chips. The Pico Ultra W exposes SPI pins as well. Linux represents SPI master controllers as /dev/
spidevX.Y devices (if the device tree is configured to create spidev nodes). Typically, X is the bus number
and Y is the chip select (CS) line number.

To use SPI on the board:

1. Check for spidev devices: Look under /dev/ for spidev* . Also, list the SPI devices recognized:

13
ls /sys/bus/spi/devices/

This might show entries like spi0.0 or spi2.0 54 . For example, spi2.0 means bus 2, chip-
select 0 is in use (perhaps a device or a spidev node).

If you see spidev devices (e.g., /dev/spidev2.0 ), that means the device tree has an entry to expose the
SPI bus for user space. If not, you may need to modify the device tree to add a spidev. Often, boards that
don’t have a specific SPI peripheral connected leave the bus free, but you must declare a spidev node (some
board DTS do this via an overlay or a generic “spidev” device binding).

1. Use spidev from Linux: The easiest way is with a userspace program. The kernel doesn’t have a
generic “spi-test” utility in-tree, but there is a widely used spidev_test.c program (you can find
source online). However, since we included Python, an easy test is using the Python spidev library.
The Luckfox documentation provides an example in Python to send and receive data on SPI 55 56 .
It does the following:
2. Open SPI bus 0, device 0: spi.open(0, 0) 57 (this corresponds to /dev/spidev0.0 ).
3. Set speed, e.g., 1 MHz: spi.max_speed_hz = 1000000 57 .
4. Prepare a transmit buffer (here a list of bytes corresponding to "hello world!" ).
5. Perform a transfer: rx_buffer = spi.xfer2(tx_buffer) which sends the tx buffer and
simultaneously reads into rx buffer 58 .
6. Print out the sent and received data.

If you had a loopback (MOSI tied to MISO) or a slave device that responds, you would see data come back.
For a basic loopback test, connect the MOSI and MISO pins together (and a common GND), then whatever
you send you should receive.

1. SPI device driver vs spidev: In real applications, if you connect a specific SPI peripheral (say a flash
memory or a sensor), you might write or use a kernel driver instead of using spidev. For example, an
SPI flash could be handled by MTD subsystem rather than via spidev. But for quick prototyping and
learning, spidev is fine.

2. Multiple chip selects: If the board’s SPI bus has multiple chip select lines, you can have spidev0.0,
spidev0.1, etc. The naming spiX.Y in /sys/bus/spi/devices helps identify which are present.
Each corresponds to one /dev/spidevX.Y node for user space. The number X (bus) might not
start at 0 depending on SoC; check dmesg for SPI controller numbering. In the Luckfox example,
they list spi2.0 and spi0.0 simultaneously 54 – possibly the SoC has multiple SPI controllers.

If spidev is not present by default, enabling it may require adding a fragment to the device tree (Yocto could
incorporate that via a bbappend or an overlay). On many boards, a quick method is to modify the device
tree source to include something like:

&spi2 {
status = "okay";
spidev@0 {
compatible = "spidev";

14
reg = <0>; /* chip select 0 */
spi-max-frequency = <10000000>;
};
};

This would instantiate a spidev at CS0 on spi2. But ensure no other driver is bound to that CS.

For learning purposes, once you have /dev/spidev* , you can experiment freely with SPI in user space.
The Python example 55 is a good starting point, or use the spidev_test utility in C. The key parameters
you might play with are mode (mode 0,1,2,3 corresponding to clock polarity and phase) and speed.

USB Usage (Host and Device Modes)


The Luckfox Pico Ultra W features a USB 2.0 OTG interface that can function as either a host (to connect
keyboards, flash drives, etc.) or a device (for example, to appear as a USB gadget or for flashing the board).
As mentioned earlier, the board has both a USB-C and a USB-A connector, switched automatically 6 .
Typically, if you power the board via the USB-C port, that port might be used for device mode (e.g., ADB or
flashing), whereas the USB-A port would be inactive. If you power the board via the 5V header or PoE, then
the USB-C is not supplying power and thus the USB-A can be the host port 59 .

Using USB in Host Mode:


Host mode is used to connect USB peripherals to the board. If you want the board to act as a “normal”
computer host: - Ensure the board is either not powered via USB-C, or if it is, that you explicitly switch to
host mode via configuration. The Luckfox system has a tool luckfox-config to set USB mode 60 . In the
official OS, one would run luckfox-config , navigate to Advanced Options -> USB -> host, then reboot 61 .
In our custom Yocto image, we might not have this tool. We need to ensure the USB controller is in host
mode by default. Often, OTG controllers can auto-detect, but given the hardware switch, it’s likely
determined by power source. - To check mode at runtime, read the sysfs entry (from Luckfox docs):

cat /sys/devices/platform/ff3e0000.usb2-phy/otg_mode

It should output “host” if in host mode 62 . If it says “device”, then the controller is in device mode.
Changing this may not be straightforward without the config utility, but possibly writing “host” to that file or
a similar sysfs might switch it. (Be careful and consult documentation; incorrect toggling could confuse the
controller.)

• Once in host mode, plug in a USB peripheral (e.g., a flash drive). You should see kernel log messages
( dmesg output) indicating a device is connected. For example, the logs might show:
[ 143.364405] usb 1-1.4: new high-speed USB device number 3 using xhci-hcd
[ 143.513956] usb-storage 1-1.4:1.0: USB Mass Storage device detected
... and eventually it will report a new SCSI disk (like /dev/sdb) with size, etc 63 64 . This means the
flash drive was recognized.

15
• Verify the device node: Run ls /dev/sd* . You might see /dev/sda (which could be the eMMC)
and now /dev/sdb and /dev/sdb1 (a partition on the USB drive) 65 . In the example, sdb1
was the partition on the flash drive.

• Mount the USB drive: Create a mount point (e.g., /mnt/usb or /mnt/sdcard as Luckfox uses in
their example 66 ). Then:

mkdir -p /mnt/usb
mount /dev/sdb1 /mnt/usb
ls /mnt/usb # list files to confirm it's mounted

If the drive uses FAT32 or another filesystem, specify the -t vfat (for FAT) or appropriate type. In
many cases the kernel auto-detects if built with filesystem support. The example explicitly used -t
vfat 67 since many small drives are FAT formatted.

Now you can read/write files to the USB drive. This is crucial for offline updates (you could put a RAUC
update bundle on a USB stick and mount it, for instance).

Using USB in Device/Gadget Mode:


In device mode, the board itself can act like a USB device to a host PC. This is how you typically flash the
board using Rockchip’s tool or use ADB. If the Pico Ultra W is powered via USB-C from your PC, it likely
enumerates as a device. Common uses: - ADB access: The Luckfox images mention ADB (Android Debug
Bridge) login 31 . If our Yocto image includes the USB gadget modules (like USB Ethernet or Serial), we
could configure the board to present as, say, an Ethernet gadget or serial gadget to the PC. This requires
setting up gadget drivers (e.g., g_ether or g_serial kernel modules, or configfs for gadgets). - Flashing: In
maskrom or loader mode, the board’s USB will appear as a Rockchip device on the PC, and then the
upgrade_tool can be used to load images. That’s a special case outside the OS (board’s ROM or loader
handles it).

If you want to enable gadget mode in your Yocto build for some reason (say to emulate a USB serial or
mass storage device when plugged into a PC), you’d need to include the gadget drivers. Ensure the kernel
config has CONFIG_USB_GADGET and relevant function drivers. Then you can use modprobe g_serial
(for a simple serial port over USB) or more complex configfs setups for composite devices.

For this guide’s scope, we assume the primary interest is using USB in host mode to attach peripherals,
since we’re running headless Linux on the board itself.

Troubleshooting USB: If a device isn’t recognized, check dmesg for errors. Ensure adequate power (the
board’s USB port can supply limited current, and if using PoE or an external supply it should be sufficient for
small devices; high-power devices may need a powered hub). Also note the USB2.0 limitation – you won’t
get more than 480 Mbps, and some high-speed devices might not work if they require USB3 or specific
drivers.

In summary, the USB interface on Pico Ultra W is versatile. With correct configuration, you can plug in
drives, Wi-Fi dongles (if not using built-in Wi-Fi on W, or for other frequencies), or other accessories to

16
enhance your board’s functionality. The luckfox-config tool in the official system abstracts enabling host vs
device mode 68 , but understanding the underlying switch helps you manage it manually in Yocto.

Conclusion and Next Steps


Congratulations – you have built a custom Yocto-based Linux image for the Luckfox Pico Ultra W and
learned how to interact with its key hardware features! We covered setting up a reproducible build
environment with Docker, configuring Yocto for the RV1106 SoC, and integrating RAUC for safe OTA
updates. We also went through using GPIOs (toggling output and reading input via sysfs), performing I2C
communication (scanning devices and reading/writing registers), doing SPI transfers (with spidev and a
Python example), and managing the USB OTG port in host mode (mounting a flash drive).

Throughout this process, we have relied on both community resources and official documentation to guide
our steps. The Luckfox board’s wiki was helpful to confirm interface availability and usage examples (for
instance, verifying that the board supports those interfaces and how to use them in Linux) 2 42 . The
RAUC integration, while complex, was introduced with its fundamental principles of A/B partitioning and
bootloader coordination 33 35 , giving you a foundation to delve deeper into implementing robust OTA in
your project.

Where to go from here:

• Polish RAUC Setup: Implement the actual dual-partition layout and test RAUC updates end-to-end.
Consult RAUC’s official integration guide 69 35 for detailed steps and adapt them to the Pico’s
storage layout. This might involve writing a custom WIC file and U-Boot environment script as
discussed. Testing on real hardware is crucial – simulate a power failure during update to see how
RAUC handles rollback, for example.

• Customize the Kernel/Device Tree: If you need additional interfaces (e.g., enabling the camera
(CSI) or adjusting which pins are used for what), you’ll likely modify the device tree. For example,
enabling an SPI device or freeing GPIOs from alternate functions requires DTS changes. You can
create a Yocto layer for your customizations (instead of editing the community layer directly) – Yocto
allows .bbappend and device tree overlays to apply such changes.

• Add More Packages/Services: For a more featureful system, consider using a larger base image
(e.g., core-image-base or even console-image) which includes more utilities. Add packages like
networkmanager or wpa_supplicant if you plan to use Wi-Fi. Given the board has Wi-Fi 6, you
might want to enable that (likely requires firmware and driver – the vendor’s Ubuntu image might
have those components you can mirror).

• Explore the NPU and Media Capabilities: The Pico Ultra W has a Neural Processing Unit and an ISP.
The Luckfox SDK likely includes Rockchip’s RKNN toolkit and GStreamer-based media pipelines (as
hinted by references to RKMPI in the wiki) 32 . If your project involves AI or camera input, you can try
to integrate those libraries into your Yocto build or use the SDK’s Debian image as a reference. Be
aware that GPU/NPU support often means using specific kernel drivers and proprietary blobs.

17
• Community and Support: Don’t hesitate to reach out on forums or communities for help. The
Luckfox forums and the Yocto mailing lists can be valuable if you encounter issues (for example,
someone on the forum had issues building U-Boot with Yocto for this board 70 – which means
you’re not alone, and solutions or patches might be available).

By building Linux from scratch for the Pico Ultra W, you’ve gained experience with embedded Linux
development that can apply to many other boards and SoCs. Yocto’s learning curve is steep, but now you
have a working project to build on. Happy hacking with your custom Linux on the Luckfox Pico Ultra W!

Sources:

• Luckfox Pico Ultra W – Product intro and interface support 1 2


• Luckfox Wiki – GPIO, I2C, SPI, USB usage examples 43 51 54 63
• Luckfox Pico SDK – Notes on kernel version and build options 9 7
• Konsulko (Leon Anavi) – RAUC overview and Yocto integration on BeagleBone 33 36
• RAUC Official Documentation – Requirements for A/B partitioning 35 and integration notes 69 .

18
1 2 3 4 5 31 32 Getting Started Tutorial | LUCKFOX WIKI
https://wiki.luckfox.com/Luckfox-Pico/Luckfox-Pico-RV1106/Luckfox-Pico-Ultra-W/Luckfox-Pico-quick-start/

6 59 60 61 62 63 64 65 66 67 68 USB | LUCKFOX WIKI


https://wiki.luckfox.com/Luckfox-Pico/Luckfox-Pico-RV1106/Luckfox-Pico-Ultra-W/Luckfox-Pico-USB/

7 8 9 13 14 GitHub - LuckfoxTECH/luckfox-pico: luckfox-pico sdk


https://github.com/LuckfoxTECH/luckfox-pico

10 11 26 SDK Environment Deployment(x86_64 platform) | LUCKFOX WIKI


https://wiki.luckfox.com/luckfox-Omni3576/Luckfox-Omni3576-SDK/

12 Install Docker Engine in Linux - Hackster.io


https://www.hackster.io/flint-weller/install-docker-engine-in-linux-1e2204

15 21 RV1106 support · JeffyCN meta-rockchip · Discussion #93 · GitHub


https://github.com/JeffyCN/meta-rockchip/discussions/93

16 20 22 27 28 GitHub - gflix/rockchip-rv1106-dev: Devel environment for Rockchip RV1106 based boards


https://github.com/gflix/rockchip-rv1106-dev

17 Release notes for 5.0 (scarthgap) - the Yocto Project Documentation


https://docs.yoctoproject.org/next/migration-guides/release-notes-5.0.html

18 19 23 24 25 29 30 GitHub - JeffyCN/meta-rockchip: Yocto BSP layer for the Rockchip SOC boards
https://github.com/JeffyCN/meta-rockchip

33 34 36 37 38 Integrating RAUC with Yocto Project on BeagleBone Black


https://www.konsulko.com/rauc-beaglebone-black

35 39 40 41 69 Integration — RAUC 1.14.127-e6b61 documentation


https://rauc.readthedocs.io/en/latest/integration.html

42 43 44 45 46 47 48 GPIO | LUCKFOX WIKI


https://wiki.luckfox.com/Luckfox-Pico/Luckfox-Pico-RV1106/Luckfox-Pico-Ultra-W/Luckfox-pinout/Luckfox-Pico-GPIO/

49 50 51 52 53 I2C Communication | LUCKFOX WIKI


https://wiki.luckfox.com/Luckfox-Pico/Luckfox-Pico-RV1106/Luckfox-Pico-Ultra-W/Luckfox-Pico-I2C

54 55 56 57 58 SPI Communication | LUCKFOX WIKI


https://wiki.luckfox.com/Luckfox-Pico/Luckfox-Pico-RV1106/Luckfox-Pico-Ultra-W/Luckfox-Pico-SPI

70 Unable to build u-boot using Yocto - Luckfox Forums


http://forums.luckfox.com/viewtopic.php?t=1458

19

You might also like