BiNome
BiNome
2450 просмотров1 комментариев

Часть 3. Программное управление линиями GPIO Repka Pi 3. Обработка цифровых и аналоговых входных сигналов с примерами на Python (libgpiod + smbus)

Содержание


Статья содержит учебные материалы с примерами на Python, которые могут быть использованы для изучения работы с линиями GPIO на Repka Pi 3, в части программной обработки входных цифровых и аналоговых сигналов, а также использовании библиотек libgpiod и smbus. Рассмотрены основы работы с кнопками, сдвиговым регистром 74HC165 и микросхемой PCF8591.

Настоящая статья не является законченным продуктом, в дальнейшем будет корректироваться и наполняться новыми материалами соответствующими ее теме.

См. также:
Часть 1. Программное управление GPIO Repka Pi 3 через системные вызовы к драйверу символьных устройств. Общие понятия. Настройка RepkaOS. Использование инструментов управления GPIO из пользовательского режима
Часть 2. Программное управление линиями GPIO Repka Pi 3 в режиме вывода с примерами на Python (libgpiod).

СОДЕРЖАНИЕ

Введение

I. Обработка цифровых входных сигналов:

1. Кнопки
   1.1. “Дребезг” контактов
   1.2. Подтягивающие резисторы
      1.2.1. Что такое сильный (strong pull-up) и слабый (weak pull-up) подтягивающий резистор?
      1.2.2. Как выбрать номинал для подтягивающего резистора?
      1.2.3. Подтягивающие резисторы в Repka Pi 3 (для версия платы 1.4 и выше)
   1.3. Обработка событий об измении состояния линии

2. Входной сдвиговый регистр 74HC165N
   2.1. Что такое входной сдвиговый регистр 74HC165N?
   2.2. Схема подключения регистра к контактам Repka Pi 3
   2.3. Алгоритм программного управления регистром

II. Обработка аналоговых входных сигналов:

3. Модуль АЦП/ЦАП на на микросхеме PCF8591
   3.1. Что такое АЦП/ЦАП?
   3.2. Что такое интерфейс I2C и протокол SMBus? Подготовка RepkaOS к работе с I2C контроллерами по протоколу SMBus.
   3.3. Управление модулем АЦП/ЦАП с использованием протокола SMBus (протокол I2C)

Введение #

Цифровые входы могут иметь только два возможных значения 0 (Low, низкое напряжение, 0 В) и 1 (High, высокое напряжение, 3,3 В). Не смотря на ограниченное количество используемых значений, цифровые входы позволяют решать широкий круг задач. Начиная от простейшего контроля бинарных состояний устройств (кнопка, датчики Холла и открытия двери), до приема больших объемов данных, передаваемых внешними устройствами (датчиками, сенсорами, фото- и видеокамерами, микроконтроллерами, и конечно же другими одноплатными компьютерами и не только) с использованием различных протоколов. Порядок работы с цифровыми входами рассмотрен в разделе 1, во 2-м разделе описана возможность увеличения количества цифровых входов с помощью входного сдвигового регистра.

Repka Pi 3 (как и ее аналоги), в отличии от микроконтроллеров, не имеет в своем арсенале входы, позволяющие обрабатывать аналоговые сигналы. В тоже время, большинство используемых во встраиваемой электронике и робототехнике датчиков передают свои данные в виде аналогового сигнала. Поэтому в разделе 3 мы рассмотрим вопрос связанный с использованием аналого-цифрового преобразователя (АЦП) для оцифровки входных аналоговых сигналов.

1. Кнопки #

Кнопка является простейшим электронным устройством, при помощи которого можно управлять ходом выполнения программы на Repka Pi 3. Физически она выполняет простую функцию: замыкает и размыкает контакт, т.е. подает высокое или низкое напряжение на входную линию. Кнопки бывают нескольких типов:

  • С фиксацией – кнопка остаётся нажатой после отпускания, без фиксации – отключается обратно.
  • Нормально разомкнутая (Normal Open, NO) – при нажатии замыкает контакты. Нормально замкнутая (Normal Closed, NC) – при нажатии размыкает контакты.
  • Тактовые кнопки – замыкают или размыкают контакт. У обычных тактовых кнопок ноги соединены вдоль через корпус (рис. 1).
  • Переключатели – обычно имеют три контакта, общий COM, нормально открытый NO и нормально закрытый NC. При отпущенной кнопке замкнута цепь COM-NC, при нажатой замыкается COM-NO.

Рис. 1. Пример кнопки и ее представления на принципиальной схеме.

Рассмотрим простой пример использования кнопки для программного включения светодиода. Реализуем следующий алгоритм работы программы: однократное нажатие (с отпусканием) кнопки будет включать светодиод, а повторное нажатие кнопки - выключать его.

Для выполнения задания нам понадобятся: макетная плата, комплект проводов, красный светодиод, тактовая кнопка, резисторы номиналом 220 Ом и 10 кОм.

Соберите схему представленную на рисунке 2. Провода красного цвета - питание 3,3 В, черный - “земля“, желтый провод подключите к 16-му контакту GPIO, синий к 18-му контакту. Резистор номиналом 10 кОм подключите к кнопке и “земле“, его назначение мы рассмотрим чуть позже. Резистор номиналом 220 Ом будем использовать как токоограничивающий, и подключим его к аноду (длинной ножке) светодиода.

Рис. 2. Макет схемы с кнопкой и светодиодом.

Введите приведенный ниже код программы и запустите его на выполнение.

# подключаем библиотеку для работы с libgpiod
import gpiod

# Получаем доступ к контроллеру и линиям
chip = gpiod.chip("gpiochip0")
line = chip.get_line(2)
led = chip.get_line(3)

config = gpiod.line_request()
# настраиваем параметры линии для кнопки
config.consumer = "Button"
# переводим линию в режим ввода
config.request_type = gpiod.line_request.DIRECTION_INPUT 
line.request(config)

