Например TDA7294

РадиоКот > Схемы > Цифровые устройства > Защита и контроль

RS-485 ATmega BootLoader

Автор: dimmer, cd1381@mail.ru
Опубликовано 21.09.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. Ну не стремлюсь я к миниатюризации, размеры квартиры позволяют, да и глаза уже не те.

Список получился такой:

  • ATmega8515, ATmega8535
  • ATmega8, ATmega16, ATmega162, ATmega32
  • ATmega48, ATmega88, ATmega168
  • ATmega164, ATmega324, ATmega1284

Вполне может так случиться, что ваша модель не попала в этот список. Поэтому  вы, ознакомившись с исходником программы и комментариями, возможно избыточными с точки зрения опытных программистов, но надеюсь достаточными для того, что бы люди, не имеющие пока достаточного опыта, смогли бы адаптировать прогу под себя (или под свою модель контроллера).

Вполне естественно, что основная программа девайса должна соответствовать определённым требованиям. Она должна уметь передавать управление загрузчику, получив соответствующий запрос по линии связи. Если основная программа ведёт какой-либо обмен с компом своими данными, то в протоколе обмена нужно зарезервировать возможность распознавания кода передачи управления загрузчику. Если своего обмена данными не предусмотрено, то программа должна просто сконфигурировать USART таким образом, чтобы модуль слушал линию связи и при получении запроса осуществлял переход по адресу программы-загрузчика.

На стороне ведущего, т.е. компьютера в обмене участвует программа BootLoader, написанная на С++. Программа не требует установки, легко поддаётся обучению, добавить недостающую модель меги можно просто имея перед глазами соответствующий апнотовский файл. Желаемый порт и скорость обмена просто вписываются в конфигурационный файл с неожиданным названием config.cfg а имена ваших девайсов с адресами в файл items.cfg, формат записей можно посмотреть в самих файлах, в папке с программой, и сделать похоже.

Поскольку загрузчик в памяти девайса находится не один, а вместе с основной программой, давайте сначала рассмотрим мероприятия, обеспечивающие безболезненное сосуществование этих двух софтин.
Для успешного ассемблирования загрузчика вместе с основной программой (ОП), в этой самой ОП должны быть определены:

  1. константа MCU_CLOCK, равная тактовой частоте контроллера в Герцах, например:
.equ MCU_CLOCK = 8000000                               
  1. константа NET_ADDR, определяющая адрес девайса в вашей линии связи (сетевой адрес). Диапазон допустимых значений 0 - 255, например:
.equ NET_ADDR = 128
  1. константа BAUDRATE, скорость передачи по каналу связи (выбрать из ряда стандартных значений), пример:
.equ BAUDRATE = 9600

Два необязательных значения: btlUSARTport и btlUSARTpin, определяющие ногу микроконтроллера, которая в приёмопередатчике RS-485 управляет режимом приём/передача, примерно так:

.equ       btlUSARTport = PORTB
.equ       btlUSARTpin  = PB2

Значения btlUSARTport и btlUSARTpin не требуется определять, если вы используете:

  • Полный дуплексный режим, т.е. интерфейс RS-422.
  • Подключение приёмопередатчика по стандарту J1708.
  • Какой-то другой преобразователь какого-то другого интерфейса, не требующего прямого определения направления передачи данных.

Если, микроконтроллер имеет два модуля 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 секунд.
Далее готовимся к получению пакета (фрейма) состоящего из:

  1. Байта адреса устройства netAddr (с установленным девятым битом кадра).
  2. Байта кода команды dataByte0.
  3. Двух байт параметров команды dataByte1, dataByte2.
;Receive MPCM Frame (netAddr + 3 data bytes)
b_FrameReceive:
               cbr flags,(1<<fnetAddr)|(1<<fdataByte0)|(1<<fdataByte1)

Сбрасываем:

  • fnetAddr – флаг успешного приёма байта адреса (своего адреса)
  • fdataByte0 – флаг приёма байта кода команды
  • 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

Обработчик команды “Get Signature”.

Каждой модели микроконтроллера ставится в соответствие трёхбайтный идентификатор (сигнатура), определённый в аппнотовском файле. Запрос компьютером сигнатуры, и её передача контроллером в ответ, во-первых является признаком успешного установления соединения, и во-вторых позволяет компьютеру определить модель контроллера. Все эти мероприятия направлены на повышения общей стабильности работы всей системы: компьютер, линия связи, контроллер.

b_GetSignature:
               ldi dataByte0,SIGNATURE_000
               ldi dataByte1,SIGNATURE_001
               ldi dataByte2,SIGNATURE_002
               rcall b_FrameTransmit
               rjmp b_main

Три байта сигнатуры отправляем в подпрограмму передачи фрейма.

Обработчик команды ”EEPROM Read”.

При получении запроса контроллер сначала подтверждает свою готовность обработать команду путем обратной пересылки фрейма.

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 успел завершить передачу.

Обработчик команды ”Flash Read”.

В отличие от 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 Write”.

Параметром данной команды является размер блока данных, предназначенных для записи в 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 Write”.

Ну вот и добрались до того, ради чего всё собственно и затевалось. Как и чтение, запись 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 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 программы.
При выборе устройства из этого списка на линию связи выдаётся последовательность из четырёх байт (фрейм):

  • Байт адреса устройства.
  • Байт кода команды GOTO_BOOTLOADER (0xFF).
  • Два нулевых байта.

Первым передаётся адрес взятый из файла 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 должен быть настроен таким образом:

  • режим MPCM включен
  • передача и приём разрешены
  • размер кадра 9 бит
  • проверка на чётность не используется
  • один стоп-бит

и не забываем разрешить прерывания.

Код 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


Все вопросы в Форум.


ID: 2388

Как вам эта статья?

 Нравится
 Так себе
 Не нравится

Заработало ли это устройство у вас?

 Заработало сразу
 Заработало после плясок с бубном
 Не заработало совсем

18 12 6