Уважаемые читатели смогли меня переубедить, что тема расширения пинов Arduino далеко не исчерпана. Поэтому - возвращаемся. Начало см. в
Части 2 и
Части 1.
Итак - практически все стандартные и недорогие логические микросхемы мы перебрали, но ведь есть и специализированные. Это значит - такие, которые функционально служат для увеличения количества выводов
DIO -
Digital Input/Output.
Что такое
PCF8574?
В двух словах - это
8 дополнительных линий DIO по I2C - шина довольно распространенная и поддерживаемая многими современными контроллерами. В ATmega поддержка I2C именуется Two Wire Interface или TWI - в пределах простых задач её можно считать аналогом I2C. В Arduino существует библиотека Wire, которая позволяет ему работать по I2C как в режиме Master, так и Slave.
Расплатившись двумя пинами
analog4 и
analog5 (именно на них у ATmega8/168/328 живет аппаратная поддержка TWI), мы получаем возможность подключить к ним от одной до шестнадцати микросхем PCF8574 параллельно, что означает не более
128 цифровых DIO дополнительно.
Схема пина наглядно представлена в документации:
Сигнал "data from shift register" поступает на привычный уже триггер-защелку (latch), который хранит выведенное однажды значение. Его выход через инвертор поступает на пару полевых транзисторов, которые стоят "на распутье" у внешнего входа пина "P0-P7". В результате вход оказывается либо притянут к Vcc, либо замкнут на GND (логическая единица и ноль соответственно).
Теперь надо обратить внимание на второй триггер - его D-вход соединен с ответвлением, служащим для получения входного сигнала (находится между все теми же полевыми транзисторами). Если приглядеться внимательнее, станет очевидно, что получить на входе единицу или ноль можно только если пин притянут к единице, в противном случае входной сигнал победно уходит на землю.
Из всего этого следует простое правило:
для работы в режиме входа надо записать логическую единицу в соответствующий разряд (для сравнения - в Arduino надо использовать оператор
pinMode, а в avrgcc - записать нужное значение в соответствующий DDR-регистр).
Внутренне микросхема устроена так:
В кубике "IO Port" располагается восемь одинаковых схем, рассмотренных выше.
Shift Register накапливает данные по мере поступления по последовательной шине I2C (в чем-то это напоминает микросхему 595), а сигнал прерывания по изменению состояния входа
INT собирается воедино в одну линию со всех разрядов.
Шина I2C представлена в виде линий SDA и SCL, а также адресных входов A0, A1 и A2.
Раньше микросхемы снабжались специальным входом CS (Crystal Select) - низкий уровень на нем соответствовал "выбору кристалла", позволяя таким образом главному устройству (как правило, микроконтроллеру или микропроцессору) определять, с какой именно микросхемой будет происходить обмен в данный конкретный момент времени. Часто это называлось "адресация", а для формирования CS обычно служили дешифраторы.
На шине I2C дешифратор должен находиться
внутри чипа, потому для адресации устройства используются передаваемые по этой же шине данные - первым в посылке идет байт адреса (точнее, 7-битный адрес плюс бит операции - чтение или запись). Вот так он должен выглядеть для PCF8574:
Если одной микросхемы на шине мало, можно подключить несколько, но каждой должен быть заранее присвоен свой адрес через входы A0-A2, как правило - путем банального притягивания к питанию и земле (единице и нулю). Тогда, приняв первый байт адреса, микросхема сравнивает его с текущим состоянием A0-A2 и легко определяет, с ним ли будет сейчас разговаривать master шины I2C. Поскольку внешних линий адреса - всего три, это дает 2 ^ 3 = 8 разных адресов. Если же требуется больше восьми микросхем на одной шине, надо закупаться микросхемой с буквой A (справа), они идентичны во всем, кроме начала адреса - 0111 вместо 0100.
Попробуем подключить одну микросхему к Arduino:
Традиционных подтягивающих резисторов для кнопок в данном случае не требуется - уже и так подтянуто (см. схему пина выше).
На макетке это выглядит так (резисторы я упразднил, поскольку и без них все работает замечательно):
Теперь напишем скетч средней сложности, который будет считывать состояние двух кнопок и управлять светодиодами - пусть один светодиод будет гаснуть и зажигаться синхронно с нажатием и отпусканием одной кнопки, а другой - мерцать при нажатии на вторую:
#include <Wire.h>
#define PCF8574 0x20
#define PCF8574A 0x38
#define A0 0
#define A1 0
#define A2 0
#define DIOADDR (PCF8574A|A0|(A1<<1)|(A2<<2))
#define LED1 B11011111
#define LED2 B01111111
#define BTN1 B00000010
#define BTN2 B00000100
boolean L1 = true;
boolean L2 = true;
void setup() {
Wire.begin();
Wire.beginTransmission(DIOADDR);
Wire.send(0xff & LED1 & LED2);
Wire.endTransmission();
}
void loop() {
Wire.requestFrom(DIOADDR,1);
while (!Wire.available()) delay(30);
byte dio_in = Wire.receive();
byte dio_out = 0xff;
L1 = dio_in & BTN1;
if (L1) {
dio_out |= ~LED1;
} else {
dio_out &= LED1;
}
if (!(dio_in & BTN2)) L2 = !L2;
else L2 = false;
if (L2) {
dio_out &= LED2;
} else {
dio_out |= ~LED2;
}
dio_out |= (BTN1|BTN2);
Wire.beginTransmission(DIOADDR);
Wire.send(dio_out);
Wire.endTransmission();
delay(30);
}
Адресные пины A0-A2 брошены на GND, что соответствует адресу
0x38 на шине I2C в случае применения микросхемы с буквой 'A' или же -
0x20, если микросхема без буквы. Вы можете всё поменять, исправив соответствующие
#define в начале скетча.
LED1, LED2 - это битовые маски светодиодов. Обратите внимание - на схеме они притянуты к +5В, поэтому, чтобы они загорелись, надо создать разность потенциалов - сформировать на выходе логический 0. Переменные L1 и L2 хранят текущее состояние светодиодов, BTN1, BTN2 - битовые маски кнопок.
Важно понимать одну деталь:
записываемый байт запоминается в защелке PCF8547 и управляет режимом вывода, в то время как
байт, который мы читаем - отражает текущее состояние по входам, и это
два разных значения. Нельзя просто поправить что-то в прочитанном по I2C байтике и вернуть его обратно: если на каком-то
входе был 0, то мы тут же переведем его таким образом в режим
выхода и он будет стабильно и независимо от состояния по входу возвращать 0 (см. пояснения к схеме пина выше) - пока мы снова не запишем туда единицу. Именно поэтому для считываемого и записываемого значений заведены две отдельные переменные
dio_in и
dio_out.
Подведем краткий итог:
- микросхема PCF8574(A) все-таки стоит дороже, чем стандартная логика, но зато позволяет произвольно гибко комбинировать входы и выходы, экономя на количестве корпусов;
- возможно каскадирование с независимым доступом к каждому входу, в отличие от 595-ой микросхемы, которая в закаскадированном режиме требует для модификации сдвиг всего каскада целиком;
- возможна работа по прерыванию.