В предыдущей статье мы рассмотрели реализацию I2C на ядре Cortex-M4. Теперь настало время что-нибудь подключить. Давайте подключим серию микросхем AT24Cxx. Кроме этих микросхем можно подключить кучу разных датчиков. Позже на подходе BMP280, BME280, SGP30 и HDC1080. Других у меня пока нет.
В библиотеку входит всего один конструктор и две функции. Конструктору AT24Cxx(I2C* I2C_Ptr, uint8_t DevAddress, uint8_t AT24C_Type, uint8_t MemAddSize)
передаётся всего четыре параметра:
I2C_Ptr
- указатель на объект I2C.DevAddress
- адрес микросхемы AT24Cxx. По умолчанию он равен 0х50.AT24C_Type
- тип микросхемы. От AT24C02 до AT24C1024.MemAddSize
- сколько бит отводится под адрес внутри микросхемы, 8 или 16.
Четвёртый пункт ввёл для совместимости с другими типами микросхем. Некоторым микросхемам нужно передавать адрес по байтам друг за другом, сначала старший, потом младший. Другим типам то же самое, но протокол передачи чуть-чуть другой. Но проверить это утверждение пока не удалось.
Далее функция Read()
. Она может читать байт, массив, структуру и любой тип переменных. Ну байт и массив понятно. А вот структуру? Я исходил из того, что ставить такую микросхему для записи пары тройки переменных смысла нет. Поэтому функции записи целых, с плавающих запятой и так далее, возможны, и в примере показано как это сделать, но большого смысла в этом нет. Придётся следить за выравниванием данных, что довольно утомительно.
Записать один байт весьма просто - Write(0x0073, 0x11)
. Данная команда запишет число 0х11 в ячейку с адресом 0х0073.
Массив байт - Write(0, BuffFill, 32)
. Команда запишет 32 байта из буфера BuffFill
, начиная с адреса 0х0000.
Но вдруг вам нужно записать кучу переменных, да ещё и разных. Вот здесь лучше всего использовать структуру. До функции main()
можно объявить структуру, если она должна быть глобальной или в самой функции main()
если локальной.
struct sSaveType // Объявляем структуру { uint32_t Data; // Целая 32-х разрядная переменная uint8_t Serial; // Целая 8-ми разрядная переменная float Fl; // Переменная с плавающей запятой }; // Инициализируем I2C I2C myI2C(I2C2, false); // Класс I2C // Инициализируем библиотеку AT24Cxx AT24Cxx EEPROM(&myI2C, 0x50, AT24C64, Size_Add_16bit); // Пользуемся ранее созданным классом I2C
В функции main()
присваиваем структуре имя и пишем её:
struct sSaveType SaveType; // Теперича у неё есть имя // Инициализируем структуру и пишем её с 20-го адреса SaveType.Data = 123456; SaveType.Serial = 0xAA; SaveType.Fl = 1246.345; EEPROM.Write(0x20, &SaveType, sizeof(SaveType)); // Пишем структуру с адреса 0х20 // Ну и читаем её обратно EEPROM.Read(0x20, &SaveType, sizeof(SaveType));
Все функции Read()
просты до безобразия:
// Чтение байта int16_t AT24Cxx::Read(uint16_t MemAddress) { uint8_t Buff[1]; int16_t Temp; Temp = _I2C_Ptr->MasterMemRead(_DevAddress, MemAddress, _MemAddSize, Buff, 1, 15); if(Temp != 0) return Temp; else return Buff[0]; } // Чтение массива int16_t AT24Cxx::Read(uint16_t MemAddress, uint8_t* DataBuff, uint16_t Size) { _MemAddress = MemAddress; _Size = Size; return _I2C_Ptr->MasterMemRead(_DevAddress, _MemAddress, _MemAddSize, DataBuff, _Size, 15); } // Чтение данных неопределённого формата int16_t AT24Cxx::Read(uint16_t MemAddress, const void* DataBuff, uint16_t Size) { return Read(MemAddress, (uint8_t*) DataBuff, Size); }
Вот с записью немного сложнее. Дело в том, что запись производится постранично, и байтов в странице для разных чипов - разное количество. Поэтому в начале инициализации в зависимости от типа микросхемы производится инициализация переменных, в которых хранится эта информация:
switch (AT24C_Type) { case AT24C02: _Pages = 32; _BytePerPages = 8; break; case AT24C04: _Pages = 32; _BytePerPages = 16; break; case AT24C08: _Pages = 64; _BytePerPages = 16; break; }
И так далее для остальных микросхем. Далее в функции записи проверяется, с какого адреса страницы пишутся данные и превышен ли размер страницы, сначала или со случайного места, вычисляется сколько байт мы можем записать. После записи куска задержка на 10 миллисекунд, необходимые микросхеме, чтобы скинуть свой буфер. И если количество данных больше, чем на одну страницу, пишется следующая. И так до тех пор, пока все данные не будут записаны.
int16_t AT24Cxx::Write(uint16_t MemAddress, uint8_t *DataBuff, uint16_t Size) { _MemAddress = MemAddress; _Size = Size; // Проверяем адрес и размер EEPROM, войдёт ли наш блок? if(!_CheckSpace(_MemAddress, _Size)) return 0; // Да ну. Не влезет. uint8_t *page = DataBuff; uint16_t left = _Size; uint16_t toWrite; // Начинаем запись while (left != 0) { // Вычисляем количество байт, записываемых в текущую страницу // Если адрес не выровнен относительно страницы, пишем невыравненный кусок, затем полные страницы и остаток, если будет. if((_MemAddress % _BytePerPages) != 0) { toWrite = (((_MemAddress / _BytePerPages) + 1) * _BytePerPages) - _MemAddress; if(toWrite > _Size) toWrite = _Size; } else { if(left <= _BytePerPages) toWrite = left; else toWrite = _BytePerPages; } // Производим запись одной страницы или её части // Запись сорвалась, возвращаем количество записанных байт. if(_I2C_Ptr->MasterMemWrite(_DevAddress, _MemAddress, _MemAddSize, page, toWrite, 15) != 0) return _Size - left; delay(11); // После записи страницы, задержка не менее 10мс, чтобы буфер сбросился. // Перерасчёт всех счётчиков left -= toWrite; _MemAddress += toWrite; page = DataBuff + toWrite; } return _Size; }
Все функции возвращают целое число. Если оно равно "0" или в случае чтения одного байта - возвращается байт, а при записи массива возвращается количество записанных. Если число отрицательное, значит произошла ошибка.
Пример применения библиотеки в каталоге WorkDevel\Developer\Tests\MK\F407\F407_I2C_AT24xx.
Все материалы на Яндекс Диске и WorkDevel.
Тема на форуме - перейти.
Опробовал эту библиотеку на AT24C16 и нашёл несколько ошибок в библиотеке. Исправил и залил.
Но использовать её для AT24C02 по AT24C16 не советую. Там что то мудрёное с адресацией.
Завтра попробую на AT24C32. Если что не так, напишу.
Разобрался с AT24C16.
При сканировании она даёт 8 адресов. С 0х50 по 0х57. Адресация не 16 битная, а 8ми. Хоть на адрес и требуется 11 бит.
Нехватающие биты добиваются выбором адреса.
Первые 256 байт считываются и пишутся по адресу 0х50, следующие по адресу 0х51 и так далее.
Я посидел, подумал. Разница в цене между AT24C16 и AT24C32 - копейки.
Не буду я под это дело библиотеку куролесить.
И времени нет заниматься такой мелочью.