Шина I2C и AVR пример, PCF8583 (RTC) вечный календарь
Шина I2C очень распространена, существуют АЦП, ЦАП, термометры, память с интерфейсом I2C. При проектировании устройств и их починке приходится сталкиваться с этой шиной. Рассмотрим ее поподробнее. Шина I2C была спроектирована компанией Philips в начале 80-х и предназначена для низкоскоростной передачи данных по двум сигнальным проводникам, SDA для данных и SCL для тактирования.
Для подтяжки линий SDA, SCL используются резисторы номиналом 4.7кОм (при большой емкости шины номинал подтягивающих резисторов требуется уменьшить). Для успешного обмена данными на шине обязательно присутствие одного ведущего устройства (master device), которое производит тактирование и одного или более ведомых устройств (slave device). Количество ведомых устройств ограничено емкостью шины 500пФ и адресным пространством. Каждое ведомое устройство на шине должно иметь8-ми битный адрес, по которому оно отзывается.
При начале обмена данными по I2C, ведущему устройству необходимо сгенерировать так званое условие старта (start condition), а при окончании – условие остановки (stop condition). Эти условия выглядят так:
При старте на линию SDA подается низкий уровень, при высоком уровне на SCL и удерживается так в течении некоторого времени. При стопе линия SDA отпускается до высокого уровня, при высоком уровне на SCL. За конкретными таймингами нужно лезть в спецификацию шины I2C. После подачи условия старта, необходимо подать 8-ми битный адрес устройства, с которым мы хотим продолжить «общение». Это делается вот так:
Начиная со старшего бита, подаем на линию SDA данные, после записываем их импульсом на линии SCL, и так 8 раз. После чего ведомое устройство, адрес которого был назван, должно откликнуться, для этого предназначен 9-й синхроимпульс на линии SCL, во время которого адресуемое устройство должно удерживать логический ноль на линии данных SDA, тем самым обозначает свое наличие. Это подтверждение в документации называется ACK (Acknowledge) и используется каждый раз после передачи байта. Подавать сигнал ACK должно устройство которому предназначается команда или данные. То есть, если мы записываем данные в ведомого, откликается ведомый, а если считываем данные – то откликаться должен ведущий, после корректного приема байта. Дальнейший обмен данными производиться по той же схема, 8 бит данных, 1 для ACK и т.д. На словах работа с шиной проста. Предлагаю сварганить простые часы на микросхеме вечного календаря PCF8583, с интерфейсом . Выводить время и дату будет на символьный ЖКИ WH1602A с помощью микроконтроллера ATmega8. Рассмотрим подключение шины I2C к микроконтроллеру.
Тактироваться микроконтроллер будет от внутреннего RC генератора частотой 4 МГц. Кварц Х1 – часовой на 32768Гц, конденсатор С1 – 30пФ, С5,С7 – электролиты на 100 и 200мкФ соответственно, С4,С6 – керамика на 0,1мкФ. Подтягивающие резисторы R1,R4 – 4,7кОм. Ограничительные резисторы R2 – 1кОм, R3 – 17 Ом, потенциометр для регулировки контраста ЖКИ VR1 – 10кОм. Три кнопки послужат для задание даты и времени.
А вот как это выглядит на макетке:
В этой заметке я хочу использовать программный I2C, так как морочиться с аппаратным пока нет желания. Не вдаваясь в подробности работы PCF8583, скажу, что если к ней присоединить кварц с конденсатором и подать питание, то с ее регистров можно будет считать время и дату в BCD формате. Т.е. 23 часа 45 минуты 11 секунд будет закодировано вот так: 0х23 часа 0х45 минуты 0х11 секунд. Кажется удобным, но мне чего-то не понравилось.
PCF8583 кроме стандартного питания и SDA, SCL имеет ножку А0, которая используется для задания одного из битов ее адреса, для возможности работы двух PCF8583 на одной шине. Также имеется ножка INT, которая может сгенерировать низкий уровень при наступлении некоторого события. Так, как на шине PCF8583 всего одна и всякие события использоваться не будет, то смело подключим А0 к земле, а INT не будем задействовать. Адрес PCF8583 состоит из 2-х тетрад, неизменяемой и изменяемой:
Для того, чтобы записать данные в PCF8583 (в нашем случае А0=0), нужно выдавать адрес 0хА0. Для чтения же, нужно выдавать адрес 0хА1. Немного неудобно. Интересующее время/дата храниться в регистрах начиная с адреса 0х02 и заканчивая адресом 0х06:
То есть, наша задача периодически считывать эти данные и выводить их на ЖКИ, предусмотрев возможно записи введенных с клавиш, данных в саму PCF8583. Считывать данные с PCF8583 нужно в таком порядке:
Выдаем на шину условие старта, выдаем адрес для записи 0хА0, выдаем адрес регистра, начиная с которого хотим считать данные, в нашем случае это 0х02. Далее снова выдаем условие старта, адрес устройства для чтения 0хА1 и считываем по одному байты из PCF8583. При последнем считанном байте не нужно давать ACK (этого делать я не стал, т.е. все время даю сигнал ACK, на работоспособность это не повлияло).
Также просмотрим диаграмму для записи данных в PCF8583:
Здесь все проще. Выдаем адрес 0хА0, после чего выдаем адрес регистра, начиная с которого хотим производить запись. После чего, один за другим, передаем байты.
Для реализации этих диаграмм напишем следующие функции:
#define SDA 0 #define SCL 1 #define READ_PCF 0xa1 //Адрес для чтения #define WRITE_PCF 0xa0 //Адрес для записи ////edition by МIROSLAV SAMES //#define READ_PCF 0xA3 // //#define WRITE_PCF 0xa2 // //to test replace by two strings below unsigned char ack=0; //АСК, если=1 – произошла ошибка unsigned char data[5]={}; //Данные время/даты … DDRB=0x03; //init PORTB PORTB=0x0c; … void start_cond (void) //Генерация условия старта { PORTB=_BV(SDA)|_BV(SCL); asm("nop"); PORTB&=~_BV(SDA); asm("nop"); PORTB&=~_BV(SCL); } void stop_cond (void) //Генерация условия стоп { PORTB=_BV(SCL); asm("nop"); PORTB&=~_BV(SDA); asm("nop"); PORTB|=_BV(SDA); } void send_byte (unsigned char data) //Отправка байта по I2С { unsigned char i; for (i=0;i<8;i++) { if ((data&0x80)==0x00) PORTB&=~_BV(SDA); //Выставить бит на SDA else PORTB|=_BV(SDA); asm("nop"); PORTB|=_BV(SCL); //Записать его импульсом на SCL asm("nop"); PORTB&=~_BV(SCL); data=data<<1; } DDRB&=~_BV(SDA); PORTB|=_BV(SDA); asm("nop"); PORTB|=_BV(SCL); asm("nop"); if ((PINB&_BV(SDA))==_BV(SDA)) ack=1; //Считать ACK PORTB&=~_BV(SCL); PORTB&=~_BV(SDA); DDRB|=_BV(SDA); } unsigned char get_byte (void) //Считывание байта по I2C { unsigned char i, res=0; DDRB&=~_BV(SDA); PORTB|=_BV(SDA); for (i=0;i<8;i++) { res=res<<1; PORTB|=_BV(SCL); //Импульс на SCL asm("nop"); if ((PINB&_BV(SDA))==_BV(SDA)) res=res|0x01;//Чтение SDA PORTB&=~_BV(SCL); asm("nop"); } PORTB&=~_BV(SDA); //Подтверждение ACK DDRB|=_BV(SDA); asm("nop"); PORTB|=_BV(SCL); asm("nop"); PORTB&=~_BV(SCL); return res; } void read_date_time (void) //Считать данные в массив data { unsigned char i; start_cond(); send_byte (WRITE_PCF); send_byte (0x02); start_cond(); send_byte(READ_PCF); for (i=0;i<5;i++) data[i]=get_byte(); stop_cond(); } void write_date_time (void) //Записать данные из массива data { unsigned char i; start_cond(); send_byte (WRITE_PCF); send_byte (0x02); for (i=0;i<5;i++) send_byte (data[i]); stop_cond(); }
Думаю особые объяснения не нужны, все делалось по диаграммам. Снятую осциллограмму с логического анализатора при чтении данных из PCF8583 можно глянуть здесь:
При отлове багов я столкнулся с «черной магией». Клавиши хаотично нажимались, не поддаваясь контролю. Причину их странного поведения выявить удалось не сразу. Виноватым оказалось пересечение проводков от клавиш и линии SCL, из которой передавались наводки, поэтому клавиши вынес на PORTC. Также мне показалось неудобной работа с BCD арифметикой и учетом всяких високосных/невисокосных годов, месяцев с 30/31 днями и т.д. Неудобен с точки зрения микроконтроллера человеческий календарь.
Посмотреть на работу часов можно здесь:
Скачать полный код проекта для AVR Studio 4
формате ВCD
Объясните начинающему, в каком виде хранятся данные (дата, время ...) в массиве data[]?
Если я правильно понимаю в формате ВCD?. Получается если число 16 то в микросхеме хранится в виде 0001 0110 а в массиве виде 0x16 (в шестнадцатеричной системе счисления)?
Все правильно, в программе в
Все правильно, в программе в архиве реализована перекодировка чисел вытянутых из PCF8583 в формате BCD в шестнадцатиричную систему. И наоборот при записи данных в микросхему.
Собрал конструкцию, все
Собрал конструкцию, все работает правда есть пару моментов:
1. На кране ЖКИ видно заметное мерцание подсветки.
2. При изменении частоты тактирования на внутренний источник 1МГц все только ухудшилось мигание стало еще более заметным.
3. Плохо работают кнопки, при входе в меню на экран выводится что-то непонятное.
4. На кране присутствует лишний символ "двоеточие" в начале второй строки.
5. Кварц выше 4МГц не советую ставить, так как начинает сбоить и сама шина I2C и ЖКИ.
1-2) Это мерцание
1-2) Это мерцание изображения, нужно увеличить частоту обновления.
3) Кнопки лучше перевесить на внешнее прерывания, все будет ок работать.
4)ХЗ кинь фотку.
5)Нужно переделать задержки, если хочешь другой кварц.
Купил батарейку CR2025 с
Купил батарейку CR2025 с напряжением 3,3Вольта вывел питание PCF8583 на нее, оставил подключенную только общую ногу.
Результат:
Шина I2C и AVR пример, PCF8583 (RTC) вечный календарь
Согласно приведенному коду, при инициализации порта, пины (SDA, SCL) регистра DDRB настроены на выход, и записав в порт PORTB значение 0x0c мы тем самым садим шину на ноль. Не логичнее было бы при инициализации не занимать шину.
Логично, почему-то завтыкал с
Логично, почему-то завтыкал с 0х0с. Нужно 0х03. Вечером поправлю код.
Тогда порт нужно будет
Тогда порт нужно будет настроить на вход (с подтягивающими резисторами), так как если на линии будет присутствовать второй мастер (или еще чего), и вдруг он захочет начать вещание, то после прижатия линии к нулю может выйти из строя порт первого мастера.