i2c display - dwilson2547/wiki_demo GitHub Wiki
Here's how to communicate with an SSD1306 OLED display via I2C from your Raspberry Pi:
First, connect your SSD1306 OLED to the Raspberry Pi's I2C pins:
- VCC → 3.3V (Pin 1) or 5V (Pin 2) - check your display's voltage requirement
- GND → Ground (Pin 6)
- SDA → GPIO 2 (Pin 3) - I2C Data
- SCL → GPIO 3 (Pin 5) - I2C Clock
# Enable I2C interface
sudo raspi-config
# Navigate to: Interfacing Options > I2C > Enable
# Or enable directly:
sudo systemctl enable i2c-arm
# Install I2C tools
sudo apt update
sudo apt install i2c-tools
# Detect connected I2C devices
sudo i2cdetect -y 1
The SSD1306 typically appears at address 0x3C
or 0x3D
.
# Install the Adafruit CircuitPython library (recommended)
pip install adafruit-circuitpython-ssd1306
pip install pillow # For image manipulation
# Alternative: luma.oled library
pip install luma.oled
#!/usr/bin/env python3
"""
SSD1306 OLED Display Examples for Raspberry Pi
Supports both Adafruit CircuitPython and luma.oled libraries
"""
import time
import board
import busio
from PIL import Image, ImageDraw, ImageFont
# Method 1: Using Adafruit CircuitPython library
def adafruit_example():
"""Basic example using Adafruit CircuitPython SSD1306 library"""
import adafruit_ssd1306
# Create I2C interface
i2c = busio.I2C(board.SCL, board.SDA)
# Create SSD1306 display object
# Common sizes: 128x64, 128x32
display = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)
# Clear display
display.fill(0)
display.show()
# Create blank image for drawing
image = Image.new("1", (display.width, display.height))
draw = ImageDraw.Draw(image)
# Draw some shapes and text
draw.rectangle((0, 0, display.width, display.height), outline=0, fill=0)
draw.rectangle((2, 2, display.width-2, display.height-2), outline=1, fill=0)
draw.text((10, 10), "Hello World!", fill=1)
draw.text((10, 25), "Raspberry Pi", fill=1)
draw.text((10, 40), "SSD1306 OLED", fill=1)
# Display image
display.image(image)
display.show()
def adafruit_scrolling_text():
"""Scrolling text example with Adafruit library"""
import adafruit_ssd1306
i2c = busio.I2C(board.SCL, board.SDA)
display = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)
# Load a font
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16)
except:
font = ImageFont.load_default()
text = "This is a scrolling message on SSD1306 OLED display!"
for i in range(len(text) * 10):
# Create image
image = Image.new("1", (display.width, display.height))
draw = ImageDraw.Draw(image)
# Clear image
draw.rectangle((0, 0, display.width, display.height), outline=0, fill=0)
# Calculate text position for scrolling
x = display.width - i * 2
draw.text((x, 20), text, font=font, fill=1)
# Display
display.image(image)
display.show()
time.sleep(0.1)
# Method 2: Using luma.oled library
def luma_example():
"""Basic example using luma.oled library"""
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
# Create device
serial = i2c(port=1, address=0x3C)
device = ssd1306(serial, width=128, height=64)
# Draw on device
with canvas(device) as draw:
draw.rectangle(device.bounding_box, outline="white", fill="black")
draw.text((10, 10), "Hello World!", fill="white")
draw.text((10, 25), "Luma OLED", fill="white")
draw.text((10, 40), "Library", fill="white")
def luma_system_stats():
"""Display system statistics using luma.oled"""
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
import psutil
import socket
serial = i2c(port=1, address=0x3C)
device = ssd1306(serial, width=128, height=64)
def get_ip():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except:
return "No connection"
while True:
# Get system stats
cpu_percent = psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
ip_address = get_ip()
with canvas(device) as draw:
# CPU usage
draw.text((0, 0), f"CPU: {cpu_percent:.1f}%", fill="white")
# Memory usage
draw.text((0, 12), f"RAM: {memory.percent:.1f}%", fill="white")
# Disk usage
draw.text((0, 24), f"Disk: {disk.percent:.1f}%", fill="white")
# IP Address
draw.text((0, 36), f"IP: {ip_address}", fill="white")
# Temperature (if available)
try:
temp = psutil.sensors_temperatures()['cpu_thermal'][0].current
draw.text((0, 48), f"Temp: {temp:.1f}°C", fill="white")
except:
draw.text((0, 48), "Temp: N/A", fill="white")
time.sleep(2)
# Comprehensive OLED class for easy use
class SSD1306Display:
"""Easy-to-use SSD1306 OLED display class"""
def __init__(self, width=128, height=64, address=0x3C):
import adafruit_ssd1306
self.width = width
self.height = height
# Initialize I2C and display
self.i2c = busio.I2C(board.SCL, board.SDA)
self.display = adafruit_ssd1306.SSD1306_I2C(width, height, self.i2c, addr=address)
# Load default font
try:
self.font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
except:
self.font = ImageFont.load_default()
self.clear()
def clear(self):
"""Clear the display"""
self.display.fill(0)
self.display.show()
def text(self, x, y, text, font=None):
"""Display text at specified coordinates"""
image = Image.new("1", (self.width, self.height))
draw = ImageDraw.Draw(image)
if font is None:
font = self.font
draw.text((x, y), text, font=font, fill=1)
self.display.image(image)
self.display.show()
def multi_line_text(self, lines, font=None):
"""Display multiple lines of text"""
image = Image.new("1", (self.width, self.height))
draw = ImageDraw.Draw(image)
if font is None:
font = self.font
y_offset = 0
line_height = 12
for line in lines:
draw.text((0, y_offset), line, font=font, fill=1)
y_offset += line_height
self.display.image(image)
self.display.show()
def progress_bar(self, x, y, width, height, progress):
"""Draw a progress bar (progress: 0.0 to 1.0)"""
image = Image.new("1", (self.width, self.height))
draw = ImageDraw.Draw(image)
# Outer rectangle
draw.rectangle((x, y, x + width, y + height), outline=1, fill=0)
# Progress fill
fill_width = int(width * progress)
if fill_width > 0:
draw.rectangle((x + 1, y + 1, x + fill_width - 1, y + height - 1), outline=1, fill=1)
self.display.image(image)
self.display.show()
# Example usage
if __name__ == "__main__":
print("SSD1306 OLED Display Examples")
print("Make sure your display is connected to I2C pins")
try:
# Test basic functionality
print("Testing basic display...")
adafruit_example()
time.sleep(3)
# Test the easy-to-use class
print("Testing custom class...")
oled = SSD1306Display()
# Display some text
oled.multi_line_text([
"Raspberry Pi",
"OLED Display",
"Working!",
"IP: 192.168.1.100"
])
time.sleep(3)
# Show progress bar
for i in range(101):
oled.clear()
oled.text(0, 0, "Loading...")
oled.progress_bar(0, 20, 100, 10, i/100.0)
oled.text(0, 40, f"{i}%")
time.sleep(0.05)
print("Demo complete!")
except Exception as e:
print(f"Error: {e}")
print("Make sure I2C is enabled and display is connected properly")
#!/usr/bin/env python3
"""
Advanced SSD1306 OLED Display Features
Including animations, graphics, and real-time data display
"""
import time
import math
import board
import busio
import adafruit_ssd1306
from PIL import Image, ImageDraw, ImageFont
import threading
import queue
class AdvancedOLEDDisplay:
"""Advanced SSD1306 display with animations and graphics"""
def __init__(self, width=128, height=64, address=0x3C):
self.width = width
self.height = height
# Initialize I2C and display
self.i2c = busio.I2C(board.SCL, board.SDA)
self.display = adafruit_ssd1306.SSD1306_I2C(width, height, self.i2c, addr=address)
# Fonts
try:
self.small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 10)
self.medium_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14)
self.large_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18)
except:
self.small_font = ImageFont.load_default()
self.medium_font = ImageFont.load_default()
self.large_font = ImageFont.load_default()
self.clear()
# Animation control
self.animation_running = False
self.animation_thread = None
def clear(self):
"""Clear the display"""
self.display.fill(0)
self.display.show()
def draw_bitmap(self, bitmap_data, x=0, y=0):
"""Draw a bitmap image"""
image = Image.new("1", (self.width, self.height))
draw = ImageDraw.Draw(image)
for row, byte_row in enumerate(bitmap_data):
for col, pixel in enumerate(byte_row):
if pixel:
draw.point((x + col, y + row), fill=1)
self.display.image(image)
self.display.show()
def bouncing_ball_animation(self, duration=10):
"""Animated bouncing ball"""
start_time = time.time()
# Ball properties
ball_x, ball_y = 20, 20
vel_x, vel_y = 3, 2
ball_size = 4
while time.time() - start_time < duration:
# Create image
image = Image.new("1", (self.width, self.height))
draw = ImageDraw.Draw(image)
# Update ball position
ball_x += vel_x
ball_y += vel_y
# Bounce off walls
if ball_x <= ball_size or ball_x >= self.width - ball_size:
vel_x = -vel_x
if ball_y <= ball_size or ball_y >= self.height - ball_size:
vel_y = -vel_y
# Keep ball in bounds
ball_x = max(ball_size, min(self.width - ball_size, ball_x))
ball_y = max(ball_size, min(self.height - ball_size, ball_y))
# Draw ball
draw.ellipse((ball_x - ball_size, ball_y - ball_size,
ball_x + ball_size, ball_y + ball_size),
outline=1, fill=1)
# Draw borders
draw.rectangle((0, 0, self.width-1, self.height-1), outline=1)
self.display.image(image)
self.display.show()
time.sleep(0.05)
def sine_wave_animation(self, duration=10):
"""Animated sine wave"""
start_time = time.time()
phase = 0
while time.time() - start_time < duration:
image = Image.new("1", (self.width, self.height))
draw = ImageDraw.Draw(image)
# Draw sine wave
points = []
for x in range(self.width):
y = int(self.height/2 + 20 * math.sin(2 * math.pi * x / 40 + phase))
points.append((x, y))
# Draw the wave
for i in range(len(points) - 1):
draw.line([points[i], points[i+1]], fill=1)
# Add labels
draw.text((5, 5), "Sine Wave", font=self.small_font, fill=1)
draw.text((5, self.height-15), f"Phase: {phase:.1f}", font=self.small_font, fill=1)
phase += 0.2
self.display.image(image)
self.display.show()
time.sleep(0.1)
def digital_clock(self, duration=30):
"""Digital clock display"""
start_time = time.time()
while time.time() - start_time < duration:
image = Image.new("1", (self.width, self.height))
draw = ImageDraw.Draw(image)
# Get current time
current_time = time.strftime("%H:%M:%S")
current_date = time.strftime("%Y-%m-%d")
day_of_week = time.strftime("%A")
# Draw time (large)
draw.text((10, 15), current_time, font=self.large_font, fill=1)
# Draw date (small)
draw.text((10, 40), current_date, font=self.small_font, fill=1)
draw.text((10, 52), day_of_week, font=self.small_font, fill=1)
# Draw border
draw.rectangle((0, 0, self.width-1, self.height-1), outline=1)
self.display.image(image)
self.display.show()
time.sleep(1)
def temperature_gauge(self, temperature, min_temp=-10, max_temp=50):
"""Display temperature as a gauge"""
image = Image.new("1", (self.width, self.height))
draw = ImageDraw.Draw(image)
# Gauge properties
center_x, center_y = self.width // 2, self.height - 10
radius = 40
# Calculate angle based on temperature
temp_range = max_temp - min_temp
temp_ratio = (temperature - min_temp) / temp_range
angle = math.pi + (temp_ratio * math.pi) # From π to 2π
# Draw gauge arc
gauge_points = []
for i in range(50):
a = math.pi + (i * math.pi / 49)
x = center_x + radius * math.cos(a)
y = center_y + radius * math.sin(a)
gauge_points.append((int(x), int(y)))
# Draw gauge outline
for i in range(len(gauge_points) - 1):
draw.line([gauge_points[i], gauge_points[i+1]], fill=1)
# Draw temperature needle
needle_x = center_x + (radius - 5) * math.cos(angle)
needle_y = center_y + (radius - 5) * math.sin(angle)
draw.line([(center_x, center_y), (int(needle_x), int(needle_y))], fill=1)
# Draw center dot
draw.ellipse((center_x-2, center_y-2, center_x+2, center_y+2), fill=1)
# Temperature text
draw.text((center_x-20, 5), f"{temperature:.1f}°C", font=self.medium_font, fill=1)
# Min/max labels
draw.text((5, center_y-5), f"{min_temp}", font=self.small_font, fill=1)
draw.text((self.width-20, center_y-5), f"{max_temp}", font=self.small_font, fill=1)
self.display.image(image)
self.display.show()
def scrolling_menu(self, menu_items, selected_index=0):
"""Display a scrolling menu"""
image = Image.new("1", (self.width, self.height))
draw = ImageDraw.Draw(image)
# Menu properties
line_height = 12
visible_lines = self.height // line_height
# Calculate scroll offset
if selected_index >= visible_lines:
scroll_offset = selected_index - visible_lines + 1
else:
scroll_offset = 0
# Draw menu items
for i, item in enumerate(menu_items[scroll_offset:scroll_offset + visible_lines]):
y = i * line_height
actual_index = i + scroll_offset
# Highlight selected item
if actual_index == selected_index:
draw.rectangle((0, y, self.width, y + line_height), fill=1)
draw.text((5, y + 2), item, font=self.small_font, fill=0)
else:
draw.text((5, y + 2), item, font=self.small_font, fill=1)
# Draw scroll indicator
if len(menu_items) > visible_lines:
scroll_height = self.height
indicator_height = max(5, scroll_height * visible_lines // len(menu_items))
indicator_y = scroll_height * scroll_offset // len(menu_items)
draw.rectangle((self.width-3, indicator_y, self.width-1,
indicator_y + indicator_height), fill=1)
self.display.image(image)
self.display.show()
def draw_graph(self, data_points, title="Graph", min_val=None, max_val=None):
"""Draw a line graph"""
if not data_points:
return
image = Image.new("1", (self.width, self.height))
draw = ImageDraw.Draw(image)
# Graph area
graph_x = 10
graph_y = 15
graph_width = self.width - 15
graph_height = self.height - 25
# Auto-scale if not provided
if min_val is None:
min_val = min(data_points)
if max_val is None:
max_val = max(data_points)
if max_val == min_val:
max_val = min_val + 1 # Avoid division by zero
# Draw title
draw.text((5, 2), title, font=self.small_font, fill=1)
# Draw axes
draw.line([(graph_x, graph_y), (graph_x, graph_y + graph_height)], fill=1) # Y-axis
draw.line([(graph_x, graph_y + graph_height), (graph_width, graph_y + graph_height)], fill=1) # X-axis
# Draw data points
points = []
for i, value in enumerate(data_points):
x = graph_x + (i * (graph_width - graph_x) // len(data_points))
y = graph_y + graph_height - int((value - min_val) * graph_height / (max_val - min_val))
points.append((x, y))
# Draw lines between points
for i in range(len(points) - 1):
draw.line([points[i], points[i+1]], fill=1)
# Draw value labels
draw.text((2, graph_y), f"{max_val:.0f}", font=self.small_font, fill=1)
draw.text((2, graph_y + graph_height - 8), f"{min_val:.0f}", font=self.small_font, fill=1)
self.display.image(image)
self.display.show()
# Example usage and demonstrations
def demo_advanced_features():
"""Demonstrate advanced OLED features"""
print("Starting advanced OLED demonstrations...")
oled = AdvancedOLEDDisplay()
try:
# Digital clock
print("Showing digital clock for 10 seconds...")
oled.digital_clock(duration=10)
# Temperature gauge
print("Showing temperature gauge...")
for temp in range(-5, 35, 2):
oled.temperature_gauge(temp)
time.sleep(0.5)
# Bouncing ball animation
print("Bouncing ball animation...")
oled.bouncing_ball_animation(duration=8)
# Sine wave animation
print("Sine wave animation...")
oled.sine_wave_animation(duration=8)
# Menu demo
print("Menu demonstration...")
menu_items = ["Option 1", "Option 2", "Option 3", "Settings", "About", "Exit"]
for selected in range(len(menu_items)):
oled.scrolling_menu(menu_items, selected)
time.sleep(1)
# Graph demo
print("Graph demonstration...")
import random
data = [random.randint(10, 90) for _ in range(20)]
oled.draw_graph(data, "Random Data")
time.sleep(5)
print("Demo complete!")
oled.clear()
except KeyboardInterrupt:
print("Demo interrupted by user")
oled.clear()
except Exception as e:
print(f"Error during demo: {e}")
oled.clear()
if __name__ == "__main__":
demo_advanced_features()
-
Display not detected:
# Check I2C address sudo i2cdetect -y 1 # If nothing appears, check connections and try: sudo i2cdetect -y 0 # For older Pi models
-
Wrong address:
- Most SSD1306 displays use
0x3C
, but some use0x3D
- Try both addresses in your code
- Most SSD1306 displays use
-
Display appears scrambled:
- Check if you have the correct width/height (128x64 vs 128x32)
- Verify power supply (3.3V vs 5V requirement)
-
Permission errors:
# Add user to i2c group sudo usermod -a -G i2c $USER # Logout and login again
#!/usr/bin/env python3
import board
import busio
import adafruit_ssd1306
from PIL import Image, ImageDraw
# Quick test
i2c = busio.I2C(board.SCL, board.SDA)
display = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)
display.fill(0)
display.show()
image = Image.new("1", (128, 64))
draw = ImageDraw.Draw(image)
draw.text((0, 0), "OLED Working!", fill=1)
display.image(image)
display.show()
The examples above provide everything you need to get started with your SSD1306 OLED display, from basic text display to advanced animations and real-time data visualization!