Сегодня на нашем операционном столе новый гость, это продукт компании Microchip расширитель портов MCP23008-E. Предназначена эта штуковина (как понятно из названия) для увеличения числа I/O ног микроконтроллера, если их вдруг стало не хватать. Конечно если нам нужные ноги-выходы то можно взять сдвиговый регистр и не париться. Если нужны ноги-входы то и тут есть решение на жесткой логике. Если же нам нужны одновременно входы и выходы да еще и управляемая подтяжка для входов, то расширитель портов это пожалуй самое нормальное решение. Что касаемо цены девайса то она весьма скромная — примерно бакс. В данной статье я попробую детально описать как рулить данной микросхемой при помощи микроконтроллера AVR.

Для начала немного о характеристиках:

  • 8 независимых пинов порта
  • Интерфейс для связи с внешним миром —  I2C ( частота до 1.7 МГц)
  • Настраиваемая подтяжка для входов
  • Может дёрнуть ногой когда состояние определённых входов изменится
  • Три входа для задания адреса микросхемы (можно повесить 8 устройств на одну шину)
  • Рабочее напряжение от 1.8 до 5.5 вольт
  • Малый ток потребления
  • Большой выбор корпусов  (PDIP/SOIC/SSOP/QFN)

Интерфейс я предпочитаю использовать I2C, он привлекает меня малым количеством проводков :-) однако если нужна очень быстрая скорость (до 10 МГц), то использовать нужно SPI интерфейс который присутствует в MCP23S08. Различие между MCP23S08 и MCP23008 как я понял только в интерфейсе и в количестве ног для задания адреса микросхемы. Кому что по душе короче. Распиновка микрухи есть в даташите, она ни чем не интересна и рассматриваться тут не будет. Поэтому сразу перейдем к  тому как начать работать с данным девайсом. Все работа сводится к тому чтоб записывать и считывать определенные данные из регистров микросхемы. Регистров на радость мне оказалось совсем не много  — всего лишь одиннадцать штук. Писать и читать данные из регистров очень просто,  пост про это уже был. Речь там правда не о регистрах, но принцип тот же. Теперь осталось выяснить из каких регистров что считывать и в какие регистры что записывать. В этом нам разумеется поможет даташит. Из даташита мы узнаем, что у микросхемы есть следующие регистры:

Регистр IODIR
Задаёт направление  в котором идут данные. Если бит соответствующий определённой ножке установлен в единицу, то ножка вход. Если сброшен в ноль, то выход. Короче этот регистр аналог DDRx в AVR (только в AVR 1- это выход а 0 — вход).

Регистр IPOL
Если бит регистра установлен,то для соответствующей ноги включена инверсия входа. Это означает, что если на ножку подать лог. ноль то из регистра GPIO считается единица и наоборот. Полезность данной фичи весьма сомнительна.

Регистр GPINTEN
Каждый бит этого регистра соответствует определённому пину порта. Если бит установлен, то соответствующий пин порта настроенный на вход может вызывать прерывание. Если бит сброшен, то что бы не делали с ногой порта — прерывания не будет. Условия возникновения прерывания задаются двумя следующими регистрами.

Регистр DEFVAL
Текущее значение пинов настроенных на вход постоянно сравнивается с этим регистром. Если вдруг текущее значение стало отличаться от того что в этом регистре, то возникает прерывание. Проще говоря — если бит установлен то прерывание будет возникать когда на соответствующей ножке произойдет смена уровня с высокого на низкий. В случае если бит сброшен, то прерывание возникает по нарастающему фронту.

Регистр INTCON
Каждый бит этого регистра соответствует определённому пину порта. Если бит сброшен, то любая смена логического уровня  на противоположный вызывает прерывание. В случае если бит установлен, то на возникновение прерывания оказывает влияние регистр DEFVAL (в противоположном случае он вообще игнорируется).

