Введение #
В этой документации мы разберём, почему пины «плавают», как резисторы это исправляют, как рассчитать нужный номинал с помощью концепции делителя напряжения, и как легко управлять этим с помощью библиотеки RepkaPi.GPIO. Также мы рассмотрим низкоуровневый пример прямой настройки через регистры, но важно помнить, что такие примеры зависят от конкретного SoC и не являются универсальными для всех моделей Repka Pi.
Теоретические основы #
Проблема: «плавающее» состояние пина
Когда вы настраиваете пин GPIO в режим входа (GPIO.IN), он превращается в чувствительный вольтметр. Он постоянно измеряет напряжение, чтобы определить его логический уровень:
- ВЫСОКИЙ (HIGH, 1): На пин подано напряжение, близкое к напряжению питания соответствующей линии ввода-вывода.
- НИЗКИЙ (LOW, 0): На пин подано напряжение, близкое к земле (0В).
А что, если к пину ничего не подключено? Или подключена кнопка, но она не нажата? В этом случае пин остаётся «висеть в воздухе». Он не соединён ни с уровнем питания своей линии, ни с GND. Это состояние называется высокоимпедансным (Z-состояние) или «плавающим». Пин в таком состоянии становится крайне уязвимым для любых электромагнитных помех, превращаясь в антенну. В результате микроконтроллер считывает случайный «мусор», что и приводит к хаотичным срабатываниям.
Решение: даём пину точку опоры
Чтобы избавиться от неопределённости, нам нужно принудительно задать пину состояние по умолчанию. Для этого мы «подтягиваем» его к одному из уровней через резистор.
1. Подтягивающий резистор (Pull-Up) Мы подключаем резистор между пином GPIO и уровнем питания соответствующей линии ввода-вывода.
- Когда кнопка не нажата: Резистор «подтягивает» напряжение на пине к уровню питания линии. Микроконтроллер уверенно читает HIGH.
- Когда кнопка нажата: Кнопка замыкает цепь, соединяя пин напрямую с землёй (GND). Ток выбирает путь наименьшего сопротивления (через кнопку, а не через резистор), и напряжение на пине падает до 0В. Микроконтроллер читает LOW.

2. Стягивающий резистор (Pull-Down) Работает по обратному принципу: мы подключаем резистор между пином GPIO и землёй (GND).
- Когда кнопка не нажата: Резистор «стягивает» напряжение на пине к 0В. Микроконтроллер читает LOW.
- Когда кнопка нажата: Кнопка замыкает цепь, соединяя пин с уровнем питания линии ввода-вывода. Микроконтроллер читает HIGH.

Почему именно 10 кОм? Разбираем делитель напряжения
Номинал резистора в 10 кОм встречается чаще всего. Это не случайность, а инженерный компромисс, который легко понять через концепцию делителя напряжения. Делитель напряжения — это простая схема из двух последовательных резисторов (R1 и R2), которая позволяет получить на выходе напряжение, являющееся частью входного.

Представим нашу схему со стягивающим резистором (pull-down) в виде делителя, где R1 — это сопротивление кнопки, а R2 — наш резистор. Формула для расчёта выходного напряжения: U_out = U_in * (R2 / (R1 + R2)).
Расчет при нажатой кнопке (R1 ≈ 0 Ом): если линия работает на 3.3 В, то V_pin = 3.3 * (10000 / (0 + 10000)) = 3.3 * 1 = 3.3В (чёткий ВЫСОКИЙ уровень).
Вывод: Резистор в 10 кОм часто удобен для кнопок, так как он достаточно велик, чтобы ток при нажатии был небольшим (для линии 3.3 В это I = 3.3В / 10000Ом = 0.33мА), но при этом достаточно мал, чтобы обеспечить стабильный логический уровень.
Для Repka Pi 5 важно учитывать, что SoC Rockchip RK3588 поддерживает GPIO-линии с разными доменами питания: часть линий может работать с логикой 3.3 В, а часть — только с 1.8 В. Кроме того, встроенные подтягивающие и стягивающие резисторы у RK3588 не имеют одного фиксированного номинала для всех линий и по документации SoC находятся в диапазоне порядка 10-100 кОм в зависимости от конкретного банка и режима питания. Поэтому при подключении внешних схем всегда сверяйтесь с распиновкой и электрическими характеристиками именно вашей модели платы.
Практическая реализация #
Способ 1: Программное управление (рекомендуемый)
К счастью, вам не всегда нужно возиться с внешними резисторами. В процессор Repka Pi уже встроены программно управляемые подтягивающие и стягивающие резисторы.
Схема:
- Pin 15 (GPIO) <-> один контакт кнопки
- Второй контакт кнопки <-> 3.3V (Pin 1)
- Pin 12 (GPIO) <-> анод светодиода (+)
- Катод светодиода (-) <-> резистор 220 Ом <-> GND (Pin 6)
Этот пример рассчитан на использование GPIO-линии 40-пинового разъёма с логикой 3.3 В. Перед повторением схемы на Repka Pi 5 убедитесь по актуальной распиновке, что выбранный пин действительно относится к 3.3-вольтовому домену и поддерживает нужный режим работы.
Код:
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: Прямое управление регистрами (продвинутый, пример для плат на Allwinner)
Библиотека RepkaPi.GPIO абстрагирует пользователя от сложностей. На самом деле, каждая команда преобразуется в низкоуровневые операции с аппаратными регистрами процессора. Однако устройство этих регистров зависит от SoC.
На процессорах Allwinner, которые используются в Repka Pi 3 и Repka Pi 4, за конфигурацию подтяжки отвечают регистры Px_PULLn. На каждый пин отводится по 2 бита, которые работают как переключатель:
00: Подтяжка выключена (Z-состояние).01: Включена подтяжка к питанию (Pull-Up).10: Включена стяжка к земле (Pull-Down).
Давайте сделаем то же самое, что и GPIO.setup(..., pull_up_down=GPIO.PUD_UP), но своими руками.
Внимание! Следующий код требует прав суперпользователя (
sudo) и работает с физической памятью напрямую. Ошибка в адресе или значении может привести к зависанию системы.Этот пример относится к платам Repka Pi 3/4 на SoC Allwinner. Для Repka Pi 5 на Rockchip RK3588 адреса регистров, схема GPIO-банков и сама организация управления подтяжкой отличаются, поэтому использовать этот код на Repka Pi 5 нельзя без полной адаптации под документацию RK3588.
Задача: Настроить пин 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)