Как создать проект на ассемблере в STM32CubeIDE

Доброго времени суток, сегодня я хотел бы поделиться своим опытом создания шаблона проекта в CubeIDE для программирование на Ассемблере.

Так как CubeIDE использует средства GNU то и синтаксис ассемблера у нас будет советующий. Для начала откроем CubeIDE и создадим новый проект. В качестве испытуемого микроконтроллера возьму STM32G030F6P6 уж очень мне они нравятся. А так данный способ работает и с другими сериями микроконтроллера STM32.

358f4e61cbc1ae749264fefd11b3611e.png

Далее необходимо дать название проекту и выбрать пустой проект и жмем завершить.

4bc37e3a8430cd7a69b79a68d252d7a6.png

Теперь необходимо удалить лишние файлы и переименовать main.c в main.s.

3d38991db7b96bc72dafec86c3165282.png

Очищаем main.s и удаляем syscall.c и sysmem.c. , но startup файл мы оставим обязательно, так как там прописаны первоначальная настройка стека и таблицы векторов после чего вызывается main, в рамках данной статьи автор рассчитывает на новичков которые только познают азы ассемблера. Должно получиться примерно так.

9304692b1c0da1e0e6762d20c68441b3.png

Теперь нам необходимо открыть файл startup_stm32…s и скопировать директивы среды. Находим данные строки ниже и копируем в наш main.s

d55da091aa5f50f1f4fe20ba9f408d56.png

После чего необходимо создать точку входа в программу, для этого нам необходимо создать метку main и сделать ее глобальной, что бы была видна из других файлов. Так же пропишем бесконечный цикл, для избежание перехода в другие области памяти. После чего компилируем и как видим проект собрался.

0c1a7d9a63d3a6dd7318d722151ae375.png

Теперь давайте напишем небольшой код, в самых лучших традициях помигаем светодиодом) , для этого нам понадобиться два адреса RCC и GPIO. Можно взять из datasheet их, но мне лень поэтому немного схитрим. Необходимо войти в режим отладки.

9f07b384eecbea1b93fb0ecec2499fdd.png

Тут просто жмем Ок.

0e0813b26f8a457914956e21e8aacd2d.png

после чего начнет загружаться прошивка и нам предложат подключиться на что мы соглашаемся и жмем Switch.

d729f3949ee7e3dcd6707da7e75367f5.png

В правом окне нам необходимо найти раздел SFRs и выбрать нам интересную периферию. Тут мы находим наш начальный адрес, для RCC он находиться 0×40021000, а для GPIOB 0×50000400. Желтым подсвечивается изменение содержимого регистра. Так как мы только что открыли их, для нас все эти значения новые.

be61aff4f6ca89a1a82a3d71f43fc2a5.pngd7a88c8cde1f5421131964674e2367e6.png

В этом разделе можно посмотреть текущее состояние битов периферии. Так же нам понадобиться раздел Registers в котором находиться регистры общего назначения.

441bddd13ba5e2b402bb84251a992339.png

После чего напишем простой скетч.

.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 порта В подлечен светодиод катодом т.е. когда мы подаем лог. ед. на вывод светодиод не горит, когда подаем лог. ноль то светодиод загорается.

a7e00b264b58bc4b1b94e256a21dfd66.png

Подключаемся в режиме отладки и начинаем идти по коду. Сначала отработает код startup файла. В нем процессор сначала вызывает обработчик сброса в котором формирует стек и векторы прерываний, а после вызывает наш main. В первом блоке кода происходит включение тактирования GPIOB.

7dd0212116c1dadefd6a32f075c8cc6f.png

Команда LDR загружает в R0 базовый адрес RCC который объявлен у нас константой. как можно видеть после выполнения команды в регистр был положен адрес. Так же можно будет заметить что счетчик команд постоянно смещается. Если перейти в окно disassembly можно сопоставить данные адреса.

ad91ae817f00e326319764271c3454cf.png073ca5a1c1b143bad984c37499bb205f.png

после чего следующей командой LDR мы загружаем значение в регистр R1 находящееся по адресу R0 смешенное на 0×34. Для проверки сравним считанное значение и посмотренное через средства отладки. Для этого откроем окно SFRs и посмотрим значения регистра RCC_IOPENB.

a5c538f315ae884b822934a3a18f645f.png

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

e3e6f9e3684a6b27273cfe78ab2564f4.png

Далее нам надо произвести манипуляции над значениями, мы загружаем чисто 2 в регистр r2 или же во второй бит записываем единицу, и потом просто складываем значения регистра r1 и r2, значения запишется в регистр r1.

6a19cb72d8f15f7e2d16ffaf2aee20f7.png43db9b77dccad26433a6ead1e1bc6f65.png

Команда STR обратная LDR, т.е. она выгружает значения из регистра общего назначения в регистр памяти по адресу R0 + смещение. Значит наше число которое должно будет оказаться в регистре RCC_IOPENB, жмем далее и убеждаемся в этом.

87a12ac58edc2e9a9a063e30caf4eb09.png

Далее такими таким же способом работаем с остальными регистрами. Алгоритм таков, сначала выгружаем значение, изменяем его и загружаем обратно. Только если вы не включите тактирование периферии то значения регистров меняться не будут, можно будет только читать. Сейчас мы перейдем в установку выхода вывода в лог. ед. так как у нас сейчас вывод стянут к нулю второй светодиод горит.

d00f56b7aa35a8d98ace45eefac3383b.png

Теперь шагами проходим и устанавливаем вывод в лог. ед. и можем убедиться наглядно.

a0c0e47ac28c12399fa7c9974b8a3554.png95438a207069c69aff6f4ba8f820dd51.png

Теперь у нас вызывается подпрограмма задержки, нажимаем на следящий шаг и переходим в подпрограмму, как можно заметить у нас изменился регистр LR, он сохранил адрес возврата из подпрограммы, т.е. адрес следующей команды после bl delay. Тут мы впервые используем команды для работы с стеком, условно PUSH загружает значения регистров в стек, а POP выгружает, но нам это в данной программе не особо и нужно, но в качестве примера.

3e04960983f5e22b1464bb1abe698b18.png

Задержка работает следующим образом. Загружается некоторое число в регистр R3, после чего из него вычитается единица, если результат вычета не равен нулю то переходим по метке иначе выполняем следующую команду. Можно в отладке потыкать и посмотреть как он отнимает число. Но смотреть как число отнимается несколько миллионов раз не интересно) поэтому можно пойти двумя путями, поставить точку останова на следующей команде или же изменить значения регистра R3 на удобное нам. Как использовать отладку и точки остановки есть куча информации в интернете, поэтому я на этом не особо заостряю внимание. А вот изменения значения регистров мало можно встретить поэтому попробуем второй вариант.

Для этого нам надо тыкнуть на значения регистра исправить его и нажать enter.

e3293562180459aef3077ebf51648a46.png

После чего мы просто делаем два шага и выходим из цикла, после чего загружаем значения из стека обратно в регистр R3 и командой BX выходим из подпрограммы, обратим внимание что в качестве метки используем регистр LR. Можно сказать что метка это и есть адрес, удобный для нашего восприятия. На , а далее все циклично думаю разобраться можно.

cfa2f042ebb339aaf6b43d196456cb38.png

Надеюсь данная статья кому ни будь окажется полезной. Это моя первая статья так что не судите строго. В качестве литературы могу порекомендовать:

  1. Джозеф Ю: Ядро Cortex-M3 компании ARM. Полное руководство

  2. Харрис, Харрис: Цифровая схемотехника и архитектура компьютера

  3. Харрис, Харрис: Цифровая схемотехника и архитектура компьютера. Дополнение по архитектуре ARM

© Habrahabr.ru