Расширители портов Ардуино
Расширители портов Arduino PCF8574 и PCF8575 приходят на выручку, когда для проекта не хватает буквально одного-двух свободных портов. А иногда портов хватает, но не хочется тянуть к другой части конструкции пучок проводов. Допустим, на передней панели устройства надо разместить несколько кнопок и светодиодов. Надежнее и проще соединить их с основной платой всего двумя проводами шины данных, а не шлейфом или жгутом, не так ли?
Для таких ситуаций предназначены различные расширители (экспандеры) портов Arduino.
Обычно выводы микроконтроллера реализуют несколько различных функций, поэтому расширители бывают разные:
- Расширитель стандартных портов ввода-вывода GPIO
- Расширитель выходов ШИМ
- Расширители аналоговых входов – мультиплексоры и внешние АЦП
Отдельно стоит упомянуть цифро-аналоговые преобразователи (ЦАП) и расширители адресного пространства шины I2C. Эти устройства не дублируют функции портов напрямую, но расширяют возможности микроконтроллеров.
В первой статье цикла мы поговорим о самых простых и полезных экспандерах, которые работают в качестве цифровых портов ввода-вывода. Это микросхемы PCF8574 и PCF8575. Они устроены и работают абсолютно идентично, и различаются только количеством портов.
Выбираем модуль расширителя для Arduino
Самый популярный и недорогой модуль изготовлен на микросхеме PCF8574 (рис. 1)

Рис. 1. Популярный модуль расширителя портов PCF8574
Достоинства:
- Низкая цена.
- Модули можно соединять цепочкой, просто вставляя штекеры одного модуля в гнезда предыдущего. Не забудьте установить перемычками разные адреса модулей!
Недостатки:
- Нельзя вставить прямо в макетную плату (рекомендую перепаять разъем портов на обратную сторону).
- Всего восемь портов в одном модуле.
Если вы настроены на более серьезные проекты, закажите на Aliexpress 16-разрядный модуль на PCF8575. Настоятельно рекомендую именно модуль, изображенный на рис. 2.

Рис. 2. Модуль расширителя портов PCF8575
Достоинства:
- Вдвое больше портов.
- Встроенный источник питания на 3.3 вольта, можно питать другие модули.
- Встроенное согласование логических уровней для шины I2C при разном напряжении питания.
- Удобный формат для макетной платы.
Недостатки:
- Выше цена.
Принцип работы расширителя портов GPIO PCF8574/PCF8575
Обмен данными происходит по шине I2C. Для подключения к плате Arduino требуется лишь четыре провода, включая питание. Адрес расширителя задается тремя перемычками на входах A0…A2, поэтому к шине можно одновременно подключить восемь одинаковых микросхем и получить максимум 8*8=64 дополнительных порта с PCF8574 или 8*16=128 с микросхемой PCF8575.
Чтобы вывести данные в порт, записывают байт данных по адресу модуля на шине I2C. Чтобы прочитать данные с порта, читают байт по этому же адресу. Байт всегда пишется и читается целиком, работа с отдельными разрядами происходит программно.
Выходы микросхемы одновременно являются входами, и никакого служебного регистра, определяющего назначение вывода, нет. Есть только регистр-защелка, в который записывают выходной байт. Как такое возможно?
Порты работают по схеме, аналогичной открытому коллектору и оснащены внутренними подтягивающими резисторами. Если в выход записан логический ноль, то открывается выходной транзистор, который принудительно тянет вывод «на землю». Чтение из такого порта всегда будет возвращать ноль.
Будьте осторожны – при подаче прямого напряжения питания на вывод с низким уровнем или при превышении допустимого тока 50 мА вы испортите микросхему!
Чтобы использовать порт как вход, запишите в него единицу. В этом случае внутренний транзистор будет закрыт, а результат чтения будет определяться внешним логическим уровнем, приложенным к выводу. Свободный вывод подтянут к питанию встроенным резистором.
Чтобы одновременно использовать часть портов как входы, а часть как выходы, перед каждой записью байта данных в экспандер необходимо при помощи операции «логическое ИЛИ» накладывать маску из единиц на те разряды, которые соответствуют входам. Вот и все)))
Генерация прерывания
Расширители портов PCF857* генерируют импульс прерывания низкого уровня на выходе INT при любом изменении входного сигнала на любом входе микросхемы. Это удобно, если расширитель обслуживает кнопочную панель. Но вы должны сами определить в обработчике прерывания, какая кнопка была нажата или отпущена. Генератор прерывания оснащен фильтром подавления дребезга контактов.
Пример 1. Использование модуля PCF8574
Соберем простую схему из четырех светодиодов, модуля PCF8574 и платы Arduino (рис. 3 и 4). При такой схеме включения нам даже не требуются гасящие резисторы для светодиодов. Ток протекает через светодиод и встроенный резистор, подключенный к шине питания.

Рис. 3. Схема подключения модуля PCF8574

