TerraStat/lib/iarduino_RTC/iarduino_RTC_I2C.h

462 lines
67 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#ifndef iarduino_I2C_h // Разрешаем включить данный код в скетч, только если он не был включён ранее
#define iarduino_I2C_h // Запрещаем повторное включение данного кода в скетч
//
// Определяем тип реализации шины I2C: //
#define iarduino_I2C_SW // Объявляем константу iarduino_I2C_SW - Возможна программная реализация шины I2C.
#if (!defined(pin_SW_SDA) || !defined(pin_SW_SCL)) // Если выводы не определены пользователем, то ...
#undef iarduino_I2C_SW // Отменяем объявление константы iarduino_I2C_SW - Программная реализация шины I2C не возможна (так как выводы не указаны).
#if defined(ESP8266) || defined(ESP32) // Если используются указанные платы, то ...
#include <Wire.h> // Подключаем библиотеку Wire.
#define pin_SW_SDA 255 // № вывода SDA не определён
#define pin_SW_SCL 255 // № вывода SCL не определён
#define iarduino_I2C_TW // Объявляем константу iarduino_I2C_TW - Будет использована аппаратная реализация шины I2C под управлением библиотеки Wire.
#elif defined(TwoWire_h) // Проверяем не подключена ли библиотека Wire.
#define pin_SW_SDA 255 // № вывода SDA не определён
#define pin_SW_SCL 255 // № вывода SCL не определён
#define iarduino_I2C_TW // Объявляем константу iarduino_I2C_TW - Будет использована аппаратная реализация шины I2C под управлением библиотеки Wire.
#elif (defined(SDA) && defined(SCL)) // Определяем выводы для шины I2C
#define pin_SW_SDA SDA // № вывода SDA = SDA
#define pin_SW_SCL SCL // № вывода SCL = SCL
#define iarduino_I2C_HW // Объявляем константу iarduino_I2C_HW - Будет использована аппаратная реализация шины I2C.
#elif (defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)) // Определяем выводы для шины I2C
#define pin_SW_SDA PIN_WIRE_SDA // № вывода SDA = PIN_WIRE_SDA
#define pin_SW_SCL PIN_WIRE_SCL // № вывода SCL = PIN_WIRE_SCL
#define iarduino_I2C_HW // Объявляем константу iarduino_I2C_HW - Будет использована аппаратная реализация шины I2C.
#elif (defined(SDA1) && defined(SCL1)) // Определяем выводы для шины I2C-1
#define pin_SW_SDA SDA1 // № вывода SDA = SDA1
#define pin_SW_SCL SCL1 // № вывода SCL = SCL1
#define iarduino_I2C_HW_1 // Объявляем константу iarduino_I2C_HW_1 - Будет использована аппаратная реализация шины I2C-1.
#else // Если выводы определить не удалось, то ...
#define pin_SW_SDA 255 // № вывода SDA не определён - Аппаратная реализация шины I2C не возможна.
#define pin_SW_SCL 255 // № вывода SCL не определён - Аппаратная реализация шины I2C не возможна.
#endif //
#endif //
//
class iarduino_I2C_BASE{ // Определяем полиморфный класс
public: // Доступные методы и функции:
// ОСНОВНЫЕ ФУНКЦИИ: (поддерживаются библиотекой Wire) //
virtual void begin (uint32_t); // Объявляем функцию указания скорости шины I2C. Аргументы: скорость в кГц.
virtual uint8_t readByte (uint8_t, uint8_t ); // Объявляем функцию чтения байта данных из регистра модуля. Аргументы: адресодуля, адрес_регистра. (адрес регистра указывает модулю, данные какого регистра требуется отправить мастеру)
virtual bool writeByte (uint8_t, uint8_t, uint8_t); // Объявляем функцию записи байта данных в регистр модуля. Аргументы: адресодуля, адрес_регистра, байт_данных. (адрес регистра указывает модулю, в какой регистр требуется сохранить данные)
virtual uint8_t readByte (uint8_t ); // Объявляем функцию чтения байта данных из модуля. Аргументы: адресодуля (функция отличается тем, что она не отправляет модулю адрес регистра)
virtual bool writeByte (uint8_t, uint8_t); // Объявляем функцию записи байта данных в модуль. Аргументы: адресодуля, байт_данных. (функция отличается тем, что она не отправляет модулю адрес регистра)
virtual bool readBytes (uint8_t, uint8_t, uint8_t*, uint8_t); // Объявляем функцию чтения байтов данных из регистров модуля. Аргументы: адресодуля, адрес_первого_регистра, указатель_наассив, количество_байт. (адрес первого регистра указывает модулю, с какого регистра требуется начать передачу данных мастеру)
virtual bool writeBytes (uint8_t, uint8_t, uint8_t*, uint8_t); // Объявляем функцию записи байтов данных в регистры модуля. Аргументы: адресодуля, адрес_первого_регистра, указатель_наассив, количество_байт. (адрес первого регистра указывает модулю, начиная с какого регистра требуется сохранять данные)
virtual bool readBytes (uint8_t, uint8_t*, uint8_t); // Объявляем функцию чтения байтов данных из модуля. Аргументы: адресодуля, указатель_наассив, количество_байт. (функция отличается тем, что она не отправляет модулю адрес первого регистра, а начинает цикл чтения сразу после отправки адреса модуля.)
virtual bool writeBytes (uint8_t, uint8_t*, uint8_t); // Объявляем функцию записи байтов данных в модуль. Аргументы: адресодуля, указатель_наассив, количество_байт. (функция отличается тем, что после отправки адреса модуля она сразу начинает цикл отправки данных, без передачи адреса первого регистра.)
// ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ: (поддерживаются библиотекой Wire) //
virtual uint8_t getType (void); // Объявляем функцию получения типа реализации шины I2C. Аргументы: отсутствуют.
virtual bool checkAddress(uint8_t); // Объявляем функцию поиска модуля на шине I2C. Аргументы: адресодуля.
// ФУНКЦИИ НИЖНЕГО УРОВНЯ: (не поддерживаются библиотекой Wire) //
virtual bool start (void); // Объявляем функцию установки состояния START. Аргументы: отсутствуют.
virtual bool reStart (void); // Объявляем функцию установки состояния RESTART. Аргументы: отсутствуют.
virtual void stop (void); // Объявляем функцию установки состояния STOP. Аргументы: отсутствуют.
virtual bool sendID (uint8_t, bool); // Объявляем функцию передачи адреса модуля. Аргументы: ID-адрес модуля, бит RW (0-запись, 1-чтение).
virtual bool setByte (uint8_t); // Объявляем функцию передачи байта данных. Аргументы: байт для передачи.
virtual uint8_t getByte (bool); // Объявляем функцию получения байта данных. Аргументы: бит подтверждения ACK/NACK
private: //
virtual bool setSCL (bool); // Объявляем функцию установки уровня на линии SCL. Аргументы: логический уровень.
virtual void setSDA (bool); // Объявляем функцию установки уровня на линии SDA. Аргументы: логический уровень.
virtual bool getSDA (void); // Объявляем функцию чтения уровня с линии SDA. Аргументы: отсутствуют.
}; //
//
class iarduino_I2C: public iarduino_I2C_BASE{ // Определяем производный класс
public: // Доступные методы и функции:
//
/** ОСНОВНЫЕ ФУНКЦИИ: **/ //
//
// Функция подготовки шины I2C: // Определяем функцию подготовки шины I2C.
void begin(uint32_t speed){ // Аргумент: скорость шины в кГц.
// _ _ _ _______ _ _ _ //
// SCL: _?_?_/ OUTPUT //
// _ _ _ _ _____ _ _ _ //
// SDA: _?_?_ _/ OUTPUT //
// //
#if defined(iarduino_I2C_TW) //
// Если используется шина I2C под управлением библиотеки Wire: //
Wire.setClock(speed*1000L); // Устанавливаем скорость передачи данных по шине I2C.
Wire.begin(); // Инициируем работу на шине I2C в качестве мастера.
#elif defined(iarduino_I2C_HW) //
// Если используется аппаратная шина I2C: //
pinMode(pin_SDA, INPUT ); digitalWrite(pin_SDA, HIGH); // Определяем вывод pin_SDA как вход и подтягиваем его к Vcc.
pinMode(pin_SCL, INPUT ); digitalWrite(pin_SCL, HIGH); // Определяем вывод pin_SCL как вход и подтягиваем его к Vcc.
TWBR=((F_CPU/(speed*1000L))-16)/2; // Определяем значение регистра скорости связи TWBR: speed = F_CPU / (16 + 2 * TWBR * 4^TWPS). Значит TWBR = (F_CPU/speed - 16) / (2 * 4^TWPS), но так как биты предделителя TWPS мы далее сбросим в 0, то формула упростится до: TWBR = (F_CPU/speed - 16) / 2. Так как speed указана в кГц, а F_CPU в Гц, то умножаем speed на 1000.
if(TWBR<10){TWBR=10;} // Если значение регистра скорости TWBR стало меньше 10 (параметр speed > 400 кГ) то оставляем значение этого регистра равным 10 иначе шина будет работать нестабильно.
TWSR&=(~(_BV(TWPS1)|_BV(TWPS0))); // Определяем значение регистра статуса TWSR: оставляем все его биты не тронутыми, кроме двух битов предделитея TWPS сбрасывая их в 0.
#elif defined(iarduino_I2C_SW) //
// Если используется программная шина I2C: //
port_SDA = digitalPinToPort (pin_SDA); // Определяем номер порта для вывода pin_SDA.
port_SCL = digitalPinToPort (pin_SCL); // Определяем номер порта для вывода pin_SCL.
mask_SDA = digitalPinToBitMask (pin_SDA); // Определяем маску для вывода pin_SDA. в переменной mask_SDA будет установлен только тот бит который соответствует позиции бита управления выводом pin_SDA.
mask_SCL = digitalPinToBitMask (pin_SCL); // Определяем маску для вывода pin_SCL. в переменной mask_SCL будет установлен только тот бит который соответствует позиции бита управления выводом pin_SCL.
mod_SDA = portModeRegister (port_SDA); // Определяем указатель на адрес регистра конфигурации направления работы выводов порта port_SDA.
mod_SCL = portModeRegister (port_SCL); // Определяем указатель на адрес регистра конфигурации направления работы выводов порта port_SCL.
inp_SDA = portInputRegister (port_SDA); // Определяем указатель на адрес регистра входных значений для управления портом port_SDA.
inp_SCL = portInputRegister (port_SCL); // Определяем указатель на адрес регистра входных значений для управления портом port_SCL.
out_SDA = portOutputRegister (port_SDA); // Определяем указатель на адрес регистра выходных значений для управления портом port_SDA.
out_SCL = portOutputRegister (port_SCL); // Определяем указатель на адрес регистра выходных значений для управления портом port_SCL.
setSCL(1); // Переводим вывод SCL в режим входа с подтяжкой к Vcc
setSDA(1); // Переводим вывод SDA в режим входа с подтяжкой к Vcc
#endif //
} //
//
// Функция чтения одного байта из регистра модуля: //
uint8_t readByte(uint8_t adr, uint8_t reg){ // Определяем функцию чтения одного байта данных из регистра модуля (аргументы: адресодуля, адрес_регистра)
uint8_t i=0; readBytes(adr, reg, &i, 1); return i; // Объявляем переменную i и указываем её ссылку для получения 1 байта из регистра reg модуля с адресом adr
} //
//
// Функция чтения одного байта из модуля: //
uint8_t readByte(uint8_t adr){ // Определяем функцию чтения одного байта данных из регистра модуля (аргументы: адресодуля)
uint8_t i=0; readBytes(adr, &i, 1); return i; // Объявляем переменную i и указываем её ссылку для получения 1 байта из модуля с адресом adr
} //
//
// Функция записи одного байта в регистр модуля: //
bool writeByte(uint8_t adr, uint8_t reg, uint8_t data){ // Определяем функцию записи одного байта данных в регистр модуля (аргументы: адресодуля, адрес_регистра, байт_данных)
return writeBytes(adr, reg, &data, 1); // Запысываем 1 байт по ссылке на переменную data в регистр reg модуля с адресом adr
} //
//
// Функция записи одного байта в модуль: //
bool writeByte(uint8_t adr, uint8_t data){ // Определяем функцию записи одного байта данных в регистр модуля (аргументы: адресодуля, байт_данных)
return writeBytes(adr, &data, 1); // Запысываем 1 байт по ссылке на переменную data в модуль с адресом adr
} //
//
// Функция пакетного чтения нескольких байт данных из регистров модуля: //
bool readBytes(uint8_t adr, uint8_t reg, uint8_t *data, uint8_t sum){ // Определяем функцию пакетного чтения нескольких байт данных из регистров модуля (аргументы: адресодуля, адрес_первого_регистра, указатель_наассив, количество_байт)
#if defined(iarduino_I2C_TW) //
// Если используется шина I2C под управлением библиотеки Wire: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это ответ по умолчанию.
Wire.beginTransmission(adr); // Инициируем передачу данных по шине I2C к устройству с адресом adr и битом RW=0 => запись. При этом сама передача не начнётся.
Wire.write(reg); // Определяем значение первого байта (reg - адреса регистра) который будет отправлен после байта адреса. Функция write() поместит указанный байт в буфер для передачи.
i=Wire.endTransmission(false); if(i){return 0;} // Выполняем инициированную ранее передачу данных (без установки состояния STOP). Функция endTransmission() возвращает: 0-передача успешна / 1 - переполнен буфер для передачи / 2 - получен NACK при передаче адреса / 3 - получен NACK при передаче данных / 4 - другая ошибка.
if(!Wire.requestFrom( adr, sum )) {return i;} // Читаем (запрашиваем) sum байт данных от устройства с адресом adr и битом RW=1 => чтение. Функция requestFrom() возвращает количество реально принятых байтов. Так как предыдущая функция не установила состояние STOP, то состояние START данной функции будет расценено как RESTART.
while(Wire.available() && i<sum){data[i]=Wire.read(); i++;} // Читаем sum принятых байт из буфера для полученных данных в массив по указателю data.
while(Wire.available()){Wire.read();}return i==sum; // Если в буфере для полученных данных есть еще принятые байты, то чистим буфер. Возвращаем true если удалось прочитать sum байт.
#else //
// Если шина управляется функциями нижнего уровня данного класса: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи.
if ( start () ) { i=1; // Если на шине I2C установилось состояние START, то ...
if ( sendID (adr,0) ) { i=2; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=0 (запись), то ...
if ( setByte (reg) ) { i=3; // Если модуль ответил ACK на получение адреса регистра i, то ...
if ( reStart () ) { i=4; // Если на шине I2C установилось состояние RESTART, то ...
if ( sendID (adr,1) ) { i=5; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=1 (чтение), то ...
while(sum>0){ *data=getByte(sum>1); // Получаем по одному байту из очередного регистра за каждый проход цикла while. Прочитанный байт записываются по указателю data. Аргумент функции getByte имеет значение true на всех проходах цикла кроме последнего
data++; sum--; // Увеличиваем адрес указателя data, и уменьшаем сумму прочитанных байт sum.
#if defined(iarduino_I2C_HW) // Проверить корректность чтения каждого байта можно только на аппаратном уровне
if (sum) { if(TWSR&0xF8!=0x50) { i=0;}} // Если после чтения очередного байта пакета значение регистра состояния шины I2C Arduino TWSR с маской 0xF8 не равно 0x50 значит произошла ошибка при чтении
else { if(TWSR&0xF8!=0x58) { i=0;}} // Если после чтения последного байта пакета значение регистра состояния шины I2C Arduino TWSR с маской 0xF8 не равно 0x58 значит произошла ошибка при чтении
#endif //
}}}}}} stop (); return i==5; // Отправляем команду STOP и возвращаем результат успешности чтения
#endif //
} //
//
// Функция пакетного чтения нескольких байт данных из модуля: //
bool readBytes(uint8_t adr, uint8_t *data, uint8_t sum){ // Определяем функцию пакетного чтения нескольких байт данных из модуля (аргументы: адресодуля, указатель_наассив, количество_байт)
#if defined(iarduino_I2C_TW) //
// Если используется шина I2C под управлением библиотеки Wire: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это ответ по умолчанию.
if(!Wire.requestFrom( adr, sum )) {return i;} // Читаем (запрашиваем) sum байт данных от устройства с адресом adr и битом RW=1 => чтение. Функция requestFrom() возвращает количество реально принятых байтов.
while(Wire.available() && i<sum){data[i]=Wire.read(); i++;} // Читаем sum принятых байт из буфера для полученных данных в массив по указателю data.
while(Wire.available()){Wire.read();}return i==sum; // Если в буфере для полученных данных есть еще принятые байты, то чистим буфер. Возвращаем true если удалось прочитать sum байт.
#else //
// Если шина управляется функциями нижнего уровня данного класса: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи.
if ( start () ) { i=1; // Если на шине I2C установилось состояние START, то ...
if ( sendID (adr,1) ) { i=2; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=1 (чтение), то ...
while(sum>0){ *data=getByte(sum>1); // Получаем по одному байту из очередного регистра за каждый проход цикла while. Прочитанный байт записываются по указателю data. Аргумент функции getByte имеет значение true на всех проходах цикла кроме последнего
data++; sum--; // Увеличиваем адрес указателя data, и уменьшаем сумму прочитанных байт sum.
#if defined(iarduino_I2C_HW) // Проверить корректность чтения каждого байта можно только на аппаратном уровне
if (sum) { if(TWSR&0xF8!=0x50) { i=0;}} // Если после чтения очередного байта пакета значение регистра состояния шины I2C Arduino TWSR с маской 0xF8 не равно 0x50 значит произошла ошибка при чтении
else { if(TWSR&0xF8!=0x58) { i=0;}} // Если после чтения последного байта пакета значение регистра состояния шины I2C Arduino TWSR с маской 0xF8 не равно 0x58 значит произошла ошибка при чтении
#endif //
}}} stop (); return i==2; // Отправляем команду STOP и возвращаем результат успешности чтения
#endif //
} //
//
// Функция пакетной записи нескольких байт данных в регистр модуля: //
bool writeBytes(uint8_t adr, uint8_t reg, uint8_t *data, uint8_t sum){ // Определяем функцию пакетной записи нескольких байт данных в регистры модуля (аргументы: адресодуля, адрес_первого_регистра, указатель_наассив, количество_байт)
#if defined(iarduino_I2C_TW) //
// Если используется шина I2C под управлением библиотеки Wire: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи
Wire.beginTransmission(adr); // Инициируем передачу данных по шине I2C к устройству с адресом adr и битом RW=0 => запись. При этом сама передача не начнётся.
Wire.write(reg); // Определяем значение первого байта (reg - адреса регистра) который будет отправлен после байта адреса. Функция write() поместит указанный байт в буфер для передачи.
Wire.write(data, sum); // Определяем значения следующих байт (data - массив данных) который будет отправлен после байта регистра. Функция write() поместит sum элементов массива data в буфер для передачи.
i = Wire.endTransmission(); return i==0; // Выполняем инициированную ранее передачу данных. Функция endTransmission() возвращает: 0-передача успешна / 1 - переполнен буфер для передачи / 2 - получен NACK при передаче адреса / 3 - получен NACK при передаче данных / 4 - другая ошибка. В качестве параметра функция endTransmission() может принимать флаг установки стсояния STOP - по умолчанию true.
#else //
// Если шина управляется функциями нижнего уровня данного класса: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи
if ( start () ) { i=1; // Если на шине I2C установилось состояние START, то ...
if ( sendID (adr,0) ) { i=2; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=0 (запись), то ...
if ( setByte (reg) ) { i=3; // Если модуль ответил ACK на получение адреса регистра reg, то ...
while(sum>0){if(!setByte(*data )) { i=0;} // Передаём один байт из массива по указателю data в очередной регистр за каждый проход цикла while. Увеличиваем адрес указателя data, и уменьшаем сумму переданных байт sum.
data++; sum--; // Увеличиваем адрес указателя data, и уменьшаем сумму переданных байт sum.
}}}} stop (); return i==3; // Отправляем команду STOP и возвращаем результат записи.
#endif //
} //
//
// Функция пакетной записи нескольких байт данных в модуль: //
bool writeBytes(uint8_t adr, uint8_t *data, uint8_t sum){ // Определяем функцию пакетной записи нескольких байт данных в модуль (аргументы: адресодуля, указатель_наассив, количество_байт)
#if defined(iarduino_I2C_TW) //
// Если используется шина I2C под управлением библиотеки Wire: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи
Wire.beginTransmission(adr); // Инициируем передачу данных по шине I2C к устройству с адресом adr и битом RW=0 => запись. При этом сама передача не начнётся.
Wire.write(data, sum); // Указываем массив данных который будет отправлен после байта адреса. Функция write() поместит sum элементов массива data в буфер для передачи.
i = Wire.endTransmission(); return i==0; // Выполняем инициированную ранее передачу данных. Функция endTransmission() возвращает: 0-передача успешна / 1 - переполнен буфер для передачи / 2 - получен NACK при передаче адреса / 3 - получен NACK при передаче данных / 4 - другая ошибка. В качестве параметра функция endTransmission() может принимать флаг установки стсояния STOP - по умолчанию true.
#else //
// Если шина управляется функциями нижнего уровня данного класса: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи
if ( start () ) { i=1; // Если на шине I2C установилось состояние START, то ...
if ( sendID (adr,0) ) { i=2; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=0 (запись), то ...
while(sum>0){if(!setByte(*data )) { i=0;} // Передаём один байт из массива по указателю data в очередной регистр за каждый проход цикла while. Увеличиваем адрес указателя data, и уменьшаем сумму переданных байт sum.
data++; sum--; // Увеличиваем адрес указателя data, и уменьшаем сумму переданных байт sum.
}}} stop (); return i==2; // Отправляем команду STOP и возвращаем результат записи.
#endif //
} //
//
/** ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ: **/ //
//
// Функция получения типа реализации шины I2C: // Определяем функцию получения типа шины Ш2С.
uint8_t getType(void){ // Аргументы: отсутсвуют.
#if defined(iarduino_I2C_TW) //
return 4; // Используется аппаратная реализация шины I2C под управлением библиотеки Wire.
#elif defined(iarduino_I2C_HW) //
return 3; // Используется аппаратная реализация шины I2C.
#elif defined(iarduino_I2C_HW_1) //
return 2; // Используется аппаратная реализация шины I2C-1.
#elif defined(iarduino_I2C_SW) //
return 1; // Используется программная реализация шины I2C.
#else //
return 0; // Тип реализации шины I2C не определён.
#endif //
} //
//
// Функция проверки наличия ведомого по его адресу: //
bool checkAddress(uint8_t adr){ // Определяем функцию записи одного байта данных в регистр модуля (аргументы: адрес_регистра, байт_данных)
#if defined(iarduino_I2C_TW) //
// Если используется шина I2C под управлением библиотеки Wire: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат проверки.
Wire.beginTransmission(adr); // Инициируем передачу данных по шине I2C к устройству с адресом adr и битом RW=0 => запись. При этом сама передача не начнётся.
i=Wire.endTransmission(); return i==0; // Выполняем инициированную ранее передачу но без данных (отправится только START, байт адреса, STOP). Функция endTransmission() возвращает: 0-передача успешна / 1 - переполнен буфер для передачи / 2 - получен NACK при передаче адреса / 3 - получен NACK при передаче данных / 4 - другая ошибка. В качестве параметра функция endTransmission() может принимать флаг установки стсояния STOP - по умолчанию true.
#else //
// Если шина управляется функциями нижнего уровня данного класса: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи.
if ( start () ) { i=1; // Если на шине I2C установилось состояние START, то ...
if ( sendID (adr,0) ) { i=2; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=0 (запись), то ...
}} stop (); return i==2; // Отправляем команду STOP и возвращаем результат записи.
#endif //
} //
//
/** ФУНКЦИИ НИЖНЕГО УРОВНЯ: **/ //
//
// Функция нижнего уровня - установка состояния START: // Определяем функцию установки состояния START.
bool start(void){ // Аргументы: отсутствуют.
// _ _ _ _____:___ _ _ _ //
// SCL: : \___ _ _ _ _ _/ //
// _ _ _ _____: _ _ _ _ _ _ // Спад логического уровня на линии SDA с «1» в «0», при наличии уровня логической «1» на линии SCL
// SDA: :\______ _ _/_ _ _ _ _ _ //
// //
#if defined(iarduino_I2C_HW) //
// Если используется аппаратная шина I2C: //
uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания установки состояния start.
TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTA); // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания), TWEN (бит разрешения работы шины), TWSTA (бит условия START).
while((!(TWCR & _BV(TWINT))) && i){i--;} // Ждём сброса флага прерывания TWINT в регистре управления TWCR, но не дольше чем i циклов.
if((TWSR & 0xF8)==0x08){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x08, значит состояние START установилось на шине I2C.
return false; // Если предыдущая строка не вернула true, значит состояние START не установилось на шине I2C, возвращаем false.
#elif defined(iarduino_I2C_SW) //
// Если используется программная шина I2C: //
bool i= setSCL(1); // Устанавливаем «1» на линии SCL. Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
setSDA(0); // Устанавливаем «0» на линии SDA
setSCL(0); // Устанавливаем «0» на линии SCL
return i; // Если счетчик i не дошел до 0 при ожидании «1» на линии SCL, то вернётся true.
#else //
// Если тип шины I2C не поддерживается или не определён: //
return false; // Возвращаем false.
#endif //
} //
//
// Функция нижнего уровня - установка состояния RESTART: // Определяем функцию установки состояния RESTART.
bool reStart(void){ // Аргументы: отсутствуют.
// _ _ _ ___:___ _ _ _ //
// SCL: \_ _ _ _ / : \___ _ _ _ _ _/ //
// _ _ _ _ _ ________: _ _ _ _ _ _ // Спад логического уровня на линии SDA с «1» в «0», при наличии уровня логической «1» на линии SCL
// SDA: _ _ _ _ _/ :\______ _ _/_ _ _ _ _ _ // Сигнал рестрат аналогичен сигналу старт и отличается лишь тем, что ему не предшествует сигнал STOP
// //
#if defined(iarduino_I2C_HW) //
// Если используется аппаратная шина I2C: //
uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания установки состояния RESTART.
TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTA); // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания), TWEN (бит разрешения работы шины), TWSTA (бит условия START).
while((!(TWCR & _BV(TWINT))) && i){i--;} // Ждём сброса флага прерывания TWINT в регистре управления TWCR, но не дольше чем i циклов.
if((TWSR & 0xF8)==0x10){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x10, значит состояние RESTART установилось на шине I2C.
return false; // Если предыдущая строка не вернула true, значит состояние RESTART не установилось на шине I2C, возвращаем false.
#elif defined(iarduino_I2C_SW) //
// Если используется программная шина I2C: //
return start(); // Программная реализация состояния RESTART идентична программной реализации состояния START.
#else //
// Если тип шины I2C не поддерживается или не определён: //
return false; // Возвращаем false.
#endif //
} //
//
// Функция нижнего уровня - установка состояния STOP: // Определяем функцию установки состояния STOP.
void stop(void){ // Аргументы: отсутствуют.
// _ _ _ ___:____ _ _ _ //
// SCL: \_ _ _ _/ : //
// _ _ _ _ _ :____ _ _ _ // Подъём логического уровня на линии SDA с «0» в «1», при наличии уровня логической «1» на линии SCL
// SDA: _ _ _ _ _\______/: //
// //
#if defined(iarduino_I2C_HW) //
// Если используется аппаратная шина I2C: //
uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания установки состояния STOP
TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTO); // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания), TWEN (бит разрешения работы шины), TWSTO (бит условия STOP).
while((!(TWCR & _BV(TWSTO))) && i){i--;} // Ждём сброса бита условия стоп TWSTO в регистре управления TWCR, но не дольше чем i циклов
// if((TWSR & 0xF8)==0xA0){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0xA0, значит состояние STOP установилось на шине I2C.
delayMicroseconds(20); // Данная функция не возвращает результат, но в любом случае делаем задержку меджу завершением текущего пакета и возможным началом следующего
#elif defined(iarduino_I2C_SW) //
// Если используется программная шина I2C: //
setSDA(0); // Устанавливаем «0» на линии SDA
setSCL(1); // Устанавливаем «1» на линии SCL. Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
setSDA(1); // Устанавливаем «1» на линии SDA
#endif //
} //
//
// Функция нижнего уровня - передача первого байта АДРЕС+RW: // Определяем функцию передачи первого байта после cигнала START. Это байт адреса модуля с битом RW
bool sendID(uint8_t adr, bool rw){ // Аргументы: ID-адрес модуля, бит RW (0-запись, 1-чтение)
// 1 2 3 4 5 6 7 8 9 //
// SCL: _ _ _ _____/\_/\_/\_/\_/\_/\_/\_/\__/\_____ _ _ _ //
// ________________________ // Передача 7-битного адреса и 1 бита RW. На 9 тактирубщем импульсе модуль отвечает ACK («0» - «я сдесь»), или NACK («1» - «нет»)
// SDA: _ _ _ ___/________ADDRESS_______RW>----____ _ _ _ //
// вход //
#if defined(iarduino_I2C_HW) //
// Если используется аппаратная шина I2C: //
uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания получения подтверждения от ведомого ввиде бита ACK.
TWDR = (adr<<1)+rw; // Определяем значение регистра данных TWDR: записываем в него адрес adr сдвигая его на 1 бит влево, при этом младший бит (освободившийся) займёт бит RW (0-запись / 1-чтение)
TWCR = _BV(TWINT) | _BV(TWEN); // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания) и TWEN (бит разрешения работы шины).
while((!(TWCR & _BV(TWINT))) && i){i--;} // Ждём сброса флага прерывания TWINT в регистре управления TWCR, но не дольше чем i циклов
if((TWSR & 0xF8)==0x40 && rw){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x40, значит ведомый опознал свой адрес с битом RW=1 и отправил бит подтверждения ACK (в противном случае значение регистра TWSR будет равно 0x48).
if((TWSR & 0xF8)==0x18 && !rw){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x18, значит ведомый опознал свой адрес с битом RW=0 и отправил бит подтверждения ACK (в противном случае значение регистра TWSR будет равно 0x20).
return false; // Если предыдущая строка не вернула true, значит на шине нет ведомых у сказанным адресом, возвращаем false.
#elif defined(iarduino_I2C_SW) //
// Если используется программная шина I2C: //
bool i=true; // Определяем флаг возвращаемый данной функцией.
uint8_t j=7; // Определяем счётчик количества переданных бит адреса.
while(j){ j--; // Передаём 7 бит адреса adr в цикле
setSDA(adr & bit(j)); // Устанавливаем очередной бит адреса на линии SDA
if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса)
} setSDA(rw); // Устанавливаем бит RW на линии SDA
if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса)
setSDA(1); // Переводим вывод SDA в режим входа с подтяжкой к Vcc
if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
if( getSDA( ) ) {i=false;} // Если на линии SDA установлена «1» значит ведомый ответил NACK
setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса)
return i; // Если линия SCL была занята или модуль ответил NACK, то вернётся false.
#else //
// Если тип шины I2C не поддерживается или не определён: //
return false; // Возвращаем false
#endif //
} //
//
// Функция нижнего уровня - передача одного байта данных: // Определяем функцию передачи одного байта (в любом месте между байтом адреса с битом RW=0 и сигналом STOP).
bool setByte(uint8_t data){ // Аргумент: байт для передачи.
// 1 2 3 4 5 6 7 8 9 //
// SCL: _ _ _ _____/\_/\_/\_/\_/\_/\_/\_/\__/\_____ _ _ _ //
// ________________________ // Передача 1 байта, каждый бит читается модулем по фронту тактирубщих импульсов. На 9 тактирубщем импульсе модуль отвечает ACK («0» - «принял»), или NACK («1» - «нет»)
// SDA: _ _ _ ___/__________DATA__________>----____ _ _ _ //
// вход //
#if defined(iarduino_I2C_HW) //
// Если используется аппаратная шина I2C: //
uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания получения подтверждения от ведомого ввиде бита ACK.
TWDR = data; // Определяем значение регистра данных TWDR: записываем в него байт для передачи по шине I2C.
TWCR = _BV(TWINT) | _BV(TWEN); // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания) и TWEN (бит разрешения работы шины).
while((!(TWCR & _BV(TWINT))) && i){i--;} // Ждём сброса флага прерывания TWINT в регистре управления TWCR, но не дольше чем i циклов
if((TWSR & 0xF8)==0x28){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x28, значит ведомый отправил бит подтверждения приёма данных ACK (в противном случае значение регистра TWSR будет равно 0x30).
return false; // Если предыдущая строка не вернула true, значит ведомый не принял отправленный ему байт, возвращаем false.
#elif defined(iarduino_I2C_SW) //
// Если используется программная шина I2C: //
bool i=true; // Определяем флаг возвращаемый данной функцией.
uint8_t j=8; // Определяем счётчик количества переданных байт.
while(j){ j--; // Передаём 8 бит байта data в цикле
setSDA(data & bit(j)); // Устанавливаем очередной бит байта на линии SDA
if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса)
} setSDA(1); // Переводим вывод SDA в режим входа с подтяжкой к Vcc
if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
if( getSDA( ) ) {i=false;} // Если на линии SDA установлена «1» значит ведомый ответил NACK
setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса)
return i; // Если счетчик i не дошел до 0 и не был сброшен в 0 при получении NACK, то вернётся true.
#else //
// Если тип шины I2C не поддерживается или не определён: //
return false; // Возвращаем false
#endif //
} //
//
// Функция нижнего уровня - получение одного байта данных: // Определяем функцию получения одного байта (в любом месте между байтом адреса с битом RW=1 и сигналом STOP).
uint8_t getByte(bool ack){ // Аргумент: бит подтверждения ACK/NACK
// 1 2 3 4 5 6 7 8 9 //
// SCL: _ _ _ _____/\_/\_/\_/\_/\_/\_/\_/\__/\_____ _ _ _ //
// __ // Получение 1 байта, каждый бит читается мастером по фронту тактирубщих импульсов. На 9 тактирубщем импульсе мастер отвечает ACK («0» - «давай еще»), или NACK («1» - «хватит»)
// SDA: _ _ _ ___/----------DATA-----------<__\____ _ _ _ //
// вход выход //
#if defined(iarduino_I2C_HW) //
// Если используется аппаратная шина I2C: //
uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания.
TWCR = _BV(TWINT) | _BV(TWEN) | ack<<TWEA; // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания) и TWEN (бит разрешения работы шины), а бит TWEA (бит разрешения подтверждения) устанавливаем только если мы собираемся отправить ведомому бит подтверждения получения данных ACK.
while((!(TWCR & _BV(TWINT))) && i){i--;} // Ждём сброса флага прерывания TWINT в регистре управления TWCR, но не дольше чем i циклов
if((TWSR & 0xF8)==0x50 && ack){return TWDR;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x50, значит данные приняты и сохранены в регистре TWDR, а ведомому отправлен бит ACK.
if((TWSR & 0xF8)==0x58 && !ack){return TWDR;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x58, значит данные приняты и сохранены в регистре TWDR, а ведомому отправлен бит NACK.
return 0; // Если предыдущая строка не вернула принятый байт из регистра данных TWDR, значит мы не приняли байт, возвращаем 0.
#elif defined(iarduino_I2C_SW) //
// Если используется программная шина I2C: //
bool i=true; // Определяем флаг возвращаемый данной функцией.
uint8_t j=8; // Определяем счётчик количества полученных битов байта.
uint8_t k=0; // Объявляем переменную для хранения прочитанного байта данных.
setSDA(1); // Переводим вывод SDA в режим входа с подтяжкой к Vcc
while(j){ j--; // Получаем 8 бит байта в цикле
if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
if( getSDA( ) ) { k |= (1<<j); } // Заполняем биты байта k в соотсетствии с уровнем на линии SDA
setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса)
} setSDA(!ack); // Устанавливаем бит «ACK/NACK» на линии SDA (ACK-прижимаем, NACK-отпускаем)
if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса)
return i?k:0; // Если во время ожидания поднятия логического уровня на линии SCL, cчётчик i не дошел до 0, то функция вернёт байт из переменной k.
#else //
// Если тип шины I2C не поддерживается или не определён: //
return 0; // Возвращаем false
#endif //
} //
//
private: //
uint8_t pin_SDA = pin_SW_SDA; // Определяем вывод pin_SDA.
uint8_t pin_SCL = pin_SW_SCL; // Определяем вывод pin_SCL.
uint8_t port_SDA; // Объявляем переменную для хранения номера порта вывода pin_SDA.
uint8_t port_SCL; // Объявляем переменную для хранения номера порта вывода pin_SCL.
uint8_t mask_SDA; // Объявляем переменную для хранения позиции бита управления выводом pin_SDA.
uint8_t mask_SCL; // Объявляем переменную для хранения позиции бита управления выводом pin_SCL.
volatile uint8_t *mod_SDA; // Объявляем указатель на регистр направления работы выводов порта port_SDA.
volatile uint8_t *mod_SCL; // Объявляем указатель на регистр направления работы выводов порта port_SCL.
volatile uint8_t *inp_SDA; // Объявляем указатель на регистр входных значений порта port_SDA.
volatile uint8_t *inp_SCL; // Объявляем указатель на регистр входных значений порта port_SCL.
volatile uint8_t *out_SDA; // Объявляем указатель на регистр выходных значений порта port_SDA.
volatile uint8_t *out_SCL; // Объявляем указатель на регистр выходных значений порта port_SCL.
//
// функция установки уровня на линии SCL: // Определяем функцию установки уровня на линии SCL.
bool setSCL(bool f){ // Аргумент: логический уровень.
uint16_t i=60000L; // Определяем счётчик ожидания освобождения линии SCL.
if(!f) {*mod_SCL |= mask_SCL; *out_SCL &= ~mask_SCL;} // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса) pinMode(pin_SCL, OUTPUT); digitalWrite(pin_SCL, LOW );
else {*mod_SCL &= ~mask_SCL; *out_SCL |= mask_SCL; // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса) pinMode(pin_SCL, INPUT ); digitalWrite(pin_SCL, HIGH);
while((*inp_SCL & mask_SCL)==0 && i){i--;} } // Ждём поднятия логического уровня на линии SCL while(digitalRead(pin_SCL)==0 && i){ цикл выполняется пока на линии 0 или пока i не сброситя d 0}
return i; //
} //
//
// Функция установки уровня на линии SDA: // Определяем функцию установки уровня на линии SDA.
void setSDA(bool f){ // Аргумент: логический уровень.
if(!f) {*mod_SDA |= mask_SDA; *out_SDA &= ~mask_SDA;} // Устанавливаем «0» на линии SDA (если бит RW=«0») pinMode(pin_SDA, OUTPUT); digitalWrite(pin_SDA, LOW );
else {*mod_SDA &= ~mask_SDA; *out_SDA |= mask_SDA;} // Устанавливаем «1» на линии SDA (если бит RW=«1») pinMode(pin_SDA, INPUT ); digitalWrite(pin_SDA, HIGH );
} //
//
// Функция чтения уровня с линии SDA: // Определяем функцию чтения уровня с линии SDA. Перед чтением необходимо вызвать функцию setSDA(1) которая переведёт вывод SDA в режим входа
bool getSDA(void){ return (*inp_SDA & mask_SDA); } // Аргументы: отсутсвуют. digitalRead(pin_SDA);
}; //
//
#endif //