# настраиваем параметры линии для светодиода
config.consumer = "LED"
# переводим линию в режим вывода
config.request_type = gpiod.line_request.DIRECTION_OUTPUT 
led.request(config)

status_led = 0
status_btn = 0

while True:
    res = line.get_value()
    # если сигнал во входной линии изменился
    if status_btn != res:
        # изменяем статус кнопки
        status_btn = res
        print(f"Кнопка: {'вкл.' if status_btn == 1 else 'выкл.'}")
        # если кнопка нажата
        if status_btn == 1:
            # изменяем статус светодиода на противоположный
            status_led = 1 if status_led == 0 else 0
            led.set_value(status_led)
            print(f"Светодиод: {'вкл.' if status_led == 1 else 'выкл.'}")

При нажатии кнопки светодиод должен включиться, а при повторном нажатии кнопки - выключится. Смена входного напряжения на 16-ом контакте должно сопровождаться выводом информации в консоль. При этом, если вы будите повторять данную процедуру многократно, то вы можете заметить эффект “моргания“ светодиода во время нажатия на кнопку (многократная смена входного сигнала за короткое время), этот эффект называется “дребезг“ контактов.

1.1. “Дребезг” контактов #

У кнопок существует такой эффект, как "дребезг" (bounce). При замыкании и размыкании между пластинами кнопки возникают микроискры, провоцирующие до десятка переключений за несколько миллисекунд, при этом микропроцессор Repki Pi 3 будет регистрировать на входе каждый переход из состояния высокого напряжения в низкое и обратно. Эффект “Дребезга” нужно учитывать при работке с кнопками и уметь нивелировать его (см. рис. 3).

Рис. 3. Пример эффекта “дребезга” при нажатии кнопки.

Избавиться от дребезга контактов можно как аппаратно, так и программно: аппаратно задача решается при помощи RC цепи (см. рис. 4), то есть резистора номиналом 1-10 кОм и конденсатора номиналом ~100 нФ.

Рис. 4. Пример схемы, для аппаратного устранения “дребезга“.

При устранении “дребезга“ программным способом можно воспользоваться следующим алгоритмом:

Начало цикла обработки событий (“бесконечный”).

  1. Считываем значение с линии.

  2. Если кнопка нажата, ждем 100 миллисекунд

    2.1. Считываем значение с линии повторно.

    2.2. Сравниваем предыдущее (п. 1) и новое (п. 2.1) значения.

    2.3. Если кнопка нажата, то подтверждаем нажатие и выполняем обработку нажатия, иначе считаем, что кнопка не нажата и переходим к п. 1.

  3. Если кнопка не нажата переходим к п. 1.

Чтобы оценить работу алгоритма, усовершенствуем программу, реализующую работу кнопки и светодиода (см. выше), внесите в код программы следующие изменения:

# подключаем библиотеку для работы с libgpiod
import gpiod
# подключаем модуль работы со временем
import time

# Получаем доступ к контроллеру и линиям
chip = gpiod.chip("gpiochip0")
line = chip.get_line(2)
led = chip.get_line(3)

config = gpiod.line_request()
# настраиваем параметры линии для кнопки
config.consumer = "Button"
# переводим линию в режим ввода
config.request_type = gpiod.line_request.DIRECTION_INPUT 
line.request(config)

# настраиваем параметры линии для светодиода
config.consumer = "LED"
# переводим линию в режим вывода
config.request_type = gpiod.line_request.DIRECTION_OUTPUT 
led.request(config)

status_led = 0
status_btn = 0

while True:
    # устраняем дребезг контактов <-- новый код
    # получаем значение из линии
    res_first = line.get_value()
    # ждем 100 миллисекунд
    time.sleep(0.1)
    # повторно получаем значение из линии
    res_now = line.get_value()
    # если старое и новое значения совпадают, меняем статус нажатия кнопки
    # если не совпадают оставляем статусу кнопки старое значение
    res = res_now if res_first == res_now else res # <-- конец нового кода
    
    # если сигнал во входной линии изменился
    if status_btn != res:
        # изменяем статус кнопки
        status_btn = res
        print(f"Кнопка: {'вкл.' if status_btn == 1 else 'выкл.'}")
        # если кнопка нажата
        if status_btn == 1:
            # изменяем статус светодиода на противоположный
            status_led = 1 if status_led == 0 else 0
            led.set_value(status_led)
            print(f"Светодиод: {'вкл.' if status_led == 1 else 'выкл.'}")

Запустите программу, проверьте, что все работает исправно. Многократное нажатие кнопки не должно приводить к эффекту “дребезга“ контактов.

Примечание: при программной реализации устранения “дребезга” контактов кнопки следует учитывать, что временная задержка приводит к увеличению времени, необходимого для фиксации состояния кнопки, то есть кнопку нужно удерживать не менее 100 миллисекунд при нажатии.

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

Контакты Repka Pi 3 имеют очень высокий входной импеданс. Следовательно, для изменения состояния вывода требуется минимальный ток, а линия может отображать случайные значения из-за внешних шумов или емкостной связи соседних контактов. Этой проблемы можно избежать, переведя вывод в известное состояние, когда состояние ввода недоступно. Для этого, как правило, используют подтягивающие вверх (pull up) или подтягивающие вниз (pull down) резисторы на входе. Использование резисторов подтягивающих вверх или подтягивающих вниз позволяет поддерживать логическое напряжение на уровне 3,3 В или 0 В, соответственно, тем самым исключая на входе линии плавающее напряжение.

Примечание: подтягивающие вниз (pull down) резисторы часто называют стягивающими.

Примечание: для явлений физических колебаний самого различного происхождения всегда характерно сопротивление среды. Условная величина этого сопротивления называется импеданс. Слово имеет происхождение от английского слова impedance, что означает в переводе препятствовать. Импеданс простым языком - это сопротивление электрической цепи, которое измеряется в Омах.

Рис. 5. Примеры принципиальных схем использования резисторов номиналом 10 кОм в качестве подтягивающего и стягивающего.

