Часть 3. Использование PCF8574
Уважаемые читатели смогли меня переубедить, что тема расширения пинов 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:
Традиционных подтягивающих резисторов для кнопок в данном случае не требуется - уже и так подтянуто (см. схему пина выше).
На макетке это выглядит так (резисторы я упразднил, поскольку и без них все работает замечательно):
Теперь напишем скетч средней сложности, который будет считывать состояние двух кнопок и управлять светодиодами - пусть один светодиод будет гаснуть и зажигаться синхронно с нажатием и отпусканием одной кнопки, а другой - мерцать при нажатии на вторую:
Адресные пины A0-A2 брошены на GND, что соответствует адресу 0x38 на шине I2C в случае применения микросхемы с буквой 'A' или же - 0x20, если микросхема без буквы. Вы можете всё поменять, исправив соответствующие #define в начале скетча.
LED1, LED2 - это битовые маски светодиодов. Обратите внимание - на схеме они притянуты к +5В, поэтому, чтобы они загорелись, надо создать разность потенциалов - сформировать на выходе логический 0. Переменные L1 и L2 хранят текущее состояние светодиодов, BTN1, BTN2 - битовые маски кнопок.
Важно понимать одну деталь: записываемый байт запоминается в защелке PCF8547 и управляет режимом вывода, в то время как байт, который мы читаем - отражает текущее состояние по входам, и это два разных значения. Нельзя просто поправить что-то в прочитанном по I2C байтике и вернуть его обратно: если на каком-то входе был 0, то мы тут же переведем его таким образом в режим выхода и он будет стабильно и независимо от состояния по входу возвращать 0 (см. пояснения к схеме пина выше) - пока мы снова не запишем туда единицу. Именно поэтому для считываемого и записываемого значений заведены две отдельные переменные dio_in и dio_out.
Подведем краткий итог:
Итак - практически все стандартные и недорогие логические микросхемы мы перебрали, но ведь есть и специализированные. Это значит - такие, которые функционально служат для увеличения количества выводов 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-ой микросхемы, которая в закаскадированном режиме требует для модификации сдвиг всего каскада целиком;
- возможна работа по прерыванию.
Немного смущает строчка в setup()
ОтветитьУдалитьWire.send(0xff & LED1 & LED2);
По идее, туда пишется сейчас просто 0, т.к.
LED1 & LED2 = 0
0xff & 0 = 0
В строчке
#define DIOADDR (PCF8574A|A0|(A1<<1)|(A2<<2))
красиво вычисляем адрес микросхемы, а потом везде используем прямой адрес 0x38.
Да что-то так и не понял, как читать данные (как пин перевести в состояние чтения), а не записи.
Не, туда пишется не 0, а B01011111.
ОтветитьУдалитьВот где нули - это выходы, а где единицы - входы.
А, все понял. Там LED1/2, а я на биты BTN1/2 смотрел.
ОтветитьУдалитьПо поводу чтения.
Т.е. получается, что мы при первом обращении к микросхеме задаем режим работы, а потом изменить его уже нельзя (и для этого изменения надо "сбрасывать" микросхему, отключая ее от питания)?
Нет, не так. Читать и писать можно в любой момент времени. Схема входа совмещена со схемой выхода.
ОтветитьУдалитьЗаписывая в разряд единицу, вы подаете на выход +5В и разрешаете работу в качестве входа. В противном случае - на выходе 0В, работа в качестве входа невозможна.
Посмотрите на схему пина внимательнее - там нагляднее, чем на словах ;)
для начинающих, как я, к сожалению, в скетче почти ничего не ясно, ...
ОтветитьУдалитьСергей, Вы явно скромничаете.
ОтветитьУдалитьПосмотрите на описание либы Wire, это даст 90% понимания.
Спасибо огромное! Получилось классно!
ОтветитьУдалитьВот только вопрос а есть ли какие либо аналоги с другими адресами? Как найти не понимаю совсем. Читаю различные датащиты но все чем-то не подходит. Вам что-то известно об аналогах?
Так получается мы можем использовать 8*8+8*8=128 пинов
ОтветитьУдалитьА вот как бы получить большее количество.
Читая датащиты нашёл кое-что.
Ответьте пожалуйста на мои 4 вопроса. Точнее если вам не трудно уделите 5 минут, гляньте верны ли мои предположения?
1. Например PCA9555 - её можно использовать вместо PCF8574
тогда из фиксированно адреса 0100 можно извлечь не 8*8 а 16*8 пинов
в результате получим 128 (PCA9555) + 64 (PCF8574A) = 192 пина
2. Может правильнее было бы использовать что-то вроде PCA9675- но так и не понял из датащита как задавать адрес. PCA9655E - Я так понял тут можно использовать "64 Programmable Slave Addresses Using Three Address Pins" но как использовать???
3. Еще очень вдохновляют PCA9539 и PCA9558 тут адресация начинается с 1 и выглядит так:
1 1 1 0 1 A1 A0 - PCA9539
1 0 0 1 1 1 A0 - PCA9558
помоему их можно использовать в сочетании с любыми вышеперечисленными - адреса реально разные со всеми остальными
4. PCA9501 - выглядит очень, очень красиво. действительно ли используя её можно просто по такой же логике как и в статье реализовать64*6=384 пина
Очень интересные наблюдения:
ОтветитьУдалитьСобрал вышеприведенную схему на макетке - все работает, но...
Т.к. в будущем планирую использовать эту микросхему в коммерческих проектах,начал выносить кнопки и светодиоды в отдельный блок через кабель в 1,5 метра от макетки и вот тут-то заметил конкретный косячок...
При подключениях и отключениях этого кабеля иногда (не каждый раз) PCF8574 начинала переводить почти все выходы в LOW и жутко греться. При передергивании питания косяк пропадал. Естественно я начал проверять всю схему на помехоустойчивость, а именно оставил на макетке только PCF8574 с подключенным питанием, землей и ногами А0,А1 и А2 подключенным к земле (контроллер, диоды, кнопки, лишние перемычки все убрал). Одной рукой "грею" микросхему, а второй касаюсь всех ног по очереди изолированной отверткой - и о чудо, если несколько раз
быстро касаться связки (А0,А1,А2 и земля) микросхема начинала греться. Дальше подключаю А0,А1,А2 к VCC и косяк пропал, затем А0 подключаю к земле, а остальные к "+" - косяк присутствует. Проблему на половину решил, но мне в схеме необходимо 2 шт PCF8574 и я копаю дальше...
Вообщем вывод такой: Адресные пины А0,А1,А2 нельзя подключать напрямую к земле только через резистор ( я поставил 1 кОм), а к питанию можно.
Вот такая шляпа ребята...
Спасибо, что поделились ценным опытом! Для чистоты эксперимента надо было все входы микросхемы подтянуть, иначе возникает фактор неопределенности.
Удалить