AVR - Запись данных во флеш память

AVR

Встроенная EEPROM стремительно заканчивается, а данные куда-то записывать надо. Знакомая ситуация, не правда-ли?

Что мы обычно делаем в таких случаях? Ставим внешнюю EEPROM, флеш или SD карточку на 32 гига. Это оправдано, если устройство достаточно сложное. А если оно состоит из одной тиньки и двух с половиной светодиодов? Тогда подключение внешней памяти грозит кардинальными изменениями в алгоритме, а может и пинов банально не хватит.

Но ведь у нас есть своя флеш память, которая в подавляющем большинстве случаев заполнена чуть менее, чем на половину. Отлично! Её и используем для записи данных.

Механизм записи во флеш память на первый взгляд немного запутанный. Но если присмотреться — все просто. Он состоит из трех основных частей:
— Очистка страницы памяти.
— Подготовка (закидываем данные во временный буфер)
— Запись.
Причем первые два пункта можно невозбранно менять местами.

Флеш память разделена на страницы. Размер страницы зависит от общего объема флеш памяти — смотри в даташите.
Например, для тини13 есть такая табличка:


Размер страницы 32 байта (16 слов), а всего таких страниц 32 штуки.
Так-же там написано, что в регистре адреса первые 4 бита (PC[3:0]) занимает адрес слова, а адрес страницы начинается с 5го бита. Значит, если нам надо записать адрес 3й страницы, то в регистр уйдет 3<<4.

При операциях со страницами (запись и очистка) от нас будут требовать номер страницы. А при работе со временным буфером — адрес слова в странице. К счастью, МК сам выделяет нужные части из адреса, который мы кидаем в регистр Z — поэтому можно не задумываться и пихать просто адрес слова в памяти.

При прошивке МК надо включить фьюз SELFPRGEN. Без этого ничего работать не будет.

1) Расчистка места.

Для очистки страницы памяти надо выполнить такую последовательность действий:
— Пихаем в регистр Z адрес. Из него МК выделит адрес страницы, а на первые 4 бита забьет.
— Поднимаем в регистре SPMCSR биты PGERS (Page Erase) и SELFPRGEN (Self Programming Enable).
— Быстро (в течении 4х тактов после записи в SPMCSR) выполняем команду SPM.

На время очистки страницы (а это около 4 мс) процессор подвисает.

2) Готовим данные к записи.

— Записываем в регистр Z адрес слова. Теперь все наоборот. МК будет ориентироваться по первым 4 битам адреса.
— В пару регистров R1:R0 пишем данные, которые хотим запихать в буфер.
— Поднимаем бит SELFPRGEN в регистре SPMCSR.
— Выполняем команду SPM.
… повторить, пока не будет заполнен весь буфер.

У этого буфера есть одна нехорошая особенность:
Записать два раза в одну ячейку нельзя. Т.е. если мы хотим переписать уже записанные в буфер данные, то его придется сначала очистить. Для очистки надо просто поднять бит CTPB в SPMCSR.
Алсо, буфер очищается сам после записи во флеш или перезагрузки.

Забавно так-же то, что данные в буфере будут потеряны, если записать что-то в EEPROM. Как-то хитро память устроена, не находите? :)
Последнее особенно актуально, если мы собираемся наполнять буфер постепенно, прерываясь на другие задачи (типа записи в EEPROM).

3) Пишем!

— Запихиваем адрес страницы в Z
— Устанавливаем биты SELFPRGEN и PGWRT.
— Выполняем SPM.

При записи страницы МК зависает на те-же 4 мс.

Пример устройства.

Для примера я хотел замутить термологгер на базе тини25. С записью температуры в флеш, работой со встроенным термометром и выдачей лога в UART. Но выяснилось, что UART у тини25 нету, а «родной» термометр уж больно кривой. Поэтому будем делать что по-проще. Например, записывать во флеш память напряжение, измеренное АЦП.

Алгоритм дешевого и сердитого логгера такой:
0) Инициализация периферии. Устанавливаем адрес записи на первую пустую страницу.
1) Ждем 1 сек — простым циклом, безо всяких таймеров и прерываний. Дешево и сердито.
2) Запускаем АЦП и ждем, пока он закончит преобразование.
3) Если мы начали писать новую страницу, то
3.1) Очишаем её.
4) Записываем во временнный буфер (по текущему адресу) значение АЦП.
5) Если мы уже заполнили весь буфер, то
5.1) Записываем его во флеш память.
6) Инкрементируем адрес записи.
7) Если дошли до конца памяти — затупляем в вечном цикле. Иначе идем на шаг 1.

Код на ассемблере:
/*
-------------------------------------
Демо проект. Запись данных в память программ (flash).

Каждую секунду
записывает значение напряжения с 3го канала АЦП (B3) во флеш память.

Тини13. 9.6Мгц
	--dcoder
-------------------------------------
*/


.include "tn13def.inc"

.def temp = R16

RESET:  //Начало..
ldi temp, 3
out ADMUX, temp

ldi temp, (1<<ADEN)|(1<<ADPS2)|(1<<ADPS0)|(1<<ADPS0)
out ADCSRA, temp


ldi ZL, Low(EndOfCode)
ldi ZH, High(EndOfCode)

loop:

;Действие первое: Задержка в 1 сек.
 LDI        R18, 49
 LDI        R17, 180
 LDI        R16, 102
 DEC        R16
 BRNE       PC-1
 DEC        R17
 BRNE       PC-3
 DEC        R18
 BRNE       PC-5

;Действие второе: запускаем АЦП и ждем, пока он завершит преобразование
 SBI ADCSRA, ADSC
 WaitADC:
 SBIS ADCSRA, ADIF
 rjmp waitADC;

;Действие третье: если PCWORD (номер слова в странице) = 0, значит
;мы перешли на новую страницу и её надо предварительно очистить.
 mov temp, ZL
 andi temp, 0x0F
 brne SkipErase // Переход, если PCWORD <> 0 
  ldi temp, 0b00000011
  out SPMCSR, temp
  spm
 SkipErase:

;Действие четвертое: записываем значения ADCH:ADCL во временный буфер.
 in R0, ADCL
 in R1, ADCH
 ldi temp, 0b00000001
 out SPMCSR, temp
 spm

;Действие пятое: если мы только-что записали последнее слово буфера -
; записываем буфер во флеш память.
 mov temp, ZL
 andi temp, 0x0F
 cpi temp, 0x0F
 brne SkipWrite
  ldi temp, 0b00000101
  out SPMCSR, temp
  spm
 SkipWrite:

;Действие шестое: инкрементируем адрес
 ldi temp,1
 add ZL, temp
 brcc ZHIncSkip 
  inc ZH
 ZHIncSkip:

;Действие седьмое и последнее: если мы достигли конца флеша, то идем
;тупить в цикле. Иначе - начинаем с начала.
cpi ZL, Low(flashend+1)
ldi temp, High(flashend+1)
cpc ZH, temp
brsh endless_loop
rjmp loop


endless_loop:
rjmp endless_loop

.org 0x60
EndOfCode:


Проверять код в протеусе бесполезно — не работает :)
Но в железе все прекрасно крутится.

Небольшое дополнение: RWW и NRWW

В МК с поддержкой бутлоадера на команду SPM накладываются ограничения. Её можно выполнить только из BLS (Bootloader section) — области памяти в самом конце флеша, где живут бутлоадеры. Размер этой области устанавливается фьюзами BOOTSZ.

Вообще, в контроллерах с поддержкой бутлоадера, весь флеш поделен на 2 части: RWW (Read-While-Write) и NRWW (No Read-While-Write). NRWW распологается в конце памяти и занимает место отведенное под бутлоадер. Т.е. размер этой области памяти равен максимальному размеру BLS. Остальное место (с начала флеша и до NRWW) занимает RWW область.


Разница между RWW и NRWW заключается вот в чем: Если бутлоадер (который находится в NRWW) пишет или стирает страницу в RWW области, то МК не останавливается на время выполения этой операции. Бутлоадер будет продолжать работать, пока страница памяти в RWW записывается или стирается. По этому поводу даже придумали прерывание SPM_RDY, которое возникает по завершении операции.