Важно! Целью использования подтягивающего резистора к 3,3 В является обеспечение на входе высокого напряжения (3,3 В), а у подтягивающего резистора к “земле” - обеспечение на входе низкого напряжения (0 В).

При работе с цифровыми входами на Repka Pi 3, подтягивающие резисторы могут быть установлены при сборке схемы, как в примере с кнопкой включающей светодиод (см. выше), или использоваться интегрированные в Репку резисторы, которые можно включить программно при настройке входной линии.

1.2.1. Что такое сильный (strong pull-up) и слабый (weak pull-up) подтягивающий резистор? #

Чем ниже сопротивление подтягивающего резистора, тем больший ток протекает через него и тем сильнее он подтягивает сигнал к питанию (или земле). Отсюда и название сильный подтягивающий резистор. Соответственно, чем выше сопротивление, тем слабже резистор подтягивает сигнал, поэтому его называют слабым.

1.2.2. Как выбрать номинал для подтягивающего резистора? #

Общая рекомендация – это, как правило, 10 кОм. От 5 кОм и ниже считается сильной подтяжкой, 20-100кОм – слабой. Подтягивающие резисторы Repka Pi 3 имеют номинал 80 кОм (за информацию о номинале резисторов отдельное спасибо Дмитрию, ник в телеграмм-канале Репка @screatorpro), т.е. являются слабыми. Поэтому при подключении к Repka Pi 3 устройств, работающих в неблагоприятных условиях или при значительной длине проводников, когда необходимо нивелировать возникновение электромагнитных помех, рекомендуется использовать более сильные подтягивающие резисторы.

1.2.3. Подтягивающие резисторы в Repka Pi 3 (для версия платы 1.4 и выше) #

В Repka Pi 3 на линиях GPIO можно использовать встроенные резисторы, которые можно подключить программно, как в качестве подтягивающих, так и стягивающих. Для выбора линии с поддержкой подтягивающих (графа Bias Up) или стягивающих (графа Bias Dn) резисторов можно воспользоваться таблицей:

Рис. 6. Справочная таблица для линий GPIO Repka Pi 3.

Рассмотрим пример программного включения подтягивающих резисторов. Для этого воспользуемся предыдущей схемой, представленной на рисунке 2. Удалите со схемы резистор номиналом 10 кОм (подключен к кнопке).

Введите код программы, реализующей программное включение интегрированного в Репку подтягивающего резистора вниз.

# подключаем библиотеку для работы с libgpiod
import gpiod
# подключаем модуль работы со временем
import time

# Получаем доступ к контроллеру и линиям
chip = gpiod.chip("gpiochip0")
line = chip.get_line(2)
led = chip.get_line(3)

config = gpiod.line_request()
# настраиваем параметры линии для кнопки
config.consumer = "Button"
# переводим линию в режим ввода
config.request_type = gpiod.line_request.DIRECTION_INPUT
# включаем интегрированную в Репку подтяжку к "земле" <-- новый код
config.flags = gpiod.line_request.FLAG_BIAS_PULL_DOWN <-- конец нового кода
line.request(config)

# настраиваем параметры линии для светодиода
config.consumer = "LED"
# переводим линию в режим вывода
config.request_type = gpiod.line_request.DIRECTION_OUTPUT 
led.request(config)

status_led = 0
status_btn = 0

while True:
    # устраняем дребезг контактов
    # получаем значение из линии
    res_first = line.get_value()
    # ждем 100 миллисекунд
    time.sleep(0.1)
    # повторно получаем значение из линии
    res_now = line.get_value()
    # если старое и новое значения совпадают, меняем статус нажатия кнопки
    # если не совпадают оставляем статусу кнопки старое значение
    res = res_now if res_first == res_now else res
    
    # если сигнал во входной линии изменился
    if status_btn != res:
        # изменяем статус кнопки
        status_btn = res
        print(f"Кнопка: {'вкл.' if status_btn == 1 else 'выкл.'}")
        # если кнопка нажата
        if status_btn == 1:
            # изменяем статус светодиода на противоположный
            status_led = 1 if status_led == 0 else 0
            led.set_value(status_led)
            print(f"Светодиод: {'вкл.' if status_led == 1 else 'выкл.'}")

Запустите программу на выполнение. Наблюдайте за выводом программы в консоль. Убедитесь, что нажатия кнопок фиксируются и отсутствуют ложные срабатывания. Как видите, программное включение интегрированных резисторов подтяжки сводится к указанию вида подтяжки при настройке входной линии.

Для справки, в модуле gpiod реализованы следующие флаги для настройки входных линий для работы с интегрированными подтягивающими резисторами:

  • gpiod.line_request.FLAG_BIAS_PULL_DOWN - использовать подтяжку вниз (“земля“);

  • gpiod.line_request.FLAG_BIAS_PULL_UP - использовать подтяжку вверх (3,3 В);

  • gpiod.line_request.FLAG_BIAS_DISABLE - не использовать подтяжку (по умолчанию).

1.3. Обработка событий об изменении состояния линии #

Микропроцессор Репки может измерять напряжение на цифровом входе, но сообщить он может только о его отсутствии (сигнал низкого уровня, LOW или 0) или наличии (сигнал высокого уровня, HIGH или 1). При теоретических расчетах отсутствием напряжения считается промежуток от 0 до ~1,65 В, а от 1,65 В до 3,3 В принимается за наличие сигнала высокого уровня. На практике существует порог неопределенности состояния линии, поэтому для каждого устройства производитель, как правило, указывает интервалы “уверенного” определения микропроцессором состояния линии. Значения таких интервалов (пороговых значений напряжения на линии) для Repka Pi 3 мы попробуем определить экспериментально.

Попробуем провести небольшой эксперимент по определению с помощью событий порога переключения процессором Repka Pi 3 входного значения с высокого на низкое и обратно при изменении напряжения на входе с помощью потенциометра. При срабатывании события мы будем фиксировать входное напряжение с помощью вольтметра. Не смотря на то, что данный способ далек от идеала и точность измерений будет под вопросом, этот эксперимент позволит нам в буквальном смысле нащупать руками порог переключения уровня входного сигнала.

