Driving an I²C OLED from an [FPGA](https://www.ampheo.com/c/fpgas-field-programmable-gate-array) is basically two parts: 1. build a reliable I²C master (START/STOP/ACK/byte send) with open-drain SDA/SCL, 2. implement an [OLED](https://www.onzuu.com/category/lcd-oled-character-and-numeric) controller state machine that sends the init commands + framebuffer data (most common OLEDs are SSD1306/SH1106). Below is a practical, “works on real boards” approach. ![maxresdefault (91)](https://hackmd.io/_uploads/rJBkuqmHWg.jpg) **1) Hardware essentials (don’t skip these)** **Bus wiring** * SCL, SDA need pull-up [resistors](https://www.onzuu.com/category/resistors) to the OLED’s I/O voltage (often 3.3V). * Typical: 4.7 kΩ (start here), 2.2–10 kΩ depending on bus speed/capacitance. * FPGA pins must behave like open-drain: * Drive 0 strongly * For 1, release (Hi-Z) and let pull-ups bring the line high **Voltage** * Ensure FPGA I/O bank voltage matches OLED module I/O (3.3V common). * Many small OLED modules accept 3.3V logic even when powered from 5V (but verify). **2) I²C basics you must implement in FPGA** I²C is byte-oriented with: * START: SDA goes low while SCL high * STOP: SDA goes high while SCL high * Write byte: 8 bits MSB first, then ACK bit from slave (SDA pulled low = ACK) For OLED modules: * 7-bit slave address is commonly 0x3C or 0x3D * Write address byte = (addr<<1) | 0 → 0x78 or 0x7A for 0x3C/0x3D Pick a bus speed * 100 kHz is easiest and very tolerant. * 400 kHz is fine on short traces. You’ll generate SCL by dividing the FPGA clock. **3) OLED (SSD1306-style) I²C packet format** After the address byte and ACK, SSD1306 uses a control byte: * 0x00 = following bytes are commands * 0x40 = following bytes are data (display RAM) So you typically send: * Init: [START][addrW][0x00][cmd1][cmd2]...[STOP] * Frame update: [START][addrW][0x40][data…many bytes][STOP] For 128×64 monochrome OLED: * framebuffer is 128×64/8 = 1024 bytes * data is often sent in 8 pages × 128 bytes/page **4) Initialization sequence (typical SSD1306 128×64)** Different modules can vary (SSD1306 vs SH1106), but this is a common SSD1306 init in page addressing mode (safe starter): Commands (sent after control byte 0x00): * AE display OFF * D5 80 clock divide * A8 3F multiplex 1/64 * D3 00 display offset * 40 start line = 0 * 8D 14 charge pump ON (common for I²C modules) * 20 02 memory mode = page addressing * A1 segment remap (mirror X) (optional) * C8 COM scan direction (mirror Y) (optional) * DA 12 COM pins config (common for 128×64) * 81 CF contrast (tune) * D9 F1 pre-charge * DB 40 VCOMH deselect * A4 display follows RAM * A6 normal (not inverted) * AF display ON If your image is upside down/mirrored, flip A1/C8. **5) How to update the display (page write pattern)** For each page 0..7: 1. set page + column: * B0 | page (page select) * 00 (lower column = 0) * 10 (higher column = 0) 2. send 128 bytes of data with control byte 0x40 **6) Verilog skeleton: open-drain I²C lines** **Open-drain pin driving (generic)** ``` // drive_low = 1 -> drive '0' // drive_low = 0 -> release (Hi-Z) so pull-up makes it '1' assign sda = sda_drive_low ? 1'b0 : 1'bz; assign scl = scl_drive_low ? 1'b0 : 1'bz; // Read the actual line level: wire sda_in = sda; wire scl_in = scl; ``` On [Xilinx](https://www.ampheo.com/manufacturer/amd) you can also use IOBUF if you prefer, but the above works if the pin supports tri-state. **7) Verilog idea: I²C byte writer (state machine concept)** You want a module like: * Inputs: start, stop, write_byte, byte_in * Outputs: busy, done, ack_error Internally it: * Generates SCL high/low phases * Puts each data bit on SDA while SCL low, then raises SCL to sample * Releases SDA on the 9th clock to read ACK Key rule: change SDA only when SCL is low (except START/STOP). **8) OLED controller architecture (what to build)** **Minimal working blocks** 1. i2c_master (byte-level) 2. oled_ctrl (high-level sequences) oled_ctrl does: * power-up delay (a few ms) * send init command list * periodically push framebuffer (or only when changed) **Framebuffer options** * BRAM framebuffer: 1024 bytes for 128×64 (easy) * Generate on the fly: if you only need text/simple graphics **9) Debug checklist (saves hours)** * Confirm pull-ups exist and match voltage. * Check SCL frequency with a scope (start with 100 kHz). * Verify you see: * START, address byte, ACK low * If no ACK: * wrong address (0x3C vs 0x3D) * OLED not powered / wrong voltage * SDA/SCL swapped * [FPGA](https://www.ampheoelec.de/c/fpgas-field-programmable-gate-array) pins not actually tri-stating for ‘1’