Подтягивающие резисторы

В этой документации мы разберём, почему пины «плавают», как резисторы это исправляют, как рассчитать нужный номинал с помощью концепции делителя напряжения, и как легко управлять этим с помощью библиотеки 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) в виде делителя:

  • +3.3_V — это напряжение питания 3.3В, которое мы подаём через кнопку.
  • R1 — это сопротивление нашей кнопки (в нажатом состоянии оно близко к 0 Ом).
  • R2 — это наш стягивающий резистор (10 кОм).
  • V_pin — это напряжение, которое «видит» наш пин GPIO.

Формула для расчёта выходного напряжения: U_out = U_in * (R2 / (R1 + R2))

Рассчитаем напряжение при нажатой кнопке:

  • +3.3_V = 3.3В
  • R1 = 0 Ом (идеальная кнопка)
  • R2 = 10 000 Ом

V_pin = 3.3 (10000 / (0 + 10000)) = 3.3 * 1 = 3.3В

Результат: 3.3В. Это чёткий ВЫСОКИЙ уровень.

А что, если у нас не кнопка, а датчик с внутренним сопротивлением, скажем, 80 кОм? Если мы оставим стягивающий резистор 10 кОм, то при срабатывании датчика получим:

  • R1 = 80 000 Ом

V_pin = 3.3 (10000 / (80000 + 10000)) = 3.3 * 0.11 = 0.36В

Результат 0.36В — это логический ноль! Датчик сработал, а мы этого не увидели. В этом случае стягивающий резистор нужно подбирать. Чтобы получить на выходе уверенный ВЫСОКИЙ уровень (например, >2.4В), сопротивление стягивающего резистора (R2) должно быть в несколько раз больше сопротивления датчика (R1). Например, взяв R2 = 480 кОм, мы получим U_out = 2.8В, что является уверенной логической единицей.

Вывод: Резистор в 10 кОм идеален для кнопок, так как он достаточно велик, чтобы ток при нажатии был мизерным (I = 3.3В / 10000Ом = 0.33мА), но при этом достаточно мал, чтобы обеспечить стабильный логический уровень.

Практика: используем встроенные резисторы Repka Pi #

К счастью, вам не всегда нужно возиться с внешними резисторами. В процессор Repka Pi уже встроены программно управляемые подтягивающие и стягивающие резисторы сопротивлением около 80 кОм.

Давайте соберём схему, используя встроенную подтяжку.

Схема (логика pull-down):

  • Pin 15 (GPIO) <-> один контакт кнопки

Второй контакт кнопки <-> 3.3V (Pin 1)

  • Pin 12 (GPIO) <-> анод светодиода (+)

Катод светодиода (-) <-> резистор 220 Ом <-> GND (Pin 6)

Код:

import RepkaPi.GPIO as GPIO
from time import sleep

# Используем нумерацию BOARD, по физическому расположению пинов
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 (1).
        if GPIO.input(button_pin) == GPIO.HIGH:
            print(f'Кнопка нажата! PIN {button_pin} = 1. Включаем светодиод.')
            GPIO.output(led_pin, GPIO.HIGH)
        else:
            GPIO.output(led_pin, GPIO.LOW)
        
        sleep(0.05) # Небольшая задержка для стабильности

finally:
    # Этот блок выполнится при выходе из программы
    print("\nЗавершение работы. Гасим светодиод и сбрасываем настройки GPIO.")
    GPIO.output(led_pin, GPIO.LOW)
    GPIO.cleanup()

Если бы мы хотели использовать подтягивающий резистор ( pull_up_down=GPIO.PUD_UP), нам бы пришлось поменять схему (кнопка должна замыкать на GND) и логику в коде ( if GPIO.input(button_pin) == GPIO.LOW: ).

Аппаратная реализация подтяжки: управляющие регистры

Библиотека RepkaPi.GPIO предоставляет удобный и высокоуровневый интерфейс, который абстрагирует пользователя от сложностей аппаратной части. Когда вы используете функцию GPIO.setup() с параметром pull_up_down, вы задаете желаемое поведение пина, не задумываясь о том, как это реализуется на уровне процессора.