А если бутлоадер попытается записать данные в секцию NRWW (это не обязательно должна быть секция самого бутлоадера), то МК замрет на 4мс, пока производится запись.
  • +4
  • 10 мая 2011, 16:39
  • dcoder

Комментарии (19)

RSS свернуть / развернуть
На время очистки страницы (а это около 4 мс) процессор подвисает.
Совсем подвисает, т.е. все эти 4мс висит на команде SPM? А если писать из NRWW секции в RWW (или наоборот ли)?
0
  • avatar
  • Vga
  • 10 мая 2011, 16:53
Даташит сообщает нам, что
The CPU is halted during the Page Write operation.
Но я сам не проверял.
0
Кстати да, было бы неплохо поподробнее раскрыть тему NRWW / RWW
Когда первый раз писал бутлоадер долго вкуривал в эту тему — всё остальное как-то понятнее =)
+1
Запилил небольшое дополнение про разные области памяти.
0
Кстати, а как нервный вачдог на это все реагирует? Не ребутит? Или если ребутит, то что будет в памяти?
0
… вопрос конечно не по теме, но
; Действие четвертое: записываем значения ADCH:ADCL во временный буфер.
in R1, ADCH
in R0, ADCL
… как минимум протеус ругается если читать ADCH перед ADCL (если нужны все биты), да и в даташите рекомендуют читать сначала ADCL — или работает как ни крути?
0
Точно! Ты прав.
Это я сначала читал ADCL в R1, а ADCH в R0. А потом исправил криво.
0
Хорошо. А как тогда будет и будет-ли работать тот же термологер на тех процах, где есть поддержка бутлоадера? Будет ли в этом случае один единственный вариант прогаммы, в которой работает только код из бутлоадера? Или возможен вариант, когда запись во флеш можно производить из основной программы?
0
Да, придется вести запись в память из BLS секции. Но прыгнуть в эту секцию можно из основной программы.
0
А точно ли не позволяет? ЕМНИП, фуз-биты на, скажем, меге16, позволяют отдельно задавать разрешения для команд SPM/LPM для разных областей памяти, что намекает на то, что извне секции бутлоадера юзать их тоже можно.
0
Хм… lock биты на меге16 позволяют запретить SPM перезаписывать бутлоадер. Только и всего.
А в остальном, как и у всех:
SPM instruction can initiate a programming when
executing from the BLS only.

А LPM можно юзать откуда угодно.
0
Да, действительно. Спутал.
0
Кстати, хорошо бы примерчик, как организовать такую подпрограммку, которая будет как раз из BLS работать. Можно тогда и МК с поддержкой бутлоадера охватить в этой статье.
0
В очередной раз обновится программа. Подключится программатор к контроллеру, зальется программа. И все данные, которые были записаны за долгий период работы МК, сотрутся. Причем бесследно…
0
Ну это уже от радиуса кривизны рук программиста зависит. Кто мешает написать бутлоадер, который будет данные аккуратно обходить стороной? Не обязательно ведь заливать прошивку программатором.
0
… а если и заливать программатором, то можно сначала лог прочитать, а потом уже стирать все.
Хотя от ошибки типа «ой, не ту кнопку нажал» тут никакой страховки нет :)
0
а как на Avr Studio на С это сделать?
0
Найти готовую библиотеку или написать функции самостоятельно. Посмотрите appnotes для AVR вроде что-то подобное там было.
0
Здравствуйте. Очень хорошая статья, чего не скажешь о сопровождающем её коде. Написал автору письмо, но, похоже, из-за «падения» сообщества оно не дошло.
Список замеченных «очепаток»
-начальная установка адреса страницы сделана неправильно
-наращивание адреса следующей записи нужно производить с учетом занесения в буфер слова
-запись производится в неподготовленную для этого страницу
Для иллюстрации вышесказанного выложил сканы работы студии. forum.easyelectronics.ru/viewtopic.php?p=17892#p17892 Первые три работа авторской программы, следующие немного модифицированной.
0
  • avatar
  • akl
  • 01 ноября 2011, 06:52
А на Си будет?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.