Теория I2C, EEPROM от Atmel. Библиотека Wire для Arduino для работы с шиной I2C Как это работает

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

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

Эта книга задумана как продолжение бестселлера «Programming Arduino: Getting Started with Sketches». Несмотря на то что эта книга включает краткое повторение основ из книги «Programming Arduino», она познакомит читателя с более продвинутыми аспектами программирования плат Arduino.

На разных моделях Arduino интерфейс I2C подключается к разным контактам. Например, в модели Uno используются контакты A4 и A5 - линии SDA и SCL соответственно, а в модели Leonardo используются контакты D2 и D3. (Подробнее о линиях SDA и SCL рассказывается в следующем разделе.) На обеих моделях линии SDA и SCL выводятся также на колодку, находящуюся рядом с контактом AREF (рис. 7.3).

В табл. 7.1 перечисляются наиболее распространенные модели платы Arduino и контакты, соответствующие интерфейсу I2C.

Рис. 7.3. Контакты I2C на плате Arduino Uno

Таблица 7.1. Контакты I2C в разных моделях Arduino

Модель Контакты Примечания
Uno A4 (SDA) и A5 (SCL) Контакты подписаны SCL и SDA и находятся рядом с контактом AREF. Эти линии интерфейса выводятся также на контакты A4 и A5
Leonardo D2 (SDA) и D3 (SCL) Контакты подписаны SCL и SDA и находятся рядом с контактом AREF. Эти линии интерфейса выводятся также на контакты D2 и D3
Mega2560 D20 (SDA) и D21 (SCL) -
Due D20 (SDA) и D21 (SCL) Модель Due имеет вторую пару контактов I2C, подписанных SDA1 и SCL1

Протокол I2C

Для передачи и приема данных через интерфейс I2C используются две линии (отсюда второе название - двухпроводной интерфейс, Two Wire Interface). Эти две линии называют также тактовой линией (Serial Clock Line, SCL) и линией данных (Serial Data Line, SDA). На рис. 7.4 изображена временная диаграмма сигнала, передаваемого через интерфейс I2C.

Рис. 7.4. Временная диаграмма сигнала, передаваемого через интерфейс I2C

Ведущее устройство генерирует тактовые импульсы на линии SCL, и, когда имеются данные для передачи, отправитель (ведущее или ведомое устройство) выводит линию SDA из третьего состояния (в режим цифрового выхода) и посылает данные в виде логических нулей и единиц в моменты положительных импульсов тактового сигнала. По окончании передачи вывод тактовых импульсов может быть остановлен, и линия SDA возвращается в третье состояние.

Библиотека Wire

Можно, конечно, генерировать описанные ранее импульсы самостоятельно, управляя битами, то есть включая и выключая цифровые выходы программно. Но чтобы упростить нам жизнь, в составе программного обеспечения для Arduino имеется библиотека Wire, принимающая на себя все сложности, связанные с синхронизацией, и позволяющая нам просто посылать и принимать байты данных.

Чтобы задействовать библиотеку Wire, ее сначала нужно подключить командой

#include

Инициализация I2C

В большинстве случаев плата Arduino играет роль ведущего устройства на любой шине I2C. Чтобы инициализировать Arduino как ведущее устройство, нужно выполнить команду begin в функции setup, как показано далее:

Обратите внимание: поскольку в данном случае плата Arduino действует как ведущее устройство, ей не нужно присваивать адрес. Если бы плата настраивалась на работу в режиме ведомого устройства, нам пришлось бы присвоить адрес в диапазоне от 0 до 127, передав его как параметр, чтобы уникально идентифицировать плату на шине I2C.

Отправка данных ведущим устройством

Чтобы отправить данные устройству на шине I2C, сначала нужно выполнить функцию beginTransmission и передать ей адрес устройства-получателя:

Wire.beginTransmission(4);

Отправка данных устройствам на шине I2C может производиться побайтно или целыми массивами типа char, как показано в следующих двух примерах:

Wire.send(123); // передача байта со значением 123

Wire.send("ABC"); // передача строки символов "ABC"

По окончании передачи должна вызываться функция endTransmission:

Wire.endTransmission();

Прием данных ведущим устройством

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

