Введение

Мы продолжаем цикл статей по 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)

-

-

-

Порты ввода-вывода и модуль GPIO

МК серии MG32F02 включают до пяти портов ввода-вывода от PA до PE (см. сравнительную таблицу в п. "Состав серии MG32F02" предыдущей статьи). Максимальное число выводов каждого порта — 16. Доступность конкретных выводов зависит от типа МК и корпуса (см. даташит на конкретный МК). Все порты могут работать во всех трех режимах питания: ON, SLEEP и STOP. На следующем рисунке показана общая структурная схема модуля GPIO (Px — порты PA, PB, PC, ..., n — номер вывода).

Общая структурная схема GPIO

Блок управления режимом вывода (т.е. "пина") IOM (Input-Output Mode Control) определяет физический режим работы конкретного вывода МК. Блок IOP (IO Port Access Control) управляет доступом в режиме чтения и записи. Блок выбора альтернативных функций AFS (Alternate Function Select) позволяет скоммутировать конкретные выводы МК на те или иные периферийные модули. Выбор альтернативных выводов сильно ограничен и иногда может просто отсутствовать. За подробной информацией следует обратиться к даташиту на конкретный тип МК. Каждый из выводов может быть также сконфигурирован как источник сигнала внешнего прерывания для контроллера прерываний EXIC. Вывод может быть также переключен на один из аналоговых модулей (АЦП, ЦАП, компаратор).

Рассмотрим более подробно блок IOM.

Структурная схема блока 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. Схема подключения МК показана на рисунке.

Схема подключения МК MG32F02A032

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

Макетная плата с МК MG32F02A032

Автором успешно были опробованы программаторы ST_LINK V2 китайского происхождения с версиями firmware V2J29S7, V2J39S7. О них написано очень много, в том числе на хабре, поэтому повторяться не буду. Для описываемых задач никакой доработки программатора не потребуется.

OpenOCD

Общие сведения

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 представлена на рисунке.

Схема 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

Если у вас уже собрана схема и подключен ST-LINK, можно попробовать к нему подключиться. Предположим, OpenOCD установлен в каталог /opt/openocd. Нужно поместить в какой-либо "проектный" каталог указанные выше три файла mg32f02a.cfg, mg32f02a032.cfg и mg32f02_mem_ap.tcl, после чего из этого каталога запустить сервер командой

/opt/openocd/bin/openocd -s /opt/openocd -d3 -f interface/stlink.cfg -f mg32f02a032.cfg

Параметр -s указывает корневой каталог OpenOCD, относительно которого программа будет искать конфигурационные файлы. Его нужно указывать, если openocd запускается не из своего каталога, как в нашем случае. Параметр -d3 устанавливает debug level на 3, что полезно указать в начале экспериментов для получения более полной отладочной информации.

Следующий аргумент -f interface/stlink.cfg определяет конфигурационный файл адаптера, т.е. интерфейс, через который OpenOCD будет связываться с чипом, в данном случае ST-LINK. Последний аргумент -f mg32f02a032.cfg определяет конфигурационный файл чипа (или отладочной платы), который нужно программировать. В принципе, таким образом можно указать и другие конфигурационные сценарии на Tcl, которые нужно прочитать при старте сервера. В нашем случае такой сценарий есть — это библиотека mg32f02_mem_ap.tcl, но она будет подключена внутри файла mg32f02a.cfg.

В случае успешного запуска в терминале будут выведены примерно следующие сообщения:

Info : 53 4 server.c:309 add_service(): Listening on port 6666 for tcl connections
Info : 54 4 server.c:309 add_service(): Listening on port 4444 for telnet connections
Info : 73 5 adapter.c:108 adapter_init(): clock speed 240 kHz
Info : 91 8 stlink_usb.c:1442 stlink_usb_version(): STLINK V2J39S7 (API v2) VID:PID 0483:3748
Info : 93 9 stlink_usb.c:1474 stlink_usb_check_voltage(): Target voltage: 3.230039
Info : 120 24 cortex_m.c:2328 cortex_m_examine(): [mg32f02a032.cpu] Cortex-M0 r0p0 processor detected
Info : 150 39 cortex_m.c:2442 cortex_m_examine(): [mg32f02a032.cpu] target has 4 breakpoints, 2 watchpoints
Info : 159 44 gdb_server.c:3716 gdb_target_start(): starting gdb server for mg32f02a032.cpu on 3333
Info : 160 44 server.c:309 add_service(): Listening on port 3333 for gdb connections

Далее запускаем telnet-клиента для "общения" с OpenOCD (рабочий каталог оставляем тем же):

telnet 127.0.0.1 4444

Теперь можно проверить работу OpenOCD, например, через следующую команду:

> halt

