- PVSM.RU - https://www.pvsm.ru -

Как увеличить количество пинов на esp32?

Как увеличить количество пинов на esp32? - 1

Картинка BRGFX, Freepik [1]

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

Решил я это всё своеобразным образом, который и описан ниже. Возможно, кому-то ещё будет интересно и полезно...

Дело в том, что этот проект я строю на базе esp32 ввиду того, что данные микроконтроллеры у меня валяются по дому коробками — и это не фигура речи, а буквально так.

Однако проект требует достаточно большого количества подключаемых устройств — восемь цифровых датчиков Холла (KY-003), один аналоговый датчик расстояния (тоже Холла, KY-035), три шаговых двигателя Nema 17, один коллекторный двигатель.

Дело с двигателями осложняется также тем, что каждый из шаговых двигателей требует по три контакта (pulse, direction, enable), так как мне пришлось вынужденно использовать драйвер ТВ6600. Хотел изначально использовать также имеющиеся в наличии L298N, однако они не потянули шаговые двигатели Nema 17 — работали на пределе и раскалялись буквально за 6 секунд работы с последующим температурным отказом.

Да, многие хают ТВ6600, и советуют выбирать более профессиональные версии, тем не менее его цена в моём случае решает: 500 руб. против 2500 руб. Да и задача у меня не такая уж сложная и не требующая круглосуточной работы, наподобие фрезерования 3D-картин, так что мне сойдёт…

Итак, у нас проблема — не хватает пинов (весьма наглядное описание пинов есть здесь [2]). Как её решать? Наверное, можно поискать какую-либо плату расширения, допускающую подключение большого количества периферии — например, у меня с незапамятных времён лежит такая для Arduino:

Как увеличить количество пинов на esp32? - 2

Но что-то не захотелось мне с этим возиться и разбираться, и пришла в голову мысль сделать всё гораздо более простым способом: с помощью соединения микроконтроллеров через шину I2C.

Природная лень заставила меня поискать готовые решения, однако здесь пришлось натолкнуться на практически полное их отсутствие (связь двух и более esp32 между собой), таким образом, «беглый гуглинг» практически ничего не дал (что, однако, не исключает их наличия где-либо — это говорит только о том, что я их не нашёл). Ну ок, начинаем вникать…

Шина I2C требует для реализации связи всего двух проводов: SCL (Serial Clock) — сигнал тактирования и SDA (Serial Data) — передаваемые данные (но, вообще говоря, в реальности больше, так как требуется ещё как минимум заземление, а ещё желательно и питание).

Эта шина относительно низкоскоростная (стандартно — 100 кбит/сек, на современных устройствах — до 400 кбит/сек), тем не менее она полностью удовлетворяет моим требованиям, так как рамки моего проекта не требуют передачи каких-то тяжёлых данных.

Работа в рамках I2C подразумевает, что любое подключённое к этим линиям (SCL, SDA) устройство может занимать одну из двух позиций: ведущую (master) или ведомую (slave). Обычно, говоря об этой шине, подразумевают работу в ней одного master-a и ряда slave-ов, хотя не запрещается и работа нескольких master-ов (насколько я понял, реализовано это через использующуюся master-ом функцию endTransmission(), которая, если вызывается с параметром true (endTransmission(true)), после передачи сообщения посылает стоп-сообщение, освобождая линию I2C, и она по сути свободна для использования каким угодно ещё master-ом).

Общение между устройствами в рамках этой шины всегда инициирует master, вызывая соответствующее ведомое устройство, обращаясь по его адресу, который является семибитным, и в сети одновременно могут присутствовать 128 устройств с адресами, обозначенными числами от 0 до 127, где 0 согласно спецификации [3] — адрес общего вызова, а ведомым можно назначать адреса от 1 до 127 (это последнее уже не по спецификации, а «по факту», если использовать библиотеку wire). Хотя на самом деле всё несколько сложнее, и если строго следовать спецификации (ссылка на которую выше), то это оставляет нам только 112 адресов:

Как увеличить количество пинов на esp32? - 3

Но если вы сами проектируете систему и вообще «правила не для вас», то 127. И, вроде как при сильном желании даже 128 :-) Но тут сделаю ремарку: используя библиотеку wire, я пытался обращаться на нулевой адрес. С «нулевым» же результатом. Видимо, если только самому библиотеку писать, так как люди вроде как используют и его… Но тут «дальше мои полномочия всё»… :-0)

И кстати говоря, почему стандарт такой странный — семибитный? А вот почему, всё оттуда же, из спецификации [3]:

Как увеличить количество пинов на esp32? - 4

А почему 127 устройств? Для этого нам всего лишь нужно открыть стандартный калькулятор Windows и переключить его в режим «программист»:

