I²C · GPIO · Arduino
PCF8574 без магії: як працюють квазідвонапрямні входи-виходи та що має правильно робити Arduino-бібліотека
У цієї мікросхеми немає звичного регістра напрямку і немає симетричного push-pull HIGH. Тому найважливіше слово в її API — не «увімкнути», а «відпустити».
PCF8574 часто з’являється в майстерні як маленька плата, що обіцяє «ще вісім GPIO лише двома дротами». Формально — так. Практично — ці GPIO поводяться так, ніби в договорі дрібним шрифтом написано: «HIGH надається на особливих умовах». Якщо їх не прочитати, кнопка раптом перестає бути входом, байт 0xFF перетворюється на «помилку», а невинний старт бібліотеки вмикає всі active-low навантаження.
Зміст статті
- Один latch-біт замість INPUT і push-pull HIGH
- Який стан повинна зберігати бібліотека
- Адреси та правильна перевірка ACK
- Що реально робить jm_PCF8574 v2.0.0
- Навантаження і конфлікт виходів
- LCD-backpack без універсального pinout
- Чому 4,7 кОм не універсальне
- Чекліст правильної бібліотеки
- Технічні джерела
1. Один latch-біт замість INPUT і push-pull HIGH
У звичайного GPIO мікроконтролера ми очікуємо окремий вибір напрямку та вихідний latch. PCF8574 влаштована інакше: кожна P-лінія квазідвонапрямна, а доступного програмі регістра напрямку немає. Після power-on I/O перебувають у HIGH/released стані; записаний біт безпосередньо визначає, чи активне стягування LOW.[1][2]
| Latch-біт | Фізична дія PCF8574 | Що може прочитати MCU | Практичне трактування |
|---|---|---|---|
0 |
Вихідний транзистор активно стягує Pn до LOW. | Фізичний LOW, доки навантаження не виведе вузол за нормовані умови. | Активний LOW-вихід. |
1 |
Сильний LOW-драйвер вимкнений; залишається слабке джерело струму до живлення. | HIGH без зовнішнього стягування або LOW, якщо зовнішня схема стягнула Pn. | Released HIGH або вхід. |
0 активно тягне вниз, 1 відпускає сильний LOW-драйвер і дозволяє зовнішньому сигналу визначити рівень.HIGH тут не push-pull
Коли в Pn записано 1, PCF8574 не вмикає звичайний сильний high-side вихід. Статичний HIGH підтримує слабке внутрішнє джерело струму. NXP описує його як джерело приблизно 100 мкА, а TI на функціональній схемі також позначає 100 мкА, але в таблиці електричних характеристик задає діапазон струму за конкретних умов. Тому перетворювати цей вузол на «внутрішній резистор 100 кОм» не слід: даташит нормує джерело струму, а не постійний лінійний опір.[1][2]
Під час запису HIGH додатково вмикається сильніше короткочасне підтягування. Воно допомагає швидше зарядити ємність лінії та підняти фронт до HIGH, після чого вимикається за фазою SCL. Це стартовий поштовх, а не постійний push-pull HIGH і точно не прискорювач переходу до LOW.[2]
digitalWrite(Pn, HIGH) для PCF8574 означає передусім «припини активно стягувати Pn». Вивід стає придатним як вхід, але не перетворюється на повністю від’єднаний CMOS-вхід.
Робочі параметри — не те саме, що Absolute Maximum
PCF8574 значно сильніше поглинає струм у LOW, ніж віддає його в HIGH. Але цифри з даташита живуть у різних «шухлядах». Electrical Characteristics описують гарантовані або типові точки за заданих умов. Recommended Operating Conditions задають область штатної роботи. Absolute Maximum Ratings — це стресові межі, перевищення яких може пошкодити мікросхему; вони не є рецептом проєктування.[2]
Наприклад, у TI Rev. K 50 мА continuous output-low current наведено саме серед Absolute Maximum Ratings, тоді як рекомендована межа IOL сягає 25 мА. І це ще не обіцянка «по 25 мА з кожного з восьми виводів»: треба одночасно перевіряти VOL за потрібного струму, сумарний струм корпуса, живлення й температуру.[2]
2. Який стан повинна зберігати бібліотека
Ключове правило даташита сформульоване без містики: перед використанням Pn як входу цей I/O має бути HIGH. У термінах latch це означає: спочатку записати 1, потім читати. Якщо біт залишився 0, мікросхема сама утримує лінію LOW, і зовнішній перемикач не отримає права голосу.[1][2]
Звідси випливає вимога до бібліотеки: вона не повинна змішувати три різні сутності.
output_latch— байт, який бібліотека хоче записати в PCF8574;input_sample— байт фізичних рівнів, щойно прочитаний з P0–P7;status— результат I²C-транзакції.
Чому read-modify-write може зіпсувати вхід
Уявімо, що P0 відпущений записаною одиницею, а кнопка зовні стягнула його до LOW. Читання справедливо повертає 0 для P0. Якщо бібліотека збереже цей sample як «тінь виходу», потім змінить лише P7 і запише весь байт назад, P0 отримає latch=0. Кнопку вже можна відпустити, але PCF8574 продовжить сама тримати P0 у LOW. Це не примха I²C — це наслідок втрати різниці між командою і спостереженням.[1][2]
Ескіз коректної семантики API
Це не код конкретної бібліотеки, а контракт, який не суперечить електриці:
bool releaseInput(uint8_t pin); // записує 1 у latch
bool writePin(uint8_t pin, bool low); // змінює output_latch
bool readPort(uint8_t& sample); // data окремо, success окремо
bool probeAddress(); // address-only ACK probe
// Перед кожним повним записом:
byte safe = output_latch | input_mask;
Назва методу може бути іншою. Важлива не косметика, а гарантія: перехід до входу фізично записує 1, а помилка шини не маскується під один із 256 цілком законних байтів.
3. Адреси та правильна перевірка ACK
PCF8574 і PCF8574A мають різні діапазони
Arduino Wire приймає 7-бітну адресу пристрою. Для PCF8574 три входи A2:A0 дають діапазон 0x20–0x27; для PCF8574A — 0x38–0x3F. Отже, 0x27 не означає «A-версія»: це PCF8574 з усіма трьома адресними входами HIGH. Маркування мікросхеми та реальні перемички плати надійніші за «типову адресу» з чужого скетчу.[1]
| Варіант | A2:A0 = 000 | A2:A0 = 111 | Повний діапазон |
|---|---|---|---|
| PCF8574 | 0x20 |
0x27 |
0x20–0x27 |
| PCF8574A | 0x38 |
0x3F |
0x38–0x3F |
Для практики можна перейти до перевіреного в каталозі модуля розширення портів PCF8574. Це внутрішнє товарне посилання, а не доказ адреси чи струмових меж конкретного екземпляра.
beginTransmission() не відповідає на запитання «пристрій є?»
Wire.beginTransmission(address) лише готує локальний TX-буфер та адресу. Офіційний Arduino API повертає nothing, а в AVR core реальна сигнатура — void TwoWire::beginTransmission(uint8_t address). Тому конструкція if (Wire.beginTransmission(...)) не перевіряє ACK і взагалі не є коректним використанням API.[3][5]
Результат передачі повертає Wire.endTransmission(): код 0 означає успіх, 2 — NACK адреси, 3 — NACK даних; інші коди описують інші помилки реалізації. Для перевірки лише відповіді за адресою не додаємо жодного data byte.[4][5]
bool i2cAddressAck(TwoWire& bus, uint8_t address)
{
bus.beginTransmission(address);
return bus.endTransmission() == 0;
}
Чому ініціалізаційний 0x00 небезпечний
Запис 0x00 — не нейтральний ping. Він наказує всім восьми P-лініям активно перейти в LOW. На невідомій платі це може ввімкнути active-low навантаження, перемкнути керувальні сигнали LCD або створити конфлікт із зовнішнім драйвером. Якщо потрібен лише ACK, TX-буфер має залишатися порожнім.[1][2][5]
На протилежному кінці байтової шкали 0xFF теж не можна резервувати як універсальну «помилку читання». Це цілком коректний фізичний стан: усі вісім ліній прочитані HIGH. Дані й статус мають іти окремими каналами.[1][8]
4. Що реально робить jm_PCF8574 v2.0.0
Розглянемо не уявну «правильну бібліотеку», а конкретний tagged release. jm_PCF8574 v2.0.0 опублікований 4 липня 2025 року; release описує перероблений API та вибір I²C bus.[6] Нижче розбираємо фактичний код цього релізу.
Реальні публічні сигнатури
У tagged header v2.0.0 оголошено саме такі основні конструктори й методи:[7]
jm_PCF8574();
jm_PCF8574(byte i2c_address);
jm_PCF8574(byte i2c_address, TwoWire & wire);
operator bool();
byte i2c_address();
bool connected();
bool begin();
bool begin(byte port_mode, byte port_shadow);
bool begin(byte i2c_address);
bool begin(byte i2c_address, byte port_mode, byte port_shadow);
bool end();
byte port_input();
void port_output(byte value);
void port_output(const byte *data, size_t quantity);
void pinMode(byte pin, byte mode);
int digitalRead(byte pin);
void digitalWrite(byte pin, byte value);
Header також містить wire(), port_mode(), port_shadow(), pin-level helper-и та wait(). Важливіше інше: overload-а begin(TwoWire*) тут немає.
Довільний TwoWire передають у конструктор
Вибір Wire, Wire1 або іншого об’єкта, успадкованого/сумісного з TwoWire, реалізовано посиланням у конструкторі: jm_PCF8574(byte, TwoWire&). Обраний об’єкт має існувати в конкретному Arduino core, а користувач повинен сам ініціалізувати шину способом, передбаченим цим core. Бібліотека зберігає TwoWire& і виконує транзакції через нього.[7][8]
// Після ініціалізації Wire1 за правилами вашого Arduino core:
jm_PCF8574 io(0x20, Wire1);
const bool started = io.begin();
begin() змінює порт, а не лише перевіряє адресу
Типовий begin() стартує з режимом all-input/released і тінню 0xFF. Внутрішній _begin() формує байт порту, записує його пристрою та встановлює успіх за endTransmission()==0. Це конфігураційний запис, хоча безпечніший за 0x00; чистим address-only probe він не є. Якщо _connected уже true, публічний begin(...) повертає false, а end() лише скидає внутрішній прапорець і не викликає Wire.end().[8]
connected() та operator bool() — кеш, не live probe
connected() просто повертає _connected; operator bool() делегує цьому ж методу. Отже, обидві форми повідомляють останній відомий бібліотеці стан. Якщо кабель зняли після успішної операції, прапорець не дізнається про це телепатично — потрібна наступна I²C-транзакція. І навпаки, повторний begin() не є зручним health check, бо за _connected=true він повертає false.[7][8]
Де v2.0.0 губить межу між latch і sample
Під час успішного port_input() реалізація читає P-порт і записує отриманий байт у _port_shadow. При помилці або disconnected вона повертає поточний _port_shadow, а не гарантований 0xFF. Тому byte port_input() не має окремого каналу помилки; будь-який повернений байт може бути і допустимим значенням стану.[8]
Ще гостріше: pinMode(INPUT) у v2.0.0 змінює лише _port_mode у RAM і не виконує I²C-запису, який фізично відпустив би pin одиницею. А після читання зовнішній LOW уже може опинитися в _port_shadow; наступна bit-операція будує запис на цій тіні. Саме так підтверджена структура source створює ризик небезпечного read-modify-write для released input bits.[8]
5. Навантаження і конфлікт виходів
Кнопка: released input, а не «плаваючий pin»
Типова кнопка між Pn та GND працює природно: бібліотека записує в Pn одиницю, слабке джерело підтримує HIGH, натискання стягує лінію до LOW. Затримка може знадобитися для debounce механічних контактів, але універсальна delay(1) не випливає з даташита і не «лікує floating input» — released вхід уже має слабке джерело струму.[1][2]
Світлодіод: active-low часто природніший
Через асиметрію струмів світлодіод із резистором часто підключають так, щоб PCF8574 поглинала струм у стані LOW. Але номінал резистора, допустимий VOL, сумарний струм та умови даташита все одно треба розрахувати. Релейну котушку не слід під’єднувати безпосередньо: потрібні відповідний транзистор або драйвер, flyback path і перевірка струму та тепла.[2]
MCU GPIO і P7 не можна просто з’єднати «для PWM»
Апаратний PWM/LEDC мікроконтролера виникає на GPIO самого MCU, а не всередині PCF8574. Щоб він вплинув на мережу підсвічування, потрібен фізичний шлях. Якщо просто з’єднати push-pull GPIO MCU з P7 і P7 колись отримає latch=0, MCU може подати HIGH назустріч активному LOW-драйверу розширювача. Це конфлікт виходів із потенційно надмірним струмом, а не спосіб «передати PWM через I²C». Електрична структура P-порту не дає підстав вважати таке пряме об’єднання безпечним.[1][2]
6. LCD-backpack без універсального pinout
PCF8574 знає лише P0–P7. Вона не знає, що поруч припаяний HD44780-compatible дисплей. Відповідність P-бітів сигналам RS, R/W, E, D4–D7 і підсвічуванню визначає плата адаптера, а не даташит розширювача.
Конфігурований New-LiquidCrystal прямо приймає номери En, Rw, Rs, D4–D7, pin підсвічування та його полярність; у репозиторії є приклад з однією конкретною розкладкою. Це демонструє гнучкість software-моделі, але не доводить pinout невідомої фізичної плати.[10][11]
R/W: керований або зафіксований у режимі запису
Контролер HD44780U має сигнал R/W, цикли читання та busy flag.[9] Конкретний backpack може провести R/W до одного з P-бітів або зробити його недоступним програмі, зафіксувавши режим запису. У другому випадку бібліотека не читає busy flag і мусить витримувати документовані затримки. Який варіант перед нами, показують схема, трасування або continuity test, а не адреса 0x27.
Підсвічування: pin і полярність залежать від плати
Каскад підсвічування може керуватися окремим P-бітом через транзистор, мати іншу інверсію або взагалі не надавати програмного керування. Те, що бібліотека просить параметри backlight pin і polarity, — корисне нагадування: «active LOW завжди» тут не існує.[10]
У каталозі MyProject є адаптер I²C для LCD1602/LCD2004 та LCD1602 з I²C-модулем. Ці сторінки корисні для вибору форм-фактора, але без схеми й точної ревізії не встановлюють P0–P7 map, R/W або полярність підсвічування.
Таймінги HD44780 залежать від умов
Один магічний набір затримок для всіх HD44780-compatible контролерів — погана інженерна звичка. В оригінальному HD44780U мінімальна ширина E HIGH становить 450 нс для VCC=2,7–4,5 В і 230 нс для 4,5–5,5 В; мінімальний enable cycle — відповідно 1000 нс і 500 нс. Стартова послідовність та час виконання команд також залежать від живлення й частоти внутрішнього генератора; clear display та return home значно довші за звичайні операції.[9]
| Параметр | VCC 2,7–4,5 В | VCC 4,5–5,5 В | Практичний висновок |
|---|---|---|---|
| E HIGH, мінімум | 450 нс | 230 нс | Не відривати число від діапазону живлення. |
| Enable cycle, мінімум | 1000 нс | 500 нс | Кожний nibble потребує коректного E-strobe. |
| Завершення команди | Залежить від команди й oscillator | Залежить від команди й oscillator | delayMicroseconds(40) не є універсальним для clear/home. |
7. Чому 4,7 кОм не універсальне I²C-підтягування
У шини I²C лінії SDA та SCL формують HIGH через pull-up, а пристрої тягнуть їх до LOW відкритим стоком/колектором. Значення резистора має потрапити у вікно між двома обмеженнями: надто малий опір створює завеликий sink current і заважає гарантованому LOW; надто великий разом із ємністю шини робить фронт HIGH запізнілим.[13]
TI SLVA689 виводить нижню межу з VCC, допустимого VOL(max) та sink current, а верхню — з дозволеного rise time tr і ємності шини Cb.[12] Тому 4,7 кОм може бути хорошим значенням у конкретній схемі, але не є універсальною константою природи.
Окрема пастка модульних макетів — кілька готових плат із власними pull-up. Паралельні 4,7 кОм дають менший еквівалентний опір, тож рахувати треба всю шину, її довжину, ємність, напругу та обраний режим.[12] Якщо потрібен вступ до ACK, адрес і фізики самої шини, стане в пригоді матеріал «I2C, SPI, UART — що це таке і як працюють інтерфейси мікросхем».
8. Чекліст правильної Arduino-бібліотеки
- Не вигадує direction register. API пояснює released input і не обіцяє звичайний push-pull HIGH.
- Фізично відпускає вхід. Перехід до input записує
1у відповідний latch-біт. - Розділяє стани. Output latch, input sample, input mask і I²C status не живуть в одному байті.
- Не резервує
0xFFяк помилку. Дані повертаються окремо від success/error. - Перевіряє ACK через
endTransmission().beginTransmission()не використовується як bool. - Має нешкідливий probe. Address-only перевірка не додає
Wire.write(0x00)і чесно каже, що ACK не ідентифікує модель. - Не називає кеш живою перевіркою.
connected()або аналог документує момент оновлення стану. - Приймає потрібний
TwoWireреальним API. Користувач бачить, хто й коли ініціалізує шину. - Не прив’язує LCD до одного pinout. Карта сигналів і полярність підсвічування конфігуруються або підтверджуються схемою конкретної плати.
- Документує стартовий байт. Жодного «безпечного»
0x00без аналізу навантажень. - Розрізняє робочі й аварійні межі. Absolute Maximum не рекламується як штатний струм.
- Не підміняє PWM. Часті I²C-записи не називаються hardware PWM, а спільна мережа MCU/PCF не допускається без ізоляції.
Короткі відповіді на типові питання
- Чи можна прочитати LOW, який сама PCF8574 виставила?
- Так. Читання повертає фізичний рівень P-порту. Просто окремого читабельного регістра latch, який пояснив би причину LOW, немає.[1][2]
- Чи достатньо записати
0xFFодин раз? - Для всіх released inputs — це правильний старт. Але кожний наступний повний запис має зберігати одиниці в input-бітах; інакше бібліотека знову перетворить їх на активні LOW.
- Чи можна робити PWM частими записами по I²C?
- Програмне перемикання можливе, але це не timer-backed PWM: частота, jitter і зайнятість шини залежать від I²C-транзакцій та Arduino core. Без вимірювань конкретної системи не слід обіцяти відсутність мерехтіння.[1][2] Для основ PWM дивіться окремий матеріал про ШІМ.
- Де почитати про
VIH,VIL,VOHтаVOL? - У статті «Таємниці логічних рівнів» — вона пояснює, чому логічне слово HIGH не замінює електричні умови даташита.
Технічні джерела
- NXP Semiconductors — “PCF8574; PCF8574A Remote 8-bit I/O expander for I²C-bus with interrupt” — Rev. 5, 27 May 2013.Квазідвонапрямний порт, слабке джерело струму, power-on state, читання/запис і адреси обох варіантів.
- Texas Instruments — “PCF8574 Remote 8-Bit I/O Expander for I2C Bus” — SCPS068K, Rev. K, September 2024.Функціональна схема P-порту, статичне й transient підтягування, I/O currents та класи електричних меж.
- Arduino — “Wire.beginTransmission()” — revision 24.01.2026.Синтаксис, 7-бітна адреса та відсутність return value.
- Arduino — “Wire.endTransmission()” — revision 24.01.2026.Завершення транзакції та коди success/NACK/error.
- Arduino — “ArduinoCore-avr / Wire.cpp” — master.Сигнатура
void beginTransmission, TX-буфер і address-only передача в AVR Wire. - Jean-Marc Paratte — “jm_PCF8574 v2.0.0” release — 4 July 2025, commit 53cfd20.Існування релізу, API refactor і selectable I²C bus.
- Jean-Marc Paratte — “src/jm_PCF8574.h” — v2.0.0, commit 53cfd20.Реальні constructors,
TwoWire&,operator bool,connected, begin/end і port/pin API. - Jean-Marc Paratte — “src/jm_PCF8574.cpp” — v2.0.0, commit 53cfd20.Реальна поведінка begin/end,
_connected, port input/output,_port_shadowтаpinMode. - Hitachi — “HD44780U (LCD-II) Dot Matrix Liquid Crystal Display Controller/Driver” — доступне дзеркало SparkFun.RS/RW/E, busy flag, 4-bit initialization, execution times і залежні від VCC bus timings.
- Francisco Malpartida — “New-LiquidCrystal / LiquidCrystal_I2C.h” — master.Конфігуровані En/Rw/Rs/D4–D7, backlight pin і polarity; це software API, не схема конкретної плати.
- Francisco Malpartida / contributor — “HelloWorld_byVac.ino” — repository master.Приклад конкретно налаштованої LCD map; не ідентифікує всі фізичні ревізії backpack.
- Texas Instruments, Rajan Arora — “I2C Bus Pullup Resistor Calculation” — SLVA689, February 2015.Формули
Rp(min),Rp(max)та залежність від струму, rise time і bus capacitance. - NXP Semiconductors — “UM10204 I²C-bus specification and user manual” — Rev. 7.0, 1 October 2021.Open-drain структура шини, pull-up, rise-time та bus-capacitance requirements.
Матеріал спирається на документацію виробників, офіційну Arduino-документацію та tagged source бібліотеки.
© 2026 Мій Проект. Автор: Jazzzman. Використання матеріалів дозволено лише з посиланням на джерело.
Написати коментар