Wire.requestFrom(4, 6); // запросить 6 байт у устройства с адресом 4

В первом аргументе этой функции передается адрес ведомого устройства, от которого ведущее устройство желает получить данные, а во втором аргументе - количество байтов, которое ожидается получить. Ведомое устройство может передать меньшее количество байтов, поэтому, чтобы определить, были ли получены данные и сколько байтов действительно получено, необходимо использовать функцию available. Следующий пример (взят из пакета примеров, входящих в состав библиотеки Wire) демонстрирует, как ведущее устройство принимает все полученные данные и выводит их в монитор последовательного порта:

#include

Wire.begin(); // подключиться к шине i2c (для ведущего

// устройства адрес не указывается)

Serial.begin(9600); // инициализировать монитор последовательного порта

Wire.requestFrom(8, 6); // запросить 6 байт у ведомого устройства #8

while (Wire.available()) { // ведомое устройство может прислать меньше

char c = Wire.read(); // принять байт как символ

Библиотека Wire автоматически буферизует входящие данные.

Примеры использования I2C

Любое устройство I2C должно иметь сопроводительное техническое описание, где перечисляются поддерживаемые им сообщения. Такие описания необходимы, чтобы конструировать сообщения для отправки ведомым устройствам и интерпретировать их ответы. Однако для многих устройств I2C, которые можно подключить к плате Arduino, существуют специализированные библиотеки, обертывающие сообщения I2C в простые и удобные функции. Фактически, если вам придется работать с каким-то устройством, для которого отсутствует специализированная библиотека, опубликуйте собственную библиотеку для всеобщего использования и заработайте себе несколько очков в карму.

Даже если полноценная библиотека поддержки того или иного устройства отсутствует, часто в Интернете можно найти полезные фрагменты кода, демонстрирующие работу с устройством.

УКВ-радиоприемник TEA5767

В первом примере, демонстрирующем взаимодействие с устройством I2C, библиотека не используется. Здесь осуществляется обмен фактическими сообщениями между Arduino и модулем TEA5767. Данный модуль можно купить в Интернете очень недорого, он легко подключается к плате Arduino и используется как УКВ-приемник, управляемый Arduino.

Самый сложный этап - подключение устройства. Контактные площадки очень маленькие и расположены очень близко друг к другу, поэтому многие предпочитают смастерить или купить адаптер для подключения к плате.

На рис. 7.5 изображена схема подключения модуля к Arduino.

Рис. 7.5. Подключение модуля TEA5767 к плате Arduino Uno через интерфейс I2C

Техническое описание модуля TEA5767 можно найти по адресу www.sparkfun.com/datasheets/Wireless/General/TEA5767.pdf . Описание содержит массу технической информации, но, если пробежать взглядом по документу, можно заметить раздел с подробным описанием сообщений, распознаваемых устройством. В документации указывается, что TEA5767 принимает сообщения длиной 5 байт. Далее приводится полностью работоспособный пример, выполняющий настройку частоты сразу после запуска. На практике же обычно требуется несколько иной механизм настройки, например на основе кнопок и жидкокристаллического дисплея.

// sketch_07_01_I2C_TEA5767

#include

setFrequency(93.0); // МГц

void setFrequency(float frequency)

byte frequencyH = frequencyB >> 8;

Wire.beginTransmission(0x60);

Wire.write(frequencyH);

Wire.write(frequencyL);

Wire.write(0xB0);

Wire.write(0x10);

Wire.write(0x00);

Wire.endTransmission();

Весь код, представляющий для нас интерес в этом примере, находится в функции setFrequency. Она принимает вещественное число - частоту в мегагерцах. То есть, если вы пожелаете собрать и опробовать этот проект, узнайте частоту, на которой вещает местная радиостанция с хорошим сильным сигналом, и вставьте ее значение в вызов setFrequency в функции setup.

Чтобы преобразовать вещественное значение частоты в двухбайтное представление, которое можно послать в составе пятибайтного сообщения, нужно выполнить некоторые арифметические операции. Эти операции выполняет следующий фрагмент:

unsigned int frequencyB = 4 * (frequency * 1000000 + 225000) / 32768;

byte frequencyH = frequencyB >> 8;

byte frequencyL = frequencyB & 0XFF;

Команда >> сдвигает биты вправо, то есть операция >> 8 сдвинет старшие 8 бит в сторону младших на 8 двоичных разрядов. Оператор & выполняет поразрядную операцию И (AND), которая в данном случае сбросит старшие 8 бит и оставит только младшие. Более полное обсуждение операций с битами вы найдете в главе 9.

Остальной код в функции setFrequency инициализирует передачу сообщения I2C ведомому устройству с адресом 0x60, который закреплен за приемником TEA5767. Затем осуществляется последовательная передача 5 байт, начиная с 2 байт частоты.

Прочитав документацию, вы узнаете о множестве других возможностей, доступных посредством разных сообщений, например о сканировании диапазона, выключении одного или двух каналов вывода звука и выборе режима моно/стерео.

В приложении мы еще вернемся к этому примеру и создадим библиотеку для Arduino, чтобы упростить работу с модулем TEA5767.

Взаимодействие между двумя платами Arduino

Во втором примере используются две платы Arduino, одна действует как ведущее устройство I2C, а другая - как ведомое. Ведущее устройство будет посылать сообщения ведомому, которое, в свою очередь, будет выводить их в монитор последовательного порта, чтобы можно было наглядно убедиться, что схема работает.

Схема соединения плат для этого примера показана на рис. 7.6. Обратите внимание на то, что модуль TEA5767 имеет встроенные подтягивающие сопротивления на линиях I2C. Однако в данном случае, когда друг к другу подключаются две платы Arduinos, такие резисторы отсутствуют, поэтому понадобится включить свои сопротивления с номиналом 4,7 кОм (рис. 7.6).

Рис. 7.6. Соединение двух плат Arduino через интерфейс I2C

В платы должны быть загружены разные скетчи. Оба скетча включены в состав примеров для библиотеки Wire. Программа для ведущей платы Arduino находится в меню File->Example->Wire->master_writer (Файл-> Примеры->Wire->master_writer), а для ведомой платы - в меню File-> Example->Wire->slave_receiver (Файл->Примеры->Wire->slave_receiver).

Запрограммировав обе платы, оставьте ведомую подключенной к компьютеру, чтобы увидеть вывод с этой платы в монитор последовательного порта и обеспечить питание ведущей платы Arduino.

Начнем со скетча в ведущей плате:

#include

Wire.begin(); // подключиться к шине i2c (для ведущего устройства

// адрес не указывается)

Wire.beginTransmission(4); // инициализировать передачу устройству #4

Wire.write("x is "); // послать 5 байт

Wire.write(x); // послать 1 байт

Wire.endTransmission(); // остановить передачу

Этот скетч генерирует сообщение вида x is 1, где 1 - число, увеличивающееся каждые полсекунды. Затем сообщение посылается ведомому устройству с адресом 4, как определено в вызове beginTransmission.

Задача ведомого скетча - принять сообщение от ведущего устройства и вывести его в монитор последовательного порта:

#include

Wire.begin(4); // подключиться к шине i2c с адресом #4

Wire.onReceive(receiveEvent); // зарегистрировать обработчик события

Serial.begin(9600); // открыть монитор последовательного порта

// эта функция вызывается всякий раз, когда со стороны ведущего устройства

// поступают очередные данные, эта функция зарегистрирована как обработчик

// события, см. setup()

void receiveEvent(int howMany) {

while (1 < Wire.available()) { // цикл по всем принятым байтам, кроме

// последнего

Serial.print(c); // вывести символ

Serial.println(x); // вывести целое число

Первое, на что следует обратить внимание в этом скетче, - функции Wire.begin передается параметр 4. Он определяет адрес ведомого устройства на шине I2C, в данном случае 4. Он должен соответствовать адресу, который используется ведущим устройством для отправки сообщений.

СОВЕТ

К одной двухпроводной шине можно подключить множество ведомых плат Arduino при условии, что все они будут иметь разные адреса I2C.

Скетч для ведомой платы отличается от скетча для ведущей платы, потому что использует прерывания для приема сообщений, поступающих от ведущего устройства. Установка обработчика сообщений выполняется функцией onReceive, которая вызывается подобно подпрограммам обработки прерываний (глава 3). Вызов этой функции нужно поместить в функцию setup, чтобы обеспечить вызов пользовательской функции receiveEvent при получении любых поступающих сообщений.

Функция receiveEvent принимает единственный параметр - количество байт, готовых для чтения. В данном случае это число игнорируется. Цикл while читает по очереди все доступные символы и выводит их в монитор последовательного порта. Затем выполняются чтение единственного однобайтного числа в конце сообщения и его вывод в монитор порта. Использование println вместо write гарантирует, что значение байта будет выведено как число, а не символ с соответствующим числовым кодом (рис. 7.7).

Рис. 7.7. Вывод в монитор порта сообщений, получаемых одной платой Arduino от другой через интерфейс I2C

Платы со светодиодными индикаторами

Еще один широкий спектр устройств I2C - разного рода дисплеи. Наиболее типичными представителями этих устройств являются светодиодные матрицы и семисегментные индикаторы, производимые компанией Adafruit. Они содержат светодиодные дисплеи, смонтированные на печатной плате, и управляющие микросхемы с поддержкой интерфейса I2C. Такое решение избавляет от необходимости использовать большое число контактов ввода/вывода на плате Arduino для управления светодиодным дисплеем и позволяет обойтись всего двумя контактами, SDA и SCL.

Библиотеки скрывают все взаимодействия через интерфейс I2C за своим фасадом, давая возможность пользоваться высокоуровневыми командами, как демонстрирует следующий фрагмент, взятый из примера, входящего в состав библиотеки:

#include

#include "Adafruit_LEDBackpack.h"

#include "Adafruit_GFX.h"

Adafruit_8x8matrix matrix = Adafruit_8x8matrix();

matrix.begin(0x70);

matrix.drawLine(0, 0, 7, 7, LED_RED);

matrix.writeDisplay();

Часы реального времени DS1307

Еще одно распространенное устройство I2C - модуль часов реального времени DS1307. Для этого модуля также имеется удобная и надежная библиотека, упрощающая взаимодействие с модулем и избавляющая от необходимости иметь дело с фактическими сообщениями I2C. Библиотека называется RTClib и доступна по адресу https://github.com/adafruit/RTClib .

Следующие фрагменты кода тоже взяты из примеров, поставляемых с библиотекой:

#include

#include "RTClib.h"

Serial.begin(9600);

if (! RTC.isrunning()) {

Serial.println("RTC is NOT running!");

// записать в модуль дату и время компиляции скетча

RTC.adjust(DateTime(__DATE__, __TIME__));

DateTime now = RTC.now();

Serial.print(now.year(), DEC);

Serial.print("/");

Serial.print(now.month(), DEC);

Serial.print("/");

Serial.print(now.day(), DEC);

Serial.print(" (");

Serial.print(daysOfTheWeek);

Serial.print(") ");

Serial.print(now.hour(), DEC);

Serial.print(":");

Serial.print(now.minute(), DEC);

Serial.print(":");

Serial.print(now.second(), DEC);

Serial.println();

Если вам интересно увидеть, как в действительности выполняются взаимодействия через интерфейс I2C, просто загляните в файлы библиотеки. Например, исходный код библиотеки RTClib хранится в файлах RTClib.h и RTClib.cpp. Эти файлы находятся в папке libraries/RTClib.

Например, в файле RTClib.cpp можно найти определение функции now:

DateTime RTC_DS1307::now() {

Wire.beginTransmission(DS1307_ADDRESS);

Wire.endTransmission();

Wire.requestFrom(DS1307_ADDRESS, 7);

uint8_t ss = bcd2bin(Wire.read() & 0x7F);

uint8_t mm = bcd2bin(Wire.read());

uint8_t hh = bcd2bin(Wire.read());

uint8_t d = bcd2bin(Wire.read());

uint8_t m = bcd2bin(Wire.read());

uint16_t y = bcd2bin(Wire.read()) + 2000;

return DateTime (y, m, d, hh, mm, ss);

Функция Wire.read возвращает значения в двоично-десятичном формате (Binary-Coded Decimal, BCD), поэтому они преобразуются в байты с помощью библиотечной функции bcd2bin.

В формате BCD байт делится на два 4-битных полубайта. Каждый полубайт представляет одну цифру двузначного десятичного числа. Так, число 37 в формате BCD будет представлено как 0011 0111. Первые четыре бита соответствуют десятичному значению 3, а вторые четыре бита - значению 7.

В заключение

В этой главе вы познакомились с интерфейсом I2C и приемами его использования для организации взаимодействий плат Arduino с периферийными устройствами и другими платами Arduino.

В следующей главе мы исследуем еще одну разновидность последовательного интерфейса, используемого для взаимодействий с периферией. Он называется 1-Wire . Этот интерфейс не получил такого широкого распространения, как I2C, но он используется в популярном датчике температуры DS18B20.

С номиналами от 10 Ом до 1 МОм);

  • 2 резистора по 4,7 кОм (из того же набора);
  • соединительные провода (например, вот хороший набор);
  • компьютер с Arduino IDE.
  • 1 Описание интерфейса I2C

    Последовательный протокол обмена данными IIC (также называемый I2C - Inter-Integrated Circuits, межмикросхемное соединение) использует для передачи данных две двунаправленные линии связи, которые называются шина последовательных данных SDA (Serial Data) и шина тактирования SCL (Serial Clock) . Также имеются две линии для питания. Шины SDA и SCL подтягиваются к шине питания через резисторы.

    В сети есть хотя бы одно ведущее устройство (Master) , которое инициализирует передачу данных и генерирует сигналы синхронизации. В сети также есть ведомые устройства (Slave) , которые передают данные по запросу ведущего. У каждого ведомого устройства есть уникальный адрес, по которому ведущий и обращается к нему. Адрес устройства указывается в паспорте (datasheet). К одной шине I2C может быть подключено до 127 устройств, в том числе несколько ведущих. К шине можно подключать устройства в процессе работы, т.е. она поддерживает «горячее подключение».

    Давайте рассмотрим временную диаграмму обмена по протоколу I2C. Есть несколько различающихся вариантов, рассмотрим один из распространённых. Воспользуемся логическим анализатором, подключённым к шинам SCL и SDA.

    Мастер инициирует обмен. Для этого он начинает генерировать тактовые импульсы и посылает их по линии SCL пачкой из 9-ти штук. Одновременно на линии данных SDA он выставляет адрес устройства , с которым необходимо установить связь, которые тактируются первыми 7-ми тактовыми импульсами (отсюда ограничение на диапазон адресов: 2 7 = 128 минус нулевой адрес). Следующий бит посылки - это код операции (чтение или запись) и ещё один бит - бит подтверждения (ACK), что ведомое устройство приняло запрос. Если бит подтверждения не пришёл, на этом обмен заканчивается. Или мастер продолжает посылать повторные запросы.

    Это проиллюстрировано на рисунке ниже.. В первом случае, для примера, отключим ведомое устройство от шины. Видно, что мастер пытается установить связь с устройством с адресом 0x27, но не получает подтверждения (NAK). Обмен заканчивается.


    Теперь подключим к шине I2C ведомое устройство и повторим операцию. Ситуация изменилась. На первый пакет с адресом пришло подтверждение (ACK) от ведомого. Обмен продолжился. Информация передаётся также 9-битовыми посылками, но теперь 8 битов занимают данные и 1 бит - бит подтверждения получения ведомым каждого байта данных. Если в какой-то момент связь оборвётся и бит подтверждения не придёт, мастер прекратит передачу.

    2 Реализация I2C в Arduino

    Arduino использует для работы по интерфейсу I2C два порта. Например, в Arduino UNO и Arduino Nano аналоговый порт A4 соответствует SDA, аналоговый порт A5 соответствует SCL.


    Для других моделей плат соответствие выводов такое:

    3 Библиотека "Wire" для работы с IIC

    Для облегчения обмена данными с устройствами по шине I2C для Arduino написана стандартная библиотека Wire . Она имеет следующие функции:

    Функция Назначение
    begin(address) инициализация библиотеки и подключение к шине I2C; если не указан адрес, то присоединённое устройство считается ведущим; используется 7-битная адресация;
    requestFrom() используется ведущим устройством для запроса определённого количества байтов от ведомого;
    beginTransmission(address) начало передачи данных к ведомому устройству по определённому адресу;
    endTransmission() прекращение передачи данных ведомому;
    write() запись данных от ведомого в ответ на запрос;
    available() возвращает количество байт информации, доступных для приёма от ведомого;
    read() чтение байта, переданного от ведомого ведущему или от ведущего ведомому;
    onReceive() указывает на функцию, которая должна быть вызвана, когда ведомое устройство получит передачу от ведущего;
    onRequest() указывает на функцию, которая должна быть вызвана, когда ведущее устройство получит передачу от ведомого.

    4 Подключение I2C устройства к Arduino

    Давайте посмотрим, как работать с шиной I2C с помощью Arduino.

    Сначала соберём схему, как на рисунке. Будем управлять яркостью светодиода, используя цифровой 64-позиционный потенциометр AD5171 (см. техническое описание), который подключается к шине I2C. Адрес, по которому мы будем обращаться к потенциометру - 0x2c (44 в десятичной системе).


    5 Управление устройством по шине IIC

    Рассмотрим диаграммы информационного обмена с цифровым потенциометром AD5171, представленные в техническом описании:


    Нас тут интересует диаграмма записи данных в регистр RDAC . Этот регистр используется для управления сопротивлением потенциометра.

    Откроем из примеров библиотеки "Wire" скетч: Файл Образцы Wire digital_potentiometer . Загрузим его в память Arduino.

    #include // подключаем библиотеку "Wire" byte val = 0; // значение для передачи потенциометру void setup() { Wire.begin(); // подключаемся к шине I2C как мастер } void loop() { Wire.beginTransmission(44); // начинаем обмен с устройством с I2C адресом "44" (0x2C) Wire.write(byte(0x00)); // посылаем инструкцию записи в регистр RDAC Wire.write(val); // задаём положение 64-позиционного потенциометра Wire.endTransmission(); // завершаем I2C передачу val++; // инкрементируем val на 1 if (val == 63) { // по достижении максимума потенциометра val = 0; // сбрасываем val } delay(500); }

    После включения вы видите, как яркость светодиода циклически нарастает, а потом гаснет. При этом мы управляем потенциометром с помощью Arduino по шине I2C.

    Мне нужно было сделать часы на основе микросхемы, имеющей I 2 C интерфейс. Микросхема RTC, т.н. "часы реального времени" PCF8583.

    Внутри микросхемы расположены: часы, будильник, таймер, календарь (кривой), и 240 байт оперативной памяти, куда можно записывать любую информацию, которую только вздумается. Оперативная память это очень полезная штука, в отличии от флеш-памяти, оперативная память не имеет ограничений по количеству циклов перезаписи, и в неё можно сохранять какие-то данные, настройки, сколь угодно часто.

    Но была одна проблемка - писАть код жутко не хотелось, и я решил найти готовый код в интернете. Как позже выяснилось, найти «на свою голову». Скачал пример работы с I 2 C, подправил, прошил микроконтроллер. Не заработало. Стал ковырять код, искать причину неработоспособности… и ужаснулся!! Запись в некоторых случаях велась во весь порт сразу, а не в конкретные биты. Таким образом, если на порт повесить ещё что-то, например, дисплей, то скорее всего, оно работать не будет. Также неправильно было реализовано чтение данных по шине (без генераций условия окончания приёма, или просто без NACK). Но это пол-беды. Основная проблема в другом. Зачастую автор кода выставлял в порт логическую «1», а как мы знаем, шина I 2 C управляется «притягиванием» выводов SDA и SCL к общему проводу. А логическая «1» на шине, в свою очередь, формируется за счёт подтяжки к плюсу питания резисторами на 4,7 килоом. Таким образом, если на выходе микроконтроллера выставить логическую «1», а ведомое устройство «притянет» этот выход к общему проводу, то получится «ба-бах» короткое замыкание. Мне это очень не понравилось, и я решил изобрести свой велосипед написать свою библиотеку, а вернее даже 2 библиотеки: одна для работы с шиной I 2 C, а другая непосредственно для работы с часами реального времени PCF8583. Да, кстати, код написан в .

    Для того, чтобы подключить библиотеку I 2 C к проекту, нужно прописать её через include, как на картинке, а также скопировать библиотеку в папку с проектом.

    После чего, необходимо открыть файл "i2c.h", и указать ножки микроконтроллера, которые будут выступать в роли шины I 2 C. По умолчанию шина настроена на ножки PC0 (SCL) и PC1 (SDA). А настройка делается вот тут:

    Всё, библиотеку I2C мы подключили, ножки настроили, библиотека готова к работе. Пример использования:

    I2c_init (); // Инициализация шины I2C i2c_start_cond(); // старт шины i2c_send_byte (0xA0); // адрес устройства, которое висит на шине i2c_send_byte (0x10); // байт данных, который записываем в устройство i2c_send_byte (0x10); // ещё один байт данных, который записываем в устройство i2c_stop_cond(); // стоп шины

    После стоп-условия, мы можем проверить, всё ли у нас в порядке с шиной I 2 C. Для этого нужно прочитать переменную «i2c_frame_error». Если всё нормально, то в ней будет 0. Если один из выводов шины не «подтянулся» к питанию, и логическая «1» не установилась на шине, то библиотека генерирует ошибку, и записвает в переменную «i2c_frame_error» циферку 1. Читать переменную «i2c_frame_error» нужно после стоп-условия. На рисунке ниже продемонстрирую как работает контроль ошибки:

    Теперь займёмся подключением библиотеки часов реального времени PCF8583. Для этого нужно проделать те же самые действия. Скопируем в папку с проектом файл "PCF8583.h", и пропишем его в include, как на фото:

    Готово. Библиотека часов реального времени PCF8583 подключена. Она не требует каких-либо настроек, поэтому можно сразу приступать к чтению времени и даты с микросхемы. Обращаю внимание, что библиотека PCF8583 работает при помощи библиотеки I2C, поэтому если хотим работать с PCF8583, то нужно подключить обе библиотеки!

    Пример использования библиотеки (запись и чтение времени и даты):

    // Инициализация шины I2C i2c_init (); // Подготавливаем время и дату для записи в микросхему PCF8583 PCF_hour=23; // 23 часа PCF_min=59; // 59 минут PCF_day=31; // 31 число PCF_month=12; // 12 месяц - декабрь PCF_year=0; // год (0 - не високосный) PCF_weekday=6; // 6 день недели (воскресенье) // Записываем время и дату в микросхему PCF8583 PCF_write_hh_mm_ss(); // Считываем время и дату из микросхемы PCF8583 PCF_read_hh_mm_ss(); Пример работы с оперативной памятью (запись и чтение): // Подготавливаем 5 байт для записи в микросхему PCF8583 PCF_data_ram_1=255; // байт 1 PCF_data_ram_2=255; // байт 2 PCF_data_ram_3=255; // байт 3 PCF_data_ram_4=255; // байт 4 PCF_data_ram_5=255; // байт 5 // Записываем 5 байт в микросхему PCF8583 PCF_write_ram(); // Считываем 5 байт из микросхемы PCF8583 PCF_read_ram();

    Чтение из микросхемы ещё проще – достаточно вызвать функцию PCF _ read _ hh _ mm _ ss () после чего, время и дата появятся в переменных, откуда их только забирай. Для чтения оперативной памяти соответственно используем функцию PCF _ read _ ram () после чего данные забираем в переменных PCF _ data _ ram _ N

    Вот список переменных, где и что хранится:

    // время и дата PCF_hour=0; // время, часы (от 0 до 23, защита от переполнения при записи и чтении) PCF_min=0; // время, минуты (от 0 до 59, защита от переполнения при записи и чтении) PCF_sec=0; // время, секунды (только для чтения, при записи сбрасываются в 00) PCF_day=0; // день (от 1 до 31, защита от переполнения при записи и чтении) PCF_weekday=0 // день недели (0-понедельник; 6-воскресенье, защита от переполнения при записи и чтении) PCF_month=0; // месяц (от 1 до 12, защита от переполнения при записи и чтении) PCF_year=0; // год (0-високосный; 1,2,3-невисокосные, защита от переполнения при записи и чтении) // оперативная память PCF_data_ram_1; // Данные (ОЗУ PCF8583), байт 1 PCF_data_ram_2; // Данные (ОЗУ PCF8583), байт 2 PCF_data_ram_3; // Данные (ОЗУ PCF8583), байт 3 PCF_data_ram_4; // Данные (ОЗУ PCF8583), байт 4 PCF_data_ram_5; // Данные (ОЗУ PCF8583), байт 5

    Теперь расскажу про защиту от переполнения. Допустим, мы забыли подключить микросхему. Прочитаем данные с микросхемы, и… прочитается байт 11111111, или число 255. Всё дело в том, что в основе шины I 2 C лежат 2 подтягивающих резистора, вот они то и выдают нам логические «единички» если микросхема не подключена. Для защиты от подобных случаев, в библиотеке PCF8583 я сделал защиту от переполнений, которая следит за тем, чтобы часики не показывали вам 62 часа 81 минуту… Наличие переполнения можно проследить, прочитав переменную «PCF_overflow». Если в ней 0, значит ошибок переполнения не было. Если в ней 1 или более, значит ошибки переполнения имеются. Читать переменную «PCF_overflow» нужно после функции чтения даты и времени PCF _ read _ hh _ mm _ ss ()

    Для наглядности, проект AVR Studio 6 под ATmega32 прилагается. Перекомпилировать можно под любой AVR. В проекте я также подключил дисплей для визуального контроля. При подаче питания, микроконтроллер устанавливает 23 часа 59 минут, 31 декабря, Воскресенье. И через минуту становится 00 часов 00 минут, 1 января, Понедельник.

    Теперь расскажу, почему я говорил про «кривой» календарь этой микросхемы. Всё дело в том, что микросхема не умеет хранить текущий календарный год, а хранит лишь флаг високосного года. Короче говоря:
    0 – високосный год
    1 – не високосный год
    2 – не високосный год
    3 – не високосный год

    И так по циклу 0-1-2-3-0-1-2-3-0…

    В общем чтобы сделать нормальный календарь, нужно реализовывать программный расчёт и сохранение года, например, в ту же оперативную память PCF8583, но это не удобно. А главное, что при обесточенной схеме память, увы, никто не перезапишет...

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

    Список радиоэлементов

    Обозначение Тип Номинал Количество Примечание Магазин Мой блокнот
    МК AVR 8-бит

    ATmega32

    1 В блокнот
    Часы реального времени (RTC)

    PCF8583

    1 В блокнот
    LCD-дисплей WH1602 1

    Why another I2C library?

    The standard I2C library for the Arduino is the Wire library . While this library is sufficient most of the time, there are situations when it cannot be used:

    • the I2C pins A4/A5 (or SDA/SCL) are in use already for other purposes,
    • the code shall run on both an ATmega processor with 16 MHz and an ATtiny processor with 1 MHz ,
    • you are short on memory (flash and RAM).

    Features

    • compatible with all 8-bit AVR MCUs
    • can make use of almost any pin
    • clock stretching (by slaves) supported
    • timeout on clock stretching
    • timeout on ACK polling for busy devices (new!)
    • internal MCU pullup resistors can be used (new!)
    • very lightweight (roughly 250 bytes of flash and 0 byte of RAM, except for call stack)
    • very fast (standard and fast mode on UNO, 33 kHz with 1 MHz CPU clock)
    • Optional Wire library compatible interface
    • no bus arbitration (i.e., only one master allowed on bus)
    • supports only master mode
    • GPL license

    Using the library

    Alternative Interface

    Meanwhile, I have written a wrapper around SoftI2CMaster that emulates the Wire library (master mode only). It is another C++-header file called SoftWire .h which you need to include instead of SoftI2CMaster .h . Directly after this include statement you need to create a Wire instance.

    This interface sacrifices some of the advantages of the original library, in particular its small footprint, but comes handy if you need a replacement of the original Wire library. The following table lists the memory requirements.

    WireSoftI2CMaster SoftWire Flash memory RAM
    1956 252 712 208 0 64