Skip to content
Edition · The Daily Resource

How to use a 0.96 inch OLED with a RISC-V board?

admin
Contributing editor
Daily Edition

How to use a 0.96 inch OLED with a RISC-V board

You can drive a 0.96 inch 128x64 OLED display with a RISC-V board by connecting it via I2C or SPI, then using a lightweight graphics library to control pixels. The most common approach is to use the SSD1306 driver chip, which is standard on these displays. For example, on a Sipeed M1s Dock (based on the BL808 RISC-V chip), you can wire the OLED’s SDA and SCL to the board’s I2C pins (like GPIO 12 and 13), set the voltage to 3.3V, and run a simple C or MicroPython script that initializes the display with a 128x64 buffer. The display draws about 20mA during active use, which is fine for battery-powered projects. The key is to match the communication protocol: I2C uses only two wires plus power, while SPI uses four wires but offers faster refresh rates (up to 10 MHz vs 400 kHz for I2C). For most RISC-V boards, I2C is simpler because it requires fewer GPIOs, but SPI is better if you need to update the screen frequently, like for animations. The 0.96 inch 128x64 spi i2c oled display supports both modes, so you can choose based on your board’s pinout.

Let’s break down the hardware setup. The OLED module typically has four pins for I2C (VCC, GND, SCL, SDA) or seven pins for SPI (VCC, GND, CS, DC, RES, SCLK, MOSI). On a RISC-V board like the ESP32-C3 (which uses a RISC-V core), the I2C pins are often GPIO 4 and 5 for SDA and SCL. You need to connect VCC to 3.3V, GND to ground, and the data lines directly. No level shifting is required because both the OLED and the board operate at 3.3V logic. If you’re using SPI, connect CS to a GPIO (e.g., GPIO 10), DC to GPIO 11, RES to GPIO 12, SCLK to GPIO 13, and MOSI to GPIO 14. The display’s maximum clock speed for SPI is 10 MHz, but most RISC-V boards run at 80 MHz or higher, so you can set the SPI clock to 4 MHz to avoid signal integrity issues. The OLED’s pixel array is 128 columns by 64 rows, with each pixel controlled by a single bit in the buffer. The SSD1306 driver has 128x64 bits of internal RAM, which equals 1024 bytes. You write to this buffer over I2C or SPI, and the driver refreshes the screen automatically.

For software, you need a driver library. On RISC-V boards running FreeRTOS or bare-metal C, you can use the Adafruit SSD1306 library, which is ported to many platforms. The library handles the I2C or SPI communication and provides functions like display.drawPixel(x, y, color) and display.display() to push the buffer to the screen. The initialization sequence for the SSD1306 includes setting the display off, setting the multiplex ratio to 63 (for 64 rows), setting the display offset to 0, setting the start line to 0, and then turning the display on. This takes about 10 milliseconds. A typical I2C write operation sends a command byte followed by data, with the device address being 0x3C (or 0x3D if the SA0 pin is pulled high). For SPI, you assert the CS pin low, send a command byte (with DC low) or data byte (with DC high), then deassert CS. The display’s contrast can be set from 0 to 255 via the setContrast() function, with 128 being a good default.

Let’s look at a concrete example using the Sipeed M1s Dock, which has a BL808 RISC-V chip with 64 MB of RAM. You can use the Bouffalo Lab SDK, which includes I2C and SPI drivers. Here’s a step-by-step: first, initialize the I2C peripheral with a clock speed of 400 kHz. Then, send the SSD1306 initialization commands: 0xAE (display off), 0xD5 (set display clock divide ratio), 0x80 (default ratio), 0xA8 (set multiplex ratio), 0x3F (64 rows), 0xD3 (set display offset), 0x00 (no offset), 0x40 (set start line), 0x8D (enable charge pump), 0x14 (enable), 0x20 (set memory addressing mode), 0x00 (horizontal mode), 0xA1 (set segment remap), 0xC8 (set COM scan direction), 0xDA (set COM pins hardware configuration), 0x12 (alternative pin configuration), 0x81 (set contrast), 0xCF (contrast value), 0xD9 (set pre-charge period), 0xF1 (default), 0xDB (set VCOMH deselect level), 0x40 (default), 0xA4 (display on resume), 0xA6 (normal display), 0x2E (deactivate scroll), 0xAF (display on). After this, you can write pixel data to the buffer. For a simple test, fill the buffer with 0xFF to turn all pixels on, then call display.display(). The screen will show a solid white rectangle. The total initialization code is about 50 lines in C.

If you’re using MicroPython on a RISC-V board like the K210 (which is a dual-core RISC-V chip), the process is even simpler. The board has a built-in I2C peripheral, and you can use the machine.I2C class. First, import the ssd1306 module from the MicroPython library. Then, create an I2C object: i2c = machine.I2C(0, scl=machine.Pin(13), sda=machine.Pin(12), freq=400000). Then, create the display object: oled = ssd1306.SSD1306_I2C(128, 64, i2c). You can then draw text: oled.text("Hello RISC-V", 0, 0), and call oled.show() to update the screen. The MicroPython library handles the buffer and communication automatically. The display’s refresh rate is limited by the I2C speed: at 400 kHz, sending 1024 bytes takes about 20 milliseconds, giving a theoretical frame rate of 50 Hz. In practice, the library adds overhead, so you get about 30 FPS for simple graphics. For SPI, the frame rate can exceed 100 FPS, but you need to manage the CS and DC pins manually.

One common issue is the display’s voltage tolerance. The SSD1306 can operate from 3.0V to 5.5V, but the logic pins are 3.3V only. If your RISC-V board uses 5V logic (like some older boards), you need a level shifter. However, most modern RISC-V boards (like the ESP32-C3, K210, or BL808) use 3.3V I/O, so direct connection works. The display’s current consumption is about 20mA with all pixels on, but it drops to 1mA in sleep mode. You can put the display to sleep by sending the 0xAE command, and wake it with 0xAF. This is useful for battery-powered projects where you want to save power. The display’s operating temperature range is -40°C to 85°C, making it suitable for outdoor use.

When choosing between I2C and SPI, consider your board’s pin availability. I2C uses only two data lines, which is great for boards with limited GPIOs, like the ESP32-C3 which has only 15 usable pins. SPI uses four data lines, but it frees up the I2C bus for other sensors. The display’s I2C address is 0x3C by default, but you can change it to 0x3D by soldering the SA0 pad on the module. This allows you to connect two OLEDs on the same I2C bus. For SPI, you can use multiple displays by giving each its own CS pin. The display’s SPI mode is Mode 0 (CPOL=0, CPHA=0), meaning the clock idles low and data is sampled on the rising edge. Most RISC-V boards support this mode natively.

Let’s talk about performance. On a RISC-V board running at 160 MHz (like the ESP32-C3), an I2C write of 1024 bytes takes about 2.5 milliseconds at 400 kHz, including protocol overhead. The SSD1306’s internal refresh is about 100 Hz, so the bottleneck is the communication speed. For SPI at 4 MHz, the same write takes 0.25 milliseconds, allowing you to update the display at 4000 Hz theoretically. However, the human eye can’t see changes faster than 60 Hz, so SPI is overkill for static text. For animations, SPI is better because you can update the buffer in the background and push it quickly. The display’s buffer is double-buffered in the SSD1306, meaning you can write to the buffer while the driver is refreshing the screen. This eliminates tearing, but you need to ensure your writes are atomic.

For advanced use, you can implement scrolling. The SSD1306 supports horizontal scrolling via hardware commands: 0x26 for right scroll, 0x27 for left scroll, and 0x2A for vertical scroll. You set the start page, end page, and scroll speed (0 to 7, where 0 is 2 frames per step and 7 is 128 frames per step). For example, to scroll the entire screen right, send: 0x26, 0x00 (start page), 0x07 (end page), 0x00 (speed), then 0x2F (activate scroll). The display will scroll continuously until you send 0x2E (deactivate). This uses no CPU overhead, which is great for RISC-V boards with limited processing power. You can also combine horizontal and vertical scrolling to create diagonal effects.

One practical tip: always initialize the display with a delay of at least 100 milliseconds after power-up. The SSD1306 needs time to stabilize, and sending commands too early can cause the display to lock up. Also, avoid using the display’s internal charge pump in high-temperature environments, as it can cause the display to flicker. Instead, use an external 10V supply for the OLED’s VCC if you’re operating above 70°C. The display’s lifetime is about 50,000 hours of continuous use, which is roughly 5.7 years. The contrast degrades over time, but you can compensate by increasing the contrast value in software.

If you’re using a RISC-V board with a Linux kernel (like the StarFive VisionFive 2), you can use the Linux kernel’s SSD1306 driver. This driver is part of the staging tree and supports I2C and SPI. You need to enable it in the kernel config: CONFIG_FB_SSD1307 for I2C or CONFIG_FB_SSD1306 for SPI. Then, you can use the fbset tool to control the display, or write to /dev/fb0 directly. The framebuffer is 128x64 pixels with 1 bit per pixel, so you can use standard Linux graphics libraries like SDL or Cairo to render to it. This is useful for embedded Linux projects where you want to display system information like CPU usage or IP address.

Another consideration is the display’s physical interface. The module usually comes with a 4-pin or 7-pin header, but you can also find versions with a 0.1-inch pitch connector. If you’re prototyping on a breadboard, use female-to-female jumper wires. For permanent installations, solder the module directly to a perfboard. The display’s thickness is about 1.2mm, and it weighs 3 grams, so it’s easy to mount. The viewing angle is 160 degrees, which is typical for OLEDs. The display’s contrast ratio is 10000:1, meaning black pixels are truly black because they emit no light. This makes it ideal for dark environments.

To debug connection issues, use a logic analyzer to check the I2C or SPI signals. The display’s I2C address should appear on the bus after you send a start condition. If you see no ACK, check the wiring and voltage. The SCL and SDA lines need pull-up resistors to 3.3V, typically 4.7k ohms. Some modules have built-in pull-ups, but not all. If your display doesn’t respond, add external 10k resistors. For SPI, check that the CS pin is active low and that the DC pin is set correctly for commands vs data. A common mistake is sending commands with DC high, which causes the display to interpret them as data.

For power consumption, the display draws 20mA with all pixels on, but only 0.5mA with a typical text display (about 10% of pixels on). You can further reduce power by using the display’s sleep mode (0.1mA) or by turning off the charge pump (0x8D, 0x10). If you’re using a battery, consider using a GPIO to switch the display’s VCC on and off. This adds a transistor, but it can save power when the display is not in use. The display’s startup time from sleep is about 100ms, so you can put it to sleep between updates.

Finally, let’s look at a code snippet for a RISC-V board using the Arduino IDE (which supports ESP32-C3 and other RISC-V chips). Install the Adafruit SSD1306 library and the Adafruit GFX library. Then, use this code:

#include
#include
#include
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
Serial.begin(115200);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 allocation failed");
for(;;);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.println("RISC-V OLED");
display.display();
}
void loop() {
// nothing
}

This code initializes the display and prints "RISC-V OLED". The display.begin() function sends the initialization sequence, and display.display() pushes the buffer. If you get an allocation failure, check the I2C address and wiring. The library uses about 1.5 KB of RAM for the buffer, which is fine for most RISC-V boards with at least 4 KB of SRAM. The code runs on any RISC-V board that supports the Arduino framework, including the ESP32-C3, K210, and BL808. For SPI, change the constructor to Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, OLED_DC, OLED_RESET, OLED_CS) and initialize the SPI pins accordingly.

In summary, using a 0.96 inch OLED with a RISC-V board is straightforward: choose I2C for simplicity or SPI for speed, wire the pins correctly, use a library like Adafruit SSD1306 or MicroPython’s ssd1306 module, and write pixel data to the buffer. The display’s low power consumption and high contrast make it ideal for embedded projects. The specific model I recommend is the 0.96 inch 128x64 spi i2c oled display because it supports both protocols and has a proven SSD1306 driver. Test your setup with a simple pixel write first, then expand to text and graphics. If you run into issues, check the voltage levels, pull-up resistors, and initialization sequence. The display’s documentation typically includes a command table, which you can use to customize the behavior. For example, you can set the display’s brightness by adjusting the contrast value, or invert the display with the 0xA7 command. The possibilities are endless, and the RISC-V ecosystem provides plenty of tools to get started.

Get tomorrow's resource free

One carefully chosen link, every weekday at 6am ET. Curated by humans, read in under five minutes.

Subscribe — it's free