MicroPython Programming for ESP32 [12]

<rawat.s>
4 min readMay 5, 2020

--

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

บทความนี้นำเสนอตัวอย่างการเขียนโค้ด MicroPython สำหรับ ESP32 และกล่าวถึง การใช้งานไอซีหรือโมดูล PCF8574 เพื่อนำมาใช้ตรวจสอบการกดปุ่มของ Keypad ขนาด 4x4

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

ตัวอย่าง 4x4 Membrane Keypad และการระบุตำแหน่งขา I/O
ตัวอย่างโมดูล PCF8574 ที่สามารถนำมาใช้กับ 4x4 Membrane Keypad ได้

สมมุติว่า เราให้ขา 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 แสดงว่ามีการกดปุ่มตรงกับคอลัมน์นั้น และตรงกับแถวแนวนอนที่กำลังตรวจสอบ

ตัวอย่างการวางแผนต่อวงจรบนเบรดบอร์ด (Fritzing)

โค้ดตัวอย่าง: ตรวจสอบการกดปุ่มบน 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 time
KEYS = [
['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 time
KEYS = [
['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 variable
def __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')
ตัวอย่างอุปกรณ์ที่นำมาใช้ทดสอบการทำงานของโค้ด
ตัวอย่างอุปกรณ์ที่นำมาใช้ทดสอบการทำงานของโค้ด (ใช้พร้อมกัน 2 ชุด)

โดยสรุป เราได้เรียนรู้เทคนิคการตรวจสอบสถานะของปุ่มกดบน Keypad แบบ 4x4 คีย์ โดยใช้ร่วมกับไอซี PCF8574 ที่เชื่อมต่อกับ ESP32 ด้วยบัส I2C นอกจากนั้นยังได้เห็นตัวอย่างการสร้างคลาสในภาษา MicroPython เพื่อใช้งานสำหรับงานดังกล่าว

--

--

<rawat.s>
<rawat.s>

Written by <rawat.s>

I'm Thai and working in Bangkok/Thailand.

Responses (1)