Пример использования ESP PSRAM64(H) с Ардуино
- Войдите на сайт для отправки комментариев
Написал небольшую недобиблиотеку для своих нужд. Если кому интересно, смотрите. Вроде, работает, но это самый первый черновик, так что ляпы и косяки более, чем вероятны. Но по мере их выявления, я буду обновлять репозиторий.
Там в папке Docs есть описание всех возможностей.
Если не использовать PinOps (а это можно - см. документацию), то, по идее, должна работать с любыми ардуинами, т.к. используется только штатная библиотека SPI и "никаких фокусов". PinOps же имеет ограничения (например, с Мегой не работает).
Примеры использования (всё это и еще несколько есть на гитхабе).
1. Самый простой пример. Тупо пишем по 6 байтов в случайный адрес, а потом читаем оттуда:
#include <Printing.h> #define USE_PINOPS true #include <ESP_PSRAM64.h> ESP_PSRAM64 psram; void setup(void) { Serial.begin(115200); Serial.println("Простой тест - пишем по 6 байтов в случайный адрес и читаем оттуда"); delayMicroseconds(150); randomSeed(analogRead(0)); psram.reset(); } void loop(void) { // Буффер со случайным содержимым byte buffer[6]; constexpr TSize size = sizeof(buffer); for (TSize i=0; i < size; i++) buffer[i] = random(0, 255); // Случайный адрес const TAddress address = random(0, psram.totalBytes - size); Serial << "Адрес: " << address << "\r\n"; Serial << " Пишем: "; psram.write(address, buffer, size); for (TSize i=0; i < size; i++) Serial << (unsigned) buffer[i] << ' '; Serial.println(); Serial << "Читаем: "; psram.read(address, buffer, size); for (TSize i=0; i < size; i++) Serial << (unsigned) buffer[i] << ' '; Serial.println(); delay(2000); }
Результат работы:
Простой тест - пишем по 6 байтов в случайный адрес и читаем оттуда Адрес: 5642841 Пишем: 74 237 72 4 188 26 Читаем: 74 237 72 4 188 26 Адрес: 1152200 Пишем: 19 154 183 213 153 118 Читаем: 19 154 183 213 153 118 Адрес: 5862110 Пишем: 87 41 173 152 173 140 Читаем: 87 41 173 152 173 140 Адрес: 1044770 Пишем: 245 235 208 102 38 59 Читаем: 245 235 208 102 38 59 Адрес: 7078519 Пишем: 249 80 244 252 87 31 Читаем: 249 80 244 252 87 31 ........ и т.д.
2. Запись и чтение сложных типов данных, включая массивы и структуры
#include <Printing.h> #define USE_PINOPS true #include <ESP_PSRAM64.h> class CAnekdot : public Printable { char m_origin[16]; char m_body[100]; int m_id; public: CAnekdot(void) {} CAnekdot(const int id, const char * origin, const char * body) { strcpy(m_origin, origin); strcpy(m_body, body); m_id = id; } size_t printTo(Print& p) const { return p.print(m_id) + p.print(", (") + p.print(m_origin) + p.println(')') + p.println(m_body); } }; ESP_PSRAM64 psram; void setup(void) { Serial.begin(115200); Serial.println("ESP PSRAM64H тест методов put и get\r\n"); delayMicroseconds(150); psram.reset(); // // Число с плавающей точкой float pi = M_PI; Serial << "Исходное число (float): " << pi << "\r\n"; psram.put(1234, pi); // запишем pi = 0; // загадим psram.get(1234, pi); // прочитаем Serial << "Прочитанное число (float): " << pi << "\r\n\r\n"; // // Число uint32_t uint32_t f_cpu = F_CPU; Serial << "Исходное число (uint32_t): " << f_cpu << "\r\n"; psram.put(1234, f_cpu); // запишем f_cpu = 0; // загадим psram.get(1234, f_cpu); // прочитаем Serial << "Прочитанное число (uint32_t): " << f_cpu << "\r\n\r\n"; // // Массив char[] char str[] = "\"Жил один еврей, так он сказал, что всё проходит.\""; Serial << "Исходный массив: " << str << "\r\n"; psram.put(1234, str); // запишем str[0] = '\0'; // загадим psram.get(1234, str); // прочитаем Serial << "Прочитанный массив: " << str << "\r\n\r\n"; // Исходная структура (запишем её по адресу 1234) CAnekdot source(321, "Одесса", "- Сёма, Вы таки куда?\r\n- Да, нет, я домой."); psram.put(1234, source); Serial.println("::: Исходная структура :::"); Serial.println(source); // Прочитанная из PSRAM структура CAnekdot dest; psram.get(1234, dest); Serial.println("::: Прочитанная структура :::"); Serial.println(dest); Serial.println("=== Тест завершён ==="); } void loop(void) {}
Результат работы
ESP PSRAM64H тест методов put и get Исходное число (float): 3.14 Прочитанное число (float): 3.14 Исходное число (uint32_t): 16000000 Прочитанное число (uint32_t): 16000000 Исходный массив: "Жил один еврей, так он сказал, что всё проходит." Прочитанный массив: "Жил один еврей, так он сказал, что всё проходит." ::: Исходная структура ::: 321, (Одесса) - Сёма, Вы таки куда? - Да, нет, я домой. ::: Прочитанная структура ::: 321, (Одесса) - Сёма, Вы таки куда? - Да, нет, я домой. === Тест завершён ===
3. Измерение скорости записи и чтения
#include <Printing.h> #define USE_PINOPS true #include <ESP_PSRAM64.h> ESP_PSRAM64 psram; void printReport(const char * title, const uint32_t duration) { uint32_t bytesPerSecond = (static_cast<uint64_t>(psram.totalBytes) * 1000 + duration / 2) / duration; const uint32_t spiBytesPerSecond = F_CPU / 16; uint32_t percent = (bytesPerSecond * 100 + spiBytesPerSecond / 2) / spiBytesPerSecond; Serial << title << "\r\n " << (duration / 1000) << "с " << (duration % 1000) << "мс; " << bytesPerSecond << " байтов в секунду - " << percent << "% от скорости SPI\r\n"; } void setup(void) { Serial.begin(115200); Serial.println("=== Тест на скорость чтения и записи ==="); delayMicroseconds(150); psram.reset(); // // Тест на скорость заполнения (заполняем 8МБ нулями) uint32_t start = millis(); psram.fill(0, psram.totalBytes); printReport("Заполнение 8МБ", millis() - start); Serial.flush(); // // Тест на скорость записи (пишем 8МБ блоками по 1кБ) uint8_t buffer[1024]; const uint16_t sz = sizeof(buffer); start = millis(); for (TAddress addr = 0; addr < psram.totalBytes; addr += sz) { psram.write(addr, buffer, sz); } printReport("Запись 8МБ блоками по 1кБ", millis() - start); Serial.flush(); // // Тест на скорость чтения (читаем 8МБ блоками по 1кБ) start = millis(); for (TAddress addr = 0; addr < psram.totalBytes; addr += sz) { psram.read(addr, buffer, sz); } printReport("Чтение 8МБ блоками по 1кБ", millis() - start); // Serial.println("=== Тест завершён ==="); } void loop(void) {}
Результат работы
=== Тест на скорость чтения и записи === Заполнение 8МБ 23с 270мс; 360490 байтов в секунду - 36% от скорости SPI Запись 8МБ блоками по 1кБ 28с 657мс; 292725 байтов в секунду - 29% от скорости SPI Чтение 8МБ блоками по 1кБ 29с 186мс; 287419 байтов в секунду - 29% от скорости SPI === Тест завершён ===
Результат работы без использования PinOps (с удалённой из кода примера второй строкой)
Заполнение 8МБ 26с 330мс; 318595 байтов в секунду - 32% от скорости SPI Запись 8МБ блоками по 1кБ 31с 724мс; 264425 байтов в секунду - 26% от скорости SPI Чтение 8МБ блоками по 1кБ 32с 254мс; 260080 байтов в секунду - 26% от скорости SPI === Тест завершён ===
Как видите, скорость не ахти, но что есть, то есть.
В догонку!
Запустил на своей второй микросхеме PSRAM - на микрочиповской 23LC1024. Заработало всё и сразу с минимальными изменениями - буквально пару строк удалил и всё также работает. На той неделе объединю библиотеки в одну и выложу в тот же репозиторий. Кому надо - возьмёте.
Хочу поделиться впечатлениями.
Микросхема от микрочипа понравилась гораздо больше. Она, конечно, проигрывает по объёму (128к против 8М) (и по частоте - 20М против 33, но нам это неважно - у нас всё равно столько нет в ардуине).
В остальном же она намного приятнее.
Во-первых в сто раз выигрывает по потреблению в режиме standby - а это значит можно присобачить на её питание таблетку CR2032 через диодную развязку и использовать как еепром - на 4+ года хватит. А если присобачить два холдера под батарею, то можно и горячую замену использовать.
Во-вторых, она стабильнее. Например и та и другая позволяет писать за один присест хоть всю память. Но микрочиповская реально это позволяет, а у ЕСП-шной при долгом удержании CE начинают проскакивать сбои (нечастые, но ...) - надо периодически останавливать запись и начинать снова (что и я делаю в библиотеке, а для микрочиповской это не нужно) . Может тут виной то, что у меня схема собрана на
сопляхмакетке - не знаю, но она же для обеих микросхем одинаково собрана.Наконец, микрочиповская имеет более широкий диапазон питания.
См. сравнительную таблицу:
Мне кажется, "первые впечатления" несколько поверхностные.
Чтобы претендовать на объективность, микросхемы должны были бы иметь сопоставимый объем памяти.
А так получается:
1. - в 64 раза меньший объем,
2. - в 100 раз меньше потребление в режиме standby,
3. - в 20 раз меньше потребление при записи,
4. - в полтора раза ниже максимальная частота,
5. - в полтора раза выше максимальное напряжение,
6. - зафиксирована более стабильная работа.
Собственно, 2 и 3 являются очевидными следствиями 1.
Очевидно, что и 5 также является следствием 1.
Ну а 6 в свою очередь, весьма вероятно, является следствием 3, т.е. тоже следствием 1. Я бы еще поэкспериментировал, установив керамику в непосредственной близости выводов питания.
Другое дело, что для Atmel328 тяжело придумать задачу, где бы требовалось более 128к памяти, следовательно, 8М практически не имеет преимуществ, а недостатки остаются.
В этом смысле интересно попробовать ее с stm32f103, где баланс между реальными плюсами и реальными минусами будет совсем другой (отсутствие 5В - уже не недостаток, а частота - явное преимущество).
А что - мило! Два замечания.
1. А что все-таки не сделал шаблон для вектора? put/get для массивов народу будут неудобны, в памяти копию держать надо ;)
2. Комменты и pdf-ка на русском. Гит - не сильно русский ресурс. Сперва на аглицком, а для души - можно и на родном! ;)))
----
ессно, что оба номера - просто шутка и придирки... ну шоп не промолчать! ;)))
До вектора и вообще высокоуровневых дел пока руки не дошли. Это то, о чём я изначально писал как о "низкоуровнеовм драйвере". put с get'ом затесались просто потому, что "бесплатно".
Евгений Петрович, у Вас в pdf-ке в таблице на первой странице перепутаны MOSI и MISO.
Да? Щас гляну.
Спасибо, поправил.
значит можно присобачить на её питание таблетку CR2032 через диодную развязку и использовать как еепром - на 4+ года хватит.
вот эту фразу не понял - а без внешнего питания она разве данные не сохраняет?
Добавка - почитал даташит, про сохранение данных ничего не сказано... мда, не знал. что такое бывает :)
b707,
а в даташите на микрочиповскую PSRAM 23LC1024 (топик же теперь уже про две сразу) , так там прямо сказано: "RAM Data Retention Voltage: 1V". Так что ни о каком сохранении и речи нет.
Сегодня (точнее, уже вчера) провел тесты устойчивости работы PSRAM64H на частоте 36 МГц. Из более миллиарда итераций записи/чтения шестибайтового фрагмента по случайным адресам не зафиксировано ни одной ошибки. Конденсатор по питанию емкостью 0.1 в плату не впаивал - стоял рядом на беспаечной макетке.
В общем, в процессе записи/чтения сбоев не зафиксировано. Можно, конечно, попытаться еще проверить на надежность хранения. Например, заполнить всю память по ДПСЧ, вернуть "затравку" в исходное состояние и через некоторое время проверить записанное на совпадение с вновь сгенерированной ППСЧ.
Но скорость - разочаровывает. Раз в 5 меньше, чем можно было ожидать. Логанализатор показал на огромные паузы между однобайтовыми посылками. Думаю, это проблема реализации SPI.h для пилюли.
Но скорость - разочаровывает. Раз в 5 меньше, чем можно было ожидать. Логанализатор показал на огромные паузы между однобайтовыми посылками. Думаю, это проблема реализации SPI.h для пилюли.
а писали/читали побайтно или сериями? - вроде как микросхема поддерживает оба варианта (и еще целыми страницами)
записи/чтения шестибайтового фрагмента по случайным адресам
Попробуйте, пожалуйста, записывать всю память одним разом (за одну #CE) или хотя бы по 10-100кб за раз. Я наблюдал проблемы именно при таком режиме).
Писал кусочками по 6 байт и другие варианты не пробовал.
Собственно, и за эти тесты взялся потому, что возникла идея использовать ее в одном проекте. Там по специфике запись/чтение будут осуществляться исключительно по 8 байт. Естественно, с таким же выравниванием. Поэтому заведомо без пересечения границы страницы.
Дойдут руки - посмотрю, что получается при записи больших фрагментов. Правда, не совсем понятно, откуда брать такое количество данных: ДПСЧ - это довольно долго, т.е. темп записи получится невысоким. А для высокого - только писать либо константу, либо буфер небольшого объема (хотя бы те же 8-16к) в цикле.
Евгений, опишите подробнее, в каких именно условиях появлялись проблемы.
1. Берём пример ValidationTest.ino из моей библиотеки. В нём ничего не меняем. Он пишет пол-памяти кусками по килобайту случайных значений, потом копирует эти пол-памяти во вторую половину (также блоками по килобайту) а потом читает из двух половин и сравнивает на равенство.
2. Берём собственно библиотеку и в ней находим макрос
и заменяем цикл в строке №4 (именно от отвечает за резание операции на куски) на просто _dataOp;, а строки №№3 и 5 выносим из цикла (при этом в строке №3 убираем "+i"). По идее должно быть как у меня было изначально - пишет "сколько дали" одним куском (с одним опусканием CE).
Петрович, почту проверь
Ответил
Собственно, добавил в setup последовательную запись. Публикую непричесанный исходник (в частности, комментарии не всегда соответствуют реальности)
Тест проходит однократно, после чего переходим к непрерывному 6-байтовому тесту. Результат:
Это, естественно, в том варианте, который был, т.е. с принудительной разбивкой на 32-байтовые блоки.
Сейчас сделаю в соответствии с рекомендациями, но должен заметить, что сравнение прочитанного с прочитанным же - не очень надежный тест.
Що маємо, то маємо :-)
Да уж...
"Ничего не меняем"- это очень оптимистично. Там и pinOps и Printing... Я как-то раньше просматривал Ваши исходники глазами и не пытался их откомпилировать, а тут...
В общем, я понимаю новичков, которые жалуются на Ваш код.
Нет, каждый, конечно, волен писать то и так, что и как он считает нужным. И тем более обустраивать свое рабочее место в соответствии с собственными представлениями об удобстве. Но в результате оказывается, что обмен исходниками сильно затрудняется, чтобы "пропихнуть" исходник, уже нужно затратить некоторые усилия и, самое главное, это занимает гораздо больше времени, чем было первоначально намечено.
Что-то не могу я пропихнуть эту конструкцию. Может, чего-то недопонял.
Вот в таком варианте работает. А стоит раскомментировать первую строку - глухо. Даже строки-заставки не выводит.
В седьмой строке не хватает i++ в цикле.
Необходимость Pinops убирается константой - она во второй строке примера.
Нормально!
А есть что-то подобное для SPI EEPROM ? Я не тороплюсь, пусть медленнее записывало бы но при выключении не стиралось. Или изображения хранить или звуки.
А есть что-то подобное для SPI EEPROM ?
Кто-то выкладывал, поищите.
А вообще, смотря что за епром, а то для некоторых на гитхабе полно библиотек.
Там чтение в основном, работы с записью с предварительным стиранием страниц не видел я. А EEPROM w25q32 64 128 ... такие и аналоги.
В седьмой строке не хватает i++ в цикле.
В общем, тест крутится уже пару часов, сколько намотал, не знаю, почему-то сам он об этом не сообщает - где-то в районе больше сотни, но меньше тысячи итераций. Ошибок не выявлено.
Ну, и слава Богу, значит источником ошибок была моя макетка с её сопливой сборкой.
Все может быть.
Кстати, по коду данные перемежаются командами раз в 32 байта, но происходит это не на границе страниц (что было бы логично), а просто через каждые 32 байта от начала указанного блока. Почему так?
И еще. Я, наверное, не слишком внимательно читал дэйташит, но что-то не смог там обнаружить данные, какой максимальный ток допустим через выводы данных. А то хочется сопрячь MISO с 5-вольтовыми узлами обычным резистивным делителем, вот и выбираю номиналы. Пока ориентируюсь на 470/1к или 330/680.
Потому, что в режиме "Linear Burst", который я использую, при частотах до 84МГц границы страниц абсолютно пофигу (раздел 4, стр. 8 даташита).
Евгений, есть вопрос по питанию ESP-шного чипа.
Пробовал запускать простой пример simpletest. Верно читает из памяти с вероятностью примерно 70% только если руками держу преобразователь уровней . Как только отпускаю - все. Либо нули, либо 255. 3,3 вольта подаю на преобразователь с импульсного понижающего dc/dc. Хотел с ардуины (нано) запитать, но она чего-то 3.3 не выдает, что-то там сломалось. Насколько требователен этот чип к питанию? Есть нюансы?
Задача вообще такая: целый день собираюсь писать данные с датчиков пачками по 32 байта в эту память с интервалом в 1 секунду, потом по нажатию кнопки нужно все данные аккуратно перенести на флешку.
Разобрался. Заработало. На преобразователе уровней контакт OE надо подключить к питанию.
"Если не работает - прочтите, наконец, инструкцию!" (С)
Разобрался. Заработало. На преобразователе уровней контакт OE надо подключить к питанию.
Блин, ну как же вы все задолбали-то! Ну, написано же в приклеенной теме для новичков, что при вопросе обязательно должны быть скетч и схема. Почему вы никогда их не приводите? Вот как Вам можно помочь, если Вы в секрете держите чего там намудрили?