How to use a 0.96 inch OLED with an FPGA?
How to Use a 0.96 Inch OLED with an FPGA
To use a 0.96 inch OLED with an FPGA, you connect the display via I2C or SPI, write a Verilog or VHDL module to initialize the SSD1306 controller, and send pixel data from FPGA memory. The most common interface is I2C, which uses only two wires (SDA and SCL) and runs at 400 kHz for most FPGAs. For a typical setup, you need a 3.3V power supply, pull-up resistors on the I2C lines (4.7 kΩ), and a 128x64 monochrome OLED like the 0.96 inch 128x64 i2c oled display. The FPGA handles the data stream, generates the clock, and manages the command sequence. This approach works for real-time applications like oscilloscopes, game displays, or custom GUI overlays where you need fast refresh without a microcontroller.
The SSD1306 driver chip inside the OLED requires a specific initialization sequence: a reset pulse (low for at least 3 µs), then commands to set the display off, charge pump voltage (0x8D, 0x14), memory addressing mode (0x20, 0x00), segment remap (0xA1), COM scan direction (0xC8), contrast (0x81, 0xCF), and finally display on (0xAF). The full sequence is 26 bytes. You can store this in a Block RAM inside the FPGA and send it byte by byte over I2C. Each command must be preceded by a control byte: 0x00 for commands, 0x40 for data. The I2C address is 0x3C (7-bit) for most modules, which translates to 0x78 for write operations. If you use SPI, the interface is faster (up to 10 MHz) but requires four wires: CS, DC, MOSI, and SCK. The SPI initialization sequence is identical but uses a different protocol: you pull CS low, send the command byte with DC low, then data bytes with DC high.
For pixel data, the OLED uses a page addressing scheme. The 128x64 display is divided into 8 pages, each 8 pixels tall. To write a pixel at (x, y), you compute the page number as y/8 and the bit position as y%8. You send a command to set the page (0xB0 + page), then the column low nibble (0x00 + x%16), then column high nibble (0x10 + x/16), then send the data byte where the bit for that pixel is set. This means you need to buffer the entire frame in FPGA memory—128x64 bits equals 1024 bytes. A typical FPGA like the Xilinx Artix-7 has 1.8 Mbits of Block RAM, so you can easily allocate 1024 bytes for a frame buffer. You can double-buffer to avoid tearing: write to one buffer while the display reads from the other. The refresh rate is limited by the I2C speed: at 400 kHz, each byte takes 26.25 µs (including start/stop bits), so a full 1024-byte frame takes 26.9 ms, giving you about 37 frames per second. With SPI at 10 MHz, a frame takes 1.1 ms, enabling 900+ fps, but the OLED's internal update rate is about 100 Hz, so anything above that is wasted.
Let's break down the FPGA implementation in Verilog. You need a state machine with at least three states: IDLE, INIT, and SEND_DATA. In IDLE, you wait for a start signal. In INIT, you send the 26-byte initialization sequence. In SEND_DATA, you send the 1024-byte frame buffer. The I2C master module handles the clock generation and bit-banging. The SDA line is open-drain, so you need a tri-state buffer. The I2C clock frequency is derived from the FPGA clock: for a 50 MHz clock, you divide by 125 to get 400 kHz (50e6 / 125 = 400 kHz). The SCL high and low times must be equal, so you toggle every 62.5 clock cycles. The I2C protocol requires a start condition (SDA low while SCL high), then the 7-bit address plus write bit (0x78), then an ACK from the slave, then the control byte, then data bytes, each with ACK, then a stop condition (SDA high while SCL high). You can implement this with a counter and a shift register. For the frame buffer, you can use a dual-port RAM: one port for the FPGA logic to write pixel data, another for the I2C module to read and send. The pixel data can come from a VGA controller, a pattern generator, or an external ADC for an oscilloscope application.
Timing is critical. The SSD1306 datasheet specifies that the SCL clock low time must be at least 4.7 µs for standard mode (100 kHz) and 1.3 µs for fast mode (400 kHz). The rise time should be under 1 µs. The data hold time is 0 ns minimum, but the setup time is 250 ns. You must ensure your FPGA I/O pins are 3.3V tolerant. Most FPGAs have 3.3V banks, but check the datasheet for your specific board. The OLED module typically has a built-in voltage regulator and charge pump, so it draws about 20 mA when all pixels are on and 10 mA when off. The I2C pull-up resistors should be 4.7 kΩ for 400 kHz operation; if you use 1 kΩ, the current draw increases but the rise time improves. For long wires (over 10 cm), use 2.2 kΩ to reduce noise. The OLED's operating temperature range is -40°C to +85°C, so it's suitable for industrial environments.
Here's a practical example: an FPGA-based sine wave generator with a 0.96 inch OLED display. The FPGA generates a 1 kHz sine wave using a DDS (Direct Digital Synthesis) lookup table stored in Block RAM. The sine wave amplitude is sampled at 10 kHz and displayed as a waveform on the OLED. The frame buffer is 128 pixels wide, so you map the amplitude to a 64-pixel height. For each sample, you compute the y position and set the corresponding bit in the frame buffer. The display updates at 30 Hz, which is enough for smooth scrolling. The Verilog code uses a 50 MHz clock, a 400 kHz I2C master, and a dual-port RAM for the frame buffer. The total resource usage is about 200 LUTs, 150 flip-flops, and 1 Block RAM (1024 bytes). This fits in any FPGA with at least 2 kbits of memory, like the Lattice iCE40 or the Xilinx Spartan-6.
Another angle: power consumption. The OLED consumes 20 mA at 3.3V, which is 66 mW. The FPGA adds 50-100 mW depending on the size and clock speed. Total system power is under 200 mW, making it suitable for battery-powered devices. You can reduce power by using the display's sleep mode (command 0xAE) when idle. The FPGA can also gate the clock to the I2C module to save dynamic power. The SSD1306 has a built-in charge pump that generates 7V for the OLED pixels, so you don't need an external boost converter. The display contrast can be adjusted via command 0x81 with a value from 0 to 255. Lower contrast reduces power consumption: at contrast 0x00, the display draws 5 mA; at 0xFF, it draws 20 mA. For indoor use, a contrast of 0x80 is usually sufficient.
For debugging, you can use a logic analyzer to capture the I2C signals. The start condition should show SDA going low 4.7 µs before SCL. The address byte should be 0x78 (binary 0111 1000). The ACK bit from the OLED should be low (0) for each byte. If you see a NACK (high), the address is wrong or the OLED is not powered. Common mistakes: forgetting to set the charge pump (0x8D, 0x14), which results in a blank display; using the wrong I2C address (0x3C vs 0x3D); or not waiting for the initialization to complete (the SSD1306 needs 100 ms after power-on). The datasheet specifies a 100 ms delay after VDD reaches 3.3V before sending commands. You can implement this with a counter in the FPGA: at 50 MHz, 100 ms is 5 million clock cycles, so you need a 23-bit counter.
Let's talk about resolution and scaling. The 0.96 inch OLED has a 128x64 pixel matrix, with each pixel being 0.21 mm square. The viewing angle is >160 degrees, and the brightness is 100 cd/m² typical. The contrast ratio is 2000:1 due to the self-emissive nature of OLED. The response time is under 10 µs, so there's no motion blur. You can display text using a 5x7 font, which gives you 21 characters per line and 8 lines (128/6 = 21, 64/8 = 8). For graphics, you can use a 128x64 bitmap. The SSD1306 supports horizontal, vertical, and page addressing modes. The default is page addressing, which is the simplest for FPGA implementation. You can also use horizontal addressing, which auto-increments the column address after each byte, making it faster for full-screen updates. The horizontal mode is set by command 0x20, 0x00. In this mode, you send all 1024 bytes sequentially without resetting the column address, reducing the command overhead.
For advanced users, you can implement partial display updates. The SSD1306 supports a "display start line" register (0x40-0x7F) that lets you scroll the display vertically. You can also set a "display offset" (0xD3) to shift the displayed area. This is useful for scrolling text or moving graphics without rewriting the frame buffer. The FPGA can update the register value every frame to create a smooth scroll effect. The scrolling speed is determined by the frame rate: at 30 fps, a 64-pixel scroll takes 2.1 seconds. You can also use the "memory start address" commands to update only a portion of the display, which reduces the data transfer time. For example, if you only need to update a 32x32 pixel area, you send only 128 bytes instead of 1024, cutting the update time by 8x.
Here's a table comparing the two interface options for the 0.96 inch OLED with an FPGA:
Interface Comparison: I2C vs SPI for 0.96 Inch OLED with FPGA
| Parameter | I2C (400 kHz) | SPI (10 MHz) |
|-----------------------|---------------|--------------|
| Number of wires | 2 (SDA, SCL) | 4 (CS, DC, MOSI, SCK) |
| Max frame rate | 37 fps | 900 fps |
| FPGA resource usage | 150 LUTs | 100 LUTs |
| Power consumption | 66 mW (display) | 66 mW (display) |
| Complexity | Moderate | Low |
| Noise immunity | Good (open-drain) | Excellent (push-pull) |
| Maximum cable length | 1 meter | 10 meters |
| Typical use case | Low pin count | High speed |
If you're using an FPGA with limited I/O pins, I2C is the better choice because it frees up pins for other peripherals. For example, on a Lattice iCE40UP5K, you have only 39 I/O pins, so using I2C saves two pins compared to SPI. The trade-off is a lower frame rate, but for most display applications (text, menus, static graphics), 37 fps is more than enough. For video or animation, SPI is necessary. The SPI interface also simplifies the FPGA design because you don't need to handle the I2C start/stop conditions and ACK bits. You just pull CS low, send data, and pull CS high. The DC pin tells the display whether the byte is a command (low) or data (high). The SCK can run at the FPGA's maximum speed, up to 10 MHz for the SSD1306. Some modules support up to 20 MHz, but check the datasheet for your specific module.
Another practical consideration: the OLED module's pinout. The standard 0.96 inch I2C module has four pins: VCC (3.3V), GND, SCL, and SDA. Some modules have a fifth pin (RST) for hardware reset. If your module has an RST pin, connect it to an FPGA GPIO and pulse it low for 1 µs at startup. This ensures a clean reset even if the power supply is noisy. The module's PCB usually has pull-up resistors on the I2C lines, but if you're using a breakout board, you might need to add external 4.7 kΩ resistors. The VCC pin should be connected to a 3.3V regulator with at least 100 mA capacity. The GND pin should be connected to the FPGA's ground plane with a short trace to avoid ground loops. The OLED's backplane is sensitive to noise, so keep the I2C traces away from high-frequency signals like clock lines.
For the FPGA code, you can find open-source Verilog and VHDL implementations on GitHub. Search for "ssd1306 fpga" or "i2c oled fpga" to get started. Most examples use a state machine with a counter for the I2C bit timing. The key is to handle the ACK properly: after sending each byte, you release the SDA line and check if the slave pulls it low. If it doesn't, you have a communication error. Some FPGAs have built-in I2C peripherals, like the Xilinx AXI IIC core, but for a simple display, a bit-banged implementation is more flexible and uses fewer resources. The bit-banged I2C module should be parameterized for clock speed, address, and data width. You can instantiate it in your top-level module and connect it to the OLED pins. The frame buffer can be a simple register array or a Block RAM. For a 128x64 display, you need 1024 bytes, which is well within the capacity of any modern FPGA.
One more data point: the SSD1306's maximum clock frequency for I2C is 400 kHz in fast mode, but some modules support 1 MHz in fast mode plus. Check your module's datasheet. If you're using a 1 MHz clock, the frame rate increases to 92 fps, but the power consumption also increases due to faster switching. The FPGA's I2C master must be designed to handle the higher speed without violating setup/hold times. The SCL low time must be at least 1.3 µs for 400 kHz and 0.5 µs for 1 MHz. The SDA hold time must be at least 0 ns, but the setup time is 100 ns for 1 MHz. You can achieve this with a 50 MHz FPGA clock by dividing by 50 for 1 MHz (50e6 / 50 = 1 MHz). The SCL high and low times are each 25 clock cycles, giving 500 ns each, which is within the spec. The data setup time is 5 clock cycles (100 ns), which is exactly the minimum. So 1 MHz is feasible but leaves no margin for clock jitter. For reliable operation, stick with 400 kHz.
In terms of reliability, the OLED's lifetime is 50,000 hours for typical use (50% brightness). The blue pixels degrade faster than white or yellow, so if you're using a blue OLED, the lifetime drops to 30,000 hours. The SSD1306 has a built-in "display RAM" that retains data even when the display is off, so you can turn off the display to save power without losing the frame. The FPGA can read back the display RAM via I2C, but this is rarely used. The display's contrast also affects lifetime: higher contrast increases the current through the OLED pixels, accelerating degradation. For long-term use, set the contrast to 0x40 (64) or lower. The operating temperature range is -40°C to +85°C, but the OLED's brightness drops by 50% at -40°C due to reduced charge carrier mobility. At +85°C, the lifetime decreases by 50% due to thermal stress. So for extreme environments, consider a lower contrast and active cooling.
Finally, a word on testing: after you write the Verilog code, simulate it with a testbench that models the SSD1306's I2C behavior. You can use a simple I2C slave model that responds with ACKs and stores the received data in a memory array. This lets you verify the initialization sequence and frame buffer transfer before you load the code onto the FPGA. The simulation should run for at least 10 ms to cover the initialization and one frame update. Use a waveform viewer to check the I2C signals: the start condition, address, control byte, and data bytes. The ACK should appear after each byte. If you see a NACK, the address or timing is wrong. Once the simulation passes, program the FPGA and connect the OLED. If the display is blank, check the power supply voltage (3.3V ± 0.1V), the I2C pull-up resistors, and the initialization sequence. A common error is missing the charge pump command, which causes the display to stay off. Another is setting the wrong memory addressing mode, which causes pixel data to appear in the wrong location. Use a logic analyzer to capture the I2C traffic and compare it to the datasheet's timing diagram. With these steps, you'll have a working 0.96 inch OLED display driven by an FPGA in under an hour.