Obsługa ekspandera I2C PCF8574 + HD44870

Moderator: bbiernat

Post Reply
User avatar
pancio
Administrator
Posts: 63
Joined: 18 September 2013, 23:02 - Wed
Location: SILESIA

Obsługa ekspandera I2C PCF8574 + HD44870

Post by pancio »

Poniższy tutorial prezentuje możliwość sterowania układami z wykorzystaniem popularnego ekspandera PCF8574. Temat przedstawia kolejne możliwe sposoby sterowania urządzeń z wykorzystaniem magistrali I2C, w tym wyświetlacza z kontrolerem HD44780 i jest rozszerzeniem artykułu GPIO w Cubietruck z poziomu Pythona. Powinieneś zapoznać się z częścią tego artykułu dotyczącą konfiguracji jądra systemu i pinów GPIO.

Założenia:
- Cubietruck
- DVK570 (niewymagana, jednak jeśli nie masz kitu - musisz sam zadbać o odpowiednie parametry prądowe np dla diod LED)
- CTdebian 4.1 (do pobrania ze strony Igora Pecovnik-a lub z działu Wsady)

Przestroga 1.
Wszystkie operacje wykonuję jako uprzywilejowany użytkownik root . Nie jest to dobra praktyka więc proponuję używać sudo i wykonywać wszystkie komendy z poziomu normalnego użytkownika...

Uwaga.
Dystrybucja, której używam to kompilacja ze źródeł CTDebian Igora Pecovnik-a. Możesz pobrać najnowszą wersję ze strony Igora lub skorzystać z mojej kompilacji.

Przestroga 2
Wszystko co robisz - robisz na własną odpowiedzialność. W trakcie tutoriala wykonywane są czynności, które mogą uszkodzić twoje urządzenie...

Z czego korzystamy:
Ekspander I2C 8bit, adres 0x38
Ekspander I2C 8bit, adres 0x38
Listwa LED
Listwa LED
Podstawowe sterowanie - odczyt i zapis z PCF8574

Zapiszmy wszelkie możliwe wartości do naszego ekspandera (write.py):

Code: Select all

import smbus
import time
 
bus = smbus.SMBus(1)

bus.write_byte(0x38,0x00)
while(1):
	for a in range(0,256):
		bus.write_byte(0x38,a)
		time.sleep(0.5)

i odczytajmy (read.py) np w drugim oknie konsoli:

Code: Select all

import smbus
 
I2C_ADDRESS = 0x38
 
bus = smbus.SMBus(1)
 
while(1):
	#Read all the unput lines
	value=bus.read_byte(I2C_ADDRESS)
	print "%02X" % value
Połączone, uruchomione...
Połączone, uruchomione...
Odczyt wartości z PCF8574
Odczyt wartości z PCF8574

Dodajmy obsługę wyświetlacza HD44780 poprzez nasz ekspander...
4 liniowy LCD z zabudowanym ekspanderem PCF8547
4 liniowy LCD z zabudowanym ekspanderem PCF8547
Kilka słów wyjaśnienia, otóż PCF8574 nie wymaga napięcia 5V ale jest ono wymagane do zasilania naszego wyświetlacza... egzemplarz przedstawiony na zdjęciu działa przy zasilaniu 3V3 lecz uzyskany kontrast jest mniej niż zadowalający... to samo tyczy się podświetlenia. Dlatego zdecydowałem się zastosować dwukanałowy, dwukierunkowy konwerter napięć 3V3<->5V. Można je nabyć na eBay-u lub zbudować samemu. Podobnie sprawa się ma z gotowymi ekspanderami, można je nabyć w serwisie aukcyjnym osobno lub już zintegrowane z wyświetlaczem. Należy zwrócić uwagę na sposób połączenia PCF8574 z wyświetlaczem (jest kilka wersji). Mając notę katalogową PCF-a i rozpiskę złącza wyświetlacza jesteśmy w stanie 'wykminić' co autor danego rozwiązania miał na myśli. Przedstawiony poniżej program/biblioteka pozwala na sterowanie wyświetlaczem z dowolnie podpiętym PCF-em po drobnych modyfikacjach.

