RS-485 ATmega BootLoader
Автор: dimmer, cd1381@mail.ru С Вашего позволения хочу предложить ещё один вариант реализации перепрошивки устройств, собранных на МК 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 копируется девятый бит кадра (признак адресного байта). Проверка его на соответствующее значение – это дополнительная линия обороны от всякого рода сбоев, помех и прочего мусора в линии связи, мы все-таки прошивку меняем, а не пиццу заказываем. 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 Если поиск совпадения ничего не дал, отправляем компьютеру вялое ругательство и слушаем эфир дальше. 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. b_wrongRange: ldi dataByte0,WRONG_RANGE rcall b_FrameTransmit rjmp b_main Обработчик команды ”EEPROM Write”.Параметром данной команды является размер блока данных, предназначенных для записи в EEPROM. Размер данных естественно не должен превышать размера EEPROM, это условие проверяется в первую очередь. Некоторое ущемление прав контроллеров ATmega8515 и ATmega8535 вызвано тем, что только у этих моделей размеры SRAM и EEPROM памяти совпадают и равны 512 байт. При загрузке блока данных такого размера он разрушает область стека, что неизбежно приведет к непредсказуемым последствиям. Поэтому максимальный размер данных для записи EEPROM у этих моделей не должен превышать 508 байт (четырёх байт достаточно для вложенного вызова одной подпрограммы из другой). 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. 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 байт), размер зависит от модели контроллера.
Программа 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 передаёт фрейм, состоящий также из четырёх байт - байта собственного адреса и трёх байт сигнатуры контроллера. Запрос: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), в котором указаны необходимые для работы параметры контроллера. DEVICE=ATmega16 FLASHEND=0x1fff EEPROMEND=0x01ff PAGESIZE=64 Наименование модели контроллера отображается в окне программы. Программа-загрузчик устройства ожидает следующую команду в течении примерно 8 секунд, затем возвращает управление в основную программу. Поэтому любую команду, посланную загрузчику, компьютер предваряет запросом сигнатуры. Рассмотрим пример передачи команды записи Flash-памяти с ошибочным параметром. Попытаемся записать файл, заведомо больший допустимого (он разрушит программу-загрузчик в памяти контроллера). После кода команды FLASH_WRITE (0xFE) идут два байта параметра команды – номера последней страницы Flash-памяти, которая будет перезаписана. Запрос: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 создайте текстовый файл, в него впишите придуманную вами последовательность (или одиночный байт), записывая каждый байт с новой строки (в десятичном или шестнадцатиричном виде). 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 создать файл с параметрами модели, взятыми из апнотовского файла, по примеру других описаний. Имя файла – сигнатура контроллера. Адаптация прошивки
|
Как вам эта статья? |
Заработало ли это устройство у вас? | |||
|
Эти статьи вам тоже могут пригодиться: