Onboard Peripheral Usage
This page collects standalone function-level demos for each onboard peripheral of the 1.47'' IPS Display. Each section is self-contained — you can pick the one that matches your use case without reading through the others.
The demo GIFs on this page are sped up to keep them short.
All demos in this page require Seeed nRF52 Boards (1.1.13) as described in Getting Started, plus the Seeed_GFX2 library installed manually as described below.
- Library Manager — go to Sketch > Include Library > Manage Libraries..., search for and install:
| Library | Search Keyword | Author | Required by |
|---|---|---|---|
| Seeed Arduino LSM6DS3 | Seeed Arduino LSM6DS3 | Seeed Studio | IMU demos |
SdFat is bundled with the Seeed nRF52 Boards (1.1.13) board package, so the SD Image Reader and Record to SD demos need no separate SdFat install. Do not install SdFat from the Library Manager, as it may override the bundled version and cause library or API conflicts.
- Seeed_GFX2 (Manual Installation) — this library is not available in Library Manager and must be installed manually:
Step 1. Click the button above to download Seeed_GFX2 v1.0.0 as a ZIP file (pinned to a release tag so the tutorial stays reproducible). Alternatively, clone the repository from Seeed-Studio/Seeed_GFX2.
Step 2. In the Arduino IDE, go to Sketch > Include Library > Add .ZIP Library... and select the downloaded ZIP. The IDE reads library.properties and installs it into the correct Seeed_GFX2 folder automatically — you do not need to rename the extracted folder. (To install manually instead, unzip the archive and rename the extracted folder to Seeed_GFX2 before placing it in Documents/Arduino/libraries/.)
Step 3. Restart the Arduino IDE so the new library is detected.
- Seeed_GFX2 is Seeed Studio's graphics library built on a layered
Board+Panel Configarchitecture. Each demo initializes the display with a singledisplay.begin<Board_..., Config_...>()call — the Board template owns the pin map (CS/DC/SCK/MOSI/RST/BL), and the Panel Config bakes in the 172×320 resolution, color order (BGR), and orientation. Nodriver.hor manual pin setup is needed. - On this board the demos use
Board_XIAO_1inch47_Touch_Display<38, 37>(RST=38, BL=37) withConfig_Seeed_1inch47_Touch_JD9853A(172×320, BGR, no inversion). - The touch controller (AXS5106L) is handled by the
Seeed_GFX2Touch layer (Touch_AXS5106L) — no extra library is needed. The IMU demos use the Seeed Arduino LSM6DS3 library (installed above).
Getting the Demo Code
Every demo on this page lives in the Display-Gadgets repository, under the code_GFX2/Function/ directory. Each demo is a folder containing a single .ino sketch. Always download the complete folder rather than copying the .ino source from the GitHub web view.
Option A — Download the repository as a ZIP (recommended):
- Open github.com/Seeed-Projects/Display-Gadgets and click Code > Download ZIP, then extract the archive anywhere convenient.
- Navigate into
code_GFX2/Function/and open the folder shown in each demo's Code location line. For example, the GraphicTest demo for this board lives incode_GFX2/Function/147_nRF52840/xiao_nrf52840_147_graphictest/. - Double-click the
.inofile to open it in the Arduino IDE.
Option B — git clone:
git clone https://github.com/Seeed-Projects/Display-Gadgets.git
Then open the demo's .ino file from the cloned code_GFX2/Function/... folder.
Screen Display — GraphicTest
This demo runs a full graphics benchmark on the 1.47-inch JD9853A panel, covering color bars, lines, rectangles, circles, triangles, rounded rectangles, text, and a pixel gradient. Use it to verify that the screen is wired correctly and that all draw calls work as expected.
Code location: code_GFX2/Function/147_nRF52840/xiao_nrf52840_147_graphictest/
How It Works
The sketch initializes the JD9853A panel via Seeed_GFX2, then runs through ten graphics primitives in sequence, measuring the execution time of each one via micros() and printing the result to the serial monitor.
The display is initialized with a single template call:
display.begin<Board_XIAO_1inch47_Touch_Display<38, 37>,
Config_Seeed_1inch47_Touch_JD9853A>();
The Board template owns the pin map — CS=D2, DC=D3, SCK=D8, MOSI=D10 — and its <RST, BL> template parameters take bare GPIO numbers, so <38, 37> sets RST=GPIO38 and BL=GPIO37. The Panel Config bakes in the 172×320 resolution, BGR color order, and no inversion — no driver.h or manual MADCTL write is needed.
Running the Demo
Step 1. Open xiao_nrf52840_147_graphictest.ino in Arduino IDE.
Step 2. Select Tools > Board > Seeed nRF52 Boards > Seeed XIAO nRF52840 Plus and the correct Port.
Step 3. Click Upload.
Step 4. Open Tools > Serial Monitor (115200 baud). You should see timing output for each test:
LCD width: 172
LCD height: 320
Color bars: 57.62 ms
Lines: 4859.38 ms
Fast lines: 95.70 ms
Rectangles: 74.22 ms
Filled rectangles: 236.33 ms
Circles: 588.87 ms
Triangles: 413.09 ms
Round rectangles: 125.98 ms
Text: 1961.91 ms
Pixel gradient: 8716.80 ms
Graphic test finished.
On the screen, you will see each test pattern displayed for about one second before the next one starts. When all tests complete, a "Finished" screen appears with a blue rounded-rectangle border.
Expected Result

After the sketch runs through all patterns, the screen shows a "Graphic Test / Finished" message. Reset the board to run the test again.
Touch — Touch Circle
This demo turns the 1.47-inch touch screen into an interactive drawing pad. Tap anywhere on the screen and a white circle appears at your fingertip. Circles stay on screen, building up as you tap. Tap the CLEAR bar at the bottom of the screen to erase all circles and start over.
Code location: code_GFX2/Function/147_nRF52840/xiao_nrf52840_147_touch_circle/
How It Works
The demo uses the AXS5106L capacitive touch controller (I2C address 0x63) connected via I2C on D4/D5. The touch interrupt line on D7 fires on the falling edge whenever a finger touches or releases the screen. Touch is handled by the Seeed_GFX2 Touch layer (Touch_AXS5106L):
Touch_AXS5106L touch(-1, D7, Wire, 172, 320);
display.attachTouch(touch, display.panel().driver().bus());
// ...
display.getTouch(&x, &y);
| Pin | Function |
|---|---|
| D4 (SDA) | I2C data bus — shared with IMU |
| D5 (SCL) | I2C clock bus — shared with IMU |
| D7 | Touch interrupt (active-low, falling edge) |
| RST | Shared with the LCD reset (GPIO38) |
Edge-triggered drawing. The sketch uses an edge-detection approach: it only adds a circle on the falling edge of a touch (finger-down), not while the finger is held. This gives crisp, intentional tap-to-draw behavior rather than continuously painting a trail as you drag.
X-axis mirroring. The touch panel is physically mounted in a different orientation than the LCD, so the raw X coordinate must be mirrored. display.getTouch() already applies this mirroring internally and returns screen coordinates, so no manual screenX = 172 - 1 - rawX transform is needed.
Circle buffer. Up to 120 circles are stored in a circular buffer. When the buffer is full, the oldest circle is removed and the screen is redrawn to keep the display clean.
CLEAR zone. The bottom 36 pixels of the screen are reserved as a CLEAR bar. Tapping this area erases all circles and resets the counter instead of drawing a new circle.
Safe drawing area. A dim gray border outlines the area where circles are fully visible.
Running the Demo
Step 1. Open xiao_nrf52840_147_touch_circle.ino in Arduino IDE.
Step 2. Select the board and port, then click Upload.
Step 3. Open Tools > Serial Monitor (115200 baud). You should see:
LCD: 172x320
Touch: AXS5106L ready
Tap screen to draw white circles.
Tap CLEAR bar at bottom to erase.
Step 4. Tap the screen — each tap prints the mapped screen coordinates:
Touch: screen=(144,124)
Touch: screen=(166,210)
Touch: screen=(121,250)
Touch: screen=(37,231)
Touch: screen=(122,44)
Tap the CLEAR bar at the bottom to erase all circles.
Expected Result