Внимание: при использовании механизма событий, обязательно используйте подтягивающие резисторы, чтобы избежать ложных срабатываний.

Соберите схему в соответствии с рисунком 7. Для этого нам понадобятся: макетная плата, потенциометр (желательно с ручкой регулировки), комплект проводов и мультиметр в режиме вольтметра для постоянного тока. Желтый провод со средней ножки потенциометра подключите к 12-му контакту GPIO. На левую ножку потенциометра подайте напряжение 3,3 В, правую подключите к “земле”. Подключите вольтметр в указанные разъемы.

Рис. 7. Макет схемы для проверки работы событий.

Введите код программы, выполняющей обработку событий изменения состояния входной линии.

# подключаем библиотеку для работы с libgpiod
import gpiod
# подключаем модули для работы со временем
from datetime import timedelta
import time

# Получаем доступ к контроллеру и линии
chip = gpiod.chip("gpiochip1")
line = chip.get_line(6)

config = gpiod.line_request()
# настраиваем параметры линии для кнопки
config.consumer = "MyApp"
# переводим линию в режим ввода
config.request_type = gpiod.line_request.DIRECTION_INPUT
# включаем интегрированную в Репку подтяжку к "земле"
config.flags = gpiod.line_request.FLAG_BIAS_PULL_UP
# включаем обработку события на появление высокого напряжения в линии 
config.request_type = gpiod.line_request.EVENT_BOTH_EDGES
line.request(config)

while True:
    # ждем наступление события 10 секунд
    if line.event_wait(timedelta(seconds=10)):
        # если событие настало, получаем его
        event = line.event_read()
        # если событие связано с получением на входе высокого напряжения (1)
        if event.event_type == gpiod.line_event.RISING_EDGE:
            # выводим в консоль вид события и время его наступления
            print("rising event: ", event.timestamp)
        # если событие связано с получением на входе низкого напряжения (0)
        elif event.event_type == gpiod.line_event.FALLING_EDGE:
            # выводим в консоль вид события и время его наступления
            print("falling event: ", event.timestamp)
        else:
            # иначе, информируем о наступлении неизвестного события
            print("unknow event")
    else:
        # информируем об окончании интервала ожидания события
        print("time out 10 sec")
# повторяем цикл "бесконечно"

Выкрутите ручку потенциометра в любое крайнее положение. Включите мультиметр в режиме вольтметра. Запустите программу. Плавно поворачивайте ручку потенциометра, следите за выводом результатов на консоль. В момент срабатывания события зафиксируйте показания вольтметра. Выполните операцию не менее 10 раз на повышение напряжения от 0 до появления события, и не менее 10 раз понижения питания с 3,3 В до появления события. Вычислите среднее арифметическое значения для каждого порога.

Результаты моего “лабораторного” исследования:

# 1→0, FALLING, В 0→1, RISING, В
1 1.62 1.88
2 1.66 1.84
3 1.62 1.87
4 1.55 1.81
5 1.65 1.83
6 1.56 1.84
7 1.60 1.85
8 1.61 1.87
9 1.61 1.84
10 1.54 1.85
Среднее 1.597 1.848

Округлим полученные средние значения до сотых в меньшую сторону. В итоге мы получим нижнюю границу неопределенных значений 1.59 В и верхнюю 1.84 В. Надо отдать должное разработчикам Репки, так как значения левой границы всего на 0.05 В меньше теоретического, а правая граница выше всего на 0.19 В, а граница перевода состояния линии, полученная практическим путем имеет значение 1.72 В, то есть смещена вправо всего на 0.25 В.

Напомню, что этот пример отражает “грубую“ оценку, и приведен в учебных целях, для точного определения границ необходимо использовать лабораторное оборудование.

2. Входной сдвиговый регистр 74HC165N #

Datasheet на микросхему SN74HC165N

2.1. Что такое входной сдвиговый регистр 74HC165N? #

Регистр 74HC165 - это входной сдвиговый регистр, который преобразует параллельный сигнал в последовательный. Данный регистр используется, чтобы увеличить количество цифровых входов (рис. 8). Также входной сдвиговый регистр удобно использовать для определения нажатий среди большого количества тактовых кнопок, не занимая лишние входы. Сдвиговые регистры 74HC165 можно объединить в каскад, подключив их один за другим, что позволяет использовать всего три контакта GPIO-разъема для обработки входных данных с 16, 24, 32 и т.д. цифровых входов.

Рис. 8. Регистр 74HC165.

Схема расположения ножек у регистра 74HC595 (см. рис. 8, слева):

  • D0-D7 - цифровые входы, состояние которых считывается в регистр;
  • VCC - питание 2-6 В;
  • GND - общий (земля);
  • DS - последовательный вывод (DATA) восемь бит; к нему можно подсоединить ножку Q7 следующего в каскаде регистра;
  • CP - тактовый вход регистра сдвига (CLOCK), тактирование занимает отдельную линию, однако позволяет не привязываться к строгим таймингам, что значительно повышает надежность и помехоустойчивость передачи данных, а также позволяет работать с любой скоростью, которую обеспечивает программа и микропроцессор;
  • PL’ - тактовый вход регистра хранения или “защелка” (LATCH), позволяет (после сессии передачи данных) устанавливать уровни на всех восьми выходных ножках одновременно;
  • Q7 - последовательный вывод, к нему подключается контроллер или входной DS предыдущего в каскаде регистра;
  • Q7’ (7 контакт) - инверсный вывод, на нём идут биты с Q7, но инвертированные;
  • CE’ - когда на нём 1 — тактирование выключено.

Микросхема принимает параллельный сигнал на 8 контактах Dx и превращает его в последовательный сигнал на контакте Q7. Синхронная передача тактируется через дополнительный контакт CP. Также отдельным контактом управляется регистр данных PL, что позволяет «загружать» параллельный сигнал для последовательного считывания с 8 выходов одновременно.

