Введение #
В этой документации мы разберём, почему пины «плавают», как резисторы это исправляют, как рассчитать нужный номинал с помощью концепции делителя напряжения, и как легко управлять этим с помощью библиотеки RepkaPi.GPIO
и напрямую через регистры Repka Pi.
Теоретические основы #
Проблема: «плавающее» состояние пина
Когда вы настраиваете пин GPIO в режим входа (GPIO.IN
), он превращается в чувствительный вольтметр. Он постоянно измеряет напряжение, чтобы определить его логический уровень:
- ВЫСОКИЙ (HIGH, 1): На пин подано напряжение, близкое к напряжению питания (3.3В).
- НИЗКИЙ (LOW, 0): На пин подано напряжение, близкое к земле (0В).
А что, если к пину ничего не подключено? Или подключена кнопка, но она не нажата? В этом случае пин остаётся «висеть в воздухе». Он не соединён ни с 3.3В, ни с GND. Это состояние называется высокоимпедансным (Z-состояние) или «плавающим». Пин в таком состоянии становится крайне уязвимым для любых электромагнитных помех, превращаясь в антенну. В результате микроконтроллер считывает случайный «мусор», что и приводит к хаотичным срабатываниям.
Решение: даём пину точку опоры
Чтобы избавиться от неопределённости, нам нужно принудительно задать пину состояние по умолчанию. Для этого мы «подтягиваем» его к одному из уровней через резистор.
1. Подтягивающий резистор (Pull-Up) Мы подключаем резистор между пином GPIO и питанием (3.3В).
- Когда кнопка не нажата: Резистор «подтягивает» напряжение на пине к 3.3В. Микроконтроллер уверенно читает HIGH.
- Когда кнопка нажата: Кнопка замыкает цепь, соединяя пин напрямую с землёй (GND). Ток выбирает путь наименьшего сопротивления (через кнопку, а не через резистор), и напряжение на пине падает до 0В. Микроконтроллер читает LOW.
2. Стягивающий резистор (Pull-Down) Работает по обратному принципу: мы подключаем резистор между пином GPIO и землёй (GND).
- Когда кнопка не нажата: Резистор «стягивает» напряжение на пине к 0В. Микроконтроллер читает LOW.
- Когда кнопка нажата: Кнопка замыкает цепь, соединяя пин с питанием (3.3В). Микроконтроллер читает HIGH.
Почему именно 10 кОм? Разбираем делитель напряжения
Номинал резистора в 10 кОм встречается чаще всего. Это не случайность, а инженерный компромисс, который легко понять через концепцию делителя напряжения. Делитель напряжения — это простая схема из двух последовательных резисторов (R1 и R2), которая позволяет получить на выходе напряжение, являющееся частью входного.
Представим нашу схему со стягивающим резистором (pull-down) в виде делителя, где R1
— это сопротивление кнопки, а R2
— наш резистор. Формула для расчёта выходного напряжения: U_out = U_in * (R2 / (R1 + R2))
.
Расчет при нажатой кнопке (R1
≈ 0 Ом): V_pin = 3.3 * (10000 / (0 + 10000)) = 3.3 * 1 = 3.3В
(чёткий ВЫСОКИЙ уровень).
Вывод: Резистор в 10 кОм идеален для кнопок, так как он достаточно велик, чтобы ток при нажатии был мизерным (I = 3.3В / 10000Ом = 0.33мА
), но при этом достаточно мал, чтобы обеспечить стабильный логический уровень.
Практическая реализация #
Способ 1: Программное управление (рекомендуемый)
К счастью, вам не всегда нужно возиться с внешними резисторами. В процессор Repka Pi уже встроены программно управляемые подтягивающие и стягивающие резисторы.
Схема:
- Pin 15 (GPIO) <-> один контакт кнопки
- Второй контакт кнопки <-> 3.3V (Pin 1)
- Pin 12 (GPIO) <-> анод светодиода (+)
- Катод светодиода (-) <-> резистор 220 Ом <-> GND (Pin 6)
Код:
import RepkaPi.GPIO as GPIO
from time import sleep
GPIO.setmode(GPIO.BOARD)
button_pin = 15
led_pin = 12
# Настраиваем пин кнопки как ВХОД и включаем внутренний СТЯГИВАЮЩИЙ резистор.
# Теперь по умолчанию на пине будет LOW.
GPIO.setup(button_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(led_pin, GPIO.OUT)
print("Скрипт запущен. Нажмите CTRL+C для выхода.")
try:
while True:
# Поскольку у нас стягивающий резистор (pull-down),
# нажатие кнопки подаст 3.3В на пин, и его состояние станет HIGH.
if GPIO.input(button_pin) == GPIO.HIGH:
GPIO.output(led_pin, GPIO.HIGH)
else:
GPIO.output(led_pin, GPIO.LOW)
sleep(0.05)
finally:
print("\nЗавершение работы. Гасим светодиод и сбрасываем настройки GPIO.")
GPIO.cleanup()
Если бы мы хотели использовать подтягивающий резистор (pull_up_down=GPIO.PUD_UP
), нам бы пришлось поменять схему (кнопка должна замыкать на GND) и логику в коде (if GPIO.input(button_pin) == GPIO.LOW:
).
Способ 2: Прямое управление регистрами (продвинутый)
Библиотека RepkaPi.GPIO
абстрагирует пользователя от сложностей. На самом деле, каждая команда преобразуется в низкоуровневые операции с аппаратными регистрами процессора. В процессоре Allwinner за конфигурацию подтяжки отвечают регистры Px_PULLn
. На каждый пин отводится по 2 бита, которые работают как переключатель:
00
: Подтяжка выключена (Z-состояние).01
: Включена подтяжка к питанию (Pull-Up).10
: Включена стяжка к земле (Pull-Down).
Давайте сделаем то же самое, что и GPIO.setup(..., pull_up_down=GPIO.PUD_UP)
, но своими руками.
Внимание! Следующий код требует прав суперпользователя (
sudo
) и работает с физической памятью напрямую. Ошибка в адресе или значении может привести к зависанию системы.
Задача: Настроить пин PL10 как вход и включить на нём внутреннюю подтяжку к питанию (Pull-Up).
import mmap
import os
import time
# --- Константы из документации на процессор Allwinner H5 ---
GPIO_BASE = 0x01C20800
PORTL_CONF_OFFSET = 0x240 # Уточненный адрес для Port L
PORTL_PULL_OFFSET = 0x25C # Уточненный адрес для Port L
PORTL_DATA_OFFSET = 0x250 # Уточненный адрес для Port L
PIN_NUM = 10
mem_fd = os.open('/dev/mem', os.O_RDWR | os.O_SYNC)
gpio_map = mmap.mmap(fileno=mem_fd, length=4096, offset=GPIO_BASE)
try:
# --- Шаг 1: Настраиваем пин PL10 на ВХОД ---
conf_reg_val = int.from_bytes(gpio_map[PORTL_CONF_OFFSET : PORTL_CONF_OFFSET + 4], 'little')
conf_reg_val &= ~(0b1111 << (PIN_NUM * 4)) # Очищаем и ставим 0000 (Input)
gpio_map[PORTL_CONF_OFFSET : PORTL_CONF_OFFSET + 4] = conf_reg_val.to_bytes(4, 'little')
print("Пин PL10 настроен как ВХОД.")
# --- Шаг 2: Включаем подтяжку (Pull-Up) ---
pull_reg_val = int.from_bytes(gpio_map[PORTL_PULL_OFFSET : PORTL_PULL_OFFSET + 4], 'little')
pull_reg_val &= ~(0b11 << (PIN_NUM * 2)) # Очищаем
pull_reg_val |= (0b01 << (PIN_NUM * 2)) # Устанавливаем 01 (Pull-Up)
gpio_map[PORTL_PULL_OFFSET : PORTL_PULL_OFFSET + 4] = pull_reg_val.to_bytes(4, 'little')
print("Для пина PL10 включена подтяжка Pull-Up.")
# --- Шаг 3: Проверяем результат ---
print("\nНачинаем чтение состояния пина (нажмите CTRL+C для выхода):")
while True:
data_reg_val = int.from_bytes(gpio_map[PORTL_DATA_OFFSET : PORTL_DATA_OFFSET + 4], 'little')
pin_state = (data_reg_val >> PIN_NUM) & 1
print(f"Состояние пина PL10: {pin_state} (1 = HIGH)")
time.sleep(1)
except KeyboardInterrupt:
print("\nЗавершение работы.")
finally:
gpio_map.close()
os.close(mem_fd)