Peripheral Access¶
Phase 4 — Track B — Module 5.1 · Application Development
Focus: Access and control hardware peripherals on the Jetson Orin Nano 8GB from Linux userspace — GPIO, PWM, UART, SPI, I2C, CAN, USB, storage, and backlight. Every interface is shown with both sysfs/chardev and library approaches.
Hub: 5. Application Development
Scope boundary: this module is about runtime access from Linux. If you need to change pinmux, device tree, or custom carrier board wiring, use:
1. GPIO (Linux)¶
Jetson Orin Nano exposes GPIOs through the gpiochip character device interface (/dev/gpiochipN). The legacy sysfs interface (/sys/class/gpio/) is deprecated — use libgpiod instead.
libgpiod tools¶
# List all GPIO chips
gpiodetect
# Show all lines on a chip
gpioinfo gpiochip0
# Read a GPIO input from a real line offset
gpioget gpiochip0 43
# Set a GPIO output high on a real line offset
gpioset gpiochip0 43=1
# Monitor GPIO events (rising/falling edge) on a real line offset
gpiomon gpiochip0 43
What gpiodetect means on Jetson¶
On a Jetson Orin Nano dev kit you will typically see something like:
gpiochip0is the main Tegra234 GPIO controllergpiochip1is the always-on GPIO controller used for low-power and wake-related signals- the number in parentheses is how many line offsets exist on that controller
For most 40-pin header work, you will usually spend your time on gpiochip0.
How to read gpioinfo¶
gpioinfo gpiochip0 prints one row per line offset. Example:
line 35: "PG.00" "Force Recovery" input active-low [used]
line 43: "PH.00" unused input active-high
line 131: "PZ.01" "interrupt" input active-high [used]
Read each field like this:
| Field | Meaning |
|---|---|
line 43 |
The chip-relative line offset. This is the number you pass to gpioget, gpioset, and gpiomon. |
"PH.00" |
The SoC pad or line name exposed by the driver/device tree. |
unused / "Force Recovery" / "interrupt" |
The current consumer. unused means no driver or userspace process owns it right now. |
input / output |
Current direction. |
active-high / active-low |
Logical polarity. |
[used] |
The line is already requested by the kernel or another process. Do not reuse it casually. |
Runtime names you actually care about¶
For this guide, keep these names separate:
| Name | Example | Use it for |
|---|---|---|
| Header pin number | Pin 7 |
Physical wiring on the dev kit |
| Board signal name | GPIO09 |
Board pinout tables and schematics |
| SoC pad name | PH.00 |
Low-level identity of the signal |
gpiochip line offset |
43 |
gpioget, gpioset, gpiomon |
Short version:
- use header pin numbers when wiring
- use board signal names when reading the pinout
- use SoC pad names when reading low-level GPIO or BSP data
- use line offsets when talking to Linux through
libgpiod
What names like PH.00 mean¶
Names like PH.00, PG.00, or PZ.01 are SoC pad / GPIO bank names.
Read PH.00 as:
P= GPIO naming prefixH= bank or port00= bit number inside that bank
So PH.00 is the SoC-side identity of the signal. It is not a header pin number and not a Linux line offset.
Why gpioget gpiochip0 <line> failed in your shell¶
<line> in the docs is a placeholder, not something you type literally.
In bash, angle brackets mean shell redirection. So:
is interpreted as "read stdin from a file named line", which is why it fails.
Replace the placeholder with a real line offset from gpioinfo, for example:
# Read line 43 on gpiochip0
gpioget gpiochip0 43
# Drive line 43 high
gpioset gpiochip0 43=1
# Monitor line 43 for edges
gpiomon gpiochip0 43
Also note:
gpiogetis for readinggpiosetis for driving outputsgpioget gpiochip0 <line>=1is invalid becausegpiogetdoes not set values
Practical workflow on Jetson¶
Use this sequence when you are trying to identify a GPIO safely:
# 1. See which controllers exist
gpiodetect
# 2. Inspect the main controller
gpioinfo gpiochip0
# 3. Pick an UNUSED line only after checking pinmux and board function
gpioget gpiochip0 43
Before driving a line with gpioset, verify all of these:
- the pin is really muxed as GPIO
- it is not marked
[used] - it is not a boot, recovery, regulator, wake, or other board-critical signal
- you know what external hardware is attached to it
Finding a line by name¶
If the line names are populated, gpiofind is often easier than scrolling through the full list:
That returns the owning chip and line offset for that named line.
Where pinmux and carrier-board work belongs¶
This guide stops at runtime Linux access.
If you need to answer questions like:
- "Which SoC pad should I route on a custom carrier?"
- "How do I edit the NVIDIA pinmux spreadsheet?"
- "How do I change device tree or BSP defaults?"
- "Why is this pin SPI on my board instead of GPIO?"
use these earlier modules instead:
libgpiod in C¶
#include <gpiod.h>
struct gpiod_chip *chip = gpiod_chip_open("/dev/gpiochip0");
struct gpiod_line *line = gpiod_chip_get_line(chip, 42);
/* Output */
gpiod_line_request_output(line, "my-app", 0);
gpiod_line_set_value(line, 1);
/* Input */
gpiod_line_request_input(line, "my-app");
int val = gpiod_line_get_value(line);
gpiod_chip_close(chip);
libgpiod in Python¶
import gpiod
chip = gpiod.Chip('gpiochip0')
line = chip.get_line(42)
# Output
line.request(consumer="my-app", type=gpiod.LINE_REQ_DIR_OUT)
line.set_value(1)
# Input
line.request(consumer="my-app", type=gpiod.LINE_REQ_DIR_IN)
val = line.get_value()
Device tree GPIO configuration¶
This module assumes the pin is already configured correctly for GPIO use.
- On the developer kit, that often means using Jetson-IO
- On a custom carrier, that means the right pinmux and device tree are already in your BSP
References:
2. GPIO naming — alphanumeric to numeric assignment¶
Jetson uses several GPIO naming schemes at once. They are easy to confuse, and they are not interchangeable.
Finding the mapping¶
# Show all lines on the main chip
gpioinfo gpiochip0
# Show all lines on the always-on chip
gpioinfo gpiochip1
# Find a line by SoC pad name, if the line has a name
gpiofind "PH.00"
# Kernel debug view
sudo cat /sys/kernel/debug/gpio
gpioinfo | grep -i "GPIO" is often not very useful on Jetson, because many lines are named like PA.00, PH.00, PQ.05, or have board-specific labels such as Force Recovery, not literal strings like GPIO09.
The four names you will see¶
| Name style | Example | Where you see it | What it means |
|---|---|---|---|
| Board/header label | GPIO09, GPIO15, GPIO22 |
Carrier schematics, 40-pin header tables, Jetson pinout diagrams | A board-facing label for a header pin or connector signal |
| SoC pad / port.pin | PH.00, PQ.05, PZ.01 |
gpioinfo, pinmux docs, low-level bring-up notes |
The Tegra GPIO bank and bit name |
gpiochip line offset |
43, 105, 131 |
gpioinfo, gpioget, gpioset, gpiomon |
The number libgpiod uses inside one chip |
| Legacy sysfs/global GPIO number | 348, 391 |
Older Jetson docs, /sys/class/gpio workflows |
Deprecated numbering model; avoid for new code |
Short mental model¶
At runtime, the one you usually act on is:
Example:
That means:
- controller:
gpiochip0 - line offset on that controller:
43
It does not mean:
- 40-pin header pin 43
- sysfs GPIO 43
- board label
GPIO43
What to trust when numbers disagree¶
For this runtime guide, prefer this order:
gpioinfoandgpiofindfor the current Linux-visible line offsets- 40-pin header tables for physical pin location on the dev kit
- board schematic for signal identity
- old sysfs numbers only when reading legacy documentation
If you need to go deeper into pinmux spreadsheet, carrier routing, or BSP integration, jump to:
3. PWM (Linux)¶
Orin Nano exposes PWM channels via the Linux PWM subsystem.
sysfs interface¶
# Export PWM channel (chip 0, channel 0)
echo 0 > /sys/class/pwm/pwmchip0/export
# Configure: 1 kHz, 50% duty cycle
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period # ns
echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle # ns
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
Common uses on Jetson¶
| Use case | PWM channel | Notes |
|---|---|---|
| Fan control | Dedicated fan PWM | Controlled by jetson_clocks or device tree pwm-fan node |
| LED brightness | GPIO-capable PWM pin | Requires pinmux set to PWM function |
| Servo motor | GPIO-capable PWM pin | 50 Hz, 1–2 ms pulse width |
| Backlight | Display backlight PWM | See Backlight |
4. ADC (Linux)¶
The Jetson Orin Nano SoM has limited ADC capability — no general-purpose ADC pins are exposed on the carrier connector. For analog sensing:
External ADC options¶
| ADC | Interface | Resolution | Channels | Common use |
|---|---|---|---|---|
| ADS1115 | I2C | 16-bit | 4 | Voltage, current, temperature sensors |
| MCP3008 | SPI | 10-bit | 8 | General-purpose analog input |
| INA226 | I2C | 16-bit | 2 (V + I) | Power monitoring |
Reading external ADC via IIO subsystem¶
If your external ADC has a Linux IIO driver:
# List IIO devices
ls /sys/bus/iio/devices/
# Read raw value
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
# Read scale
cat /sys/bus/iio/devices/iio:device0/in_voltage0_scale
Voltage = raw * scale (in millivolts).
5. UART (Linux)¶
Orin Nano exposes multiple UART ports. The debug console uses ttyTCU0 (Tegra Combined UART). Application UARTs appear as /dev/ttyTHS*.
Available UARTs¶
| Device | Use | Baud rate |
|---|---|---|
/dev/ttyTCU0 |
Debug console (bootloader + kernel) | 115200 |
/dev/ttyTHS0 |
Application UART 0 | Configurable |
/dev/ttyTHS1 |
Application UART 1 | Configurable |
Using UART from userspace¶
# Quick test with minicom
minicom -D /dev/ttyTHS0 -b 115200
# Or with stty + cat
stty -F /dev/ttyTHS0 115200 cs8 -cstopb -parenb
echo "hello" > /dev/ttyTHS0
cat /dev/ttyTHS0
Python (pyserial)¶
import serial
ser = serial.Serial('/dev/ttyTHS0', 115200, timeout=1)
ser.write(b'hello\n')
data = ser.readline()
ser.close()
6. SPI (Linux)¶
SPI buses appear as /dev/spidevX.Y (bus X, chip-select Y).
Enable SPI in device tree¶
This module assumes SPI is already enabled.
- On the developer kit, use Jetson-IO
- On a custom carrier, use your pinmux + device tree configuration in the BSP
References:
spidev test¶
Python (spidev)¶
import spidev
spi = spidev.SpiDev()
spi.open(0, 0) # bus 0, CS 0
spi.max_speed_hz = 1000000
response = spi.xfer2([0x01, 0x02, 0x03])
spi.close()
7. I2C (Linux)¶
I2C buses appear as /dev/i2c-N.
Scanning for devices¶
Reading/writing registers¶
# Read register 0x00 from device 0x50 on bus 1
sudo i2cget -y 1 0x50 0x00
# Write 0xFF to register 0x01
sudo i2cset -y 1 0x50 0x01 0xFF
Python (smbus2)¶
from smbus2 import SMBus
with SMBus(1) as bus:
data = bus.read_byte_data(0x50, 0x00)
bus.write_byte_data(0x50, 0x01, 0xFF)
Common I2C devices on Jetson carriers¶
| Device | Address | Purpose |
|---|---|---|
| EEPROM (carrier ID) | 0x50–0x57 | Board identification |
| INA3221 | 0x40–0x43 | Power monitoring (VIN, 3.3V, 5V) |
| IMU (BMI088, ICM-42688) | 0x68–0x69 | Inertial measurement (robotics) |
| RTC (DS3231) | 0x68 | Real-time clock (battery-backed) |
8. CAN (Linux)¶
CAN bus on Jetson requires an external CAN transceiver on the carrier board (the SoC has CAN controllers but no integrated PHY).
Setup¶
# Configure CAN interface (500 kbps)
sudo ip link set can0 type can bitrate 500000
sudo ip link set can0 up
# For CAN-FD
sudo ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on
sudo ip link set can0 up
Send and receive¶
# Send a frame
cansend can0 123#DEADBEEF
# Receive (dump all frames)
candump can0
# Generate test traffic
cangen can0
Python (python-can)¶
import can
bus = can.interface.Bus(channel='can0', interface='socketcan')
# Send
msg = can.Message(arbitration_id=0x123, data=[0xDE, 0xAD, 0xBE, 0xEF])
bus.send(msg)
# Receive
msg = bus.recv(timeout=1.0)
print(f"ID: {msg.arbitration_id:#x}, Data: {msg.data.hex()}")
9. USB host mode (Linux)¶
Jetson Orin Nano supports USB 3.2 Gen 2 (10 Gbps) and USB 2.0 host ports.
Enumeration¶
# List connected USB devices
lsusb
# Show USB tree with speeds
lsusb -t
# Detailed device info
lsusb -v -d <vendor>:<product>
Common USB peripherals¶
| Device type | Driver | Notes |
|---|---|---|
| USB camera | UVC (uvcvideo) |
Works out of the box, see Multimedia |
| USB serial (FTDI, CP210x) | ftdi_sio, cp210x |
Appears as /dev/ttyUSB* |
| USB Ethernet | cdc_ether, r8152 |
Appears as eth* or usb* |
| USB storage | usb-storage |
Appears as /dev/sd* |
| USB audio | snd-usb-audio |
ALSA device |
10. USB device mode (Linux)¶
The Orin Nano dev kit's USB-C port supports device (gadget) mode for flashing and development.
USB gadget framework¶
# Load the USB gadget configfs
modprobe libcomposite
# Common gadgets:
# - g_ether: USB Ethernet gadget (device appears as network adapter on host)
# - g_serial: USB serial gadget (device appears as ttyACM on host)
# - g_mass_storage: USB mass storage gadget
Typical use cases¶
| Gadget | Use case |
|---|---|
| Ethernet (RNDIS/ECM) | SSH into Jetson over USB without network cable |
| Serial (ACM) | Debug console over USB |
| Mass storage | Expose a partition or file as USB drive |
11. NVMe / PCIe storage (Linux)¶
Most Jetson Orin Nano deployments use NVMe SSD for the root filesystem (faster and more reliable than SD card).
NVMe operations¶
# List NVMe devices
nvme list
# Check health
sudo nvme smart-log /dev/nvme0
# Benchmark
sudo fio --name=test --rw=randread --bs=4k --numjobs=4 \
--size=1G --runtime=30 --time_based --filename=/dev/nvme0n1
# Check PCIe link speed
sudo lspci -vv | grep -A 20 "Non-Volatile"
PCIe link verification¶
If the link trains at a lower speed than expected, check trace routing (see Module 2 — Carrier Board).
12. SD/MMC card (Linux)¶
SD card slot (if present on carrier) appears as /dev/mmcblk*.
# Check SD card info
sudo fdisk -l /dev/mmcblk1
# Mount
sudo mount /dev/mmcblk1p1 /mnt
# Check speed class
cat /sys/block/mmcblk1/device/speed_class
Production note: SD cards have limited write endurance. Use NVMe for root filesystem; reserve SD for optional data logging or field updates only.
13. Backlight (Linux)¶
If your carrier has an LCD with PWM-controlled backlight:
# List backlight devices
ls /sys/class/backlight/
# Get current brightness
cat /sys/class/backlight/<device>/brightness
# Get max brightness
cat /sys/class/backlight/<device>/max_brightness
# Set brightness (0 = off, max = full)
echo 128 > /sys/class/backlight/<device>/brightness
Configure the backlight PWM channel in the device tree for your display panel.
For custom panel timing, PWM routing, and BSP integration, see:
14. Projects¶
- Sensor dashboard: Read an I2C temperature sensor (e.g., TMP102) and a SPI ADC (e.g., MCP3008 for analog input), display values on a UART terminal at 1 Hz.
- CAN bus monitor: Build a CAN bus sniffer that logs all frames to a file with timestamps. Add filtering by arbitration ID.
- GPIO interrupt counter: Use
gpiomonor libgpiod event monitoring to count rising edges on an input pin. Measure maximum event rate. - USB gadget network: Configure the Jetson as a USB Ethernet gadget so a host laptop can SSH into it over a single USB-C cable.
15. Resources¶
| Resource | Description |
|---|---|
| Jetson Orin Nano Developer Kit User Guide | Pin header mapping, UART/SPI/I2C bus assignments |
| libgpiod documentation | Character device GPIO API (replaces sysfs) |
| Linux kernel SPI/I2C docs | Documentation/spi/ and Documentation/i2c/ in kernel source |
| SocketCAN documentation | Linux CAN subsystem, ip link, candump, cansend |
| NVMe CLI | nvme-cli package for NVMe management and diagnostics |
| 2. Custom Carrier Board | Connector routing, electrical design, pinmux planning |
| 3. L4T Customization | Device tree, BSP integration, flash and production image workflows |