Как увеличить количество пинов на esp32? - 5

Затем мы переключимся в десятичный режим (показано ниже стрелкой слева) и забьём число 127. После чего мы увидим в битовом выражении наверху справа, что число 127 для компьютера кодируется семью знаками:

Как увеличить количество пинов на esp32? - 6

Теперь, если мы попробуем стереть это число и забить туда 128, то увидим наверху справа, что в битовом выражении это число кодируется уже восемью знаками. А наша шина — семибитовая (т. е. можно сказать семизнаковая), и этот адрес просто не поместится в рамках её стандарта (тут следует сделать оговорку — возможно, это не шина семибитовая, а библиотека wire, но я глубже не копал, честно признаюсь):

Как увеличить количество пинов на esp32? - 7

Кстати говоря, это подробное описание я привёл не просто так — оно может быть полезно для тех, кто не дружит с шестнадцатеричной системой счисления (потому что в стандартных примерах библиотеки wire.h показано использование шестнадцатеричных чисел в качестве адресов).

То есть если вы захотите, так же как и я, увеличить количество пинов, соединив между собой одну или более плат esp32, то вам понадобится назначить каждой из них адрес в рамках этой шины (всем, кроме master-устройства(в), так как, насколько я понимаю, им система неявно сама выдаёт master-адрес при инициализации).

Делается это крайне просто:

  1. Запускаете калькулятор Windows.
  2. Переключаетесь в десятичный режим (Dec).
  3. Вбиваете любое число от 1 до 127.
  4. Переключаетесь в шестнадцатеричный режим (Hex).
  5. Копируете получившееся число из окошка наверху и вставляете с припиской «0х...» в свой код (например, 0х7F).

Как увеличить количество пинов на esp32? - 8

И это всё! Теперь, когда у вашего ведомого устройства есть адрес, к нему легко может обратиться ведущее…

Кстати говоря, это (программное назначение адреса) касается только моего случая, так как, насколько мне известно, имеющиеся в продаже устройства (I2C ЖК-экраны и т. д.) уже содержат жёстко прошитые I2C-адреса (у некоторых их можно менять перемычкой).

Что ещё нам нужно знать о шине I2C? Один из самых важных моментов состоит в том, что она требует для своей работы подтягивающие к питанию (pull-up) резисторы:

Как увеличить количество пинов на esp32? - 9

Картинка: randomnerdtutorials.com [4]

Об этом даже упоминает и производитель esp32:

Как увеличить количество пинов на esp32? - 10

Хорошая статья на тему, что такое подтягивающие резисторы, и зачем они нужны для esp32, есть вот здесь [5].

Тем не менее, насколько я знаю, ряд пинов esp32 уже содержит подтягивающие резисторы. Обратимся к документации [6] производителя esp32:

Как увеличить количество пинов на esp32? - 11

И мы увидим, что если явным образом программно не определено, пины, имеющие подтягивающие резисторы (45 кОм при подтягивании к питанию, и такой же на 45 кОм при подтягивании к земле — это я уже в даташите глянул), находятся в высокоимпедансном состоянии. Разбираемся, что это за состояние… Вот что нам говорит Вики [7] по этому поводу:

Высокоимпедансное состояние, высокоомное состояние, Z-состояние или состояние «Выключено» — состояние выхода цифровой микросхемы, при котором он «отключается» от сигнальной шины, к которой подключены несколько передатчиков сигналов. Таким образом, сопротивление между её внутренней схемой, формирующей выходной сигнал, и внешней схемой, очень большое. Вывод микросхемы, переведённый в состояние «Выключено», ведёт себя как не подключённый к ней. Внешние устройства (микросхемы), подключённые к этому выводу, могут изменять напряжение на нём по своему усмотрению (в некоторых рамках), не влияя на работу микросхемы. И наоборот — схема не мешает внешним устройствам менять напряжение на выводе микросхемы.

И ещё: Состояние «Выключено» применяется, когда устройству приходится временно отключаться от шины — например, в программаторах, мультиплексорах, многоточечных интерфейсах передачи данных наподобие JTAG, I2C или USB и т. д.

То есть получается, что такая «подтяжка» важна для работы в рамках шины I2C. А как её реализовать, учитывая, что у нас вроде как уже есть резисторы на борту платы?

Реализуется это с помощью функции pinMode. Например, так:

pinMode (21, INPUT_PULLUP)

pinMode (22, INPUT_PULLUP)

Хорошая статья про функции подтягивания есть здесь [8]. В ходе экспериментов пробовал вешать эти функции и на master, и на slave (несмотря на то, что это не совсем входы, а двунаправленное общение как бы). Разницы не увидел никакой.

Приходилось видеть рассуждения в сети, что на пинах интерфейса I2C такая подтяжка (программная) не работает, и при желании использовать именно внутренние подтягивающие резисторы люди включали подтягивание на других пинах и соединяли их с I2C-пинами с помощью перемычек. Забегая вперёд – у меня всё хорошо работает и без этого (возможно, это связано с тем, что у меня весьма малое расстояние – всего 30 см между платами). Или же оно реализовано аппаратно платой (на пинах, определяемых для I2C).

По крайней мере, из того, что мне удалось выяснить:

  1. Если расстояния между устройствами малы, то особого смысла в подтягивающих резисторах нет, и все устройства используют один и тот же источник питания.
  2. Внутренние встроенные в плату pull-up резисторы слишком большого номинала. Да, их можно использовать, но это не позволит достаточно быстро осуществлять подтягивание [9]: «Передача/Приём сигналов осуществляется прижиманием линии в 0, в единичку устанавливается сама за счёт подтягивающих резисторов. Их ставить обязательно всегда! Стандарт! Резисторы на 10к оптимальны. Чем больше резистор, тем дольше линия восстанавливается в единицу (идёт перезаряд паразитной ёмкости между проводами), и тем сильней заваливаются фронты импульсов, а значит скорость передачи падает. Именно поэтому у I2C скорость передачи намного ниже, чем у SPI. Обычно IIC работает либо на скорости 10кбит/с — в медленном режиме, либо на 100кбит/с в быстром. Но в реальности можно плавно менять скорость вплоть до нуля.»

В любом случае, было бы интересно прочитать в комментах, если вы знаете что-то больше по этому вопросу…

Мы же рискнём соединить платы просто напрямую. Теперь — куда нам необходимо подключаться?

Если мы посмотрим на распиновку, то увидим, что интерфейс расположен на 22 (SCL) и 21 (SDA) пинах:

Как увеличить количество пинов на esp32? - 12

Картинка: circuits4you.com [10]

И именно сюда же и предлагает подключаться производитель:

Как увеличить количество пинов на esp32? - 13

Картинка: docs.espressif.com [11]

Тем не менее, плата поддерживает два таких интерфейса, они могут быть конфигурированы на любом из универсальных портов ввода-вывода.

Для работы с этой шиной будем использовать стандартную библиотеку wire.h.

Здесь есть один любопытный момент, который касается назначения пинов под I2C: если мы обратимся к описанию [12] библиотеки wire.h, на сайте Arduino, то среди функций этой библиотеки
мы не увидим важнейших, которые мы могли бы использовать (о них ниже).

Как увеличить количество пинов на esp32? - 14

И даже на сайте espressif [13] упоминается о возможности назначения любых пинов, только для master-устройств:

Как увеличить количество пинов на esp32? - 15

Приходилось видеть и другой вариант, но также для master-устройств, для назначения на любые пины, например 18-19:

Wire.begin(18,19)

Тем не менее, существует и ещё один вариант [14], который гораздо более универсален и подходит как для master-a, так и для slave-a — мне он кажется самым оптимальным и нравится больше всего:

Как увеличить количество пинов на esp32? - 16

Итак, попробуем соединить платы, как было описано выше, кроме того, соединим у обеих плат GND и Vin (пробовал и без этого, тоже работает, но идёт сильная потеря пакетов).

Загрузим на каждую из esp32 свою часть примера библиотеки wire.h: на левую — сканер адресов подключённых устройств, на правую — slave. И мы видим, что slave-устройство отлично обнаруживается:

Как увеличить количество пинов на esp32? - 17

А теперь попробуем установить на ведущее устройство код master-части. Всё работает ОК, потерь пакетов не наблюдается:

Как увеличить количество пинов на esp32? - 18

Теперь переключим монитор порта на slave-устройство и посмотрим, что пишет оно. Тоже потерь пакетов не наблюдается:

Как увеличить количество пинов на esp32? - 19

UPD: после долгого тестирования обнаружил, что в некоторых ситуациях всё-таки наблюдаются потери пакетов, однако от этого удалось избавиться очень простым способом: провода I2C и провода питания (Vin, GND) смотал друг с другом, наподобие витой пары. После этого потери пакетов прекратились. Видно, как отчитывается функция endTransmission() [15], где 0 означает успешную передачу:

Как увеличить количество пинов на esp32? - 20

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

В качестве такого датчика возьмём уже упомянутый цифровой (то есть который выдаёт только значения LOW и HIGH) датчик Холла KY-003, а в качестве шагового двигателя также упомянутый выше Nema 17 и драйвер двигателя ТВ6600:

Всё работает как и должно! Таким вот нехитрым образом вы можете увеличить количество пинов esp32, соединив между собой n-плат. В показанном выше видео код опроса датчика помещён прямо в цикле loop, что есть не совсем правильно и сделано это для ускорения демонстрации. В реальном проекте, чтобы не занимать процессорное время, датчик надо подключать через функцию attachInterrupt(interrupt, function, mode) [16]. В своём проекте собираюсь сделать именно так. Мало того — чтобы постоянно не занимать линию опросами датчиков, все датчики повешу на master, а все двигатели (ну или почти все) — на slave.

Здесь я выложил [17] упрощённый пример показанного в видео, где код управления шаговым двигателем заменён на код светодиода (срабатывание датчика на master-e зажигает встроенный светодиод на slave-e).

Я не ставил целью строить распределённую систему (одна esp32 — в одной комнате, другая — в другой и т. д.), поэтому для моих целей (простое расширение количества пинов) всё работает хорошо. Если вам нужна будет именно распределённая система, возможно, вам придётся углубиться в изучение этой темы дальше. Этот пост не претендует на идеальное знание темы и «учебника» по ней. Скорее это история про то, как у меня возникла проблема и как я её решил.

Соединение между платами по беспроводным каналам (wifi, bluetooth) не рассматривал, так как в моём случае это будет в высшей мере странно — «забивание гвоздей микроскопом» :-)

Автор:
DAN_SEA

Источник [18]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/i2c/386797

Ссылки в тексте:

[1] BRGFX, Freepik: https://www.freepik.com/free-vector/train-children_8131463.htm#query=cartoon%20locomotive&position=0&from_view=search&track=country_rows_v1

[2] есть здесь: https://developer.alexanderklimov.ru/arduino/esp32/

[3] согласно спецификации: https://sysadminmosaic.ru/_media/i2c/i2c_description_rus.pdf

[4] randomnerdtutorials.com: https://randomnerdtutorials.com/esp32-i2c-communication-arduino-ide/

[5] вот здесь: https://kotyara12.ru/iot/i2c/

[6] к документации: https://docs.espressif.com/projects/arduino-esp32/en/latest/api/gpio.html?highlight=pullup#internal-pullup-and-pulldown

[7] Вики: https://ru.wikipedia.org/wiki/%D0%92%D1%8B%D1%81%D0%BE%D0%BA%D0%BE%D0%B8%D0%BC%D0%BF%D0%B5%D0%B4%D0%B0%D0%BD%D1%81%D0%BD%D0%BE%D0%B5_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5

[8] есть здесь: https://xn--80aanbzjgivicdg0b3l.xn--p1ai/statya-czifrovye-vhody-arduino-podtyazhka-pull-up-i-pull-down

[9] но это не позволит достаточно быстро осуществлять подтягивание: http://easyelectronics.ru/interface-bus-iic-i2c.html#:~:text=%D0%9F%D0%B5%D1%80%D0%B5%D0%B4%D0%B0%D1%87%D0%B0/%D0%9F%D1%80%D0%B8%D0%B5%D0%BC%20%D1%81%D0%B8%D0%B3%D0%BD%D0%B0%D0%BB%D0%BE%D0%B2,%D0%B2%D0%BF%D0%BB%D0%BE%D1%82%D1%8C%20%D0%B4%D0%BE%20%D0%BD%D1%83%D0%BB%D1%8F.

[10] circuits4you.com: https://circuits4you.com/2018/12/31/esp32-devkit-esp32-w%D0%A2room-gpio-pinout/

[11] docs.espressif.com: https://docs.espressif.com/projects/arduino-esp32/en/latest/api/i2c.html?highlight=i2c#i2c-master-apis

[12] обратимся к описанию: https://www.arduino.cc/reference/en/language/functions/communication/wire/

[13] даже на сайте espressif: https://docs.espressif.com/projects/arduino-esp32/en/latest/api/i2c.html?highlight=i2c#i2c-master-apis:~:text=bool%20begin(int%20sdaPin%2C%20int%20sclPin%2C%20uint32_t%20frequency)

[14] существует и ещё один вариант: https://docs.espressif.com/projects/arduino-esp32/en/latest/tutorials/io_mux.html?highlight=i2c%20frequency

[15] endTransmission(): https://www.arduino.cc/reference/en/language/functions/communication/wire/endtransmission/

[16] attachInterrupt(interrupt, function, mode): https://arduino.ru/Reference/AttachInterrupt

[17] Здесь я выложил: https://drive.google.com/file/d/1aMMUGwtvSxxSC42wU7B4c1DrIyY4r-Ty/view?usp=sharing

[18] Источник: https://habr.com/ru/companies/ruvds/articles/755700/?utm_source=habrahabr&utm_medium=rss&utm_campaign=755700