Можно ли загружать и запускать код из EEPROM во время загрузки?
У меня возникли проблемы при написании пользовательского загрузчика, так что он загружает код из EEPROM (на данный момент внутренний, поскольку у меня нет внешней памяти в моих руках) и записывает во флэш-память. После ~ 2 недель борьбы я столкнулся с этими вопросами и ответами. После этого я понял, что совершил ошибку, не проверив возможность этого. Тем не менее, я все еще в замешательстве по поводу нескольких вещей:
Если Гарвардская архитектура ограничивает то, что я хочу, почему optiboot может прошивать память из входящих данных UART, которые даже не являются памятью?
Если UART считывает и прошивает память соответствующим образом, не могу ли я сделать то же самое с EEPROM? Считывание байтов, стирание флэш-памяти, запись байтов.
Если это невозможно, учитывая два ответа в приведенной выше ссылке, то как это работает? Я предполагаю, что это та же логика, что и я, используя внешнюю память и перезаписывая flash. Означает ли это, что Гарвардская архитектура ограничивает только обмен внутренней памятью?
ОБНОВЛЕНИЕ: Вот что я сделал на данный момент:
В отдельном скетче EEPROM заполняется двоичным кодом. Загрузчик (отредактированный optiboot v8):
// Настройка сторожевого таймера для запуска после желаемого тайм-аута
watchdogConfig(WDTPERIOD);
#if BIGBOOT
if ((eeprom_read(0x000) == 0x0C) && (eeprom_read(0x001) == 0x94))
{
//while (EECR & (1 << EEPE)); //подождите, если предыдущие данные все еще записываются
uint8_t i, j;
for (i = 0; i < 8; ++i) // всего 8 страниц
{
uint8_t *code;
code = buff.bptr;
address.word = (i * 128);
for (j = 0; j < 128; ++j) // 128 слов за раз
{
if (j % 64 == 0)
LED_PORT ^= _BV(LED);
*code++ = eeprom_read(address.word + j);
}
#ifdef VIRTUAL_BOOT_PARTITION
virtualBootPartition(buff, address);
#endif
writebuffer(0x46, buff, address, 128);
}
while(1); // дождитесь WDT
}
#endif
#if (LED_START_FLASHES > 0) || LED_DATA_FLASH || LED_START_ON
/* Set LED pin as output */
LED_DDR |= _BV(LED);
#endif
в то время как моя функция выглядит следующим образом:
#if BIGBOOT
static uint8_t eeprom_read(uint16_t addr)
{
EEAR = addr; //считывать с этого адреса
EECR |= (1 << EERE); //включить чтение
return EEDR;
}
#endif
Я использую make atmega328 BIGBOOT=1
для создания шестнадцатеричного файла. Это не работает + загрузка UART также завершается неудачей. Если я установлю для BIGBOOT значение 0 и сгенерирую, загрузка UART будет работать нормально.
P.S. virtualBootPartition -
это функция, которая вызывается в случае STK_PROG_PAGE. Чтобы не повторять один и тот же фрагмент кода, я собрал эту часть в функцию.
#ifdef VIRTUAL_BOOT_PARTITION
/*
* Как работает виртуальный загрузочный раздел:
* В начале обычной программы AVR находится набор векторов, которые
* реализовать механизм прерывания. Каждый вектор обычно представляет собой один
* инструкция, которая отправляется в соответствующий ISR.
* Инструкция обычно представляет собой rjmp (на AVR с 8k или менее флэш-памяти)
* или jmp-инструкция, и 0-й вектор выполняется при сбросе и переходит
* к запуску пользовательской программы:
* векторы: запуск jmp
* jmp ISR1
* jmp ISR2
* : ;; и т.д
* jmp lastvector
* Для реализации "Виртуального загрузочного раздела" Optiboot определяет, когда
программируется страница * flash, содержащая векторы, и заменяет
вектор * startup переходом к началу Optiboot. Тогда это спасает
* вектор запуска приложения в другом (должен быть неиспользуем приложением
*) и, наконец, программирует страницу с измененными векторами.
* После этого, при сбросе, вектор будет отправлен в начало
* Optiboot. Когда Optiboot решит, что он запустит пользовательское приложение,
* он извлекает сохраненный начальный адрес из неиспользуемого вектора и переходит
* там.
* Логика зависит от размера flash и от того, находится ли вектор сброса
* на той же странице flash, что и сохраненный начальный адрес.
*/
#if FLASHEND > 8192
/*
* AVR с 4-байтовыми векторами ISR и "jmp"
* ПРЕДУПРЕЖДЕНИЕ: это работает только до 128 КБ флэш-памяти!
*/
#if FLASHEND > (128*1024)
#error "Can't use VIRTUAL_BOOT_PARTITION with more than 128k of Flash"
#endif
if (address.word == RSTVEC_ADDRESS) {
// Это страница сброса вектора. Нам нужно обновить код
//, чтобы загрузчик запускался первым.
//
// Сохранить цели jmp (для "Проверки")
rstVect0_sav = buff.bptr[rstVect0];
rstVect1_sav = buff.bptr[rstVect1];
// Добавить "перейти к Optiboot" при СБРОСЕ вектора
// ПРЕДУПРЕЖДЕНИЕ: это работает до тех пор, пока "main" находится в первом разделе
buff.bptr[rstVect0] = ((uint16_t)pre_main) & 0xFF;
buff.bptr[rstVect1] = ((uint16_t)pre_main) >> 8;
#if (SAVVEC_ADDRESS != RSTVEC_ADDRESS)
// save_vector необязательно находится на той же флэш-странице, что и сброс
// вектор. Если это не так, то мы ждем, чтобы действительно написать это.
}
else if (address.word == SAVVEC_ADDRESS) {
// Сохранить старые значения для проверки
saveVect0_sav = buff.bptr[saveVect0 - SAVVEC_ADDRESS];
saveVect1_sav = buff.bptr[saveVect1 - SAVVEC_ADDRESS];
// Переместить цель СБРОСА jmp в вектор "сохранения"
buff.bptr[saveVect0 - SAVVEC_ADDRESS] = rstVect0_sav;
buff.bptr[saveVect1 - SAVVEC_ADDRESS] = rstVect1_sav;
}
#else
// Сохранить старые значения для проверки
saveVect0_sav = buff.bptr[saveVect0];
saveVect1_sav = buff.bptr[saveVect1];
// Переместить цель СБРОСА jmp в вектор "сохранения"
buff.bptr[saveVect0] = rstVect0_sav;
buff.bptr[saveVect1] = rstVect1_sav;
}
#endif
#else
/*
* AVR with 2-byte ISR Vectors and rjmp
*/
if (address.word == rstVect0) {
// Это страница сброса вектора. Нам нужно жить-исправлять
// код, чтобы загрузчик запускался первым.
//
// Переместить вектор СБРОСА в вектор "сохранения"
// Сохранить цели jmp (для "Проверки")
rstVect0_sav = buff.bptr[rstVect0];
rstVect1_sav = buff.bptr[rstVect1];
addr16_t vect;
vect.word = ((uint16_t)pre_main-1);
// Инструкция - это относительный переход (rjmp), поэтому пересчитайте.
// инструкция RJMP равна 0b1100xxxx xxxxxxxx, поэтому мы должны иметь возможность
// выполнять математические вычисления для смещений, не маскируя их сначала.
// Обратите внимание, что rjmp относится к уже увеличенному ПК, поэтому смещение
// на единицу меньше, чем вы могли бы ожидать.
buff.bptr[0] = vect.bytes[0]; // rjmp для запуска загрузчика
buff.bptr[1] = vect.bytes[1] | 0xC0; // создать "rjmp"
#if (SAVVEC_ADDRESS != RSTVEC_ADDRESS)
}
else if (address.word == SAVVEC_ADDRESS) {
addr16_t vect;
vect.bytes[0] = rstVect0_sav;
vect.bytes[1] = rstVect1_sav;
// Сохранить старые значения для проверки
saveVect0_sav = buff.bptr[saveVect0 - SAVVEC_ADDRESS];
saveVect1_sav = buff.bptr[saveVect1 - SAVVEC_ADDRESS];
vect.word = (vect.word-save_vect_num); //вычтите позицию прерывания 'save'
// Переместить цель СБРОСА jmp в вектор "сохранения"
buff.bptr[saveVect0 - SAVVEC_ADDRESS] = vect.bytes[0];
buff.bptr[saveVect1 - SAVVEC_ADDRESS] = (vect.bytes[1] & 0x0F)| 0xC0; // создать "rjmp"
}
#else
// Сохранить старые значения для проверки
saveVect0_sav = buff.bptr[saveVect0];
saveVect1_sav = buff.bptr[saveVect1];
vect.bytes[0] = rstVect0_sav;
vect.bytes[1] = rstVect1_sav;
vect.word = (vect.word-save_vect_num); //вычтите позицию прерывания 'save'
// Переместить цель СБРОСА jmp в вектор "сохранения"
buff.bptr[saveVect0] = vect.bytes[0];
buff.bptr[saveVect1] = (vect.bytes[1] & 0x0F)| 0xC0; // создать "rjmp"
// Добавьте rjmp в загрузчик при СБРОСЕ вектора
vect.word = ((uint16_t)pre_main-1); // (main) всегда <= 0x0FFF; маскировка не требуется.
buff.bptr[0] = vect.bytes[0]; // инструкция rjmp 0x1c00
}
#endif
#endif // ПРОШИВКА
#endif // VBP
1 ответ
Лучший ответ:
Если у вас есть sketch, который может записывать свою обновленную версию в EEPROM ATmega328P, а затем загружать из EEPROM, то вы можете использовать flash для той же цели. ATmega328P имеет 32 КБ флэш-памяти и только 1 КБ EEPROM.
Для моей библиотеки ArduinoOTA я разработал способ сохранения загруженного двоичного файла в верхней половине флэш-памяти, а затем позволил загрузчику скопировать двоичный файл по адресу 0 и сбросить MCU.
Optiboot 8 имеет функцию do_spm(). Эта функция может быть вызвана из скетча для записи во флэш-память.
Чтобы скопировать двоичный файл, хранящийся в верхней половине флэш-памяти, по адресу 0, я написал новую функцию для Optiboot. Он называется copy_flash_pages и имеет дополнительный логический параметр для запроса сброса MCU после копирования страниц.
Для сетевой загрузки размер скетча больше половины флэш-памяти 328p из-за сетевой библиотеки. Таким образом, ArduinoOTA предназначена только для ATmega с флэш-памятью более 64 КБ.
Если у вас есть способ получить двоичный файл размером менее половины флэш-памяти 328p, то вы можете записать его в верхнюю половину флэш-памяти, а затем использовать мою функцию copy_flash_pages для его загрузки.
Ресурсы:
- файл заголовка для доступа к функциям Optiboot из
- определения sketch boards и загрузчика для 328p с помощью Optiboot с функцией copy_flash_pages в my_boards (обратите внимание на измененные предохранители в boards.txt для Uno / Nano / Mini)
- класс InternalStorageAVR в библиотеке ArduinoOTA в качестве примера того, как хранить и применять двоичный файл
Пример использования внутреннего хранилища:
if (!InternalStorage.open(length)) {
Serial.println("There is not enough space to store the update. Can't continue with update.");
return;
}
byte b;
while (length > 0) {
if (!source.readBytes(&b, 1)) // чтение байта с таймаутом
break;
InternalStorage.write(b);
length--;
}
InternalStorage.close();
if (length > 0) {
Serial.print("Timeout downloading update file at ");
Serial.print(length);
Serial.println(" bytes. Can't continue with update.");
return;
}
Serial.println("Sketch update apply and reset.");
InternalStorage.apply(); // это не возвращает
Это почти решает мою проблему. Однако я не понял этого: "Если у вас есть способ получить двоичный файл размером меньше половины 328p флэш-памяти, то вы можете записать его в верхнюю половину флэш-памяти, а затем использовать мою функцию copy_flash_pages для загрузки это."
. ATmega328p имеет флэш-память 32 КБ, а размер двоичного файла составляет 1 КБ., @Miradil Zeynalli
@MiradilZeynalli, для следующей загрузки загруженный код должен поддерживать загрузку. можно сделать этот код таким маленьким?, @Juraj
Более того, я в основном хотел бы решить это с помощью загрузчика, как следует из моего вопроса. Потому что наличие двоичного файла во флэш-памяти + самой программы + загрузчика может иметь ограничения для больших программ (без использования вашей сетевой библиотеки). В моем случае я хотел бы использовать загрузчик максимум 1k (я даже планирую удалить поддержку STK500, поэтому я могу уменьшить его до 0,5k)., @Miradil Zeynalli
тогда почему вы хотите перейти на EEPROM? загрузчик может писать напрямую во флэш-память. Я не понимаю процесса, которого вы пытаетесь достичь. как передается бинарник?, @Juraj
Я просто должен проверить это, если честно :). Но, я думаю, исходя из вашего опыта, вы этого не сделали. Тогда, пожалуйста, поправьте меня, если я вас неправильно понял. Даже если у меня уже есть массив значений (скажем, я получил его из bluetooth) бинарного файла, я могу прошить память и загрузить код с вашим классом InternalStorageAVR
и его функциями, @Miradil Zeynalli
да. аналогично этому примеру https://github.com/jandrassy/ArduinoOTA/blob/master/examples/Advanced/OTASketchDownload/OTASketchDownload.ino, @Juraj
Я пока использую внутреннюю EEPROM, так как у меня сейчас нет внешней. Чего я хочу добиться, так это прочитать из eeprom и перезаписать флэш-память ВО ВРЕМЯ загрузчика. В коде приложения прочитать двоичный файл - сохранить двоичный файл в EEPROM - перезагрузить себя., @Miradil Zeynalli
Я проанализировал ваш код и, как вы сказали, он хорош для небольших программ или больших микроконтроллеров флэш-памяти. Если я правильно понимаю ваш код, вы используете верхнюю половину флэш-памяти в качестве временного хранилища для кода, а затем копируете его в реальный раздел программы, поэтому вам нужно, чтобы ваш код был меньше размера флэш-памяти. Тогда у меня такой вопрос: можно ли хранить бинарник не во флеше, а в EEPROM (или в другом внешнем запоминающем устройстве) и потом копировать его во флешь своими функциями?, @Miradil Zeynalli
код для записи в EEPROM прост. затем в загрузчике вы проверяете, содержит ли EEPROM новый двоичный файл, и копируете его во флэш-память. и я так понимаю код у тебя есть, а новый бинарник не запускается?, @Juraj
проблема с внешним устройством заключается в том, что вы должны кодировать связь через SPI или аналогичный, и это делает код больше, @Juraj
Да, я пытаюсь читать из eeprom во время загрузчика и записывать во флэш-память (как это делает optiboot). Но, кажется, это не удается. Я включил все необходимые фрагменты кода в вопрос., @Miradil Zeynalli
Все работало правильно, я просто неправильно тестировал. Как вы сказали, с вашей библиотекой я могу хранить более тяжелый код, чем EEPROM. Я все равно буду использовать внешний EEPROM, но все же ваш ответ правильный, поэтому я его отметил (несмотря на то, что прошло 6 месяцев), @Miradil Zeynalli
- Не удается снова загрузиться после смены платы
- Путаница с ATmega328P и 3,3 В/8 МГц
- Как постоянно считывать/записывать переменные на Arduino Due (без EEPROM/shield)?
- Что означает avrdude: Device signature = 0x000000? Неисправная проводка? Проблема с программным обеспечением? Неправильная инструкция?
- Не могу записать загрузчик на Atmega328P с помощью Arduino Uno
- Arduino EEPROM сохраняет старые данные после прошивки новой программой
- Можем ли мы записать загрузчик Arduino в любой чип микроконтроллера?
- Atmega328p — переход на низкое энергопотребление 1,8 В с использованием генератора 4 МГц — прошивка загрузчика
Вы можете запускать код только из флэш-памяти. Только загрузчик может писать во флэш-память. Когда загрузчик «выходит», он запускает код на «обычном» разделе флэш-памяти. Загрузчик может получить код из ввода, который он хочет, UART, IR, I2C, SPI. Он просто должен реализовать его и, возможно, какой-то протокол поверх него, чтобы он знал, когда данные программы запускаются и останавливаются. Он может читать из EEPROM, но это будет означать, что ваш код может быть только размером с EEPROM, который составляет 1 КБ, если я правильно помню, для 328, а флэш-память составляет 32 КБ (минус размер вашего загрузчика)., @Gerben
Я нашел эти загрузчики: https://github.com/mysensors/DualOptiboot. Он получает свою программу из SPI-EEPROM (где основная программа скачивает ее откуда-то и помещает в EEPROM. Затем, после «перезагрузки», загрузчик переносит ее из EEPROM во флэш-память)., @Gerben
Спасибо за ответ. Я тщательно проанализировал последовательную связь между ПК и Arduino при загрузке. Размер кода для atmega328p (для других MCU не знаю) ровно 1К. На этот раз я написал EEPROM в другом скетче, который является бинарным файлом. Затем я отредактировал загрузчик, чтобы, если в EEPROM что-то есть, прочитать и загрузить его, как это происходит при получении последовательности UART. Но, к сожалению, безуспешно. Думаю, внешняя память решит эту проблему., @Miradil Zeynalli
вы также можете сохранить этот 1 КБ во флэш-памяти. см. функцию do_spm в Optiboot 7 https://github.com/Optiboot/optiboot/blob/master/optiboot/examples/demo_dospm/demo_dospm.ino, @Juraj
Похоже, ты на правильном пути. Не уверен, почему это было неудачно. Хотя ваш акцент на «последовательной связи» и «загрузке» меня немного смущает. Я не понимаю, как это вдруг заработает с внешней EEPROM., @Gerben
@Juraj Я не думаю, что это сработает, если вы хотите переписать весь скетч. Но, может быть, это сработает для ОП., @Gerben
@Gerben, чтобы переписать скетч, есть моя функция
copy_flash_pages
в загрузчике для загрузки с верхней половины флэш-памяти. https://github.com/jandrassy/ArduinoOTA/blob/master/src/InternalStorageAVR.cpp https://github.com/Optiboot/optiboot/pull/269, @JurajСвязано: [Можно ли использовать дополнительную флэш-память AVR в качестве энергонезависимой флэш-памяти, подобной EEPROM?](https://arduinoprosto.ru/q/51277/7727)., @Gabriel Staples
То, что вы пытаетесь сделать, может потребовать редактирования скрипта компоновщика (в данный момент я даже не уверен, где он находится для Arduino)., @Gabriel Staples
Похоже, что файлы скриптов компоновщика находятся, например, в: **arduino-1.8.13/hardware/tools/avr/avr/lib/ldscripts/avr1.x**. Однако я хватаюсь за соломинку - не знаю, как определить, какая из них для ATmega328. Кто-нибудь знает? Кроме того, вот код optiboot. Я думаю, стоит изучить, чтобы увидеть, как именно это работает, попробовать и посмотреть, как это работает, каковы его ограничения, и перейти оттуда: https://github.com/arduino/ArduinoCore-avr/blob/master/bootloaders/ optiboot/optiboot.c. Вероятно, у него будут подсказки, которые можно найти в таблицах данных и официальных документах Atmel/Microchip., @Gabriel Staples
Я отредактировал свой пост и добавил дополнительные пояснения и фрагменты кода., @Miradil Zeynalli
@GabrielStaples, зачем мне скрипт компоновщика?, @Miradil Zeynalli
чтобы использовать BIGBOOT с 328p, вы должны изменить фьюзы https://github.com/jandrassy/my_boards/blob/14f363387bbe70bfe66e1b6c2b7a398338d92533/avr/boards.txt#L89 при записи загрузчика, @Juraj
А я то думал, почему BIGBOOT без моего кода не работает). Спасибо!, @Miradil Zeynalli
прочитайте мой ответ ниже для получения дополнительных ноу-хау, @Juraj