Ответ должен быть примерно таким:

target halted due to debug-request, current mode: Thread 
xPSR: 0x21000000 pc: 0x000000ee msp: 0x20000ff8

Далее, например, можно просмотреть регистры ЦПУ через команду reg. Для возобновления работы ЦПУ нужно дать команду resume.

Проверим работоспособность библиотеки. Запросим настройки памяти (переводить МК в halt необязательно):

> print_memcfg

Результат для "нового" МК скорее всего будет такой:

OB0: BOOT_MS=1 LOCK_DIS=1 IAP_SIZE=0 ISP_SIZE=2
ORx: BOOT_MS=1 LOCK_DIS=1 IAP_SIZE=0 ISP_SIZE=2
MEM: BOOT_MS=0 ISP_REN=0 ISP_WEN=0

т.е. старт через бутлоадер (ORx: BOOT_MS=1) с выделенной областью ISP в 2 кбайт (ISP_SIZE=2) и перезагрузка в область приложения AP (MEM: BOOT_MS=0).

Можно вывести дамп памяти AP области (ограничим 16 словами по 32 бита):

> mdw 0x18000000 16

Результат будет примерно такой:

0x18000000: 20001000 00000179 00000177 00000185 ffffffff ffffffff ffffffff ffffffff
0x18000020: ffffffff ffffffff ffffffff 00000175 ffffffff ffffffff 00000175 00000175

В случае загрузки из области AP команда дампа с нулевого адреса

> mdw 0x00000000 16

должна дать точно такой же результат, поскольку область AP отображается на адрес 0.

Напомним читателю, что первое слово здесь определяет начальное значение стека, а далее идут векторы прерываний (исключений), первый из которых — Reset. Все адреса нечетные, потому что в Cortex-M0 требуется устанавливать младший бит адреса перехода в единицу в качестве индикатора используемого набора инструкций Thumb.

Аналогично, например, можно посмотреть ОЗУ (результат работы команды не приводится):

> mdw 0x20000000 16

а также значения всех доступных по чтению регистров периферии.

В случае возникновения ошибки стоит посмотреть вывод в первом терминале, где запущен сервер openocd.

Если с ST-LINK и МК связь установлена, можно переходить собственно к разработке первой прошивки.

Ссылки для дальнейшего изучения

Программное обеспечение разработки

Структура DFP и DK

Пакет разработки от 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 необходимо исправить регистр имени включаемого файла

#include "MG32x02z_COMMON_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 в качестве скриптового языка):

workspace "Megawin"

-- MCPU name:
  MCPU = "cortex-m0"

-- Chip name:
  CHIP = "MG32F02A032"

-- Device Family Pack path:
  DFP_PATH =  "/opt/arm/megawin/DFP/Device/MG32x02z/"

-- Development Kit Drivers path
  DRV_PATH =  "/opt/arm/megawin/DFP/Device/MG32x02z/MG32F02A_Driver/"

-- Development Kit Middlewares path
  MID_PATH =  "/opt/arm/megawin/DFP/Device/MG32x02z/MG32F02A_Middleware/"

  DFP_INC = {DFP_PATH.."Common/Initial/Include", DFP_PATH..CHIP.."/Include", DFP_PATH.."Package/"..CHIP}

  DRV_INC =   DRV_PATH.."Include"
  DRV_SRC =   DRV_PATH.."Source/"

  MID_INC =   MID_PATH.."Include"
  MID_SRC =   MID_PATH.."Source/"

-- CMSIS paths:
  CMSIS_PATH = "/opt/arm/CMSIS/"
  CMSIS_INC = {CMSIS_PATH.."CMSIS/Core/Include"}

-- Linker script dir
  LDSCRIPT_PATH = "ld"

-- GCC toolchain prefix
  GCCPREFIX = "/opt/gcc-arm/bin/arm-none-eabi-"

  configurations {"Debug"}
  gccprefix (GCCPREFIX)
  defines {CHIP}
  includedirs {".", "src"}
  buildoptions {"-mthumb", "-mcpu="..MCPU, "-Wall", "-Os", "-g", "-fno-common", "-ffunction-sections", "-fdata-sections"}
  linkoptions {
    "-mthumb", "-mcpu="..MCPU,
    "-L"..LDSCRIPT_PATH,
    "-Wl,--undefined=arm_stack_area",
    "-Wl,-Map,%{cfg.buildtarget.relpath}.map"
  }
  targetdir "bin"
  postbuildcommands {
    GCCPREFIX.."objcopy -O binary %{cfg.buildtarget.relpath} %{cfg.buildtarget.relpath}.bin",
    GCCPREFIX.."objdump -Mforce-thumb -d -S -G %{cfg.buildtarget.relpath} > %{cfg.buildtarget.relpath}.lst",
    GCCPREFIX.."size %{cfg.buildtarget.relpath}"
  }

  includedirs {DFP_INC, DRV_INC, MID_INC, CMSIS_INC}
  symbols "On"