Примечание: контакт Q7’ удобно использовать при установке подтягивающих вверх (к 3,3 В) резисторов, чтобы инвертировать входные сигналы, так как при разомкнутых цепях на вход будет подаваться высокое напряжение (1).

2.2. Схема подключения регистра к контактам Repka Pi 3 #

Реализуем схему использования входного регистра с 8-ю тактовыми кнопками (рис. 9). Для выполнения задания нам понадобятся: макетная плата, регистр 74HC595, 8-мь тактовых кнопок, 8-мь резисторов номиналом 10 кОм для реализации подтяжки вниз, комплект проводов. Зеленый провод подключите к 11-му контакту GPIO и к 9-ой (Q7) ножке микросхемы, синий провод к 12-му контакту и к 1-ой (PL’) ножке, коричневый к 13-му контакту и 2-ой (CP) ножке.

Примечание: при отсутствии необходимого количества тактовых кнопок, можно реализовать схему с помощью 4-х кнопок, переключая их между входами D0-D3 и D4-D7.

Рис. 9. Макет схемы подключения входного сдвигового регистра 74HC595 с 8-ю кнопками.

2.3. Алгоритм программного управления регистром #

Несмотря на то, что задача у регистра “165-ого” прямо противоположная “595-ому”, принцип работы и использование у них очень схожи. Вместо выходов у него входы, к которым подключаются бинарные датчики (кнопки, герконы, энкодеры, оптроны и т.д.).

Алгоритм работы с входным регистром:

  1. Подаем сигнал начала приема данных: закрываем (0) и сразу открываем (1) LATCH (“защелку”) - 12-ый контакт GPIO (чип - 1, линия - 9).
  2. Считываем из канала DATA (Q7) состояние очередного бита из памяти регистра - 11-ый контакт GPIO (чип -1, линия - 8).
  3. Если количество считанных бит < (8 * кол-во регистров).
  4. То тактируем канал CLOCK (CP) для записи регистром в DATA следующего бита (открываем - 1 и сразу закрываем - 0), и повторяем п. 2 - 13-ый контакт (чип - 1, линия - 10).
  5. Иначе завершаем работу.

Введите текст программы, для проверки работы реализованного программным способом протокола SPI для управления микросхемой 74HC595.

# подключаем библиотеку для работы с libgpiod
import gpiod
# подключаем модуль для работы со временем
import time

# номера используемых линий на 1 чипе
DS_PIN = 8
STCP_PIN = 9
SHCP_PIN = 10

# Получаем доступ к контроллеру и линиям
chip = gpiod.chip("gpiochip1")
data = chip.get_line(DS_PIN)
latch = chip.get_line(STCP_PIN)
clock = chip.get_line(SHCP_PIN)

# конфигурируем линии
config = gpiod.line_request()
config.consumer = "DS"
config.request_type = gpiod.line_request.DIRECTION_INPUT # входная
data.request(config)

config.consumer = "STCP"
config.request_type = gpiod.line_request.DIRECTION_OUTPUT # выходная
latch.request(config)

config.consumer = "SHCP"
config.request_type = gpiod.line_request.DIRECTION_OUTPUT # выходная
clock.request(config)

# подаем высокое питание на "защелку"
latch.set_value(1)

while True:
    # подаем сигнал начала передачи,
    # открываем и закрываем "защелку"
    latch.set_value(0)
    latch.set_value(1)

    # инициализируем список для приема бит с регистра
    byte_list = list()
    for i in range(8):
        # читаем первый бит (по факту, сигнал с ножки D7)
        # добавляем его в список
        byte_list.append(data.get_value())
        # подаем синхроипульс
        clock.set_value(1)
        clock.set_value(0)
    # делаем реверс полученных бит
    # так как они поступают в обратном порядке
    # выводим список в консоль
    byte_list.reverse()
    print(f"Состояние кнопок (0 - выкл, 1 - вкл): {byte_list}")
    # ждем 500 миллисекунд и продолжаем цикл 
    time.sleep(0.5)

Запустите программу. Нажимайте кнопки, наблюдайте за выводом программы в консоль. Попробуйте нажать несколько кнопок одновременно. Подумайте, как реализовать программную защиту от “дребезга“ контактов при использовании входного сдвигового регистра.

3. Модуль АЦП/ЦАП на на микросхеме PCF8591 #

Datasheet на микросхему PCF8591 8-bit

3.1. Что такое АЦП/ЦАП? #

ЦАП – цифро-аналоговые преобразователи – устройства, предназначенные для преобразования дискретного (цифрового) сигнала в непрерывный (аналоговый) сигнал. Преобразование производится пропорционально двоичному коду сигнала.

АЦП – аналого-цифровые преобразователи – устройства, предназначенные для преобразования непрерывных (аналоговых) сигналов в цифровые. АЦП осуществляет операции дискретизации и квантования. При дискретизации, отсчеты непрерывного сигнала берутся только в определенные моменты времени, а при квантовании значение сигнала в эти моменты времени округляется до одного из фиксированных уровней, квантованные уровни затем представляются в двоичном виде (рис. 10).

Рис. 10. Принцип работы аналого-цифровых преобразователей.

Примечание: не нужно путать понятия ЦАП и ШИМ. У них абсолютно разные принципы работы, хотя ШИМ часто используют, как дешовый и грубый способ имитации работы ЦАП. ЦАП выполняет частотное преобразование сигнала из цифровой формы в аналоговую, а ШИМ осуществляет управление выходным сигналом при помощи ширины импульса.

АЦП/ЦАП на микросхеме PCF8591

Для изучения работы с обработкой входных аналоговых сигналов на Repka Pi 3 мы воспользуемся модулем АЦП/ЦАП на основе микросхемы PCF8591 (рис. 11). Микросхема PCF8591 предоставляет 8-битный аналого-цифровой и 8-битный цифро-аналоговый преобразователи, позволяющие считывать (4 входа) и генерировать (1 выход) аналоговые значения. Для работы модуля требуется напряжение питания 2,5-6 В. Обмен данными с микросхемой осуществляется посредством I2C интерфейса.

