Следующим экспериментом в серии выступает применение протокола передачи I2C. Здесь нет описания самого протокола, этого добра в сети хватает в статье содержится инструкция по установке связи между микроконтроллером PIC16f628a и так называемым «расширителем» портов мк PCA9539.
Итак: Задача: Установка связи по протоколу I2C.
Исходный материал: PIC16f628a, PCA9539, devboard.
Вкратце о том, зачем это мне: у меня сейчас на стадии разработки проект часы/термометр, и я выбирал средство индикации для него. Остановился на двух дисплеях: трехразрядный семисегментник и четырехразрядный семисегментник. Для управления ими нужно 8+4+8+3 = 23 бита с нашими 16 портами это нереально. Сначала я думал использовать для этого сдвиговый регистр, но потом мне прилетели халявные образцы от NXP и я решил заюзать их. В данном случае будет использоваться 7+2 = 9 портов, причем 2 порта для шины I2C будут использоваться и для связи с термометром и часами реального времени.
Для начала посмотрим внутрь нашего io expander’a:
Помимо 16 портов здесь присутствует два бита ручного указания адреса, а также вывод прерываний. Фактически, адрес конкретно устройства здесь формируется только двумя битами А0 и А1:
В моем случае я эти биты подтянул на землю, то есть адрес устройства выглядит так:
То есть начало передачи для нашей индикации должно начинаться с такого байта.
Далее должен следовать командный байт, который указывает куда именно собираемся записывать информацию (конфигурационный бит, непосредственно порты и т.д.).
*Цифры ниже приведены в десятичной системе0 Входной порт 0 1 Входной порт 1 2 Выходной порт 0 3 Выходной порт 1 4 Инверсия полярности порта 0 5 Инверсия полярности порта 1 6 Конфигурационный порт 1 7 Конфигурационный порт 2
При работе с данной микросхемой нужно помнить, что регистры всегда работают в паре, то есть если вы пишите в порт 0 данные, то придется их записывать и порт 1, командный бит по сути просто указывает на то, в какой из портов данные будут записываться сначала.
После командного байта записываются два нужных нам байта. В общем случае запись в выходные порты выглядит примерно так:
Остановимся пока на только на записи в микросхему, с чтением будем разбираться чуть позже. Переходим непосредственно к коду. Здесь главная фишка в том, что мы оперируем не непосредственно состояниями выходов порта, а лишь изменением направления данного порта (ввод/вывод). Поэтому не забываем вначале нашей программы добавлять следующие строки:
#define SCL TRISB6
#define SDA TRISB5
#define SCL_IN RB6
#define SDA_IN RB5
Задержка для тактирования:
void i2c_delay (void)
{
__delay_us(10);
}
Далее процедура старта:
void i2c_start(void) //
{
SCL = 1;
SDA = 1;
i2c_delay();
SDA = 0;
i2c_delay();
SCL = 0;
}
Процедура окончания:
void i2c_stop (void)
{
SDA = 0;
i2c_delay();
SCL = 1;
i2c_delay();
SDA = 1;
i2c_delay();
}
Процедура записи выглядит так:
bit i2c_tx(unsigned char d)
{
char x;
static bit b;
for (x=0; x<8; x++) { //передача 8 бит данных i2c_delay(); if (d&0x80) SDA = 1; else SDA = 0; i2c_delay(); SCL = 1; d <<= 1; i2c_delay(); SCL = 0; } i2c_delay(); i2c_delay(); SDA = 1; //готовимся получить ACK бит SCL = 1; i2c_delay(); b = SDA_IN; //получаем ACK бит SCL = 0; return b; //Возвращаем значение бита ACK через функцию }
Итак, для записи в микросхему у нас есть все что нужно. В качестве примера будем выводить двухзначное число на дисплей, которое будет увеличиваться на единицу по нажатию на кнопку. Для вывода на дисплей создадим отдельную функцию:
void dispo(unsigned char temp)
{ unsigned char i = 0; i = trunc(temp/10); maj= 0; min =0; i2c_start(); i2c_tx(0b11101000); i2c_tx(0b00000010); i2c_tx(digit[i]); i2c_tx(digit[i]); i2c_stop(); maj= 1; min = 0; __delay_ms(5); i = temp - (trunc(temp/10)*10); maj= 0; min = 0; i2c_start(); i2c_tx(0b11101000); i2c_tx(0b00000010); i2c_tx(digit[i]); i2c_tx(digit[i]); i2c_stop(); maj= 0; min = 1; __delay_ms(5); }
Надо также помнить, что перед записью в порты их нужно сконфигурировать на выход, записав нули в конфигурационные биты. Нажатие на кнопку обрабатывается прерыванием. Результат работы: