19.05.2010

Удвоение пинов Arduino (3)

Часть 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:


Традиционных подтягивающих резисторов для кнопок в данном случае не требуется - уже и так подтянуто (см. схему пина выше).

На макетке это выглядит так (резисторы я упразднил, поскольку и без них все работает замечательно):


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

#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-ой микросхемы, которая в закаскадированном режиме требует для модификации сдвиг всего каскада целиком;
  • возможна работа по прерыванию.

10 комментариев:

  1. Немного смущает строчка в setup()
    Wire.send(0xff & LED1 & LED2);

    По идее, туда пишется сейчас просто 0, т.к.
    LED1 & LED2 = 0
    0xff & 0 = 0

    В строчке
    #define DIOADDR (PCF8574A|A0|(A1<<1)|(A2<<2))
    красиво вычисляем адрес микросхемы, а потом везде используем прямой адрес 0x38.

    Да что-то так и не понял, как читать данные (как пин перевести в состояние чтения), а не записи.

    ОтветитьУдалить
  2. Не, туда пишется не 0, а B01011111.

    Вот где нули - это выходы, а где единицы - входы.

    ОтветитьУдалить
  3. А, все понял. Там LED1/2, а я на биты BTN1/2 смотрел.

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

    ОтветитьУдалить
  4. Нет, не так. Читать и писать можно в любой момент времени. Схема входа совмещена со схемой выхода.

    Записывая в разряд единицу, вы подаете на выход +5В и разрешаете работу в качестве входа. В противном случае - на выходе 0В, работа в качестве входа невозможна.

    Посмотрите на схему пина внимательнее - там нагляднее, чем на словах ;)

    ОтветитьУдалить
  5. для начинающих, как я, к сожалению, в скетче почти ничего не ясно, ...

    ОтветитьУдалить
  6. Сергей, Вы явно скромничаете.

    Посмотрите на описание либы Wire, это даст 90% понимания.

    ОтветитьУдалить
  7. Спасибо огромное! Получилось классно!
    Вот только вопрос а есть ли какие либо аналоги с другими адресами? Как найти не понимаю совсем. Читаю различные датащиты но все чем-то не подходит. Вам что-то известно об аналогах?

    ОтветитьУдалить
  8. Так получается мы можем использовать 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 пина

    ОтветитьУдалить
  9. Очень интересные наблюдения:
    Собрал вышеприведенную схему на макетке - все работает, но...
    Т.к. в будущем планирую использовать эту микросхему в коммерческих проектах,начал выносить кнопки и светодиоды в отдельный блок через кабель в 1,5 метра от макетки и вот тут-то заметил конкретный косячок...
    При подключениях и отключениях этого кабеля иногда (не каждый раз) PCF8574 начинала переводить почти все выходы в LOW и жутко греться. При передергивании питания косяк пропадал. Естественно я начал проверять всю схему на помехоустойчивость, а именно оставил на макетке только PCF8574 с подключенным питанием, землей и ногами А0,А1 и А2 подключенным к земле (контроллер, диоды, кнопки, лишние перемычки все убрал). Одной рукой "грею" микросхему, а второй касаюсь всех ног по очереди изолированной отверткой - и о чудо, если несколько раз
    быстро касаться связки (А0,А1,А2 и земля) микросхема начинала греться. Дальше подключаю А0,А1,А2 к VCC и косяк пропал, затем А0 подключаю к земле, а остальные к "+" - косяк присутствует. Проблему на половину решил, но мне в схеме необходимо 2 шт PCF8574 и я копаю дальше...
    Вообщем вывод такой: Адресные пины А0,А1,А2 нельзя подключать напрямую к земле только через резистор ( я поставил 1 кОм), а к питанию можно.
    Вот такая шляпа ребята...

    ОтветитьУдалить
    Ответы
    1. Спасибо, что поделились ценным опытом! Для чистоты эксперимента надо было все входы микросхемы подтянуть, иначе возникает фактор неопределенности.

      Удалить