MicroPython: Развиваем навыки — #11 Клавиатуры
Добро пожаловать в одиннадцатый выпуск серии «Развиваем навыки MicroPython» — цикла уроков, цель которого — улучшить ваши навыки программирования на MicroPython, попутно знакомя с новыми компонентами и приёмами написания кода на базе Raspberry Pi Pico!
В этом выпуске мы покажем, как вводить значения с помощью матричной клавиатуры.
Матричные клавиатуры выпускаются в конфигурациях 3x4 и 4x4. Некоторые модели можно подключать к Pico без пайки. Мембранные клавиатуры недорогие и имеют клейкий слой под защитной бумагой, что позволяет легко крепить их к плоской поверхности.
Что вам понадобится
Предполагается, что вы уже установили Thonny на компьютер и настроили Raspberry Pi Pico с последней прошивкой MicroPython (UF2).
Вам понадобится:
Raspberry Pi Pico (или Pico 2) с контактными выводами
Кабель Micro-USB (для питания и программирования Pico)
Матричная клавиатура:
Мембранная версия 3x4 — пайка не требуется
Версия 4x4 — требует пайки контактов, но позволяет вводить числа с плавающей точкой
Провода-перемычки (папа/папа и папа/мама, желательно разных цветов)
Дополнительно:
OLED-дисплей SSD1315 (0.96», 128x64)
Беспаечная макетная плата
Целые числа и числа с плавающей точкой
Прежде чем переходить к коду, давайте разберёмся, как работают числа в MicroPython — мы будем использовать их в нашей программе.
В MicroPython существует два типа чисел: целые числа (Integers) и числа с плавающей точкой (Floats).
Целые числа
Могут быть положительными или отрицательными и не содержат десятичной точки (например: -123, 3458, -999).
Числа с плавающей точкой
Содержат десятичную точку и могут быть как положительными, так и отрицательными.
Если они представляют очень большие или очень маленькие значения, они могут быть записаны в научной нотации с символом „e“ (например: -1.23, 0.000000789, 12345.678, 5.23e3, -2.75e-5).
Число после „e“ обозначает степень 10 и указывает, на сколько позиций нужно сдвинуть десятичную точку — вправо для положительных, влево для отрицательных значений.
1.23 e 5 представляет число 123000.0, а -1.23 e -5 представляет -0.0000123.
Для ввода таких чисел мы будем использовать следующие клавиши:
„A“ — клавиша «минус»
„B“ — клавиша „e“
„D“ — клавиша десятичной точки
„*“ — стирание/Backspace
„#“ — клавиша ENTER/ВВОД, сигнализирующая об окончании ввода числа
Внутренние соединения матрицы
Все эти устройства ввода имеют одинаковую внутреннюю схему подключения.
Горизонтальные и вертикальные провода соединяют контакты кнопок. При нажатии кнопки кратковременно замыкается соединение между проводом строки и проводом столбца — к ним обеспечивается доступ через шлейф или контакты в нижней части устройства.
Обратите внимание на маленькие чёрные точки на соединениях R0–R3. Клавиатура 4x4 имеет 4 столбца: C0–C3.
Мембранная версия 3x4
Кнопочная версия 4x4
Подключение клавиатуры к Pico
Во всех случаях соединения выполняются следующим образом:
Контакт клавиатуры |
GPIO Raspberry Pi Pico |
|---|---|
R0 |
13 |
R1 |
12 |
R2 |
11 |
R3 |
10 |
C0 |
9 |
C1 |
8 |
C2 |
7 |
C3 (если присутствует) |
6 |
Распиновку для альтернативных клавиатур можно найти в документации на страницах соответствующих товаров — существует несколько различных конфигураций.
Мы выбрали именно эти контакты, чтобы не конфликтовать с SPI GPIO контактами, используемыми для подключения OLED-дисплея SSD1315 (рассмотренного в предыдущих выпусках). Благодаря этому оба устройства можно использовать одновременно (если у вас нет дисплея — просто закомментируйте строки с OLED и смотрите результаты в окне Shell).
Как мы считываем нажатия клавиш
Мы последовательно устанавливаем каждую строку в состояние high, переходя от R0 к R3.
Пока строка установлена в high (3.3В), мы проверяем каждый столбец — не перешёл ли он тоже в high. В большинстве случаев, когда ни одна кнопка не нажата, мы считываем ~0В — состояние low.
Если кнопка нажата, мы обнаруживаем high на столбце и прерываем процесс сканирования, фиксируя текущие значения строки и столбца. По ним мы определяем, какая кнопка была нажата, и с помощью таблицы находим символ, назначенный этой кнопке.
Код
Этот блок импортирует необходимые библиотеки и настраивает OLED-дисплей:
# Using a Matrix Keypad
# Waveshare 0.96inch OLED Module via SPI (SSD1315)
# Matrix Keypad 4x4 or 4x3
# Tony Goodhew 21th Aug 2023
from machine import Pin, SPI
import time
# ============ Set up the display ==========
# Connect Red to 3.3V and Black to GND
import ssd1306
# Uses SPI port 0
spi_port = 0
MOSI = 19 # blue
CLK = 18 # yellow
CS = 17 # orange
RST = 16 # white #NB MISO not used
DC = 20 # green
WIDTH = 128
HEIGHT = 64
spi = SPI(
spi_port,
baudrate=40000000,
mosi=Pin(MOSI),
sck=Pin(CLK))
oled = ssd1306.SSD1306_SPI(WIDTH,HEIGHT,
spi,
dc=Pin(DC),
res=Pin(RST),
cs=Pin(CS),
external_vcc=False
)
oled.fill(0) # Clear display
oled.show()
def block(x,y,w,h,c): # Filled rectangle
for yy in range(h):
oled.hline(x,y+yy,w,c)
# ========= Display set up =========
Следующий блок кода создаёт двумерный массив (список списков), чтобы по нажатой кнопке можно было найти соответствующий символ:
# === MAIN PROGRAM ===
# Map characters to keys
# These are lists of lists!
# A 2 dimensional array
# C0 C1 C2 C3
keys = [['1', '2', '3', 'A'], # R0\
['4', '5', '6', 'B'], # R1\
['7', '8', '9', 'C'], # R2\
['*', '0', '#', 'D']] # R3
Здесь мы сохраняем списки контактов строк и столбцов и настраиваем GPIO: строки — как ВЫХОДЫ, столбцы — как ВХОДЫ:
# Pin connections left to right
row_pins = [13, 12, 11, 10]
col_pins = [9, 8, 7, 6]
cols = [] # Creat empty lists
rows = []
# Set up rows and columns
# Rows are OUTPUTS / Cols are INPUTS
for x in range(0,4):
# Rows are OUTPUTs which we set HIGH or LOW
rows.append(Pin(row_pins[x], Pin.OUT))
rows[x].value(0) # Set LOW = 0V
# Columns are INPUTS with internal PULL_DOWNS
cols.append(Pin(col_pins[x], Pin.IN, Pin.PULL_DOWN))
Этот блок настраивает встроенный светодиод Raspberry Pi Pico для световой индикации нажатий:
# Setup LED to indicate key pressed
led = Pin(25, Pin.OUT) # Pi Pico Comment out one of these
#led = Pin("LED", Pin.OUT) # Pi Pico W
# Short flash of LED to indicate program running
led.value(1)
time.sleep(0.5)
led.value(0)
В следующем блоке функция сканирует клавиатуру, ожидая нажатия кнопки — оно обнаруживается по переходу столбца в состояние high (3.3В).
Мы используем цикл while, управляемый переменной scanning, который поочерёдно устанавливает каждую строку в high. Затем поочерёдно опрашиваются столбцы — не установлено ли соединение:
if cols[col].value() == 1: # KEY PRESSED!
Как только нажатие обнаружено, цикл прерывается (переменная running устанавливается в False), и по значениям строки и столбца из таблицы определяется символ нажатой кнопки. Если нажатия нет, строка возвращается в состояние LOW.
Светодиод Pico кратко мигает, сигнализируя о нажатии, и символ возвращается вызывающему коду. Включена небольшая задержка для устранения «дребезга». В самих циклах задержки нет, поэтому сканирование происходит очень быстро:
# Function to scan for a key press - Returns the character pressed
def scan():
scanning = True
while scanning: # Until key pressed - blocking!
for row in range(4):
for col in range(4):
rows[row].high() # Equivalent to .value(1)
if cols[col].value() == 1:
scanning = False # Terminate looping
k = keys[row][col] # Read character from grid
time.sleep(0.3) # Debounce
rows[row].low() # Equivalent to .value(0)
led.value(1) # Flash LED to indicate key pressed
time.sleep(0.1)
led.value(0)
return k # Supply values to calling code
Следующая функция, get_value(xx,yy), постепенно строит строку из нажатых символов, добавляя их в переменную ns, которая изначально является пустой строкой.
(xx, yy) — это позиция на экране для отображения формирующейся строки ns. Окончание ввода пользователь обозначает нажатием „#“ — аналогично клавише «ENTER» на клавиатуре компьютера.
Клавиша „*“ работает как «BACKSPACE» и стирает последний введённый символ, уменьшая длину строки на один символ. При изменении строки она выводится на экран и в окне Shell. На экране перед обновлением предыдущая строка перекрывается блоком пикселей цвета фона.
Для обозначения отрицательного числа: на клавиатуре 4x4 можно использовать кнопку „A“. На клавиатуре 4x3 применяется программный приём — если первым нажатием при пустой строке (длина ns равна нулю) является „*“ (стирание), оно интерпретируется как «минус».
Для клавиш „A“, „B“ и „D“ в строку ns добавляются соответствующие символы: „-“, „e“ и „.“.
После выхода из цикла по нажатию „#“ сначала проверяется, не является ли строка пустой — тогда возвращается целое значение 0. Иначе проверяется наличие „.“ или „e“ в строке, что означает FLOAT, и строка конвертируется в соответствующий тип — целое число или число с плавающей точкой.
В конце значение возвращается вызывающему коду:
def get_value(xx,yy): # INPUT a float - includes e-format
ns ="" # Needs 4 column keypad for full functionality
while True:
key = scan() # Get a chacter from the keypad
if key == '#': # ENTER/RETURN
break # Break out of loop
elif (key == "*") :
if len(ns) == 0:
ns = ns + "-" # Minus character (for 3 column displays)
else:
ns = ns[0:-1] # Backspace
elif key == "C":
pass # Not valid
elif key == "A":
ns = ns + "-" # 'Minus' character
elif key == "D": # Decimal point
ns = ns + "."
elif key == "B":
ns = ns +"e" # Allow scientific notation values
else:
ns = ns + key # Add character to string
print(ns) # Current string to Shell window
block(xx,yy,128,10,0) # Remove outdated number from display
oled.text(ns,xx,yy,1) # Display updated number
oled.show()
if len(ns) == 0:
return 0
if (ns.find(".") > 0) or (ns.find("e") > 0): # This is a FLOAT:
# It contains an "." OR an "e"
return float(ns)
else:
# Integer
return int(ns)
Последний блок кода использует написанные функции для ввода значений с клавиатуры и отображает их вместе с их типом. Ввод НУЛЯ останавливает программу и очищает экран:
# ======= Main Loop =========
while True:
oled.text("N:",0,10) # Prompt
oled.show()
n = get_value(16,10)
oled.text(str(n),5,25,1)
oled.text(str(type(n)),5,35,1)
oled.show()
print("\n", n)
tp = str(type(n))
print(tp)
time.sleep(2)
oled.fill(0)
if n == 0: # HALT if zero
break
# Tidy up
oled.fill(0)
oled.show()
Запуск кода
Теперь ваша очередь — соберите программу в окне кода Thonny и запустите её, а затем попробуйте вводить числа с помощью клавиатуры. Используйте целые числа, числа с плавающей точкой и числа в научной нотации.
Посмотрите, что произойдёт, если сразу нажать клавишу хэш.
Приложение — Распиновки
У Adafruit есть удобная распиновка для подобных матриц (обратите внимание: Adafruit нумерует строки и столбцы с 1, а не с 0).