Рис. 4. Макет схемы с модулем PCF8574
Скопируйте и запишите в плату Arduino скетч 1:
// Библиотека для работы с I2C #include <Wire.h> // Адрес модуля на шине (A0, A1, A2 = 0) int address = 0x38; // Данные, прочитанные из модуля uint8_t dataReceive; // Данные для записи в модуль uint8_t dataSend; void setup() { Wire.begin(); Serial.begin(9600); // Высокий уровень во все порты PCF8574 dataSend = B11111111; pcf8574_write(dataSend); } void loop() { // Читаем байт из модуля dataReceive = pcf8574_read(); // Выводим в монитор в двоичном формате Serial.println(dataReceive, BIN); // Сдвигаем биты влево на полубайт dataSend = dataReceive << 4; // Накладываем битовую маску dataSend |= B00001111; // Записываем байт в модуль pcf8574_write(dataSend); delay(500); } // Процедура записи байта в модуль void pcf8574_write(uint8_t dt) { Wire.beginTransmission(address); Wire.write(dt); Wire.endTransmission(); } // Процедура чтения байта из модуля int8_t pcf8574_read() { Wire.beginTransmission(address); Wire.endTransmission(); Wire.requestFrom(address, 1); return (Wire.read()); }
Во все порты микросхемы изначально записывается высокий уровень, поэтому порты P0…P3 могут работать, как входы.
Уровни на выводах порта считываются каждые 500 мс и результат считывания выводится в монитор. Если вы соединяете один из входов P0…P3 с общим проводом, в его разряде появляется ноль. Затем считанное значение сдвигается влево на четыре бита, результат выводится в порт и гаснет один из светодиодов. Например, если прочитан ноль на выводе P0, то погаснет светодиод, подключенный к выводу P4.
Обратите внимание, что мы должны перед каждой записью в расширитель наложить битовую маску из единиц на все разряды, которые должны быть входами: dataSend |= B00001111;
Подпрограммы работы с шиной I2C предельно упрощены, никакие ошибки не обрабатываются.
Совет: для поиска и проверки адреса модуля на шине I2C можно использовать простой скетч. Он выводит в терминал адреса всех устройств, которые отвечают на запрос шины.
Пример 2. Использование модуля PCF8575
Особенность модуля PCF8575 состоит в том, что у него 16 портов, поэтому в него всегда записывают по два байта и читают по два байта. Это правило надо соблюдать, даже если второй байт не нужен.
Немного изменим схему. Светодиоды подключим к портам P10…P13, а соединять перемычкой с общим проводом будем порты P00…P03 (рис. 5 и 6).

Рис. 5. Схема подключения модуля PCF8575

Рис. 6. Макет схемы с модулем PCF8575
В скетче 2 сначала записываются единицы во все порты, затем каждые 500 мс читается их состояние. Процедура чтения возвращает 16-разрядное слово, которое разделяется на байты. Содержимое младшего байта (выводы P00…P07) копируется в старший байт и выгружается обратно в модуль. Если соединить с общим проводом один из выводов P00…P03, то погаснет один из светодиодов, подключенных к P10…P13.
// Библиотека для работы с I2C #include <Wire.h> // Адрес модуля на шине по умолчанию int address = 0x20; // Данные, прочитанные из модуля uint8_t hi, lo; uint16_t dataReceive; uint8_t dataHighByte; // Старший байт (P10...P17) uint8_t dataLowByte; // Младший байт (P00...P07) void setup() { Wire.begin(); Serial.begin(9600); // Высокий уровень во все порты PCF8575 dataHighByte = B11111111; dataLowByte = B11111111; pcf8575_write(dataLowByte, dataHighByte); } void loop() { // Читаем байт из модуля dataReceive = pcf8575_read(); // Выводим в монитор в двоичном формате Serial.println(dataReceive, BIN); // Выделяем младший байт из длинного слова dataLowByte = lowByte(dataReceive); // Копируем младший байт в старший байт dataHighByte = dataLowByte; // Накладываем маску на младший байт dataLowByte |= B11111111; // Записываем новые данные в модуль, два байта pcf8575_write(dataLowByte, dataHighByte); delay(500); } // Процедура записи байта в модуль void pcf8575_write(uint8_t dtl, int8_t dth) { Wire.beginTransmission(address); Wire.write(dtl); // Записываем младший байт (P00...P07) Wire.write(dth); // Записываем старший байт (P10...P17) Wire.endTransmission(); } // Процедура чтения байта из модуля int16_t pcf8575_read() { Wire.beginTransmission(address); Wire.endTransmission(); Wire.requestFrom(address, 2); lo = Wire.read(); // Читаем младший байт (P00...P07) hi = Wire.read(); // Читаем старший байт (P10...P17) return (word(hi, lo)); // Возвращаем длинное слово }
Библиотека Arduino для PCF8574/PCF8575
Библиотеку можно скачать на GitHub. Но, как вы могли видеть, работа с расширителями портов очень проста и можно легко обойтись без специальной библиотеки.
Спасибо за наводку, а то я смотрел в сторону cd4051 и cd74hc4067 немного скептически, а по протоколу i2c, прямо как и хотел. И отдельное спасибо за код. Интересно, а 400кГц поддерживают данные микросхемы?
Будьте осторожны:
PCF8574 НЕ поддерживает 400 кГц.
PCF8575 более новый чип с поддержкой 400 кГц. Он в целом более надежный и живучий. Поэтому в любом случае лучше применять PCF8575.
Спасибо большое!
Как раз на этом модуле и остановился, уже полистал спецификации.