Sprawdźmy pod jakim adresem nasz ekspander jest dostępny...
i2detect -y 1 - 0x27 to adres naszego ekspandera
i2detect -y 1 - 0x27 to adres naszego ekspandera
Stwórz i zapisz plik hd44870_lib.py:

Code: Select all

#!/usr/bin/python
#--------------------------------------
#           hd44870_lib.py
#  LCD script using I2C based on PCF8574.
#  Supports 16x2 and 20x4 screens.
#
# Author : based on Matt's Hawkins work
# Date   : 20/09/2015#
# http://www.raspberrypi-spy.co.uk/
#
# Cubieboard version : Blazej Biernat
#
# The wiring for the LCD is as follows:
# 1 : GND
# 2 : 5V
# 3 : Contrast (0-5V)*
# 4 : RS (Register Select)
# 5 : R/W (Read Write)       - GROUND THIS PIN
# 6 : Enable or Strobe
# 7 : Data Bit 0             - NOT USED
# 8 : Data Bit 1             - NOT USED
# 9 : Data Bit 2             - NOT USED
# 10: Data Bit 3             - NOT USED
# 11: Data Bit 4
# 12: Data Bit 5
# 13: Data Bit 6
# 14: Data Bit 7
# 15: LCD Backlight +5V**
# 16: LCD Backlight GND
#--------------------------------------
import smbus
from itertools import cycle
import time

backlite = 1

bus = smbus.SMBus(1)
I2C_ADDR	=	0x27

#LCD_SIG = PORT(BIT)
LCD_RS	=	0
LCD_RW	=	1
LCD_E	=	2
LCD_BL	=	3
LCD_D4	=	4
LCD_D5	=	5
LCD_D6	=	6
LCD_D7	=	7

# Define some device constants
LCD_WIDTH = 20		# Maximum characters per line
LCD_CHR = True
LCD_CMD = False

# Some commands
LCD_CLRSCR = 0x01
 
LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xc0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 1st line
LCD_LINE_4 = 0xd4 # LCD RAM address for the 2nd line
 
# Timing constants
INIT_DELAY = 0.00100
E_PULSE = 0.00005
E_DELAY = 0.00005

def lcd_init():
	#read actual PCF8574 variable
	i2c_value = bus.read_byte(I2C_ADDR)
	
	i2c_value = clearBit(i2c_value,LCD_RW)
	bus.write_byte(I2C_ADDR,i2c_value)
	
	# Initialise display sequense
	lcd_byte(0x03,LCD_CMD)
	time.sleep(INIT_DELAY)		
	lcd_byte(0x03,LCD_CMD)
	time.sleep(INIT_DELAY)	
	lcd_byte(0x03,LCD_CMD)
	time.sleep(INIT_DELAY)	
	lcd_byte(0x02,LCD_CMD)
	time.sleep(INIT_DELAY)	
	lcd_byte(0x02,LCD_CMD)
	time.sleep(E_DELAY)	
	lcd_byte(0x08,LCD_CMD)
	time.sleep(E_DELAY)	
	lcd_byte(0x01,LCD_CMD)
	time.sleep(E_DELAY/2)	
	lcd_byte(0x06,LCD_CMD)	
	time.sleep(E_DELAY)
	
	lcd_byte(0x0c,LCD_CMD)	

def lcd_clean():
	lcd_byte(LCD_CLRSCR, LCD_CMD)
	
def lcd_backlite(mode):
	i2c_value = bus.read_byte(I2C_ADDR)
	
	if (mode):
		i2c_value = setBit(i2c_value,LCD_BL)
	else:
		i2c_value = clearBit(i2c_value,LCD_BL)
	bus.write_byte(I2C_ADDR,i2c_value)