project "main"
  kind "ConsoleApp"
  language "C"
  files {"src/init.c", "src/startup.c", "src/main.c"}
  linkoptions { "-Wl,--gc-sections", "-nostdlib"}
  linkoptions {"-T mg32f02a032.ld"}

Скрипт включает множество настроек, не все из которых понадобятся для первого примера, однако они потребуются в будущем. По-умолчанию для всех целей сборки будет использоваться конфигурация "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, приведем лишь основную его часть, связанную с определением областей памяти:

MEMORY {
  /* MG32F02A032 */
  FLASH (rx) : ORIGIN = 0x0, LENGTH = 0x8000 /* 32K */
  RAM (rwx) : ORIGIN = 0x20000A00, LENGTH = 1536
}

Размеры областей FLASH (в данном случае целиком AP или ISP) и RAM читатель может скорректировать согласно специфике своего проекта.

Приведем перечень команд сборки:

  • premake5 gmake — подготовка Makefile,

  • make <цель> — сборка указанной цели (у нас пока только единственная цель main),

  • make <цель> verbose=1 — сборка с подробным выводом командных строк.

Просмотр листинга дизассемблера

Для более удобного просмотра листинга дизассемблера в режиме синтаксического цветовыделения автором предлагается использовать утилиту source-highlight (в Linux поставляется пакетом с одноименным названием). Под набор инструкций Thumb для нее разработан файл синтаксического выделения objdump-armthumb.lang. Можно достичь более адекватного цветовыделения, если предварительно добавить символ ; в начало всех строк с комментариями с помощью sed. В итоге просматривать листинг можно с помощью shell-скрипта disview:

sed -E -e '/^(\s+[0-9a-fA-F]+:)|([0-9a-fA-F]{8})/!s/^/;/' < $1 | source-highlight -s asm -f esc --lang-def=objdump-armthumb.lang --style-file disview.style | less

Здесь disview.style — стилевой файл для задания конкретных цветов.

Код инициализации МК

DFP и DK от Megawin включают startup код только на ассемблере Keil, поэтому потребовалось реализовать альтернативный вариант на Си под gcc-arm. В предлагаемом файле startup.c "прописываются" все обработчики исключений согласно таблице из п. "Прерывания" первой статьи цикла.

Обработчик исключения Reset(1) имеет следующий вид:

void __attribute__ ((interrupt)) __attribute__ ((noreturn)) Reset_Handler(void) {
  init_clock();
  main();
  while (1);
}

Здесь для первого примера указан только вызов функции инициализации подсистемы тактирования init_clock(), после чего управление передается функции main(). Выход из нее не предусмотрен, поэтому до цикла while (1) управление дойти не должно, он указан здесь чисто символически. Большинство остальных обработчиков представляют собой объявления типа

void __attribute__ ((interrupt, weak, alias("Default_Handler"))) SVC_Handler();

Атрибут weak позволяет переопределить эту функцию в других объектных файлах, которые могут быть включены в сборку. Атрибут alias("Default_Handler") вместо реальной функции подставляет адрес функции Default_Handler() (т.е. обработчик по-умолчанию, функция-"заглушка"), объявленной в этом же файле как

void __attribute__ ((interrupt)) Default_Handler(void) {}

Функция init_clock() объявлена в файле init.h и реализована в файле init.c. Приведем весь файл:

#include "init.h"
#include "MG32x02z__RegAddress.h"

void init_clock() {
  *((volatile uint16_t*)CSC_KEY_h0) = 0xA217; // unlock access to CSC regs
  *((volatile uint32_t*)CSC_AHB_w) |= 2 | 4;  // CSC_IOPB_EN = 1, CSC_IOPC_EN = 1
  *((volatile uint16_t*)CSC_KEY_h0) = 0x1111; // lock access to CSC regs
}

В начале функции init_clock() выполняется разблокировка возможности записи в регистры CSC_xxx. Далее разрешается тактирование для портов PB и PC, которые будут использоваться в примере (порт PC — для SWD). И затем запись в регистры CSC_xxx блокируется. Никаких других настроек тактирования не производится, поэтому источником тактирования по-умолчанию остается генератор IHRCO с частотой 12,0000 МГц. Это простейший вариант инициализации, пригодный для нашего первого эксперимента.

Файл MG32x02z__RegAddress.h содержит определения адресов всех регистров периферии. Он единственный файл из DFP и DK, который будет нам нужен в первом проекте (не считая тех, которые включает он сам).

Программа тестирования GPIO

Наконец, перейдем к реализации основной части прошивки. Традиционный пример "Hello, world!" для МК усложним управлением кнопками. Задача: по нажатию кнопки S1 включается мигание светодиода D1, по нажатию кнопки S2 светодиод отключается. Приведем главный файл проекта main.c полностью:

#include "MG32x02z__RegAddress.h"

// Множитель для delay (мс) для частоты Fo=12 МГц
#define DKMS (12000/9)

// Задержка. Аргумент в мс умножать на DKMS
void delay(uint32_t span) {
  for (int i=span; i; i--) __NOP();
}


__attribute__ ((naked)) // omit prologue/epilogue sequences (garbage push/pop instructions)
int main (void) {
  uint8_t is_on=0;
  uint32_t ds=200*DKMS;

  *(volatile uint16_t*)PB_SC_h1 = 0x0004; // clear bit 2
  *(volatile uint16_t*)PB_CR0_h0 = 0x0023; // digital input with pull-up
  *(volatile uint16_t*)PB_CR1_h0 = 0x0023; // digital input with pull-up
  *(volatile uint16_t*)PB_CR2_h0 = 0x0002; // push-pull output

  while (1) {
    if ( (*(volatile uint16_t*)PB_IN_h0 & 1)==0 ) is_on=1;
    if ( (*(volatile uint16_t*)PB_IN_h0 & 2)==0 ) is_on=0;

    if (is_on) {
      *(volatile uint16_t*)PB_SC_h0 = 0x0004; // set bit 2
      delay(ds);
      *(volatile uint16_t*)PB_SC_h1 = 0x0004; // clear bit 2
      delay(ds);
    }
  }
}

Здесь реализована простейшая функция задержки 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:

premake5 gmake

Сборка прошивки выполняется командой

make main verbose=1

В результате в каталоге bin должны быть созданы файлы:

  • main — бинарный ELF-файл,

  • main.bin — бинарный образ памяти,

  • main.lst — листинг дизассемблера,

  • main.map — MAP-файл линковки.

Прошивка МК выполняется в telnet-соединении OpenOCD с помощью команды

mem_ap_flash bin/main.bin 0x18000000

В случае успеха вывод команды будет примерно таким:

Reading 366 bytes...
Warning: appended 2 bytes (zero)
target halted due to debug-request, current mode: Thread 
xPSR: 0x21000000 pc: 0x000000ee msp: 0x20000ff8
Setup for erase...
Pages: 1
addr: 0x18000000
target halted due to debug-request, current mode: Handler HardFault
xPSR: 0xc1000003 pc: 0xfffffffe msp: 0xffffffd8
Write...
Done

Из вывода видно, что размер прошивки составляет всего 366 байт! Надо учесть, что в этот объем также входит 192 байта таблицы векторов. При программировании flash-памяти потребовалось стереть одну страницу, а также были добавлены 2 нулевых байта, чтобы размер образа был кратен 4 байтам. Если всё прошло без ошибок, МК должен начать выполнять поставленную задачу.

В завершении приведем еще один пример, в котором используется программное ШИМ-управление яркостью светодиода. Кнопка S1 плавно увеличивает яркость, т.е. коэффициент заполнения k, а кнопка S2 уменьшает. Период ШИМ составляет примерно 1 мс. Достаточно заменить только функцию main():

__attribute__ ((naked))
int main (void) {
  uint32_t ds=DKMS;
  uint32_t k=0,q=0;

  *(volatile uint16_t*)PB_SC_h1 = 0x0004; // clear bit 2
  *(volatile uint16_t*)PB_CR0_h0 = 0x0023;
  *(volatile uint16_t*)PB_CR1_h0 = 0x0023;
  *(volatile uint16_t*)PB_CR2_h0 = 0x0002;

  while (1) {
    if ((q & 0x07)==0) {
      if ( (*(volatile uint16_t*)PB_IN_h0 & 1)==0 ) {if (k < ds) k++;}
      if ( (*(volatile uint16_t*)PB_IN_h0 & 2)==0 ) {if (k > 0) k--;}
    }
    q++;

    *(volatile uint16_t*)PB_SC_h0 = 0x0004; // set bit 2
    delay(k);
    *(volatile uint16_t*)PB_SC_h1 = 0x0004; // clear bit 2
    delay(ds-k);
  }
}

В этом примере добавлено условие if ((q & 0x07)==0), которое предназначено для более плавного изменения k и снижает частоту опроса кнопок в 8 раз.

В прилагаемом архиве собраны все файлы, упомянутые в статье.

На этом мы завершаем вторую статью цикла. В следующий раз мы рассмотрим методику отладки кода в ОЗУ МК с базовой частью инициализации во флэш-памяти, управление тактированием МК, а также модуль UART в различных режимах работы.