Как создать проект на ассемблере в STM32CubeIDE
Доброго времени суток, сегодня я хотел бы поделиться своим опытом создания шаблона проекта в CubeIDE для программирование на Ассемблере.
Так как CubeIDE использует средства GNU то и синтаксис ассемблера у нас будет советующий. Для начала откроем CubeIDE и создадим новый проект. В качестве испытуемого микроконтроллера возьму STM32G030F6P6 уж очень мне они нравятся. А так данный способ работает и с другими сериями микроконтроллера STM32.
Далее необходимо дать название проекту и выбрать пустой проект и жмем завершить.
Теперь необходимо удалить лишние файлы и переименовать main.c в main.s.
Очищаем main.s и удаляем syscall.c и sysmem.c. , но startup файл мы оставим обязательно, так как там прописаны первоначальная настройка стека и таблицы векторов после чего вызывается main, в рамках данной статьи автор рассчитывает на новичков которые только познают азы ассемблера. Должно получиться примерно так.
Теперь нам необходимо открыть файл startup_stm32…s и скопировать директивы среды. Находим данные строки ниже и копируем в наш main.s
После чего необходимо создать точку входа в программу, для этого нам необходимо создать метку main и сделать ее глобальной, что бы была видна из других файлов. Так же пропишем бесконечный цикл, для избежание перехода в другие области памяти. После чего компилируем и как видим проект собрался.
Теперь давайте напишем небольшой код, в самых лучших традициях помигаем светодиодом) , для этого нам понадобиться два адреса RCC и GPIO. Можно взять из datasheet их, но мне лень поэтому немного схитрим. Необходимо войти в режим отладки.
Тут просто жмем Ок.
после чего начнет загружаться прошивка и нам предложат подключиться на что мы соглашаемся и жмем Switch.
В правом окне нам необходимо найти раздел SFRs и выбрать нам интересную периферию. Тут мы находим наш начальный адрес, для RCC он находиться 0×40021000, а для GPIOB 0×50000400. Желтым подсвечивается изменение содержимого регистра. Так как мы только что открыли их, для нас все эти значения новые.
В этом разделе можно посмотреть текущее состояние битов периферии. Так же нам понадобиться раздел Registers в котором находиться регистры общего назначения.
После чего напишем простой скетч.
.syntax unified
.cpu cortex-m0plus
.fpu softvfp
.thumb
.equ RCC_BASE, 0x40021000
.equ GPIOB_BASE, 0x50000400
.global delay
.global main
main:
//Включения тактирования GPIOB
ldr r0, =RCC_BASE
ldr r1, [r0, #0x34]
movs r2, #0b10
adds r1, r2
str r1, [R0, #0x34]
//Настройка GPIOB_PIN_0 как выход
ldr r0, =GPIOB_BASE
ldr r1, [r0, #0x0]
movs r2, #0b11
bics r1, r2
adds r1, #1
str r1, [r0, #0x0]
Loop:
//Установка PIN_0 в лог. ед.
ldr r1, [r0, #0x18]
movs r2, #1
orrs r1, r2
str r1, [r0, #0x18]
bl delay
//Установка PIN_0 в лог. ноль
ldr r1, [r0, #0x18]
movs r2, #1
lsls r2, #16
orrs r1, r2
str r1, [r0, #0x18]
bl delay
b Loop
//задержка
delay:
push {r3}
ldr r3, =#0x00100000;
delay_loop:
subs r3, #1
bne delay_loop
pop {r3}
bx lr
Скомпилируем наш код и зашьем его. В качестве примера использую китайскую плату. У данной платы к ножке 0 порта В подлечен светодиод катодом т.е. когда мы подаем лог. ед. на вывод светодиод не горит, когда подаем лог. ноль то светодиод загорается.
Подключаемся в режиме отладки и начинаем идти по коду. Сначала отработает код startup файла. В нем процессор сначала вызывает обработчик сброса в котором формирует стек и векторы прерываний, а после вызывает наш main. В первом блоке кода происходит включение тактирования GPIOB.
Команда LDR загружает в R0 базовый адрес RCC который объявлен у нас константой. как можно видеть после выполнения команды в регистр был положен адрес. Так же можно будет заметить что счетчик команд постоянно смещается. Если перейти в окно disassembly можно сопоставить данные адреса.
после чего следующей командой LDR мы загружаем значение в регистр R1 находящееся по адресу R0 смешенное на 0×34. Для проверки сравним считанное значение и посмотренное через средства отладки. Для этого откроем окно SFRs и посмотрим значения регистра RCC_IOPENB.
И жмем дальше шаг дальше. И переходим снова в окно регистров общего назначения и убеждаемся, что загруженное число советует.
Далее нам надо произвести манипуляции над значениями, мы загружаем чисто 2 в регистр r2 или же во второй бит записываем единицу, и потом просто складываем значения регистра r1 и r2, значения запишется в регистр r1.
Команда STR обратная LDR, т.е. она выгружает значения из регистра общего назначения в регистр памяти по адресу R0 + смещение. Значит наше число которое должно будет оказаться в регистре RCC_IOPENB, жмем далее и убеждаемся в этом.
Далее такими таким же способом работаем с остальными регистрами. Алгоритм таков, сначала выгружаем значение, изменяем его и загружаем обратно. Только если вы не включите тактирование периферии то значения регистров меняться не будут, можно будет только читать. Сейчас мы перейдем в установку выхода вывода в лог. ед. так как у нас сейчас вывод стянут к нулю второй светодиод горит.
Теперь шагами проходим и устанавливаем вывод в лог. ед. и можем убедиться наглядно.
Теперь у нас вызывается подпрограмма задержки, нажимаем на следящий шаг и переходим в подпрограмму, как можно заметить у нас изменился регистр LR, он сохранил адрес возврата из подпрограммы, т.е. адрес следующей команды после bl delay. Тут мы впервые используем команды для работы с стеком, условно PUSH загружает значения регистров в стек, а POP выгружает, но нам это в данной программе не особо и нужно, но в качестве примера.
Задержка работает следующим образом. Загружается некоторое число в регистр R3, после чего из него вычитается единица, если результат вычета не равен нулю то переходим по метке иначе выполняем следующую команду. Можно в отладке потыкать и посмотреть как он отнимает число. Но смотреть как число отнимается несколько миллионов раз не интересно) поэтому можно пойти двумя путями, поставить точку останова на следующей команде или же изменить значения регистра R3 на удобное нам. Как использовать отладку и точки остановки есть куча информации в интернете, поэтому я на этом не особо заостряю внимание. А вот изменения значения регистров мало можно встретить поэтому попробуем второй вариант.
Для этого нам надо тыкнуть на значения регистра исправить его и нажать enter.
После чего мы просто делаем два шага и выходим из цикла, после чего загружаем значения из стека обратно в регистр R3 и командой BX выходим из подпрограммы, обратим внимание что в качестве метки используем регистр LR. Можно сказать что метка это и есть адрес, удобный для нашего восприятия. На , а далее все циклично думаю разобраться можно.
Надеюсь данная статья кому ни будь окажется полезной. Это моя первая статья так что не судите строго. В качестве литературы могу порекомендовать:
Джозеф Ю: Ядро Cortex-M3 компании ARM. Полное руководство
Харрис, Харрис: Цифровая схемотехника и архитектура компьютера
Харрис, Харрис: Цифровая схемотехника и архитектура компьютера. Дополнение по архитектуре ARM