Each tap leaves a white circle at your fingertip. The screen title bar shows the running count. Tap the CLEAR bar and the screen resets to blank with the border and title bar redrawn.
SD Card — Image Reader
This demo reads .bmp image files from a MicroSD card and displays them on the screen. It supports 24-bit uncompressed BMP images and center-crops them to fit the 172×320 display.
Code location: code_GFX2/Function/147_nRF52840/xiao_nrf52840_147_sd_image_reader/
How It Works
The LCD and SD card share the same hardware SPI bus (SCK = D8, MOSI = D10, MISO = D9). To avoid bus contention, the sketch de-asserts the SD card chip-select (D6) before any LCD operation and re-asserts it before SD access. The SD card is driven by SdFat in SHARED_SPI mode on the default SPI instance, while the LCD runs on Seeed_GFX2's SPI host — both share the same physical D8/D9/D10 pins.
The sketch scans the SD card root directory for .bmp files (up to 24), then displays them in a loop with a 2-second interval between images.
Supported BMP formats:
| Format | Bit Depth | Notes |
|---|---|---|
| Uncompressed BMP | 24-bit | BGR888 converted to RGB565 for display |
Images larger than 172×320 are center-cropped.
Running the Demo
Step 1. Format a MicroSD card as FAT32.
Step 2. Copy one or more .bmp images to the root of the SD card.
Step 3. Insert the SD card into the MicroSD slot on the display board.
Step 4. Open xiao_nrf52840_147_sd_image_reader.ino in Arduino IDE, select the board and port, and click Upload.
Step 5. Open Tools > Serial Monitor (115200 baud). You should see:
[IMAGE] /Atest.bmp
[IMAGE] /Another test.bmp
[IMAGE] /test.bmp
[SD] mounted @ 8000000
The filenames listed reflect the .bmp files you placed on the SD card. Your output will vary depending on the files you copy to the card.
The screen displays each image for 2 seconds, then advances to the next one in a continuous loop.
Expected Result

If no BMP files are found, the screen shows "No BMP found". If an image fails to decode, the screen briefly shows the file path with "BMP decode failed" and moves to the next file.
Microphone & Speaker
The 1.47'' IPS Display has an onboard PDM (Pulse Density Modulation) digital microphone for audio input, plus I2S output pads for driving an external speaker/amplifier. This section shows two demos: a real-time Volume Bar visualization of the microphone input (no extra hardware), and a Record to SD demo that records 5 seconds of audio to a MicroSD card and plays it back through an external I2S amplifier.
| Pin | Signal | Function |
|---|---|---|
| D0 | PDM_CLK | PDM clock output to microphone |
| D1 | MIC_DATA | PDM data input from microphone |
Demo 1: Volume Bar
This demo turns the onboard PDM microphone into a large, responsive volume meter. A 10-segment bar fills the center of the screen — green at low levels, yellow at mid-range, red when loud. The percentage is displayed above the bar and changes color to match the level.
Code location: code_GFX2/Function/147_nRF52840/xiao_nrf52840_147_mic_canvas/
How It Works
The onboard PDM (Pulse Density Modulation) digital microphone is connected to the nRF52840's PDM peripheral via D0 (PDM_CLK) and D1 (MIC_DATA) as shown in the pin table above.
The Arduino PDM library handles the low-level PDM-to-PCM conversion in hardware. The sketch configures the PDM peripheral at 16 kHz mono with a gain of 30, then registers an interrupt-driven callback (onPDMdata) that fires whenever a 256-sample buffer is ready.
Signal processing:
- Peak extraction — each callback scans the 256-sample buffer for the largest absolute value (peak amplitude).
- Normalization — the raw peak is mapped from a floor of 40 to a ceiling of 16,000, producing a 0.0–1.0 volume value. Values below the floor are treated as silence.
- Exponential smoothing — the displayed volume is an exponential moving average of the raw peak (α = 0.20) to prevent jitter. When silence is detected, the displayed value decays by ×0.94 per frame.
Bar drawing:
| Segment | Color | Volume Range |
|---|---|---|
| 0–4 (bottom 5) | Green | 0% – 50% |
| 5–8 (middle 4) | Yellow | 50% – 90% |
| 9 (top) | Red | 90% – 100% |
The bar uses differential rendering: only segments whose state changed since the last frame are redrawn. Unchanged segments are left as-is, minimizing SPI traffic and preventing flicker.
Running the Demo
Step 1. Open xiao_nrf52840_147_mic_canvas.ino in Arduino IDE.
Step 2. Select the board and port, then click Upload.
Step 3. Open Tools > Serial Monitor (115200 baud). You should see:
[MIC] ready
Step 4. Speak into the PDM microphone (located near the bottom-left corner of the display board) or blow on it. The bar fills from green to yellow to red, and the percentage updates above it.
Expected Result

