4. DISPLAYS, I2C PROTOCOL

The source code of all examples can be downloaded from here.

 

Introduction

 

Visual output indicators are widely used in microprocessor systems to provide system information. They may consist of simple LEDs that are switched on continuously or are blinking/dimmed in different rhythms. LED arrays may be arranged in lines or in two dimensions such as 7-segment displays. Finally full alphanumeric and graphics screens are available in different formats. Except the most simple LED displays, the code to handle the display is not trivial and often put in some special software library, called a display driver.

It is recommended to add a simple optical indicator to every microcontroller system and we will emphasize here on 7-segment displays that are a good compromise between simplicity and versatility. More complicated alphanumeric displays will be treated later on.

A 7-segment display consists of 7 LEDs (+ eventually a decimal point). So an array of 4 displays needs 28 digital output ports, too many for the Raspberry Pi GPIO. There are different options to reduce the amount of data needed to drive the display.

Time multiplexing:
One display is driven after the other in a round-robin cycle

  If each display is turned on at least 25 times/s, for the human eye all 4 displays appear to be continuously turned on.

Encoding:
The 7 segments + the decimal point may be addressed by a 8 bit number (in the decimal range of 0..255). With other words, each segment has a dual weight 0, 1, 2, 4, 8, 16, 32, 64 and the decimal point 128. The standard coding is the following:

  Examples:


 Character  Code
 A  1 + 2 + 4 + 16 + 32 + 64 = 119
 b  4 + 8 + 16 + 32 + 64 = 124
 1. (dp)  2 + 4 + 128 = 134

With this encoding, the information for displaying the 4 digits (including 4 decimal points) is 4 bytes. (If you want to know more about the character mapping and which ASCII characters can be displayed, download the mapping sheet.)

Serial data communication:
To transfer the 4 bytes information to the display array, a serial communication protocol is used. for most microprocessor systems either the I2C (Inter-Integrated Circuit, mostly pronounced "i-squared-c") or the SPI (Serial Peripheral Interface) protocol. Both protocol are supported via the Raspberry Pi GPIO port.

The I2C protocol requires only 3 wires: GND, SDA (serial data), SCL (serial clock). The bus master (normally the microprocessor) sends serialized bits over the SDA line. SCL is used to synchronize the master and slave devices. Each slave is identified by its I2C address that is 7 bit wide (0x00 to 0x7F).

After you attached an I2C device to the GPIO, you may detect its address by calling i2cdetect -y 1 from a Linux terminal (use -y 0 for old Raspberry Pi Revision A). Sometimes the 8th bit with value 0 is considered to be part of the address. Because it is the LSB, the 8 bit address range is 0x00 to 0xFF with even steps. Example:

7 bit address: 0x38 = 0111000 -> 8 bit address = 01110000 = 0x70. So every time you use an I2C address, inform you if it is a 7 or 8 bit address.

In Python, most of the time the SMBus-library is used where device addresses defined as 7 bit addresses. A function summery is given here.

A simple device detection program in Python sends a smbus read_byte() command. If a exception is thrown, the device is at the given address in not present.

Program to discover I2C devices :[►]

# I2CDetect.py

import smbus

def getI2CDevices():
    bus = smbus.SMBus(1)
    addresses = [] 
    for address in range(128):
        try:
            bus.read_byte(address)
            addresses.append(address)
        except:
            pass
    return addresses

print "Found I2C devices at:"
devices = getI2CDevices()
for address in devices:
    print("0x" + format(address, "02X"))
Highlight program code (Ctrl+C copy, Ctrl+V paste)

 

 

Experiment 1: Using the Didel PyTell display

 

Source of supply: PyTell display (http://www.didel.com).

A set of demonstration examples and the PyTell driver library can be downloaded from here.

PyTell is a low power miniature 4 digit display that works from 3V (@20mA) to 5.5V (@30mA). The devices uses a very simple data protocol over the I2C bus and its own PIC microcontroller dispatches the incoming 4 bytes. So the GPIO bus is completely freed after the data is transferred.

Aim:
Become acquainted with the I2C protocol and display some text/numbers.

Circuitry:

 

You may connect the display using the 4 wires Grove connector or provide a special break-out connector available from Didel. (The 5V line is not part of the I2C bus, it is used to drive the device.)

Didel produces an interesting breakout board called RaspEasy, where the display may simply be plugged in.

Program to display "0123":[►]

# Display1a.py
# Write 4 bytes (integers)

import smbus

i2c_address = 0x20
bus = smbus.SMBus(1)  # RPi revision 2 (0 for revision 1)
cmd = 1 # segment mode
data = [63, 6, 91, 79] # 0123
bus.write_block_data(i2c_address, cmd, data)
Highlight program code(Ctrl+C copy, Ctrl+V paste)

Remarks:
The I2C address of the display is 0x20. We only use segment mode (cmd = 1) here, where data is transferred as a list of 4 integers, corresponding to the 4 bytes encoded as explained above.

Program to display "run", then counting up numbers to 1000 and "donE"

Program: [►]

# Display1b.py

import smbus
import time

PATTERN = {' ': 0, '!': 134, '"': 34, '#': 0, '$': 0, '%': 0, 
'&': 0, '\'': 2, '(': 0, ')': 0, '*': 0, '+': 0, ',': 4, '-': 64, 
'.': 128, '/': 82, '0': 63, '1': 6, '2': 91, '3': 79, '4': 102,
'5': 109, '6': 125, '7': 7, '8': 127, '9': 111, ':': 0, ';': 0,
'<': 0, '=': 72,'>': 0, '?': 0,'@': 93, 'A': 119,'B': 124,'C': 57,
'D': 94, 'E': 121, 'F': 113, 'G': 61, 'H': 118, 'I': 48, 'J': 14,
'K': 112, 'L': 56, 'M': 85, 'N': 84, 'O': 63, 'P': 115, 'Q': 103,
'R': 80, 'S': 45, 'T': 120, 'U': 62, 'V': 54, 'W': 106, 'X': 73,
'Y': 110, 'Z': 27, '[': 57, '\\': 100, ']': 15, '^': 35, '_': 8,
'`': 32, 'a': 119, 'b': 124, 'c': 88, 'd': 94, 'e': 121, 'f': 113,
'g': 61, 'h': 116, 'i': 16, 'j': 12, 'k': 112, 'l': 48, 'm': 85,
'n': 84, 'o': 92, 'p': 115, 'q': 103, 'r': 80, 's': 45, 't': 120,
'u': 28, 'v': 54, 'w': 106, 'x': 73, 'y': 110, 'z': 27, '{': 0,
'|': 48, '}': 0, '~': 65}


def toSegment(text):
   data = []
   for c in text:
       data.append(PATTERN[c])
   return data

i2c_address = 0x20
bus = smbus.SMBus(1)  # RPi revision 2 (0 for revision 1)
cmd = 1 # segment mode
text = "run "
data = toSegment(text)
bus.write_block_data(i2c_address, cmd, data)
time.sleep(3)
for i in range(1001):
    text = "%4d" %i # right adjusted
    data = toSegment(text)
    bus.write_block_data(i2c_address, cmd, data)
    time.sleep(0.01)
time.sleep(3)
text = "donE"
data = toSegment(text)
bus.write_block_data(i2c_address, cmd, data)
Highlight program code(Ctrl+C copy, Ctrl+V paste)

Remarks:
It is a nice programming challenge to write a little display driver that also supports word scrolling, text tickers and blinking text. Download the module pytell.py (and some useful demonstration examples) from here and copy it into the directory where your programs reside. You may then explore the nice display features.

Program to show scrolling and blinking text. [►]

# Display1c.py

from pytell import PyTell
import time

pt = PyTell()
pt.showText("run")
time.sleep(3)

text = "HELLO Python"
pt.showTicker(text, count = 2, speed = 4, blocking = True)

text = "8ye"
pt.showBlinker(text, dp = [0, 0, 0, 1], count = 4, speed = 2, blocking = True)
Highlight program code(Ctrl+C copy, Ctrl+V paste)

Remarks:
The Python documentation for the pytell module is shown here. If you want to know more about the character mapping and which ASCII characters can be displayed, download the mapping sheet.

 

 

Experiment 2: Using the 4tronix Pi2Go display

 

Source of supply: IP Display Dongle/7-Segment Display (http://www.4tronix.co.uk/store)

This 4 digit 7-segment array is mainly used to display the IP address when the Pi2Go robot attaches to a WLAN hotspot. But it can also be used as general purpose display without the Pi2Go robot. Like the PyTell display the I2C protocol is used to transfer data, but it uses the MCP23017 16-bit I/O Expander chip to provide the necessary number of digital output lines to select and drive one of the digits. To show all 4 digits at the same time, multiplexing is needed.

Aim:
Display one digit using the I2C protocol.

Circuitry:

 

You may connect the display with 4 wires directly or through a breadboard to the GPIO. A special break-out connector is available from 4tronix.

Like many other I2C interfaces, the MCP23017 uses a control register to select the mode of operation. It is always a cumbersome work to investigate how to use it. Without detailed explanation we show in the programs how to setup the display using SMBus.

Program displays one character after the other: [►]

# Display2a.py

from smbus import SMBus
import time

def setup():
    bus.write_byte_data(i2c_address, 0x00, 0x00) 
        # Set all of bank 0 to outputs
    bus.write_byte_data(i2c_address, 0x01, 0x00) 
        # Set all of bank 1 to outputs

def setValue(val, digit):
    '''
    @param digit: 0: leftmost digit
    @param val: 0..255: segment value
    '''
    n = (1 << (3 - digit)) ^ 255
    bus.write_byte_data(i2c_address, 0x13, n)
    bus.write_byte_data(i2c_address, 0x12, val)

i2c_address = 0x20

bus = SMBus(1) # For revision 2 Raspberry Pi
setup()
word = [118, 119, 56, 63]  # HALO
for i in range(4):
    setValue(word[i], i)
    time.sleep(2)
Highlight program code(Ctrl+C copy, Ctrl+V paste)

Remarks:
It is a good idea to structure the program by using functions. When the program terminates, the last display remains turned on. To display all 4 characters at the same time, you must display and clear every digit in a rapid sequence.

setup()
word = [118, 119, 56, 63]  # HALO
while True:
    for i in range(4):
        setValue(word[i], i)
        time.sleep(0.002)
        setValue(0, i)
        time.sleep(0.002)

This is quite a heavy burden for the processor, so using a on-board microcontroller like in the PyTell display is much better. Again it is a programming challenge to write a display driver that handles the multiplexing internally and allows text scrolling and blinking.

Download the module Disp4tronix.py (and some useful demonstration examples) from here and copy it into the directory where your programs reside. You may then explore the nice display features.

Program to show scrolling and blinking text. [►]

# Display2c.py

from Disp4tronix import Disp4tronix
import time

dp = Disp4tronix()
dp.showText("run")
time.sleep(3)

text = "HELLO Python"
dp.showTicker(text, count = 2, speed = 4, blocking = True)

text = "8ye"
dp.showBlinker(text, dp = [0, 0, 0, 1], count = 4, speed = 2, blocking = True)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
The Python documentation for the Disp4tronix module is shown here.

 

 

Experiment 3: Using your home-brew 4 digit 7-segment display

 

Source of supply: 7-Segment-Anzeige, order # 105697 (http://www.elv.de)

You can also build the display from scratch using the SAA1064 4-digit LED-driver with I2C-bus interface. Schematics are found by an Internet search. Unfortunately the SAA1064 IC in DIL and SMD package is no longer fabricated. But you find many suppliers on Ebay or you may purchase a complete display kit including a 4 button panel, a I2C temperature module and several types of front panels from ELV Germany.

 
The ELV board (can easily be cut in 3 pieces)   A nice looking front panel

Aim:
Build your own 7-segment display using a standard LED driver with I2C-bus interface.

Circuitry:
You find many schematics on the Internet that show the typical wiring.

The address selections is performed by a voltage divider that delivers the voltage of the ADR input as follows:

ADR voltage Address (7-bit)
GND 0x38
3/8 VCC 0x39
3/8 VCC 0x3A
VCC 0x3B

So if the ADR pin is set to GND, the 7-bit address 0x38 is selected. The two extra transistors can be any standard NPN-type (e.g. BC547, 2N2222, etc.)

The display may be driven from the 5V supply of the Raspberry Pi (GPIO pin # 2). The SDA and SCL lines are connected directly to the GPIO #3 and #5 with no harm, even if the display is powered with 5 V (no level shifter or pull-up resistor needed).

The ELV panel that combines the 4-digit 7-segment display and 4 push buttons is well-designed to be used in a home-automation project with the Raspberry Pi as controller unit.

In principle, programming the display is straight-forward, but somewhat tricky when using the SMBus library. The following examples provide the know-how for you.

Program to display "0123":[►]

# Display3a.py

import smbus
import time

i2c_address = 0x38
bus = smbus.SMBus(1)  # RPi revision 2 (0 for revision 1)

def setup():
    instruction_byte = 0x00
    control_byte = 0b00010111 
    # b0 = 1: dynamic mode (autoincrement digits)  
    # b1 = 1: digit 1/3 not blanked, b2 = 1: digit 2/4 not blanked
    # b4 = 1: 3 mA segment current 
    # write to control register
    bus.write_byte_data(i2c_address, instruction_byte, control_byte)

def clear():
    bus.write_i2c_block_data(i2c_address, 1, [0, 0, 0, 0])
   
setup()
data = [63, 6, 91, 79] # 0123
# write starting from digit 1
bus.write_i2c_block_data(i2c_address, 1, data)
time.sleep(5)
clear()
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
The SAA1064 display driver has a single I2C address and uses the instruction byte to select the control or data registers. For more details consult the datasheet.

Program to display "run", then counting up numbers to 1000 and "donE"[►]

# Display3b.py

import smbus
import time

PATTERN = {' ': 0, '!': 134, '"': 34, '#': 0, '$': 0, '%': 0, '&': 0,
'\'': 2, '(': 0, ')': 0, '*': 0, '+': 0, ',': 4, '-': 64, '.': 128,
'/': 82, '0': 63, '1': 6, '2': 91, '3': 79,'4': 102, '5': 109, '6': 125,
'7': 7, '8': 127, '9': 111, ':': 0, ';': 0, '<': 0, '=': 72, '>': 0,
'?': 0, '@': 93, 'A': 119, 'B': 124, 'C': 57, 'D': 94, 'E': 121,'F': 113,
'G': 61, 'H': 118, 'I': 48, 'J': 14, 'K': 112, 'L': 56, 'M': 85, 'N': 84,
'O': 63, 'P': 115, 'Q': 103, 'R': 80, 'S': 45, 'T': 120, 'U': 62, 'V': 54,
'W': 106, 'X': 73, 'Y': 110, 'Z': 27, '[': 57, '\\': 100, ']': 15,
'^': 35, '_': 8, '`': 32, 'a': 119, 'b': 124, 'c': 88, 'd': 94, 'e': 121,
'f': 113, 'g': 61, 'h': 116, 'i': 16, 'j': 12, 'k': 112, 'l': 48, 'm': 85,
'n': 84, 'o': 92, 'p': 115, 'q': 103, 'r': 80, 's': 45, 't': 120, 'u': 28,
'v': 54, 'w': 106, 'x': 73, 'y': 110, 'z': 27, '{': 0, '|': 48, '}': 0,
'~': 65}

i2c_address = 0x38
bus = smbus.SMBus(1)

def setup():
    instruction_byte = 0x00
    control_byte = 0b00010111 
    # b0 = 1: dynamic mode (autoincrement digits)  
    # b1 = 1: digit 1/3 not blanked, b2 = 1: digit 2/4 not blanked
    # b4 = 1: 3 mA segment current 
    # write to control register
    bus.write_byte_data(i2c_address, instruction_byte, control_byte)

def toSegment(text):
   data = []
   for c in text:
       data.append(PATTERN[c])
   return data

setup()
text = "run "
data = toSegment(text)
bus.write_i2c_block_data(i2c_address, 1, data)
time.sleep(3)
for i in range(1001):
    text = "%4d" %i # right adjusted
    data = toSegment(text)
    bus.write_i2c_block_data(i2c_address, 1, data)
    time.sleep(0.01)
time.sleep(3)
text = "donE"
data = toSegment(text)
bus.write_i2c_block_data(i2c_address, 1, data)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
Programming the display becomes delightful if you use our py7seg.py device driver. Download the module py7seg.py (and some instructive demonstration examples) from here and copy it into the directory where your programs reside. You may then explore the nice display features. The Python documentation for the py7seg module is shown here.

Program to show scrolling and blinking text. [►]

# Display3c.py

from py7seg import Py7Seg
import time

ps = Py7Seg()
ps.setBrightness(7)
ps.showText("run")
time.sleep(3)

text = "HELLO Python"
ps.showTicker(text, count = 2, speed = 4, blocking = True)

text = "8ye"
ps.showBlinker(text, dp = [0, 0, 0, 1], count = 4, speed = 2, blocking = True)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

 

 

Experiment 4: Using an alphanumeric/graphics display

 
Sources of supply:

Monochrom 1.3" 128x64 OLED graphic display (Adafruit)

  Linker Kit OLED-Display (ELV and others)
  SSD1306/OLED 128x64 (Ebay search)

Compared to a simple 4-digit display an alphanumeric or even a pixel-based graphics display opens a new dimension for the user interaction. In the field of embedded systems OLED displays are more and more used because of their high contrast ratio and low power consumption.

display6
Cheap display units are available based on the SSD1306 controller and driven by I2C or SPI. With I2C only 4 wires are necessary to connect the display with the Raspberry Pi. The SSD1306 controller is designed to drive 128x64 black&white OLED displays, but 128x32 pixels OLEDs are also supported. Since each pixel is individually addressable, text and graphics may be displayed. Any true type text font (TTF) is supported.

As you may expect, the communication with the SSD1306 controller is somewhat laborious. Fortunately Tony DiCola from Adafruit wrote a Python driver that handles all low-level (register based) tasks. Because it uses a GPIO port to reset the display that is not needed for most devices, we modified it slightly and distribute the driver as SSD1306.py. In addition we added a thin software layer OLED1306.py to simplifiy the use of the OLED display to a maximum. Both modules (and some text fonts, example scripts and pictures) can be downloaded from here.

Program to show a single line: [►]

# Display4a.py

import RPi.GPIO as GPIO

from OLED1306 import OLED1306

disp = OLED1306()
disp.setText("Hello Python")
Highlight program code (Ctrl+C copy, Ctrl+V paste)

To display a graphics, the image file should have PPM format with a 128x64 bitmap (B/W) size. You may edit the picture using Photoshop. Use and modify one of the example pictures to get acquainted to the special format requirements.

Program to show a picture /home/pi/Pictures/einstein.ppm mixed with text:[►]

# Display4b.py

from OLED1306 import OLED1306

disp = OLED1306("/home/pi/Pictures/einstein.ppm")
disp.setText("God\ndoes not\nplay dice!", 0, 15, 60) 
# Parameters: text, line number, font size, indent (pixels)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

The OLED1306 module also supports console-like text scrolling.

Program to show automatic line scrolling:[►]

# Display4c.py

from OLED1306 import OLED1306
import time

disp = OLED1306()
nb = 1
while True:
    disp.println("Line #" + str(nb))
    nb += 1
    time.sleep(0.5)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

To get information about all features of the class OLED1306 consult the Python documentation. Be aware that as default the OpenSans font family is used that is assumed to be located in /home/pi/Fonts.

 

 

Experiment 5: Using the cheap TM1637 4-digit display

 
Sources of supply:

Grove 4-Digit Display (Seed)

  Search for TM1637 (Ebay)


The display unit uses the driver module TM1637 and a 4-digit seven-segment array with a colon in the middle. In addition to the power leads for 5V and GND, only 2 lines clock (CLK) and data (DIO) are required.

A special protocol is used (similar, but not compatible with I2C). There are many sources of supply (Arduino / Raspberry Pi suppliers, Grove, eBay, the price varies between $ 1 and $ 10).

display7


The TM1637 is a 5V device and therefore should be powered by the 5V pin of the GPIO. The lines CLK and DIO are connected to any two GPIO pins (defaults: CLK GPIO # 38, DIO GPIO # 40). Because of the proprietary protocol, a small Python module TM1637.py was written, which can be downloaded from here along with some test programs. (It is also included in the Python path of the RaspiBrick firmware.) The display is abstracted as a class FourDigit, which has similar methods and the same mapping of ASCII characters as the devices described above.

For more information about the FourDigit class, consult the Python Doc.

Program: Perpetual counter 0..9999 [►]

from TM1637 import FourDigit
        
d = FourDigit()

while True:
    for n in range(10000):
        d.show(n)

Highlight program code (Ctrl+C copy, Ctrl+V paste)

It's very easy to display a scrolling text. The methods toLeft() and toRight() provides more more freedom than scroll(), which blocks until the text is completely displayed.

Program: Scroll text (endless) [►]

from TM1637 import FourDigit
        
d = FourDigit()
while True:
    d.scroll("HELLo PYthon")   

Highlight program code (Ctrl+C copy, Ctrl+V paste)

As a useful application, the display shows the current time (hours/minutes) with the colon flashing every second. The current time is delivered by the real-time clock DS1307, as described in the chapter Timers. There you also find how to preset the clock.

Program: Digital clock [►]

from RTC_DS1307 import RTC
from time import sleep
from TM1637 import FourDigit
        
d = FourDigit()
rtc = RTC()
showColon = True
while True:
    hr = rtc.getHours()
    min = rtc.getMinutes()
    d.show("%02d%02d" %(hr, min))
    d.setColon(showColon)
    showColon = not showColon
    sleep(0.5)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

In order to display leading zeros for 1-digit hours and minutes, the string formatter %02d is used.