Рис. 11. Модуль АЦП/ЦАП PCF8591 (слева - YL-40, справа - Adafruit).

На рисунке 11 представлены два варианта АЦП / ЦАП модулей построенных на основе микросхемы PCF8591. Они имеют один и тот же принцип работы и управления, но различаются по компонентам, распаянным на схеме и по разводке контактов. Так например на модуле Adafruit имеется контакты для параллельного подключения к линии SDA и SCL модуля других I2C устройств (разъемы слева и справа), на верхнюю часть платы дополнительно выведены контакты питания и “земля“. В тоже время на модуле YL-40 имеются дополнительные компоненты: потенциометр, фоторезистор, терморезистор, которые подключены к входам АЦП (0, 1 и 3) с помощью перемычек, а расположение входных контактов позволяет использовать его на макетной плате без дополнительной доработки. На обоих модулях имеются подтягивающие к 3,3 В резисторы номиналом 10 кОм для линий SDA и SCL. Таким образом модуль YL-40 хорошо подходит для прототипирования и обучения работы с микросхемой, а модуль Adafruit пригодится при сборке проектов.

Примечание: далее при работе со схемами в Fritzing будет использоваться макет модуля Adafruit, а для выполнения заданий рекомендую использовать модуль YL-40, так как на нем уже распаяны необходимые для примеров датчики и потенциометр.

Рассмотрим несколько формул, которые пригодятся нам при работе с входными аналоговыми сигналами. Так как при работе с модулями АЦП / ЦАП на входные линии Repka Pi 3 мы будем получать преобразованные микросхемой значения входного сигнала, т.е. целочисленные значения в интервале от 0 до 255.

Значения входного сигнала вычисляются по формуле:

V = (U_in / U_adc) * 255,

где:

V - значение со входа АЦП;

U_in - напряжение полученное АЦП на входе;

U_adc - напряжение питания.

Пример: дана электрическая цепь с фоторезистором, подключенным к входу АЦП. Напряжение цепи 3,3 В, напряжение на входе АЦП 1,2 В. Вычислить значение выдаваемое АЦП.

(1,2 / 3,3) * 255 = 92,7272 ~ 93.

Соответственно напряжение на входе АЦП можно вычислить по формуле:

U_in = (V / 255) * U_adc.

Пример: с АЦП получено значение 93, известно, что АЦП подключен к сети напряжением 3,3 В. Вычислить напряжение, поступившее на вход АЦП сигнала.

(93 / 255) * 3,3 = 1,2 В.

На основе входных значений от АЦП очень часто управляют другими устройствами, в том числе ШИМ (PWM), что требует преобразования значения из диапазона АЦП (0-255) в целевой диапазон (например 12 разрядный ШИМ PCA9685 имеет диапазон 0-4096).

((x - in_min) * (out_max - out_min) / (in_max - in_min)) + out_min,

где:

x - значение АЦП, которой необходимо преобразовать;

in_min - нижний предел текущего диапазона переменной x;

in_max - верхний предел текущего диапазона переменной x;

out_min - нижний предел целевого диапазона;

out_max - верхний предел целевого диапазона.

Примечание: драйвер сервоприводов / Servo driver - RoboIntellect controller m1 является расширенным аналогом PCA9685.

Иногда, требуется представить значение с АЦП в процентном отношении, например для вывода значения уровня громкости или “мощности”, для этого необходимо вычислить значение по формуле:

(V / 255) × 100%

Пример: получено значение от АЦП равное 93. Вычислить долю в процентном соотношении от максимального значения, выдаваемого АЦП (255).

93 / 255 × 100% = 36,47%

Примечание: 8-ми битные микросхемы АЦП / ЦАП отлично подходят для изучения способов программной обработки входных аналоговых сигналов с различных датчиков, и могут применяться при разработке устройств не требующих высокой точности измерений, но если у вас возникнет необходимость повысить точность измерений, то можно воспользоваться микросхемами АЦП / ЦАП разрядностью 10, 16, 24 бит и т.д. Например, 16 и 24 битные АЦП / ЦАП микросхемы используются в аппаратно-программных устройствах для распознавания и/или синтеза речи. Для справки, в Arduino используется 10-ти битный АЦП (0-1023), ЦАП - отсутствует (имитируется ШИМ); в ESP32 - 12-ти битный (0-4095) АЦП, и 8-ми битный ЦАП; в микроконтроллере “Амур“ (инженерный образец) установлены 12-ти битный АЦП, и 12-ти битный ЦАП.

3.2. Что такое интерфейс I2C и протокол SMBus? Подготовка RepkaOS к работе с I2C контроллерами по протоколу SMBus #

Интерфейс I2C (Inter-Integrated Circuit, IIC, меж-интеграционная цепь или последовательная асимметричная шина) — это последовательный, синхронный, полудуплексный двухпроводный внутрисхемный сетевой последовательный интерфейс, разработанный фирмой Philips в 1982 году и позволяющий передавать данные на высокой скорости (обычно до 100 кбит/с, в современных микросхемах до 2 мбит/с). Изначально этот интерфейс был разработан для обмена данными между контроллером и периферийными блоками внутри одного устройства.

Устройство подключенное к шине I2C может выполнять одну из двух ролей: master (ведущий) или slave (ведомый). I2C позволяет одновременно сосуществовать нескольким ведущим и ведомым устройствам на одной шине. Обмен данными происходит сеансами, которыми полностью управляет master. Каждое slave-устройство имеет 7-битный уникальный адрес по которому master передает данные. I2C позволяет подключать к одной шине до 127 slave-устройств одновременно при условии, что их адреса не совпадают.

I2C является протоколом связи, подходящий для передачи данных на короткие расстояния. Если вам необходимо передавать данные на большие расстояния, этот протокол не рекомендуется.

Физически шина I2C состоит из двух линий:

  • SCL или CLOCK (Signal CLock) - линия служит для тактирования, то есть для управления передачей данных и согласования всех устройств между собой.
  • SDA или DATA (Signal DAta) - линия предназначена для передачи данных.

Все устройства I2C имеют сигнальные выводы с "открытым коллектором" или "открытым стоком" (в зависимости от реализации). Когда выходной транзистор закрыт — на соответствующей линии через внешний подтягивающий резистор устанавливается высокий уровень, когда выходной транзистор открыт — он притягивает соответствующую линию к земле и на ней устанавливается низкий уровень. Поэтому наличие подтягивающих резисторов номиналом 10 кОм на линиях SDA и SCL Repka Pi 3 обязательно и должны быть подключены линии питания 3,3 В.

Примечание: если вы будите использовать устройства с поддержкой I2C интерфейса в виде готовых модулей, то настоятельно рекомендую ознакомится с инструкцией к ним, так как скорее всего на них уже могут быть установлены подтягивающие резисторы.

SMBus (System Management Bus) — последовательный протокол обмена данными для устройств. Основан на шине I2C, но использует более низкий уровень сигнального напряжения (3,3 В). Был предложен Intel в 1995 году. С версии SMBus 2.0 (2000 год) используется для внутренних устройств компьютера. По сравнению с I2C, SMBus работает на скоростях до 100 кбит/сек и не поддерживает скорость 0,4 и 2 мбит/сек.

Для работы с АЦП/ЦАП PCF8591 мы будем использовать протокол SMBus, так как поддержка этого протокола реализована на уровне драйверов ядра Linux. И данный протокол рекомендуется разработчиками ядра к использованию при написании драйверов устройств.

Ниже приведен перечень пакетов Linux, которые понадобятся нам для работы с контроллерами I2C и протоколом SMBus.

Важно! По умолчанию, доступ к I2C-устройствам разрешен только администратору (root), что требует повышение прав доступа при запуске утилит и выполнении программ из пользовательского режима. Для разрешения доступа пользователей к контроллерам I2C необходимо:

  1. Создать группу i2c (в RepkaOS версии 1.0.4 есть “из коробки”) командой sudo groupadd i2c.

  2. Назначить группу i2c владельцем I2C-контроллера, например команда sudo chown :i2c /dev/i2c-1 изменяет владельца для первого контроллера.

  3. Разрешить членам группы i2c читать и писать данные в I2C-контроллер, например команда sudo chmod g+rw /dev/i2c-1 разрешает чтение и запись данных в первый контроллер.

  4. Добавить пользователя в группу i2c командой sudo usermod -aG i2c user_name.

  5. Выйти из системы и зайти заново, для применения настроек.

В RepkaOS версии 1.0.4 установлены “из коробки“:

  • libi2c0 и libi2c-dev - бинарные библиотеки для программирования, предоставляющие доступ ко всем устройствам контроллера I2C из пользовательского пространства.
  • i2c-tools - пакет программ, предназначенных для управления контроллерами I2C и подключенными к ним устройствами.

Пакеты требующие установки:

  • python3-smbus - модуль Python 3 обеспечивающий работу с интерфейсом I2C посредством протокола SMBus. Ядро Linux должно иметь поддержку интерфейса устройства I2C и драйвер адаптера шины. Установка модуля выполняется командой:

    sudo apt install python3-smbus
    

Важно! для использования протокола SMBus необходимо использовать любую распиновку портов на 40 pin разъеме Repka Pi 3 кроме первого варианта. Для изменения распиновки используйте утилиту repka-config с правами администратора. После изменения распиновки перезагрузите устройство.

Кратко рассмотрим возможности программ из пакета i2c-tools на примере тестирования подключенного к I2C-1 контроллеру (2 вариант распиновки) модуля АЦП/ЦАП PCF8591 (см. ниже рис. 13).

i2cdetect - используется для обнаружения и перечисления всех контроллеров I2C, доступных и известных Linux.

Для перечисления всех доступных контроллеров используйте команду i2cdetect -l

Важно! На Repka Pi 3 доступно 3 контроллера I2C:

  • Контроллер i2c-0 используется HDMI портом.

  • Контроллер i2c-1 доступен для 2-6 вариантов распиновки на контактах 3-SDA и 5-SCL.

  • Контроллер i2c-2 доступен для 4 варианта распиновки на контактах 27-SDA и 28-SCL.

Для получения информации о всех подключенных устройствах к конкретному контроллеру I2C и их адресах введите команду i2cget -y 1.

Если подключение модуля АЦП/ЦАП выполнено правильно, то в выведенной таблице на пересечении строки 0×40 и столбца 8 должна появиться соответствующая отметка.

i2cget - позволяет получать данные от ведомого устройства I2C по их адресу. Например, команда i2cget -у 1 0×48 0×40 позволяет получить значение АЦП для входа AIN0.

i2cset - используется для записи данных по указанному внутреннему адресу ведомого устройства I2C. Операция записи I2C может быть защищена на уровне устройства, или любой внутренний адрес может быть доступен только для записи. Например, выполнение команды i2cget -у 1 0×48 0×40 0xff подаст на выход ЦАП максимальное напряжение 3,3 В (проверьте мультиметром), а команда i2cget -у 1 0×48 0×40 0×0 установит напряжение 0 В.

i2cdump - выводит дамп данных с любого ведомого устройства I2C.

i2ctransfer - используется для чтения или записи нескольких байтов в одной команде.

3.3. Управление модулем АЦП/ЦАП с использованием протокола SMBus #

Прежде чем начать работу с модулем АЦП/ЦАП необходимо разобраться с некоторыми особенностями работы микросхемы PCF8591, в частности:

  1. Адрес устройства на шине I2C: 0x48 (0b_0100_1000);

  2. Команда для доступа к данным устройства имеет следующий формат: 0b_0ABB_0CDD, где

    • A - состояние АЦП (0 - выключен, 1 - включен);
    • B - конфигурация входов (см. стр. 6 документации);
    • C - режим инкрементации (0 - не автоматический, 1 - автоматический);
    • D - выбор входного канала (0b00 - 0, 0b01 - 1, 0b10 - 2 и 0b11 - 3).

