Мы продолжаем цикл статей по 32-разрядным микроконтроллерам компании Megawin на ядре Cortex-M0. В первой статье были рассмотрены основные возможности МК серии MG32F02, структурная схема, ЦПУ, адресное пространство, тактирование, сброс и прерывания. В этой статье будут рассмотрены: средства разработки и программирования МК на основе ПО с открытым исходным кодом, контроллер flash-памяти МК, пакет OpenOCD для взаимодействия с программатором ST-LINK, модуль GPIO и простейшая программа управления светодиодом. Компания Megawin предоставляет следующее ПО в исходном коде для разработки на базе МК серии MG32F02: пакет поддержки Device Family Pack (DFP) для интегрированной среды разработки Keil µVision IDE, загружаемый через саму IDE; отдельный архив ПО Development Kit (DK), включающий также пакет DFP в виде zip-архива. На момент написания статьи версия DFP — 3.2.1, DK — 4.31. Компания также предоставляет собственный программатор MLink отдельно или в составе отладочных комплектов, ориентированный на работу через Keil µVision IDE. В данном цикле статей предлагается нестандартное решение для Megawin — проводить разработку с применением иных инструментов. Мы будем использовать: популярный программатор-отладчик ST-LINK V2 с интерфейсом SWD (который умеет работать не только с микроконтроллерами STM32); OpenOCD для взаимодействия с МК через ST-Link; стандартный компилятор gcc-arm от ARM; любой привычный для читателя редактор или IDE для написания кода. Во всех примерах далее в качестве хоста разработки рассматривается ОС Linux, конкретно openSUSE. Полагаю, что читателю не составит труда повторить упражнения на другой версии Linux или на Windows. Перед началом практической части рассмотрим еще две важных подсистемы МК: контроллер памяти и порты ввода-вывода с модулем GPIO. Для дальнейших экспериментов нам потребуются некоторые знания по контроллеру памяти МК. Адресное пространство и области памяти были рассмотрены в предыдущей статье цикла. Контроллер памяти обеспечивает запись и стирание flash-памяти, а также контроль доступа к областям flash и SRAM. Прежде всего, рассмотрим конфигурационные ячейки OB0. В таблице приведен формат ячеек OB0 и соответствующих полей регистров CFG_ORx: Параметр конфигурации Описание Размер Инвертируется Адрес OB0 Биты Регистры CFG_ORxx Биты BOOT_MS Область памяти для загрузки системы после холодного сброса,отображается на адрес 0x00000000:0 - flash-область AP,1 - flash-область ISP (бутлоадер) (по-умолчанию), 2 - встроенное ОЗУ (SRAM),3 - зарезервировано 2 да 0x1FF10000 0-1 CFG_OR00 (0x4FF00010) 0-1 LOCK_DIS Блокировка flash-памяти:0 - чтение, стирание и запись памяти заблокированы,1 - блокировка отключена  (по-умолчанию) 1 нет 0x1FF10000 8 CFG_OR00 (0x4FF00010) 8 IAP_SIZE Размер области IAP в единицах 1024 байта для MG32F02A032,в единицах 512 байт для остальных МК (по-умолчанию 0 - области нет) 16 да 0x1FF10004 8-15 CFG_OR01 (0x4FF00014) 8-15 ISP_SIZE Размер области ISP в единицах 1024 байта для MG32F02A032,в единицах 512 байт для остальных МК (по-умолчанию 2 кбайт) 16 да 0x1FF10008 8-15 CFG_OR02 (0x4FF00018) 8-15 Значения полей ячеек OB0 копируются в соответствующие поля регистров CFG_ORx (некоторые при этом инвертируются) после выполнения холодного сброса. Затем поле BOOT_MS регистра CFG_OR0 копируется в поле BOOT_MS регистра контроллера памяти MEM_CR0, значения других полей CFG_ORx также копируются в другие системные регистры. При "софтовом", или "горячем" сбросе ячейки OB0 уже не используются, поэтому бутлоадер может, например, переключить основную память с ISP-области на AP-область перед выполнением "горячего" сброса. Принцип контроля доступа приведен в таблице: Откуда выполняется команда Доступ к ISP Доступ к ISPD Доступ к AP Доступ к IAP Доступ к SRAM OpenOCD через ST-LINK чтение при ISP_REN=1(если выполняется код ISP) чтение при ISPD_REN=1(если выполняется код ISP) чтение всегда,запись при AP_WEN=1 чтение всегда,запись при IAP_WEN=1 полный Область кода ISP только чтение чтение всегда,запись при ISPD_WEN=1 чтение всегда,запись при AP_WEN=1 чтение всегда,запись при IAP_WEN=1 полный Область кода AP чтение при ISP_REN=1,запись при ISP_WEN=1 чтение при ISPD_REN=1 только чтение чтение всегда,запись при IAP_WEN=1 полный SRAM чтение при ISP_REN=1,запись при ISP_WEN=1 чтение при ISPD_REN=1 чтение всегда,запись при AP_WEN=1 чтение всегда,запись при IAP_WEN=1 полный Изменение самих управляющих регистров MEM_CR0 и MEM_CR1 возможно только по SWD или из области ISP, при условии разблокировки доступа к ним через специальный регистр MEM_KEY (реализовано в командах mem_unlock и mem_unlock2 предлагаемой библиотеки). Согласно документации, область памяти OBx может стираться и программироваться только через фирменный программатор/отладчик по интерфейсу SWD. В User Guide приведены алгоритмы стирания и программирования областей AP/IAP и ISP/ISPD. В процессе написания библиотеки mg32f02_mem_ap.tcl выяснилось, что работоспособен только первый алгоритм, т.е. для AP/IAP. Доступ к областям ISP/ISPD по записи через OpenOCD и ST-LINK пока получить не удалось. Полный доступ к МК обеспечивает программа ISP32_Programmer через фирменный программатор/отладчик MLink. Однако ISP32_Programmer не позволяет что-либо считывать из МК, даже конфигурационные ячейки OBx. Имеется возможность только всё стереть и записать заново (судя по всему, программа всегда выполняет "chip erase"). Чтение ячеек OBx и (если разрешено) области ISP возможно через OpenOCD. С завода МК поставляются с прошитым бутлоадером. Бутлоадер при старте, если к нему не обращается хост, переключает путем записи 0 в MEM_BOOT_MS отображение области AP на адрес 0x00000000 и передает ей управление через "горячий" сброс. В итоге конфигурация получается следующая: Конфигурация BOOT_MS LOCK_DIS IAP_SIZE ISP_SIZE Область OB0 1 (ISP) 1 (нет блокировки) 0 2 (2 кбайт) Регистры ORx 1 (ISP) 1 (нет блокировки) 0 2 (2 кбайт) Регистр MEM_CR0 0 (AP) - - - МК серии MG32F02 включают до пяти портов ввода-вывода от PA до PE (см. сравнительную таблицу в п. "Состав серии MG32F02" предыдущей статьи). Максимальное число выводов каждого порта — 16. Доступность конкретных выводов зависит от типа МК и корпуса (см. даташит на конкретный МК). Все порты могут работать во всех трех режимах питания: ON, SLEEP и STOP. На следующем рисунке показана общая структурная схема модуля GPIO (Px — порты PA, PB, PC, ..., n — номер вывода). Блок управления режимом вывода (т.е. "пина") IOM (Input-Output Mode Control) определяет физический режим работы конкретного вывода МК. Блок IOP (IO Port Access Control) управляет доступом в режиме чтения и записи. Блок выбора альтернативных функций AFS (Alternate Function Select) позволяет скоммутировать конкретные выводы МК на те или иные периферийные модули. Выбор альтернативных выводов сильно ограничен и иногда может просто отсутствовать. За подробной информацией следует обратиться к даташиту на конкретный тип МК. Каждый из выводов может быть также сконфигурирован как источник сигнала внешнего прерывания для контроллера прерываний EXIC. Вывод может быть также переключен на один из аналоговых модулей (АЦП, ЦАП, компаратор). Рассмотрим более подробно блок IOM. Блок позволяет сконфигурировать каждый вывод независимо на следующие режимы работы: двухтактный ("пуш-пульный") выход (Push-Pull output), квази-двунаправленный вывод (Quasi bidirectional) (только для порта PC), выход с открытым стоком (Open-drain output), вход с высоким импедансом (Input only with high impedance), аналоговый вывод. Входной сигнал вывода, настроенного как цифровой вход, поступает на вход триггера Шмитта. На выходе триггера формируется сигнал IO_DI, который далее может быть обработан схемой подавления импульсных помех (Deglitch Filter) (по сути ФНЧ) с настраиваемой частотой дискретизации (3 варианта), определяемой полем Px_FDIVn соответствующего регистра настройки порта. Выбор опорной частоты ФНЧ определяется мультиплексором на весь порт и задается полем Px_FCKS регистров Px_FLT из возможных источников CLK_AHB, CLK_ILRCO, TM00_TRGO. Кроме того, каждый вывод, настроенный как цифровой вход, может быть аппаратно инвертирован установкой бита Px_INVn соответствующего регистра. Электрическая схема внутренних элементов для управления выводом IO Pad приведена ниже. Каждый вывод имеет возможность подключения резистора "подтяжки" номиналом 12,5 кОм (для MG32F02A032) или 13,3 кОм (для остальных МК) (кроме вывода сброса RSTN PC6 с номиналом 250 кОм для всех МК). Максимальный выходной ток двухтактного каскада программируется и в зависимости от МК и порта может настраиваться на 2 или 4 варианта в диапазоне от 3 до 39 мА. За конфигурацию каждого отдельного вывода отвечает один из регистров группы Port Configure Register PA_CR0...PA_CR15, PB_CR0...PB_CR15, ..., PE_CR0...PE_CR15. Регистры в целом имеют общий формат, приведенный в таблице ниже (зарезервированные разряды не представлены), однако смысл значений полей может отличаться. Все регистры имеют значения по-умолчанию (при сбросе) 0 и настроены как аналоговые входы, кроме регистров PC_CR4, PC_CR5 и PC_CR6, которые соответствуют сигналам SWCLK, SWDIO и RSTN и настроены как квази-двунаправленные выводы с подтяжкой (значение 0x00000024). Конфигурация этих выводов порта PC при POR-сбросе МК определяется ячейками конфигурации OB0 (поле PC_IOM). Биты Поле Описание 0-2 Px_IOMn Основной режим:0 - аналоговый (AIO),1 - выход с открытым стоком (ODO),2 - двухтактный выход (PPO),3 - цифровой вход (DIN),4 - квази-двунаправленный вывод (QB) (только для порта PC) 3 Px_HSn Разрешение высокоскоростного режима 5 Px_PUn Включение резистора подтяжки 7 Px_INVn Включение инвертирования цифрового входа 9 Px_ODCn Выбор ограничения выходного тока для выхода типа PPO (0 - макс. значение) 10-11 Px_FDIVn Включение схемы подавления импульсных помех и выбор частоты ФНЧ (значения см. в документе Registers, 0 - фильтр выключен) 12-15 Px_AFSn Выбор альтернативной функции вывода, т.е. выбор периферийного модуля, связанного с выводом По-умолчанию все выводы портов в поле Px_AFSn сконфигурированы как GPIO (значение 0), т.е. не подключены к какому-либо периферийному модулю и могут управляться программно. Для управления выводами используются регистры группы Port Control Registers. Логическое состояние входов порта, т.е. сигнала IO_DI, можно узнать чтением регистра Px_IN. Каждый разряд 0-15 в регистре соответствует выводу порта Px0-Px15, разряды 16-31 не используются. Для установки выходов существуют три способа. Регистр Px_OUT применяется для управления всеми выводами порта Px сразу. Регистр доступен по чтению и записи. При записи 16-битного полуслова или 32-битного слова все сконфигурированные как GPIO выводы порта устанавливаются в логическое состояние соответствующих разрядов регистра. Регистр Px_SC (Set/Clear) удобно использовать для установки или сброса отдельных битов порта, не затрагивая остальные. Регистр разбит на две части: младшие разряды 0-15 используются для установки в лог. "1" выводов 0-15 порта Px путем записи лог. "1" в соответствующий разряд. Старшие разряды 16-31 используются для сброса выводов 0-15 в лог. "0" также путем записи лог. "1". Состояние выводов, разряды которых при записи были нулевыми, не изменяется. Таким образом, записью одного 32-битного слова можно  установить в нужное состояние только отдельные выводы порта. Регистры Px_SCR0-Px_SCR3 предназначены для байт-ориентированного обращения к порту. В каждом из регистров используется только четыре разряда с номерами 0, 8, 16 и 24, т.е. четыре байта для непосредственного управления выводами. Таким образом, всего доступно 16 байт с используемым только младшим разрядом для каждого из выводов порта Px. Регистры доступны как по записи, так и по чтению. Для обращения к регистрам удобно применять побайтовые инструкции пересылки. Режим квази-двунаправленного вывода реализуется аппаратно с помощью изображенных на схеме четырех транзисторов. Программного переключения режима на вход или на выход не требуется. Когда вывод нужно переключить в состояние лог. "0", включается "сильная" подтяжка к нулю (Strong_N). При переключении вывода (как выхода) из состояния лог. "0" в состояние лог. "1" (например, через регистр Px_OUT) для ускорения переключения на время одного такта сигнала CK_SYS включается "сильная" подтяжка (Strong_P). Далее вывод поддерживается в состоянии лог. "1" включением "слабой" (Weak) подтяжки, что позволяет внешнему устройству перевести его в лог. "0". Если это происходит, т.е. в регистре записано состояние лог. "1", а на самом выводе присутствует лог. "0", подключается "очень слабая" подтяжка (Very Weak). Тем самым сокращается до минимума ток через вывод, а сам вывод не окажется "в воздухе" при отключении лог. "0" у внешнего устройства. Перед началом работы с модулем GPIO необходимо разрешить тактирование каждого используемого порта PA, PB, ...PE установкой соответственно битов CSC_IOPA_EN, CSC_IOPB_EN, ... CSC_IOPE_EN в регистре CSC_AHB. Для первого практического знакомства с серией MG32F02 автором выбран МК MG32F02A032 в корпусе TSSOP20. Схема подключения МК показана на рисунке. В данном случае МК (U1) тактируется внутренним RC-генератором (IHRCO), поэтому кварцевый резонатор не подключен. Фильтрующие емкости C1-C4 подключены согласно рекомендациям User Guide. Через вывод VDD (5) подается напряжение питания 3,3 В, емкости C3, C4 подключены к выходу внутреннего LDO стабилизатора питания ядра VR0 (4). Для простейшего управления МК в схеме предусмотрены две кнопки S1 и S2, кнопка S3 для сброса опциональна. Все входы будут запрограммированы на внутренние "подтяжки". К МК также подключен контрольный светодиод D1. Схема подключается к программатору/отладчику ST-LINK V2 через разъем J1, от него же МК и запитывается. МК можно распаять, например, на плате-адаптере SSOP->DIP, чтобы было удобно монтировать на любую макетную плату. Внешний вид получившейся макетной платы показан на рисунке (без внешних соединений): Автором успешно были опробованы программаторы ST_LINK V2 китайского происхождения с версиями firmware V2J29S7, V2J39S7. О них написано очень много, в том числе на хабре, поэтому повторяться не буду. Для описываемых задач никакой доработки программатора не потребуется. OpenOCD (Open On-Chip Debugger) представляет собой ПО для взаимодействия с микроконтроллерами, flash-памятью или ПЛИС для отладки и программирования. Особенностями ПО является: открытый исходный код и кроссплатформенность, поддержка большого числа аппаратных программаторов и отладчиков, прежде всего с интерфейсами JTAG и SWD, поддержка большого числа МК от 8-разрядных AVR до 64-разрядных Cortex-A, архитектура клиент-сервер, встроенный интерпретатор языка Tcl (Jim-Tcl), на котором написаны все конфигурационные файлы, обобщенный программный интерфейс (API) для отладки и программирования различных микросхем, поддержка отладчика GDB. OpenOCD легко встраивается в различные IDE благодаря своей модульности и архитектуре клиент-сервер. Для желающих подробнее изучить ПО рекомендуется использовать самую последнюю версию User Guide, поскольку в релиз-версии нет описания некоторых необходимых нам команд (например, write_memory и read_memory). На сайте OpenOCD можно найти ссылки для скачивания уже собранного ПО или же исходный код для самостоятельной сборки, которая обычно не вызывает затруднений. Общая схема взаимодействия ПО и МК при работе OpenOCD представлена на рисунке. В наших упражнениях по освоению МК серии MG32F02 мы будем управлять сервером OpenOCD через telnet-соединение, предварительно запустив на нем конфигурационные скрипты: mg32f02a.cfg — общий скрипт для серии MG32F02, включает библиотеку mg32f02_mem_ap.tcl; mg32f02a032.cfg — скрипт для тестируемого МК MG32F02A032, вызывает mg32f02a.cfg. OpenOCD поддерживает общие команды SWD для отладки МК, чтения данных из flash-памяти, ОЗУ или регистров, а также запись данных в ОЗУ и регистры. Однако для стирания и программирования flash-памяти под каждый конкретный тип МК требуется специальный низкоуровневый драйвер. К сожалению, на данный момент драйвер для работы с falsh-памятью МК Megawin серии MG32F02 никто не написал, поэтому автором разработана библиотека на языке Tcl mg32f02_mem_ap.tcl, позволяющая стирать и программировать AP-область flash-памяти. Библиотека не является полноценным драйвером OpenOCD (который должен быть написан на Си), однако успешно его заменяет. Чтобы не было путаницы, имена команд в библиотеке сделаны отличными от принятых в данном ПО. Библиотека mg32f02_mem_ap.tcl автоматически загружается из скрипта mg32f02a.cfg и предоставляет следующие команды верхнего уровня: print_memstatus — вывод в терминал текущего статуса контроллера памяти; print_memcfg — вывод в терминал основных параметров конфигурации OB0, CFG и MEM; mem_unlock, mem_unlock2 — разблокировка возможности модификации регистров MEM_xxx; mem_lock — блокировка возможности модификации регистров MEM_xxx; mem_isp_ren — включение доступа по чтению в область ISP; mem_ap_flash — основная команда для прошивки образа AP с автоматическим стиранием страниц. Также для удобства дальнейшей работы приводим краткую справку по используемым стандартным командам OpenOCD: Команда Аргументы Описание halt [ms] время, мс (опционально) Останов работы ЦПУ с опциональным ожиданием. resume [add] адрес (опционально) Возобновление работы ЦПУ с опциональным адресом. reset - Сброс МК. reg [number [value]]reg [number [force]] number - номер или имя регистра,value - значение,force - читать сразу из чипа, минуя внутренний кэш Запрос (или установка) значений регистров ЦПУ. Без аргументов выводит содержимое всех регистров. mdd addr [count]mdw addr [count]mdh addr [count]mdb addr [count] addr - начальный адрес,count - число ячеек Дамп памяти МК в терминал с опциональным указанием числа ячеек:mdd - словами по 64-бита,mdw - словами по 32 бита,mdh - словами по 16 бит,mdb - байтами. dump_image filename addr size addr - начальный адресsize - размер дампа в байтах Дамп памяти МК в бинарный файл. load_image filename addr [[format] min_addr max_length] addr - адрес загрузки,format - формат файла (bin,ihex,elf,s19)min_addr - начальный адрес,max_length - макс. число байт для загрузки Загрузка данных из файла в память МК с указанием формата файла. read_memory addr width count addr - адрес,width - размер ячейки в битах (8,16,32,64),count - кол-во ячеек для чтения Чтение из памяти с произвольного адреса с указанием размера ячеек и количества. Возвращает Tcl-список с данными. write_memory addr width data addr - адрес,width - размер ячейки в битах (8,16,32,64),data - Tcl-список данных для записи Запись в память с указанием размера ячеек. exit - Завершение работы клиента telnet. Если у вас уже собрана схема и подключен ST-LINK, можно попробовать к нему подключиться. Предположим, OpenOCD установлен в каталог /opt/openocd. Нужно поместить в какой-либо "проектный" каталог указанные выше три файла mg32f02a.cfg, mg32f02a032.cfg и mg32f02_mem_ap.tcl, после чего из этого каталога запустить сервер командой Параметр -s указывает корневой каталог OpenOCD, относительно которого программа будет искать конфигурационные файлы. Его нужно указывать, если openocd запускается не из своего каталога, как в нашем случае. Параметр -d3 устанавливает debug level на 3, что полезно указать в начале экспериментов для получения более полной отладочной информации. Следующий аргумент -f interface/stlink.cfg определяет конфигурационный файл адаптера, т.е. интерфейс, через который OpenOCD будет связываться с чипом, в данном случае ST-LINK. Последний аргумент -f mg32f02a032.cfg определяет конфигурационный файл чипа (или отладочной платы), который нужно программировать. В принципе, таким образом можно указать и другие конфигурационные сценарии на Tcl, которые нужно прочитать при старте сервера. В нашем случае такой сценарий есть — это библиотека mg32f02_mem_ap.tcl, но она будет подключена внутри файла mg32f02a.cfg. В случае успешного запуска в терминале будут выведены примерно следующие сообщения: Далее запускаем telnet-клиента для "общения" с OpenOCD (рабочий каталог оставляем тем же): Теперь можно проверить работу OpenOCD, например, через следующую команду: Ответ должен быть примерно таким: Далее, например, можно просмотреть регистры ЦПУ через команду reg. Для возобновления работы ЦПУ нужно дать команду resume. Проверим работоспособность библиотеки. Запросим настройки памяти (переводить МК в halt необязательно): Результат для "нового" МК скорее всего будет такой: т.е. старт через бутлоадер (ORx: BOOT_MS=1) с выделенной областью ISP в 2 кбайт (ISP_SIZE=2) и перезагрузка в область приложения AP (MEM: BOOT_MS=0). Можно вывести дамп памяти AP области (ограничим 16 словами по 32 бита): Результат будет примерно такой: В случае загрузки из области AP команда дампа с нулевого адреса должна дать точно такой же результат, поскольку область AP отображается на адрес 0. Напомним читателю, что первое слово здесь определяет начальное значение стека, а далее идут векторы прерываний (исключений), первый из которых — Reset. Все адреса нечетные, потому что в Cortex-M0 требуется устанавливать младший бит адреса перехода в единицу в качестве индикатора используемого набора инструкций Thumb. Аналогично, например, можно посмотреть ОЗУ (результат работы команды не приводится): а также значения всех доступных по чтению регистров периферии. В случае возникновения ошибки стоит посмотреть вывод в первом терминале, где запущен сервер openocd. Если с ST-LINK и МК связь установлена, можно переходить собственно к разработке первой прошивки. ARM Debug Interface v5 Architecture Specification Jim Tcl Documentation Tcl Commands Пакет разработки от Megawin включает исходный код под Keil µVision IDE, однако ничто не мешает его использовать и для gcc-arm. Кратко рассмотрим структуру DFP и DK. Каталог(и) Описание DFP/Device/MG32x02z/Common Включает в основном общий код инициализации МК и реализации ISR (обработчики прерываний) на Си, а также startup файл на ассемблере Keil. DFP/Device/MG32x02z/MG32F02Axxx Включают файлы, специфичные для конкретного типа МК. DFP/Device/MG32x02z/MG32F02A_Driver Повторяет содержимое каталога DK/Code/Drivers, но может иметь файлы более ранней версии, чем файлы DK. DFP/Device/MG32x02z/MG32F02A_Middleware Повторяет содержимое каталога DK/Code/Middlewares, но также может быть "старее", чем файлы DK. DFP/Device/MG32x02z/Package Содержит заголовочные файлы, специфичные для конкретного типа МК и типа его корпуса. DK/Code/Drivers Низкоуровневые драйверы периферийных модулей DK/Code/Middlewares Функции уровня "middleware" Необходимые каталоги будут указываться в файле сборки проекта premake5. Замечания по поводу исходного кода от Megawin при разработке в Linux. В файлах Middleware MG32x02z_CSC_MID.h и MG32x02z_WWDT_MID.h необходимо исправить регистр имени включаемого файла Правильное название файла — MG32x02z_Common_MID.h, что принципиально в UNIX-системах с регистрозависимостью имен файлов. Также подобные ошибки обнаружены в некоторых *.c файлах в каталоге Samples. В первых примерах мы будем использовать только один заголовочный файл, содержащий адреса всех периферийных регистров МК. В качестве компилятора будем использовать официальный gcc от ARM gcc-arm-none-eabi-10.3-2021.10. Ссылки для скачивания последних версий для различных хост-платформ и ОС можно найти здесь. Также для удобства сборки нам потребуется premake5, который отличается своей компактностью, высокой скоростью работы и простотой использования. Читатель может использовать любой другой привычный инструмент, например CMake или встроенные средства какой-либо IDE. В процессе разработки на gcc мы не будем придерживаться какой-то конкретной IDE или редактора (автором использует среда Code::Blocks). Для компиляции, сборки и отладки достаточно будет командной строки. Далее будем исходить из следующего расположения каталогов: /opt/arm/CMSIS — корневой каталог фреймворка CMSIS из DFP, включает подкаталоги CMSIS и Device; /opt/arm/megawin/DFP — корневой каталог DFP, включает подкаталог Device/MG32x02z, в котором находятся подкаталоги Common, MG32F02A032, MG32F02A128, MG32F02A132,  MG32F02A_Driver, MG32F02A_Middleware, MG32F02V032 и Package; /opt/arm/megawin/DK — корневой каталог DK, который включает подкаталоги Drivers и Middlewares; /opt/gcc-arm — корневой каталог gcc-arm. Прежде всего создадим скрипт premake5.lua в каталоге проекта (premake использует Lua в качестве скриптового языка): Скрипт включает множество настроек, не все из которых понадобятся для первого примера, однако они потребуются в будущем. По-умолчанию для всех целей сборки будет использоваться конфигурация "Debug". Поиск заголовочных файлов будет происходить в рассмотренных выше каталогах DFP и DK, а также в каталоге проекта (.) и подкаталоге src, куда и нужно будет поместить исходный код. Опции компиляции buildoptions, полагаю, не требуют комментариев. Опции линковки linkoptions предусматривают подкаталог ld, в который следует помещать все ld-скрипты. Итоговые бинарные файлы будут сохраняться в подкаталоге bin, включая форматы ELF и BIN. Кроме того, в опциях postbuildcommands предусмотрена генерация листинга дизассемблера из бинарного ELF-файла. Методику удобного просмотра листинга рассмотрим чуть позже. Первая цель main включает компиляцию следующих файлов: startup.c — исходный код для генерации таблицы векторов прерываний и части функций их обработки, init.c — исходный код инициализации МК, main.c — главная часть прошивки. Отметим, что в данном случае включается максимальная оптимизация по размеру и из кода мы убираем "все лишнее", даже исключаем стандартную библиотеку (-nostdlib). Это потребует от нас исключить использование глобальных переменных с инициализацией вне функций. Для сборки итогового бинарного образа прошивки будет использован отдельный ld-скрипт mg32f02a032.ld. Он имеет довольно стандартный вид для МК на ядре ARM, приведем лишь основную его часть, связанную с определением областей памяти: Размеры областей FLASH (в данном случае целиком AP или ISP) и RAM читатель может скорректировать согласно специфике своего проекта. Приведем перечень команд сборки: premake5 gmake — подготовка Makefile, make <цель> — сборка указанной цели (у нас пока только единственная цель main), make <цель> verbose=1 — сборка с подробным выводом командных строк. Для более удобного просмотра листинга дизассемблера в режиме синтаксического цветовыделения автором предлагается использовать утилиту source-highlight (в Linux поставляется пакетом с одноименным названием). Под набор инструкций Thumb для нее разработан файл синтаксического выделения objdump-armthumb.lang. Можно достичь более адекватного цветовыделения, если предварительно добавить символ ; в начало всех строк с комментариями с помощью sed. В итоге просматривать листинг можно с помощью shell-скрипта disview: Здесь disview.style — стилевой файл для задания конкретных цветов. DFP и DK от Megawin включают startup код только на ассемблере Keil, поэтому потребовалось реализовать альтернативный вариант на Си под gcc-arm. В предлагаемом файле startup.c "прописываются" все обработчики исключений согласно таблице из п. "Прерывания" первой статьи цикла. Обработчик исключения Reset(1) имеет следующий вид: Здесь для первого примера указан только вызов функции инициализации подсистемы тактирования init_clock(), после чего управление передается функции main(). Выход из нее не предусмотрен, поэтому до цикла while (1) управление дойти не должно, он указан здесь чисто символически. Большинство остальных обработчиков представляют собой объявления типа Атрибут weak позволяет переопределить эту функцию в других объектных файлах, которые могут быть включены в сборку. Атрибут alias("Default_Handler") вместо реальной функции подставляет адрес функции Default_Handler() (т.е. обработчик по-умолчанию, функция-"заглушка"), объявленной в этом же файле как Функция init_clock() объявлена в файле init.h и реализована в файле init.c. Приведем весь файл: В начале функции init_clock() выполняется разблокировка возможности записи в регистры CSC_xxx. Далее разрешается тактирование для портов PB и PC, которые будут использоваться в примере (порт PC — для SWD). И затем запись в регистры CSC_xxx блокируется. Никаких других настроек тактирования не производится, поэтому источником тактирования по-умолчанию остается генератор IHRCO с частотой 12,0000 МГц. Это простейший вариант инициализации, пригодный для нашего первого эксперимента. Файл MG32x02z__RegAddress.h содержит определения адресов всех регистров периферии. Он единственный файл из DFP и DK, который будет нам нужен в первом проекте (не считая тех, которые включает он сам). Наконец, перейдем к реализации основной части прошивки. Традиционный пример "Hello, world!" для МК усложним управлением кнопками. Задача: по нажатию кнопки S1 включается мигание светодиода D1, по нажатию кнопки S2 светодиод отключается. Приведем главный файл проекта main.c полностью: Здесь реализована простейшая функция задержки delay(), которая дает приблизительную задержку в 1 миллисекунду при аргументе со значением DKMS. Функция main() объявлена как naked для того, чтобы компилятор не добавлял бесполезные инструкции push и pop в начало и конец функции, поскольку, как говорилось ранее, выход из нее не предусмотрен. Флаговая переменная is_on определяет, включено мигание светодиода или нет. Переменная ds определяет временной интервал (200 мс) включения и отключения светодиода, т.е. примерно половину периода мигания. В начале функции, перед конфигурированием GPIO, сбрасывается разряд 2 порта PB, чтобы в исходном состоянии светодиод был выключен. Далее выводы 0 и 1 порта PB конфигурируются как цифровые входы (биты 0-2 в значении 3), а также включается подтяжка (бит 5 в значении 1). Вывод 2 порта PB конфигурируется как двухтактный выход (биты 0-2 в значении 2). Далее в цикле while (1) проверяется состояние входов 0 и 1 путем считывания регистра PB_IN и в зависимости от их состояния устанавливается флаг is_on. Если флаг активен, генерируется один период мигания светодиода путем отдельной установки/сброса бита 2 порта PB через "Set/Clear" регистр PB_SC. Подготовка Makefile выполняется только в первый раз или после изменения premake5.lua: Сборка прошивки выполняется командой В результате в каталоге bin должны быть созданы файлы: main — бинарный ELF-файл, main.bin — бинарный образ памяти, main.lst — листинг дизассемблера, main.map — MAP-файл линковки. Прошивка МК выполняется в telnet-соединении OpenOCD с помощью команды В случае успеха вывод команды будет примерно таким: Из вывода видно, что размер прошивки составляет всего 366 байт! Надо учесть, что в этот объем также входит 192 байта таблицы векторов. При программировании flash-памяти потребовалось стереть одну страницу, а также были добавлены 2 нулевых байта, чтобы размер образа был кратен 4 байтам. Если всё прошло без ошибок, МК должен начать выполнять поставленную задачу. В завершении приведем еще один пример, в котором используется программное ШИМ-управление яркостью светодиода. Кнопка S1 плавно увеличивает яркость, т.е. коэффициент заполнения k, а кнопка S2 уменьшает. Период ШИМ составляет примерно 1 мс. Достаточно заменить только функцию main(): В этом примере добавлено условие if ((q & 0x07)==0), которое предназначено для более плавного изменения k и снижает частоту опроса кнопок в 8 раз. В прилагаемом архиве собраны все файлы, упомянутые в статье. На этом мы завершаем вторую статью цикла. В следующий раз мы рассмотрим методику отладки кода в ОЗУ МК с базовой частью инициализации во флэш-памяти, управление тактированием МК, а также модуль UART в различных режимах работы.