Interface


// Hover to preview • Click to pin • Esc to unpin

Interface
├── 
│   └── 
│   ├── 
├── 
│   ├── Web-based UI (PyScript & JavaScript)
│   ├── Dedicated UI (iOS & watchOS)
│   ├── Local Standalone UI
│   └── Local-backended UI
└── 
    ├── R Packages
    └── Python Interpreter

  

Kernel Driver on Raspberry Pi 5


Table of Contents

Character Driver

nGene Character Driver

nGene Character Driver — signal-based interrupt (SIGIO)

nGene Character Driver — Poll/Select/Epoll readiness

Why signal-based interrupts matter for the nGene character driver (v0.1.8) (Written August 15, 2025)

poll() callback and SIGIO synergy for nGene v0.1.8 (Written August 15, 2025)

nGene Character Misc Driver


Interfacing with Devices

Understanding GPIO, PWM, I²C, SPI, and other Raspberry Pi 5 interfaces (Written August 13, 2025)


GPIO

nGene LED Character Misc Driver

nGene Switch Interrupt Driver

Device tree overlays for the nGene switch interrupt driver (v0.1.0-dev) (Written August 19, 2025)

PWM

nGene LED PWM Character Misc Driver

I²C @ GPIO2 (pin 3) / GPIO3 (pin 5)

nGene I²C BMP280 Character Misc Driver (Temperature sensor)

nGene I²C MPU-6050 Character Misc Driver (Gyroscope sensor)

nGene I²C LCD Character Misc Driver

Sense HAT

nGene Sense HAT Driver

Sense HAT bring-up and feature exploration(Written August 20, 2025)

Practical Sense HAT IMU calibration and human-readable tools (Written August 20, 2025)


USB Driver

nGene USB Driver for OSR USB FX2

nGene USB Driver for Cypress CYUSB3KIT-003




User-level Device Interface


Table of Contents

Build HAT

Raspberry Pi Build HAT on Raspberry Pi 4 with LEGO Spike (Written August 21, 2025)


BrickPi

BrickPi3 on Raspberry Pi 4: from first connection to a successful motor spin (with troubleshooting log) (Written August 24, 2025)

LEGO NXT Snatcher Prototype III (BrickPi3 + Raspberry Pi 4): reproducible setup and run log (Written January 3, 2026)


Raspberry Pi AI Kit (M.2 HAT+ + Hailo-8) and a CSI camera module (IMX708 class)

Clean re-install playbook for Raspberry Pi 5 AI Kit and camera (Written January 4, 2026)

Camera verification and 5-second MP4 test video on Raspberry Pi 5 (IMX708) (Written January 4, 2026)

Local Hailo person recognition using CSI camera (assumed working) (Written January 4, 2026)


Lego Spike Pro - Technic Large Hub (45601)

Getting started with LEGO SPIKE Large Hub Python motor control (Written September 6, 2025)


Gyro Boy

Lego SPIKE MicroPython

Why EV3 GyroBoy Demonstrates Superior Stability and How 0.1.9-dev Can Be Upgraded by Benchmarking Its Gyro Script (Written September 16, 2025)


Kernel Driver


Character Driver



I. Kernel Driver Quick Guide (v0.1.7-dev)

Build

make clean && make

Load (examples)

Verify load (major/minor and devnode)

cat /proc/devices | grep nGene
ls -l /dev/nGene_char_driver

Driver logs (Raspberry Pi OS Bookworm+)

sudo dmesg | tail -30
sudo journalctl -k -f

Check / Unload

sudo /sbin/rmmod nGene_char_driver

Device node

/proc interface

cat /proc/nGene_proc_char_device
echo "Hello" | sudo tee /proc/nGene_proc_char_device

Module params on sysfs (selected)

ls /sys/module/nGene_char_driver/parameters/
# -> TIMEOUT_MS, nparam_int, nparam_str

Timer — verification

The driver arms a periodic kernel timer. Each expiration logs a line and re-arms for TIMEOUT_MS.

  1. Load with faster tick (e.g., 1s):
    sudo /sbin/insmod ./nGene_char_driver.ko TIMEOUT_MS=1000
  2. Watch kernel log:
    sudo journalctl -k -f
    # Expect messages like:
    # nGene: timer_callback TIME[<n>]
  3. Exercise write paths (visible in logs):
    echo hello | sudo tee /dev/nGene_char_driver
    echo world | sudo tee /proc/nGene_proc_char_device
    # Expect logs such as:
    # nGene: [BUFFER WRITE] <N> bytes
    # nGene: PROC - world

II. User-space App Guide (v0.1.7-dev)

Build

make user

Run (permissions)

sudo ./user_level_app ...  (device node usually requires root)

IOCTL ops / Flags

Examples

sudo ./user_level_app -i
sudo ./user_level_app -r
sudo ./user_level_app -w ints=7,8,9 strs=hello,world
sudo ./user_level_app -w ints=1,2,3
sudo ./user_level_app -w strs=alpha,beta,gamma
sudo ./user_level_app -c

Notes


Show v0.1.6 Quick Guide (older)

I. Kernel Driver Quick Guide (v0.1.6)

Build

make clean && make

Load (examples)

  • Basic:
    sudo /sbin/insmod ./nGene_char_driver.ko
    
  • With integer params:
    sudo /sbin/insmod ./nGene_char_driver.ko nparam_int=1,2,3
    
  • With string params:
    sudo /sbin/insmod ./nGene_char_driver.ko nparam_str=alpha,beta,gamma
    
  • Mixed (ints, strs, timeout):
    sudo /sbin/insmod ./nGene_char_driver.ko nparam_int=10,20 nparam_str=pi,robotics TIMEOUT_MS=60000
    

Verify load (major/minor and devnode)

cat /proc/devices | grep nGene
ls -l /dev/nGene_char_driver

Driver logs (Raspberry Pi OS Bookworm+)

sudo dmesg | tail -30
sudo journalctl -k -f

Check / Unload

sudo /sbin/rmmod nGene_char_driver

Device node

  • udev creates /dev/nGene_char_driver automatically.

/proc interface

cat /proc/nGene_proc_char_device
echo "Hello" | sudo tee /proc/nGene_proc_char_device

Module params on sysfs (selected)

ls /sys/module/nGene_char_driver/parameters/
# -> TIMEOUT_MS, nparam_int, nparam_str

II. User-space App Guide (v0.1.6)

Build

make user

Run (permissions)

sudo ./user_level_app ...   (device node usually needs root)

IOCTL ops / Flags

  • -i : show verbose info (driver name, device path, major/minor, TIMEOUT_MS, then state)
  • -r : GET_STATE (print ints/strs)
  • -w : SET_STATE with optional ints=.. and/or strs=..
  • -c : CLEAR state

Examples

sudo ./user_level_app -i
sudo ./user_level_app -r
sudo ./user_level_app -w ints=7,8,9 strs=hello,world
sudo ./user_level_app -w ints=1,2,3
sudo ./user_level_app -w strs=alpha,beta,gamma
sudo ./user_level_app -c

Show v0.1.4 Quick Guide (older)

I. Kernel Driver Quick Guide

Build

make

Load (examples)

  • Basic:
    sudo /sbin/insmod ./nGene_char_driver.ko MAJOR_NUM=90 param_int_vars=1,2,3
    
  • Legacy alias accepted:
    sudo /sbin/insmod ./nGene_char_driver.ko MAJOR_NUM=90 param=1,2,3
    
  • Array-only style (first element becomes MAJOR):
    sudo /sbin/insmod ./nGene_char_driver.ko param_int_vars=90,1,1
    
  • Explicit flags:
    sudo /sbin/insmod ./nGene_char_driver.ko MAJOR_NUM=90 STATIC_CDEV=1
    

Install + modprobe

sudo mkdir -p /lib/modules/$(uname -r)/extra
sudo cp ./nGene_char_driver.ko /lib/modules/$(uname -r)/extra/
sudo depmod -a
sudo /sbin/modprobe nGene_char_driver MAJOR_NUM=90 param_int_vars=1,2,3

Seeing driver logs on Raspberry Pi OS (Bookworm+)

  • Note: tail -f /var/log/syslog or tail -f /var/log/messages
    /var/log/syslog and /var/log/messages usually do not exist.
  • Use one of these:
    sudo dmesg -w                      # live kernel ring buffer
    sudo journalctl -k -f              # follow kernel logs
    sudo journalctl -k --since "5 min ago"
    
  • Optional: enable persistent journal (survives reboot)
    sudo mkdir -p /var/log/journal
    sudo sed -i 's|^#*Storage=.*|Storage=persistent|' /etc/systemd/journald.conf
    sudo systemctl restart systemd-journald
    
  • Optional (legacy /var/log/syslog):
    sudo apt-get update && sudo apt-get install -y rsyslog
    sudo systemctl enable --now rsyslog
    

Check / Unload

lsmod | grep nGene
sudo dmesg -w | tail -n 60
sudo /sbin/rmmod nGene_char_driver

Device node

  • udev creates /dev/nGene_char_driver automatically.
  • Manual node (if needed):
    mknod /dev/nGene_char_driver c <major> 0
    
  • Permissions for quick tests:
    sudo chmod 666 /dev/nGene_char_driver
    

/proc interface

cat /proc/nGene_proc_char_device
echo "Hello" | sudo tee /proc/nGene_proc_char_device

Module params on sysfs

ls /sys/module/nGene_char_driver/parameters/
# => param_int_vars, param (alias), MAJOR_NUM, STATIC_CDEV

Parameter precedence

  1. Explicit MAJOR_NUM / STATIC_CDEV (highest)
  2. param_int_vars[0]MAJOR_NUM, param_int_vars[1]STATIC_CDEV (fallback)
  3. Legacy alias "param=" is accepted identically to "param_int_vars="

II. User-space App Guide

Build

make app     # or just `make` to build module + app

Run (permissions)

sudo ./app ...   (device node usually needs root)

IOCTL ops

  • Read struct:
    sudo ./app -r
    
  • Write struct (prompts for int; sends "TYPENEW"):
    sudo ./app -w
    
  • Clear struct:
    sudo ./app -c
    

Info summary

  • Same as -i:
    sudo ./app
    
  • Explicit:
    sudo ./app -i
    

Change what the PREVIEW shows (write human-readable data to the device)

  • Simple text:
    echo -n "Hello nGene Pi5!" | sudo tee /dev/nGene_char_driver >/dev/null
    sudo ./app -i
    
  • With newlines / carriage returns:
    printf "Line1\nLine2\r\n" | sudo tee /dev/nGene_char_driver >/dev/null
    sudo ./app -i
    
  • Random printable sample:
    head -c 64 /dev/urandom | tr -dc 'A-Za-z0-9 .,_-\n' | head -c 64 | sudo tee /dev/nGene_char_driver >/dev/null
    sudo ./app -i
    

See driver logs on Raspberry Pi OS (Bookworm+)

  • sudo dmesg -w
    sudo journalctl -k -f
    
  • Optional: enable persistent logs
    sudo mkdir -p /var/log/journal
    sudo sed -i 's|^#*Storage=.*|Storage=persistent|' /etc/systemd/journald.conf
    sudo systemctl restart systemd-journald
    
  • Optional (legacy /var/log/syslog):
    sudo apt-get install -y rsyslog
    sudo systemctl enable --now rsyslog
    

Optional udev rule to avoid sudo

# /etc/udev/rules.d/99-ngene.rules
KERNEL=="nGene_char_driver", MODE="0666"

# Apply:
sudo udevadm control --reload
sudo udevadm trigger

Driver load examples (from the app guide)

sudo /sbin/insmod ./nGene_char_driver.ko MAJOR_NUM=90 param_int_vars=1,2,3
sudo /sbin/modprobe nGene_char_driver MAJOR_NUM=90 param_int_vars=1,2,3

Written on August 10, 2025


nGene Character Driver — signal-based interrupt (SIGIO)


I. Kernel Driver Quick Guide (v0.1.8)

Build

make clean && make

Load (examples)

Verify load (major/minor and devnode)

cat /proc/devices | grep nGene
ls -l /dev/nGene_char_driver

Driver logs (Raspberry Pi OS Bookworm+)

sudo dmesg | tail -30
sudo journalctl -k -f

Check / Unload

sudo /sbin/rmmod nGene_char_driver

Device node

/proc interface

cat /proc/nGene_proc_char_device
echo "Hello" | sudo tee /proc/nGene_proc_char_device   # also triggers SIGIO to async listeners

Module params on sysfs (selected)

ls /sys/module/nGene_char_driver/parameters/
# -> TIMEOUT_MS, nparam_int, nparam_str

Signals-by-Interrupt (SIGIO) — Verification

The driver sends SIGIO to any process that opened the device and enabled O_ASYNC. Events that trigger SIGIO: timer tick, writes to /dev/nGene_char_driver, writes to /proc/nGene_proc_char_device.

  1. Terminal A (receiver):
    sudo ./user_level_app -a
    # Prints "SIGIO received" upon events and waits a full 5s (robust against EINTR).
    
  2. Terminal B (event generators):
    echo irq   | sudo tee /proc/nGene_proc_char_device
    echo hello | sudo tee /dev/nGene_char_driver
    # Each line above should cause Terminal A to print "SIGIO received".
    
  3. Timer-driven SIGIO:
    # If loaded with TIMEOUT_MS=1000, Terminal A should print about once per second.
    
  4. What to watch in kernel logs:
    # journalctl -k -f
    # Expect messages such as:
    #   nGene: timer_callback TIME[<n>]
    #   nGene: PROC - irq
    #   nGene: [BUFFER WRITE] <N> bytes
    # (Linux does not log "signal delivered"; the lines above indicate the event source.)
    

II. User-space App Guide (v0.1.8)

Build

make user

Run (permissions)

sudo ./user_level_app ...   (device node usually needs root)

IOCTL ops / Flags

Examples

sudo ./user_level_app -i
sudo ./user_level_app -r
sudo ./user_level_app -w ints=7,8,9 strs=hello,world
sudo ./user_level_app -w ints=1,2,3
sudo ./user_level_app -w strs=alpha,beta,gamma
sudo ./user_level_app -c
sudo ./user_level_app -T
sudo ./user_level_app -t 1000
sudo ./user_level_app -a

Notes


nGene Character Driver — Poll/Select/Epoll readiness


I. Kernel Driver Quick Guide (v0.1.8b)

What’s new vs 0.1.8: event readiness via poll/select/epoll, clean quiesce/HUP through /proc, and a dedicated iotest demo. No SIGIO in this build.

Build

make clean && make
make user    # builds user_level_app and iotest

Load (examples)

Verify load (major/minor and devnode)

cat /proc/devices | grep nGene
ls -l /dev/nGene_char_driver

Driver logs (Raspberry Pi OS Bookworm+)

sudo dmesg | tail -30
sudo journalctl -k -f

Device node

/proc interface (quiesce / wake-all)

cat /proc/nGene_proc_char_device
echo "Hello" | sudo tee /proc/nGene_proc_char_device
echo hup | sudo tee /proc/nGene_proc_char_device     # sets quiesce/HUP, wakes all waiters

Module params on sysfs (selected)

ls /sys/module/nGene_char_driver/parameters/
# -> TIMEOUT_MS, nparam_int, nparam_str

Clean unload

# (optional) see who holds the device
sudo fuser -v /dev/nGene_char_driver

# make user-space unblock & exit fast, then remove
echo hup | sudo tee /proc/nGene_proc_char_device
sudo /sbin/rmmod nGene_char_driver

II. User-space Tools (v0.1.8b)

A) iotest — poll/select/epoll demo

Run (typical)
sudo ./iotest -m poll   /dev/nGene_char_driver
sudo ./iotest -m select /dev/nGene_char_driver
sudo ./iotest -m epoll  /dev/nGene_char_driver
Produce events
# In another terminal:
printf 'hello world' | sudo tee /dev/nGene_char_driver
# Expect on iotest: POLLIN (and a read of the payload)
Back-pressure (single-message model)
# Reader:
sudo ./iotest -m poll /dev/nGene_char_driver

# Writer (terminal B):
echo "A" | sudo tee /dev/nGene_char_driver
echo "B" | sudo tee /dev/nGene_char_driver   # blocks until "A" is consumed
Quiesce / exit all waiters
# With iotest waiting, do:
echo hup | sudo tee /proc/nGene_proc_char_device
# iotest prints POLLHUP|POLLERR and exits the wait loop
Troubleshooting

B) user_level_app — IOCTL/state helper

Build & run
make user
sudo ./user_level_app -i
IOCTL ops / Flags
Examples
sudo ./user_level_app -i
sudo ./user_level_app -r
sudo ./user_level_app -w ints=7,8,9 strs=hello,world
sudo ./user_level_app -w ints=1,2,3
sudo ./user_level_app -w strs=alpha,beta,gamma
sudo ./user_level_app -c

Notes


Why signal-based interrupts matter for the nGene character driver (v0.1.8) (Written August 15, 2025)

I. Executive summary

Signal-driven notification provides a lightweight and immediate way for a character driver to inform user space that an event has occurred without busy-waiting or frequent polling. In nGene v0.1.8, SIGIO via FASYNC is integrated to surface three classes of events: periodic timer ticks (simulating a hardware interrupt), writes to the device node, and writes to the companion /proc entry. This design is well-suited for low-rate, control-plane notifications where latency and simplicity are valued and where payload can be obtained afterward via read or ioctl.

The approach is intentionally conservative: signals carry no data and may coalesce under load, so the driver maintains state separately and exposes read/ioctl paths for any substantive retrieval. This combination keeps the notification path fast and the data path explicit and reliable.

II. Architectural context in nGene v0.1.8

The following elements implement the signal-by-interrupt path:

III. Why signals are beneficial for this driver

  1. Eliminating needless polling

    Polling imposes CPU overhead, adds wake-ups, and complicates power management. By contrast, a signal interrupts the process only when an event occurs, allowing the user process to remain idle between events. This is particularly valuable on embedded targets such as Raspberry Pi systems typically used in laboratory settings.

  2. Preserving simplicity for control-plane events

    For low-throughput control-plane notifications (e.g., “a new timer tick occurred” or “an external poke arrived”), the cost and complexity of building buffered queues can be unnecessary. Signals keep the notification path minimal while pushing any substantial data retrieval to explicit read/ioctl calls.

  3. Broadcasting to multiple listeners

    Any process that has the device open with O_ASYNC receives the notification. This allows diagnostics, logging tools, and a primary controller to observe events concurrently without constructing additional multiplexing infrastructure.

  4. Establishing an interrupt-like mental model

    The timer and the /proc write path simulate hardware interrupt sources. This helps validate end-to-end responsiveness early in development, long before the device is integrated with real hardware lines or DMA paths.

IV. Where signals are not sufficient—and how this driver addresses it

  1. No payload and possible coalescing

    Signals do not carry payload, and multiple events may coalesce into a single notification under load. The driver therefore keeps event state separate and expects user space to query details via read or ioctl after a signal arrives.

  2. Ownership semantics and routing

    SIGIO targets the process or process group specified via F_SETOWN. This is adequate for the nGene demonstration and multi-observer scenarios but is not a substitute for fine-grained per-stream routing. For richer routing needs, eventfd or signalfd can be introduced alongside or in place of SIGIO.

  3. High-rate data and back-pressure

    For high-throughput data planes, a buffered wait-queue with poll/epoll, eventfd, or a lock-free ring buffer is preferable. nGene v0.1.8 deliberately keeps signals for control-plane timing and external pokes while leaving the data plane to conventional file operations.

V. Comparative view of notification options

Mechanism Strengths Limitations Best fit nGene v0.1.8 usage
SIGIO (FASYNC) Immediate wake-up; minimal code; multi-listener broadcast No payload; events may coalesce; owner semantics apply Low-rate control-plane “something happened” notifications Primary notification for timer and external pokes
poll/select/epoll Scalable readiness model; integrates with FD loops Requires well-defined readiness and buffering Data-plane readiness and structured consumption Available via .poll; always-ready in demo
eventfd / signalfd FD-centric; easy epoll integration; counters with eventfd More code; not broadcast by default Structured, countable events; clean integration with epoll Candidate for future enhancement
wait queues + read Precise blocking semantics; natural for data readiness More driver complexity; requires buffer discipline Throughput-oriented paths with back-pressure Out of scope for the minimal control-plane demo
netlink / uevent Structured messages; user/kernel multi-cast Heavier weight; more moving parts System-wide eventing beyond a single device Not required for this driver’s scope

VI. Event mapping in the nGene driver

Source Kernel path Signal Driver action Intended user-space reaction
Timer tick nGene_core.c::timer_callback() SIGIO kill_fasync(..., SIGIO, POLL_IN) Observe tick; optionally read state or adjust timing
Write to device nGene_fops.c::device_write() SIGIO kill_fasync(..., SIGIO, POLL_IN) React to external stimulus; inspect buffer if needed
Write to /proc nGene_proc.c::device_proc_write() SIGIO kill_fasync(..., SIGIO, POLL_IN) Handle “out-of-band” poke; read/ioctl for details
State change via ioctl NGENE_IOC_SET_STATE / CLEAR SIGIO kill_fasync(..., SIGIO, POLL_PRI/OUT) Optionally refresh state and update UI

VII. Usage pattern and verification

The recommended usage aligns with a control-plane signal-and-query pattern. The following steps demonstrate the end-to-end path:

The signal announces “an event occurred.” Any payload or counters should be obtained immediately afterward via read or ioctl to ensure correctness in the presence of coalesced signals.

VIII. Reliability and timing considerations

IX. Practical guidance for productionizing

X. Conclusion

