Project 1: Hello Display - Raw SPI Communication
Bring the LCD to life from scratch: configure SPI and GPIO, send the ST7789 init sequence, and light pixels with a deterministic test pattern.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 1: Beginner |
| Time Estimate | 4-8 hours |
| Main Programming Language | C (Alternatives: Rust, MicroPython) |
| Alternative Programming Languages | Rust (pico-sdk), MicroPython |
| Coolness Level | Level 2: Fun |
| Business Potential | 2. The “Prototype” Level |
| Prerequisites | Basic C, GPIO basics, SPI framing, ability to build Pico SDK examples |
| Key Topics | SPI framing, ST7789 command flow, GPIO pad control, RGB565 pixels |
1. Learning Objectives
By completing this project, you will:
- Configure RP2350 GPIO pads and SPI peripheral to match an ST7789 LCD.
- Implement a command/data abstraction that sends valid ST7789 sequences.
- Draw deterministic pixel patterns (solid color, gradient, test grid) using RGB565.
- Diagnose display bring-up failures with a logic analyzer and timing checks.
- Document a repeatable boot and reset sequence for display initialization.
2. All Theory Needed (Per-Concept Breakdown)
2.1 SPI Framing and LCD Command/Data Separation
Fundamentals
SPI is a synchronous serial bus where the master provides clock edges and places bits on MOSI. Unlike UART, there is no framing byte; framing happens with chip-select (CS) boundaries. For LCDs, SPI transfers are not just a stream of bytes: the display interprets the first byte after CS (and the state of a separate DC pin) as either a command or data. That means you must understand that the bus alone does not encode meaning–your firmware and GPIO pins define it. CPOL/CPHA determine when data is sampled and can cause intermittent corruption if mismatched. Practical bring-up requires matching the LCD’s required SPI mode, respecting CS setup/hold, and keeping DC stable during a byte transfer. The display’s internal state machine expects a precise order: command byte, then argument bytes, then an optional pixel stream. A simple and deterministic abstraction (“write_command”, “write_data”, “write_pixels”) eliminates ambiguity.
Deep Dive into the concept
SPI framing on LCDs is often misunderstood because the “protocol” spans both SPI signals and auxiliary GPIO lines. The ST7789 treats the DC pin as a semantic marker: DC=0 indicates a command byte, DC=1 indicates data. The microcontroller must hold DC stable for the full byte transfer, and CS must remain low for the entire command plus its arguments. If CS toggles between bytes, the LCD may reset its internal parser or treat argument bytes as new commands. SPI mode also matters: the ST7789 expects a specific relationship between clock edges and data sampling. If CPOL/CPHA are wrong, you can see “almost working” behavior, such as colors that look valid but are shifted or commands that intermittently fail. Another nuance is bus speed: SPI clocks that are too fast relative to the LCD or board trace quality will produce setup/hold violations, especially on the DC line. These show up as random line noise or occasional wrong colors. On a microcontroller, you typically configure SPI with a divider; you should start at a conservative frequency, then increase once you have stable bring-up.
The command/data split also changes how you design your driver. You want a small layer that toggles DC and issues bytes, and a higher layer that enforces “command then data” semantics. This helps avoid subtle bugs like sending pixel data with DC still low, which the LCD will interpret as a stream of commands (garbage). Another common issue is mixing endianness: SPI sends bytes in order; RGB565 pixels are two bytes, so your driver must choose high-byte-first or low-byte-first to match the LCD’s expectation. ST7789 generally expects big-endian pixel data (MSB first). If you get this wrong, colors will look swapped or bands will appear. Testing with a deterministic pattern (red/green/blue bars, gradient) is the fastest way to verify correctness.
How this fits on projects
You will use this SPI framing model in Section 3.2 Functional Requirements and Section 5.4 Concepts You Must Understand First. It also reappears in Project 3 (DMA display streaming) and Project 8 (TF card image streaming) where continuous pixel pushes require flawless framing. Also used in: Project 3, Project 8.
Definitions & key terms
- CPOL/CPHA -> Clock polarity and phase; define when data is sampled and shifted.
- CS (Chip Select) -> Frame boundary for SPI transactions.
- DC (Data/Command) -> LCD-specific pin; 0=command, 1=data.
- SPI mode -> One of four CPOL/CPHA combinations.
- RGB565 -> 16-bit color format: 5 bits red, 6 bits green, 5 bits blue.
Mental model diagram (ASCII)
CS ____------------------------------____
DC ----0------------------------------1---
SCK _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
MOSI C 0x2A ARG1 ARG2 ... DATA DATA
(CASET) (pixel stream)
How it works (step-by-step, with invariants and failure modes)
- Assert CS low.
- Set DC=0 and send a command byte.
- Set DC=1 and send N data bytes for that command.
- Keep CS low until the full command+data sequence completes.
- Deassert CS to end the frame.
Invariants:
- DC must not change mid-byte.
- CS must remain low for a single command’s arguments.
- SPI mode must match LCD expectations.
Failure modes:
- Wrong CPOL/CPHA -> random pixels or no response.
- CS toggled too early -> LCD ignores arguments.
- DC glitch -> command stream corruption.
Minimal concrete example
static inline void lcd_write_cmd(uint8_t cmd) {
gpio_put(PIN_DC, 0);
spi_write_blocking(spi0, &cmd, 1);
}
static inline void lcd_write_data(const uint8_t *buf, size_t len) {
gpio_put(PIN_DC, 1);
spi_write_blocking(spi0, buf, len);
}
void lcd_set_column(uint16_t x0, uint16_t x1) {
uint8_t data[4] = {x0 >> 8, x0 & 0xFF, x1 >> 8, x1 & 0xFF};
lcd_write_cmd(0x2A); // CASET
lcd_write_data(data, 4);
}
Common misconceptions
- “SPI is just bytes, so CS can toggle any time.” -> CS defines the frame; toggling can reset the LCD parser.
- “DC is optional.” -> On SPI LCDs, DC is required to distinguish commands from data.
- “Any SPI mode works if it looks right.” -> Some modes appear to work but fail at higher speeds.
Check-your-understanding questions
- Why must CS stay low across command + argument bytes?
- What happens if DC changes in the middle of a byte?
- Which byte order does RGB565 use on the wire?
- Why test with primary colors first?
Check-your-understanding answers
- The LCD parses arguments only while the command frame is active.
- The LCD may misinterpret the byte, corrupting its state machine.
- Most ST7789 setups expect MSB first for each pixel.
- Primary colors reveal byte order and channel swap errors quickly.
Real-world applications
- LCD bring-up on consumer devices
- SPI sensor drivers with framing requirements
- Display drivers in wearables and instrumentation
Where you’ll apply it
References
- ST7789 datasheet (command set, timing)
- RP2350 datasheet (SPI controller)
- “Making Embedded Systems” Ch. 6
Key insights
SPI LCDs are “protocol + GPIO,” not just bytes on a bus.
Summary
Correct SPI framing is the difference between a blank screen and a working display. A tiny abstraction layer prevents most bring-up bugs.
Homework/Exercises to practice the concept
- Draw CS/DC timing for a CASET command with 4 data bytes.
- Modify a driver to switch between SPI mode 0 and 3 and note behavior.
- Construct RGB565 values for red, green, blue, white, and black.
Solutions to the homework/exercises
- CS stays low; DC=0 for command byte, then DC=1 for 4 data bytes.
- Mode mismatch causes corrupted commands or flickering pixels.
- Red=0xF800, Green=0x07E0, Blue=0x001F, White=0xFFFF, Black=0x0000.
2.2 ST7789 Initialization and Address Windowing
Fundamentals
The ST7789 is a display controller that expects a specific initialization sequence: software reset, sleep out, pixel format, memory access control, and display on. These commands configure the internal GRAM (graphics RAM) mapping to your panel’s resolution. If you skip steps or use wrong delays, the screen stays blank or shows corrupt data. Address windowing (CASET/RASET) defines a rectangular region for subsequent RAMWR pixel writes; without it, you might draw only to a default region or to the wrong offsets. The 172x320 panel uses non-zero offsets relative to the controller’s internal coordinate system. Understanding the controller’s memory map and offsets is crucial to drawing a pixel at (0,0) that actually appears at the top-left of your physical display.
Deep Dive into the concept
The ST7789 command set is stateful: commands often rely on previous configuration state. The canonical init sequence is a carefully ordered list derived from the datasheet and panel vendor examples. Key commands include SWRESET (0x01), SLPOUT (0x11), COLMOD (0x3A) to set RGB565 or RGB666, MADCTL (0x36) to configure rotation and BGR order, CASET (0x2A) and RASET (0x2B) to define the address window, and RAMWR (0x2C) to start pixel transfer. The delays after SWRESET and SLPOUT are not optional–internal power circuits and timing generators need time to stabilize. Skipping these delays leads to intermittent boot failures that are notoriously hard to debug.
Address windows are equally important. The LCD controller’s internal coordinate system is often larger than the panel or includes “hidden” pixels. Your specific 172x320 panel likely has an offset (e.g., X offset 0, Y offset 34 or similar), meaning physical pixel (0,0) maps to internal GRAM (0,offset). If you do not apply the offset, your graphics will appear shifted or clipped. For sanity, you should write a border rectangle and confirm that the full panel updates. A deterministic test (left edge red, right edge blue, top green, bottom yellow) will show offset errors immediately.
How this fits on projects
This is the heart of the bring-up path in Section 3.1 and Section 3.7. It is foundational for Project 2 (primitives) and Project 3 (DMA), because every rendering pipeline ultimately writes via CASET/RASET + RAMWR. Also used in: Project 2, Project 3.
Definitions & key terms
- GRAM -> Graphics RAM inside the LCD controller.
- CASET/RASET -> Column/row address set commands.
- RAMWR -> Begin pixel data write into GRAM.
- COLMOD -> Set pixel format (RGB565/RGB666).
- MADCTL -> Memory access control (rotation, mirror, BGR).
Mental model diagram (ASCII)
Panel (172x320) mapped into ST7789 GRAM
GRAM (240x320)
+----------------------------------+
| offset area (unused) |
| +----------------------------+ |
| | visible 172x320 panel | |
| | (x0,y0) = (0,offset) | |
| +----------------------------+ |
+----------------------------------+
How it works (step-by-step)
- Reset the LCD (hardware or SWRESET).
- Exit sleep (SLPOUT), wait 120ms.
- Configure pixel format (COLMOD = 0x55 for RGB565).
- Configure orientation and color order (MADCTL).
- Set CASET/RASET to include offsets and full panel.
- Issue RAMWR and stream pixel data.
Failure modes:
- Skipped delay -> random boot failures.
- Wrong offsets -> clipped or shifted image.
- Wrong COLMOD -> incorrect colors or bus width mismatch.
Minimal concrete example
void lcd_init(void) {
lcd_write_cmd(0x01); // SWRESET
sleep_ms(150);
lcd_write_cmd(0x11); // SLPOUT
sleep_ms(120);
uint8_t colmod = 0x55; // RGB565
lcd_write_cmd(0x3A);
lcd_write_data(&colmod, 1);
uint8_t madctl = 0x00; // RGB order, default rotation
lcd_write_cmd(0x36);
lcd_write_data(&madctl, 1);
}
Common misconceptions
- “Init sequences are identical across panels.” -> Offsets and timing differ.
- “If it boots once, delays aren’t needed.” -> Temperature and voltage changes break it.
- “MADCTL only rotates.” -> It also controls color order and mirroring.
Check-your-understanding questions
- Why must SLPOUT be followed by a long delay?
- What is the role of CASET and RASET?
- How can you detect an incorrect X/Y offset?
Check-your-understanding answers
- Internal circuits and timing generators need stabilization time.
- They define the rectangular region that RAMWR will fill.
- A full-screen border or test grid will appear shifted or clipped.
Real-world applications
- Factory display calibration and panel bring-up
- Firmware for wearables and handheld devices
- Boot logos and diagnostics on embedded products
Where you’ll apply it
- This project: Section 3.1, Section 3.7, Section 5.10 Phase 1
- Also used in: Project 2, Project 10
References
- ST7789V datasheet (init sequence and MADCTL)
- Waveshare RP2350-LCD-1.47-A notes
Key insights
Display bring-up is 90% sequencing and offsets–get those right and everything works.
Summary
A precise init sequence plus correct address offsets is required for any visible pixel output.
Homework/Exercises to practice the concept
- Write a minimal init list and annotate delays.
- Modify MADCTL to rotate the display 90 deg and observe offsets.
- Build a border test that proves full-screen coverage.
Solutions to the homework/exercises
- SWRESET -> delay -> SLPOUT -> delay -> COLMOD -> MADCTL -> CASET/RASET -> RAMWR.
- Rotation changes axis mapping; offsets must be recalculated.
- Draw top/bottom/left/right lines and verify alignment with physical edges.
2.3 RGB565 Pixel Encoding
Fundamentals
RGB565 stores color in 16 bits: 5 bits red, 6 bits green, 5 bits blue. This is a compromise between color fidelity and bandwidth, common in embedded displays. Each pixel is two bytes, usually transmitted MSB first. Understanding bit packing lets you create colors deterministically, blend images, and debug wrong color order. You must also understand that RGB565 values are not linear in brightness; the green channel has one extra bit, giving it more precision. For display bring-up, you can test with pure color values (red/green/blue/white/black) to verify channel order and endianness.
Deep Dive into the concept
RGB565 is often the simplest format that still looks good. It is built from component fields: R[15:11], G[10:5], B[4:0]. Converting from 8-bit-per-channel RGB requires scaling: red = (r * 31) / 255, green = (g * 63) / 255, blue = (b * 31) / 255. If you shift without scaling, colors will look dark or washed out. When streaming pixels, you must pick a byte order that matches the LCD. Some controllers can be configured for BGR order; if your red appears blue, you can flip the MADCTL bit or swap channels in software. For gradients, choose a deterministic pattern like incrementing the red channel across x and green across y. This makes it easy to spot if axes are swapped or if stride is wrong. RGB565 also affects memory layout and bandwidth: at 172x320, a full frame is ~110 KB. This matters for DMA and double buffering later.
How this fits on projects
You will use RGB565 encoding directly in Section 3.4 Example Output and Section 3.7 Real World Outcome. It becomes a core performance topic in Project 3 and Project 5, where frame bandwidth and buffer sizes matter. Also used in: Project 3, Project 5.
Definitions & key terms
- RGB565 -> 16-bit packed color format (5-6-5).
- Endianness -> Order of bytes for multi-byte values.
- Color order -> RGB vs BGR channel order.
- Stride -> Bytes per row in a framebuffer.
Mental model diagram (ASCII)
16-bit pixel: [R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0]
15 8 7 0
How it works (step-by-step)
- Convert 8-bit RGB to 5/6/5 bits.
- Pack into a 16-bit value.
- Send MSB then LSB over SPI.
- LCD unpacks into internal color channels.
Failure modes:
- Wrong byte order -> swapped colors.
- Wrong channel order -> red/blue inverted.
- Missing scaling -> low-contrast images.
Minimal concrete example
static inline uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b) {
uint16_t r5 = (r * 31) / 255;
uint16_t g6 = (g * 63) / 255;
uint16_t b5 = (b * 31) / 255;
return (r5 << 11) | (g6 << 5) | (b5);
}
Common misconceptions
- “Shifting 8-bit values is enough.” -> You need scaling, not just shifting.
- “Endian doesn’t matter for SPI.” -> It matters for multi-byte pixels.
- “RGB565 is low quality.” -> It is adequate for small LCDs.
Check-your-understanding questions
- Why does green have 6 bits in RGB565?
- How do you detect endian mistakes on the display?
- What is the framebuffer size for 172x320 in RGB565?
Check-your-understanding answers
- Human vision is more sensitive to green, so it gets more precision.
- Use primary color bars; red and blue will appear swapped.
- 172 * 320 * 2 ~= 110,080 bytes.
Real-world applications
- UI graphics on wearables and embedded panels
- Low-bandwidth displays in IoT devices
- Video streaming to small LCDs
Where you’ll apply it
References
- ST7789 datasheet (pixel formats)
- Embedded graphics references for RGB565 conversion
Key insights
RGB565 is simple but unforgiving: one wrong bit makes colors lie.
Summary
Know how to pack RGB565 and verify it with deterministic color tests.
Homework/Exercises to practice the concept
- Compute RGB565 for (255, 128, 0) and explain the steps.
- Create a 16-pixel gradient from black to white and predict values.
- Identify the byte order used by your LCD from its datasheet.
Solutions to the homework/exercises
- r5=31, g6=31, b5=0 -> 0xFBE0.
- Increment channels evenly; values increase nonlinearly due to scaling.
- Datasheet indicates MSB-first for 16-bit pixel data.
3. Project Specification
3.1 What You Will Build
A minimal firmware that initializes SPI and the ST7789 LCD, then draws a deterministic boot logo and gradient test pattern on the 1.47” display. The firmware provides a clean separation between command and data writes and includes a repeatable reset and init sequence.
Included:
- SPI + GPIO init for LCD pins
- LCD reset, sleep-out, and pixel format configuration
- Address window setup for full panel
- Deterministic test pattern (color bars + gradient + text)
Excluded:
- Full graphics library (see Project 2)
- DMA acceleration (see Project 3)
- Multi-core rendering (see Project 5)
3.2 Functional Requirements
- SPI driver bring-up: Configure SPI mode, frequency, and pins to match the LCD.
- Command/data abstraction: Implement
lcd_write_cmd()andlcd_write_data()and use them consistently. - Reset and init sequence: Implement delays and initialization in the correct order.
- Address window: Set CASET/RASET to match 172x320 with offsets.
- Test pattern: Draw a deterministic gradient + color bars + a text label.
3.3 Non-Functional Requirements
- Performance: Full-screen fill completes in under 250 ms at 10 MHz SPI.
- Reliability: Display init works on 10 consecutive power cycles.
- Usability: Clear comments for pin assignments and offsets.
3.4 Example Usage / Output
LCD BOOT: RP2350 LCD HELLO
Pattern: Left->Right Red gradient, Top bar RGB, Bottom bar grayscale
Status: SPI OK | LCD OK
3.5 Data Formats / Schemas / Protocols
- Command stream: [CMD][DATA…], with DC=0 for CMD, DC=1 for DATA
- Pixel stream: RGB565 MSB-first
- Address window: CASET(x0,x1), RASET(y0,y1), RAMWR
3.6 Edge Cases
- SPI mode mismatch (colors incorrect or no display)
- Incorrect offsets (image shifted or clipped)
- Reset pin not asserted (blank screen)
- SPI too fast for wiring (noise, banding)
3.7 Real World Outcome
You can plug the board in, flash firmware, and see a stable boot screen with a gradient and color bars. The screen must look identical on each reset.
3.7.1 How to Run (Copy/Paste)
cd LEARN_RP2350_LCD_DEEP_DIVE/hello_display
mkdir -p build
cd build
cmake ..
make -j4
cp hello_display.uf2 /Volumes/RP2350
3.7.2 Golden Path Demo (Deterministic)
- Reset board.
- LCD shows a top RGB bar, a left-to-right red gradient, and a bottom grayscale bar.
- Text reads exactly: “RP2350 LCD HELLO”.
3.7.3 Failure Demo (Deterministic)
- Set SPI mode to CPOL=1, CPHA=1.
- Flash firmware.
- Expected result: screen stays white or shows corrupted colors.
- Fix: restore SPI mode to CPOL=0, CPHA=0.
3.7.4 If CLI: exact terminal transcript
$ cmake ..
-- Configuring done
-- Generating done
-- Build files have been written to: /.../build
$ make -j4
[100%] Built target hello_display
$ cp hello_display.uf2 /Volumes/RP2350
# LCD updates within 2 seconds
4. Solution Architecture
4.1 High-Level Design
[App] -> [LCD Driver] -> [SPI + GPIO] -> [ST7789]
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| Pin map | Assign SPI, DC, RST, BL pins | Use board’s default mapping |
| SPI init | Configure SPI mode and clock | Start at 10 MHz, mode 0 |
| LCD init | Send command sequence | Datasheet + known-good delays |
| Pattern renderer | Draw gradient and bars | Deterministic, no randomness |
4.3 Data Structures (No Full Code)
struct lcd_config {
uint32_t spi_hz;
uint8_t mode;
uint16_t x_offset;
uint16_t y_offset;
};
4.4 Algorithm Overview
Key Algorithm: Full-Screen Fill
- Set CASET/RASET for full panel.
- Issue RAMWR.
- Stream RGB565 pixels row by row.
Complexity Analysis:
- Time: O(width * height)
- Space: O(1) extra, streaming
5. Implementation Guide
5.1 Development Environment Setup
# Install pico-sdk and toolchain
# Ensure PICO_SDK_PATH is set
5.2 Project Structure
hello_display/
- CMakeLists.txt
- src/
- main.c
- lcd_st7789.c
- lcd_st7789.h
- README.md
5.3 The Core Question You’re Answering
“How do I speak the LCD’s language well enough to make a pixel appear?”
5.4 Concepts You Must Understand First
Stop and research these before coding:
- SPI framing and DC pin semantics
- ST7789 init sequence and required delays
- RGB565 packing and byte order
- GPIO pad control (drive strength, slew, pull-ups)
5.5 Questions to Guide Your Design
- How will you abstract command vs data writes?
- What reset timing does the LCD require?
- How will you validate correct offsets?
- How will you verify SPI mode with a logic analyzer?
5.6 Thinking Exercise
Draw the exact byte sequence for: SWRESET -> SLPOUT -> COLMOD(0x55) -> CASET -> RASET -> RAMWR.
5.7 The Interview Questions They’ll Ask
- Why is DC required on SPI LCDs?
- What happens if you change SPI mode mid-transfer?
- How do you detect incorrect address offsets?
5.8 Hints in Layers
- Hint 1: Start at a slow SPI clock (1-2 MHz).
- Hint 2: Use a known-good init list and only change one value at a time.
- Hint 3: Draw a 1-pixel border to verify offsets.
- Hint 4: Capture MOSI on a logic analyzer to verify bytes.
5.9 Books That Will Help
| Topic | Book | Chapter | |——-|——|———| | SPI and timing | “Making Embedded Systems” | Ch. 6 | | LCD protocols | “Embedded Systems Architecture” | Display section | | Debugging | “The Art of Debugging with GDB” | Ch. 6 |
5.10 Implementation Phases
Phase 1: SPI Bring-up (2-3 hours)
Goals: Validate SPI and GPIO pins. Tasks: Configure SPI, toggle DC/CS, send a single command. Checkpoint: Logic analyzer shows clean MOSI and correct CS/DC framing.
Phase 2: LCD Init (2-3 hours)
Goals: LCD exits sleep and accepts pixel data. Tasks: Implement init sequence with delays. Checkpoint: Solid color fills the screen.
Phase 3: Deterministic Pattern (1-2 hours)
Goals: Prove pixel encoding and offsets. Tasks: Render gradient, color bars, and text. Checkpoint: Pattern matches the golden output.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale | |———-|———|—————-|———–| | SPI speed | 2-40 MHz | 10 MHz | Safe for bring-up, fast enough | | Pixel order | RGB/BGR | RGB | Matches most ST7789 panels | | Reset path | HW reset + SW reset | Both | Improves reliability |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples | |———-|———|———-| | Unit Tests | Validate helper functions | RGB565 conversion tests | | Integration Tests | Verify init sequence | Full-screen fill | | Edge Case Tests | Catch timing errors | Delayed reset removal |
6.2 Critical Test Cases
- Color Bars: red/green/blue appear correctly.
- Border Test: a 1-pixel border touches all edges.
- Reboot Stress: 10 consecutive resets show the same image.
6.3 Test Data
Test colors: 0xF800 (red), 0x07E0 (green), 0x001F (blue), 0xFFFF (white)
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution | |———|———|———-| | Wrong SPI mode | Blank or noisy display | Verify CPOL/CPHA in datasheet | | Bad reset timing | Works once, fails later | Add required delays | | Offset mismatch | Shifted image | Adjust CASET/RASET offsets |
7.2 Debugging Strategies
- Logic analyzer capture to confirm command order and data bytes.
- Binary search of init sequence: comment out half, find culprit.
7.3 Performance Traps
- Overly slow SPI makes full-screen fill feel sluggish; increase only after stability.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a splash screen bitmap stored in flash.
- Add a simple progress bar animation.
8.2 Intermediate Extensions
- Implement rotation with MADCTL and update offsets.
- Add a “diagnostic” mode that cycles test patterns.
8.3 Advanced Extensions
- Implement tearing-effect pin (TE) sync for perfect frames.
- Add on-screen SPI timing diagnostics.
9. Real-World Connections
9.1 Industry Applications
- Wearable device boot screens: deterministic bring-up checks.
- Instrumentation panels: LCD init is required for every power cycle.
9.2 Related Open Source Projects
- Adafruit ST7789 driver: reference init sequences.
- Pico SDK LCD examples: baseline SPI setup.
9.3 Interview Relevance
- SPI timing, GPIO pad control, and stateful protocols are common embedded topics.
10. Resources
10.1 Essential Reading
- ST7789V Datasheet - command set and timing
- RP2350 Datasheet - SPI, GPIO pad control
10.2 Video Resources
- SPI bus timing tutorials (logic analyzer walkthroughs)
10.3 Tools & Documentation
- Logic Analyzer: verify MOSI/CS/DC timing
- Pico SDK docs: SPI API reference
10.4 Related Projects in This Series
11. Self-Assessment Checklist
11.1 Understanding
- I can explain why DC is required for SPI LCDs.
- I can compute RGB565 values by hand.
- I can explain why address offsets matter.
11.2 Implementation
- LCD init works reliably on reset.
- Gradient and bars match the golden output.
- Logic analyzer capture matches my command list.
11.3 Growth
- I documented my init sequence and offsets.
- I can explain my driver in a job interview.
12. Submission / Completion Criteria
Minimum Viable Completion:
- LCD init sequence works on 10 consecutive resets.
- Gradient and color bars display correctly.
- SPI mode and clock documented.
Full Completion:
- All minimum criteria plus:
- Offset-correct border test passes.
- Logic analyzer capture attached to README.
Excellence (Going Above & Beyond):
- Include rotation modes and verify all orientations.
- Provide a troubleshooting guide with photos.