Например, для получения информации с входных каналов при включенном АЦП необходимо передавать следующие команды:

  1. для записи значения выхода ЦАП AOUT: 0×40 (0b_0100_0000);

  2. для чтения значения входа AIN0: 0×40 (0b_0100_0000);

  3. для чтения значения входа AIN1: 0×41 (0b_0100_0001);

  4. для чтения значения входа AIN2: 0×42 (0b_0100_0010);

  5. для чтения значения входа AIN3: 0×43 (0b_0100_0011).

Также необходимо учитывать, что при чтении значений АЦП микросхема PCF8591 дважды передает старые данные, это удобно для отслеживания динамики изменения значений, но при этом для получения актуальных значений требует 3 раза читать данные из памяти микросхемы.

Для работы с АЦП/ЦАП соберите схему, представленную на рисунке 12. Для выполнения задания вам понадобятся: макетная плата, модуль АЦП/ЦАП на микросхеме PCF8591, фоторезистор, терморезистор, светодиод, резистор номиналом 220 Ом, потенциометр, комплект проводов. Подключите синий провод к 3-му контакту GPIO (SDA шина), зеленый к 5-му контакту (SCL шина).

Рис. 12. Макет схемы подключения АЦП / ЦАП к Repka Pi 3.

Примечание: если вы используете модуль АЦП / ЦАП YL-40, то вам не нужно собирать всю схему, а только подключить светодиод с токоограничивающим резистором на 220 Ом (или придумать способ не подавать больше 1,9 В на выход ЦАП) к выходу AOUT модуля и убедится, что установлены все перемычки.

Введите текст программы.

import os # модуль для работы с ОС
import time # модуль работы со временем
from smbus import SMBus # модуль для работы с I2C контроллером

# адрес устройства (PCF8591) на шине I2C
DEV_ADDR = 0x48

# словарь адресов данных для входных каналов АЦП
adc_channels = {
    'AIN0':0b_0100_0000, # 0x40 (фоторезистор)
    'AIN1':0b_0100_0001, # 0x41 (терморезистор)
    'AIN2':0b_0100_0010, # 0x42 (свободный)
    'AIN3':0b_0100_0011, # 0x43 (потенциометр)
}
# адрес данных для ЦАП
dac_channel = 0b_0100_0000 # 0x40

# получаем доступ к контроллеру I2C 
# (1 контроллер I2C для 2-5 вариантов распиновки - контакты 3-SDA и 5-SCL)
# (2 контроллер I2C для 4 варианта распиновки - контакты 27-SDA и 28-SCL)
bus = SMBus(1)
# устанавливаем значение для ЦАП
dac_value = 0

while(True):
    os.system('clear') # очищаем терминал
    print("Нажмите Ctrl + C для завершения работы...\n")
    # запускаем "бесконечный" цикл для обмена информацией с АЦП/ЦАП
    for channel in adc_channels:
        # получаем значения из АЦП для всех аналоговых входов
        bus.write_byte(DEV_ADDR, adc_channels[channel])

        # выбрасываем старое значение АЦП дважды (особенность устройства)
        # т.к. нам не нужно сравнивать новое значение с предыдущим
        bus.read_byte(DEV_ADDR) 
        bus.read_byte(DEV_ADDR) 

        value = bus.read_byte(DEV_ADDR) # получаем новое значение
        # если текущая лини 3, то получаем значение с потенциометра
        if channel == 'AIN3': 
            dac_value = value
        # выводим в консоль значение АЦП для каждого входа
        print('Канал ' + channel + ' ---> ' + str(value))
    # устанавливаем значение для ЦАП (изменяем яркость светодиода)
    bus.write_byte_data(DEV_ADDR, dac_channel, dac_value)
    # ждем 100 миллисекунд
    time.sleep(0.1)

Запустите программу, проверьте работу следующих элементов:

  • посветите фонариком на фоторезистор, наблюдайте за изменением значения канала AIN0;
  • поворачивайте ручку потенциометра, наблюдайте за изменением значения канала AIN3 и яркостью свечения светодиода.

Внимание! Не нужно проверять работу терморезистора путем его нагрева огнем или феном! Во-первых это не безопасно, а во-вторых сломаете! Можно просто подышать на него ;).

Заключение #

Данная часть завершает цикл статей, предназначенных для людей только начинающих знакомство с использованием Repka Pi 3, и желающих изучить основы программирования для управления внешними устройствами (написанием драйверов устройств), подключенными к GPIO-разъему. На протяжении трех частей мы изучили основы настройки и использования программного управления GPIO Repka Pi 3 через системные вызовы к драйверу символьных устройств с помощью библиотеки libgpiod. На практических примерах рассмотрели работу с линиями в режиме вывода, управляя светодиодами и электромагнитными реле, изучили возможность расширения входных и выходных портов. Познали основы работы с линиями в режиме ввода, использовали их для обработки цифровых и аналоговых сигналов, попрактиковались с использованием входного и выходного сдвиговых регистров, а также рассмотрели работу с ЦАП/АЦП модулем, управляя им через протокол SMBus.

Напомню, что библиотеки libgpiod доступна не только для Python, но и для C#, C++, Rust, Java (Kotlin) и конечно же Си. Так что изучив основы, вы можете смело реализовывать приведенные в статье примеры на этих языках, изменяя только синтаксис, так как логика работы с библиотекой останется неизменной.

Сделано много, но Вам предстоит сделать еще больше. Желаю успехов в этом нелегком но благородном деле! Но я не прощаюсь с вами. Как и было обещано, по мере возможности, статьи будут расширяться и дополняться новой полезной информацией, примерами и заданиями. При этом, очень надеюсь на обратную связь.

P.S. С моей стороны начало положено - дело за Вами!


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

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

круто!

Темы

Навигация

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