|
Теги статьи: | RS-485AVR |
RS-485 ATmega BootLoader
Автор: dimmer, cd1381@mail.ru
Опубликовано 21.09.2016
Создано при помощи КотоРед.
Участник Конкурса "Поздравь Кота по-человечески 2016!"
С Вашего позволения хочу предложить ещё один вариант реализации перепрошивки устройств, собранных на МК AVR.
Имеется: некоторое количество неких устройств, расположенных в различных, возможно труднодоступных местах, на некотором отдалении от вашего местонахождения (дальше вытянутой руки). Все они, разумеется, должны быть подключены к некоторой линии связи. Требуется: не имея физического доступа к устройству, совершая минимум телодвижений, считывать и записывать содержимое их Flash и EEPROM памяти, проще говоря - обновлять прошивку.
Сразу нужно сказать, что кроме собственно программы в самом девайсе, как минимум требуется программа на компе, и немного железа в виде аппаратной части интерфейса (предполагается RS-485, у нас ведь несколько девайсов). На стороне меги должен стоять преобразователь USART - RS485 (или какого-нибудь другого интерфейса), например микросхема ADM485ANZ фирмы AD, или что-нибудь от фирмы Maxim. На стороне компьютера, при наличии COM порта можно использовать например преобразователь HB-485A ,или при отсутствии оного - виртуальный COM порт и преобразователь USB - RS485. Физически линия связи представляет собой одну витую пару, к которой подключен компьютер и все девайсы, т.е. не требуется отдельно прокладывать линию связи от каждого девайса до компьютера. Для расстояний порядка 1км можно задействовать одну пару в кабеле типа UTP или STP (интернет таким проводят) или на небольших расстояниях (200 - 300м) кабель КСПВ.
Так как у нас две программы ведут обмен данными, необходимы определённые правила, по которым этот самый обмен и происходит, то есть - протокол обмена. В первую очередь задаём механизм выбора девайса (не забываем, что к линии подключено некое их количество). Атмелевские разработчики всё это дело продумали, за что им большое человеческое спасибо, и у всех ATmegовских USARTов есть режим мультипроцессорного обмена (MPCM). Подробно о модуле USART и режиме MPCM написано в любом справочнике по микроконтроллерам AVR. Таким образом, каждому устройству назначается адрес, и ведущее устройство (компьютер) начинает сеанс обмена данными с передачи адресного кадра. Микроконтроллер, адрес которого совпал с адресом, посланным ведущим, начинает принимать кадры, содержащие данные. Модули USART остальных девайсов игнорируют кадры данных, ожидая следующего адресного кадра. Используется режим передачи 9-битных данных, признаком адресного кадра является установленный в 1 старший бит кадра.
Максимальный размер кадра у винды - 8 бит, но по удачному стечению обстоятельств есть ещё бит паритета, который передаётся в конце кадра, т.е. после 8-го бита, ну а если после восьмого, то он по-любому будет девятым. И не менее удачно то, что мелкомягкие предусмотрели возможность манипулировать этим битом вручную. Таким образом мы получаем возможность удалённого доступа к любому устройству, подключенному к линии связи, не имея физического доступа к самому устройству.
Итак, начнём с программы-загрузчика на стороне микроконтроллера. Семейство контроллеров mega довольно-таки большое, и написать программу для всех немного утомительно, да и наверное не нужно. Писал я её для себя, поэтому ограничился мегами, которые выпускаются в корпусах DIP. Ну не стремлюсь я к миниатюризации, размеры квартиры позволяют, да и глаза уже не те.
Список получился такой:
Вполне может так случиться, что ваша модель не попала в этот список. Поэтому вы, ознакомившись с исходником программы и комментариями, возможно избыточными с точки зрения опытных программистов, но надеюсь достаточными для того, что бы люди, не имеющие пока достаточного опыта, смогли бы адаптировать прогу под себя (или под свою модель контроллера).
Вполне естественно, что основная программа девайса должна соответствовать определённым требованиям. Она должна уметь передавать управление загрузчику, получив соответствующий запрос по линии связи. Если основная программа ведёт какой-либо обмен с компом своими данными, то в протоколе обмена нужно зарезервировать возможность распознавания кода передачи управления загрузчику. Если своего обмена данными не предусмотрено, то программа должна просто сконфигурировать USART таким образом, чтобы модуль слушал линию связи и при получении запроса осуществлял переход по адресу программы-загрузчика.
На стороне ведущего, т.е. компьютера в обмене участвует программа BootLoader, написанная на С++. Программа не требует установки, легко поддаётся обучению, добавить недостающую модель меги можно просто имея перед глазами соответствующий апнотовский файл. Желаемый порт и скорость обмена просто вписываются в конфигурационный файл с неожиданным названием config.cfg а имена ваших девайсов с адресами в файл items.cfg, формат записей можно посмотреть в самих файлах, в папке с программой, и сделать похоже.
Поскольку загрузчик в памяти девайса находится не один, а вместе с основной программой, давайте сначала рассмотрим мероприятия, обеспечивающие безболезненное сосуществование этих двух софтин.
Для успешного ассемблирования загрузчика вместе с основной программой (ОП), в этой самой ОП должны быть определены:
.equ MCU_CLOCK = 8000000
.equ NET_ADDR = 128
.equ BAUDRATE = 9600
Два необязательных значения: btlUSARTport и btlUSARTpin, определяющие ногу микроконтроллера, которая в приёмопередатчике RS-485 управляет режимом приём/передача, примерно так:
.equ btlUSARTport = PORTB
.equ btlUSARTpin = PB2
Значения btlUSARTport и btlUSARTpin не требуется определять, если вы используете:
Если, микроконтроллер имеет два модуля USART, необходимо также явно указать, который из них используется для связи с компьютером, например:
#define USART1
Основной программе, в свою очередь, нужно «знать» адрес, по которому в памяти располагается программа-загрузчик. Этот адрес (BOOTSTART) определяется в файле bootloader2.inc, который нужно подключить к ОП директивой include после строки подключения файла из папки Appnotes. Также к ОП требуется подключить файл homebus.inc нём прописаны управляющие коды загрузчика. Все эти файлы есть в архиве. И последнее, если вы ассемблируете ОП для первой загрузки в чистый контроллер, то последней строкой вашего исходника нужно подключить файл с текстом загрузчика:
.include "mbtl2.asm"
Итак, исходник загрузчика. При написании программ подобного типа, нужно помнить о том, что их текст фактически дописывается к тексту основной программы (конечно, можно ассемблировать ОП и загрузчик отдельно, и затем склеивать hex-файлы, но это быстро утомляет). По этой причине надо свести к минимуму возможные конфликты, и учесть некоторые не совсем очевидные моменты, но обо всём по порядку.
Присвоение регистрам имён при помощи директивы препроцессора #define предотвращает конфликт имён ОП с именами регистровых переменных загрузчика. Далее, определённые неудобства вызывает то, что программа поддерживает 13 различных контроллеров семейства Mega. Приходиться учитывать различие в именах портов, способах обращений к портам, и некоторых других особенностях каждой модели. Чтобы немного упростить жизнь, в файле bootloader2.inc определяются псевдопеременные (mega8, mega16 и т.д.) на основании модели контроллера, указанной в аппнотовском файле. Далее в программе эти псевдопеременные используются в директивах условной трансляции.
После определения имён регистров следует присвоение имён портам и битам портов USART в зависимости от модели контроллера, и модуля USART в моделях с двумя этими самыми модулями.
Макрокоманды setTimer и decTimer производят соответственно загрузку начального значения и декремент программного таймера/счетчика, позволяющего избежать зависания загрузчика при обрыве соединения с компьютером. Команды RS485_transmit и RS485_receive переключают режим приём/передача преобразователя интерфейса RS-485, если в этом есть необходимость. И наконец, MPCM_ON и MPCM_OFF соответственно включают и выключают мультипроцессорный режим работы модуля USART.
В области SRAM выделяем буфер для хранения данных, принятых от компьютера, и предназначенных для записи в память Flash или EEPROM.
Сегмент кода начинается с адреса BOOTSTART, определённого в файле bootloader2.inc, обратите внимание, что этот адрес никак не связан с константами FIRST-, SECONDBOOTSTART и т.д. Начальный адрес загрузчика выровнен по началу страницы Flash-памяти, что позволяет максимально использовать адресное пространство.
Поскольку в загрузчик мы попадаем из некой абстрактной основной программы, мы не имеем ни малейших понятий о задействованных прерываниях, режимах работы различных модулей и внешних подключениях. Поэтому первым делом запрещаем все прерывания.
.cseg
.org BOOTSTART
cli
Мы не знаем как, куда и что подключено, а раз не знаем – ничего не трогаем (может там выхода на лампочки в прихожей, а может управление стержнями ядерного реактора).
Делаем себе две нулевые регистровые константы, поможет сэкономить несколько байтов в будущем.
clr Lnull
clr Hnull
Если требуется, конфигурируем порт и ногу для управления драйвером RS-485.
;USART configuration
.if defined( btlUSARTport ) && defined( btlUSARTpin )
cbi btlUSARTport,btlUSARTpin
sbi btlUSARTport - 1,btlUSARTpin
.else
.warning "Control for RS-485 transceiver not defined"
.endif
В общем случае в загрузчик мы попадаем с неизвестными настройками USART, поэтому позаботимся и об этом.
;Transmitter enable, receiver enable,9 bit, parity none, one stop bit
outi UCSRxB,(1<<TXEN0)|(1<<RXEN0)|(1<<UCSZ02)
#if (mega8 || mega16 || mega162 || mega32)
outi UCSRxC,(1<<URSEL0)|(1<<UCSZ01)|(1<<UCSZ00)
#else
outi UCSRxC,(1<<UCSZ01)|(1<<UCSZ00)
#endif
;set baud rate
outi UBRRxL,low(MCU_CLOCK / BAUDRATE / 16 - 1)
outi UBRRxH,high(MCU_CLOCK / BAUDRATE / 16 - 1)
Смешной префикс меток (b_) применён для минимизации вероятности совпадения с именами меток ОП.
clr flags
ldi bitMPCM,(1<<MPCM0)
b_main:
outi SPL,low(RAMEND)
outi SPH,high(RAMEND)
rcall b_delay10ms
RS485_receive
setTimer 8000
Очищаем регистр программных флагов, инициализируем регистровую константу bitMPCM (также поможет экономить память), поднимаем стек, USART на приём, и взводим программный таймер примерно на 8 секунд.
Далее готовимся к получению пакета (фрейма) состоящего из:
;Receive MPCM Frame (netAddr + 3 data bytes)
b_FrameReceive:
cbr flags,(1<<fnetAddr)|(1<<fdataByte0)|(1<<fdataByte1)
Сбрасываем:
MPCM_ON
Включаем мультипроцессорный режим USART, и ждём приёма байта.
b_getNextByte:
rcall b_ByteReceive
brne b_FrameReceive
Если при выходе из подпрограммы приёма байта не установлен бит Z регистра SREG, значит, произошла ошибка кадра.
sbrc flags,fdataByte1
rjmp b_getDataByte2
sbrc flags,fdataByte0
rjmp b_getDataByte1
sbrc flags,fnetAddr
rjmp b_getDataByte0
В зависимости от состояния флагов регистра flags, осуществляется переход на нужный блок обработки.
;get net address
brtc b_FrameReceive
cpi mfr,NET_ADDR
brne b_FrameReceive
sbr flags,(1<<fnetAddr)
MPCM_OFF
rjmp b_getNextByte
b_getDataByte0:
brts b_FrameReceive
mov dataByte0,mfr
sbr flags,(1<<fdataByte0)
rjmp b_getNextByte
b_getDataByte1:
brts b_FrameReceive
mov dataByte1,mfr
sbr flags,(1<<fdataByte1)
rjmp b_getNextByte
b_getDataByte2:
brts b_FrameReceive
mov dataByte2,mfr
MPCM_ON
Обратите внимание на то, что после успешного приёма адресного байта, нужно выключить MPCM, а после приёма всего фрейма режим опять включаем. В бит T регистра SREG подпрограммой b_ByteReceive копируется девятый бит кадра (признак адресного байта). Проверка его на соответствующее значение – это дополнительная линия обороны от всякого рода сбоев, помех и прочего мусора в линии связи, мы все-таки прошивку меняем, а не пиццу заказываем.
После успешного приёма фрейма следует переход на обработчик команды, код которой находится в регистре dataByte0.
rcall b_delay10ms
cpi dataByte0,GET_SIGNATURE
breq b_GetSignature
cpi dataByte0,FLASH_READ
breq b_ReadFlash
cpi dataByte0,FLASH_WRITE
breq near_b_WriteFlash
cpi dataByte0,EEPROM_READ
breq b_ReadEEPROM
cpi dataByte0,EEPROM_WRITE
breq b_WriteEEPROM
Два слова о паузах в 10мс, они встречаются и далее в программе. В принципе без них всё почти работает, только очень уж как-то нервно. Если их убрать, то примерно каждая пятая попытка что-либо записать или прочитать заканчивается зависанием, и последующим завершением работы загрузчика по программному таймеру. Вставка таких пауз в ключевых точках повышает стабильность программы почти до небес.
b_wrongCommand:
ldi dataByte0,WRONG_COMMAND
rcall b_FrameTransmit
rjmp b_FrameReceive
Если поиск совпадения ничего не дал, отправляем компьютеру вялое ругательство и слушаем эфир дальше.
Следующие две строки из-за коротких рук инструкций семейства branch.
near_b_WriteFlash:
rjmp b_WriteFlash
Каждой модели микроконтроллера ставится в соответствие трёхбайтный идентификатор (сигнатура), определённый в аппнотовском файле. Запрос компьютером сигнатуры, и её передача контроллером в ответ, во-первых является признаком успешного установления соединения, и во-вторых позволяет компьютеру определить модель контроллера. Все эти мероприятия направлены на повышения общей стабильности работы всей системы: компьютер, линия связи, контроллер.
b_GetSignature:
ldi dataByte0,SIGNATURE_000
ldi dataByte1,SIGNATURE_001
ldi dataByte2,SIGNATURE_002
rcall b_FrameTransmit
rjmp b_main
Три байта сигнатуры отправляем в подпрограмму передачи фрейма.
При получении запроса контроллер сначала подтверждает свою готовность обработать команду путем обратной пересылки фрейма.
b_ReadEEPROM:
rcall b_FrameTransmit
И далее, после паузы передаёт содержимое памяти EEPROM
rcall b_delay10ms
movw pointer,Hnull:Lnull
movw Hcsum:Lcsum,Hnull:Lnull
b_putNexteByte:
rcall b_EEPROMread
sub Lcsum,mfr
sbc Hcsum,null
rcall b_ByteTransmit
adiw pointer,1
cpi Hpointer,high(EEPROMEND + 1)
brne b_putNexteByte
cpi Lpointer,low(EEPROMEND + 1)
brne b_putNexteByte
и два байта контрольной суммы.
b_putControlSum:
mov mfr,Lcsum
rcall b_ByteTransmit
mov mfr,Hcsum
rcall b_ByteTransmit
rjmp b_main
Контрольная сумма получается последовательным вычитанием байт данных из пары регистров Hcsum:Lcsum. На приёмной стороне проверка производится сложением контрольной суммы и суммы всех байт данных, если получился 0, то передача считается успешной. Участок кода с меткой b_putControlSum используется также обработчиком чтения Flash-памяти. При переходе на метку b_main, выдерживается пауза 10 мс перед переводом драйвера RS-485 в режим приёма, для того, что бы модуль USART успел завершить передачу.
В отличие от EEPROM чтение и передача Flash-памяти осуществляется постранично. После приёма фрейма команды, в параметре (паре dataByte1:dataByte2) содержится номер страницы, которую хочет получить компьютер. Для начала всё-же лучше убедиться, что запрашиваемая страница существует:
b_ReadFlash:
ldi mfr,high((FLASHEND + 1)/PAGESIZE)
cpi dataByte2,low((FLASHEND + 1)/PAGESIZE)
cpc dataByte1,mfr
brsh b_wrongRange
Если компьютер требует страницу не из параллельной реальности, то подтверждаем корректность запроса, и после паузы передаём блок данных с контрольной суммой.
rcall b_FrameTransmit
rcall b_delay10ms
movw Hcsum:Lcsum,Hnull:Lnull
ldi Liter,low(PAGESIZE*2)
rcall b_calcFlashPointer
Подпрограмма b_calcFlashPointer по номеру страницы вычисляет её начальный адрес в памяти.
b_putNextfByte:
.if FLASHEND > 0x7FFF ; > 64Kb
elpm mfr,pointer+
.else
lpm mfr,pointer+
.endif
sub Lcsum,mfr
sbc Hcsum,null
rcall b_ByteTransmit
dec Liter
brne b_putNextfByte
rjmp b_putControlSum
В контроллерах с объёмом памяти более 64Kb используем инструкцию elpm.
Далее следуют строки передачи ошибки WRONG_RANGE, причем передаётся и тот самый некорректный номер страницы.
b_wrongRange:
ldi dataByte0,WRONG_RANGE
rcall b_FrameTransmit
rjmp b_main
Параметром данной команды является размер блока данных, предназначенных для записи в EEPROM. Размер данных естественно не должен превышать размера EEPROM, это условие проверяется в первую очередь. Некоторое ущемление прав контроллеров ATmega8515 и ATmega8535 вызвано тем, что только у этих моделей размеры SRAM и EEPROM памяти совпадают и равны 512 байт. При загрузке блока данных такого размера он разрушает область стека, что неизбежно приведет к непредсказуемым последствиям. Поэтому максимальный размер данных для записи EEPROM у этих моделей не должен превышать 508 байт (четырёх байт достаточно для вложенного вызова одной подпрограммы из другой).
Это в какой-то степени является недостатком программы, но скажем так, не очень часто приходится иметь дело с необходимостью загрузки полностью всех адресов EEPROM у контроллеров mega8515 и mega8535.
b_WriteEEPROM:
#if defined(_M8515DEF_INC_) || defined(_M8535DEF_INC_)
ldi mfr,high(EEPROMEND - 2)
cpi dataByte2,low(EEPROMEND - 2)
#else
ldi mfr,high(EEPROMEND + 2)
cpi dataByte2,low(EEPROMEND + 2)
#endif
cpc dataByte1,mfr
brsh b_wrongRange
Количество требуемых байт перегружаем в пару Hiter:Liter и вызываем подпрограмму b_DataReceive, которая отсылает компьютеру запрос данных, и загружает в буфер SRAM принятый блок данных.
movw Hiter:Liter,dataByte1:dataByte2
rcall b_DataReceive
При выходе из b_DataReceive в паре регистров index, находится адрес, на единицу больший адреса (в SRAM буфере) последнего принятого байта. Запись данных в EEPROM ведётся от конца к началу буфера. Никакого философского смысла здесь искать не нужно, просто так удобнее инициализировать исходные регистры для цикла записи.
movw pointer,dataByte1:dataByte2
b_writeByte:
sbiw pointer,1
brcs b_completeOK
rcall b_EEPROMread
ld storeByte,-index
cp storeByte,mfr
breq b_writeByte
Предварительно читаем байт из EEPROM и сравниваем с байтом из буфера, если они равны, то наверное перезаписывать данный байт вроде как не нужно. Далее опять немного условной трансляции, всё из-за разных имён битов регистра EECR.
out EEDR,storeByte
#if (mega8 || mega16 || mega162 || mega32)
sbi EECR,EEMWE
sbi EECR,EEWE
#else
sbi EECR,EEMPE
sbi EECR,EEPE
#endif
rcall b_EEPROMread
cp storeByte,mfr
breq b_writeByte
И после записи проверяем полученный результат, если ошибка – сообщаем компьютеру адрес байта, при записи которого случилась неприятность.
b_writeErr:
ldi dataByte0,WRITE_ERROR
movw dataByte1:dataByte2,Hpointer:Lpointer
rjmp b_endWrite
Если запись данных завершена успешно, сообщаем об этом компу, пусть тоже порадуется.
b_completeOK:
ldi dataByte0,COMPLETE
b_endWrite:
rcall b_FrameTransmit
rjmp b_tryGoToAppl
И делаем попытку вернуться в основную программу.
Ну вот и добрались до того, ради чего всё собственно и затевалось. Как и чтение, запись Flash-памяти производится постранично, начиная с нулевой страницы. Параметром данной команды (dataByte1:dataByte2) является номер последней страницы. Для начала проверяем, что код прошивки не коррумпирует адреса расположения загрузчика.
b_WriteFlash:
ldi mfr,high(BOOTSTART/PAGESIZE)
cpi dataByte2,low(BOOTSTART/PAGESIZE)
cpc dataByte1,mfr
brsh b_wrongRange
Номер последней страницы сохраняем в паре регистров HlastPage:LlastPage, освободившуюся пару dataByte1:dataByte2 используем для хранения номера текущей страницы. Запрашиваем, принимаем и сохраняем данные в буфере, вызывая подпрограмму b_DataReceive.
movw HlastPage:LlastPage,dataByte1:dataByte2
movw dataByte1:dataByte2,Hnull:Lnull
b_cycleWriteFlash:
ldi Liter,low(PAGESIZE*2)
ldi Hiter,high(PAGESIZE*2)
rcall b_DataReceive
Далее с помощью подпрограммы b_cpPageAndBuff сравниваем содержимое SRAM буфера и текущую Flash страницу. Если они совпадают, то и переписывать ничего не нужно, переходим к следующей странице.
rcall b_cpPageAndBuff
breq b_nextPage
В противном случае начинаем программирование страницы Flash-памяти.
Сначала стираем страницу.
rcall b_calcFlashPointer
ldi modeSPM,(1<<PGERS) + 1 ;1 as (1<<SPMEN) or (1<<SELFPRGEN)
rcall b_executeSPM
ldi modeSPM,(1<<RWWSRE) + 1
rcall b_executeSPM
Процедура стандартная, взята из мануалов AVR, единственная неприятность – различные имена у одних и тех же битов и регистров в разных моделях контроллеров. Я не знаю для чего это сделано, и какой в этом смысл, но как говорится – если у кого-нибудь, с твоей точки зрения, отсутствует логика, значит у него своя логика. В данном случае бит с именем SPMEN или SELFPRGEN является самым младшим, и соответственно имеет вес 1.
Загружаем содержимое SRAM буфера в специальный буфер данных модуля самопрограммирования контроллера.
ldi Liter,PAGESIZE
ldi modeSPM,1 ;1 as (1<<SPMEN) or (1<<SELFPRGEN)
ldi Lindex,low(vbBuffer)
ldi Hindex,high(vbBuffer)
b_loadNextWord:
ld LbyteSPM,index+
ld HbyteSPM,index+
rcall b_executeSPM
adiw pointer,2
dec Liter
brne b_loadNextWord
Восстанавливаем значение начального адреса страницы, и активируем процесс записи.
rcall b_calcFlashPointer
ldi modeSPM,(1<<PGWRT) + 1
rcall b_executeSPM
Дожидаемся окончания записи.
ldi modeSPM,(1<<RWWSRE) + 1
rcall b_executeSPM
Проверяем записанную страницу, и если всё хорошо, переходим к следующей. Иначе устанавливаем флаг ошибки Flash-памяти, и передаём фрейм с кодом WRITE_ERROR и адресом злополучного байта.
rcall b_cpPageAndBuff
breq b_nextPage
sbr flags,(1<<fFlashErr)
sbiw pointer,1
rjmp b_writeErr
b_nextPage:
rcall b_delay10ms
subi dataByte2,low(-1)
sbci dataByte1,high(-1)
cp LlastPage,dataByte2
cpc HlastPage,dataByte1
brsh b_cycleWriteFlash
После записи всех нужных страниц сбрасываем флаг ошибки, и говорим компьютеру OK.
cbr flags,(1<<fFlashErr)
rjmp b_completeOK
Два слова о флаге fFlashErr, он предотвращает переход из загрузчика в возможно неработоспособную прошивку, если при её записи возникла ошибка. Вы наверное уже обратили внимание на то, что некоторые строки программы являются вообще-то необязательными. Проверки адресов, номеров страниц, требование точного соблюдения протокола связи, всё это для повышения стабильности работы программы. Переход в потенциально разрушенную прошивку опасен, контроллер может просто зависнуть, и мы уже не достучимся до него. А если в агонии он будет дрыгать ногами? А ведь мы даже не знаем что к ним привязано. Оставив управление загрузчику, мы ещё можем попытаться исправить ситуацию повторной записью прошивки.
В подпрограмме исполнения инструкции spm, реализован, для примера, ещё один способ решения проблемы с именами. В процессе написания данной программы я уже свыкся с этой неприятностью, и пожалуй теперь, смотрю на неё, как на ещё одно доказательство несовершенства этого мира.
b_executeSPM:
.ifndef SPMCR
.equ SPMCR = SPMCSR
.endif
.ifndef SPMEN
.equ SPMEN = SELFPRGEN
.endif
in mfr,SPMCR
sbrc mfr,SPMEN
rjmp b_executeSPM
out SPMCR,modeSPM
spm
ret
Далее, уже упоминавшаяся подпрограмма сравнения по содержимому страницы Flash и буфера SRAM. Флаг Z регистра SREG, при выходе передаёт результат сравнения. Об использовании инструкции wdr поговорим позже.
b_cpPageAndBuff:
wdr
ldi Liter,low(PAGESIZE*2)
ldi Lindex,low(vbBuffer)
ldi Hindex,high(vbBuffer)
rcall b_calcFlashPointer
b_cpBytes:
.if FLASHEND > 0x7FFF ; > 64Kb
elpm storeByte,pointer+
.else
lpm storeByte,pointer+
.endif
ld mfr,index+
cp mfr,storeByte
brne b_exitCompare
dec Liter
brne b_cpBytes
b_exitCompare:
ret ;if fZ=1, page == vbBuffer
Подпрограмма b_calcFlashPointer вычисляет стартовый адрес страницы Flash-памяти по её номеру, находящемуся в паре регистров dataByte1: dataByte2, и помещает его в пару Hpointer:Lpointer. Стоит ли упоминать, что алгоритм вычисления зависит от модели контроллера.
b_calcFlashPointer:
clr Lpointer
mov Hpointer,dataByte2
.if FLASHEND <= 0x0FFF ; <= 8Kb
lsr Hpointer
ror Lpointer
lsr Hpointer
ror Lpointer
.elif FLASHEND <= 0x3FFF ; <= 32Kb
lsr Hpointer
ror Lpointer
.elif FLASHEND > 0x7FFF ; > 64Kb
out RAMPZ,dataByte1
.endif
ret
При вызове подпрограммы b_DataReceive в паре Hiter:Liter должен находиться размер блока данных, который требуется принять и разместить в SRAM-памяти. Параметр фрейма запроса (dataByte1:dataByte2) трактуется компьютером как номер запрашиваемой страницы при записи Flash-памяти, или как количество байт при записи EEPROM.
Взводим программный таймер, сохраняем размер данных.
b_DataReceive:
setTimer 8000
movw storeHiter:storeLiter,Hiter:Liter
Следующая строка – вход для попытки запросить и принять данные, попытки возможно не первой, поэтому восстанавливаем сохранённую пару Hiter:Liter. Выключаем мультипроцессорный режим, компьютер при передаче блока данных не предваряет его адресным байтом (кто запросил, тот и пусть и принимает). Готовим регистры Hcsum:Lcsum к подсчету контрольной суммы и посылаем запрос.
b_tryDataReceive:
movw Hiter:Liter,storeHiter:storeLiter
MPCM_OFF
movw Hcsum:Lcsum,Hnull:Lnull
ldi dataByte0,REQUEST_DATA
rcall b_FrameTransmit
Драйвер RS-485 переключаем на приём, указатель index ставим на начало буфера.
RS485_receive
ldi Lindex,low(vbBuffer)
ldi Hindex,high(vbBuffer)
И принимаем данные, подсчитывая попутно сумму всех байт.
b_getNextdByte:
rcall b_ByteReceive
brne b_byteErr
brts b_byteErr
add Lcsum,mfr
adc Hcsum,null
st index+,mfr
sbiw Hiter:Liter,1
brne b_getNextdByte
Вслед принимаем два байта контрольной суммы (младшим байтом вперёд) и складываем с подсчитанной суммой всех байт данных. При ненулевом результате, считается, что при передаче данных произошла ошибка и делаем ещё попытку, иначе выходим из подпрограммы.
rcall b_ByteReceive
brne b_byteErr
brts b_byteErr
mov storeByte,mfr
rcall b_ByteReceive
brne b_byteErr
brts b_byteErr
add storeByte,Lcsum
adc mfr,Hcsum
brne b_tryDataReceive
MPCM_ON
ret
Число повторных попыток ограничено временем, отсчитываемым программным таймером/счетчиком.
Далее следует участок кода, на который передаётся управление при обнаружении ошибки приёма кадра. Запрещаем работу модуля USART, и выдерживаем паузу, длительностью немного большей, чем нужно компьютеру для передачи самого длинного блока данных (данных для EEPROM).
b_byteErr:
outi UCSRxB,(1<<UCSZ02)
.set W_ITER =(EEPROMEND+1)*11/BAUDRATE +1
ldi mfr,W_ITER
mov w_iter,mfr
ldi mfr,100
rcall b_delay10ms
dec mfr
brne pc-2
dec w_iter
brne pc-5
На всякий случай очищаем приёмный буфер USART, разрешаем работу этого модуля, и делаем ещё попытку получить порцию данных.
#if (mega8 || mega16 || mega162 || mega32)
in mfr,UDRx
sbic UCSRxA,RXC0
rjmp pc-2
#else
lds mfr,UDRx
lds mfr,UCSRxA
sbrc mfr,RXC0
rjmp pc-3
#endif
outi UCSRxB,(1<<TXEN0)|(1<<RXEN0)|(1<<UCSZ02)
rjmp b_tryDataReceive
Подпрограмма приёма байта b_ByteReceive является единственным местом загрузчика, где возможно зацикливание и зависание контроллера при ошибке соединения или при обрыве соединения. Так как изначально было решено не использовать прерывания по причинам, изложенным ранее, в загрузчике реализован программный таймер/счетчик. В каждом цикле опроса состояния бита RXC производится декремент этого счетчика, и при окончании времени происходит выход из цикла (на метку b_tryGoToAppl).
b_ByteReceive:
decTimer
wdr
brcs b_tryGoToAppl ;throw "time overflow"
Теперь, как обещал, по поводу инструкции wdr. В первой версии загрузчика собачий таймер просто отключался при входе в программу. Но как вы знаете, его также можно установить фузами как постоянно включенный, поэтому в новой версии было решено вставить код сброса собаки в те точки программы, в которых возможны потери времени.
Далее проверяем возможные ошибки, девятый бит кадра сохраняем в бите T регистра SREG, принятый байт считываем в регистр mfr.
#if (mega8 || mega16 || mega162 || mega32)
sbis UCSRxA,RXC0
rjmp b_ByteReceive
in mfr,UCSRxA
andi mfr,(1<<FE0)|(1<<DOR0)
in mfr,UCSRxB
bst mfr,RXB80
in mfr,UDRx
#else
lds mfr,UCSRxA
sbrs mfr,RXC0
rjmp b_ByteReceive
lds mfr,UCSRxA
andi mfr,(1<<FE0)|(1<<DOR0)
lds mfr,UCSRxB
bst mfr,RXB80
lds mfr,UDRx
#endif
ret ;return successfully if flag Z=1
Далее фрагмент кода, делающий попытку перейти в основную программу прошивки, при установленном флаге ошибки записи Flash-памяти загрузчик перезапускается.
b_tryGoToAppl:
sbrc flags,fFlashErr
rjmp b_main
.if FLASHEND > 0x0FFF
jmp RWW_START_ADDR
.else
rjmp RWW_START_ADDR
.endif
Две следующие подпрограммы b_FrameTransmit и b_ByteTransmit осуществляют передачу фрейма и одного байта соответственно.
;Transmit Frame (NET_ADDR + 3 data bytes)
b_FrameTransmit:
RS485_transmit
ldi mfr,NET_ADDR
rcall b_ByteTransmit
mov mfr,dataByte0
rcall b_ByteTransmit
mov mfr,dataByte1
rcall b_ByteTransmit
mov mfr,dataByte2
rcall b_ByteTransmit
rcall b_delay10ms
ret
Пауза в конце гарантирует завершение передачи до перевода преобразователя интерфейса в режим приёма.
b_ByteTransmit:
wdr
#if (mega8 || mega16 || mega162 || mega32)
sbis UCSRxA,UDRE0
rjmp pc-1
out UDRx,mfr
#else
lds mfr,UCSRxA
sbrs mfr,UDRE0
rjmp pc-2
sts UDRx,mfr
#endif
ret
Подпрограмма чтения байта из области памяти EEPROM стандартная, только вот у контроллера mega48 регистр EEARH отсутствует по понятным причинам.
b_EEPROMread:
#if (mega8 || mega16 || mega162 || mega32)
sbic EECR,EEWE
#else
sbic EECR,EEPE
#endif
rjmp pc-1
#if !mega48
out EEARH,Hpointer
#endif
out EEARL,Lpointer
sbi EECR,EERE
in mfr,EEDR
ret
И наконец, подпрограмма паузы длительностью примерно 10 мс. Число итераций подсчитывается исходя из тактовой частоты.
b_delay10ms:
.set D_ITER = MCU_CLOCK/400
wdr
ldi d_iter1,byte1(D_ITER)
ldi d_iter2,byte2(D_ITER)
sbiw d_iter2:d_iter1,1
brne pc-1
ret
На случай, если вашей прошивке не хватает пары десятков байт памяти, есть версия загрузчика мини (файлы mbtl2m.asm и bootloader2m.inc). Она умеет только производить загрузку прошивки во Flash-память, и занимает менее 512 байт (версия "не мини" около 640 байт), размер зависит от модели контроллера.
Обе версии проверялись на тестовой плате, на контроллере ATmega16. На стороне контроллера использовался драйвер интерфейса RS-485 ADM485ANZ, на стороне компьютера конвертер RS232-RS485 HB-485A и программа BootLoader v2.2.
Со стороны компьютера обмен данными ведётся программой BootLoader. Программа не требует установки, необходимо только произвести настройку требуемых режимов работы. Все изменения осуществляются редактированием конфигурационных файлов. В файле config.cfg нужно указать имя порта, через который планируется вести обмен данными с микроконтроллерами, и одну из стандартных скоростей обмена. В файле items.cfg нужно перечислить названия и адреса устройств, с которыми этот самый обмен и будет происходить. Файлы должны располагаться в папке программы, их формат нужно сохранить таким же как в примерах.
Пример файла config.cfg
Port=COM1
Baudrate=9600
Пример файла items.cfg
UPS PLC=0x01
Light PLC=0x02
Guard PLC=0x03
Fire PLC=0x04
Power PLC=0x05
Test Board=0x10
Пусть в квартире (доме) есть контроллеры освещения, охранная и пожарная сигнализация, схема слежения за сетевым напряжением и умный бесперебойник.
Адреса, естественно указываются те, которые вы назначили устройствам в прошивках (константа NET_ADDR). Имена устройств будут отображаться в выпадающем списке, в окне Device программы.
При выборе устройства из этого списка на линию связи выдаётся последовательность из четырёх байт (фрейм):
Первым передаётся адрес взятый из файла items.cfg соответствующий тому имени девайса, который был выбран. Байт адреса передаётся в кадре с установленным девятым битом, что для модуля USART микроконтроллера, работающего в режиме мультипроцессорного обмена, является признаком передачи адресного байта. Основная программа (ОП) устройства должна уметь распознать, следующий за адресным байтом, байт кода команды GOTO_BOOTLOADER (0xFF), и передать управление загрузчику, получив такую команду. Компьютер передаёт, на всякий случай, максимум 4 таких фрейма с паузами около 40 мс, так что при хорошей погоде второй фрейм принимает уже не ОП, а загрузчик. Для него код 0xFF соответствует команде запроса сигнатуры контроллера (GET_SIGNATURE).
Не получив ответ после четырёх запросов компьютер считает устройство недоступным.
Программа-загрузчик получив запрос GetSignature передаёт фрейм, состоящий также из четырёх байт - байта собственного адреса и трёх байт сигнатуры контроллера.
Так выглядит распечатка обмена, полученная с помощью программы-монитора COM порта (адрес девайса 0x10):
Запрос:02.08.2016 0:20:12.91964 (+86.7969 seconds)
10 FF 00 00 10 FF 00 00 .ÿ...ÿ..
Ответ:02.08.2016 0:20:12.99764 (+0.0156 seconds)
10 1E 94 03 ...
В данном случае загрузчик передаёт сигнатуру 1E 94 03, что соответствует модели микроконтроллера ATmega16. Получив сигнатуру, компьютер в папке AVR_signatures ищет соответствующий файл (1E9403.cfg), в котором указаны необходимые для работы параметры контроллера.
Файл 1E9403.cfg
DEVICE=ATmega16
FLASHEND=0x1fff
EEPROMEND=0x01ff
PAGESIZE=64
Наименование модели контроллера отображается в окне программы.
Программа-загрузчик устройства ожидает следующую команду в течении примерно 8 секунд, затем возвращает управление в основную программу. Поэтому любую команду, посланную загрузчику, компьютер предваряет запросом сигнатуры.
Рассмотрим пример передачи команды записи Flash-памяти с ошибочным параметром. Попытаемся записать файл, заведомо больший допустимого (он разрушит программу-загрузчик в памяти контроллера). После кода команды FLASH_WRITE (0xFE) идут два байта параметра команды – номера последней страницы Flash-памяти, которая будет перезаписана.
Контроллер считает команду некорректной, и отвечает фреймом с кодом WRONG_RANGE (0x84).
Запрос:02.08.2016 0:20:12.91964 (+86.7969 seconds)
10 FF 00 00 10 FF 00 00 .ÿ...ÿ..
Ответ:02.08.2016 0:20:12.99764 (+0.0156 seconds)
10 1E 94 03 ...
Запрос:02.08.2016 0:20:12.02964 (+0.0313 seconds)
10 FE 00 7F .þ.
Ответ:02.08.2016 0:20:12.06064 (+0.0313 seconds)
10 84 00 7F ..
А вот пример успешной записи программы-примера из архива application.asm.
Запрос:02.08.2016 0:21:45.09164 (+93.0313 seconds)
10 FF 00 00 10 FF 00 00 .ÿ...ÿ..
Ответ:02.08.2016 0:21:45.15464 (+0.0156 seconds)
10 1E 94 03 ...
Запрос:02.08.2016 0:21:45.18564 (+0.0313 seconds)
10 FE 00 01 .þ..
Ответ:02.08.2016 0:21:45.21664 (+0.0313 seconds)
10 81 00 00 ...
Запрос:02.08.2016 0:21:46.35764 (+0.1406 seconds)
0C 94 49 00 00 00 18 95 00 00 18 95 00 00 18 95 .I..........
00 00 18 95 00 00 18 95 00 00 18 95 00 00 18 95 ............
00 00 18 95 00 00 18 95 00 00 18 95 0C 94 2A 00 ..........*.
00 00 18 95 00 00 18 95 00 00 18 95 00 00 18 95 ............
00 00 18 95 00 00 18 95 00 00 18 95 00 00 18 95 ............
00 00 18 95 0F 93 0F B7 0F 93 1F 93 2F 93 1C B1 .....·../.±
20 91 60 00 20 FD 06 C0 10 31 49 F4 21 60 00 E0 `. ý.À.1Iô!`.à
0B B9 05 C0 2E 7F 1F 3F 51 F0 01 E0 0B B9 20 93 .¹.À..?Qð.à.¹
2B E1 +á
Ответ:02.08.2016 0:21:46.38864 (+0.0313 seconds)
10 81 00 01 ...
Запрос:02.08.2016 0:21:46.52964 (+0.1406 seconds)
60 00 2F 91 1F 91 0F 91 0F BF 0F 91 18 95 0C 94 `./...¿...
C0 1E 0F E5 0D BF 04 E0 0E BF 04 E0 07 BB 01 E0 À..å.¿.à.¿.à.».à
0B B9 0C E9 0A B9 06 E8 00 BD 03 E3 09 B9 00 E0 .¹.é.¹.è.½.ã.¹.à
00 BD 00 27 00 93 60 00 78 94 FF CF FF FF FF FF .½.'.`.xÿÏÿÿÿÿ
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
E3 A4 ã¤
Ответ:02.08.2016 0:21:46.56064 (+0.0313 seconds)
10 80 00 02
После приёма команды записи прошивки контроллер отправляет запрос страницы с номером 0: 10 81 00 00, и получает 128 байт данных с контрольной суммой. После записи страницы контроллер запрашивает следующую. В конце он отправляет сообщение об успешном завершении – код 0x80 и количество записанных страниц.
Если у вас реализован какой-либо свой протокол, без использования мультипроцессорного режима, и вы не хотите от него отказываться, можно поступить следущим образом. Зарезервируйте в своём протоколе байт или последовательность байтов, получив которую ваша программа-прошивка будет передавать управление загрузчику. В папке с программой BootLoader v2.2 создайте текстовый файл, в него впишите придуманную вами последовательность (или одиночный байт), записывая каждый байт с новой строки (в десятичном или шестнадцатиричном виде).
Сохраните файл с именем prefix.cfg.
Программа BootLoader будет передавать этот префикс со сброшенным девятым битом перед каждым фреймом запроса сигнатуры (с паузой около 100мс между ними). Получив префикс, ОП запустит загрузчик, а он сразу настроит модуль USART для работы в MPCM режиме, и получит фрейм GET_SIGNATURE. Можете посмотреть как выглядит обмен данными при проведении операции проверки программы в Flash-памяти (Verify), если вы создали например, такой файл prefix.cfg:
0xaa
0x55
0x30
Запрос:02.08.2016 0:24:46.53264 (+9.6719 seconds)
AA 55 30 10 FF 00 00 .ÿ...ÿ..
Ответ:02.08.2016 0:24:46.59464 (+0.0156 seconds)
10 1E 94 03 ...
Запрос:02.08.2016 0:24:46.62664 (+0.0313 seconds)
10 FB 00 00 .û..
Ответ:02.08.2016 0:24:46.64164 (+0.0156 seconds)
10 FB 00 00 0C 94 49 00 00 00 18 95 00 00 18 95 .û...I.......
00 00 18 95 00 00 18 95 00 00 18 95 00 00 18 95 ............
00 00 18 95 00 00 18 95 00 00 18 95 00 00 18 95 ............
0C 94 2A 00 00 00 18 95 00 00 18 95 00 00 18 95 .*..........
00 00 18 95 00 00 18 95 00 00 18 95 00 00 18 95 ............
00 00 18 95 00 00 18 95 0F 93 0F B7 0F 93 1F 93 ........·..
2F 93 1C B1 20 91 60 00 20 FD 06 C0 10 31 49 F4 /.± `. ý.À.1Iô
21 60 00 E0 0B B9 05 C0 2E 7F 1F 3F 51 F0 01 E0 !`.à.¹.À..?Qð.à
0B B9 20 93 2B E1 .¹ +á
Запрос:02.08.2016 0:24:47.81364 (+0.0000 seconds)
10 FB 00 01 .û..
Ответ:02.08.2016 0:24:47.84464 (+0.0313 seconds)
10 FB 00 01 60 00 2F 91 1F 91 0F 91 0F BF 0F 91 .û..`./...¿.
18 95 0C 94 C0 1E 0F E5 0D BF 04 E0 0E BF 04 E0 ..À..å.¿.à.¿.à
07 BB 01 E0 0B B9 0C E9 0A B9 06 E8 00 BD 03 E3 .».à.¹.é.¹.è.½.ã
09 B9 00 E0 00 BD 00 27 00 93 60 00 78 94 FF CF .¹.à.½.'.`.xÿÏ
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
FF FF FF FF E3 A4 ÿÿÿÿã¤
Видно, что всё работает как обещано. Обратите внимание на то, что загрузчик, получив запрос чтения Flash-страницы, сначала подтверждает корректность полученной команды, передав назад фрейм запроса. Затем передаются данные и контрольная сумма (два байта).
Для того чтобы добавить модель контроллера, которую программа не поддерживает, нужно в папке AVR_signatures создать файл с параметрами модели, взятыми из апнотовского файла, по примеру других описаний. Имя файла – сигнатура контроллера.
Помимо выполнения своих прямых обязанностей, т.е. управления чем-либо, от программы вашего девайса требуется уметь передавать управление программе-загрузчику.
Рассмотрим подробно некоторые места из примера программы, которую мы хотим приспособить для работы с загрузчиком.
Кроме файла из папки Appnotes, подключаем файл с определениями протокола загрузчика, файл с удобными макросами и файл bootloader2.inc, в котором определяется адрес (BOOTSTART) по которому можно перейти в программу-загрузчик. Этот адрес определяется исходя из модели микроконтроллера, которую вы указали, включив в текст тот или иной апнотовский файл (в данном случае это ATmega16).
.include <m16def.inc>
.include “homebus.inc”
.include “macros.mac”
.include “bootloader2.inc”
Далее определяем тактовую частоту, адрес девайса (выбранный в диапазоне 0 – 255), скорость передачи по каналу связи. Например:
.equ MCU_CLOCK = 8000000
.equ NET_ADDR = 128
.equ BAUDRATE = 9600
Если вы используете преобразователь интерфейса, которому требуется управление режимом приём/передача, нужно указать ногу контроллера, которая это делает. К примеру разряд PB2, порта B.
.equ btlUSARTport = PORTB
.equ btlUSARTpin = PB2
Резервируем переменную flagUSART, которая будет признаком получения по линии связи адресного байта с нашим адресом.
;DATA segment
.dseg
.org SRAM_START
flagUSART:
.byte 1
Во избежании недоразумений в дальнейшем, уточню вот какой момент: константы MCU_CLOCK и др. используются в тексте программы-загрузчика, это константы времени компиляции, а переменная flagUSART - это переменная основной программы. В вашей программе она может быть реализована как угодно, это вообще может быть регистровая переменная.
Рассмотрим как может выглядеть обработчик прерывания USART Rx Complete:
#define data r17
#define flag r18
USART_RXC:
pushw ; сохраняет в стеке r16 и SREG
push data
push flag
in data,UDR
lds flag,flagUSART
sbrc flag,0
rjmp usartrxc1 ; получен байт команды
;Мы получили адресный байт, проверим – это наш адрес?
cpi data,NET_ADDR
brne exitUSART_RXC ; нет, не наш
;Это наш адрес, установим флаг получения своего адреса.
sbr flag,1
;Выключаем режим MPCM.
outi UCSRA,(0<<MPCM)
;Выходим из прерывания, будем ждать получения байта команды.
rjmp exitUSART_RXC
;Вслед за адресным байтом, получили байт команды.
;Сбрасываем флаг, и проверяем – это команда перехода в программу-загрузчик?
usartrxc1:
cbr flag,1
cpi data,GOTO_BOOTLOADER
breq near_BOOTSTART ; передаём управление загрузчику
/*
здесь может располагаться
обработчик команд обмена данными основной программы,
если конечно он есть
*/
;После обработки команд включаем режим MPCM.
outi UCSRA,(1<<MPCM)
exitUSART_RXC:
sts flagUSART,flag
pop flag
pop data
popw
reti
near_BOOTSTART:
sbi portLED,pinLED
jmp BOOTSTART
#undef data
#undef flag
Если вы пишете программу для контроллера, в памяти которого уже есть загрузчик, то последнюю строку
.include "mbtl2.asm"
нужно закомментировать или удалить.
Модуль USART должен быть настроен таким образом:
и не забываем разрешить прерывания.
Код GOTO_BOOTLOADER (0xFF) должен быть зарезервирован в вашем протоколе обмена, он используется для перехода в загрузчик.
Рассмотрим вариант, когда вы не можете, или не хотите использовать режим мультипроцессорного обмена.
В этом случае необходимо в вашем протоколе обмена данными определить байт или последовательность байтов, получив которую, ваша программа передаст управление программе-загрузчику. Затем в папке программы BootLoader2.2 нужно создать текстовый файл (например в Блокноте) с данным байтом или их последовательностью, по одному байту в строке, например:
0xaa
0x55
0x30
Можно использовать десятичные числа, если вам привычнее:
170
85
48
Сохраните файл с именем prefix.cfg. Программа BootLoader при каждом запуске проверяет наличие этого файла, и если находит, то при каждом обращении BootLoader’а к вашему девайсу будет предварительно передаваться данная последовательность.
Кроме того, в файле bootloader2.inc указаны значения конфигурационных бит, определяющих область расположения программы-загрузчика, для каждой модели микроконтроллера.
При необходимости использовать мини версию загрузчика извлекайте файлы mbtl2m.asm и bootloader2m.inc.
Файлы:
программма BootLoader v2.2
пример программы
загрузчик для ATmega
Все вопросы в Форум.
Эти статьи вам тоже могут пригодиться:
Автомат управления насосом для системы автополива с накопительной ёмкостью