Signal-based notification in nGene v0.1.8 offers a disciplined and efficient pathway for surfacing interrupt-like conditions to user space, keeping the notification path fast while delegating payload retrieval to explicit operations. The result is a clean separation of concerns: signals announce, file operations deliver. For the driver’s instructional and research objectives—where prompt awareness of state transitions matters more than bulk data throughput—this design delivers a balanced foundation that can be incrementally extended with eventfd, signalfd, or buffered readiness when the application demands evolve.

Written on August 15, 2025


poll() callback and SIGIO synergy for nGene v0.1.8 (Written August 15, 2025)

I. Original verdict and guidance

Verdict
  Yes — adding a real poll() callback is synergistic with v0.1.8’s SIGIO; it is not redundant.

Why it helps
  • Provides structured readiness for select/poll/epoll event loops.
  • Mitigates SIGIO coalescing by exposing persistent “event pending” bits.
  • Enables future back-pressure (gate EPOLLIN on bytes available or flags).
  • Supports mixed usage: broadcast SIGIO for immediate attention + FD readiness for orderly consumption.

When SIGIO alone suffices
  • Low-rate, single-observer demos where a simple “nudge + read/ioctl” pattern is adequate.

Minimal implementation delta
  • Add wait_queue_head_t and an atomic/bitmask of event flags to device state.
  • On each event: set flags → wake_up_interruptible(&wq) → kill_fasync(...).
  • Implement poll(): poll_wait(...,&wq,...) and return EPOLLIN only when flags are set.
  • Clear/ack flags after handling (via read/ioctl), not inside poll().

Bottom line
  • Keep SIGIO for immediate, lightweight interrupts; add poll() to integrate cleanly with FD-driven reactors and to scale.

II. Supplementary table — why poll() and SIGIO are synergistic

Concern What SIGIO provides What poll/select/epoll provides Combined benefit in nGene v0.1.8 Implementation cue
Immediate attention Asynchronous nudge to listeners with minimal latency Fast wake-up for UIs/handlers without busy-waiting kill_fasync(..., SIGIO, POLL_IN) on each event
Structured consumption “Something happened” (no readiness contract) Clear readiness model for FD loops Signals notify; epoll drives orderly reads/ioctls poll_wait() + compute mask from event flags
Coalescing protection May coalesce multiple events into one signal Level-triggered flags persist until acknowledged No missed edges; pending bits survive bursts Bitmask (e.g., EV_TIMER, EV_DEV_WRITE, EV_PROC_WRITE, EV_STATE)
Back-pressure & scaling Does not encode queue depth or bytes available Gate EPOLLIN on bytes-available / ring occupancy Natural path to a throughput data plane when added Return EPOLLIN only when readable state is true
Multi-observer semantics Broadcast to all O_ASYNC owners One reactor can multiplex many FDs deterministically Diagnostics and controller can coexist cleanly Keep SIGIO; add epoll loop in main process
Power & CPU efficiency Wakes only on events Blocks efficiently until readiness Low wake-up overhead with no polling spin Use wait queues (wake_up_interruptible)
Error isolation Handler must stay minimal Heavy work stays in main loop Robustness: signal = nudge, loop = work Do minimal work in handler; defer via flags
Future extensibility Works well for control-plane nudges Naturally extends to eventfd/signalfd and rings Straightforward upgrade path as needs grow Add eventfd counters if drop detection is required

III. Definitions of key technical terms

Term Plain definition Meaning in this driver
File descriptor (FD) A small integer that identifies an open file or device in a process The handle returned by open("/dev/nGene_char_driver")
FD readiness Whether an FD can be read/written without blocking Derived from event flags; governs EPOLLIN/EPOLLOUT returned by poll()
poll / select / epoll APIs to wait for one or many FDs to become ready Apps block until nGene signals readiness via device_poll()
EPOLLIN Readable readiness: reading will not block Set when nGene has a pending event or readable state
EPOLLOUT Writable readiness: writing will not block Returned continually for the minimal demo (no back-pressure)
EPOLLPRI Urgent/priority data available Optional signal for state changes (e.g., POLL_PRI band)
EPOLLET (edge-triggered) Notify only on state transitions, not while level persists Optional mode in user space; default example is level-triggered
Level-triggered vs edge-triggered Level: readiness remains until cleared; Edge: notify on change Driver exposes level semantics via persistent event flags
poll_wait() Registers a wait queue so the caller can be woken on events Used inside device_poll() to sleep until wake_up_interruptible()
wait_queue_head_t Kernel queue that tracks sleepers for an event nGene_char_device.wq wakes pollers when flags change
kill_fasync() Sends SIGIO to processes that enabled async I/O on the FD Used to nudge listeners immediately on each event
fasync_struct Kernel structure listing async-signal subscribers Stored in nGene_char_device.async_queue
O_ASYNC FD flag that enables SIGIO delivery on I/O events Set by the test app to receive signals
F_SETOWN Specifies the PID or process group to receive SIGIO Test app sets ownership of the device FD before arming O_ASYNC
O_NONBLOCK Non-blocking I/O flag for an FD Prevents reads/writes from sleeping; pairs well with event loops
Back-pressure Mechanism that slows producers when consumers lag Future data-plane can gate EPOLLIN on buffer/ring occupancy

IV. Practical mapping for nGene v0.1.8

V. Typical application patterns

SIGIO-only (control-plane notifications without an event loop)

When appropriate: low-rate events, simple demos, diagnostics tools, or scripts that benefit from immediate attention without maintaining a central event loop.

// User-space sketch
fd = open("/dev/nGene_char_driver", O_RDWR | O_NONBLOCK);
sigaction(SIGIO, &sa, NULL);
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_ASYNC | O_NONBLOCK);
// Handler prints a nudge; main loop issues read/ioctl to fetch details

poll/epoll-only (event-loop readiness without signals)

When appropriate: applications already structured around select/poll/epoll that manage many FDs (timers, sockets, devices) in one loop, requiring level-triggered readiness and a path to back-pressure.

// User-space sketch
fd = open("/dev/nGene_char_driver", O_RDWR | O_NONBLOCK);
ep = epoll_create1(EPOLL_CLOEXEC);
ev.events = EPOLLIN; ev.data.fd = fd;
epoll_ctl(ep, EPOLL_CTL_ADD, fd, &ev);
for (;;) {
  n = epoll_wait(ep, events, N, -1);
  // On EPOLLIN: read/ioctl, then acknowledge handled flags if provided
}

SIGIO + poll/epoll (hybrid: immediate nudge + structured readiness)

When appropriate: operator UIs, controllers, or labs that want immediate attention on state transitions while keeping a robust reactor for orderly work and future scaling.

// Kernel on event
ev_flags |= EV_TIMER;                          // mark pending
wake_up_interruptible(&wq);                    // wake pollers
if (async_queue) kill_fasync(&async_queue, SIGIO, POLL_IN); // nudge listeners

// User-space topology
//  - UI thread: receives SIGIO (instant feedback)
//  - Worker thread: epoll() loop reads/queries details and ACKs flags
Pattern Best fit Latency Scales to many FDs Back-pressure path Implementation effort
SIGIO-only Low-rate control-plane nudges; quick demos; diagnostics Very low (immediate signal) Limited No (requires extra plumbing) Lowest
poll/epoll-only Event-loop apps; many FDs; structured consumption Low (on readiness) Excellent Yes (gate EPOLLIN/bytes available) Moderate
Hybrid (SIGIO + poll/epoll) UI + worker models; responsiveness + order Very low (signal) + orderly loop Excellent Yes (use readiness gating) Moderate (two paths)

Written on August 15, 2025


nGene Character Misc Driver

Written on August 15, 2025


Interfacing with Devices


Understanding GPIO, PWM, I²C, SPI, and other Raspberry Pi 5 interfaces (Written August 13, 2025)

This note offers a structured overview of common hardware interfaces on Raspberry Pi 5—extending beyond GPIO, PWM, I²C, and SPI—clarifies their differences, and explains which approach best suits an LED on/off example. The tone remains deliberately modest and precise, and the organization emphasizes clear hierarchy for publication.

I. General-purpose input/output (GPIO)

GPIO pins are configurable digital lines that operate either as inputs (sensing logical high/low) or outputs (driving logical high/low). They are protocol-agnostic and serve as the foundational mechanism for toggling signals, latching states, and simple device enable/disable control. Alternate functions on many pins can remap them to specific peripherals (e.g., I²C, SPI, UART), but in default mode they behave as generic digital pins.

In the provided LED driver, the lines corresponding to “GPIO 17” and “GPIO 18” (resolved to Linux global GPIO numbers via the gpiochip base) are used strictly as digital outputs. No PWM or serial protocol is generated by the module; the pins are set to 0 or 1 to turn the LED fully off or on.

II. Peripheral interfaces on Raspberry Pi 5: concepts and differences

The following subsections summarize major interfaces commonly exposed on the 40-pin header or board connectors of Raspberry Pi 5. Each item begins with a short definition, followed by typical uses, advantages, and limitations.

  1. Pulse Width Modulation (PWM)

    PWM is a modulation technique rather than a distinct bus. A pin is toggled at a fixed frequency, and the duty cycle (percentage of the period spent high) controls average power. On Raspberry Pi, PWM channels can be mapped to certain pins; software can also emulate PWM at a cost in CPU timing or jitter.

    • Primary uses: LED dimming, DC motor speed control, hobby servos (with appropriate pulse timing).
    • Advantages: Efficient brightness/speed control using digital hardware; fine resolution without analog DACs.
    • Limitations: Requires PWM-capable pins or careful software timing; LED loads benefit from current limiting and sometimes filtering.
  2. Inter-Integrated Circuit (I²C)

    I²C is a two-wire, open-drain, multi-drop serial bus with clock (SCL) and data (SDA). Devices share the bus via unique addresses and pull-up resistors are required. It favors moderate speed and simplicity over bandwidth.

    • Primary uses: Sensors, real-time clocks, GPIO expanders, LED driver ICs, small displays.
    • Advantages: Many peripherals on two wires; straightforward addressing and configuration.
    • Limitations: Lower throughput than SPI; total bus capacitance and wiring length are constrained.
  3. Serial Peripheral Interface (SPI)

    SPI is a high-speed synchronous bus with separate lines for data in/out and a dedicated chip-select per device. It is simple and fast but uses more wires than I²C.

    • Primary uses: High-rate sensors, displays, external flash, LED drivers, DAC/ADC devices.
    • Advantages: High bandwidth, full-duplex, low protocol overhead.
    • Limitations: Requires one chip-select per device (or bus multiplexing); wiring scales with device count.
  4. Universal Asynchronous Receiver/Transmitter (UART)

    UART provides asynchronous serial communication (e.g., 3.3 V TTL at configurable baud rates). It uses TX/RX lines and optional flow control (RTS/CTS).

    • Primary uses: Debug consoles, GNSS modules, serial peripherals, point-to-point device links.
    • Advantages: Very simple wiring; ubiquitous support.
    • Limitations: Point-to-point; lower bandwidth compared to SPI; timing tolerance matters at high baud.
  5. PCM/I²S (digital audio)

    PCM/I²S transports digital audio using a bit clock, word clock, and data line(s). It targets deterministic, continuous data streams.

    • Primary uses: External audio codecs, DACs, and ADCs; microphone arrays; speakers.
    • Advantages: Low-jitter audio transport; standardized framing.
    • Limitations: Not intended for general control; continuous stream management required.
  6. General-Purpose Clock (GPCLK)

    GPCLK outputs a programmable clock on certain pins. Useful as a reference clock for external logic or as a carrier for specific encodings.

    • Primary uses: Clocking external devices, radio/modulation experiments.
    • Advantages: Hardware-generated stable clocks.
    • Limitations: Limited pin availability; not a data protocol by itself.
  7. Parallel/DPI (display parallel interface)

    DPI exposes a parallel RGB interface for certain displays. It consumes many pins and is less common on compact builds but enables deterministic high-rate pixel output.

    • Primary uses: Parallel TFT displays requiring continuous pixel clocks.
    • Advantages: High throughput; direct drive of compatible panels.
    • Limitations: Heavy pin usage; tight timing requirements.
  8. MIPI CSI-2 and DSI (camera and display serial interfaces)

    CSI-2(camera) and DSI(display) are high-speed differential serial links available on the board’s dedicated FFC connectors. They are not on the 40-pin header and require matching modules.

    • Primary uses: Official and compatible cameras and displays.
    • Advantages: Very high bandwidth; standardized ecosystems.
    • Limitations: Dedicated connectors and drivers; not general-purpose.
  9. PCI Express, USB, Ethernet, and SDIO (board-level I/O)

    These are board-level or connector-level interfaces rather than header pins. Raspberry Pi 5 exposes PCIe for high-speed peripherals, multiple USB ports, Gigabit Ethernet, and SDIO for the onboard microSD interface.

    • Primary uses: High-speed storage, networking, and expansion (PCIe).
    • Advantages: Mature, high-throughput standards.
    • Limitations: Not used through the 40-pin GPIO header; require appropriate hardware endpoints.
  10. Emulated or protocol-on-GPIO techniques

    Some protocols are implemented in software (“bit-banging”) on GPIO, or by leveraging PWM/PCM with DMA to produce precise waveforms (e.g., addressable LEDs).

    • Examples: One-Wire on a GPIO; WS2812/NeoPixel signaling via PWM or PCM + DMA; software-I²C/SPI/UART for low speeds.
    • Advantages: Flexibility when hardware peripherals are busy or unavailable.
    • Limitations: CPU overhead, timing sensitivity, and potential jitter.

III. Interface comparison at a glance

Interface Wires (typical) Speed (typical) Topology Typical roles Notes
GPIO (digital) 1 per signal Instant on/off Point/any Switches, enables, binary LEDs Simplest control; no protocol
PWM 1 (plus GND) kHz–tens of kHz Point-to-load LED dimming, motors, servos Analog-like control via duty cycle
I²C 2 (+ GND) Up to a few MHz (mode-dependent) Multi-drop Sensors, expanders, LED drivers Pull-ups required; addressable
SPI 4+ (per device CS) MHz–tens of MHz Star (via CS) Displays, flash, fast ADC/DAC High throughput; more wiring
UART 2 (TX/RX) ± RTS/CTS kbps–Mbps Point-to-point Debug, GNSS, modems Asynchronous; simple
PCM/I²S 3–4 kHz–MHz (audio clocks) Point/chain Audio codecs, DACs/ADCs Continuous streams
GPCLK 1 Programmable N/A Reference clocks Clock source, not data bus

IV. Applying these concepts to LED control

An LED can be controlled through several of the above mechanisms. The best choice depends on whether simple on/off is sufficient, or whether dimming, scaling to many LEDs, or precise patterns are required.

Approach How it drives the LED When it is good Potential drawbacks
GPIO (digital on/off) Sets pin high for ON, low for OFF; current-limit resistor in series Binary indicators, status LEDs, minimal software complexity No native brightness control; one pin per LED
PWM (hardware or timed software) Varies duty cycle to modulate apparent brightness Dimming, smooth transitions, power-efficient control Requires PWM channels or careful timing; audible noise if frequency is low
I²C LED drivers External IC sources/sinks LED currents; often includes per-channel PWM Many LEDs with few wires; uniform brightness via constant current Additional component; I²C bandwidth and address management
SPI LED drivers / matrices High-speed updates to many LEDs or display segments Large arrays, faster animations More wiring; chip-select management
Addressable LEDs (e.g., WS2812-class) Single-wire serial timing from PWM/PCM + DMA or specialized controllers Dozens to thousands of RGB LEDs with one signal wire Tight timing; careful power delivery; library support recommended

For a single LED that should only indicate ON or OFF, standard GPIO is the most direct and robust solution. For brightness control, PWM is preferred. When managing many LEDs with consistent current and per-channel dimming, I²C/SPI LED driver ICs become advantageous. For dense RGB lighting and animations, addressable LED strips are efficient but require precise waveform generation and adequate power design.

V. Interpreting the example driver

The example character driver configures one output line ( linux_out_gpio ) and optionally a second line ( linux_in_gpio ) used in a two-pin “sink” arrangement. The apply_state() routine sets the output pin high or low, and—when two-pin mode is enabled—drives the complementary state on the sink pin. This produces a binary result at the LED. No PWM generation or serial bus transactions are performed by the driver.

In short: the module exercises digital GPIO only. Using GPIO 17 and 18 in this context does not invoke PWM, I²C, or SPI; it simply drives fixed high/low levels to switch an LED on or off.

VI. Practical notes and safety

VII. Conclusion

GPIO, PWM, I²C, SPI, UART, PCM/I²S, GPCLK, and the board-level links such as CSI/DSI and PCIe serve distinct roles on Raspberry Pi 5. For an LED with simple on/off behavior, digital GPIO is both appropriate and efficient. As requirements expand toward dimming, large arrays, or high-speed updates, PWM and bus-controlled LED drivers become the more suitable options, chosen with attention to wiring complexity, timing, and power integrity.

Written on August 13, 2025


GPIO


nGene LED Character Misc Driver



LED Driver Quick Guide (v0.1.1 · Raspberry Pi 5 · Linux 6.12.34+rpt-rpi-2712)

Purpose. Character device that toggles an LED using one or two GPIO lines. Uses legacy integer GPIO API (gpio_request/gpio_direction_*). Registers as a miscdevice so /dev/nGene_led is created automatically.

Directory

driver/char_LED0.1.1/
  ├── nGene_RaspPi5_LED.c
  └── Makefile

Sanity check with libgpiod (confirm wiring first)

gpiodetect
gpioinfo gpiochip0 | grep -E "line +17:|line +18:"
gpioset -m time -s 1 gpiochip0 17=1 18=0    # LED ON  for 1s
gpioset -m time -s 1 gpiochip0 17=0 18=1    # LED OFF for 1s

Important: compute correct Linux “global” GPIO numbers

sudo mount -t debugfs none /sys/kernel/debug 2>/dev/null || true
awk '
  /^gpiochip/ { chip=$1; base=$2; ngpio=$4 }
  /label:/    { lbl=$2; printf("chip=%s base=%s ngpio=%s label=%s\n", chip, base, ngpio, lbl) }
' /sys/kernel/debug/gpio | sed -n '1,120p'

# On your Pi 5 this showed: chip=gpiochip0 base=571 label=pinctrl-rp1
# Therefore:
#   BCM17 → 571+17 = 588
#   BCM18 → 571+18 = 589

Build

make clean && make

Load (examples)

Verify device node

ls -l /dev/nGene_led
# EXPECT: leading 'c' (character device), major 10 (misc), e.g.:
# crw-rw-rw- 1 root root 10, 123 ... /dev/nGene_led

Toggle

echo 1 | sudo tee /dev/nGene_led > /dev/null   # LED ON  => out->1, in->0 (sink engaged)
sleep 1
echo 0 | sudo tee /dev/nGene_led > /dev/null   # LED OFF => out->0, in->1 (sink released)

Driver logs (Raspberry Pi OS Bookworm+)

sudo dmesg | tail -30
sudo journalctl -k -f

Check / Unload

lsmod | grep nGene_RaspPi5_LED
sudo /sbin/rmmod nGene_RaspPi5_LED

Safety

Use a series resistor (330–1000 Ω). GPIOs are 3.3 V only.


Show v0.1.0 Quick Guide (older)

LED Driver Quick Guide (v0.1.0 · Raspberry Pi 5 · Linux 6.12.34+rpt-rpi-2712)

Purpose. Minimal character device that toggles an LED using one or two GPIO lines. Uses legacy integer GPIO API (gpio_request/gpio_direction_*). No gpiod internals.

Directory

driver/char_LED0.1.0/
  ├── nGene_RaspPi5_LED.c
  └── Makefile

Sanity check with libgpiod (confirm wiring first)

gpiodetect
gpioinfo gpiochip0 | grep -E "line +17:|line +18:"
gpioset -m time -s 1 gpiochip0 17=1 18=0    # LED ON for 1s
gpioset -m time -s 1 gpiochip0 17=0 18=1    # LED OFF for 1s

Build

make clean && make

Important: compute correct Linux “global” GPIO numbers

sudo mount -t debugfs none /sys/kernel/debug 2>/dev/null || true
awk '
  /^gpiochip/ { chip=$1; base=$2; ngpio=$4 }
  /label:/    { lbl=$2; printf("chip=%s base=%s ngpio=%s label=%s\n", chip, base, ngpio, lbl) }
' /sys/kernel/debug/gpio | sed -n '1,120p'

# On your Pi 5 this showed: chip=gpiochip0 base=571 label=pinctrl-rp1
# Therefore:
#   BCM17 → 571+17 = 588
#   BCM18 → 571+18 = 589

Load (examples)

  • One-pin mode (BCM17 only; anode on BCM17 via 330–1000 Ω, cathode to GND)
    sudo rmmod nGene_RaspPi5_LED 2>/dev/null || true
    sudo insmod ./nGene_RaspPi5_LED.ko linux_out_gpio=588 linux_in_gpio=-1 sink_with_in_gpio=0
    
  • Two-pin sink mode (BCM17/18; out=588, in=589; ON ⇒ out=1, in=0)
    sudo rmmod nGene_RaspPi5_LED 2>/dev/null || true
    sudo insmod ./nGene_RaspPi5_LED.ko linux_out_gpio=588 linux_in_gpio=589 sink_with_in_gpio=1
    

Create device node from sysfs major:minor (authoritative)

