Пример использования 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. Берём собственно библиотеку и в ней находим макрос
#define MULTIBYTE_OPERATION(_operation, _dataOp) \ for (TSize i = 0; i < size; ) { \ opStart(_operation, address + i); \ for (uint8_t j = 0; j < dataBlockSize && i < size; j++, i++) _dataOp; \ highCE(); \ }и заменяем цикл в строке №4 (именно от отвечает за резание операции на куски) на просто _dataOp;, а строки №№3 и 5 выносим из цикла (при этом в строке №3 убираем "+i"). По идее должно быть как у меня было изначально - пишет "сколько дали" одним куском (с одним опусканием CE).
Петрович, почту проверь
Ответил
Собственно, добавил в setup последовательную запись. Публикую непричесанный исходник (в частности, комментарии не всегда соответствуют реальности)
static int do_rand (unsigned long *ctx) { /* * Вычисляет x = (7^5 * x) mod (2^31 - 1) * с переполнением по 31 битам: * (2^31 - 1) = 127773 * (7^5) + 2836 * Из публикации "Генераторы случайных чисел: хорошие найти трудно" * ("Random number generators: good ones are hard to find") * Park and Miller, Communications of the ACM, vol. 31, no. 10, * October 1988, p. 1195. */ long hi, lo, x; /* Нельзя инициализировать нулем, поэтому будем использовать другое значение. */ if (*ctx == 0) *ctx = 123459876; hi = *ctx / 127773; lo = *ctx % 127773; x = 16807 * lo - 2836 * hi; if (x < 0) x += 0x7fffffff; return ((*ctx = x) % ((u_long)RAND_MAX + 1)); } int rand_r(unsigned int *ctx) { u_long val = (u_long) *ctx; int r = do_rand(&val); *ctx = (unsigned int) val; return (r); } static u_long next = 1; int rand (void) { return (do_rand(&next)); } void srand(u_int seed) { next = seed; } //#include <Printing.h> //#define USE_PINOPS true #define PSRAM64_CE_PIN PA4 #include <SPI.h> #include <ESP_PSRAM64.h> ESP_PSRAM64 psram; const int size = 16384; byte bufOut[size]; void setup(void) { Serial.begin(115200); while(!Serial); Serial.println("Simple test - write 6 bytes to random address and read from here"); delayMicroseconds(150); // randomSeed(analogRead(0)); srand(analogRead(0)); psram.reset(); srand(1234567890); int t0 = millis(); for(int i = 0; i < size; i++) bufOut[i] = rand()&0xFF; int t1 = millis(); Serial.print("fill buffer time(ms): "); Serial.println(t1-t0); t0 = millis(); for(int i = 0; i < (8192*1024); i += size) psram.write(i, bufOut, size); t1 = millis(); Serial.print("write to PSRAM time(ms): "); Serial.println(t1-t0); int numErrors = 0; t0 = millis(); for(int i = 0; i < (8192*1024); i++) { if((i % size) == 0) srand(1234567890); byte b0, b1; psram.read(i, &b0, 1); b1 = rand()&0xFF; if(b0 != b1) { Serial.print(" !!! PSRAM ERROR !!!, address: "); Serial.print(i); Serial.print(", written: "); Serial.print(b1); Serial.print(", read: "); Serial.println(b0); numErrors++; } } t1 = millis(); Serial.print("read from PSRAM time(ms): "); Serial.println(t1-t0); Serial.print(numErrors); Serial.println(" errors found"); Serial.println(); } const bool DEBUG = false; const byte bb[2][6] = {{0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA},{0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55}}; void loop(void) { // Буфер со случайным содержимым const int size = 6; static int counter = 0; byte buffer_out[size], buffer_in[size]; // constexpr TSize size = sizeof(buffer); // for (TSize i=0; i < size; i++) buffer_out[i] = rand()&0xFF; // random(0, 255); // for (TSize i=0; i < size; i++) buffer[i] = i+48; // random(0, 255); for (TSize i=0; i < size; i++) buffer_out[i] = bb[counter & 1][i]; // random(0, 255); // Случайный адрес const TAddress address = rand()%(psram.totalBytes - size); // random(0, psram.totalBytes - size); if(DEBUG) { Serial.print("Address: "); Serial.println(address); Serial.print(" Writing: "); } psram.write(address, buffer_out, size); if(DEBUG) { for (TSize i=0; i < size; i++) { Serial.print((unsigned) buffer_out[i]); Serial.print(' '); } Serial.println(); Serial.print("Reading: "); } psram.read(address, buffer_in, size); int j = 0; // счетчик ошибок static int j0 = 0; for (TSize i=0; i < size; i++) { if(DEBUG) { Serial.print((unsigned) buffer_in[i]); Serial.print(' '); } j += (buffer_in[i] != buffer_out[i]); } if(DEBUG) { Serial.println(); } if(j) Serial.println(" !!! ERROR !!!"); j0 += j; counter++; if(((counter%100000) == 0) || DEBUG) { Serial.print("Iterations: "); Serial.print(counter); Serial.print(", Errors: "); Serial.print(j0); Serial.print(", Elapsed time: "); Serial.print(millis()/1000.0); Serial.println(" s"); if(DEBUG) delay(2000); } }Тест проходит однократно, после чего переходим к непрерывному 6-байтовому тесту. Результат:
Это, естественно, в том варианте, который был, т.е. с принудительной разбивкой на 32-байтовые блоки.
Сейчас сделаю в соответствии с рекомендациями, но должен заметить, что сравнение прочитанного с прочитанным же - не очень надежный тест.
Що маємо, то маємо :-)
Да уж...
"Ничего не меняем"- это очень оптимистично. Там и pinOps и Printing... Я как-то раньше просматривал Ваши исходники глазами и не пытался их откомпилировать, а тут...
В общем, я понимаю новичков, которые жалуются на Ваш код.
Нет, каждый, конечно, волен писать то и так, что и как он считает нужным. И тем более обустраивать свое рабочее место в соответствии с собственными представлениями об удобстве. Но в результате оказывается, что обмен исходниками сильно затрудняется, чтобы "пропихнуть" исходник, уже нужно затратить некоторые усилия и, самое главное, это занимает гораздо больше времени, чем было первоначально намечено.
Что-то не могу я пропихнуть эту конструкцию. Может, чего-то недопонял.
//#define OLD_VARIANT #ifdef OLD_VARIANT #define MULTIBYTE_OPERATION(_operation, _dataOp) \ opStart(_operation, address); \ for (TSize i = 0; i < size; ) { \ _dataOp; \ }; \ highCE() #undef OLD_VARIANT #else #define MULTIBYTE_OPERATION(_operation, _dataOp) \ for (TSize i = 0; i < size; ) { \ opStart(_operation, address + i); \ for (uint8_t j = 0; j < dataBlockSize && i < size; j++, i++) _dataOp; \ highCE(); \ } #endifВот в таком варианте работает. А стоит раскомментировать первую строку - глухо. Даже строки-заставки не выводит.
В седьмой строке не хватает 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 надо подключить к питанию.
Блин, ну как же вы все задолбали-то! Ну, написано же в приклеенной теме для новичков, что при вопросе обязательно должны быть скетч и схема. Почему вы никогда их не приводите? Вот как Вам можно помочь, если Вы в секрете держите чего там намудрили?