I set up the Black Pill to drive a 1602 LCD with a PC8574T I2C backpack board.
I wrote a little MicroPython code for hardware I2C and... it didn't work. I beat my head against that wall for a long time. In the end, as far as I can find out from lots of online searching and trying a long series of code variations, it seems that the library for that is currently broken. They're working on it. Great.
EDIT: Nope. I was doing it wrong. After many hours of searching and futzing around I finally found enough info to get hardware I2C working. I was pretty close, but close doesn't count in programming. Rejigged my code to use it and it works perfect. And it's WAY faster than my bitbang code, though I'm sure that could be sped up some. Scroll down past the bitbang source to find my hardware I2C source.
In this photo I'm supplying separate 5V power to the LCD, rather than running it off the USB 5V as in the lower photo.
So I found an old C bitbang I2C source file that I'd done some years ago and ported it over. Not too terribly difficult. Had some weird bugs that took forever to find, but fixed all those and it works fine.
This pic shows the LCD running off the 5V from USB. It works ok, but if you run much else off the USB the Black Pill processor will brown out. Found that out the hard way with other hardware - constant crashes and errors until I supplied separate power for external stuff.
Bitbang I2C source code. Nothin fancy - just a simple demo. (Scroll way down for hardware I2C source)
# ghmicro.com
# 1602 LCD w/PC8574T I2C backpack - bitbang I2c
from pyb import Pin
import math
import time
CLK = Pin('B6',Pin.OUT) #set low when output
DIO = Pin('B7',Pin.OUT)
CLK.low()
DIO.low()
CLK = Pin('B6',Pin.IN) #idle high when input
DIO = Pin('B7',Pin.IN)
backlight = 0x08 # backlight on
tbuff = [1,2,3,4]
buff = [1,2,3,4]
#I2C functions
#*************
def start(): #claim the bus
DIO = Pin('B7',Pin.OUT) #dio low
time.sleep_us(10)
CLK = Pin('B6',Pin.OUT) #clk low
time.sleep_us(10)
def restart():
DIO = Pin('B7',Pin.IN) #release data
time.sleep_us(10)
CLK = Pin('B6',Pin.IN) #clk high
time.sleep_us(10)
DIO = Pin('B7',Pin.OUT) #dio low
time.sleep_us(10)
CLK = Pin('B6',Pin.OUT) #clk low
time.sleep_us(10)
def stop(): #release the bus
DIO = Pin('B7',Pin.OUT) #dio low
time.sleep_us(10)
CLK = Pin('B6',Pin.IN) #clk high
time.sleep_us(10)
DIO = Pin('B7',Pin.IN) #dio high
time.sleep_us(10)
def i2c_clock(): #cycle clock to latch a bit
CLK = Pin('B6',Pin.IN) #clock high
time.sleep_us(10) #wait a bit
CLK = Pin('B6',Pin.OUT) #clock low
time.sleep_us(10)
def byteout(dat): #clock out byte
time.sleep_us(10)
for x in range(8):
DIO = Pin('B7',Pin.OUT) #set dio low
if dat & 0x80: #but if output bit is high
DIO = Pin('B7',Pin.IN) #then set dio high
i2c_clock() #clock it out
dat = dat << 1 #shift next bit into position
DIO = Pin('B7',Pin.IN) #set dio to be input
DIO.low() #clear data bit
CLK = Pin('B6',Pin.IN) #clock high
time.sleep_us(5) #wait half a clock pulse
if DIO == True: #check the data bit
return 1 #nack error
time.sleep_us(5) #ack good, wait other half of clock pulse
CLK = Pin('B6',Pin.OUT) #set clock low
time.sleep_us(10)
DIO = Pin('B7',Pin.IN) #set dio high
return 0
# LCD functions
#**************
def lcd_string(buff):
pointer = 0
for ch in buff:
lcd_char(buff[pointer])
pointer += 1
def lcd_cmd(letter):
temp = letter
temp = temp & 0xf0 #clear lower nybble
temp = temp | 0b00000100 #set E, RW = write, RS = command
temp = temp | backlight
byteout(temp) #send upper nybble
temp = temp & 0b11110000 #clear E to latch data and resend
temp = temp | backlight
byteout(temp)
temp = letter
temp = temp & 0x0f #clear upper nybble
temp = temp << 4 #shift left 4 bits
temp = temp | 0b00000100 #send lower nybble
temp = temp | backlight
byteout(temp)
temp = temp & 0b11110000
temp = temp | backlight
byteout(temp)
def lcd_char(letter):
temp = ord(letter)
temp = temp & 0xf0 #clear lower nybble
temp = temp | 0b00000101 #set E, RW = write, RS = data
temp = temp | backlight
byteout(temp) #send upper nybble
temp = temp & 0b11110001 #clear E to latch data and resend
temp = temp | backlight
byteout(temp)
temp = ord(letter)
temp = temp & 0x0f #clear upper nybble
temp = temp << 4 #shift left 4 bits
temp = temp | 0b00000101 #send lower nybble
temp = temp | backlight
byteout(temp)
temp = temp & 0b11110001
temp = temp | backlight
byteout(temp)
def lcd_nybble(nyb):
nyb = nyb << 4
nyb = nyb | 0b00001100
byteout(nyb)
nyb = nyb & 0b11111000
byteout(nyb)
def lcd_init():
start()
byteout(0x4e) #send lcd slave address & write bit
pyb.delay(50)
lcd_nybble(0x03)
pyb.delay(5)
lcd_nybble(0x03)
pyb.delay(160)
lcd_nybble(0x03)
pyb.delay(160)
lcd_nybble(0x02)
pyb.delay(50)
lcd_cmd(0x28) #set 4-bit mode & 2 lines
pyb.delay(50)
lcd_cmd(0x10) #cursor move & shift left
pyb.delay(50)
lcd_cmd(0x06) #entry mode = increment
pyb.delay(50)
lcd_cmd(0b00001100) #display on - cursor & blink off
pyb.delay(50)
lcd_cmd(0x01) #clear display
stop()
pyb.delay(50)
#------------------------------------------------------
# Begin here
#------------------------------------------------------
lcd_init()
start()
byteout(0x4e) #send lcd slave address & write bit
lcd_cmd(0x80) #line 1
lcd_string('Black Pill')
lcd_cmd(0xc0) #line 2
for i in range(100000):
string = str("%06d" % i)
lcd_string(string)
lcd_cmd(0xc0) #go to start of line
pyb.delay(10)
stop()
Hardware I2C source code. Nothin fancy - just a simple demo.
# ghmicro.com
# 1602 LCD w/PC8574T I2C backpack - hardware I2c
import time, machine
from machine import I2C, Pin
i2c = machine.I2C(1)
backlight = 0x08 #backlight on
byt = bytearray(1)
# LCD functions
#**************
def lcd_string(buff):
pointer = 0
for ch in buff:
lcd_char(buff[pointer])
pointer += 1
def lcd_cmd(letter):
temp = letter
temp = temp & 0xf0 #clear lower nybble
temp = temp | 0b00000100 #set E, RW = write, RS = command
temp = temp | backlight
byt[0] = temp
i2c.writeto(0x27,byt) #send upper nybble
temp = temp & 0b11110000 #clear E to latch data and resend
temp = temp | backlight
byt[0] = temp
i2c.writeto(0x27,byt)
temp = letter
temp = temp & 0x0f #clear upper nybble
temp = temp << 4 #shift left 4 bits
temp = temp | 0b00000100 #send lower nybble
temp = temp | backlight
byt[0] = temp
i2c.writeto(0x27,byt)
temp = temp & 0b11110000
temp = temp | backlight
byt[0] = temp
i2c.writeto(0x27,byt)
def lcd_char(letter):
temp = ord(letter)
temp = temp & 0xf0 #clear lower nybble
temp = temp | 0b00000101 #set E, RW = write, RS = data
temp = temp | backlight
byt[0] = temp
i2c.writeto(0x27,byt) #send upper nybble
temp = temp & 0b11110001 #clear E to latch data and resend
temp = temp | backlight
byt[0] = temp
i2c.writeto(0x27,byt)
temp = ord(letter)
temp = temp & 0x0f #clear upper nybble
temp = temp << 4 #shift left 4 bits
temp = temp | 0b00000101 #send lower nybble
temp = temp | backlight
byt[0] = temp
i2c.writeto(0x27,byt)
temp = temp & 0b11110001
temp = temp | backlight
byt[0] = temp
i2c.writeto(0x27,byt)
def lcd_nybble(nyb):
nyb = nyb << 4
nyb = nyb | 0b00001100
byt[0] = nyb
i2c.writeto(0x27,byt)
nyb = nyb & 0b11111000
byt[0] = nyb
i2c.writeto(0x27,byt)
def lcd_init():
pyb.delay(50)
lcd_nybble(0x03)
pyb.delay(5)
lcd_nybble(0x03)
pyb.delay(160)
lcd_nybble(0x03)
pyb.delay(160)
lcd_nybble(0x02)
pyb.delay(50)
lcd_cmd(0x28) #set 4-bit mode & 2 lines
pyb.delay(50)
lcd_cmd(0x10) #cursor move & shift left
pyb.delay(50)
lcd_cmd(0x06) #entry mode = increment
pyb.delay(50)
lcd_cmd(0b00001100) #display on - cursor & blink off
pyb.delay(50)
lcd_cmd(0x01) #clear display
pyb.delay(50)
#------------------------------------------------------
# Begin here
#------------------------------------------------------
lcd_init()
lcd_cmd(0x80) #line 1
lcd_string('Black Pill')
lcd_cmd(0xc0) #line 2
while(1):
for i in range(1000000):
string = str("%06d" % i)
lcd_string(string)
lcd_cmd(0xc0) #go to start of line
pyb.delay(100)