MAJMIN=$(cat /sys/class/nGene_led_class/nGene_led/dev); MAJOR=${MAJMIN%:*}; MINOR=${MAJMIN#*:}
sudo rm -f /dev/nGene_led
sudo mknod /dev/nGene_led c "$MAJOR" "$MINOR"
sudo chmod 666 /dev/nGene_led

Toggle

echo 1 | sudo tee /dev/nGene_led >/dev/null   # LED ON
echo 0 | sudo tee /dev/nGene_led >/dev/null   # LED OFF

Check logs / Unload

sudo dmesg -w
sudo rmmod nGene_RaspPi5_LED

Written on August 13, 2025


nGene Switch Interrupt Driver


nGene Switch Interrupt Driver Quick Guide (v0.1.0-dev · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. Platform driver that binds to a Device Tree overlay node (compatible = "ngene,switch-int") and reads an active-high push button via the switch-gpios property. The driver exposes: /dev/nGene_interrupt_device and /proc/nGene_proc_char_device.

Directory

driver/char_IRQ0.1.0/
  ├── nGene_Switch_Interrupt_driver.c
  ├── ngene-switch-int.dts
  └── Makefile

Prereqs

sudo apt-get install -y device-tree-compiler gpiod

Sanity-check wiring (press the button 3×)

sudo gpiomon --num-events=3 --rising-edge gpiochip0 17
# Expect 3 "RISING EDGE" events as you press.

Build (module + overlay)

make clean && make
# If building manually:
dtc -@ -I dts -O dtb -o ngene-switch-int.dtbo ngene-switch-int.dts

Load the runtime overlay (default BCM pin = 17; change with pin=<N>)

sudo dtoverlay -v ./ngene-switch-int.dtbo pin=17
sudo dtoverlay -l
# Expect a line like: "0:  ngene-switch-int  pin=17"

Dual IRQ setup on Raspberry Pi: overlay loaded and verification output

Verify overlay property (optional)

hexdump -Cv /sys/firmware/devicetree/base/ngene-switch-int/switch-gpios
# Presence indicates the overlay node is live and the GPIO is bound.

Insert the module & watch logs

sudo insmod ./nGene_Switch_Interrupt_driver.ko
sudo journalctl -k -f

What you should see in journalctl when it’s working

ngene: probe: got GPIO, set input, optional debounce ok/ignored
ngene: probe: gpiod_to_irq -> <irq>
ngene: probe: registered threaded IRQ
ngene: chardev: created /dev/nGene_interrupt_device
ngene: proc: created /proc/nGene_proc_char_device
ngene: ready: waiting for rising edges...
ngene: isr: interrupt handled (count=<n>)
ngene: timer: heartbeat...

Read status

cat /proc/nGene_proc_char_device
cat /dev/nGene_interrupt_device

Troubleshooting: “Unknown error 517” (EPROBE_DEFER) on insmod

# 1) Ensure the overlay is loaded:
sudo dtoverlay -l

# 2) If not present, (re)load it:
sudo dtoverlay -v ./ngene-switch-int.dtbo pin=17

# 3) Make sure no userspace program is holding the line:
sudo killall -q gpiomon gpioset gpioget pigpiod 2>/dev/null || true

Unload (driver and overlay)

sudo rmmod nGene_Switch_Interrupt_driver
sudo dtoverlay -l                # note the index (e.g., 0)
sudo dtoverlay -r 0

Written on August 19, 2025


Device tree overlays for the nGene switch interrupt driver (v0.1.0-dev) (Written August 19, 2025)

I. Purpose and scope

This document refines the explanation of why a Device Tree (DT) overlay is required for the nGene Switch Interrupt driver, how to compile and apply the overlay, and—most critically— how to interpret dtoverlay diagnostics and hexdumps to validate a correct result. The target environment is Raspberry Pi 5 (BCM2712) with a recent Linux kernel where GPIO and IRQ ownership are described via the Device Tree.

II. Why a DT overlay is required

A DT overlay provides the kernel with a precise, declarative description of the intended wiring and binding. It creates a platform device whose compatible string matches the driver, and it supplies a switch-gpios property that identifies the GPIO line and polarity used for edge interrupts. This unified description ensures that pin control, IRQ routing, and driver binding share the same source of truth, avoiding conflicts with other overlays and alternate pin functions.

III. Reference overlay source (DTS)

/dts-v1/;
/plugin/;

/*
  nGene Switch Interrupt overlay (v0.1.0-dev)

  Creates a platform device:
    compatible = "ngene,switch-int";
    switch-gpios = <&gpio 17 0>;  (default BCM pin = 17, flags=0 means ACTIVE_HIGH)

  Change the pin at load time:
    sudo dtoverlay ./ngene-switch-int.dtbo pin=<N>
*/

/ {
    compatible = "brcm,bcm2712";

    fragment@0 {
        target-path = "/";
        __overlay__ {
            /* Node name without unit-address to avoid unit_address_vs_reg warning */
            ngene_switch_int: ngene-switch-int {
                compatible = "ngene,switch-int";
                /* flags=0 == GPIO_ACTIVE_HIGH; use 1 for ACTIVE_LOW if needed */
                switch-gpios = <&gpio 17 0>;
                status = "okay";
            };
        };
    };

    /* Override the 2nd cell (the pin index) of the 3-cell 'switch-gpios'
       Cells: [ phandle | pin | flags ]  ->  offset 4 bytes selects 'pin' */
    __overrides__ {
        pin = <&ngene_switch_int>, "switch-gpios:4";
    };
};

IV. Anatomy and expectations

Element What it conveys What to expect at runtime
compatible = "ngene,switch-int" Binding identity for the platform device. Driver probe runs when module is inserted.
switch-gpios = <&gpio 17 0> Three cells: GPIO controller phandle, BCM pin index, flags. Hexdump shows 12 bytes; middle cell equals 0x00000011 for BCM 17; flags 0 for active-high.
__overrides__{ pin = ..., "switch-gpios:4" } Permits pin=<N> on load; edits the pin cell in-place. dtoverlay -v reports “cell target switch-gpios @ offset 4 (size 4)”.
Node name without unit-address Avoids unit_address_vs_reg warning for nodes lacking reg. No warnings during dtc compile or overlay load.

V. Build step

Compile the overlay once from DTS to DTB with symbol support for live patching:

dtc -@ -I dts -O dtb -o ngene-switch-int.dtbo ngene-switch-int.dts

VI. Load step (verbose) and how to verify correctness

Apply the overlay at runtime via configfs and examine the verbose diagnostics to confirm that the correct cell was overridden.

1) Verbose load transcript

sudo dtoverlay -v ./ngene-switch-int.dtbo pin=17
DTOVERLAY[debug]: using platform 'bcm2712'
DTOVERLAY[debug]: overlay map loaded
run_cmd: which dtoverlay-pre >/dev/null 2>&1 && dtoverlay-pre
DTOVERLAY[debug]: loading file './ngene-switch-int.dtbo'
DTOVERLAY[debug]: found override pin
DTOVERLAY[debug]:   override pin: cell target switch-gpios @ offset 4 (size 4)
DTOVERLAY[debug]: wrote 565 bytes to '/tmp/.dtoverlays/0_ngene-switch-int.dtbo'
DTOVERLAY[debug]: wrote 573 bytes to '/sys/kernel/config/device-tree/overlays/0_ngene-switch-int/dtbo'
run_cmd: which dtoverlay-post >/dev/null 2>&1 && dtoverlay-post

2) What to pay attention to

Line to check Why it matters Correct signal
using platform 'bcm2712' Confirms the overlay targets Raspberry Pi 5’s SoC family. Platform string is bcm2712.
loading file './ngene-switch-int.dtbo' Validates that the correct binary overlay is being applied. Path matches the intended .dtbo.
found override pin Indicates that the pin parameter from __overrides__ was recognized. Presence of this line is required.
cell target switch-gpios @ offset 4 (size 4) Shows the exact 32-bit cell being patched (the pin index, second cell). Offset 4, size 4 bytes.
wrote ... to .../overlays/.../dtbo Confirms that the overlay has been committed to the live DT via configfs. Non-zero byte count written.

VII. Confirm the overlay is resident

sudo dtoverlay -l
Overlays (in load order):
0:  ngene-switch-int  pin=17

VIII. Verify the resolved property bytes

The live Device Tree exposes the resolved node and properties. The following hexdump demonstrates the expected layout for switch-gpios.

hexdump -Cv /sys/firmware/devicetree/base/ngene-switch-int/switch-gpios
00000000  00 00 00 2e 00 00 00 11  00 00 00 00              |............|
0000000c

How to make sure it is the right one

Offset / cell Expected value Meaning Validation guidance
0x00–0x03 (cell 0) Non-zero (example: 00 00 00 2e) Phandle to &gpio controller. Value is opaque and may vary; non-zero is sufficient.
0x04–0x07 (cell 1) 00 00 00 11 BCM pin index (decimal 17). Must match the requested pin=<N>. For BCM 17, hex must be 0x00000011.
0x08–0x0B (cell 2) 00 00 00 00 Flags (0 = active-high; 1 = active-low). Confirm that polarity matches the electrical design.
Total length 12 bytes (ends at 0000000c) Three 32-bit big-endian cells. Any other length indicates a malformed property.

IX. Alternative views via /proc/device-tree

The same node and properties can be examined through /proc/device-tree, which is convenient for quick listing and ASCII inspection.

# List the node with surrounding context
ls -R /proc/device-tree | grep -A1 -B1 ngene-switch-int

# Confirm the compatible string (expects ASCII "ngene,switch-int\0")
hexdump -C /proc/device-tree/ngene-switch-int/compatible

# Confirm the GPIO triplet (expects 12 bytes as above)
hexdump -C /proc/device-tree/ngene-switch-int/switch-gpios

X. Troubleshooting cues (quick reference)

Observation Likely cause Action
No found override pin line during verbose load. Parameter name does not match __overrides__ or DTS compiled without -@. Use the exact parameter name pin; recompile with -@.
dtoverlay -l does not list ngene-switch-int. Overlay not applied or failed to load. Re-run dtoverlay -v; check for conflicting overlays in config.txt.
Hexdump middle cell does not match requested pin. Mismatched override or wrong node inspected. Reload with the intended pin=<N>; verify the node path.
Driver insertion reports EPROBE_DEFER (error 517). Overlay not yet active; GPIO/IRQ infra not ready. Load overlay first; verify with dtoverlay -l; attempt insmod again.

XI. Summary

Correct application of the ngene-switch-int overlay is demonstrated by three converging signals: verbose dtoverlay diagnostics that show the pin cell override at offset 4; a resident overlay listing with the chosen parameter; and a 12-byte switch-gpios property whose middle cell equals the selected BCM pin. These checks establish that the kernel’s Device Tree, IRQ routing, and the driver’s binding context are aligned, enabling reliable delivery of rising-edge interrupts from the designated GPIO line.

Written on August 19, 2025


PWM


nGene LED PWM Character Misc Driver



LED PWM Character Misc Driver Quick Guide (v0.1.0 · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. Character device driver that controls an LED via a single GPIO output or optional two-pin sink mode. Provides both ON/OFF control and software-based PWM (implemented with hrtimer and a private ordered workqueue). Operates on any valid GPIO without requiring device-tree overlays. Registers as a miscdevice, creating /dev/nGene_led by default.

Directory

driver/char_LED_PWM0.1.0/
  ├── nGene_RaspPi5_LED_PWM.c
  └── Makefile

Pre-deployment hardware verification

gpiodetect
gpioinfo gpiochip0 | grep -E "line +17:|line +18:"
gpioset -m time -s 1 gpiochip0 18=1   # Set high for 1s
gpioset -m time -s 1 gpiochip0 18=0   # Set low for 1s

Important: Compute correct Linux “global” GPIO numbers

sudo mount -t debugfs none /sys/kernel/debug 2>/dev/null || true
awk '
  /^gpiochip/ { chip=$1; base=$2; ngpio=$4 }
  /label:/    { lbl=$2; printf("chip=%s base=%s ngpio=%s label=%s\n", chip, base, ngpio, lbl) }
' /sys/kernel/debug/gpio | sed -n '1,120p'

# Example (Raspberry Pi 5):
#   gpiochip0 base=571 label=pinctrl-rp1
# Therefore:
#   BCM18 → 571+18 = 589

Build

make clean && make

Load (examples)

Verify device node

ls -l /dev/nGene_led
# EXPECT: leading 'c' (character device), major 10 (misc), e.g.:
# crw-rw-rw- 1 root root 10, 123 ... /dev/nGene_led

Basic toggle (steady ON/OFF)

echo 1 | sudo tee /dev/nGene_led > /dev/null   # LED ON
sleep 1
echo 0 | sudo tee /dev/nGene_led > /dev/null   # LED OFF

Software PWM control

Default: 1 kHz frequency, 50 % duty cycle.

echo "d=50"  | sudo tee /dev/nGene_led > /dev/null   # Set duty to 50 % (starts PWM)
echo "f=500" | sudo tee /dev/nGene_led > /dev/null   # Set frequency to 500 Hz
echo "stop"  | sudo tee /dev/nGene_led > /dev/null   # Stop PWM (maintains output level)
echo "start" | sudo tee /dev/nGene_led > /dev/null   # Resume PWM

Gradual brightness change demonstration

Brightness can be adjusted in steps by varying the PWM duty cycle:

echo "d=0"  | sudo tee /dev/nGene_led > /dev/null   # LED fully off
echo "d=10" | sudo tee /dev/nGene_led > /dev/null   # LED dim
echo "d=90" | sudo tee /dev/nGene_led > /dev/null   # LED bright

Diagnostics and recovery

echo "reset"   | sudo tee /dev/nGene_led > /dev/null  # Stop PWM + force steady LOW
echo "probe"   | sudo tee /dev/nGene_led > /dev/null  # Report live GPIO output level
echo "invert"  | sudo tee /dev/nGene_led > /dev/null  # Toggle software polarity
echo "reclaim" | sudo tee /dev/nGene_led > /dev/null  # Re-request GPIO and re-apply output mode

Read current driver status

cat /dev/nGene_led
# Example output:
# state=1 pwm=running duty_pct=50 period_ns=1000000 freq_hz=1000 out_gpio=589 in_gpio=-1 sink=0 invert=0 debug=1 dev=nGene_led

Driver logs

sudo dmesg | tail -30
sudo journalctl -k -f

Check / Unload

lsmod | grep nGene_RaspPi5_LED_PWM
sudo /sbin/rmmod nGene_RaspPi5_LED_PWM

I²C @ GPIO2 (pin 3) / GPIO3 (pin 5)


Important: If you need I²C on GPIO2 (pin 3) / GPIO3 (pin 5), keep dtparam=i2c_arm=on in /boot/firmware/config.txt; if you want those pins as normal GPIO instead, comment it out.

nGene I²C BMP280 Character Misc Driver (Temperature sensor)


I²C BMP280 Character Misc Driver Quick Guide (v0.1.1 · Raspberry Pi 5 · Linux 6.12.34+rpt-rpi-2712)

Purpose. Character driver for Bosch BMP280 (temperature & pressure). Registers as a miscdevice (/dev/nGene_bmp by default). Adds poll() support so a single open can block until new data is ready, enabling streaming reads (e.g., cat /dev/nGene_bmp | head -n 5 produces 5 timed lines).

Directory

driver/char_I2C_BMP280_0.1.1/
  ├── nGene_RaspPi5_BMP280.c
  └── Makefile

0) Packages & headers

sudo apt-get update
sudo apt-get install -y i2c-tools build-essential raspberrypi-kernel-headers

1) Enable I²C

sudo emacs /boot/firmware/config.txt
# add / ensure:
dtparam=i2c_arm=on
sudo reboot

2) Wiring

VCC  -> 3V3
GND  -> any GND
SDA  -> pin 3 (GPIO2)
SCL  -> pin 5 (GPIO3)
(OPTIONAL) CSB  -> 3V3 (I²C mode)
(OPTIONAL) SDO  -> GND (0x76) or 3V3 (0x77)

I2C connection diagram

3) Probe bus

ls -l /dev/i2c-1
sudo i2cdetect -y 1
sudo i2cget -y 1 0x76 0xD0   # expect 0x58 (BMP280)

4) Build

make clean && make

5) Load

sudo rmmod nGene_RaspPi5_BMP280 2>/dev/null || true
sudo insmod ./nGene_RaspPi5_BMP280.ko i2c_bus=1 i2c_addr=0x76 debug=1 nGene_dev_name=nGene_bmp
dmesg | tail -n 80

6) Streaming example (poll advantage)

# Put driver in 1 Hz forced sampler mode
echo "forced"      | sudo tee /dev/nGene_bmp >/dev/null
echo "period=1000" | sudo tee /dev/nGene_bmp >/dev/null
echo "start"       | sudo tee /dev/nGene_bmp >/dev/null

# One open: multiple lines delivered at 1 s interval
stdbuf -oL cat /dev/nGene_bmp | head -n 5

I2C diagram 02


Earlier version v0.1.0 (baseline, no poll)

I²C BMP280 Character Misc Driver Quick Guide (v0.1.0 · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. Character device driver for Bosch BMP280 (temperature & pressure) on Raspberry Pi 5. Registers as a miscdevice (default /dev/nGene_bmp) and exposes a simple text interface: cat /dev/nGene_bmp for a one-line status, and echo "<cmd>" > /dev/nGene_bmp for configuration/diagnostics. This reuses the nGene misc/locking backbone from the LED/PWM driver, but removes PWM/GPIO and adds I²C/SMBus transactions for BMP280.

Directory

driver/char_I2C_BMP280_0.1.0/
  ├── nGene_RaspPi5_BMP280.c
  └── Makefile

0) Packages & tools

sudo apt-get update
sudo apt-get install -y i2c-tools build-essential raspberrypi-kernel-headers

1) Enable I²C (if not already)

sudo emacs /boot/firmware/config.txt
# add / ensure:
dtparam=i2c_arm=on
sudo reboot

2) Wiring (3.3V only, 40-pin header)

VCC  -> 3V3 (pin 1 or 17)
GND  -> any GND (pin 6/9/14)
SDA  -> pin 3 (GPIO2)
SCL  -> pin 5 (GPIO3)
(OPTION) CSB  -> 3V3 (I²C mode)
(OPTION) SDO  -> GND (address 0x76)  OR  SDO -> 3V3 (address 0x77)
# Ensure 4.7–10 kΩ pull-ups to 3V3 on SDA/SCL (most breakouts already have them).

3) Probe bus

ls -l /dev/i2c-1
sudo i2cdetect -y 1              # expect '76' or '77'
sudo i2cget -y 1 0x76 0xD0       # expect 0x58 (BMP280) or 0x60 (BME280)
# (use 0x77 if SDO strapped high)

4) Build

make clean && make

5) Load (always remove first to avoid “File exists”)

sudo rmmod nGene_RaspPi5_BMP280 2>/dev/null || true
sudo insmod ./nGene_RaspPi5_BMP280.ko i2c_bus=1 i2c_addr=0x76 debug=1 nGene_dev_name=nGene_bmp
dmesg | tail -n 80
cat /dev/nGene_bmp

6) Commands (echo to device)

echo "probe"        | sudo tee /dev/nGene_bmp >/dev/null   # read chip-id, re-read calibration
echo "normal"       | sudo tee /dev/nGene_bmp >/dev/null   # continuous conversion (CTRL_MEAS mode=3)
echo "forced"       | sudo tee /dev/nGene_bmp >/dev/null   # one-shot per read (mode=1)
echo "sleep"        | sudo tee /dev/nGene_bmp >/dev/null   # sleep (mode=0)
echo "osrs=2,4"     | sudo tee /dev/nGene_bmp >/dev/null   # temp x2, pressure x16 (1..5 -> x1..x16)
echo "iir=4"        | sudo tee /dev/nGene_bmp >/dev/null   # IIR filter coef (0,2,4,8,16)
echo "tstandby=125" | sudo tee /dev/nGene_bmp >/dev/null   # standby in ms (62.5/125/250/500/1000/2000/4000)

Example readout

cat /dev/nGene_bmp
# temp_c=31.63 press_pa=100497 mode=forced osrs_t=1 osrs_p=4 iir=0 tstandby_ms=125 dev=nGene_bmp

Revert when done (disable I²C)

sudo emacs /boot/firmware/config.txt
# comment the line back:
# dtparam=i2c_arm=on
sudo reboot

Driver logs

sudo dmesg | tail -30
sudo journalctl -k -f

Check / Unload

lsmod | grep nGene_RaspPi5_BMP280
sudo /sbin/rmmod nGene_RaspPi5_BMP280

nGene I²C MPU-6050 Character Misc Driver (Gyroscope sensor)


I²C MPU-6050 Character Misc Driver Quick Guide (v0.1.0-dev)

Purpose. Character driver for InvenSense MPU-6050 (3-axis accel + 3-axis gyro + temp). Registers as a miscdevice (/dev/nGene_mpu by default). Supports poll() and a built-in sampler so a single open can stream timed lines (e.g., cat /dev/nGene_mpu | head -n 5 prints 5 lines at the configured cadence).

Directory

driver/char_I2C_Gyro0.1.0/
  ├── nGene_RaspPi5_MPU6050.c
  └── Makefile

0) Packages & headers

sudo apt-get update
sudo apt-get install -y i2c-tools build-essential raspberrypi-kernel-headers

1) Enable I²C

sudo emacs -nw /boot/firmware/config.txt
# add / ensure:
dtparam=i2c_arm=on
# also load the userspace helper:
echo i2c-dev | sudo tee /etc/modules-load.d/i2c.conf
sudo modprobe i2c-dev
sudo reboot

2) Wiring (3.3V only, 40-pin header)

VCC  -> 3V3 (pin 1 or 17)
GND  -> any GND (pin 6/9/14/20/25/30/34/39)
SDA  -> pin 3 (GPIO2 / I2C1 SDA)
SCL  -> pin 5 (GPIO3 / I2C1 SCL)
AD0  -> GND => address 0x68    |    AD0 -> 3V3 => address 0x69
INT  -> (optional) a 3.3V GPIO input (e.g., GPIO17 / pin 11) if you add IRQ handling later
XDA/XCL -> leave unconnected (aux I²C pass-through)

