Controlling an I2C Display

From Phidgets Support
Revision as of 15:03, 13 February 2026 by Mparadis (talk | contribs) (→‎Resources)


Introduction

The I2C Adapter Phidget allows you to interface with a wide range of I2C-compatible devices, including sensors, displays, and more.

In this project, we will use it to control a 14-segment alphanumeric display.

Steps

To complete this project, we will work through the following steps:

  1. Determine the device's basic configuration parameters
  2. Identify the sequence of commands expected by the device
  3. Test the device using the Phidget Control Panel
  4. Create a custom program.

Resources

Information from the following resources will be referenced throughout this project.

Demonstration

Configuration Parameters

Product page features
VK16K33 datasheet (Page 29)

During this step, we are looking for the following information:

  • Supply Voltage
  • Communication Frequency
  • Device Address

This information is typically found using the device's product page and/or any relevant datasheets. Based on these resources, we will use the following parameters:

  • Supply Voltage: 3.3V*
  • Communication Frequency: 400kHz
  • I2C Address: 0x70

*The datasheet specifies an operating voltage of 4.5-5V, however, this applies only when the chip is powered directly. The breakout board includes an onboard 3.3V to 5V boost circuit, which provides the required operating voltage for the chip.

Command Sequence

Next, we need to determine the sequence of commands the device is expecting. The VK16K33 datasheet provides flowcharts that we can use to create a list of actions:


Adp0002 an flow.png
Adapted from datasheet (page 26)


Based on the flowcharts, our list of actions should look like this:

Power On Steps:

  1. Enable the internal system clock
  2. Set the dimming register
  3. Set the blinking register

* The ROW/INT step is ignored because the feature is not being used.

Display Steps

  1. Set the write address
  2. Send the data to display
  3. Turn on the display

Writing to VK16K33 Registers

I2C devices typically expect the following format:

Adp0002 an typicalcomms3.png


The VK16K33 instead expects the following format, where the register address and data is combined into a single byte:

Adp0002 an VK16K33comms4.png

Power On Steps

Enable the System Clock

The system clock (oscillator) is managed by register 0x20:

I2C Sensor SysClock.png
VK16K33 datasheet (page 7)


This register only has one relevant bit, the S bit in position D8. By sending the command 0010 0001 (0x21 in hexadecimal), the system clock will be enabled. Bits marked with an X are irrelevant and can be set to either 0 or 1 with no impact.

Set Dimming Register

Dimming is managed by register 0xEF:

Adp0002 an dimmingreg.png
VK16K33 datasheet (page 25)


The register has four relevant bits, P0 through P3, in positions D8 through D11. These bits are used to set the duty cycle of the display, which allows for fine control of brightness. By sending the command 1110 1111 (0xEF in hexadecimal), maximum brightness is selected.

Set Blinking Register

The blinking frequency and display state is managed by register 0x80:

I2C Sensor Blink.png
Datasheet page 9


There are three relevant bits in this register: D, B0, and B1 in positions D8 through D10. By sending the command 1000 0001 (0x81 in hexadecimal), the display will be turned on, and blinking will be set to off.

Display Steps

Set the Write Address

The display RAM is 16 bytes and begins at address 0x00:

Adp0002 an ram.png
Datasheet page 10

Send Data to Display

The VK16K33 datasheet explains how the sent data translates into certain segments being activated. This is outside the scope of this project, so for the sake of simplicity, we will skip the conversion. A full code sample is provided later in this project and can be reviewed for more information.

Turn On Display

The display was previously turned on during the Power On Steps, so this step can be ignored.

Testing in the Phidget Control Panel

Now that we have the configuration parameters and the command sequence, we can use the Phidget Control Panel to confirm our device is working as expected.

Power On Steps

We can set the voltage to 3.3V, the frequency to 400kHz, and the address to 0x70. We can then queue up the commands we determined in the previous step.

I2C Sensor ControlPanel2.png

Since we have not sent any data to the RAM yet, the segments are uninitialized, with some on and some off:

