Building A Custom Embedded Linux (Yocto) For The Luckfox Pico Ultra W
Building A Custom Embedded Linux (Yocto) For The Luckfox Pico Ultra W
• 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.
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:
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:
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).
• 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:
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:
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:
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:
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.
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.
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 \
"
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.
# 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:
and set an appropriate image name to build (we will use core-image-minimal, which is fine).
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:
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.
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:
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.
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):
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:
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.
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
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.
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.
This tells the kernel we want to control GPIO41. A directory /sys/class/gpio/gpio41/ will
appear if successful 43 .
3. Configure 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:
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.
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:
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.
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.
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:
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.
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.
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).
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.
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.
• 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:
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/
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
19