MPU-6050 I²C wiring on Raspberry Pi 5 (3V3, GND, SDA pin 3, SCL pin 5; AD0 selects 0x68/0x69)

3) Probe bus

ls -l /dev/i2c-1
sudo i2cdetect -y 1                # expect "68" or "69"
# If AD0=3V3 (addr=0x69), WHO_AM_I still reads 0x68 from register 0x75:
sudo i2cget -y 1 0x69 0x75         # expect 0x68

4) Build

make clean && make

5) Load

# Remove first to avoid "File exists"
sudo rmmod nGene_RaspPi5_MPU6050 2>/dev/null || true

# If your board straps AD0=3V3 (address 0x69), autostart the sampler at 0.5 s:
sudo insmod ./nGene_RaspPi5_MPU6050.ko i2c_bus=1 i2c_addr=0x69 debug=1 nGene_dev_name=nGene_mpu sample_period_ms=500 autostart=1

dmesg | tail -n 80

# With autostart=1, this will block and print a line every 0.5 s
stdbuf -oL cat /dev/nGene_mpu | head -n 5  

MPU-6050 streaming output at 0.5 s cadence using the poll-friendly sampler

6) Commands (echo to the device; case-insensitive)

# Basic diag / power
echo "probe"    | sudo tee /dev/nGene_mpu >/dev/null   # WHO_AM_I (expect 0x68)
echo "reset"    | sudo tee /dev/nGene_mpu >/dev/null   # device reset + reconfig
echo "sleep"    | sudo tee /dev/nGene_mpu >/dev/null   # set sleep bit
echo "wake"     | sudo tee /dev/nGene_mpu >/dev/null   # clear sleep, PLL clock

# Filters and ranges
echo "dlpf=3"   | sudo tee /dev/nGene_mpu >/dev/null   # 0..6 (default 3)
echo "afs=0"    | sudo tee /dev/nGene_mpu >/dev/null   # accel ±2g/±4g/±8g/±16g -> 0..3
echo "gfs=0"    | sudo tee /dev/nGene_mpu >/dev/null   # gyro  ±250/±500/±1000/±2000 dps -> 0..3
echo "smplrt=7" | sudo tee /dev/nGene_mpu >/dev/null   # SMPLRT_DIV (1kHz/(1+div) with DLPF on)

# Address / rebinding
echo "addr=0x69" | sudo tee /dev/nGene_mpu >/dev/null  # set then:
echo "reclaim"   | sudo tee /dev/nGene_mpu >/dev/null  # drop/reacquire client + reapply config

# Sampler (poll-friendly streaming)
echo "period=500" | sudo tee /dev/nGene_mpu >/dev/null # 500 ms
echo "start"      | sudo tee /dev/nGene_mpu >/dev/null
echo "stop"       | sudo tee /dev/nGene_mpu >/dev/null

Streaming demo

echo "period=500" | sudo tee /dev/nGene_mpu >/dev/null
echo "start"      | sudo tee /dev/nGene_mpu >/dev/null
stdbuf -oL cat /dev/nGene_mpu | head -n 5

Troubleshooting

# If insmod fails with "No such device" or "I/O error"
ls -l /dev/i2c-1
sudo i2cdetect -y 1                # make sure 0x68 or 0x69 is present

# If the address is already owned by an in-tree driver:
sudo modprobe -r mpu6050 inv_mpu6050_i2c 2>/dev/null || true
sudo rmmod nGene_RaspPi5_MPU6050 2>/dev/null || true
sudo insmod ./nGene_RaspPi5_MPU6050.ko i2c_bus=1 i2c_addr=0x69 debug=1

# Live logs
dmesg | tail -n 80
journalctl -k -f

Revert (disable I²C) — optional

sudo emacs -nw /boot/firmware/config.txt
# comment the line back:
# dtparam=i2c_arm=on
sudo rm -f /etc/modules-load.d/i2c.conf
sudo reboot

nGene I²C LCD Character Misc Driver


I²C 1602 LCD Character Misc Driver Quick Guide (v0.1.0-dev)

Purpose. Character driver for HD44780-class 1602 LCD modules with PCF8574 I²C backpacks (labels such as “1602A IIC”, “GJD1602A-IIC”). Registers as a miscdevice (/dev/nGene_lcd). On module load, initializes the LCD and prints the banner http://nGene.org. Userspace can update the display by writing one or two lines (newline separates lines).

Directory

driver/char_LCD0.1.0/
  ├── nGene_RaspPi5_LCD1602_I2C.c
  └── Makefile

0) Packages & headers

sudo apt-get update
sudo apt-get install -y i2c-tools build-essential raspberrypi-kernel-headers

1) Enable I²C

sudo emacs -nw /boot/firmware/config.txt
# ensure:
dtparam=i2c_arm=on
# userspace helper:
echo i2c-dev | sudo tee /etc/modules-load.d/i2c.conf
sudo modprobe i2c-dev
sudo reboot

2) Wiring (4 pins; I²C is 3.3 V logic)

VCC  → 5V (pin 2 or 4)   [see safety note regarding pull-ups]
GND  → any GND (pin 6/9/14/20/25/30/34/39)
SDA  → pin 3 (GPIO2 / I2C1 SDA)
SCL  → pin 5 (GPIO3 / I2C1 SCL)

Important: Contrast. With backlight on but no characters visible, adjust the small blue trimpot on the backpack; characters typically become visible near one end.

Safety (pull-ups and voltage). The Pi’s I²C lines must be pulled up to 3.3 V. Many backpacks include pull-ups tied to VCC. If VCC=5 V and those pull-ups are to 5 V, SDA/SCL are back-driven at 5 V (unsafe). Remove/disable the 5 V pull-ups or ensure pull-ups are at 3.3 V. Powering the backpack at 3.3 V is an alternative on some modules.


GJD1602A-IIC wiring on Raspberry Pi 5 (VCC, GND, SDA pin 3, SCL pin 5)

3) Probe bus

ls -l /dev/i2c-1
sudo i2cdetect -y 1              # expect 0x27 or 0x3F
# 'UU' before insmod indicates another kernel client already owns the address.

4) Build

make clean && make

5) Load (order optimized for GJD1602A-IIC)

# remove first to avoid "File exists"
sudo rmmod nGene_RaspPi5_LCD1602_I2C 2>/dev/null || true

# (B) Alternate mapping preset #1 (very common) — recommended first
sudo insmod ./nGene_RaspPi5_LCD1602_I2C.ko i2c_bus=1 i2c_addr=0x27 map_preset=1

# (C) If still blank, add timing margin (EN pulse + power-on wait)
sudo rmmod nGene_RaspPi5_LCD1602_I2C 2>/dev/null || true
sudo insmod ./nGene_RaspPi5_LCD1602_I2C.ko \
  i2c_bus=1 i2c_addr=0x27 map_preset=1 en_high_us=5 en_low_us=60 power_on_ms=80

# (A) Default mapping preset #0 (less common on GJD1602A-IIC)
sudo rmmod nGene_RaspPi5_LCD1602_I2C 2>/dev/null || true
sudo insmod ./nGene_RaspPi5_LCD1602_I2C.ko i2c_bus=1 i2c_addr=0x27

dmesg | tail -n 80

Why (B) & (C) succeed when (A) does not. Many GJD1602A-IIC units use the alternate PCF8574 wiring (map_preset=1), so the default mapping (#0) routes control/data to incorrect pins (no text). Certain modules also require longer EN pulse and/or initial delay; (C) provides additional margin.

6) Update text

echo "Hello, world" | sudo tee /dev/nGene_lcd
printf "Line1 text\nLine2 text" | sudo tee /dev/nGene_lcd
# '\n' moves to line 2; each line is padded/truncated to 16 characters

7) Useful module parameters

# Mapping presets:
map_preset=1                     # alternate, very common (recommended)
map_preset=0                     # default mapping

# Timing margins:
en_high_us=5 en_low_us=60 power_on_ms=80

# Backlight and boot banner:
backlight=1                      # 1=on, 0=off
init_banner="http://nGene.org"   # banner on line 1 at init

Troubleshooting

# Address ownership conflict ('UU' before insmod):
sudo nano /boot/firmware/config.txt   # remove conflicting overlays if present
sudo modprobe -r conflicting_module   # if a bound in-tree client exists
sudo rmmod nGene_RaspPi5_LCD1602_I2C 2>/dev/null || true
sudo insmod ./nGene_RaspPi5_LCD1602_I2C.ko i2c_bus=1 i2c_addr=0x27 map_preset=1

# Live logs:
dmesg | tail -n 80
journalctl -k -f

Revert (disable I²C) — optional

sudo emacs -nw /boot/firmware/config.txt
# comment the line:
# dtparam=i2c_arm=on
sudo rm -f /etc/modules-load.d/i2c.conf
sudo reboot

Sense HAT


nGene Sense HAT Driver