На самом деле, каждая такая команда преобразуется в низкоуровневые операции с аппаратными регистрами процессора. Регистры — это специальные области памяти, напрямую связанные с аппаратным обеспечением (концепция memory-mapped I/O). Запись определённых числовых значений в эти регистры позволяет управлять физическими параметрами пинов: их режимом работы (вход/выход), скоростью и, что важно для нашей темы, состоянием внутренних подтягивающих (pull-up) и стягивающих (pull-down) резисторов.

В этой главе мы рассмотрим, как именно параметр pull_up_down реализуется на этом фундаментальном уровне. Понимание этого процесса не только раскроет внутреннюю логику работы Repka Pi, но и продемонстрирует методы прямого управления GPIO для задач, где требуется максимальная производительность и полный контроль над аппаратной частью.

В процессоре Allwinner H5, который является сердцем Repka Pi, за конфигурацию подтяжки отвечают специальные регистры Px_PULLn (где x — это буква порта, например, A, C, L, а n — номер регистра в группе).

Каждый такой регистр — это 32-битное число, которое управляет подтяжкой для 16 пинов одновременно. На каждый пин отводится по 2 бита, которые работают как переключатель:

  • 00: Подтяжка выключена (высокоимпедансное состояние, Z).

  • 01: Включена подтяжка к питанию (Pull-Up).

  • 10: Включена стяжка к земле (Pull-Down).

  • 11: Зарезервировано.

Чтобы включить подтяжку для пина PL10, нам нужно найти регистр, отвечающий за порт L, и изменить в нём два бита, соответствующие десятому пину, на значение 01. Давайте сделаем это на практике.

Практика: включаем Pull-Up вручную на Python

Сейчас мы сделаем то же самое, что и GPIO.setup(..., pull_up_down=GPIO.PUD_UP), но своими руками, записывая данные напрямую в память.

Внимание! Следующий код требует прав суперпользователя (`sudo`) и работает с физической памятью напрямую. Ошибка в адресе или значении может привести к зависанию системы. Действуйте осторожно.

Задача: Настроить пин PL10 (физический пин 24) как вход и включить на нём внутреннюю подтяжку к питанию (Pull-Up).

import mmap
import os
import time

# --- Константы из документации на процессор Allwinner H5 ---
# Базовый адрес контроллеров GPIO
GPIO_BASE = 0x01C20800
# Смещение для регистра конфигурации порта L (PL)
PORTL_CONF_OFFSET = 0x240 # Уточненный адрес для Port L
# Смещение для регистра управления подтяжкой порта L (PL)
PORTL_PULL_OFFSET = 0x25C # Уточненный адрес для Port L
# Смещение для регистра чтения данных порта L (PL)
PORTL_DATA_OFFSET = 0x250 # Уточненный адрес для Port L

# Наш целевой пин - PL10
PIN_NUM = 10

# Открываем "файл" физической памяти
mem_fd = os.open('/dev/mem', os.O_RDWR | os.O_SYNC)

# Отображаем страницу памяти с регистрами GPIO в нашу программу
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')
    # Очищаем 4 бита, отвечающие за PL10, и устанавливаем значение 0000 (Input)
    conf_reg_val &= ~(0b1111 << (PIN_NUM * 4))
    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')
    # Очищаем 2 бита, отвечающие за PL10
    pull_reg_val &= ~(0b11 << (PIN_NUM * 2))
    # Устанавливаем значение 01 (Pull-Up)
    pull_reg_val |=  (0b01 << (PIN_NUM * 2))
    # Записываем новое значение в регистр подтяжки
    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, 0 = LOW)")
        time.sleep(1)

except KeyboardInterrupt:
    print("\nЗавершение работы.")
finally:
    # Крайне важно закрыть map и файл
    gpio_map.close()
    os.close(mem_fd)

66 просмотров0 комментариев

Комментарии (0)

Для участия в обсуждении Вы должны быть авторизованным пользователем
Разделы

Навигация

ВойтиРегистрация