PCF8574 I/O Port Expander
ไอซี PCF8574 เป็นอุปกรณ์ประเภท I/O Port Expander ขนาด 8 บิต สามารถเชื่อมต่อได้ด้วยบัส I2C โดยทำหน้าที่เป็นอุปกรณ์สเลฟ (Slave Device) และสามารถนำมาเชื่อมต่อกับไมโครคอนโทรลเลอร์ที่ทำหน้าที่เป็นอุปกรณ์มาสเตอร์ (Master Device) โดยใช้สายสัญญาณเพียง 2 เส้น คือ SCL (Clock) และ SDA (Data)
ในการสื่อสารข้อมูลกับ PCF8574 ก็จะใช้วิธีการเขียนหรืออ่านข้อมูลที่มีขนาดเพียงหนึ่งไบต์เท่านั้น และจะต้องระบุแอดเดรส (Address) ของอุปกรณ์ให้ตรงกันด้วย
หลักการทำงานของ PCF8574 เคยได้อธิบายไว้บ้างแล้ว เช่น ในบทความต่อที่ [11] ดังนั้นจึงจะไม่ขอกล่าวถึงในรายละเอียด
4x4 Keypad Scan
ในบทความนี้ เราจะมาสาธิตการเชื่อมต่อระหว่างไอซี PCF8574 และโมดูล 4x4 Membrane Keypad ซึ่งมีจำนวนขา I/O เท่ากับ 8 ขา แบ่งเป็น 2 กลุ่มคือ แถวแนวนอน หรือ Rows (R1..R4) และแถวคอลัมน์หรือ Columns (C1..C4) และเราจะนำมาต่อกับขา P0..P7 ของไอซี PCF8574
สมมุติว่า เราให้ขา P3..P0 ของ PCF8574 เป็นขาอินพุตที่นำไปต่อกับ C1..C4 ของ Keypad ซึ่งมีสถานะปรกติเป็น 1111
(เลขฐานสอง) และให้ขา P7..P4 เป็นเอาต์พุตที่ต่อกับขา R1..R4 และให้เอาต์พุตเป็น 1111
ข้อสังเกต: โดยปรกติแล้ว เราจะต้องต่อตัวต้านทานแบบ Pull-up ที่ขาอินพุตของไมโครคอนโทรลเลอร์ด้วย แต่เนื่องจากเราใช้ไอซี PCF8574 จึงไม่จำเป็น
การตรวจสอบว่า มีปุ่มใดกดอยู่หรือไม่ หรือที่เรียกว่า Keypad Scan นั้น จะต้องทำไปทีละแถวแนวนอน โดยกำหนดค่าเอาต์พุตดังนี้ 0111
, 1011
, 1101
, 1110
สำหรับขาที่ต่อกับ R1, R2, R3, R4 (ตรงกับขา P7..P4) ในขณะที่ขาอินพุตอีก 4 บิต (P3..P0) จะต้องเขียนค่าเป็น 1111
ดังนั้นข้อมูลไบต์ (4 บิตบนสำหรับเอาต์พุต และ 4 บิตล่างสำหรับอินพุต) สำหรับเขียนไปยัง PCF8574 จะมีดังนี้ (เรียงตามแถวแนวนอน 4 แถว)
[0b01111111, 0b10111111, 0b11011111, 0b11101111]
ในแต่ละครั้งที่เขียนข้อมูลไบต์ไปแล้ว ให้อ่านข้อมูลหนึ่งไบต์กลับมา เพื่อตรวจสอบอินพุตจาก C1..C4 ซึ่งตรงบิตที่ 3..0 ถ้ามีบิตใดที่มีค่าเป็น 0 แสดงว่ามีการกดปุ่มตรงกับคอลัมน์นั้น และตรงกับแถวแนวนอนที่กำลังตรวจสอบ
โค้ดตัวอย่าง: ตรวจสอบการกดปุ่มบน 4x4 Keypad
มาดูตัวอย่างโค้ด MicroPython ที่สาธิตการตรวจสอบซ้ำไปเรื่อย ๆ เพื่อดูว่า มีการกดปุ่มบน Keypad หรือไม่ ในตัวอย่างนี้ ได้กำหนดแอดเดรสของ PCF8574 ให้เท่ากับ 0x20
(เมื่อต่อขา A0=0, A1=0, A2=0 โดยต่อกับ GND)
ฟังก์ชัน scan_keypad()
จะทำการสแกนแป้นกดคีย์หนึ่งรอบ โดยสแกนไปทีละแถวแนวนอน 4 แถว แต่ถ้าตรวจพบว่า มีการกดปุ่มในแถวใด ก็จะระบุว่า ปุ่มที่ถูกกดนั้นตรงกับพิกัดใด (row
,col
) และตรงกับคีย์ใดในอาร์เรย์สองมิติ KEYS
ข้อสังเกต: ถ้ามีการกดปุ่มพร้อมกันมากกว่าหนึ่งปุ่มในแถวเดียวกัน จะถือว่าเป็นการกดปุ่มไม่ถูกต้อง แต่ถ้ามีการกดปุ่มพร้อมกันแต่ต่างแถวกัน และมีหนึ่งปุ่มที่ถูกกดในหนึ่งแถว ฟังก์ชันจะให้ค่ากลับคืน (Return Value) เป็นรายการของคีย์ที่ตรวจพบ แต่ถ้าไม่มีการกดปุ่มใด ๆ จะได้ค่ากลับคืนเป็นรายการว่างเปล่า (Empty List)
from machine import Pin, I2C
import utime as timeKEYS = [
['1','2','3','A'],
['4','5','6','B'],
['7','8','9','C'],
['*','0','#','D'] ]COLUMN_BITS = [0b01111111,0b10111111,0b11011111,0b11101111]def scan_keypad(i2c, addr):
buf = bytearray(1)
keys = []
for row in range(4): # scan each row
# write one byte to PCF8574 (for row scanning)
buf[0] = COLUMN_BITS[ row ]
i2c.writeto(addr, buf) # read one byte from PCF8574
x = i2c.readfrom(addr,1)[0] & 0xf if (~x & 0xf) not in [1,2,4,8]:
# no keypress or multiple keypress
continue col = -1
# check the i-th column for key press
for i in range(4):
# the key at this column is pressed
if (x>>i) & 1 == 0:
col = (3-i) # save column index
break
if col >= 0:
# lookup the key at (row,column)
key = KEYS[row][col]
# show active row and input bits
print("R{}='{:>04s}'".format(row+1,bin(x)[2:]))
keys.append(key)
return keys# Use GPIO22=SCL, GPIO21=SDA
i2c = I2C(freq=100000, scl=Pin(22), sda=Pin(21))
addr = 0x20
try:
while True:
keys = scan_keypad(i2c,addr)
if len(keys) >= 1:
print(keys)
time.sleep_ms(100)
except KeyboardInterrupt:
pass
finally:
print('Done')
การสร้างคลาสสำหรับ PCF8574 Keypad Scan
เมื่อได้เรียนรู้เทคนิคการใช้ PCF8574 เพื่อตรวจสอบการกดปุ่มหรือคีย์บน Keypad ไปบ้างแล้ว มาดูตัวอย่างการสร้างคลาสในภาษา MicroPython โดยตั้งชื่อเป็น KEYPAD
และบันทึกลงในไฟล์ pcf8574_keypad.py
ข้อสังเกต: ในการตรวจสอบเพื่อดูว่ามีการกดปุ่มหรือไม่ เราจะใช้ Software Timer มาเป็นตัวช่วยสำหรับการทำงานหรือขั้นตอนซ้ำแบบมีคาบ (Periodic Task) โดยให้ทำการสแกนปุ่มกดซ้ำไปเรื่อย ๆ โดยเว้นระยะเวลา 100 มิลลิวินาที และสามารถเรียกฟังก์ชัน read()
เพื่อดูว่า มีปุ่มใดที่ถูกกดจากการตรวจสอบครั้งล่าสุด
# file: pcf8574_keypad.py
from machine import Pin, I2C, Timer
import utime as timeKEYS = [
['1','2','3','A'],
['4','5','6','B'],
['7','8','9','C'],
['*','0','#','D'] ]COLUMN_BITS = [0b01111111,0b10111111,0b11011111,0b11101111]class KEYPAD():
_id = -10 # static variabledef __init__(self,i2c,addr):
self._i2c = i2c
self._addr = addr
self._timer = Timer(KEYPAD._id)
KEYPAD._id -= 1
self._timer.init(mode=Timer.PERIODIC,
period=100,
callback=self.scan_keypad)
self._keys = []
def read(self):
return list(self._keys)
def deinit(self):
self._timer.deinit() # stop timer
def scan_keypad(self,t):
self._keys = []
buf = bytearray(1)
for row in range(4): # scan each row
# write one byte to PCF8574 (for row scanning)
buf[0] = COLUMN_BITS[ row ]
try:
self._i2c.writeto(self._addr, buf)
except OSError:
return
# read one byte from PCF8574
x = self._i2c.readfrom(self._addr,1)[0] & 0xf
if (~x & 0xf) not in [1,2,4,8]:
# no keypress or multiple keypress
continue
col = -1
# check the i-th column for key press
for i in range(4):
# the key at this column is pressed
if (x>>i) & 1 == 0:
col = (3-i) # save column index
break
if col >= 0:
self._keys.append( KEYS[row][col] )
ถัดไปเป็นโค้ดสาธิตการใช้คลาส pcf8574_keypad.KEYPAD
ในตัวอย่างนี้ เราจะใช้อุปกรณ์ 2 ชุด และต้องตั้งค่าแอดเดรสที่ไม่ซ้ำกันสำหรับ PCF8574 ตามลำดับ (เช่น 0x20
และ 0x21
)
# file: pcf8574_keypad_demo.py
from machine import Pin, I2C, Timer
import utime as time
from pcf8574_keypad import KEYPAD# Use GPIO22=SCL, GPIO21=SDA
i2c = I2C( freq=100000,scl=Pin(22),sda=Pin(21) )
addr_list = i2c.scan()
print( [hex(addr) for addr in addr_list] )keypads =[ KEYPAD(i2c,0x20),
KEYPAD(i2c,0x21) ]
num_keypads = len(keypads)
try:
while True:
for i in range(num_keypads):
keys = keypads[i].read()
if len(keys) == 1:
key = keys[0]
print("Keypad {}, key='{}'".format(i,key) )
time.sleep_ms(200)
except KeyboardInterrupt:
for keypad in keypads:
keypad.deinit()
finally:
print('Done')
โดยสรุป เราได้เรียนรู้เทคนิคการตรวจสอบสถานะของปุ่มกดบน Keypad แบบ 4x4 คีย์ โดยใช้ร่วมกับไอซี PCF8574 ที่เชื่อมต่อกับ ESP32 ด้วยบัส I2C นอกจากนั้นยังได้เห็นตัวอย่างการสร้างคลาสในภาษา MicroPython เพื่อใช้งานสำหรับงานดังกล่าว