nGene Sense HAT Driver Quick Guide (v0.1.7-dev · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. Minimal kernel module that, on insmod, locates the Sense HAT framebuffer ("RPi-Sense FB"), scrolls a tiny 5×7 message across the 8×8 RGB565 LED matrix once (with rotation/low-light options), then clears. It exposes a runtime device /dev/nGene_sensehat for text and sensors (env + IMU).

What’s new in v0.1.7-dev

echo -n '!temp'     | sudo tee /dev/nGene_sensehat >/dev/null   # temperature (°C)
echo -n '!hum'      | sudo tee /dev/nGene_sensehat >/dev/null   # humidity (%RH)
echo -n '!pres'     | sudo tee /dev/nGene_sensehat >/dev/null   # pressure (hPa)
echo -n '!compass'  | sudo tee /dev/nGene_sensehat >/dev/null   # heading like "315° NW"
echo -n '!level'    | sudo tee /dev/nGene_sensehat >/dev/null   # tilt: 3× @0.7s → avg once (e.g., "6°")
echo -n '!motion'   | sudo tee /dev/nGene_sensehat >/dev/null   # motion meter: animate ~1s (50 ms step)
echo -n '!cal'      | sudo tee /dev/nGene_sensehat >/dev/null   # IMU calibration (mag hard-iron + scales)
echo -n 'Hello'     | sudo tee /dev/nGene_sensehat >/dev/null   # scroll text once
# Back-compat: "temp", "humidity", "pres" (without '!') still work

Directory

driver/SenseHAT0.1.7/
  ├── Makefile
  ├── nGene_SenseHAT.c                     # main (scroll/dev interface + UI)
  ├── nGene_SenseHAT_Environmental.c/.h    # T/H/P via IIO/HWMON + I²C fallback
  ├── nGene_SenseHAT_IMU.c/.h              # IMU: compass/level/motion/cal + IIO + I²C fallback
  ├── nGene_font_5x7.c/.h                  # 5×7 glyphs (° rendered inline by scroller)

Quick guide

  1. Enable overlay once, then reboot
    sudoedit /boot/firmware/config.txt
    # add:
    dtparam=i2c_arm=on
    dtoverlay=rpi-sense
    # save, then:
    sudo reboot
  2. Verify framebuffer after reboot
    dmesg | grep -i "RPi-Sense FB"
    for f in /sys/class/graphics/fb[0-7]/name; do echo -n "$f: "; cat "$f"; done
    # Expect: /sys/class/graphics/fb0/name: RPi-Sense FB
  3. Build
    make clean && make
  4. Prep calibration dir, load (scrolls once, then clears)
    sudo rmmod nGene_SenseHAT_bootscroll 2>/dev/null || true
    sudo mkdir -p /var/lib/ngene_sensehat
    sudo insmod ./nGene_SenseHAT_bootscroll.ko rotation=0 low_light=1 scroll_ms=100 msg="nGene"
    Tip: If orientation is off, try rotation=90, 180, or 270. If colours look odd on your stack, try swap_bytes=1.
  5. Use the device
    echo -n '!temp'    | sudo tee /dev/nGene_sensehat >/dev/null
    echo -n '!hum'     | sudo tee /dev/nGene_sensehat >/dev/null
    echo -n '!pres'    | sudo tee /dev/nGene_sensehat >/dev/null
    echo -n '!compass' | sudo tee /dev/nGene_sensehat >/dev/null   # arrow + "DDD° XX"
    echo -n '!level'   | sudo tee /dev/nGene_sensehat >/dev/null   # 3 samples @0.7s; shows avg once ("D°")
    echo -n '!motion'  | sudo tee /dev/nGene_sensehat >/dev/null   # ~1s animation at 50 ms step
    echo -n '!cal'     | sudo tee /dev/nGene_sensehat >/dev/null   # rotate during mag, then keep still
    /dev/nGene_sensehat is mode 0666. Reads return the last message:
    sudo cat /dev/nGene_sensehat
  6. Optional IIO hints and overrides
    # Prefer specific IIO devices by substring:
    sudo insmod ./nGene_SenseHAT_bootscroll.ko \
      iio_hint_temp=cpu  iio_hint_hum=hts  iio_hint_pres=lps \
      iio_hint_mag=lsm9  iio_hint_accel=lsm9 msg="nGene"
    
    # Force exact sysfs env attributes (decimal or milli-units ok):
    sudo insmod ./nGene_SenseHAT_bootscroll.ko \
      path_hum=/sys/bus/iio/devices/iio:deviceX/in_humidityrelative_input \
      path_pres=/sys/bus/iio/devices/iio:deviceY/in_pressure_input
  7. IMU: raw I²C fallback and addresses
    # If IIO isn't present, driver uses safe adapter reads or a temp dummy client.
    # You can force bus/addresses if needed:
    sudo insmod ./nGene_SenseHAT_bootscroll.ko \
      imu_i2c_bus=1 lsm9_mag_addr=0x1c lsm9_acc_addr=0x6a msg="nGene"
  8. Calibration
    # ROTATE (mag range), PAUSE, HOLD (gyro placeholder).
    # Stores to /var/lib/ngene_sensehat/imu_cal.bin
    echo -n '!cal' | sudo tee /dev/nGene_sensehat >/dev/null
  9. Unload (clears again on exit)
    sudo rmmod nGene_SenseHAT_bootscroll

Expected dmesg on success

nGene_SenseHAT v0.1.7-dev loading
nGene_SenseHAT: using /dev/fb0 (Sense HAT)
nGene_SenseHAT: scrolling "nGene" (...)
nGene_SenseHAT: created /dev/nGene_sensehat
nGene_SenseHAT: init done.

# Compass example
nGene_SenseHAT(IMU): LSM9DS1 mag configured via adapter (addr=0x1c, bus=1)
nGene_SenseHAT(IMU): heading 270 deg W (cal:ON) raw[mx=-2198,my=520,mz=-1249]

# Level example
nGene_SenseHAT(IMU): LEVEL — sampling tilt @0.7s for 3 samples...
nGene_SenseHAT(IMU): LEVEL sample 1/3: ax=-92 ay=16 az=863 mg -> tilt=6 deg
...
# Motion example
nGene_SenseHAT(IMU): MOTION — sampling @50ms for 1000ms...

Parameters

Troubleshooting

Driver logs

sudo dmesg | tail -120
sudo journalctl -k -f

Check / Unload

lsmod | grep nGene_SenseHAT_bootscroll
sudo /sbin/rmmod nGene_SenseHAT_bootscroll

Notes

We write full 8×8 RGB565 frames to screen_base and then queue the first framebuffer page into fbdefio->pagereflist, scheduling the deferred work so the LED matrix updates. IMU uses IIO first; if absent, it falls back to safe I²C adapter reads or a temporary dummy client. The degree symbol (0xB0) is rendered inline by the scroller, so it always displays correctly. Level samples 3× at 700 ms intervals and displays a single averaged tilt angle in degrees. Motion runs for ~1 s with a ~50 ms step, mapping |accel|-1g to LED brightness.


Show v0.1.6 Quick Guide (older)

nGene Sense HAT Driver Quick Guide (v0.1.6-dev · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. Minimal kernel module that, on insmod, locates the Sense HAT framebuffer ("RPi-Sense FB"), scrolls a tiny 5×7 message across the 8×8 RGB565 LED matrix once (with rotation/low-light options), then clears. It exposes a runtime device /dev/nGene_sensehat for text and sensors (env + IMU):

echo -n '!temp'     | sudo tee /dev/nGene_sensehat >/dev/null   # temperature (°C)
echo -n '!hum'      | sudo tee /dev/nGene_sensehat >/dev/null   # humidity (%RH)
echo -n '!pres'     | sudo tee /dev/nGene_sensehat >/dev/null   # pressure (hPa)
echo -n '!compass'  | sudo tee /dev/nGene_sensehat >/dev/null   # heading like "315° NW"
echo -n '!level'    | sudo tee /dev/nGene_sensehat >/dev/null   # tilt: samples 3× @0.7s; shows avg once (e.g., "6°")
echo -n '!cal'      | sudo tee /dev/nGene_sensehat >/dev/null   # IMU calibration (mag hard-iron + scales)
echo -n 'Hello'     | sudo tee /dev/nGene_sensehat >/dev/null   # scroll text once
# Back-compat: "temp", "humidity", "pres" (without '!') still work

Directory

driver/SenseHAT0.1.6/
  ├── Makefile
  ├── nGene_SenseHAT.c                     # main (scroll/dev interface + UI)
  ├── nGene_SenseHAT_Environmental.c/.h    # T/H/P via IIO/HWMON + I²C fallback
  ├── nGene_SenseHAT_IMU.c/.h              # IMU: compass/level/cal + IIO + I²C fallback
  ├── nGene_font_5x7.c/.h                  # 5×7 glyphs; degree(°) rendered inline
  

Quick guide

  1. Enable overlay once, then reboot
    sudoedit /boot/firmware/config.txt
    # add:
    dtparam=i2c_arm=on
    dtoverlay=rpi-sense
    # save, then:
    sudo reboot
  2. Verify framebuffer after reboot
    dmesg | grep -i "RPi-Sense FB"
    for f in /sys/class/graphics/fb[0-7]/name; do echo -n "$f: "; cat "$f"; done
    # Expect one line:
    # /sys/class/graphics/fb0/name: RPi-Sense FB
  3. Build
    make clean && make
  4. Prep calibration dir, load (scrolls once, then clears)
    sudo rmmod nGene_SenseHAT_bootscroll 2>/dev/null || true
    sudo mkdir -p /var/lib/ngene_sensehat
    sudo insmod ./nGene_SenseHAT_bootscroll.ko rotation=0 low_light=1 scroll_ms=100 msg="nGene"
    Tip: If orientation is off, try rotation=90, 180, or 270. If colours look odd on your stack, try swap_bytes=1 (degree/white/black unaffected).
  5. Use the device
    echo -n '!temp'    | sudo tee /dev/nGene_sensehat >/dev/null
    echo -n '!hum'     | sudo tee /dev/nGene_sensehat >/dev/null
    echo -n '!pres'    | sudo tee /dev/nGene_sensehat >/dev/null
    echo -n '!compass' | sudo tee /dev/nGene_sensehat >/dev/null   # shows arrow + "DDD° XX"
    echo -n '!level'   | sudo tee /dev/nGene_sensehat >/dev/null   # 3 samples @0.7s; shows avg once (e.g., "6°")
    echo -n '!cal'     | sudo tee /dev/nGene_sensehat >/dev/null   # rotate during mag, then keep still
    /dev/nGene_sensehat is mode 0666. Reads return last message:
    sudo cat /dev/nGene_sensehat
  6. Optional IIO hints and overrides
    # Prefer specific IIO devices by substring:
    sudo insmod ./nGene_SenseHAT_bootscroll.ko \
      iio_hint_temp=cpu  iio_hint_hum=hts  iio_hint_pres=lps \
      iio_hint_mag=lsm9  iio_hint_accel=lsm9 msg="nGene"
    
    # Force exact sysfs env attributes (decimal or milli-units ok):
    sudo insmod ./nGene_SenseHAT_bootscroll.ko \
      path_hum=/sys/bus/iio/devices/iio:deviceX/in_humidityrelative_input \
      path_pres=/sys/bus/iio/devices/iio:deviceY/in_pressure_input
  7. IMU: raw I²C fallback and addresses
    # If IIO isn't present, driver uses safe adapter reads or a temp dummy client.
    # You can force bus/addresses if needed:
    sudo insmod ./nGene_SenseHAT_bootscroll.ko \
      imu_i2c_bus=1 lsm9_mag_addr=0x1c lsm9_acc_addr=0x6a msg="nGene"
    Compass uses LSM9DS1 magnetometer. Level uses accelerometer; samples 3× at 700 ms, shows only the averaged degrees once. Degree symbol is rendered inline (no font dependency).
  8. Calibration
    # Starts 3 phases: ROTATE (mag range), PAUSE, HOLD (gyro placeholder).
    # Stores to /var/lib/ngene_sensehat/imu_cal.bin
    echo -n '!cal' | sudo tee /dev/nGene_sensehat >/dev/null
  9. Unload (clears again on exit)
    sudo rmmod nGene_SenseHAT_bootscroll

Expected dmesg on success

nGene_SenseHAT v0.1.6-dev loading
nGene_SenseHAT: using /dev/fb0 (Sense HAT)
nGene_SenseHAT: scrolling "nGene" (5 chars, ... cols, 100 ms/col, rot=0, low_light=1)
nGene_SenseHAT: created /dev/nGene_sensehat
nGene_SenseHAT: init done.

# Compass example (with calibration loaded/saved)
nGene_SenseHAT(IMU): LSM9DS1 mag configured via adapter (addr=0x1c, bus=1)
nGene_SenseHAT(IMU): heading 270 deg W (cal:ON) raw[mx=-2198,my=520,mz=-1249]

# Level example (IIO accel not found → raw I²C fallback)
nGene_SenseHAT(IMU): LEVEL — sampling tilt @0.7s interval for 3 samples...
nGene_SenseHAT(IMU): LSM9DS1 accel enabled via adapter (addr=0x6a, bus=1)
nGene_SenseHAT(IMU): LEVEL sample 1/3: ax=-92 ay=16 az=863 mg -> tilt=6 deg
nGene_SenseHAT(IMU): LEVEL sample 2/3: ax=-103 ay=18 az=958 mg -> tilt=6 deg
nGene_SenseHAT(IMU): LEVEL sample 3/3: ax=-102 ay=18 az=958 mg -> tilt=6 deg

Parameters

  • Display/UX:
    • rotation0|90|180|270; default 0
    • low_light — dim to ~35%; default 1
    • scroll_ms — ms per column; default 100
    • msg — initial scroll text; default "Frank"
    • swap_bytes — swap RGB565 byte order; default 0
  • Environmental hints/overrides:
    • iio_hint_temp, iio_hint_hum, iio_hint_pres
    • path_hum, path_pres (absolute sysfs files)
    • i2c_bus, hts_addr=0x5f, lps_addr=0x5c
  • IMU (mag/accel) & calibration:
    • iio_hint_mag, iio_hint_accel (e.g., lsm9)
    • imu_i2c_bus=1, lsm9_mag_addr=0x1c, lsm9_acc_addr=0x6a
    • imu_cal_path=/var/lib/ngene_sensehat/imu_cal.bin
    • cal_rotate_ms=10000, cal_pause_ms=1000, cal_hold_ms=5000

Troubleshooting

  • No LEDs change?
    1. Confirm overlay: grep -i "RPi-Sense FB" /var/log/kern.log /var/log/syslog or dmesg.
    2. Confirm fb mapping:
      for f in /sys/class/graphics/fb[0-7]/name; do echo -n "$f: "; cat "$f"; done
    3. Try a different rotation. If colours look swapped, use swap_bytes=1.
  • Env shows N/A?
    1. List IIO devices: ls /sys/bus/iio/devices/iio:device*/name and try hints:
      sudo insmod ./nGene_SenseHAT_bootscroll.ko iio_hint_hum=hts iio_hint_pres=lps
    2. Try HWMON attrs:
      grep -H . /sys/class/hwmon/hwmon*/{humidity*,pressure*,temp*_input} 2>/dev/null
    3. Force bus/addr: i2c_bus, hts_addr, lps_addr.
  • Compass/Level not responding?
    1. Ensure mag/accel IIO exist or pass hints: iio_hint_mag=lsm9 iio_hint_accel=lsm9.
    2. Override bus/addr: imu_i2c_bus, lsm9_mag_addr, lsm9_acc_addr.
    3. Create calibration dir once:
      sudo mkdir -p /var/lib/ngene_sensehat
    4. Re-run !cal and rotate in all orientations during Phase 1.
  • Text speed off? Tune scroll_ms (e.g., 70 faster, 140 slower).

Driver logs

sudo dmesg | tail -120
sudo journalctl -k -f

Check / Unload

lsmod | grep nGene_SenseHAT_bootscroll
sudo /sbin/rmmod nGene_SenseHAT_bootscroll

Notes

We write full 8×8 RGB565 frames to screen_base and then queue the first framebuffer page into fbdefio->pagereflist, scheduling the deferred work so the LED matrix reliably updates. IMU uses IIO first; if absent, it falls back to safe I²C adapter reads or a temporary dummy client. The degree symbol (0xB0) is rendered inline by the scroller, so it always displays correctly even if the base font lacks it. The level command samples 3× at 700 ms intervals and displays a single averaged tilt angle in degrees.


Show v0.1.3 Quick Guide (older)

nGene Sense HAT Driver Quick Guide (v0.1.3-dev · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. Minimal kernel module that, on insmod, locates the Sense HAT framebuffer ("RPi-Sense FB"), scrolls a tiny 5×7 message across the 8×8 RGB565 LED matrix once (with rotation/low-light options), then clears. It also exposes a runtime device /dev/nGene_sensehat for text and sensor display:

echo -n '!temp' | sudo tee /dev/nGene_sensehat >/dev/null   # show temperature (°C)
echo -n '!hum'  | sudo tee /dev/nGene_sensehat >/dev/null   # show humidity (%RH)
echo -n '!pres' | sudo tee /dev/nGene_sensehat >/dev/null   # show pressure (hPa)
echo -n 'Hello' | sudo tee /dev/nGene_sensehat >/dev/null   # scroll text once
# Back-compat: "temp", "humidity", "pres" (without '!') still work

Directory

driver/SenseHAT0.1.3/
  ├── Makefile
  ├── nGene_SenseHAT.c
  ├── nGene_font_5x7.c
  └── nGene_font_5x7.h

Quick guide

  1. Enable overlay once, then reboot
    sudoedit /boot/firmware/config.txt
    # add:
    dtparam=i2c_arm=on
    dtoverlay=rpi-sense
    # save, then:
    sudo reboot
  2. Verify framebuffer after reboot
    dmesg | grep -i "RPi-Sense FB"
    for f in /sys/class/graphics/fb[0-7]/name; do echo -n "$f: "; cat "$f"; done
    # Expect one line like:
    # /sys/class/graphics/fb0/name: RPi-Sense FB
  3. Build
    make clean && make
  4. Load (scrolls once, then clears)
    sudo rmmod nGene_SenseHAT_bootscroll 2>/dev/null || true
    sudo insmod ./nGene_SenseHAT_bootscroll.ko rotation=0 low_light=1 scroll_ms=100 msg="nGene"
    Tip: If orientation is off, try rotation=90, 180, or 270. If colours ever look odd on your stack, try swap_bytes=1 (white/black unaffected).
  5. Trigger from userspace (bang-prefixed commands)
    echo -n '!temp' | sudo tee /dev/nGene_sensehat >/dev/null
    echo -n '!hum'  | sudo tee /dev/nGene_sensehat >/dev/null
    echo -n '!pres' | sudo tee /dev/nGene_sensehat >/dev/null
    echo -n 'Hello!'| sudo tee /dev/nGene_sensehat >/dev/null
    Device is /dev/nGene_sensehat (mode 0666 by default). Reads return the last message:
    sudo cat /dev/nGene_sensehat
  6. Optional IIO hints (validated) and exact sysfs overrides
    # Prefer a specific IIO device by substring:
    sudo insmod ./nGene_SenseHAT_bootscroll.ko iio_hint_hum=hts iio_hint_pres=lps msg="nGene"
    
    # Force exact sysfs attribute files (decimal or milli-units accepted):
    sudo insmod ./nGene_SenseHAT_bootscroll.ko \
      path_hum=/sys/bus/iio/devices/iio:deviceX/in_humidityrelative_input \
      path_pres=/sys/bus/iio/devices/iio:deviceY/in_pressure_input
  7. Raw I²C fallback (automatic)
    # You can force bus/addresses if needed:
    sudo insmod ./nGene_SenseHAT_bootscroll.ko i2c_bus=1 hts_addr=0x5f lps_addr=0x5c msg="nGene"
    If the address is owned by another kernel driver, we read safely via the adapter (no dummy client). If it’s free, we create a temporary dummy client and read via SMBus helpers.
  8. Unload (clears again on exit)
    sudo rmmod nGene_SenseHAT_bootscroll

Expected dmesg on success

nGene_SenseHAT v0.1.3-dev loading
nGene_SenseHAT: using /dev/fb0 (Sense HAT)
nGene_SenseHAT: scrolling "nGene" (5 chars, ... cols, 100 ms/col, rot=0, low_light=1)
nGene_SenseHAT: created /dev/nGene_sensehat
nGene_SenseHAT: init done.

# Sensor reads (examples; path depends on your setup)
nGene_SenseHAT: Temperature (hwmon:temp1_input) 50.1C (dir=/sys/class/hwmon/hwmon0)
nGene_SenseHAT: Humidity (i2c/hts221) 37.00% (bus=1 addr=0x5f)
nGene_SenseHAT: Pressure (iio) 1012.3hPa (dir=/sys/bus/iio/devices/iio:deviceX)

Parameters

  • rotation0|90|180|270. Default 0.
  • low_light1 dims to ~35% via RGB565 scaling. Default 1.
  • scroll_ms — ms per column; default 100.
  • msg — string to scroll once at module load; default "Frank".
  • swap_bytes — swap RGB565 byte order; default 0.
  • iio_hint_temp, iio_hint_hum, iio_hint_pres — substring hints to choose IIO devices (e.g., hts, lps).
  • path_temp, path_hum, path_pres — absolute sysfs attribute overrides.
  • i2c_bus — I²C bus (default 1), hts_addr=0x5f, lps_addr=0x5c.

Troubleshooting

  • No LEDs change?
    1. Confirm overlay: grep -i "RPi-Sense FB" /var/log/kern.log /var/log/syslog or dmesg.
    2. Confirm fb mapping:
      for f in /sys/class/graphics/fb[0-7]/name; do echo -n "$f: "; cat "$f"; done
      Ensure the driver logs “using /dev/fbN (Sense HAT)”.
    3. Try a different rotation: rotation=90, then 180, 270.
    4. If colours look swapped, use swap_bytes=1.
  • Pressure/Humidity shows N/A?
    1. List IIO devices: ls /sys/bus/iio/devices/iio:device*/name and try hints:
      sudo insmod ./nGene_SenseHAT_bootscroll.ko iio_hint_hum=hts iio_hint_pres=lps
    2. Try HWMON attrs:
      grep -H . /sys/class/hwmon/hwmon*/{humidity*,pressure*,temp*_input} 2>/dev/null
    3. Force raw I²C bus/addr if needed:
      sudo insmod ./nGene_SenseHAT_bootscroll.ko i2c_bus=1 hts_addr=0x5f lps_addr=0x5c
    4. As a last resort, pass exact sysfs files via path_hum/path_pres.
  • Writing to /dev/nGene_sensehat fails?
    1. Check device exists: ls -l /dev/nGene_sensehat.
    2. Inspect logs for “created /dev/nGene_sensehat”.
    3. Use sudo if permissions differ due to udev rules.
  • Text speed off? Tune scroll_ms (e.g., 70 faster, 140 slower).

Driver logs

sudo dmesg | tail -80
sudo journalctl -k -f

Check / Unload

lsmod | grep nGene_SenseHAT_bootscroll
sudo /sbin/rmmod nGene_SenseHAT_bootscroll

Notes

We write full 8×8 RGB565 frames to screen_base and then queue the first framebuffer page into fbdefio->pagereflist, scheduling info->deferred_work so the Sense HAT LED matrix reliably updates. Pressure uses IIO/HWMON when available; otherwise a safe I²C adapter read is used when the address is owned by a kernel driver, or a temporary dummy client is created if free.


Show v0.1.1 Quick Guide (older)

Sense HAT Bootscroll Driver Quick Guide (v0.1.1-dev · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. Minimal kernel module that, on insmod, locates the Sense HAT framebuffer ("RPi-Sense FB"), scrolls a tiny 5×7 “Frank” across the 8×8 RGB565 LED matrix once (with rotation/low-light options), then clears. Frames are written directly to screen_base and deferred-IO is explicitly kicked so LEDs update reliably.
New in v0.1.1-dev: exposes a runtime writer /dev/nGene_sensehat. Write a line to scroll it once; read returns the last message.

Directory

driver/SenseHAT0.1.1/
  ├── Makefile
  ├── nGene_BootScroll.c
  ├── nGene_font_5x7.c
  └── nGene_font_5x7.h

Quick guide

  1. Enable overlay once, then reboot
    sudoedit /boot/firmware/config.txt
    # add:
    dtparam=i2c_arm=on
    dtoverlay=rpi-sense
    # save, then:
    sudo reboot
  2. Verify framebuffer after reboot
    dmesg | grep -i "RPi-Sense FB"
    for f in /sys/class/graphics/fb[0-7]/name; do echo -n "$f: "; cat "$f"; done
    # Expect one line like:
    # /sys/class/graphics/fb0/name: RPi-Sense FB
  3. Build
    make clean && make
  4. Load (scrolls once, then clears)
    sudo insmod ./nGene_SenseHAT_bootscroll.ko rotation=0 low_light=1 scroll_ms=100 msg="Frank"
    Tip: If orientation is off, try rotation=90, 180, or 270. If colours ever look odd on your stack, try swap_bytes=1 (white/black unaffected).
  5. Send text at runtime (no re-insmod needed)
    # scroll once:
    echo -n "Hello!" | sudo tee /dev/nGene_sensehat >/dev/null
    # read back last message:
    sudo cat /dev/nGene_sensehat
    Device is created as /dev/nGene_sensehat (mode 0666 by default). The module keeps the Sense HAT framebuffer open while loaded, so runtime writes update reliably.
  6. Unload (clears again on exit)
    sudo rmmod nGene_SenseHAT_bootscroll

Expected dmesg on success

nGene_SenseHAT_bootscroll v0.1.1-dev loading
nGene_SenseHAT_bootscroll: using /dev/fb0 (Sense HAT)
nGene_SenseHAT_bootscroll: scrolling "Frank" (5 chars, ... cols, 100 ms/col, rot=0, low_light=1)
nGene_SenseHAT_bootscroll: created /dev/nGene_sensehat
nGene_SenseHAT_bootscroll: init done.

Parameters

  • rotation0|90|180|270. Rotates the 8×8 before write. Default 0.
  • low_light1 dims to ~35% via RGB565 scaling. Default 1.
  • scroll_ms — milliseconds per column; e.g. 110 ≈ 0.11 s/col. Default 100.
  • msg — string to scroll once at module load; default "Frank".
  • swap_bytes1 swaps RGB565 byte order in the direct path; default 0.

Rotation cheat-sheet (physical board orientation)

  • rotation=0 — USB ports right, Raspberry logo upright → text upright.
  • rotation=90 — turn the Sense HAT 90° clockwise relative to above.
  • rotation=180 — upside-down relative to 0.
  • rotation=270 — 90° counter-clockwise relative to 0.

Troubleshooting

  • No LEDs change?
    1. Confirm overlay: grep -i "RPi-Sense FB" /var/log/kern.log /var/log/syslog or dmesg.
    2. Confirm fb mapping:
      for f in /sys/class/graphics/fb[0-7]/name; do echo -n "$f: "; cat "$f"; done
      Ensure the driver logs “using /dev/fbN (Sense HAT)”.
    3. Try a different rotation: rotation=90, then 180, 270.
    4. If colours look swapped on your setup (rare), use swap_bytes=1.
  • Writing to /dev/nGene_sensehat fails?
    1. Check it exists: ls -l /dev/nGene_sensehat (created at module init).
    2. Inspect kernel logs for “created /dev/nGene_sensehat”.
    3. If permissions changed by udev rules, prefix with sudo or adjust rules.
  • Text speed off? Tune scroll_ms (e.g., 70 faster, 140 slower).

Driver logs

sudo dmesg | tail -60
sudo journalctl -k -f

Check / Unload

lsmod | grep nGene_SenseHAT_bootscroll
sudo /sbin/rmmod nGene_SenseHAT_bootscroll

Notes

We write full 8×8 RGB565 frames to screen_base and then queue the first framebuffer page into fbdefio->pagereflist, scheduling info->deferred_work so the Sense HAT LED matrix reliably updates (regardless of deferred-IO settings). If colours ever look wrong on your stack, try swap_bytes=1 (white/black are unaffected).


Show v0.1.0 Quick Guide (older)

Sense HAT Bootscroll Driver Quick Guide (v0.1.0-dev · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. Minimal kernel module that, on insmod, locates the Sense HAT framebuffer ("RPi-Sense FB"), scrolls a tiny 5×7 “Frank” across the 8×8 RGB565 LED matrix once (with rotation/low-light options), then clears. Writes frames directly to screen_base and explicitly kicks deferred-IO to ensure the LEDs update on all stacks.

Directory

driver/SenseHAT0.1.0/
  ├── nGene_SenseHAT_bootscroll.c
  └── Makefile

Quick guide

  1. Enable overlay once, then reboot
    sudoedit /boot/firmware/config.txt
    # add:
    dtparam=i2c_arm=on
    dtoverlay=rpi-sense
    # save, then:
    sudo reboot
  2. Verify framebuffer after reboot
    dmesg | grep -i "RPi-Sense FB"
    for f in /sys/class/graphics/fb[0-7]/name; do echo -n "$f: "; cat "$f"; done
    # Expect one line like:
    # /sys/class/graphics/fb0/name: RPi-Sense FB
  3. Build
    make clean && make
  4. Load (scrolls once, then clears)
    sudo insmod ./nGene_SenseHAT_bootscroll.ko rotation=0 low_light=1 scroll_ms=100 msg="Frank"
    Tip: If orientation is off, try rotation=90, 180, or 270. White/black aren’t affected by byte order, but if colours ever look odd on your stack, try swap_bytes=1.
  5. Unload (clears again on exit)
    sudo rmmod nGene_SenseHAT_bootscroll

Expected dmesg on success

nGene_SenseHAT_bootscroll v0.1.0-dev loading
nGene_SenseHAT_bootscroll: using /dev/fb0 (Sense HAT)
nGene_SenseHAT_bootscroll: scrolling "Frank" (5 chars, ... cols, 100 ms/col, rot=0, low_light=1)
nGene_SenseHAT_bootscroll: init done.

Parameters

  • rotation0|90|180|270. Rotates the 8×8 before write. Default 0.
  • low_light1 dims to ~35% via RGB565 scaling. Default 1.
  • scroll_ms — milliseconds per column; e.g. 110 ≈ 0.11 s/col. Default 100.
  • msg — string to scroll once; default "Frank".
  • swap_bytes1 swaps RGB565 byte order in the direct path; default 0.

Rotation cheat-sheet (physical board orientation)

  • rotation=0 — USB ports right, Raspberry logo upright → text upright.
  • rotation=90 — turn the Sense HAT 90° clockwise relative to above.
  • rotation=180 — upside-down relative to 0.
  • rotation=270 — 90° counter-clockwise relative to 0.

Re-run quickly (loop test)

for i in 1 2 3; do
  sudo insmod ./nGene_SenseHAT_bootscroll.ko scroll_ms=110 msg="Frank"
  sleep 1
  sudo rmmod nGene_SenseHAT_bootscroll
done

Troubleshooting

  • No LEDs change?
    1. Confirm overlay: grep -i "RPi-Sense FB" /var/log/kern.log /var/log/syslog or dmesg.
    2. Confirm fb mapping:
      for f in /sys/class/graphics/fb[0-7]/name; do echo -n "$f: "; cat "$f"; done
      Ensure the driver logs “using /dev/fbN (Sense HAT)”.
    3. Try a different rotation: rotation=90, then 180, 270.
    4. If colours look swapped on your setup (rare), use swap_bytes=1.
  • Text is too fast/slow? Adjust scroll_ms (e.g., scroll_ms=70 faster, scroll_ms=140 slower).
  • Competing framebuffer? If you see only vc4drmfb, ensure rpi-sense overlay is enabled and detected; the Sense HAT fb must appear as RPi-Sense FB.

Driver logs

sudo dmesg | tail -40
sudo journalctl -k -f

Check / Unload

lsmod | grep nGene_SenseHAT_bootscroll
sudo /sbin/rmmod nGene_SenseHAT_bootscroll

Notes

We write full 8×8 RGB565 frames to screen_base and then queue the first framebuffer page into fbdefio->pagereflist, scheduling info->deferred_work so the Sense HAT LED matrix reliably updates (regardless of deferred-IO settings). If colours ever look wrong on your stack, try swap_bytes=1 (white/black are unaffected).


Sense HAT bring-up and feature exploration (Written August 20, 2025)


I. Purpose and scope

This document presents a practical pathway to exercise a Sense HAT attached to Raspberry Pi 5 entirely from user space. The material covers enabling interfaces, installing user-space dependencies, verifying hardware presence, and conducting hands-on tests of the LED matrix, joystick, and environmental and inertial sensors. The approach is suitable as a prelude to kernel driver work, with attention to reproducible procedures, clear expectations, and concise diagnostics.

II. Hardware and system prerequisites

III. Enabling I²C and applying the Sense HAT overlay

The Sense HAT relies on I²C devices and a framebuffer-driven LED matrix, typically enabled through a device-tree overlay. Either an interactive tool or direct configuration may be used.

Option A — interactive (raspi-config)

sudo raspi-config
# Interface Options → I2C → Enable (accept reboot)

Option B — headless (edit boot configuration)

On Raspberry Pi 5, the boot configuration file is /boot/firmware/config.txt. Add:

dtparam=i2c_arm=on
dtoverlay=rpi-sense

Reboot to apply:

sudo reboot

Optional — live load without reboot

sudo dtoverlay rpi-sense
sudo dtoverlay -l

IV. Installing user-space libraries and tools

sudo apt update
sudo apt install -y sense-hat i2c-tools evtest python3-pip

Optional emulator (runs example code without the HAT):

sudo apt install -y sense-emu-tools

V. Hardware visibility and initial sanity checks

The following commands confirm that the overlay is active and the devices are visible to the operating system.

Overlay and device nodes

sudo dtoverlay -l
ls -l /dev/fb*            # LED matrix framebuffer (commonly /dev/fb1)
ls -l /dev/input/event*   # Joystick appears as an input event device

I²C device discovery (bus 1)

i2cdetect -y 1

Functional block Typical manifestation Expectation
LED matrix /dev/fb1 (framebuffer) Present when the overlay is active.
Joystick /dev/input/eventX (evdev) Reports KEY_UP/DOWN/LEFT/RIGHT/ENTER events.
Environmental sensors I²C addresses on bus 1 Addresses commonly include 0x5F (humidity/temperature) and 0x5C (pressure).
IMU (accel/gyro/compass) I²C addresses on bus 1 Addresses typically in the 0x1C/0x6A range, model-dependent.

VI. Functional tests (user space)

The examples below are self-contained. Each script exercises one functional block and returns clear observable outcomes. It is recommended to run them one by one.

A. LED matrix smoke test

from time import sleep
from sense_hat import SenseHat

s = SenseHat()
s.low_light = True         # reduces glare/heat; optional
s.set_rotation(0)          # 0, 90, 180, 270 as needed
s.show_message("Hello", scroll_speed=0.06)
s.clear()
sleep(0.5)
# Simple test pattern
R = (255, 0, 0); G = (0, 255, 0); B = (0, 0, 255); K = (0, 0, 0)
pixels = [R]*8 + [G]*8 + [B]*8 + [K]*8 + [R]*8 + [G]*8 + [B]*8 + [K]*8
s.set_pixels(pixels)
sleep(2)
s.clear()

Expected: a scrolling “Hello,” followed by a four-stripe pattern, then a blank matrix.

B. Joystick verification

This section details a reliable path to discover the Sense HAT joystick event device and to observe live key events at the Linux input layer, followed by an equivalent Python-based verification using the Sense HAT library.

  1. Install the input event tool

    sudo apt-get update
    sudo apt-get install -y evtest
    

    The evtest utility prints kernel input events in real time and is appropriate for confirming correct exposure of the joystick.

  2. Identify the joystick event node

    # Query all input event devices and locate the Sense HAT joystick by name
    grep -H . /sys/class/input/event*/device/name | grep -i "sense.*hat.*joystick"
    
    # Example output (device index varies by system):
    # /sys/class/input/event5/device/name:Raspberry Pi Sense HAT Joystick
    

    The path’s eventN component (e.g., event5) is the event device. The corresponding character node is /dev/input/eventN.

  3. Run a verbose live trace

    # Replace N with the discovered index (e.g., 5)
    sudo evtest /dev/input/eventN
    

    Successful operation shows a header with the device name and subsequent lines for each key transition. Typical events include KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, and KEY_ENTER. Values commonly appear as 1 (press), 0 (release), and sometimes 2 (auto-repeat/hold), depending on input subsystem settings.

  4. Convenient auto-detection (optional)

    EVENT=$(grep -il "sense.*hat.*joystick" /sys/class/input/event*/device/name \
            | sed -E 's#.*/(event[0-9]+)/.*$#\1#I')
    [ -n "$EVENT" ] && echo "Using /dev/input/$EVENT" && sudo evtest "/dev/input/$EVENT" \
      || echo "Sense HAT joystick event node not found"
    
  5. Expected evtest output cues

    Input driver version is 1.0.1
    Input device name: "Raspberry Pi Sense HAT Joystick"
    ...
    Event: time  ..., type 1 (EV_KEY), code 103 (KEY_UP),    value 1   # press
    Event: time  ..., type 1 (EV_KEY), code 103 (KEY_UP),    value 0   # release
    Event: time  ..., type 1 (EV_KEY), code 108 (KEY_DOWN),  value 1
    Event: time  ..., type 1 (EV_KEY), code 108 (KEY_DOWN),  value 0
    Event: time  ..., type 1 (EV_KEY), code 105 (KEY_LEFT),  value 1
    Event: time  ..., type 1 (EV_KEY), code 105 (KEY_LEFT),  value 0
    Event: time  ..., type 1 (EV_KEY), code 106 (KEY_RIGHT), value 1
    Event: time  ..., type 1 (EV_KEY), code 106 (KEY_RIGHT), value 0
    Event: time  ..., type 1 (EV_KEY), code  28 (KEY_ENTER), value 1
    Event: time  ..., type 1 (EV_KEY), code  28 (KEY_ENTER), value 0
    
  6. Mapping reference

    Sense HAT direction evdev key code Event value semantics
    up KEY_UP (103) 1=pressed, 0=released, 2=repeat (if enabled)
    down KEY_DOWN (108) 1=pressed, 0=released, 2=repeat
    left KEY_LEFT (105) 1=pressed, 0=released, 2=repeat
    right KEY_RIGHT (106) 1=pressed, 0=released, 2=repeat
    middle (press) KEY_ENTER (28) 1=pressed, 0=released, 2=repeat
  7. Python alternative (Sense HAT library)

    from sense_hat import SenseHat
    s = SenseHat()
    print("Waiting for joystick events (Ctrl+C to stop)...")
    while True:
        for e in s.stick.get_events():
            print(e.direction, e.action)  # e.g., 'up pressed', 'left released'

    The appearance of lines such as down pressed and down released confirms functional event delivery through the Sense HAT library. This complements the kernel-layer check by evtest.

  8. Notes and practical considerations

    • Permissions: input devices typically have group ownership input. Adding the current user to the input group enables non-root access after re-login:
      sudo usermod -aG input "$USER"
    • Multiple event nodes: if more than one matching device is present, prefer the node whose name exactly matches “Raspberry Pi Sense HAT Joystick”.
    • Busy devices: if another process holds the device, evtest may fail to open. Closing other readers resolves the issue.

C. Environmental sensors (temperature, humidity, pressure)

from sense_hat import SenseHat
s = SenseHat()
t = s.get_temperature()            # °C (can read a bit high near the matrix)
h = s.get_humidity()               # %RH
p = s.get_pressure()               # hPa
print(f"T={t:.1f} C  H={h:.0f} %  P={p:.0f} hPa")

For improved ambient temperature accuracy, keep the LED matrix off during sampling, enable low_light, or apply a small calibration offset determined empirically. External sensors placed away from heat sources provide the most reliable reference.

VII. Interpreting typical outputs

Command Key indicators Interpretation
sudo dtoverlay -l Contains rpi-sense Overlay is active; HAT subsystems are configured.
i2cdetect -y 1 Non-empty set of addresses (e.g., 0x5F, 0x5C) I²C sensors respond at expected locations.
ls -l /dev/fb* /dev/fb1 present LED matrix framebuffer is available for drawing.
evtest /dev/input/eventX KEY_* press/release events while actuating joystick Input subsystem receives joystick events correctly.

VIII. Troubleshooting

Symptom Probable cause Remedy
No devices on i2cdetect -y 1 I²C disabled, poor seating, header misalignment, insufficient power Enable dtparam=i2c_arm=on, reseat HAT, verify power and header alignment.
LED matrix remains off Overlay inactive or framebuffer missing Confirm dtoverlay -l; ensure /dev/fb1 exists; reboot after enabling overlay.
No joystick events Wrong event node or permissions Identify device via /sys/class/input; test with sudo evtest.
Temperature reads high Self-heating from LED matrix Disable LEDs during sampling; enable low_light; apply measured offset if needed.
Import error for sense_hat Packages not installed or Python path issue Reinstall: sudo apt install --reinstall sense-hat; quick check: python3 -c "import sense_hat; print('ok')".

IX. Recommended workflow toward kernel development

Written on August 20, 2025


Practical Sense HAT IMU calibration and human-readable tools (Written August 20, 2025)


I. Purpose and scope

This note replaces raw vector dumps with concise, human-interpretable tools that validate the Raspberry Pi Sense HAT inertial sensors on Raspberry Pi 5. A short calibration step is provided, followed by three utilities that communicate orientation and motion in ways that are immediately meaningful: a large arrow compass, a bubble level, and a movement meter. No kernel driver work is required for these tasks.

II. What merits calibration (and expected benefit)

III. Installation (once)

sudo apt update
sudo apt install -y sense-hat evtest python3-sense-hat jq

IV. Quick calibration (15 s magnetometer, ~8 s gyro)

The script below captures magnetometer extremes during free-space rotations (≈15 s), then measures gyroscope bias while the board is still (≈8 s). Results are written to ~/.config/ngene_sensehat_cal.json and are consumed by the tools that follow.

python3 nGene_SenseHAT_calibrate.py
cat ~/.config/ngene_sensehat_cal.json

V. Tools for human-readable feedback

Tool Purpose Display behavior Console readout
nGene_SenseHAT_compass.py Absolute heading visualization Single bright arrow on the outer ring pointing toward N/NE/… Heading in degrees and cardinal (e.g., 123.4° SE)
nGene_SenseHAT_level.py Level/tilt indication Green dot moves with tilt; centered dot indicates level Accelerometer values with current dot coordinates
nGene_SenseHAT_motion.py Movement magnitude Matrix brightness increases with motion intensity Acceleration magnitude and excess above 1g
  1. Compass (large arrow; uses calibration automatically)

  2. Bubble level (dot centers when flat)

  3. Movement meter (brighter = more motion)

VI. Suggested run order

# One-time calibration (perform slow figure-eights; then keep still)
python3 nGene_SenseHAT_calibrate.py

# Compass (board level)
python3 nGene_SenseHAT_compass.py

# Bubble level
python3 nGene_SenseHAT_level.py

# Movement meter
python3 nGene_SenseHAT_motion.py

Written on August 20, 2025


USB Driver


nGene USB Driver for OSR USB FX2





nGene USB2.x Driver Quick Guide (v0.1.3-dev · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. A more generic USB2.x-capable driver (FX2 used as the test device).

What’s in v0.1.3-dev

Directory

driver/USB0.1.3/
  ├── Makefile
  ├── ReadMe.txt
  ├── common_nGene.h      
  ├── nGene_USB_driver.c
  ├── nGene_USB_descriptor.c
  ├── nGene_USB_interrupt.c
  ├── nGene_USB_bulk.c
  └── nGene_user_level_app.c

Quick guide

  1. Prereqs (headers & toolchain)
    sudo apt-get update
    sudo apt-get install -y raspberrypi-kernel-headers build-essential \
      libncurses5-dev bison flex libssl-dev bc
  2. Build
    cd driver/USB0.1.3
    make clean && make
    make app    # builds ./nGene_user_level_app
  3. Load
    sudo rmmod nGene 2>/dev/null || true
    # 'verbose' controls log verbosity; 'monitor_all' dumps descriptors for any new USB2.x device
    sudo insmod ./nGene.ko verbose=1 monitor_all=1
  4. Verify & inspect
    dmesg | tail -n 200
    ls -l /dev/nGene_usb_0 /dev/device/nGene_usb_* 2>/dev/null || true
    With monitor_all=1, plugging any USB2.x device after insmod prints a rich descriptor dump in dmesg.
  5. Exercise user app (LEDs & 7-seg on FX2)
    sudo ./nGene_user_level_app 128 9
    sudo ./nGene_user_level_app 255 7
    sudo ./nGene_user_level_app 0 0
    led_mask 0..255 controls the 8-LED bar; digit 0..9 shows on the 7-seg (other values = dot only).
  6. Bulk OUT / IN sanity checks (FX2)
    # Bulk OUT (host → device) — always shows in dmesg
    python3 - <<'PY'
    import os
    with open("/dev/nGene_usb_0","wb", buffering=0) as f:
        f.write(bytes([i & 0xFF for i in range(512)]))
    PY
    
    # Bulk IN (device → host) — requires firmware producing data on 0x88
    sudo timeout 3 dd if=/dev/nGene_usb_0 bs=512 count=1 status=none | hexdump -C | head
    If firmware doesn’t produce IN data, the read times out (expected). Flash loopback (OUT 0x06 → IN 0x88) to see a 512-byte hexdump.
  7. Descriptor monitor demo (any USB2.x device)
    # With the module already loaded using monitor_all=1:
    dmesg -w   # keep this running, then plug any USB 2.x device and watch the dump
  8. Remove
    sudo rmmod nGene

How to test the FX2 interrupt switch (DIP)

  1. Ensure the module is loaded and the FX2 is connected.
  2. Watch kernel logs:
    dmesg -w
  3. Flip any DIP switch on the FX2 board.
    Each toggle causes an interrupt-IN packet; the handler schedules a work item to read the switch byte via a vendor request and prints:
    ~~~~~~~~~~~> SWITCH: ..*.*.**
    (*=ON, .=OFF; left→right = Switch1..Switch8).

Expected dmesg on success (FX2 bind)

nGene: nGene_print_descriptors USB Device (0x0547:0x1002) Plugged...
nGene: [INT-IN ] addr=81 interval=1 maxpkt=64
nGene: [BULK-OUT] addr=06 interval=0 maxpkt=512
nGene: [BULK-IN] addr=88 interval=0 maxpkt=512
nGene: interrupt_handler START
nGene: interrupt_handler Interrupt IN transfer OK
~~~~~~~~~~~> SWITCH: ...*....
[IN] ======> nGene_USB_unlocked_ioctl:
[OUT] <===== nGene_USB_unlocked_ioctl: return (0).
[IN] ======> __ngene_bulk_write:
[OUT] <===== __ngene_bulk_write: return (512).
# Only if firmware sends IN data:
[IN] ======> __ngene_bulk_read:
[OUT] <===== __ngene_bulk_read: return (512).

Parameters

Troubleshooting

Notes

FX2 endpoints typically enumerate as INT-IN 0x81, BULK-OUT 0x06, BULK-IN 0x88. The interrupt handler never sleeps; it schedules work that performs vendor control reads and LED/7-seg updates. Descriptor monitoring never binds to non-FX2 devices; it only logs descriptors for inspection.


Show v0.1.1 Quick Guide (older)

nGene USB-FX2 Driver Quick Guide (v0.1.1-dev · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. USB class driver for the OSR USB-FX2 board (VID 0x0547, PID 0x1002). Sets up an interrupt-IN URB for DIP switch state, provides LED bar + 7-segment helpers via vendor requests, and exposes a simple bulk read/write interface at /dev/nGene_usb_0 (or a udev symlink to it).

What’s in v0.1.1-dev

  • Interrupt path: ISR defers vendor control to a workqueue; switch state prints as SWITCH:.
  • Bulk I/O fixes: Bulk-IN uses kmalloc per transfer (DMA-map safe); Bulk-OUT uses usb_alloc_coherent + URB_NO_TRANSFER_DMA_MAP.
  • Correct HALT clearing: Uses usb_rcvbulkpipe/usb_sndbulkpipe with usb_clear_halt().
  • Verbose logs: VERBOSE=1 prints device/config/interface/endpoint descriptors and [IN]/[OUT] traces.

Directory

driver/USB0.1.1/
  ├── Makefile
  ├── ReadMe.txt
  ├── app.c
  ├── common_nGene.h
  ├── nGene.mod
  ├── nGene_USB_bulk.c
  ├── nGene_USB_driver.c
  └── nGene_USB_interrupt.c

Quick guide

  1. Prereqs (headers & toolchain)
    sudo apt-get update
    sudo apt-get install -y raspberrypi-kernel-headers build-essential \
      libncurses5-dev bison flex libssl-dev bc
  2. Build
    cd driver/USB0.1.1
    make clean && make
    make app
  3. Load
    sudo rmmod nGene 2>/dev/null || true
    sudo insmod ./nGene.ko VERBOSE=1
  4. Verify & inspect
    lsusb -d 0547:1002 -v | less
    dmesg | tail -n 100
    ls -l /dev/nGene_usb_0 /dev/device/nGene_usb_* 2>/dev/null || true
    The class driver may create /dev/device/nGene_usb_0; the provided udev rule adds a symlink /dev/nGene_usb_0.
  5. Exercise user app (LEDs & 7-seg)
    sudo ./app 128 9       # LED mask + digit (0..9; other = dot only)
    sudo ./app 255 5
    sudo ./app 0 0
  6. Bulk OUT / IN sanity checks
    # Bulk OUT (host → device) — always shows in dmesg
    python3 - <<'PY'
    import os
    with open("/dev/nGene_usb_0","wb", buffering=0) as f:
        f.write(bytes([i & 0xFF for i in range(512)]))
    PY
    # or the same helper script you have:
    sudo python bulk_write.py
    
    # Bulk IN (device → host) — requires firmware to produce data on 0x88
    sudo timeout 3 dd if=/dev/nGene_usb_0 bs=512 count=1 status=none | hexdump -C | head
    If your FX2 firmware does not send data, the read will time out with no output (expected). Flash a loopback firmware (OUT 0x06 → IN 0x88) to see a 512-byte hexdump and a matching dmesg line.
  7. Remove
    sudo rmmod nGene

How to test the interrupt switch (DIP)

  1. Ensure the module is loaded and the FX2 is connected.
  2. Watch kernel logs:
    dmesg -w
  3. Flip any DIP switch on the FX2 board.
    Each toggle causes an interrupt-IN packet. The handler logs and schedules a work item to read switches via a vendor control request, printing:
    ~~~~~~~~~~~> SWITCH: ..*.*.**
    (*=ON, .=OFF; left→right = Switch1..Switch8).

Expected dmesg on success

nGene: print_descriptors USB Device (0x0547:0x1002) Plugged...
nGene: [INT-IN ] addr=81 interval=1 maxpkt=64
nGene: [BULK-OUT] addr=06 interval=0 maxpkt=512
nGene: [BULK-IN] addr=88 interval=0 maxpkt=512
nGene: interrupt_handler START
nGene: interrupt_handler Interrupt IN transfer OK
~~~~~~~~~~~> SWITCH: ...*....
[IN] ======> nGene_USB_unlocked_ioctl:
[OUT] <===== nGene_USB_unlocked_ioctl: return (0).
[IN] ======> __ngene_bulk_write:
[OUT] <===== __ngene_bulk_write: return (512).
# Only if firmware sends IN data:
[IN] ======> __ngene_bulk_read:
[OUT] <===== __ngene_bulk_read: return (512).

Parameter

  • VERBOSE (int, default 1): set to 0 to reduce descriptor/trace prints.

Troubleshooting

  • Bulk IN prints nothing / times out: firmware is not producing data on 0x88; use loopback or a producer firmware. Wrap tests with timeout to avoid hangs.
  • DMA warning “rejecting DMA map of vmalloc memory”: fixed in this version (Bulk-IN uses kmalloc; Bulk-OUT uses coherent URBs).
  • Vendor control EACCES: make sure bmRequestType matches device/firmware (we use USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE for READ_SWITCHES).

Notes

Endpoints typically enumerate as INT-IN 0x81, BULK-OUT 0x06, BULK-IN 0x88. Interrupt handler never sleeps; it schedules work that performs vendor control reads and LED/7-seg updates.


Show v0.1.0 Quick Guide (older)

nGene USB-FX2 Driver Quick Guide (v0.1.0-dev · Raspberry Pi 5, Linux 6.12.34+rpt-rpi-2712)

Purpose. Minimal USB class driver for the OSR USB-FX2 board (VID 0x0547, PID 0x1002). On probe it prints device/interface/endpoint descriptors, sets up an interrupt-IN URB for the DIP switch state, and exposes /dev/usb/nGene_usbN. Read/write are simple echo stubs; vendor control helpers are used internally to read switches and drive the LED bar + 7-segment.

What’s in v0.1.0-dev

  • Workqueue status fetch: interrupt handler schedules process-context work to read switch state (fixes “scheduling while atomic”).
  • Descriptor logging: VERBOSE=1 prints detailed device/config/interface/endpoint info.
  • Stable device node: /dev/usb/nGene_usb%d via class minor base 192.
  • Safer kernel-6.x APIs: user pointers annotated; size prints fixed (%zu); allocs use kzalloc.
  • Echo demo: ./app -w stores a message; ./app -r reads it back.

Directory

driver/USB0.1.0/
  ├── Makefile
  ├── nGene_USB_driver.c
  ├── common_nGene.c
  ├── common_nGene.h
  └── app.c

Quick guide

  1. Prereqs (headers & toolchain)
    sudo apt-get update
    sudo apt-get install -y raspberrypi-kernel-headers build-essential \
      libncurses5-dev bison flex libssl-dev bc
  2. Build
    cd driver/USB0.1.0
    make clean && make
  3. Load
    sudo rmmod nGene 2>/dev/null || true
    sudo insmod ./nGene.ko VERBOSE=1
  4. Verify & inspect
    lsusb -d 0547:1002 -v | less
    dmesg | tail -n 100
    ls -l /dev/usb/nGene_usb*
  5. Exercise user app (echo demo)
    make app
    ./app -w                      # prompted write; stores message
    ./app -r                      # reads back the last stored message
    ./app -c                      # sends IOCTL_CLR (no-op in v0.1.0-dev)
  6. Remove
    sudo rmmod nGene

How to test the interrupt switch (DIP)

  1. Ensure the module is loaded and the FX2 is connected (see “Verify & inspect”).
  2. Watch kernel logs in another terminal:
    dmesg -w
  3. Flip any DIP switch on the FX2 board.
    Each toggle causes the device to send an interrupt-IN packet. The driver’s ISR logs nGene: interrupt IN OK and schedules a work item that reads switch states via a vendor control request, printing a line like:
    %%%%%%%%%%%%%%%%%%%% SWITCH: **..*..*
    (*=ON, .=OFF; left-to-right = Switch1..Switch8).
  4. Optional: trace raw USB traffic with usbmon:
    sudo modprobe usbmon
    sudo cat /sys/kernel/debug/usb/usbmon/1u | grep -i 0547:1002
    You should see periodic int-in URB completions around the time you flip switches.

Expected dmesg on success

nGene: driver loaded (v0.1.0-dev)
nGene: print_descriptors USB Device (0x0547:0x1002) Plugged...
nGene: [INT IN ] addr=81 intrv=1 maxp=64
nGene: [BULKOUT] addr=06 intrv=0 maxp=512
nGene: [BULK IN] addr=88 intrv=0 maxp=512
nGene: nGene_USB_probe_helper02
nGene: interrupt IN OK
%%%%%%%%%%%%%%%%%%%% SWITCH: **......   # (example pattern after a toggle)

Parameter

  • VERBOSE (int, default 1): set to 0 to reduce descriptor prints.

Troubleshooting

  • Enumeration timeout (-110)? Try a short USB-2.0 cable, a simple USB-2.0 hub, or add usbcore.quirks=0547:1002:k to /boot/firmware/cmdline.txt, then reboot.
  • No switch prints? Confirm you see nGene: interrupt IN OK after a toggle; keep VERBOSE=1 for descriptor sanity.
  • User app read/write? Use ./app -w then ./app -r; these are independent of the interrupt switch path.

Driver logs

sudo dmesg | tail -120
sudo journalctl -k -f

Check / Unload

lsmod | grep -E '^nGene\\b'
sudo /sbin/rmmod nGene

Notes

Endpoints typically enumerate as INT-IN 0x81, BULK-OUT 0x06, BULK-IN 0x88. The driver’s interrupt handler does not sleep; it schedules a work item that performs the vendor control read of switch state. LED bar/7-segment demo updates are triggered during probe and disconnect (via set_status()).


nGene USB Driver for Cypress CYUSB3KIT-003



nGene USB 3.x Driver Quick Guide (v0.1.2-dev · Cypress FX3 · Linux)

Purpose. SuperSpeed-capable USB driver targeting Cypress FX3 (CYUSB3KIT-003), while keeping legacy FX2 support for continuity.

Supported devices

lsusb | egrep '0547:1002|04b4:00f[0-3]'

What’s in v0.1.2-dev

Directory

driver/USB_cypress0.1.2/
  ├── Makefile
  ├── ReadMe.txt
  ├── common_nGene.h
  ├── nGene_USB_driver.c
  ├── nGene_USB_descriptor.c
  ├── nGene_USB_interrupt.c
  ├── nGene_USB_bulk.c
  └── nGene_user_level_app.c

Build & Load

cd driver/USB_cypress0.1.2
make
sudo rmmod nGene 2>/dev/null
sudo insmod nGene.ko verbose=1 in_timeout_ms=1000

Device node (important)

The driver registers usb/nGene_usb_%d; devtmpfs creates:

/dev/usb/nGene_usb_0

On successful bind you’ll see in dmesg:

nGene bound (speed=SS). Device node: /dev/usb/nGene_usb_0

Prefer /dev/nGene_usb_0 instead? Create a udev symlink:

echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="04b4", ATTR{idProduct}=="00f[0-3]", SYMLINK+="nGene_usb_0"' | sudo tee /etc/udev/rules.d/99-ngene.rules
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0547", ATTR{idProduct}=="1002",    SYMLINK+="nGene_usb_0"' | sudo tee -a /etc/udev/rules.d/99-ngene.rules
sudo udevadm control --reload
sudo udevadm trigger

Two ways to talk to the device

  1. Driver node mode (preferred): use /dev/usb/nGene_usb_0 for bulk read/write tests.
  2. usbfs mode (control + optional bulk): direct IOCTLs on /dev/bus/usb/BBB/DDD; avoid mixing with 1 at the same time.

Typical FX3 Bulk Source (00F1) flow — start, then read

sudo ./nGene_user_level_app --fx3-start          # sends vendor OUT bReq=0xE0
sudo ./nGene_user_level_app --read 4096          # driver-node mode
# or, usbfs mode:
sudo ./nGene_user_level_app --usbfs-read 4096

Note: If you try 0xB0/0xB1/0xB2 and get STALL, that’s expected for this firmware—use 0xE0.

Working read/write examples (by PID)

Dealing with node timing

sudo ./nGene_user_level_app --wait-node 500 --read 4096

USB 3.x “what to look for” in dmesg

Troubleshooting

Parameters


User-level Device Interface


Build HAT


Raspberry Pi Build HAT on Raspberry Pi 4 with LEGO Spike (Written August 21, 2025)

A terminology clarification is appropriate. The correct product name is Build HAT (the official Raspberry Pi add-on for LEGO® elements). The following material consistently adopts Build HAT and provides a structured, risk-aware guide for operation on Raspberry Pi 4, together with practical methods for integrating the LEGO Spike ecosystem. The approach favors conservative electrical assumptions, careful first-power procedures, and incremental testing.

I. Scope and orientation

The document addresses two objectives: (i) verifying reliable operation of the Build HAT on Raspberry Pi 4, and (ii) employing LEGO Spike components and hubs in complementary arrangements. Emphasis is placed on electrical safety (3.3 V logic domain on the Pi), power integrity (external motor power via the Build HAT), mechanical fit (cases, standoffs), and repeatable software enablement on current Raspberry Pi OS for Raspberry Pi 4.

II. Executive summary

III. Quick compatibility checklist (Build HAT + Raspberry Pi 4)

Aspect Expectation on Raspberry Pi 4 Verification guidance
GPIO logic domain 3.3 V CMOS; pins are not 5 V-tolerant Confirm that control lines remain within 0–3.3 V; employ level shifting if mixed-voltage accessories are present.
Power arrangement Build HAT powers motors/sensors from its external DC input Provision an adequate supply for simultaneous motor loads; avoid drawing motor current from the Pi header rails.
Back-powering protection No unintended power path into the Pi 5 V rail Verify that energizing the HAT alone does not power the Pi; follow a deliberate power-up sequence.
I²C access User I²C bus available for control Enable I²C and confirm visibility with i2cdetect; proceed to library-level tests afterward.
Software enablement Supported on current Raspberry Pi OS with Python Install the Build HAT library; validate by exercising a single motor and sensor.
Mechanical clearance Common Pi 4 cases and heatsinks are compatible Select suitable standoffs; maintain cable bend radii and keep-outs for enclosure lids.
EMC/ESD robustness Good practice remains advisable Route motor leads away from sensitive signals; ensure strain relief and tidy harnessing.

IV. Electrical and power practices specific to the Build HAT

1. Supply and distribution

2. Inrush and transient control

3. Noise and cabling

V. Software enablement on Raspberry Pi OS (Raspberry Pi 4)

1. Base configuration (I²C and system readiness)

# Enable I²C support (example via raspi-config)
sudo raspi-config   # Interfacing Options → I2C → Enable

# Install essentials (Python tooling and I²C utilities)
sudo apt update
sudo apt install -y python3-pip python3-venv i2c-tools

2. Install the Build HAT Python library

# Option A: system-wide installation
pip3 install buildhat

# Option B: per-project virtual environment (recommended)
python3 -m venv ~/venv/buildhat
. ~/venv/buildhat/bin/activate
pip install --upgrade pip
pip install buildhat

3. Sanity checks and minimal exercise

# Confirm I²C bus presence and probe the user bus (commonly '1')
i2cdetect -l
i2cdetect -y 1

# Minimal Python exercise (illustrative motor test; adjust port labeling as needed)
python3 - <<'PY'
from time import sleep
try:
    from buildhat import Motor
    m = Motor('A')             # Ports typically labeled A–D on the Build HAT
    m.run_for_degrees(180, speed=30)
    sleep(0.5)
    m.stop()
    print("Motor A exercised.")
except Exception as e:
    print("Test failed:", e)
PY

VI. LEGO Spike ecosystem integration options

1. Direct control via the Build HAT (recommended entry path)

2. Coordination with a Spike hub over Bluetooth Low Energy

3. Hybrid workflows

VII. Mechanical and thermal fit on Raspberry Pi 4

VIII. Troubleshooting and diagnostics

1. Common Build HAT symptoms

2. Useful runtime commands

dmesg | egrep -i "i2c|dtbo|overlay|build|eeprom"
lsmod | grep i2c
python3 -c "import sys; import buildhat, platform; print(buildhat.__version__, platform.python_version())"

IX. Quick-start procedure (practical bring-up)

  1. Mount the Build HAT on Raspberry Pi 4 with suitable standoffs; verify enclosure and heatsink clearances.
  2. Connect the external DC supply to the Build HAT per rating; leave motors disconnected for the first power-on.
  3. Enable I²C on Raspberry Pi OS; install prerequisites and the Build HAT library.
  4. Run i2cdetect to confirm bus visibility; execute the minimal Python exercise to validate the control path.
  5. Attach one motor or sensor; perform incremental tests before adding additional devices.
  6. Document port assignments, tested speeds/loads, and observed temperatures; retain conservative defaults for demonstrations and classroom use.

X. Appendices

1. Typical port/device mapping (illustrative)

Build HAT port Typical device Notes
A / B Spike/Powered-Up motors Begin with low speeds; measure current where sustained torque is required.
C / D Spike sensors (e.g., color, distance) Verify identification; calibrate readings against known references.

2. Minimal Python patterns (illustrative)

# Motor ramp and stop
from time import sleep
from buildhat import Motor
m = Motor('B')
for s in (20, 40, 60):
    m.start(speed=s); sleep(0.5)
m.stop()

# Simple sensor read (device type dependent)
from buildhat import ColorSensor
c = ColorSensor('C')
print("Color:", c.get_color())

XI. Closing remarks

The Build HAT operates cohesively with Raspberry Pi 4 when powered and configured as described. A cautious first-power sequence, conservative electrical budgets, and incremental software testing materially reduce integration risk. Where requirements evolve toward tighter timing or heavier mechanical loads, it is prudent to revisit supply margins, thermal limits, and interface choices with measured data.

Written on August 21, 2025


BrickPi


BrickPi3 on Raspberry Pi 4: from first connection to a successful motor spin (with troubleshooting log) (Written August 24, 2025)

I. Objective

Establish a clean, reproducible procedure to verify motor operation on a Dexter/Modular Robotics BrickPi3 attached to a Raspberry Pi 4, capturing the issues encountered and the remedies applied, so that the process can be repeated reliably in the future.

II. Hardware and prerequisites

The session assumed the following:

III. Symptoms observed

During initial attempts, multiple obstacles appeared:

IV. Root causes mapped

Symptom Likely cause Resolution
APT lock error Background package manager (packagekitd) held the lock Optionally stop PackageKit temporarily before running APT; not required for the final venv-based approach
PEP 668 error with pip System Python protected from direct pip installs Create and use a virtual environment (python3 -m venv)
pip install brickpi3 fails brickpi3 not published on PyPI Install from the BrickPi3 Git repository (pip install . in Software/Python)
Installer script expects pi user Script hardcodes user path assumptions Skip the script; install from source in the active venv
get_board_info missing Different library revision without that helper Skip that call; rely on other available methods
IndentationError Unintended whitespace or mix of tabs/spaces Rewrite the script with clean 4-space indents
No motor motion Motor plugged into a sensor port (e.g., S2) or BrickPi3 lacking external VIN power Move motor to MA–MD; ensure BrickPi3 is externally powered and switch is ON

V. Final working installation (Option B: virtual environment)

1. Create and enter a virtual environment

sudo apt-get install -y python3-venv
python3 -m venv ~/brickpi-env
source ~/brickpi-env/bin/activate
pip install --upgrade pip

2. Satisfy dependencies and install BrickPi3 from source

sudo apt-get install -y git
pip install spidev
git clone https://github.com/DexterInd/BrickPi3.git
cd BrickPi3/Software/Python
pip install .

3. Enable SPI and reboot

sudo raspi-config
# Interface Options → SPI → Enable → Finish
sudo reboot

After reboot, re-activate the virtual environment:

source ~/brickpi-env/bin/activate

VI. Port model and a critical warning

BrickPi3 ports MA–MD (also referenced as PORT_A–PORT_D) are motor ports. Ports S1–S4 are sensor ports. A motor connected to S1–S4 will not run.
Label on board Python constant Intended device
MA BP.PORT_A Motor
MB BP.PORT_B Motor
MC BP.PORT_C Motor
MD BP.PORT_D Motor
S1–S4 (sensor configuration) Sensors (touch, ultrasonic, color, etc.)

VII. Minimal motor spin verification

With the motor connected to MB and BrickPi3 externally powered, the following script proved sufficient:

import time
import brickpi3

BP = brickpi3.BrickPi3()

try:
    print("Running motor on Port B at 50% power for 3 seconds...")
    BP.set_motor_power(BP.PORT_B, 50)
    time.sleep(3)
    print("Stopping motor...")
    BP.set_motor_power(BP.PORT_B, 0)
except KeyboardInterrupt:
    pass
finally:
    BP.reset_all()

Expected terminal output:

Running motor on Port B at 50% power for 3 seconds...
Stopping motor...
BrickPi3 motor test on raspberry pi 4 — demonstrates a clean, reproducible procedure using a Python virtual environment, installing the BrickPi3 driver from source, enabling SPI, ensuring external VIN power, and verifying motion on MB (PORT_B) with a minimal script.

VIII. Diagnostics when motion is absent

If no motion is observed despite clean script execution, the following checks have been effective:

IX. Notes on earlier errors and their prevention

X. Condensed “do once” checklist

  1. Install tools and create a virtualenv: python3 -m venv ~/brickpi-env && source ~/brickpi-env/bin/activate && pip install --upgrade pip.
  2. Install dependencies and BrickPi3 from source: pip install spidev, clone the repo, and pip install . from Software/Python.
  3. Enable SPI via raspi-config and reboot; re-activate the venv after reboot.
  4. Connect motor to MA–MD (e.g., MB) and supply external power to BrickPi3.
  5. Run the minimal test script; expect the motor to spin and stop cleanly.

XI. Session command history (captured excerpt)

The following commands were recorded during the session. This list is retained to preserve context and reproduce the path taken.

  1. sudo apt-get update
  2. sudo apt-get install python3-pip
  3. pip3 install brickpi3
  4. sudo apt-get update
  5. sudo apt-get install python3-pip
  6. pip3 install brickpi3
  7. sudo apt-get install -y python3-venv
  8. python3 -m venv ~/brickpi-env
  9. source ~/brickpi-env/bin/activate
  10. pip install --upgrade pip
  11. pip install brickpi3
  12. (brickpi-env) curl -kL dexterindustries.com/update_brickpi3 \
  13. | bash -s -- --env-local --bypass-gui-installation
  14. curl -kL dexterindustries.com/update_brickpi3 | bash -s -- --env-local --bypass-gui-installation
  15. sudo apt-get install -y git
  16. ip install spidev
  17. pip install spidev
  18. git clone https://github.com/DexterInd/BrickPi3.git
  19. cd BrickPi3/Software/Python
  20. pip install .
  21. emacs motor_test.py
  22. sudo apt-get install emacs
  23. emacs motor_test.py
  24. python motor_test.py
  25. sudo raspi-config
  26. sudo shutdow -r now
  27. sudo reboot
  28. history

XII. Final remark

The procedure above distilled the working path: isolate a virtual environment, install BrickPi3 from source, enable SPI, ensure external motor power, connect to a motor port (MA–MD), and run a minimal test script. Following these steps restored deterministic behavior and yielded a successful motor spin on the BrickPi3 platform.

Written on August 24, 2025


LEGO NXT Snatcher Prototype III (BrickPi3 + Raspberry Pi 4): reproducible setup and run log (Written January 3, 2026)

I. Objective

Provide a compact, repeatable procedure to operate the LEGO NXT Snatcher (Prototype III) using Dexter Industries (Modular Robotics) BrickPi3 on a Raspberry Pi 4 with Python, ensuring deterministic motor behavior and encoder-based return-to-origin control.

II. Reference and demonstration

III. Hardware and prerequisites

IV. One-time installation (Python virtual environment)

1. Create and activate a virtual environment

sudo apt-get update
sudo apt-get install -y python3-venv git

python3 -m venv ~/brickpi-env
source ~/brickpi-env/bin/activate
pip install --upgrade pip

2. Install dependencies and BrickPi3 library from source

pip install spidev

git clone https://github.com/DexterInd/BrickPi3.git
cd BrickPi3/Software/Python
pip install .

3. Enable SPI and reboot

sudo raspi-config
# Interface Options → SPI → Enable → Finish
sudo reboot

After reboot:

source ~/brickpi-env/bin/activate

V. Wiring model (critical)

VI. Run procedure (each session)

  1. Activate the Python virtual environment. This step is mandatory, as BrickPi3 and all dependencies are installed exclusively inside this environment.
  2. Confirm BrickPi3 external power is present and the power switch is ON.
  3. Confirm motors are connected to MA–MD.
  4. Run the control script.
source ~/brickpi-env/bin/activate
python snatcher.py

Expected behavior: the snatcher arm executes the scripted motion and returns deterministically to its original position via encoder-based position control.

VII. Full control script (authoritative version)

# -----------------------------------------------------------------------------
# Title: NXT Snatcher Motor Return-to-Origin Test
#
# Description:
# Motor control and encoder-based position reset test for the NXT Snatcher
# autonomous robotic arm, adapted from "The LEGO MINDSTORMS NXT 2.0 Discovery Book".
# Executed using Dexter Industries BrickPi with Raspberry Pi to validate
# deterministic return-to-origin behavior via closed-loop position control.
#
# © 2013–present
# Hyunsuk Frank Roh, MD · nGene Hemodynamic Research Center.
# All rights reserved.
# -----------------------------------------------------------------------------

import time
import brickpi3

BP = brickpi3.BrickPi3()

try:
    # Reset encoder reference and capture the starting position
    BP.offset_motor_encoder(BP.PORT_B, BP.get_motor_encoder(BP.PORT_B))
    start_position = BP.get_motor_encoder(BP.PORT_B)

    print("Running motor on Port B at 50% power for 3 seconds...")
    BP.set_motor_power(BP.PORT_B, 50)
    time.sleep(3)

    print("Stopping motor...")
    BP.set_motor_power(BP.PORT_B, 0)
    time.sleep(0.5)

    print("Returning to original position...")
    BP.set_motor_position(BP.PORT_B, start_position)

    # Allow time for position control to complete
    time.sleep(2)

except KeyboardInterrupt:
    pass
finally:
    BP.reset_all()

VIII. Session command history (captured excerpt)

28  source ~/brickpi-env/bin/activate
29  ls
30  emacs snatcher.py
31  python snatcher.py
32  history

IX. Condensed checklist

  1. Virtual environment active
  2. SPI enabled; system rebooted after enabling
  3. BrickPi3 externally powered; switch ON
  4. Motor connected to MA–MD
  5. Script executed from active virtual environment

Written on January 3, 2026


Raspberry Pi AI Kit (M.2 HAT+ + Hailo-8) and a CSI camera module (IMX708 class)


Clean re-install playbook for Raspberry Pi 5 AI Kit and camera (Written January 4, 2026)

YouTube thumbnail

This document is intended as a repeatable checklist for a fresh microSD installation on Raspberry Pi 5 with the Raspberry Pi AI Kit (M.2 HAT+ + Hailo-8) and a CSI camera module (IMX708 class). Emacs is used exclusively for file edits. Where commands are provided, execution is assumed on the Raspberry Pi unless explicitly marked as “macOS host”.

The AI Kit occupies the M.2 slot with a PCIe accelerator. NVMe storage is not expected and nvme list being empty is normal unless a separate NVMe device is installed via other hardware.

I. Hardware assembly and pre-boot checks

Power-off assembly (do not hot-plug)

  1. Remove power completely

    Shut down and unplug the power supply before touching the M.2 HAT+, Hailo module, or the CSI ribbon cable.

  2. Assemble the AI Kit stack
    • Install the 16 mm stacking header cleanly and fully seated.
    • Mount the M.2 HAT+ flat and level using the provided spacers and screws.
    • Insert the Hailo M.2 module at a clear angle, then press down fully and secure with the screw (avoid PCB flex).
  3. Connect the camera before first boot
    • Insert the CSI ribbon cable fully and lock the connector.
    • Confirm correct cable orientation (common cause of “no cameras available”).
    • Prefer connecting the camera before boot; CSI sensor binding is boot-time oriented.

Mechanical failure patterns (fast reminders)

  1. Hailo absent in lspci

    Typically caused by partial seating of the HAT/header or incomplete insertion of the M.2 module.

  2. Camera absent in rpicam-hello

    Typically caused by ribbon orientation, wrong CAM connector, or incomplete insertion.

II. microSD imaging and first-boot prerequisites

SD imaging (recommended settings)

  1. Flash a fresh Raspberry Pi OS image to the new microSD

    Use a standard imaging tool. Prefer a 64-bit Raspberry Pi OS build suitable for Raspberry Pi 5.

  2. Pre-configure basic OS settings
    • Hostname, username, and password set at imaging time.
    • Wi‑Fi region and credentials set if Wi‑Fi is required.
    • SSH enabled at imaging time when headless access is needed.

Optional pre-boot edits on the boot partition (reduces reboots)

  1. Pre-enable external PCIe x1 for the AI Kit

    If the boot partition is accessible from the imaging host, edit config.txt on the boot partition and add:

    dtparam=pciex1
    dtparam=pciex1_gen=2

    Gen 2 is a conservative starting point. After stable operation, Gen 3 can be tested if desired.

  2. Headless SSH enable (only if SSH was not enabled in the imager)

    Create an empty file named ssh in the boot partition. This enables SSH on first boot.

III. First boot baseline and bulk package installation

System upgrade and one-shot package installation

  1. Update the OS and perform a full upgrade
    sudo apt update
    sudo apt full-upgrade -y
    sudo apt autoremove -y
  2. Install baseline + camera + AI tooling in one apt transaction

    The following snippet builds a package list from candidates that exist in the current repository and installs them with a single apt install.

    BASE_PKGS="
      emacs-nox
      openssh-server locales
      git curl wget ca-certificates
      build-essential pkg-config cmake ninja-build meson
      python3 python3-venv python3-pip
      gdb strace ltrace
      pciutils usbutils
      i2c-tools v4l-utils
      ripgrep fd-find tree lsof psmisc
      nvme-cli smartmontools fio
      device-tree-compiler
      clangd clang-format universal-ctags
      bc bison flex libssl-dev libelf-dev dwarves
      linux-perf trace-cmd bpftrace
      raspberrypi-kernel-headers
    "
    
    AI_CAM_PKGS="
      rpicam-apps libcamera-apps libcamera-dev
      hailo-all hailo-rt hailo-firmware hailo-driver hailo-tappas
    "
    
    PKGS=""
    for p in $BASE_PKGS $AI_CAM_PKGS; do
      apt-cache show "$p" >/dev/null 2>&1 && PKGS="$PKGS $p"
    done
    
    sudo apt install -y $PKGS
  3. Reboot after upgrades and driver/tool installation
    sudo reboot

Workspace and Python environment (user-level work)

  1. Create a simple workspace layout
    mkdir -p ~/work/{ai,cam,notes,build}
    mkdir -p ~/venv
  2. Create a Python virtual environment and update tooling
    python3 -m venv ~/venv/rpi
    source ~/venv/rpi/bin/activate
    python -m pip install --upgrade pip wheel setuptools

IV. PCIe x1 and Hailo bring-up verification

Confirm external PCIe x1 configuration (Emacs-only)

  1. Edit the firmware configuration
    sudo emacs -nw /boot/firmware/config.txt
  2. Ensure the PCIe x1 parameters exist
    dtparam=pciex1
    dtparam=pciex1_gen=2
  3. Reboot to apply
    sudo reboot

Hardware recognition gate (expected outputs)

  1. Confirm the Hailo endpoint appears in PCIe inventory
    lspci -nn

    Expected pattern:

    Co-processor ... Hailo Technologies Ltd. Hailo-8 AI Processor ...
  2. Confirm PCIe link training and driver probe
    dmesg | grep -iE "pcie|hailo" | tail -n 120
  3. Confirm firmware presence (prevents error -2 probe failures)
    ls -l /lib/firmware/hailo/
    ls -l /lib/firmware/hailo/hailo8_fw.bin
Check Pass condition Typical failure Most effective remedy
lspci -nn Hailo endpoint present Only bridges/RP1 present Reseat HAT/header/M.2 module; confirm dtparam=pciex1
dmesg | grep -i hailo No firmware load failure Failed to write ... hailo8_fw.bin (err -2) Install Hailo firmware packages; verify /lib/firmware/hailo/hailo8_fw.bin
nvme list May be empty (normal) Interpreted as a failure Remember: M.2 is occupied by Hailo, not NVMe storage

V. Camera bring-up verification (IMX708 class)

Hardware binding gate (kernel level)

  1. Confirm sensor driver and related modules are loaded
    lsmod | grep -E "imx708|dw9807|rp1_cfe|unicam|pisp" || true

    IMX708 + autofocus modules indicate correct electrical detection and binding.

  2. Confirm sensor-related boot messages exist
    dmesg | grep -iE "imx708|camera|csi|unicam|rp1.*csi" || true

Userspace detection gate (libcamera/rpicam)

  1. List cameras
    rpicam-hello --list-cameras || true
  2. Run a short preview test
    rpicam-hello -t 3000 || true
  3. Optional device listing for debugging
    v4l2-ctl --list-devices || true
    ls -l /dev/media* /dev/v4l-subdev* /dev/video* 2>/dev/null || true

If “No cameras available” appears

  1. Perform only physical corrections under power-off
    • Reseat CSI ribbon cable fully; verify orientation.
    • Try the alternate CAM connector if available.
    • Verify correct adapter type (15-pin vs 22-pin) if an adapter is used.
  2. Reboot after physical changes

    Camera sensor enumeration is boot-time oriented.

VI. SSH reliability and host-key hygiene

Enable and validate the SSH server

  1. Enable and start SSH
    sudo systemctl enable --now ssh
    sudo systemctl status ssh --no-pager
  2. If SSH becomes unreachable, recover locally

    “Connection refused” indicates sshd is not listening. Local console access is the most reliable recovery path.

Host key change after OS reinstall (macOS host)

  1. Remove the stale host key entry
    ssh-keygen -R 192.168.0.29
  2. Reconnect and accept the new key
    ssh frank@192.168.0.29

Safe SSH configuration edits (avoid lockout)

  1. Validate SSH configuration before restarting
    sudo sshd -t
  2. Restart SSH only after successful validation
    sudo systemctl restart ssh

VII. Locale hygiene (optional, recommended for clean logs)

Locale hygiene is not required for Hailo/camera functionality, but it reduces warning noise and avoids encoding-related toolchain surprises.

Generate and select a real UTF-8 locale

  1. Generate locales
    sudo dpkg-reconfigure locales
  2. Set defaults explicitly
    sudo update-locale LANG=en_GB.UTF-8 LC_ALL=en_GB.UTF-8
  3. Validate
    locale
    locale -a | grep -i utf

If LC_CTYPE=UTF-8 reappears via SSH environment forwarding

  1. Disable accepting forwarded locale variables (server-side)
    sudo emacs -nw /etc/ssh/sshd_config.d/99-no-acceptenv.conf

    File contents:

    AcceptEnv
  2. Validate and restart SSH
    sudo sshd -t
    sudo systemctl restart ssh
  3. Verify a clean session after re-login
    echo "LC_CTYPE=$LC_CTYPE"
    locale

VIII. User-level programming setup (camera + Hailo)

Python environment for prototyping

  1. Activate the virtual environment
    source ~/venv/rpi/bin/activate
  2. Install common prototyping packages (optional)
    python -m pip install numpy

C/C++ environment for controlled experiments

  1. Confirm libcamera dev flags resolve
    pkg-config --cflags --libs libcamera || true
  2. Confirm compiler toolchain
    cc --version
    cmake --version
    ninja --version

Emacs-oriented navigation (optional)

  1. Confirm clangd availability
    clangd --version || true
  2. Generate tags for local code browsing
    ctags -R ~/work 2>/dev/null || true

IX. Kernel-level preparation (later phase)

Headers and build prerequisites

  1. Confirm matching headers are installed
    uname -r
    ls -ld /lib/modules/$(uname -r)/build || true
  2. Keep kernel work out-of-tree initially

    Start with minimal modules, then progress to tracing and device-tree inspection only after a stable user-level baseline is established.

X. Quick verification bundle (copy/paste friendly)

Single-run diagnostics (Raspberry Pi)

  1. echo "=== kernel/firmware ==="
    uname -a
    vcgencmd version
    
    echo "=== PCIe inventory (Hailo must appear) ==="
    lspci -nn
    
    echo "=== Hailo logs (no firmware error -2 expected) ==="
    dmesg | grep -iE "hailo|pcie" | tail -n 160 || true
    ls -l /lib/firmware/hailo/ || true
    
    echo "=== Camera kernel binding (IMX708 expected) ==="
    lsmod | grep -E "imx708|dw9807|rp1_cfe|pisp" || true
    dmesg | grep -iE "imx708|camera|csi|unicam" | tail -n 120 || true
    
    echo "=== Camera userspace ==="
    rpicam-hello --list-cameras || true
    rpicam-hello -t 2000 || true

Single-run hygiene (macOS host)

  1. # Remove stale SSH host key after SD re-image
    ssh-keygen -R 192.168.0.29
    
    # Reconnect
    ssh frank@192.168.0.29

Written on January 4, 2026


Camera verification and 5-second MP4 test video on Raspberry Pi 5 (IMX708) (Written January 4, 2026)

I. Purpose

Provide a repeatable procedure to confirm that a Raspberry Pi CSI camera module (IMX708 class) is physically connected and correctly bound to the libcamera pipeline on Raspberry Pi 5, and then produce a short sample video as MP4 for convenient playback and sharing.

II. Preconditions and operating rules

III. Camera connection verification

Kernel and libcamera enumeration

  1. List cameras detected by libcamera:

    rpicam-hello --list-cameras
  2. Pass condition:

    • An entry such as 0 : imx708 is shown.
    • Supported modes are listed (e.g., 1536×864, 2304×1296, 4608×2592).
  3. Fail condition:

    • No cameras listed, or error messages indicating missing sensor binding.

Short runtime acquisition test

  1. Attempt a short acquisition (headless-friendly):

    rpicam-hello -t 3000 || true
  2. Pass condition:

    • No “failed to acquire camera” error appears.
    • Mode selection and stream configuration lines appear.
  3. Common benign output:

    • Preview window unavailable (typical over SSH/headless).

IV. Still image test

Capture and verify a JPEG

  1. Capture a still image:

    rpicam-still -o test.jpg
  2. Verify output file presence and type:

    ls -lh test.jpg
    file test.jpg
  3. Pass condition:

    • test.jpg exists and is non-trivial in size.
    • file reports JPEG image data.

V. Video test (5 seconds)

Record 5 seconds as H.264

  1. Record a 5-second test clip (recommended baseline settings):

    rpicam-vid -t 5000 \
      --width 1280 --height 720 \
      --framerate 30 \
      --codec h264 \
      --inline \
      --nopreview \
      -o test_5s.h264
  2. Verify the H.264 file exists:

    ls -lh test_5s.h264
  3. Optional probe (header/stream sanity):

    ffprobe test_5s.h264

Convert H.264 to MP4 (no re-encode)

  1. Mux into MP4 with copy mode (fast, no quality loss):

    ffmpeg -y \
      -r 30 \
      -i test_5s.h264 \
      -c:v copy \
      -movflags +faststart \
      test_5s.mp4
  2. Verify MP4 duration and file size:

    ls -lh test_5s.mp4
    ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 test_5s.mp4
  3. Pass condition:

    • test_5s.mp4 exists.
    • Duration is approximately 5 seconds.

VI. Quick pass/fail checklist

Check Command Pass condition Typical failure Primary remedy
Camera enumerates rpicam-hello --list-cameras IMX708 appears with modes No cameras listed Power off; reseat CSI cable; verify connector orientation; reboot
Camera acquisition rpicam-hello -t 3000 No “failed to acquire camera” Device busy / pipeline in use Stop other camera processes; see Troubleshooting
Still image output rpicam-still -o test.jpg JPEG created, non-trivial size No file or capture error Re-check enumeration; confirm no concurrent camera user
Video output rpicam-vid ... -o test_5s.h264 H.264 file created, several MB Short/empty file Verify no concurrent camera user; re-run at 1280×720/30fps baseline
MP4 created ffmpeg ... -c:v copy ... test_5s.mp4 MP4 exists, ~5s duration MP4 missing or duration incorrect Ensure the H.264 file exists first; re-run mux command

VII. Troubleshooting

Camera device busy / failed to acquire camera

  1. Identify camera users (optional):

    ps aux | grep -E 'rpicam|libcamera|ffmpeg|go2rtc|mediamtx' | grep -v grep
  2. Stop common camera holders:

    sudo pkill -f rpicam
    sudo pkill -f libcamera
    sudo pkill -f ffmpeg
    sudo pkill -f go2rtc
    sudo pkill -f mediamtx
  3. Re-test acquisition:

    rpicam-hello -t 3000

Preview window unavailable

  1. This message is expected in SSH/headless operation and does not block file capture.

  2. Use rpicam-still and rpicam-vid for headless testing, as shown above.

VIII. Deliverables (expected output files)

Stable camera validation is best performed with file outputs (still + short H.264 + MP4 mux) under headless SSH. Preview is optional and depends on local display availability.

Written on January 4, 2026


Local Hailo person recognition using CSI camera (assumed working) (Written January 4, 2026)

YouTube thumbnail

I. Objective

Provide a repeatable, headless-safe workflow to record a short “proof video” that shows person-related overlays produced by Hailo AI using a locally connected CSI camera. The procedure assumes that the camera and Hailo device are already installed and functioning properly.

II. Core concept

The most reproducible workflow is file-based:

III. Preconditions

The following assumptions are made:

IV. Recommended model for person recognition overlays

For a visually obvious “person recognition” proof, the most reliable configuration is the person+face detector JSON:

V. Headless-safe capture procedure

Environment setup

  1. Ensure the pipeline does not attempt to use an X11/Wayland display:

    unset DISPLAY
    export QT_QPA_PLATFORM=offscreen

Record an AI-annotated H.264 clip

  1. Capture 10 seconds at 1280×720 (30 fps) with overlays:

    rpicam-vid -t 10000 \
      --width 1280 --height 720 \
      --framerate 30 \
      --codec h264 \
      --inline \
      --nopreview \
      --post-process-file /usr/share/rpi-camera-assets/hailo_yolov5_personface.json \
      -o ai_personface.h264
  2. Confirm that the output file exists and is non-trivial in size:

    ls -lh ai_personface.h264

Mux to MP4 without re-encoding

  1. Convert H.264 to MP4 using timestamp generation for compatibility:

    ffmpeg -y \
      -fflags +genpts \
      -r 30 \
      -f h264 -i ai_personface.h264 \
      -c:v copy \
      -movflags +faststart \
      ai_personface.mp4
  2. Confirm duration:

    ffprobe -v error -show_entries format=duration \
      -of default=nw=1:nk=1 ai_personface.mp4

VI. Verification methods (headless-first)

Frame extraction for proof (recommended)

  1. Extract one PNG per second and inspect the images for boxes/labels:

    rm -f pf_%02d.png
    ffmpeg -y -i ai_personface.mp4 -vf fps=1 pf_%02d.png
    ls -lh pf_*.png | head
  2. Success criteria:

    • Person box appears when a person is in frame.
    • Face box appears when the face is visible and sufficiently large.
    • Boxes track movement across frames.

Optional local playback

  1. If a display session is available, playback can be used as a quick visual check:

    ffplay -autoexit ai_personface.mp4

VII. Practical guidance for reliable detections

VIII. Common issues and direct fixes

Camera pipeline is busy

  1. If “device busy” or “pipeline handler in use” occurs, terminate conflicting processes:

    sudo pkill -f rpicam
    sudo pkill -f ffmpeg

Output exists but overlays are missing

  1. Ensure the post-process file is an overlay-producing configuration:

    • Recommended: hailo_yolov5_personface.json
    • Alternative: hailo_yolov8_inference.json (general object boxes)
  2. Confirm overlays by extracting frames (preferred) rather than relying on raw H.264 playback.

MP4 muxing shows timestamp warnings

  1. Use timestamp generation and explicit H.264 demuxing:

    ffmpeg -y -fflags +genpts -r 30 -f h264 -i ai_personface.h264 \
      -c:v copy -movflags +faststart ai_personface.mp4

IX. Minimal one-command “proof video” set

The following is a concise, repeatable command set for a 10-second person+face proof:

unset DISPLAY
export QT_QPA_PLATFORM=offscreen

rpicam-vid -t 10000 --width 1280 --height 720 --framerate 30 \
  --codec h264 --inline --nopreview \
  --post-process-file /usr/share/rpi-camera-assets/hailo_yolov5_personface.json \
  -o proof_personface.h264 && \
ffmpeg -y -fflags +genpts -r 30 -f h264 -i proof_personface.h264 \
  -c:v copy -movflags +faststart proof_personface.mp4 && \
rm -f proof_%02d.png && \
ffmpeg -y -i proof_personface.mp4 -vf fps=1 proof_%02d.png

X. Expected deliverables

Written on January 4, 2026


Lego Spike Pro - Technic Large Hub (45601)


Getting started with LEGO SPIKE Large Hub Python motor control (Written September 6, 2025)

I. Purpose and scope

This document provides a professional guide to controlling a Powered Up motor using the LEGO SPIKE Large Hub within the macOS Desktop App environment. Unlike the SPIKE App or Pybricks runtime, this environment uses a streamlined Python API based on hub, motor, and runloop. The text emphasizes reproducible setup, safe wiring, and a verified sample program that achieves a high-velocity, one-revolution motor move. The material is intended for technical readers who require clarity, hierarchy, and reliability for practical use.

II. System requirements and components

Item Minimum specification Notes
Hub LEGO SPIKE Large Hub (6-port Powered Up) Confirm battery is adequately charged.
Motor LEGO Powered Up motor (Technic/Medium/XL) Any Powered Up motor compatible with the Large Hub.
Cable Integrated Powered Up connector Securely insert into ports A–F.
Computer macOS with LEGO SPIKE Desktop App App provides the runtime for hub, motor, runloop.

III. Environment preparation

  1. Install the LEGO SPIKE Desktop App on macOS.
  2. Power on the Large Hub and connect via USB or Bluetooth.
  3. Update the hub firmware if the app prompts for it.
  4. Create a new Python project in the Desktop App. The environment loads default imports: hub, motor, and runloop.

IV. Hardware connection and safety checklist

Caution: Avoid running motors into mechanical end stops or prolonged stalls, as this may trigger thermal shutdown.

V. Quick start with the Desktop App Python runtime

The default Python environment differs from the SPIKE App API. It does not use the spike module but provides direct asynchronous primitives. The following example rotates a motor connected to Port A exactly one revolution at high velocity.

Example A — One revolution at 900 degrees per second

# file: motor_one_rotation_spike_desktop.py
# Environment: LEGO SPIKE Desktop App (macOS) — runtime with 'hub', 'motor', 'runloop'
# Purpose: Rotate motor on Port A exactly one revolution at high angular velocity, then stop.

from hub import port, light_matrix
import motor
import runloop

# Angular velocity in degrees per second
VELOCITY_DPS = 900

async def main():
    # Status message
    await light_matrix.write("Start")

    # Perform one rotation (360 degrees) at specified velocity
    await motor.run_for_degrees(port.A, 360, VELOCITY_DPS)

    # Stop motor explicitly
    motor.stop(port.A)

    # Completion message
    await light_matrix.write("Done")

# Entrypoint
runloop.run(main())

VI. Operational guidelines

VII. Troubleshooting and verification matrix

Symptom Likely cause Recommended action
Motor does not move Wrong port identifier Check port mapping, e.g., use port.A for Port A.
Motor spins too slowly Velocity too low Increase VELOCITY_DPS (e.g., 1200).
Script error on import Non-Desktop API code used Use hub, motor, runloop modules only.
Hub unresponsive Battery or connection issue Recharge hub; reconnect via USB/Bluetooth.

VIII. Appendix: quick reference

IX. Closing remarks

The macOS LEGO SPIKE Desktop App enables straightforward motor control through asynchronous Python scripts. By specifying angular velocity in degrees per second and movement distance in degrees, one can achieve precise, high-speed operations with minimal code. The provided sample serves as a reliable baseline that can be extended to more advanced robotics tasks, including multi-motor coordination and integration with sensors.

Written on September 6, 2025


Gyro Boy


Reference



Lego EV3 MicroPython


Lego SPIKE MicroPython




Why EV3 GyroBoy Demonstrates Superior Stability and How 0.1.9-dev Can Be Upgraded by Benchmarking Its Gyro Script (Written September 16, 2025)

I. Overview

The LEGO EV3 GyroBoy program has been refined over years of development and demonstrates a higher level of sophistication compared to a typical Spike Prime balancing implementation. Although both platforms share similar physical principles, the EV3 code integrates advanced control mechanisms that significantly improve stability and recovery. The following sections outline the core differences that explain why EV3 maintains balance more effectively.

II. Key differences in control strategy

  1. Control loop timing

    EV3: Employs StopWatch to precisely measure the actual loop period (average_control_loop_period). The integration of body angle and wheel calculations directly use this measured timing, which ensures consistency even if the loop experiences delays.

    Spike: Utilizes a fixed dt = loop_ms / 1000.0. In Python on Spike Prime, loop execution often deviates from the requested schedule. As a result, angle integration and control outputs drift or overshoot, reducing stability.

    Conclusion: EV3 achieves robustness against jitter, while Spike remains sensitive to timing irregularities.

  2. Dynamic gyro offset tracking

    EV3: Continuously refines the gyro offset using a leaky integrator. This adaptive method gradually corrects offset errors during operation:

    gyro_offset *= (1 - GYRO_OFFSET_FACTOR)
    gyro_offset += GYRO_OFFSET_FACTOR * gyro_sensor_value

    This approach accounts for sensor drift and temperature-induced variation.

    Spike: Performs offset calibration only once during initialization. Over time, gyro drift accumulates, causing imbalance.

  3. Filtering of wheel rate

    EV3: Implements a moving average of wheel movement (motor_position_change list) to compute wheel rate smoothly. This reduces noise and provides stable input to the controller.

    Spike: Calculates wheel rate based on a simple difference between the current and last wheel angle, introducing more noise and instability.

  4. Integrated drive and steering control

    EV3: Blends balancing with external commands such as driving forward, turning, and reacting to sensors. The control law incorporates terms for body angle, body rate, wheel angle, wheel rate, and external drive speed:

    output_power = (-0.01 × drive_speed) + (0.8 × body_rate + 15 × body_angle + 0.08 × wheel_rate + 0.12 × wheel_angle)

    This layered structure ensures that stability is preserved while executing higher-level behaviors.

    Spike: Currently combines only rate, angle, wheel, and speed terms without dynamic drive-speed or steering integration, limiting adaptability.

  5. Fall detection and recovery

    EV3: Detects instability by monitoring both output_power and elapsed time. A fall is declared only if saturation persists beyond one second, giving the robot more opportunity to recover from disturbances.

    Spike: Declares a fall immediately if body angle exceeds ±45°. This threshold-based method often terminates balancing prematurely, even during recoverable tilts.

III. Comparative summary

Feature EV3 GyroBoy Spike Implementation
Loop timing Measured per cycle with StopWatch Fixed interval assumption (loop_ms)
Gyro offset Adaptive leaky integrator One-time calibration
Wheel rate Filtered using moving average Single-step difference
Drive & steering Integrated into control law Limited or absent
Fall detection Time-delayed with output saturation check Immediate angle threshold

IV. Conclusion

The EV3 GyroBoy program demonstrates a more advanced control design, characterized by precise timing management, dynamic sensor offset correction, noise reduction through filtering, and integrated action control. These refinements collectively explain its superior balancing performance and resilience against disturbances. For Spike Prime to reach comparable stability, improvements in timing accuracy, continuous offset tracking, wheel rate filtering, and smarter fall detection strategies are necessary.

Written on September 16, 2025

Back to Top