The bar responds in real time. In a quiet room the bar stays empty. Speaking at a normal volume from ~20 cm away lights up the green segments. Blowing directly into the mic pushes into the yellow or red range.
Demo 2: Record to SD
This demo records 5 seconds of audio from the onboard PDM microphone into RAM, saves it to a MicroSD card as a WAV file, then plays it back through an external I2S amplifier. Press one button to record, another to play.
Code location: code_GFX2/Function/147_nRF52840/xiao_nrf52840_147_sd_unline_record/
Hardware Setup
Playback requires an external I2S audio amplifier and speaker. The demo is written for a MAX98357A breakout connected to the board's I2S output pads:
| I2S Pad | XIAO Pin | MAX98357A |
|---|---|---|
| 3V3 | 3V3 | VIN |
| GND | GND | GND |
| I2S_SD | D11 | DIN |
| I2S_SCK | D12 | BCLK |
| I2S_WS | D13 | LRC |
The I2S pads (3V3, GND, D11, D12, D13) are exposed on the bottom expansion pad group of the display board.
How It Works
Recording. The onboard PDM microphone is captured at 16 kHz mono, 16-bit through the nRF52840's PDM peripheral, using the same D0 (PDM_CLK) / D1 (MIC_DATA) pins as Demo 1. When you press USR1, the sketch samples 5 seconds of audio directly into a static RAM buffer, then writes it to the SD card as a WAV file (/REC_001_RAW.WAV) using the SdFat library bundled with Seeed nRF52 Boards 1.1.13.
The recording is buffered in RAM because the nRF52840 has only 256 KB of RAM. At 16 kHz × 16-bit mono, 5 seconds needs 160,000 bytes — which fits. 10 seconds would need 320,000 bytes and would not fit, so the demo is fixed at 5 seconds.
Playback. Pressing USR2 reads the WAV back from the SD card (skipping the 44-byte WAV header) and streams it out through the nRF52840's I2S peripheral in Philips stereo mode on D11/D12/D13. The mono samples are duplicated to both channels with a 0.75× gain applied to avoid clipping. The amplifier drives a small speaker so you can hear the recording.
State machine. The recorder runs through a deterministic sequence of states, printing each transition to the serial monitor:
IDLE → PREPARE_SYSTEM → QUIET_RADIO → PREPARE_PERIPHERALS → START_HFCLK → START_PDM
→ DISCARD_WARMUP → CAPTURE_RAM → STOP_PDM → SAVE_RAW → DONE
- QUIET_RADIO disables the RADIO peripheral (this sketch never initializes BLE) to keep the timing-sensitive capture section stable.
- START_HFCLK switches the high-frequency clock to the external 32 MHz crystal, which the PDM peripheral needs for accurate sampling.
- DISCARD_WARMUP drops the first 300 ms of PDM output while the microphone settles.
- CAPTURE_RAM fills the buffer until 80,000 samples (5 s) are collected, drawing a live progress bar on screen.
On-screen states:
| State | Description |
|---|---|
| Ready | "RAM Recorder" title with "USR1: record" and "USR2: play last" |
| Recording | "Recording" label, an elapsed timer ("2.3s / 5s"), and a red progress bar |
| Done | "Done" title with the saved filename and "Saved raw WAV", plus "USR1: record" / "USR2: play raw" |
| Playback | "Playback" title showing "Loading RAW audio..." then "Playing RAW audio", ending on "Finished" |
Running the Demo
Step 1. Format a MicroSD card as FAT32 and insert it into the MicroSD slot on the display board.
Step 2. Connect a MAX98357A amplifier and speaker to the I2S pads as described above.
Step 3. Open xiao_nrf52840_147_sd_unline_record.ino in Arduino IDE, select the board and port, and click Upload.
Step 4. Open Tools > Serial Monitor (115200 baud). On boot you should see:
=== XIAO nRF52840 Plus RAM PDM recorder ===
[RAM] record buffer bytes=160000
[RADIO] BLE is not initialized by this sketch
[PDM] library uses EasyDMA double buffering
[STATE] IDLE
Step 5. Press USR1 (D19) to record 5 seconds of audio from the onboard microphone. The progress bar fills as it records, and the state machine prints each transition:
[STATE] PREPARE_SYSTEM
[STATE] QUIET_RADIO
[STATE] PREPARE_PERIPHERALS
[STATE] START_HFCLK
[STATE] START_PDM
[STATE] DISCARD_WARMUP
[STATE] CAPTURE_RAM
[STATE] STOP_PDM
[STATE] SAVE_RAW
[STATE] DONE
[SAVE] /REC_001_RAW.WAV
Step 6. Press USR2 (D15) to play the recording back through the speaker:
[PLAY] latest RAW audio
[PLAY] finished
Each new recording is saved as a numbered WAV file (REC_001_RAW.WAV, REC_002_RAW.WAV, …), so previous recordings are kept. The "Done" screen shows the filename of the most recent recording.
Expected Result

