После того как вы подключите к компьютеру программатор AVR JTAGICE3 и установите драйверы в среде разработки AVR Studio 5 (или ATMEL Studio 6) у вас появится возможность использовать в качестве отладчика это замечательное устройство. В данной статье я предоставил информацию о том как начать использовать данный отладчик и понять как он работает. Имея опыт работы с этим устройством я обнаружил ряд особенностей, которые, лично у меня, поначалу сеяли ряд сомнений относительно того насколько JTAGICE3 адекватен собственной стоимости (ведь он достаточно дорогой и хочется ждать от него гораздо большего). Но сомнения эти, в общем, со временем улетучились и на данный момент я полностью доволен этим устройством, а чтобы у вас не создавалось подобных сомнений хочу поделиться данным материалом.
Итак, если вы уже уверены, что вас устраивает именно JTAGICE3 (см. характеристики AVR JTAGICE3) или если вы еще сомневаетесь в этом можем приступать.
Первым делом, конечно, рассмотрим процедуру настройки данного устройства в среде разработки. В данной статье в качестве среды разработки я имею ввиду среду AVR Studio 5, но сразу хочу отметить, что более свежая версия этой среды под названием ATMEL Studio 6 практически ничем не отличается от пятой версии в плане работы с отладчиками и, поэтому все сказанное здесь также будет актуально и для шестой версии среды разработки.
Также отмечу, что программатор в нашем случае будет более корректно называть отладчик, т.к. во-первых, функция отладки у этого устройства является его основным достоинством (возможно, ради которого мы на него и запали), а во-вторых, будем придерживаться терминологии, принятой в среде разработки, где он называется Debugger.
Итак, рассмотрим первый этап:
Если у вас уже имеется проект, то вы можете просто пропустить этот пункт и произвести дальнейшие действия на вашем проекте. Если вы еще не создавали ни одного проекта AVR Studio я распишу как это сделать, потому что хоть это и может быть кому-то смешно, но пытаясь опробовать отладчик JTAGICE3 я долго не мог разобраться как создать новый пустой проект в AVR Studio, даже несмотря на то что я с оболочкой Microsoft Visual Studio, на которой основана AVR Studio работаю уже более 10 лет.
Создадим тестовый проект в среде разработки. Пусть это будет пустой проект — выбираем в меню Файл->Создать->Проект… или просто нажимаем Ctrl+Shift+N и в открывшемся окне выбираем «Empty AVR GCC Project» как это показано на следующем видео:
К сожалению, продемонстрировать не могу, т.к. у меня в AVR Studio указанный пункт отсутствует и окно «Создать проект» у меня выглядит следующим образом:
У кого такая же петрушка пишите в комменты, решение есть, а мы поехали дальше.
На данном этапе мы имеем созданный проект в среде AVR Studio, при создании проекта мы указали тип микроконтроллера (надеюсь вы выбрали совместимый тип микроконтроллера с описываемым в данной статье отладчиком). Также к разъему USB компьютера подключен наш отладчик.
Давайте попробуем как будет среда разработки взаимодействовать с подключенным устройством. Для этого подсоединим отладчик к имеющейся у вас плате и включим ее питание. Если отладчик увидит подключенную плату, то он отобразит это загоревшимся зеленым индикатором (из трех индикаторов он расположен слева). Если лампочка загорелась, ок — можно пробовать подключаться к отладчику.
Выбираем в среде разработке в меню Сервис пункт AVR Programming. На экране отобразится следующее окно:
В выпадающем списке Tool выберите Jtagice3. Если вы не видите его в списке, значит скорее всего у вас проблемы с установкой драйверов для этого устройства или вы его забыли подключить к компьютеру.
В выпадающем списке Device укажите тип вашего микроконтроллера, ну и укажите интерфейс по которому вы подключили программатор к своему устройству. Предполагаю, что вы выберите JTAG.
После этого нажимаем кнопку Apply и ждем обмена данными компьютера с программатором. Что мы должны увидеть на экране? Компьютер должен загрузить сведения с программатора и отобразить список доступных вкладок на экране:
Первые три вкладки Interface settings, Tool Information и Device Information относятся к программатору и указанному типу микроконтроллера. Если вы правильно подключили программатор, то компьютер успешно соединится с программатором и получит сведения для данных вкладок при нажатии кнопки Apply.
Рассмотрим опции на вкладках Interface settings, Tool Information и Device Information подробнее.
Здесь мы видим параметры настроек интерфейса отладчика. Ползунок JTAG Clock задает скорость интерфейса JTAG. Чем выше этот показатель, тем выше скорость обмена данными между отладчиком и микроконтроллером во время отладки программы. Но следует иметь ввиду, что указанная частота не должна превышать 1/4 частоты на которой будет работать сам микроконтроллер. Т.е. если ваш микроконтроллер работает на частоте 8 МГц, то выставляйте параметр JTAG Clock не больше 2 МГц, иначе будут проблемы с синхронизацией отладчика с вашим микроконтроллером.
Используйте галочку «Use external reset» если по каким либо причинам вам необходимо запретить отладчику посылать команду сброса микроконтроллера. Вместо этого вам необходимо будет производить сброс микроконтроллера иными способами, которые у вас должны быть предусмотрены. Актуально, если, к примеру, логике вашей программы принципиально, чтобы отладчик не прерывал исполнение программы.
Опции Daisy chain трогать не будем, т.к. они касаются гирляндных (или шлейфовых) соединений устройств, что относится к достаточно продвинутым параметрам, которые мы не будем использовать.
На вкладке отображаются сведения о вашем отладчике — наименование устройства, версия прошивки, серийный номер и прочее.
По поводу версии прошивки. При подключении отладчика, в случае, если версия прошивки вашего отладчика не является последней, будет выведено окно с предупреждением о необходимости обновить прошивку отладчика. Обновление обычно необходимо для расширения списка поддерживаемых микроконтроллеров и инструкций отладчиком. Также возможны исправления некоторых ошибок, поэтому обновлять прошивку рекомендуется.
На данной вкладке содержатся сведения о типе микроконтроллера, который вы выбрали. Если отладчик смог соединиться с вашей платой, то он должен на этой вкладке отобразить также некоторые сведения о конкретном микроконтроллере, установленном на вашей плате — такие как сигнатура устройства, JTAG ID, ревизия. В данном примере мы не будем использовать эти сведения.
Следующие три вкладки Memories, Fuses и Lock Bits расписывать подробно не буду — это совершенно привычные опции по управлению памятью, фьюзами и битами блокировки микроконтроллера информацию о которых можно почерпнуть в других источниках.
Единственное, скажу только что вкладки Fuses и Lock Bits будут доступны только в том случае если отладчик успешно смог соединиться с микроконтроллером и, поэтому при нажатии на эти вкладки сведения о текущих параметрах предварительно загружаются с микроконтроллера.
Ну а если выскакивает ошибка при нажатии на эти вкладки, значит ищите проблемы. А в какой последовательности их искать рассмотрим далее.
Для выявления возможных проблем с подключением отладчика и отлаживаемых модулей удобнее всего использовать окно AVR Programming, о котором шла речь выше. Рассмотрим последовательность действий для выявления наиболее типичные проблем с подключением отладочных устройств.
Во-первых убедимся, что все подключено и все включено. Также важно перед тем как вы откроете окно AVR Programming убедиться что вы вышли из режима отладки. Иначе вы будете получать ошибки на все попытки обращения к отладчику.
Открываем окно AVR Programming, выбираем программатор, микроконтроллер и интерфейс и жмем кнопку Apply. Если у вас отобразились вкладки, значит отладчик успешно найден и подключен правильно. Если все сделали правильно, а отладчик ругается на таймауты, то настало время перезагрузить AVR Studio (заодно, на всякий случай, отключите разъем USB программатора и снова воткните его в компьютер), теперь все снова должно заработать.
Теперь начинаем проверять, получается ли у отладчика связаться с вашим модулем. Для этого еще раз убедитесь, что отладчик правильно подключен к вашему модулю, у модуля включено питание и у отладчика горит зеленый индикатор слева. Если все так, то проверяем получается ли у отладчика связаться с микроконтроллером. Нажмем на кнопку Read, расположенную рядом с Device Id. Если отобразится в поле сигнатура устройства, то значит все ОК, отладчик и микроконтроллер, в принципе готовы к работе, если нет проверяем питание на разъеме JTAG — нажимаем кнопку Read, расположенную около Target Voltage. Посмотрите уровень напряжения, который измерил отладчик. Если он соответствует схеме модуля, то проблема в разъемах, схеме или сдох микроконтроллер. Необходимо все это проверить, после чего повторить.
Ну вроде это все. Если у вас возникли иные проблемы напишите о них в комментариях. Продолжение, думаю, следует.
]]>Наименование программатора | Уровень | Описание |
---|---|---|
AVRprog
программатор |
бюджетный, самоделка | ![]() Аппаратная часть программатора состоит из MAX232A, 6 конденсаторов 10нФ, 10КОм-ного резистора и кнопочки. |
JTAGICE mkII
программатор |
промышленного изготовления, Atmel
имеются клоны |
![]() Нареканий нет, работает и через USB и через COM-порт. Поддерживается в AVR Studio 4, AVR Studio 5, ATMEL Studio 6, ну и соответственно, avrdude. Немного крупноват в плане габаритов. Дорогой, но при желании можно найти схему и собрать клон. |
AVR910
программатор |
промышленного изготовления, Atmel (устаревший)
имеются клоны |
![]() К примеру, если вы планируете работать над созданием собственного загрузчика вашего модуля, работающего по последовательному порту, то протокол этого программатора вам может пригодиться в плане того что ваш модуль сможет «закосить» под AVR910 и обновить у себя прошивку самостоятельно. Преимущество в том, что вы для этого сможете использовать программное обеспечение, совместимое с данным программатором. Тем не менее если вы хотите собрать такую штуку, то в Интернете представлена не одна версия клонов. |
USBtinyISP
программатор |
бюджетный, самоделка | ![]() |
USB AVR programmer
программатор |
бюджетный, самоделка | ![]() |
STK500
программатор |
промышленного изготовления, Atmel | ![]() Подключается к компьютеру через RS232 и позволяет программировать микроконтроллеры в корпусах DIP непосредственно, для чего имеет набор различных слотов. Имеет возможность подключать различные модули расширения. |
STK600
программатор |
промышленного изготовления, Atmel | ![]() Подключается к компьютеру через разъем USB и является более расширенной версией STK500, позволяющая задействовать кроме интерфейса ISP также JTAG и aWire, что обеспечивает поддержку всех разновидностей микроконтроллеров компании Atmel. |
USBasp | бюджетный, самоделка | ![]() |
Немного подробнее остановлюсь на программаторах-отладчиках, с которыми мне удалось поработать лично.
Официальный программатор для микроконтроллеров фирмы Atmel семейства AVR, поддерживающих интерфейс для отладки и программирования JTAG.
Применяется данный программатор для микроконтроллеров фирмы Atmel семейства AVR, поддерживающих интерфейс для отладки и программирования JTAG. Данный программатор является клоном оригинального Atmel’евского программатора. Производит его фирма Olimex и отличается он от официального более выгодной ценой (приблизительно 4000 рублей через официальных дилеров в России, и естественно, можно дешевле напрямую из-за бугра), при этом по функциональности вполне надежный и во время работы с ним нареканий у меня не возникало. Работает и питается от USB порта компьютера.
Комплектация: программатор/эмулятор AVR-JTAG-USB.
Для работы может понадобиться USB кабель «А-А» — SCUAA-1
Основные преимущества этой программы следующие:
Установка очень проста. Вам нужно скачать архив, содержащий программу AVR8 Burn-O-Mat с их сайта: http://avr8-burn-o-mat.aaabbb.de/avr8_burn_o_mat_avrdude_gui_en.html, затем его распаковать в удобном для вас месте. Не забудьте предварительно установить Java, если вы это еще не сделали: http://java.sun.com/javase/downloads.
Программа имеет интуитивно понятный интерфейс. После запуска оглядите все возможные настройки. Убедитесь, что пути к avrdude и к конфигурации avrdude прописаны верно. Затем выберите нужный тип программатора и порт, к которому он подключен (список поддерживаемых программаторов берется из файла avrdude.conf)
Вот список поддерживаемых микроконтроллеров:
ATmega8, ATmega16, ATmega32, ATmega64, ATmega128, ATmega48, ATmega88, ATmega168, ATmega162, ATmega8515, ATmega8335, ATmega164, ATmega324, ATmega644, ATmega169, ATmega329, ATmega3290, ATmega649, ATmega6490, ATtiny2313, ATtiny13, ATtiny25, ATtiny45, ATtiny85, ATtiny26
Новые микроконтроллеры можно с легкостью добавить самостоятельно. Вся необходимая для этого информация содержится в файле AVR8_Burn_O_Mat_Config.xml.
]]>avrdude
Avrdude
[опции]
[опции]
задают параметры команды в формате -ключ_значение
.Avrdude -p <partno>
где <partno>
— тип микроконтроллера. Данная команда указывает какой тип микроконтроллера используется. Например, для микроконтроллера Atmega32 <partno>
будет равен m32
.
Avrdude -b <baudrate>
где <baudrate> переопределяет скорость обмена данными с программатором по последовательному интерфейсу (относится не ко всем типам программаторов). Не рекомендуется изменять данный параметр без особых причин, т.к. программаторы обычно оптимизированы на скорость по умолчанию.
Avrdude -B <bitclock>
где < bitclock> переопределяет скорость обмена данными программатора с микроконтроллером. Данный параметр может потребоваться если частота микроконтроллера слишком низкая и не позволяет производить программирование на скорости по умолчанию. В таком случае с помощью данного параметра скорость программирования может быть снижена.
Avrdude -F
Avrdude -e
Avrdude -U <
memtype
>:r|w|v:<filename>[:format]
test.hex
будет следующей:Avrdude -U flash:w:test.hex:i
Команда на чтение содержимого энергонезависимой памяти микроконтроллера в файл eedump
.hex
будет следующей:
Avrdude -U eeprom:r:eedump.hex:i
Avrdude -n
Avrdude -u
С помощью опции -c <programmer>
возможен выбор одного из следующих программаторов:
C:\>avrdude -c avrisp ...
set AVRDUDECMD="<путь к папке с avrdude>\avrdude.exe" set FIRMWARESOURCEDIR=<путь к папке с текущей прошивкой> set FIRMWAREDIR=%TMP%\firmware\ :SELECTPORT set /P port="Выберите номер порта COM" echo %AVRDUDECMD% echo Проверка подключения... %AVRDUDECMD% -c jtagmkI -p m32 -P COM%port% IF %ERRORLEVEL% EQU 9009 GOTO SELECTPORT echo Удаляем временные файлы rmdir /S /Q %FIRMWAREDIR% echo Копируем прошивку во временную папку mkdir %FIRMWAREDIR% xcopy "%FIRMWARESOURCEDIR%*.*" "%FIRMWAREDIR%" echo Прошиваем микроконтроллер @echo on %AVRDUDECMD% -c jtagmkI -p m32 -P COM%port% -U flash:w:"%FIRMWAREDIR%firmware.hex":i -U hfuse:w:^<0x91^>:m -U lfuse:w:^<0xFF^>:m @echo off pause
Имя файла с прошивкой должно быть firmware.hex
и располагаться в папке FIRMWARESOURCEDIR
.
Выберите номер порта COM2 "\\center\Прошивки для плат\avrdude\avrdude.exe" Проверка подключения... avrdude.exe: jtagmkI_open(): failed to synchronize to ICE avrdude.exe: jtagmkI_close(): unsupported baudrate -1 avrdude.exe done. Thank you. Удаляем временные файлы Копируем прошивку во временную папку \\center\Прошивки для плат\МОС-Универсальный\revision01.00\version01.00\MOS.hex Скопировано файлов: 1. Прошиваем микроконтроллер C:\WINDOWS>"\\center\Прошивки для плат\avrdude\avrdude.exe" -c jtagmkI -p m32 -P COM2 -U flash:w:"C:\DOCUME~1\valeyev\LOCALS~ 1\Temp\firmware\MOS.hex":i -U hfuse:w:<0x91>:m -U lfuse:w:<0xFF>:m avrdude.exe: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 0.05s avrdude.exe: Device signature = 0x1e9502 avrdude.exe: NOTE: FLASH memory has been specified, an erase cycle will be performed To disable this feature, specify the -D option. avrdude.exe: erasing chip avrdude.exe: reading input file "C:\DOCUME~1\valeyev\LOCALS~1\Temp\firmware\MOS.hex" avrdude.exe: writing flash (19104 bytes): Writing | ################################################## | 100% 7.19s avrdude.exe: 19104 bytes of flash written avrdude.exe: verifying flash memory against C:\DOCUME~1\valeyev\LOCALS~1\Temp\firmware\MOS.hex: avrdude.exe: load data flash data from input file C:\DOCUME~1\valeyev\LOCALS~1\Temp\firmware\MOS.hex: avrdude.exe: input file C:\DOCUME~1\valeyev\LOCALS~1\Temp\firmware\MOS.hex contains 19104 bytes avrdude.exe: reading on-chip flash data: Reading | ################################################## | 100% 4.11s avrdude.exe: verifying ... avrdude.exe: 19104 bytes of flash verified avrdude.exe: reading input file "<0x91>" avrdude.exe: invalid byte value (<0x91>) specified for immediate mode avrdude.exe: write to file '<0x91>' failed avrdude.exe: safemode: Fuses OK avrdude.exe done. Thank you. Для продолжения нажмите любую клавишу . . .
Начиная с версии AVR Studio 5 среда разработки основывается на оснастке Micrisoft Visual Studio 2012, которая содержит в свойствах проекта опцию по вызову произвольных пакетных команд. Выполнение команд привязано к основным событиям постоения проекта (Build Events) — команды, выполняемые до построения проекта и команды, выполняемые после построения проекта.
Итак, открываем среду разработки AVR Studio 5, или ATMEL Studio 6, открываем существующий или создаем новый проект и открываем его свойства:
На экране в центральной области откроется вкладка со свойствами выбранного проекта. Выберите вкладку «Build Events»:
Прошивать микроконтроллер необходимо после успешного построения проекта, поэтому команды следует вводить в соответствующее поле «Post-build event command line» .
В это поле необходимо ввести команду, которая выполнит заливку только что сделанной прошивки в микроконтроллер. Для этого воспользуемся программой avrdude.exe.
Для того чтобы сформировать команду на заливку прошивки необходимо изучить параметры командной строки avrdude.exe для вашего типа микроконтроллера и сформировать макрос, определяющий путь к файлу, генерируемой прошивки (информацию по командам avrdude вы можете найти здесь: прошивание микроконтроллеров утилитой Avrdude). Я же приведу пример вызова команды для своего типа программатора jtagmkI.
Команда avrdude требует в качестве параметра путь к файлу с прошивкой. Этот путь можно сформировать автоматически воспользовавшись макросами среды разработки:
"$(OutputDirectory)\$(MSBuildProjectName).hex"
Использование макросов предпочтительно тому, если вы пропишите путь к прошивке жестко, т.к. путь к файлу прошивки может измениться если вы, к примеру, перенесете свой проект в другую папку , переименуете проект или даже переключитесь из режима Debug в режим Release. Применение макросов гарантирует, что каждый раз при вызове события построения проекта команде будет сформирован правильный путь к прошивке.
Итак, разобравшись с параметрами команды у меня получилась строчка следующего вида:
avrdude.exe -c jtagmkI -p m32 -P COM3 -U flash:w:"$(OutputDirectory)\$(MSBuildProjectName).hex":i -U hfuse:w:^<0x91^>:m -U lfuse:w:^<0xFF^>:m
Осталось только определить путь к программе avrdude и убедиться что в опциях проекта стоит галочка напротив пункта «Генерировать .hex». Для этого заходим в свойствах проекта на вкладку «Buid» и ставим галочку:
Теперь всякий раз после того как вы будете выполнять построение проекта будет автоматически производиться вызов команды прошивания микроконтроллера.
В случае, если avrdude не сможет выполнить заливку программы среда разработки выдаст в окне «Список ошибок» соответствующую запись.
Конечно, с помощью данного способа, будет выполняться только выгрузка сделанной прошивки в микроконтроллер и вы не сможете использовать функции отладки, имеющиеся в среде исполнения. Но для случаев, когда отсутствие возможности отлаживать не принципиально, а покупать дорогой программатор из списка поддерживаемых по каким либо причинам не хочется или не целесообразно это вполне приемлемое решение.
]]>Эта статья является продолжением AVR BootLoader в вопросах и ответах. Часть 1
Говорят: «Нет, нет и 100% определенно нет».
Хоть технически это возможно, но ответ все равно — «Нет». Ваш загрузчик будет гораздо более надежным, если он имеет нулевую зависимость от приложения. Основная цель загрузчика — стереть и перепрограммировать приложение. Вы же не хотите выполнять вызовы участков кода приложения в то время как стираете его содержимое.
Плохой практикой также считается хранить код, относящийся к загрузчику в разделе RWW. Не существует полного доказательства или способа защиты от случайного стирания и перепрограммирования этой области. Одно полное стирание RWW и загрузчик потенциально становится бесполезным.
Да, сделать это довольно легко. Особенно если код для совместного использования не имеет доступа к глобальным переменным. Только не пытайтесь достичь общего кода путем создания загрузчика и приложения в качестве одного двоичного файла. Лучше всего строить их по отдельности и использовать указатели на функции общего кода.
.section .jumps,"ax",@progbits // The gnu assembler will replace JMP with RJMP when possible .global _jumptable _jumptable: jmp shared_func1 jmp shared_func2 jmp shared_func3
JUMPSTART = 0x3FE0 # 32 bytes from the end of the AT90USB162 4kb boot section LDFLAGS += -Wl,--section-start=.jumps=$(JUMPSTART)
-ffunction-sections
совместно с флагами линковщика --gc-sections
и --relax
. Поэтому если вы не уверены, то в любом случае это не помешает добавить:LDFLAGS += -Wl,--undefined=_jumptable
typedef void (*PF_VOID)(void); typedef void (*PF_WHATEVER)(uint8_t); static __inline__ void call_func1(void) { ((PF_VOID) (0x3FE0/2))(); } static __inline__ void call_func2(void) { ((PF_VOID) (0x3FE2/2))(); } static __inline__ void call_func3(uint8_t arg) { ((PF_WHATEVER) (0x3FE2/2))(arg); }
call_func3(1);
Так как у вас два совершенно разных двоичных файла, то каждый будет иметь свое собственное распределение памяти. Позиции глобальных переменных в приложении никак не связаны с позициями глобальных переменных в
загрузчике.
Допустим, общая функция загрузчика по ошибке производит чтение и запись в глобальную переменную напрямую.
Когда загрузчик был отлинкован, пространство глобальных переменных по сути было жестко запрограммированно в его исполняемый код. Когда загрузчик исполняется, то его позиция правильная, и все работает нормально. Но когда запускается приложение, которое вызывает те же функции, то жестко заданные позиции глобального пространства уже совсем не те. Создается неправильная ситуация, т.к. когда приложение исполняется, то ее распределение памяти отличается от распределения памяти загрузчика. Таким образом, вы не можете получить непосредственный доступ к глобальным переменным из общих функций без потенциальной угрозы уничтожения данных в приложении.
// In a shared header file typedef struct { uint8_t val1; uint16_t val2; } globals_t; // The shared function void func4(globals_t *vars) { vars->val1 = 0; vars->val2 = 512; } // Globally defined in each binary globals_t g_vars; // Calling the shared function call_func4(&g_vars);
Если по каким-то причинам вы не можете передать параметр в общую функцию см. вопрос № 17 ниже.
На самом деле это проще сделать, чем организовать общие функции, поскольку векторы прерывания по умолчанию уже обеспечивают переход чтобы найти процедуру обработки прерывания (ISR). Достаточно написать и откомпилировать привычным способом в загрузчике ISR (только без доступа напрямую к глобальным переменным). После этого необходимо в дизассемблированном файле загрузчика найти таблицу прерываний. Записать адрес вектора ISR, который необходимо сделать общим. Вам не потребуются адреса обработчиков из этого вектора. Вам нужет адрес самого вектора прерываний. Его адрес будет в начале загрузчика.
// This must be declared "naked" because we want to let the // bootloader function handle all of the register push/pops // and do the RETI to end the handler. void USB_GEN_vect(void) __attribute__((naked)); ISR(USB_GEN_vect) { asm("jmp 0x302C"); }
Такая же ситуация как и в вопросе №15, но на этот раз решение не такое простое, поскольку вы не можете поместить в стек обработчика ISR указатель. Есть целый ряд возможных решений, но, поскольку это руководство уже довольно обширно об этом будет сказано не так подробно. Есть два варианта, таких как использование регистров GPIO для передачи указателей на глобальные или резервирование части SRAM с известным адресом, где расположены глобальные данные. Если вам нужен иной способ, то вам сюда: http://tinyurl.com/q3fpud . Идея с зарезервированной областью SRAM, кажется неплохой.
Да, зачастую таким образом можно сэкономить 100 и более байт загрузчика. Эта также относиться и к обычным приложениям, но такая экономия для загрузчика более существенна чем к приложению. Некоторые архитектуры AVR имеют 40 или более прерываний, каждый из которых в таблице векторов прерываний принимает по 4 байта. Вы можете не только незначительно сэкономить, но и переопределять неиспользуемые вектора как переходы на общие функции (обсуждается в вопросе № 14).
.section .blvects,"ax",@progbits .global __vector_default __vector_default: jmp __init
LDFLAGS += -T bootloader.x # Or whatever you named the linker script
.text : { *(.vectors) KEEP(*(.vectors))
Добавьте строчку DISCARD и замените «vectors», на ваше имя секции:
/DISCARD/ : { *(.vectors); } /* Discard standard vectors */ .text : { *(.blvects) /* Position and keep custom vectors */ KEEP(*(.blvects))
Если загрузчик использует таблиуц прерываний ISR, вы можете сэкономить на размере прошивки заменив полную таблицу прерываний на уменьшенную версию. Допустим, нам необходимо только 11-е прерывание (USB на AT90USB162), поэтому мы усечем таблицы и повторно задействуем слоты до прерывания 11 в качестве переходов на общую функцию. При этом убедитесь, что положение оставшихся векторов прерываний правильно. Каждый должен быть нацелен на адрес указанный для этого прерывания в даташите (‘nop’ы в примере ниже). Повторно задействуя часть таблицы векторов вы можете избежать необходимости в таблице с отдельными переходами, как рассказано в вопросе №14. Вам просто потребуются указатели на функции для повторного вызова векторов. См. комментарии ниже для более подробной информации:
.section .bootvect,"ax",@progbits ; Custom vector table that eliminates wasted space after the last used ; vector (__vector_11, usb general). Also re-purpose the unused space ; between the reset vector and the usb vector for the jumps to shared ; code. ; .global __vector_default ; There are 21 "word" spaces between __init and __vector_11. This fits ; 21 RJMPs or 10 JMPs. Since the bootloader is only "2K words" long, ; use RJMPs. ; - Don't change the order of these (unless it is before any devices ; shipped)! ; - Add new entries by replacing nop's ; - Remove entries by replace them with nop's (without reordering) __vector_default: rjmp __init ; 0x3000 !used interrupt! rjmp shared_func1 ; 0x3002 rjmp shared_func2 ; 0x3004 rjmp shared_func3 ; 0x3006 rjmp shared_func4 ; 0x3008 rjmp shared_func5 ; 0x300a nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop rjmp __vector_11 ; 0x302C !used interrupt!
#ifdef RTS_ENABLE #define RTS_PIN PA6 #define RTS_DDR DDRA #define RTS_PORT PORTA #define RTS_INIT \ do { \ RTS_DDR |= _BV( RTS_PIN ); \ RTS_PORT &= ~( _BV( RTS_PIN ) ); \ } while( 0 ); #define RTS_HIGH \ do { \ RTS_PORT |= _BV( RTS_PIN ); \ } while( 0 ); #define RTS_LOW \ do { \ RTS_PORT &= ~( _BV( RTS_PIN ) ); \ } while( 0 ); #endif
Макрос содержит три определения и три инструкции, необходимые для управления режимом передачи/чтения. Определения указывают к какой ноге микроконтроллера подключен управляющий сигнал RS485, а инструкции определяют процедуру инициализации порта две инструкции включения и выключения режима передачи.
// AVR306: Using the AVR UART in C // Routines for polled UART // Last modified: 02-06-21 // Modified by: AR /* Includes */ #include <io8515.h> #ifdef RTS_ENABLE #define RTS_PIN PA6 #define RTS_DDR DDRA #define RTS_PORT PORTA #define RTS_INIT \ do { \ RTS_DDR |= _BV( RTS_PIN ); \ RTS_PORT &= ~( _BV( RTS_PIN ) ); \ } while( 0 ); #define RTS_HIGH \ do { \ RTS_PORT |= _BV( RTS_PIN ); \ } while( 0 ); #define RTS_LOW \ do { \ RTS_PORT &= ~( _BV( RTS_PIN ) ); \ } while( 0 ); #endif /* Prototypes */ void InitUART( unsigned char baudrate ); unsigned char ReceiveByte( void ); void TransmitByte( unsigned char data ); uint8_t receiveMode = 1; /* Main - a simple test program*/ void main( void ) { InitUART( 11 ); /* Set the baudrate to 19,200 bps using a 3.6864MHz crystal */ for(;;) /* Forever */ { TransmitByte( ReceiveByte() ); /* Echo the received character */ } } /* Initialize UART */ void InitUART( unsigned char baudrate ) { UBRR = baudrate; /* Set the baud rate */ UCR = ( (1<<RXEN) | (1<<TXEN) ); /* Enable UART receiver and transmitter */ } uint8_t isReceivedByte( void ) { if (!receiveMode) { RTS_LOW; cbi(UCSRB, TXEN); sbi(UCSRB, RXEN); receiveMode = 1; } return UCSRA & (1<<RXC); } /* Read and write functions */ unsigned char ReceiveByte( void ) { while ( !isReceivedByte() ) /* Wait for incomming data */ ; /* Return the data */ return UDR; } void TransmitByte( unsigned char data ) { while ( !( UCSRA & (1<<UDRE)) ) ; if (receiveMode) { cbi(UCSRB, RXEN); sbi(UCSRB, TXEN); RTS_HIGH; receiveMode = 0; } /* Put data into buffer, sends the data */ UDR = data; while ( !( UCSRA & (1<<TXC)) ) ; _delay_ms(0.5); return; }
По сути, загрузчик — это просто обычное приложение AVR, которое расположено в специальной области флэш-памяти. В простейшей форме, приложение можно сделать загрузчиком AVR путем указания дополнительного флага компоновщику, который необходимо добавить в свой Makefile:
LDFLAGS += -Wl,--section-start=.text=0x3000
Этот флаг указывает начальный адрес байта из загрузчика. Вы можете найти это значение в даташите для своего типа AVR. Убедитесь, что вы используете адрес байта, а не адрес слова (большинство даташитов от Atmel определяют адреса флеш-памяти списком из слов, но некоторые списками байт). Вместо жесткого указанного начального адреса загрузчика я предлагаю объявить константу в файлеMakefile:
BOOTSTART = 0x3000 LDFLAGS += -Wl,--section-start=.text=$(BOOTSTART)
Это описано в даташитах, но название этих терминов может запутать. NRWW и RWW обозначают разные области флэш-памяти микроконтроллеров и определяют что происходит с процессором в то время как к указанной области памяти происходит обращение на чтение или запись. Загрузчик всегда находится в в области no-read-while-write или NRWW (буквально не читать пока писать). Очень часто область NRWW резервируют полностью под загрузчик, но это вовсе не обязательно. Когда происходит стирание или запись в области памяти NRWW процессор останавливается, потому что задействуется режим «не-чтения» (no-read). Сами приложения, как правило, хранятся в области read-while-write (RWW) что буквально означает читать пока писать. Пока производится запись или стирание в области RWW процессор может продолжать работать до тех пор, пока процессор исполняет код расположенный в разделе NRWW (в области загрузчика). Пока область памяти RWW перепрограммируется любые попытки считать из нее данные будут возвращать 0xFF. Прежде чем область RWW снова станет доступна для чтения после программирования область необходимо переактивировать (подробнее об этом в вопрос № 3).
Каким образом загрузчик получит программу для перепрограммирования контроллера зависит от вас. Распространенными каналами связи являются UART и USB. Протокол обмена данными по каналу может быть собственный или стандартный, наподобие AVR109 или DFU. При реализации стандартных протоколов можно использовать уже существующие инструменты, такие как AVR Studio, работающий по протоколу AVR109 или Atmel’s Flip работающий по протоколу DFU. Тем не менее эти стандартные протоколы как правило немного раздуты, а при реализации простого пользовательского протокола загрузчик как правило будет меньше размером и чище. Правда в таком случае вам понадобится также разработать собственное решение для передачи данных загрузчику.
_safe
, чтобы не забыть применять ожидание готовности MCU;boot_page_fill
принимает адрес в байтах, но пишет слово за один раз. В цикле необходимоboot_rww_enable
С AVR это невозможно. Для этого вам потребуется внешний программатор.
Несмотря на свое название макрос BOOTLOADER_SECTION не является полезным при написании загрузчика. Этот макрос просто помогает вам переназначить позицию вашей функции в раздел NRWW флеш-памяти. Вероятно, для этого макроса более подходящим названием является что-то вроде NRWW_SECTION.
Так как загрузчику не просто обновить самого себя, а приложения практически никогда не изменяют загрузчик, то вам потребуется внешний программатор для записи загрузчика в микроконтроллер. В качестве примера программаторов можно привести STK500, AVRISP, AVR Dragon, JTAGICE MKII и т.д. Режимы программирования вы будете задавать в зависимости от вашего типа AVR. Вам не нужно делать ничего особенного, чтобы прошить в микроконтроллер загрузчик. Если у вас есть рабочий внешний программатор, который может заливать прошивки и менять фьюзы, то он вполне подойдет и для заливки загрузчика. Программатор просто читает файл прошивки с загрузчиком и записывает его по указанному адресу. Программатору все равно, что вам посчастливилось писать в область NRWW флеш-памяти.
Так как, надеюсь, вы создали отдельно приложение и загрузчик отдельно, то в конечном итоге у вас появится два отдельных шестнадцатеричных файла с прошивками (например, app.hex и boot.hex). Возникает вопрос: как залить их обе в AVR. Существует несколько вариантов:
В два этапа: Залейте загрузчик с помощью внешнего программатора как описано в Вопросе № 6. Возможно, вам при этом потребуется также установить требуемые значения фьюзов включаяBOOTSZ
иBOOTRST
. После этого можно просто использовать обычный механизм связи загрузчика для передачи и прошивки в микроконтроллер приложения.В один этап: Объединить файлы app.hex и boot.hex в один и использовать внешний программатор для записи объединенного файла в микроконтроллер. Возможно, вам при этом потребуется также установить требуемые значения фьюзов включаяBOOTSZ
иBOOTRST
.
srec_cat
. Этот инструмент входит в набор инструментов srecord
. Он поставляется с WinAVR и очень легко устанавливается на Unix-подобных ОС. Вот команда, которую вы должны при этом использовать:srec_cat app.hex -I boot.hex -I -o combined.hex -I
Также вы можете вручную объединить файлы app.hex и boot.hex файлов с небольшими правками:
Да, для этого достаточно сказать процессору, чтобы тот использовал вектор прерываний, расположенный в области загрузчика а не в области приложения. Для этого где-то в начале кода загрузчика (до использования прерываний), надо добавить строки вроде этих:
MCUCR = (1<<IVCE); MCUCR = (1<<IVSEL);
После чего во время прерываний программный счетчик будет переведен в соответствующее положение согласно таблице прерываний загрузчика. Для нормальной работы, приведенная выше последовательность кода должна быть скомпилирована с включенной оптимизацией, также вручную требуется удостовериться, что в результирующую сборку записаны команды, выполняемые за 4 машинных цикла.
Для лучшей работы загрузчиков необходимо чтобы они представляли собой небольшие, надежные и редко изменяемые программы. С того дня как вы отправили свое устройство в эксплуатацию вам действительно больше не захочется изменить на нем загрузчик. Приложение же наоборот требует больше свободы в плане обновления. Если в приложении возникает ошибка, приводящая к отказу или зависанию, или во время заливки прошивки произойдет сбой питания надежный загрузчик, который будет запускаться первым сможет без проблем исправить данную ситуацию.
Есть много возможностей. Если устройство имеет кнопку или другой механизм ввода, вы можете послать соответствующий сигнал нажатием. Тогда загрузчик, как правило, запустит приложение сразу. А в случает если кнопка будет не нажата во время сброса, то загрузчик продолжает исполнение. Это пример того как работает загрузчик чипа STK500.
// Bootloader code const uint8_t app_run = eeprom_read_byte(ADDR_APP_RUN); if(app_run == APP_RUN) { // In case the app is faulty, clear the eeprom byte so that // the BL will run next time. A properly running app should // set this back to APP_RUN. eeprom_write_byte(ADDR_APP_RUN, 0xFF); run_application(); } // Only run the bootloader once, then go back to the app // (comment out the next line during app development) eeprom_write_byte(ADDR_APP_RUN, APP_RUN);
Затем где-то подальше в приложении, в той части кода, при обработке которого вы уже точно уверены, что приложение работает правильно добавьте следующее:
// Application code eeprom_write_byte(ADDR_APP_RUN, APP_RUN);
Это работает следующим образом: если приложение было с успехом исполнено ранее, то загрузчик будет читать
байт APP_RUN подготавливать к переходу к исполнению приложения. Однако незадолго до запуска приложения байт APP_RUN очищается. Приложение затем снова пишет байт APP_RUN в тот момент когда она считает, что она работает правильно. Таким образом последовательность повторяется. Если с приложением до сброса произошел сбой до того момента как оно производит сброс байта APP_RUN, то после перезагрузки загрузчик будет оставаться на исполнении. Если вы закомментируете указанную выше строку в коде загрузчика, то загрузчик будет оставаться на исполнении только один раз, а в следующие разы после сброса будет запускать приложение.
Существует на самом деле только один способ, вы должны переместить программный счетчик в начало приложения (которое, как правило, является вектором сброса). Если у вас есть приложение на основе
обычной библиотеки AVR-Libc, то в начале вашего приложения будет расположен рантайм языка с, который инициализирует стек и глобальные переменные, а затем располагается ваша функция «main». Вы можете переместить счетчик программы с помощью ассемблерной команды jump или вызовом функции, расположенной по нулевому адресу. Я выбрал команду jump:
asm("jmp 0000");
Disable_interrupt(); Wdt_change_16ms(); while(1);
Disable_interrupt(); // Shutdown USB cleanly Usb_detach(); Usb_disable(); Stop_pll(); // Put interrupts back in app land MCUCR = (1<<IVCE); MCUCR = 0; // Run the applicat
Продолжение: AVR BootLoader в вопросах и ответах. Часть 2