Регистр IOCON
Это регистр настроек. Состоит он из четырёх бит:
SEQOP — бит управляет автоувеличением адреса. Если он установлен то автоувеличение отключено, в противном случае включено. Если мы выключим его, то можем очень быстро считывать значение одного и того же регистра за счёт того, что нам не придётся передавать его адрес каждый раз. Если же нужно быстро прочитать все 11 регистров по очереди то автоувеличение нужно включить. При каждом считывании байта из регистра адрес будет сам увеличиваться на единицу и передавать его не придётся.
DISSLW — фиг знает чё за бит.  Как его не крутил всё равно работает всё. Буду рад если кто объяснит.
HAEN — Бит настройки вывода INT. Если он установлен то вывод сконфигурирован как открытый сток, если бит сброшен, то активный уровень на ноге INT определяет бит INTPOL
INTPOL — определяет активный уровень на ноге INT. Если он установлен то активный уровень единица, в противном случае ноль.

Есть еще бит HAEN но он не используется в данной микросхеме (включает/выключает пины аппаратной адресации в MCP23S08)

Регистр GPPU
Управляет подтяжкой входов. Если бит установлен то на соответствующем ему пине появится подтяжка к питанию через резистор 100 кОм.

Регистр INTF
Регистр флагов прерываний. Если  бит установлен то это означает, что соответствующая ему нога порта вызывала прерывание. Разумеется прерывания для нужных ног должны быть включены в регистре GPINTEN

Регистр  INTCAP
В момент возникновения прерывания в этот регистр считывается весь порт. Если потом после этого будут еще прерывания, то содержимое этого регистра не затрется новым значением до тех пор пока мы не считаем его или GPIO.

Регистр GPIO
Считывая данные из регистра — считываем логические уровни на ножках порта. Записывая данные — устанавливаем логические уровни. При записи в этот регистр автоматически происходит запись этих же самых данных в регистр OLAT

Регистр OLAT
Записывая данные в этот регистр — выводим данные в порт. Если прочитать оттуда данные, то прочитается то что записали, а не то что что фактически есть на входах порта. Для чтения входов используем только GPIO.

Описание регистров на мой взгляд вполне нормальное и мозг ни кому взорвать не должно. Теперь ради интереса напишем небольшой демонстрационный пример который будет опрашивать 4 кнопки подключенные к расширителю порта, и в зависимости от их состояния зажигать или гасить 4 соответствующих светодиода подключенных к тому же расширителю. Пример будет максимально простым, без всяких прерываний. Просто постоянно считываем состояние кнопок и выводим это состояние на светодиоды. Схема нашего устройства будет выглядеть так :

Вывод прерывания можно не подключать, он тут нам не нужен, а вот подтяжка для ноги сброса обязательна! Я потратил много времени пока понял, что расширитель портов у меня периодически сбрасывался из-за отсутствия подтяжки. Теперь возьмёмся за код. Писать будем конечно же на микропаскале.  Код простецкий:

program rashiritel;
const  AdrR=%01000001; //Адрес микросхемы с битом чтение
const  AdrW=%01000000; //Адрес микросхемы с битом запись
var r:byte;
///Процедура записывает данные из переменной Dat в регистр по адресу Adr
Procedure WriteReg(Dat,Adr:byte);
Begin
     TWI_Start();
     TWI_Write(AdrW);
     TWI_Write(Adr);
     TWI_Write(Dat);
     TWI_Stop();
End;
 
///Функция возвращает значение регистра по адресу Adr
Function ReadReg(Adr:byte):byte;
var a:byte;
Begin
     TWI_Start();
     TWI_Write(AdrW);
     TWI_Write(Adr);
     TWI_Start();
     TWI_Write(AdrR);
     a:=TWI_Read(0);
     TWI_Stop();
     result:=a;
End;
 
begin
     TWI_INIT(200000); ///Инициализация i2c
     WriteReg(%00001111,0x00); //Младшие 4 бита входы а остальные 4 выходы
     WriteReg(%00001111,0x06); //Вкл. подтяжку для 4-х входов
     While TRUE do
     Begin
         r:=ReadReg(0x09); //Считали состояние входов
         r:= NOT r; //Надо инвертировать биты иначе при отпущеной кнопке светодиоды будут гореть
         r:= r shl 4; //Сдвигаем на 4 бита влево...
         WriteReg(r,0x0A); //Выводим состояние кнопок
     end;
end.

Паяем, компилируем, зашиваем и вуаля! Всё работает!  Если же не работает, то вы знаете куда писать :-)