I2C Sensor UnInit.png

Display Steps

To make the screen display PHID, we will first clear the screen with the following command:

  • 0x0000000000000000000000000000.

We can then send the following data:

  • 0x003D000B00CA000C000300C30003

I2C Multisend.png


We can now see the expected result:


I2C Sensor Phid.png

Reading Display RAM

This display also supports reading the RAM, so you can programmatically know what's currently showing on the display. For most devices, this is as simple as providing the proper command and specifying a number of expected reply bytes. In this case, the device is expecting a stop condition in between the command and the data, as shown by the 'P' in the third block in this diagram:

I2C Sensor Read.png
From page 23 of the datasheet


In order to replicate this in the control panel, we must send 0x00 first, and then send again with no data to listen for our reply bytes.

I2C Panel Read.png

Writing a Custom Python Script

To begin writing a custom program for your Phidget device, navigate to the product page and select the Code Samples tab. From there, you can customize the code for your own purposes. Here we have modified the code sample to match our steps from the Phidget Control Panel.

from Phidget22.Phidget import *
from Phidget22.Devices.DataAdapter import *

def main():


    clear = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]
    phid = [0x00,0x3D,0x00,0x0B,0x00,0xCA,0x00,0x0C,0x00,0x03,0x00,0xC3,0x00,0x03]


    adp = DataAdapter()
    adp.openWaitForAttachment(5000)


    # Set properties
    adp.setFrequency(DataAdapterFrequency.FREQUENCY_400kHz)
    adp.setDataAdapterVoltage(DataAdapterVoltage.DATAADAPTER_VOLTAGE_3_3V)
    addr = 0x70


    # Clear Screen
    adp.i2cSendReceive(addr, clear, 0)


    # Initialize display
    adp.i2cSendReceive(addr, [0x21], 0)
    adp.i2cSendReceive(addr, [0xEF], 0)
    adp.i2cSendReceive(addr, [0x81], 0)


    # Print PHID
    adp.i2cSendReceive(addr, phid, 0)


    try:
        input("Press Enter to Stop\n")
    except (Exception, KeyboardInterrupt):
        pass


    adp.close()


main()

That’s about it, we went from reading the datasheet to a working script. Not all datasheets are created equal, so if you’re struggling with getting your sensor set up, feel free to ask for help by sending us an email.

Full Script

Here is the full Python script that was used in the demonstration video. It will take input from the console and print scrolling messages on the display.

Expand All
Full Script (Click to expand)
from Phidget22.Phidget import *
from Phidget22.Devices.DataAdapter import *
import time

ram = ["0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000","0000"]

#   _ _ _ b _ _ _ 
# | \     |     / |
# |  c    e    g  |
# l   \   |   /   d
# |    \  |  /    |
# | _ n _ _ _ a _ |
# |    /  |  \    |
# j   /   |   \   f
# |  m    k    i  |
# | /     |     \ |
# | _ _ _ h _ _ _ |


letters = {
	#      abcdefghijklmn         abcdefghijklmn         abcdefghijklmn
	"A": 0b11010100010101, "N": 0b00110100110100, "0": 0b01010111010110,
	"B": 0b11011101001001, "O": 0b01010101010100, "1": 0b00010110000000,
	"C": 0b01000001010100, "P": 0b11010000010101, "2": 0b11010001010001,
	"D": 0b01011101001000, "Q": 0b01010101110100, "3": 0b11010101000001,
	"E": 0b11000001010101, "R": 0b11010000110101, "4": 0b10010100000101,
	"F": 0b11000000010101, "S": 0b11100101000000, "5": 0b11000101000101,
	"G": 0b11000101010100, "T": 0b01001000001000, "6": 0b11000101010101,
	"H": 0b10010100010101, "U": 0b00010101010100, "7": 0b01010100000000,
	"I": 0b01001001001000, "V": 0b00000010010110, "8": 0b11010101010101,
	"J": 0b00010101010000, "W": 0b00010100110110, "9": 0b11010100000101,
	"K": 0b00000010110101, "X": 0b00100010100010,
	"L": 0b00000001010100, "Y": 0b00100010001000,
	"M": 0b00110110010100, "Z": 0b01000011000010	
}

# loadRAM Reads all 13 bytes of of the screen memory into an array of 26 strings (for easier debugging)
# It should be loaded before overwriting a character, to preserve the other three.
def loadRAM(phid, addr):
	for i in range(13):
		byte = format(phid.i2cSendReceive(addr,[i],1)[0],'08b')
		ram[(i*2)] = byte[:4]
		ram[(i*2)+1] = byte[4:]

# printMessage prints a scrolling marquee messasge 'str'
def printMessage(str, phid, addr):
	# Add four spaces to ensure the message scrolls off
	str += "    "
	a0 = a1 = a2 = a3 = ""

	for a in str:
		a0 = a1
		a1 = a2
		a2 = a3
		a3 = a.upper()
		clearScreen(phid,addr)
		printLetter(a0, phid, addr, 0)
		printLetter(a1, phid, addr, 1)
		printLetter(a2, phid, addr, 2)
		printLetter(a3, phid, addr, 3)
		time.sleep(0.08)

# printLetter prints letter 'a' to position 'pos'
def printLetter(a, phid, addr,pos):

	# Confirm parameters are valid
	if a not in letters or (pos > 3) or (pos < 0):
		return
	
	# Get the segments required for the letter
	bits = [(letters[a] >> bit) & 1 for bit in range(13,-1,-1)]

	# For each segment, AND or OR it to the existing memory to preseve the other three digits
	# It had to be broken into two in order to skip over other bytes in the memory string.
	# The memory string is laid out like this: 00ab0:cd0.ef00gh00ij00kl00mn
	# Where ':' turns on the center colon and '.' turns on the decimal point when 0001 is written there.
	for i in range(7):
		if bits[2*i] == 0:
			tmp = int(ram[4*i], 2)
			tmp &= ~(1 << pos)
			ram[4*i] = f"{tmp:04b}"
		else:
			tmp = int(ram[4*i], 2)
			tmp |= (1 << pos)
			ram[4*i] = f"{tmp:04b}"

		if bits[(2*i)+1] == 0:
			tmp = int(ram[(4*i)+1], 2)
			tmp &= ~(1 << pos)
			ram[(4*i)+1] = f"{tmp:04b}"
		else:
			tmp = int(ram[(4*i)+1], 2)
			tmp |= (1 << pos)
			ram[(4*i)+1] = f"{tmp:04b}"

	hex = bytes(
		(int(ram[i], 2) << 4) | int(ram[i+1], 2)
		for i in range(0, 26, 2)
	)
	hex = list(hex)
	hex.insert(0,0x00) # add the leading 0x00 for writing
	phid.i2cSendReceive(addr, hex, 0)

# clearScreen clears the screen and the ram array
def clearScreen(phid,addr):
	phid.i2cSendReceive(addr, [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], 0)
	loadRAM(phid,addr)

# initialize sets the clock, brightness and blinking so the screen can operate
def initialize(phid,addr):
	phid.i2cSendReceive(addr, [0x21], 0)
	phid.i2cSendReceive(addr, [0xEF], 0)
	phid.i2cSendReceive(addr, [0x81], 0)

def main():

	# Declare and open dataAdapter channel
	adp = DataAdapter()
	adp.openWaitForAttachment(5000)

	# Set properties
	adp.setFrequency(DataAdapterFrequency.FREQUENCY_400kHz)
	adp.setDataAdapterVoltage(DataAdapterVoltage.DATAADAPTER_VOLTAGE_3_3V)
	addr = 0x70

	# Clear Screen
	clearScreen(adp,addr)

	# Initialize display
	initialize(adp,addr)

	print("Type a message to display, or 'exit' to quit.")

	message = ""

	while message != "exit":
		try:
			message = input()
			printMessage(message,adp,addr)
		except (Exception, KeyboardInterrupt):
			pass

	adp.close()

main()