Press USR1 and the screen shows a recording progress bar. After 5 seconds it confirms the WAV was saved to the SD card. Press USR2 and the audio plays through the connected speaker while the screen shows the playback status.
IMU
Demo 1: Electronic Quicksand
This demo turns the screen into an interactive fluid simulation — golden sand particles that flow and settle according to gravity, as measured by the onboard LSM6DS3 6-axis IMU. Tilt the board and the sand shifts direction in real time.
Code location: code_GFX2/Function/147_nRF52840/xiao_nrf52840_147_electronic_quicksand/
How It Works
The simulation uses a 24×45 occupancy grid overlaid on the 172×320 screen, where each cell is 7×7 pixels. Around 180 particles are placed in the grid, each with a position, velocity, and a golden color gradient.
The LSM6DS3 accelerometer is read via I2C (D4/D5) every 8 ms. The raw acceleration values are low-pass filtered and used to derive a gravity vector. When you tilt the board:
- Gravity vector updates — accelerometer data is smoothed with an exponential moving average to avoid jitter.
- Particle velocity — each particle accelerates in the direction of the gravity vector, with damping and a per-particle mobility factor based on its depth in the flow.
- Cell occupancy — particles deeper in the flow (closer to the "bottom" relative to gravity) have reduced mobility, creating a realistic packing effect.
- Differential rendering — only cells where particles moved into or out of are redrawn, minimizing SPI traffic and keeping the animation smooth.
Particles near the surface flow freely (higher mobility); particles buried deeper pack tightly (lower mobility) — mimicking how real sand behaves.
Running the Demo
Step 1. Open xiao_nrf52840_147_electronic_quicksand.ino in Arduino IDE.
Step 2. Select the board and port, then click Upload.
Step 3. Once uploaded, the screen fills with golden particles at the bottom. Tilt the board in different directions — the sand flows as if pulled by gravity.
Step 4. Open Tools > Serial Monitor (115200 baud) to confirm initialization:
=== Electronic Quicksand ===
imu.begin=0
Expected Result

The golden sand particles flow smoothly as you tilt the board. When held flat, the sand settles at the bottom of the screen. Rotate the board 90 degrees and the sand flows to the new "bottom" within a second.
Demo 2: Raise to Wake
This demo implements a screen sleep/wake system driven by the LSM6DS3 IMU's built-in wake-up interrupt on D14. The screen automatically turns off (backlight off + CPU enters System ON sleep) after 8 seconds of inactivity, and wakes instantly when you pick up or move the device.
Code location: code_GFX2/Function/147_nRF52840/xiao_nrf52840_147_wakeup/
How It Works
The demo uses the LSM6DS3's embedded wake-up event detector — a hardware feature that monitors accelerometer data internally and asserts the INT1 pin (routed to D14 on this board) when motion exceeds a configurable threshold. This means the MCU does not need to poll the accelerometer continuously.
IMU configuration:
| Register | Value | Purpose |
|---|---|---|
CTRL1_XL | 0x40 | Accelerometer @ 104 Hz, ±2g |
CTRL3_C | 0x44 | Block data update + auto-increment |
TAP_CFG | 0x80 | Enable embedded interrupts |
WAKE_UP_THS | 0x05 | Wake-up threshold (medium-low sensitivity) |
WAKE_UP_DUR | 0x00 | No duration filter (responsive wake) |
MD1_CFG | 0x20 | Route wake-up to INT1 |
Sleep/wake flow:
- Active state — screen is on, backlight at PWM 120. IMU data and battery status refresh every 250 ms / 1000 ms respectively. A countdown timer shows seconds remaining until auto-sleep.
- Auto-sleep — after 8 seconds of no activity, the sketch turns off the backlight, draws a "Sleeping... Pick up device to wake" message, and enters nRF52840 System ON sleep via WFE (Wait For Event).
- Wake-up — when the user picks up the board, the LSM6DS3 detects motion and asserts D14 HIGH. The GPIO interrupt fires, the CPU wakes from WFE, the backlight turns on, and the UI is fully redrawn.
In System ON sleep, all RAM and peripheral states are preserved — wake-up is nearly instant (under 1 ms from interrupt to backlight on).
Manual test buttons:
| Button | Pin | Action |
|---|---|---|
| USR1 | D19 | Force sleep |
| USR2 | D15 | Force wake |
Running the Demo
Step 1. Open xiao_nrf52840_147_wakeup.ino in Arduino IDE, select the board and port, and click Upload.
Step 2. The screen shows a dashboard with power state, motion data, and a countdown timer. Let the board sit still for 8 seconds — it will automatically sleep.
Step 3. Pick up the board or shake it gently — the screen wakes immediately.
Step 4. Open Tools > Serial Monitor (115200 baud) to observe the sleep/wake transitions:
[SLEEP] screen backlight off, waiting for IMU D14 wake
[SYS_ON_SLEEP] waiting, sleepLoops=26625 D14=0 awake=N
[SYS_ON_SLEEP] waiting, sleepLoops=27649 D14=0 awake=N
[SYS_ON_SLEEP] waiting, sleepLoops=28673 D14=0 awake=N
[SYS_ON_SLEEP] waiting, sleepLoops=29697 D14=0 awake=N
[WAKE] reason=IMU_D14 wakeCount=1 sleptMs=58776 sleepLoops=29705
[WAKE] reason=IMU_D14 wakeCount=2 sleptMs=60110 sleepLoops=29705
Expected Result

