462 lines
67 KiB
C++
462 lines
67 KiB
C++
#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 //
|