def lcd_write_lines(msg1, msg2):
	if(msg1)<>"":
		lcd_byte(LCD_LINE_1, LCD_CMD)
		lcd_string(msg1)

	if(msg2)<>"":
		lcd_byte(LCD_LINE_2, LCD_CMD)
		lcd_string(msg2)

def lcd_write_4lines(msg1, msg2, msg3, msg4):
	if(msg1)<>"":
		lcd_byte(LCD_LINE_1, LCD_CMD)
		lcd_string(msg1)

	if(msg2)<>"":
		lcd_byte(LCD_LINE_2, LCD_CMD)
		lcd_string(msg2)

	if(msg3)<>"":
		lcd_byte(LCD_LINE_3, LCD_CMD)
		lcd_string(msg3)

	if(msg4)<>"":
		lcd_byte(LCD_LINE_4, LCD_CMD)
		lcd_string(msg4)

			
def lcd_string(message):
	# Send string to display 
	message = message.ljust(LCD_WIDTH," ") 
 
	for i in range(LCD_WIDTH):
		lcd_byte(ord(message[i]),LCD_CHR)
 
def lcd_byte(bits, mode):
	global backlite
	
	# Send byte to PCF8574 data register 
	# bits = data
	# mode = True for character
	# mode = False for command
	# i2c_value = actually  data from PCF8574
	
	i2c_value=bus.read_byte(I2C_ADDR)	
	
	if (mode):
		i2c_value = setBit(i2c_value,LCD_RS)
	else:
		i2c_value = clearBit(i2c_value,LCD_RS)
		
	if (backlite):
		i2c_value = setBit(i2c_value,LCD_BL)
				
	bus.write_byte(I2C_ADDR,i2c_value)	

	# High bits
	i2c_value = clearBit(i2c_value,LCD_D4)
	i2c_value = clearBit(i2c_value,LCD_D5)
	i2c_value = clearBit(i2c_value,LCD_D6)
	i2c_value = clearBit(i2c_value,LCD_D7)

	if bits&0x10==0x10:
		i2c_value = setBit(i2c_value,LCD_D4)
	if bits&0x20==0x20:
		i2c_value = setBit(i2c_value,LCD_D5)
	if bits&0x40==0x40:
		i2c_value = setBit(i2c_value,LCD_D6)
	if bits&0x80==0x80:
		i2c_value = setBit(i2c_value,LCD_D7)

	bus.write_byte(I2C_ADDR,i2c_value)
	
	# Toggle 'Enable' pin
	time.sleep(E_DELAY)
	
	i2c_value = setBit(i2c_value,LCD_E)
	bus.write_byte(I2C_ADDR,i2c_value)
	time.sleep(E_PULSE)
	
	i2c_value = clearBit(i2c_value,LCD_E)
	bus.write_byte(I2C_ADDR,i2c_value)
	time.sleep(E_DELAY)		
	
	# Low bits
 	i2c_value = clearBit(i2c_value,LCD_D4)
	i2c_value = clearBit(i2c_value,LCD_D5)
	i2c_value = clearBit(i2c_value,LCD_D6)
	i2c_value = clearBit(i2c_value,LCD_D7)

	if bits&0x01==0x01:
		i2c_value = setBit(i2c_value,LCD_D4)
	if bits&0x02==0x02:
		i2c_value = setBit(i2c_value,LCD_D5)
	if bits&0x04==0x04:
		i2c_value = setBit(i2c_value,LCD_D6)
	if bits&0x08==0x08:
		i2c_value = setBit(i2c_value,LCD_D7)

	bus.write_byte(I2C_ADDR,i2c_value)
	
	# Toggle 'Enable' pin
	time.sleep(E_DELAY)
	
	i2c_value = setBit(i2c_value,LCD_E)
	bus.write_byte(I2C_ADDR,i2c_value)
	time.sleep(E_PULSE)
	
	i2c_value = clearBit(i2c_value,LCD_E)
	bus.write_byte(I2C_ADDR,i2c_value)
	time.sleep(E_DELAY)	
		
# testBit() returns a nonzero result, 2**offset, if the bit at 'offset' is one.
def testBit(int_type, offset):
	mask = 1 << offset
	return(int_type & mask)

# setBit() returns an integer with the bit at 'offset' set to 1.
def setBit(int_type, offset):
	mask = 1 << offset
	return(int_type | mask)

# clearBit() returns an integer with the bit at 'offset' cleared.
def clearBit(int_type, offset):
	mask = ~(1 << offset)
	return(int_type & mask)

# toggleBit() returns an integer with the bit at 'offset' inverted, 0 -> 1 and 1 -> 0.
def toggleBit(int_type, offset):
	mask = 1 << offset
	return(int_type ^ mask)

def lcd_log2file(fp,line):
	pattern = "[LCD]: "
	logfile = open(fp, "a+")
	line = str(ticks) + '|' + pattern + line +'\n'
	logfile.write(line)
	logfile.close()	

Czas wysłać coś na wyświetlacz (lcd-test.py):

Code: Select all

#!/usr/bin/python
import smbus
import time
import hd44870_lib as lcd

lcd.lcd_init()
lcd.lcd_clean()

while(1):
	backlite = 1

	lcd.lcd_write_lines("test1","test2")
	#lcd.lcd_backlite(1)	
	time.sleep(1)	
	lcd.lcd_write_lines("test2","test1")
	#lcd.lcd_backlite(1)	
	time.sleep(1)

	lcd.lcd_write_lines("test1","test2")	
	time.sleep(1)	
	lcd.lcd_write_lines("test2","test1")
	time.sleep(1)
I oczekiwany efekt:
lcd_test.py
lcd_test.py
Attachments
44780.pdf
(278.43 KiB) Downloaded 1370 times
PCF8574.pdf
(132.53 KiB) Downloaded 1415 times

pdrobek
Posts: 4
Joined: 04 February 2016, 13:27 - Thu

Re: Obsługa ekspandera I2C PCF8574 + HD44870

Post by pdrobek »

Była by opcja abyś dokładniej opisał pracę z twoim rozwiązaniem ale pod cubieboard2?

Jestem żywo zainteresowany takim tematem :-)

User avatar
pancio
Administrator
Posts: 63
Joined: 18 September 2013, 23:02 - Wed
Location: SILESIA

Re: Obsługa ekspandera I2C PCF8574 + HD44870

Post by pancio »

Da się robić, muszę tylko odszukać i odkurzyć CB2.

User avatar
pancio
Administrator
Posts: 63
Joined: 18 September 2013, 23:02 - Wed
Location: SILESIA

Re: Obsługa ekspandera I2C PCF8574 + HD44870

Post by pancio »

Obsługa I2C na CubieBoard2 niczym nie różni się od obsługo na Cubietruck, przy założeniu że korzystamy z tego samego środowiska jakim jest Armbian, Igora Pečovnika. Oczywiście musimy pamiętać o poziomach napięć, które dla CB2 wynosi 3V3 dla wszystkich GPIO. Poniżej przedstawie sposób podłączenia CB2 do wyświetlacza...
Wykaz dostępnych pinów dla CT1/CT2 z zaznaczonymi pinami do obsługi I2C
Wykaz dostępnych pinów dla CT1/CT2 z zaznaczonymi pinami do obsługi I2C
przykładowo podpiąłem do CB2 dwa układy PCF8574 - jeden - moduł uniwersalny, drugi zintegrowany z wyświetlaczem LCD:
Dwa PCF8574 wykryte przez Cubieboard 2
Dwa PCF8574 wykryte przez Cubieboard 2
Do uruchomienia wyświetlacza używamy tej samej biblioteki jak dla Cubietruck....
A tak to wygląda po złożeniu w całość...
A tak to wygląda po złożeniu w całość...

Post Reply