The screen displays real-time accelerometer and gyroscope data while awake. After 8 seconds of stillness, the screen goes dark and the nRF52840 enters low-power sleep. Pick up the device and the screen restores within a fraction of a second, with the wake counter incremented.
User Button
The 1.47'' IPS Display has two physical push buttons connected to the XIAO nRF52840 Plus:
| Button | Pin | Logic | Silkscreen Label |
|---|---|---|---|
| BTN_A | D19 | Active-low (pressed = LOW) | USR1 |
| BTN_B | D15 | Active-low (pressed = LOW) | USR2 |
Reading a Button
Both buttons use the XIAO's internal pull-up resistors. A simple non-blocking read looks like this:
const int BTN_A = D19;
const int BTN_B = D15;
void setup() {
pinMode(BTN_A, INPUT_PULLUP);
pinMode(BTN_B, INPUT_PULLUP);
Serial.begin(115200);
}
void loop() {
if (digitalRead(BTN_A) == LOW) {
Serial.println("BTN_A pressed");
delay(200); // simple debounce
}
if (digitalRead(BTN_B) == LOW) {
Serial.println("BTN_B pressed");
delay(200);
}
}
Debounce with Interrupts
For responsive, debounced button handling without blocking the main loop, you can use pin-change interrupts:
volatile bool btnAFlag = false;
volatile bool btnBFlag = false;
void btnAIsr() { btnAFlag = true; }
void btnBIsr() { btnBFlag = true; }
void setup() {
pinMode(D19, INPUT_PULLUP);
pinMode(D15, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(D19), btnAIsr, FALLING);
attachInterrupt(digitalPinToInterrupt(D15), btnBIsr, FALLING);
}
void loop() {
if (btnAFlag) {
btnAFlag = false;
delay(30); // debounce settling time
if (digitalRead(D19) == LOW) {
// handle BTN_A press
}
}
if (btnBFlag) {
btnBFlag = false;
delay(30);
if (digitalRead(D15) == LOW) {
// handle BTN_B press
}
}
}
Default Behavior in the Factory Dashboard
In the preloaded factory firmware, the buttons are mapped as follows (you can override these in your own code):
| Button | Action |
|---|---|
| BTN_A (D19) | Short press: cycle screen brightness 100% → 75% → 50% → 25% → 0% → 100% |
| BTN_B (D15) | Short press: toggle screen off / restore to last brightness |
The button breakout pads (labeled U1 and U2 on the board) mirror D19 and D15 respectively, allowing you to connect external buttons if desired.
Battery Status
This demo shows the battery status — a battery icon with charge level and charging state — on the 1.47'' IPS Display. It detects whether a LiPo battery is physically connected and shows one of three states: USB PWR (no battery), percentage (battery only), or charging (USB + battery).
The 1.47'' IPS Display includes an onboard battery voltage measurement circuit. The nRF52840 Plus reads the LiPo battery voltage through a voltage divider and can display the remaining capacity as a percentage.
Code location: code_GFX2/Function/147_nRF52840/xiao_nrf52840_147_battery_status/
How It Works
Display:
The screen is driven by Seeed_GFX2 with Board_XIAO_1inch47_Touch_Display<38, 37> and Config_Seeed_1inch47_Touch_JD9853A (172×320, BGR, rotation 2) over 10 MHz hardware SPI.
Battery circuit:
The nRF52840 Plus uses three GPIO pins to form a complete battery monitoring system:
| Signal | nRF52840 Pin | Function |
|---|---|---|
READ_BAT | P0.14 | Battery voltage divider enable. Active-low — set LOW to enable the divider, then release to HIGH (high-impedance) to save power. |
VBAT_ADC | PIN_VBAT (AIN7 / P0.31) | Analog input reading the divided battery voltage. |
CHG | P0.17 | Charging status indicator. Active-low — reads LOW when a charger is connected and the battery is charging. |
Detection:
Under USB-C, a static VBAT voltage cannot tell whether a battery is present — the charger's BAT node can look like a real Li-ion cell even with no battery attached. So the demo first learns a USB-only baseline, then confirms battery insertion only after a sustained downward VBAT shift, and confirms removal after a noisy/jumped reading combined with ~CHG going HIGH. This mirrors the factory Dashboard's detection logic.
Icon states:
- No battery — grey outline battery with a red cross, labelled USB PWR.
- Battery present — white outline battery with a coloured fill (green / yellow / red by percentage), labelled with the percentage and voltage.
- Charging — cyan fill with a lightning-bolt icon, labelled with the percentage and voltage.
The ~CHG pin is read through the nRF52840's raw GPIO registers (nrf_gpio_cfg_input() and NRF_P0->IN) instead of digitalRead(). In the Arduino API, pin numbers follow the board package's mapping, where digitalRead(17) actually reads P0.07 (the 6D IMU's I2C data line) rather than P0.17. The constants 14 and 17 here are raw Nordic P0.x pin numbers (P0.14 and P0.17), which is exactly what the register calls expect.
The demo uses the factory-calibrated 499 kΩ low-side resistor (divider ratio ≈ 3.004), not the 510 kΩ nominal value. The divider is built into the XIAO nRF52840 Plus module itself, not the display board. The P0.14 enable pin is active-low: drive it LOW to enable the divider, then release it to high-impedance (INPUT) to minimize quiescent current drain when the battery is not being measured.
Running the Demo
Step 1. Open xiao_nrf52840_147_battery_status.ino in Arduino IDE.
Step 2. Select Tools > Board > Seeed nRF52 Boards > Seeed XIAO nRF52840 Plus and the correct Port.
Step 3. Click Upload.
Step 4. Observe the screen — it shows the battery icon with the current state. Plug or unplug a LiPo battery (or the USB-C cable) to watch the icon switch between the three states.
Expected Result
![]() USB PWR (no battery) | ![]() Percentage (battery only) |
![]() Charging (USB + battery) | ![]() Battery connector (back) |
Without a battery, the screen shows a grey battery with a red cross and the label USB PWR. Insert a LiPo battery and the icon switches to a coloured fill with the percentage and voltage. Plug in USB-C while a battery is present and the fill turns cyan with a lightning bolt, indicating charging.
The demo also prints a diagnostic line to the Serial Monitor every 500 ms, for example:
VBAT 3.87V charging 85 spread=5 usb=ON base=4.140 baseValid=Y state=PRESENT filter=STABLE removeCount=0
Resources
- 🗃️[PCB Design Files] XIAO 1.47'' IPS Display (nRF52840) KiCad Project
- 📄[Schematic] XIAO 1.47'' IPS Display (nRF52840) Schematic
- 📦[3D Model] XIAO 1.47'' IPS Display (STEP)
- 📄[Datasheet] 1.47 Inch Display Datasheet
- 💾[Factory Firmware] XIAO 1.47'' IPS Display (nRF52840) Factory Firmware
- [Demo] XIAO Display Board Demo Code — all Function demos are in the
code_GFX2/Function/147_nRF52840/directory
Tech Support & Product Discussion
Thank you for choosing our products! We are here to provide you with different support to ensure that your experience with our products is as smooth as possible. We offer several communication channels to cater to different preferences and needs.



