Мы уже узнали довольно много об организации и работе микросхемы памяти M25P40VP, установленной на плате беспроводных контроллеров uniSensors nRF24, но мы не станем останавливаться на достигнутом и продолжим наши исследования. На этом уроке мы разберём работу (чтение и запись) данных типа float. Это очень важный и востребованный тип данных и знание принципов работы с ним очень пригодится вам в создании ваших реальных проектов.
Read & Write float
В среде программирования Ардуино числа типа float занимают 4 байта (32 бита) и могут содержать значения с «плавающей» точкой от -3.4028235E+38 до 3.4028235E+38.
Как вы сами понимаете, работа по сохранению и извлечению из памяти микросхемы M25P40VP чисел с плавающей точкой несколько сложнее и специфичнее, чем работа с целочисленными типами данных, обо всех этих нюансах мы и поговорим в этой статье.
Теперь давайте создадим скетч, который позволит записывать числа с плавающей точкой типа float в память микросхемы M25P40VP и считывать их обратно.
Код скетча M25P40 Read & Write Float:
/* M25P40 Read & Write Float */ #include <SPI.h> #include <SPIFlash.h> #define FLASH_SS 8 #define TEST_ADR 0 #define TEST_VAL 3.1415926535 //#define TEST_VAL 0.0 //#define TEST_VAL -3.1415926535 #define DEPTH 2 //#define DEPTH 7 union { float fl; byte b[4]; } flb; SPIFlash flash(FLASH_SS); void setup() { Serial.begin(115200); Serial.println(F("M25P40 Read & Write Float start...")); Serial.print(F("Init ")); if (flash.initialize()) {Serial.println(F("OK"));} else {Serial.println(F("FAIL"));} erase(); readFloat(TEST_ADR); writeFloat(TEST_ADR, TEST_VAL); readFloat(TEST_ADR); } void checkBusy() { uint32_t stt = millis(); while (flash.busy()) { } uint32_t del = millis() - stt; if (del) { Serial.print(F(" (")); Serial.print(del); Serial.print(F(" ms)")); } Serial.println(); } void erase() { Serial.print(F("Full erase...")); flash.chipErase(); checkBusy(); } void invert() { byte bytes[4]; bytes[0] = flb.b[0]; bytes[1] = flb.b[1]; bytes[2] = flb.b[2]; bytes[3] = flb.b[3]; flb.b[0] = bytes[3]; flb.b[1] = bytes[2]; flb.b[2] = bytes[1]; flb.b[3] = bytes[0]; } void readFloat(uint32_t adr) { flash.readBytes(adr, flb.b, 4); invert(); Serial.print(F("Read #")); Serial.print(adr); Serial.print(F(": ")); Serial.println(flb.fl, DEPTH); Serial.println(); } void writeFloat(uint32_t adr, float fl) { flb.fl = fl; invert(); flash.writeBytes(adr, flb.b, 4); Serial.print(F("Write #")); Serial.print(adr); Serial.print(F(": ")); Serial.println(fl, DEPTH); } void loop() { }
Вначале мы задаём адрес тестовой ячейки памяти M25P40VP, в нашем случае это нулевая ячейка.
#define TEST_ADR 0
Затем мы определяем тестовое число из диапазона, поддерживаемого типом float. Для примера мы возьмём число pi равное 3.1415926535 с точностью 10 знаков после точки. Эта точность взята с нескольким запасом и об этом мы поговорим чуть ниже.
Здесь же мы задаём нулевое значение тестового числа и отрицательное значение — эти данные понадобятся нам в дальнейшем для тестирования корректности алгоритмов записи и считывания информации из M25P40VP. Пока два последних числа оставляем закомментированными.
#define TEST_VAL 3.1415926535 //#define TEST_VAL 0.0 //#define TEST_VAL -3.1415926535
Далее мы определяем также «дефайн» DEPTH — это количество знаков после точки с которым будет работать наш скетч. Пока оставляем значение 2 (знака после точки).
#define DEPTH 2 //#define DEPTH 7
Также мы создаём объединение (union) для совместного хранения в памяти различных типов данных — 4-байтового числа fl типа float и 4-байтового массива b[4]. Это нам понадобится в дальнейшем для записи, чтения и приведения различных типов данных.
union { float fl; byte b[4]; } flb;
Далее следует блок, определяющий функционал нашего скетча. Вначале мы стираем память чипа и читаем содержимое тестовой ячейки.
erase(); readFloat(TEST_ADR);
Затем записываем в неё новое значение типа float и снова читаем и проверяем результат.
erase(); readFloat(TEST_ADR); writeFloat(TEST_ADR, TEST_VAL); readFloat(TEST_ADR);
Чтение производится функцией readFloat(), которой мы передаём адрес тестовой ячейки в качестве параметра.
void readFloat(uint32_t adr) { flash.readBytes(adr, flb.b, 4); invert(); Serial.print(F("Read #")); Serial.print(adr); Serial.print(F(": ")); Serial.println(flb.fl, DEPTH); Serial.println(); }
Здесь мы используем функцию flash.readBytes() чтения массива (из 4-х байт) из памяти M25P40VP по заданному адресу и «массивное» представление объединённого 4-байтового числа flb.b[].
flash.readBytes(adr, flb.b, 4);
Поскольку порядок байтов, выдаваемых функцией flash.readBytes() противоположен порядку байтов в представлении чисел типа float, мы дополнительно инвертируем этот порядок в объединении flb при помощи функции invert().
byte bytes[4]; bytes[0] = flb.b[0]; bytes[1] = flb.b[1]; bytes[2] = flb.b[2]; bytes[3] = flb.b[3]; flb.b[0] = bytes[3]; flb.b[1] = bytes[2]; flb.b[2] = bytes[1]; flb.b[3] = bytes[0];
Далее выводим на печать полученный результат при помощи объединённой версии flb.fl типа float.
Serial.print(F("Read #")); Serial.print(adr); Serial.print(F(": ")); Serial.println(flb.fl, DEPTH); Serial.println();
Запись числа типа float в память микросхемы M25P40VP производится при помощи функции writeFloat(), которой передаются адрес нужной ячейки в памяти микросхемы и само тестовое число.
void writeFloat(uint32_t adr, float fl) { flb.fl = fl; invert(); flash.writeBytes(adr, flb.b, 4); Serial.print(F("Write #")); Serial.print(adr); Serial.print(F(": ")); Serial.println(fl, DEPTH); }
Далее мы присваиваем объединению flb.fl тестовое значение.
flb.fl = fl;
Инвертируем его.
invert();
И записываем в память M25P40VP.
flash.writeBytes(adr, flb.b, 4);
Далее для контроля выводим в Serial записываемое число.
Serial.print(F("Write #")); Serial.print(adr); Serial.print(F(": ")); Serial.println(fl, DEPTH);
Вот результат работы нашего скетча:
Обратите внимание, что чтение числа типа float из тестовой ячейки сразу после стирания памяти приводит к некорректному результату типа nan — это обусловлено тем, что после стирания все ячейки заняты числами 255 (b11111111) и система воспринимает их как некорректные для чисел типа float.
Обратите внимание также, что числа выводятся с точностью 2 знака после точки — это стандартное представление float чисел в среде Ардуино.
Проверка корректности работы с нулевым значением
Теперь проведём дополнительный тест корректности работы нашего алгоритма, для этого закомментируем первую строку блока тестовых значений и закомментируем вторую.
//#define TEST_VAL 3.1415926535 #define TEST_VAL 0.0 //#define TEST_VAL -3.1415926535
Компилируем и загружаем исправленный вариант скетча и убеждаемся в корректности работы нашего алгоритма с нулевыми значениями float чисел.
Проверка корректности работы с отрицательными числами
Комментируем две первые строки блока тестовых значений и оставляем раскомментированной последнюю.
//#define TEST_VAL 3.1415926535 //#define TEST_VAL 0.0 #define TEST_VAL -3.1415926535
После компиляции и загрузки нового варианта скетча убеждаемся в корректности его работы с отрицательными числами типа float.
Количество знаков после точки
Теперь проведём эксперимент с выводом чисел типа float с различной точностью (различным количеством знаков после точки). Для этого в блоке
#define DEPTH 2 //#define DEPTH 7
меняем комментирование на противоположное.
//#define DEPTH 2 #define DEPTH 7
После этого в фрагментах кода
Serial.print(F("Read #")); Serial.print(adr); Serial.print(F(": ")); Serial.println(flb.fl, DEPTH);
и
Serial.print(F("Write #")); Serial.print(adr); Serial.print(F(": ")); Serial.println(fl, DEPTH);
будут выводиться более точные результаты.
Также тут стоит обратить внимание на то, что исходное число с точностью 10 знаков после точки
#define TEST_VAL 3.1415926535
выводится системой Ардуино с округлением и точностью только 7 (6) знаков после точки.
Write #0: 3.1415927 Read #0: 3.1415927
Заключение
На этом мы заканчиваем изучение работы с числами типа float при записи и чтении их из микросхемы M25P40VP, установленной на плате беспроводных контроллеров uniSensors nRF24, и переходим и разбору работы со строковыми типами данных. Ждём вас на следующем уроке!
Ссылки по теме
Обзор контроллера uniSensors nRF24
Программирование uniSensors nRF24
Работа с памятью M25P40. Часть 1. Спецификации и библиотека
Работа с памятью M25P40. Часть 2. Sleep, Wakeup, Erase и Busy
Работа с памятью M25P40. Часть 3. Read и Write Byte и Arrays
Работа с памятью M25P40. Часть 4. Работа с беззнаковыми типами данных
Работа с памятью M25P40. Часть 5. Работа со знаковыми типами данных
Работа с памятью M25P40. Часть 6. Read и Write Float
Работа с памятью M25P40. Часть 7. Read и Write Char array и String
Работа с памятью M25P40. Часть 8. Работа с секторами
Работа с памятью M25P40. Часть 9. Выборочное стирание секторов
Работа с памятью M25P40. Часть 10. Копирование секторов
Работа с памятью M25P40. Часть 11. Восстановление (backup) секторов
Работа с памятью M25P40. Часть 12. Работа с блоками памяти
Работа с памятью M25P40. Часть 13. Пишем библиотеку для M25P40
Работа с памятью M25P40. Часть 14. Пишем библиотеку для M25P40 (2)
Работа с памятью M25P40. Часть 15. Пишем библиотеку для M25P40 (3)
Где купить?
uniSensors nRF24 в магазине «Electromicro»
Техническая поддержка
Мы внимательно относимся к потребностям наших клиентов и осуществляем техническую поддержку всей выпускаемой продукции. Вы можете написать нам письмо с вашим вопросом или позвонить по телефону и специалист нашей компании проконсультирует вас и поможет решить вашу проблему.
- Емейл для вопросов по нашей продукции: electromicro@bk.ru
- Наш телефон: +7 (495) 997-37-74