AVR. Учебный курс. Стартовая инициализация
Автор DI HALT
Опубликовано 09 июля 2008
Рубрики: AVR. Учебный курс
Метки: Assembler, Мелочи, Программирование
Инициализация памяти
Мало кто подозревает о том, что при включении в оперативке далеко не всегда все байты равны 0xFF. Они могут, но не обязаны. Равно как и регистры РОН не всегда равны нулю при запуске. Обычно да, все обнулено, но я несколько раз сталкивался со случаями когда после перезапуска и/или включения-выключения питания, микроконтроллер начинал творить не пойми что. Особнно часто возникает когда питание выключаешь, а потом, спустя некоторое время, пара минут, не больше, включаешь. А всему виной остаточные значения в регистрах.
Итак, возьмите себе за правило после каждого включения, в разделе инициализации, еще даже до инициализации стека, делать зануление памяти и очистку всех регистров. Разумеется делается это все в цикле. Вот примерный вариант кода:
1 2 3 4 5 6 7 8 9 10 11 12 | RAM_Flush: LDI ZL,Low(SRAM_START) ; Адрес начала ОЗУ в индекс LDI ZH,High(SRAM_START) CLR R16 ; Очищаем R16 Flush: ST Z+,R16 ; Сохраняем 0 в ячейку памяти CPI ZH,High(RAMEND+1) ; Достигли конца оперативки? BRNE Flush ; Нет? Крутимся дальше! CPI ZL,Low(RAMEND+1) ; А младший байт достиг конца? BRNE Flush CLR ZL ; Очищаем индекс CLR ZH |
Поскольку адрес оперативки у нас двубайтный, то мы вначале смотрим, чтобы старший байт совпал с концом, а потом добиваем оставшиеся 255 байт в младшем байте адреса.
Далее убиваем все регистры от первого до последнего. Все, контроллер готов к работе.
1 2 3 4 5 | LDI ZL, 30 ; Адрес самого старшего регистра CLR ZH ; А тут у нас будет ноль DEC ZL ; Уменьшая адрес ST Z, ZH ; Записываем в регистр 0 BRNE PC-2 ; Пока не перебрали все не успокоились |
За процедурку зануления регистров спасибо Testicq
Либо значения сразу же инициализируются нужными величинами. Но, обычно, я от нуля всегда пляшу. Поэтому зануляю все.
Комментарии
20 комментариев на «AVR. Учебный курс. Стартовая инициализация»
Оставьте свой отзыв
Вы должны войти, чтобы оставлять комментарии.





я ещё помню, когда смотрел по линку на программатор на сайт Николаева, то тоже видел там раздел про “тонкости”. про использование символических имён, работу с флагами и компаратор в режиме пониженно потребления ) мелочи, а знать надо
Такой метод инициализации катит только если у тебя переменные инициируются нулями. Но такое не всгеда бывает.
Как вариант, хранить во флеше слепок начальных значений переменных и копировать их. Или просто не забывать инициировать их по мере надобности.
А чистить регистры и место для стека считаю пустой тратой тактов =) Регистры ты так или иначе будешь инициировать по ходу дела, а стек у тебя изначально нулевой и при росте перекроет любые левые значения.
Ну так а что мешает потом забить переменные нужынми значениями. Все проще чем слепок памяти держать.
По поводу регистров. Да вот вчера трахался с тем, что у меня на Tiny2323 после вырубания питания значение в регистре торчало по несколько минут!!! Врубаю МК - на тебе - мигать начинать самопроизвольно. Мигание у меня зависило от состояния регистра R20.
Сам подход, что ты полагаешь, что у тебя в регистрах есть значения по умолчанию - порочен. Сейчас ты чистишь всё что можно после сброса и это работает. Но когда тебе потребуется в ходе работы переинициализировать какой-то модуль, ты опять столкнёшься с тем, что в регистрах у тебя мусор. Каждый модуль должен сам инициализировать то что ему нужно.
Я у себя для каждого модуля пишу процедуру void xxxInit(void), которая полностью готовит модуль к работе. И это при том, что ‘C’ умеет инициировать переменные при старте.
Не забывай, что я пишу на асме у меня часто регистры юзаются как статичные переменные. Т.е. очень часто какой либо регистр юзается только для одной конкретной цели.
Знаю. Я тоже пишу на асме. Но это тебя не освобождает от инициализации переменных =)
Тут фишка такая, когда ты используешь умолчальные инициализации, ты тереяшь в понятности кода.
Сделай инициализацию явно, и уже потом, на этапе оптимизации сможешь эту строчку закомментить с комментарием “// а оно и так уже такое”. В дальнейшем избавишь себя от головной боли.
Задание начальных значений переменным должно сидеть в подсознании, на чем бы не писал. В этом отношении хороши компиляторы семейства Паскалей. Например, в Дельфи компилятор постоянно предупреждает, если какая-то переменная может перед использованием иметь неопределенное значение, (например, значение задается в куске кода или цикле, который может в некоторых случаях не выполняться. Си в этом отношении не так строг, много чего пропускает, что потом сносит башку при отладке.
Вчера я начал писать программку уже для контроллера бамперов своего робота, и в самом начале, в процедуре инициализации, сразу поставил кусок (В ходовом контроллере тоже):
if PCON.1 = 0 then // Если был сброс по питанию, Очищаем память!
begin
FSR := $20; //Начало ОЗУ
while FSR < $40 do // Первые 32 байта.
begin
INDF := 0; // Очищаем память!
Inc(FSR);
end;
PCON.1 := 1;
end;
Т.Е. по холодному старту очищаю область памяти, используемую пока в программе, далее в программе тем переменным, которые должны быть отличны от нуля, присваиваются конкретные значения. Сначала хотел сделать этот кусок на асме, но потом посмотрел, как его расписал компилятор, и решил оставить на Паскале. Вот этот кусок после компиляции:
;ROBO_SWG_2.ppas,81 :: begin
;ROBO_SWG_2.ppas,82 :: if PCON.1 = 0 then // Если был сброс по питанию, Очищаем память!
$00FA $1303 BCF STATUS, RP1
$00FB $1683 BSF STATUS, RP0
$00FC $080E MOVF PCON, 0
$00FD $00F1 MOVWF STACK_1
$00FE $3000 MOVLW 0
$00FF $18F1 BTFSC STACK_1, 1
$0100 $3001 MOVLW 1
$0101 $00F1 MOVWF STACK_1
$0102 $0871 MOVF STACK_1, 0
$0103 $3A00 XORLW 0
$0104 $1D03 BTFSS STATUS, Z
$0105 $2910 GOTO ROBO_SWG_2_L_9
$0106 $ ROBO_SWG_2_L_8:
;ROBO_SWG_2.ppas,84 :: FSR := $20; //Начало ОЗУ
$0106 $3020 MOVLW 32
$0107 $0084 MOVWF FSR
;ROBO_SWG_2.ppas,85 :: while FSR < $40 do // Первые 32 байта.
$0108 $ ROBO_SWG_2_L_12:
$0108 $3040 MOVLW 64
$0109 $0204 SUBWF FSR, 0
$010A $1803 BTFSC STATUS, C
$010B $290F GOTO ROBO_SWG_2_L_13
;ROBO_SWG_2.ppas,87 :: INDF := 0; // Очищаем память!
$010C $0180 CLRF INDF, 1
;ROBO_SWG_2.ppas,88 :: Inc(FSR);
$010D $0A84 INCF FSR, 1
;ROBO_SWG_2.ppas,89 :: end;
$010E $2908 GOTO ROBO_SWG_2_L_12
$010F $ ROBO_SWG_2_L_13:
;ROBO_SWG_2.ppas,90 :: PCON.1 := 1;
$010F $ ROBO_SWG_2_L_16:
$010F $148E BSF PCON, 1
$0110 $ ROBO_SWG_2_L_17:
;ROBO_SWG_2.ppas,91 :: end;
Всего 23 байта. Можно сделать меньше, но зачем?
При других видах сброса (например, по сторожевому таймеру) память пока не очищаю, (вдруг какие-то данные понадобятся), только задаю значения основным переменным.
я больше программлю под обычные писишки. на сях в основном.
про инициализацию в общем могу сказать так. вначале надо изучать компилятор. как и что он устанавливает при определении переменной. изучить проц - обнуляется ли в нём всё при старте?
потом все глобальные переменные определить - как надо. все те, что в функциях - смотреть внимательно.
я конечно понимаю, что тут не бывает - когда в массив 1000 на 1000 забивают мемкопи инициализирующий )
вообщем, инициализация тоже имеет в себе тонкости.
В микроконтроллерах при сбросе или холодном старте обычно задается наиболее безопасная конфигурация периферии в спецрегистрах, регистры же общего назначения и оперативка никогда не очищаются, по крайней мере в массовых типах микроконтроллеров. (Среди тех PIC, ATMEL и INTEL, про которые читал или использовал сам, таких не попадалось). Так что “Спасение утопающих - дело рук самих утопающих”. Просто должно войти в привычку не пускать ничего на самотек, все определять заранее. Сразу отпадет куча проблем, а высвободившееся при отладке время можно будет направить на повышение “интелекта” самой программы.
А разьве прерывание таймера не выполнится после выхода из прерывания INT0?
Выполнится, но мне надо было прерывание таймера внутри обработчика INT0
Слабо представляю зачем это нужно. Сколько по времени у тебя выполнялся обработчик INT0?
На обработчик INT0 у меня повешен альтернативный режим работы девайса. Так что он перехватывает на себя управление и держит его до тех пор пока не отработает всю логику.
Вот тут как раз бы и пригодились многоуровневые приоритетные прерывания. Хорошая была штука! При работе в реальном времени с информацией, которую специально для контроллера никто повторять не будет, они многое очень сильно упрощали. Например, в одной из разработок, контролировавшей процент искажений одновременно в 4 телеграфных каналах, в диалоговом режиме, с одновременным накоплением и выдачей результатов, и обслуживанием еще управляющего канала - аппарата “диспетчера”, чтобы уложиться в требуемую точность в +-1%, требовалось для худшего случая (если во время обработки прерывания от одного канала поступали прерывания от всех остальных 4), уложиться до 200мксек. Мне удалось обрабатывать самые худшие случаи максимум за 160! (на 580 процессоре с контроллером прерываний 580ВН59). Правда, пришлось поддержать аппаратно простенькой схемкой (на 555ЛП5), сравнивавшей текущее состояние с предыдущим, которое обработчик прерывания обновлял в специальном регистре, и также формировавшей запрос прерывания при каждом изменении полярности в любом из каналов. Мерялось смещение каждого перехoда в каждом знаке, с определением максимального отдельно по + и по - для каждого из каналов, с пересчетом в проценты. Тот контроллер до сих пор в работе, правда, прошивку в 573РФ5 пришлось один раз обновить (заряд стек). Сделал я его году в 86, а программа была около 3600 байт. Всего на плате могло быть установлено до 8кб(4шт РФ5 по 2к). ОЗУ было 4 кб.
Полностью согласен с мнением автора.
Может кому пригодиться…
Вот мои 10 байт кода, обнуляющие все регистры R00-R31:
P.S. Работает на любых! МК AVR.
P.P.S. Интересно - можно ли сделать еще короче?
Классно. Но я почему-то был свято убежден, что данный метод не работает. То ли у меня студия глюкнула и не показывала изменения регистров то ли еще что. Щас и не вспомню, но почему то тогда у меня с регистрами цикл не прокатил. Щас проверил - все ок.
Мне пригодилось, уже применяю, проверил, работает. Круто спасибо.
Гонял код чистки SRAM, и заметил что она чистится не до конца, память по адресу RAMEND остается неизменной.
По моему это связано этим:
ST Z+,R16 // в Z=RAMEND-1, ячейку по адресу Z зачистили и cделали инкримент Z,
…
CPI ZL,Low(RAMEND) // Z=RAMEND, проверили и опа вышли из цикла, а ячейку с адресом
// RAMEND не почистили
правильно будет так
CPI ZL,Low(RAMEND+1)
Хм, действительно…