Ну что же, новогодние праздники кончились, и начались трудовые будни :-) Продолжим разбираться с ассемблером на примере микроконтроллера STM32F4
Обсуждение статьи доступно на платформе VK: vk.com/topic-200545792_46642025
Ссылки на прошлые публикации:
STM32F4: GNU AS: Программирование на ассемблере (Часть 1)
STM32F4: GNU AS: Мигаем светодиодом (Оживление) (Часть 2)
В комментариях никто не отписался о том что ему удалось запустить «мигалку» на STM32F4 Discovery — значит либо не пробовали, либо не удалось. Исправим эту мелочь:
1) Как я уже писал в прошлой публикации для нашего примера разница между моей отладочной платой и STM32F4 Discovery фактически только в других выводах к которым подключены светодиоды. То есть используется другой порт GPIOx и другой выход этого порта.
Узнать куда же все таки подключены светодиоды можно из документации. Я использовал Discovery kit for STM32F407/417 lines.
Информация о подключениях есть как минимум в трех местах:
Таким образом все становиться очевидным «до противного»: 4 светодиода подключены к GPIOD выводам 12, 13, 14, 15.
Как говорится выбираем любой, пусть в «мигалке» используется светодиод синего цвет подключенный к GPIOD 15
2) Изменяем программу для использования GPIOD (вместо GPIOH) и вывода 15 (вместо 2)
Первое что мы должны сделать — это включить тактирование GPIOD, тут даже не буду делать новый скриншот, новое значение для настройки есть на старом:
![image](https://habrastorage.org/r/w780q1/files/3a8/7a1/f53/3a87a1f533fc415ba083dbb1ceca2d83.jpg)
выделено значение для GPIOH, а для GPIOD нужно использовать: RCC_AHB1ENR_GPIODEN — разница с уже написанным кодом программы ранее в одной букве!!!
Теперь необходимо провести настройку вывода 15 GPIOD на выход: тут мы делаем небольшое такое «открытие» — дело в том что каждый GPIO настраивается одним и тем же набором регистров с одними и теми же значениями(!), и именно этим вызвана такая «странная» на первый взгляд конструкция вычисления адреса того или иного регистра
Фактически вместо константы с именем GPIO_MODER_MODER2_0 используем GPIO_MODER_MODER15_0 и не забываем в расчете адреса регистра MODER указывать смещение GPIOD_BASE (раньше там стояло GPIOH_BASE)
Аналогичным образом изменяются блоки собственно самого «мигания»: изменяем базу GPIO на GPIOD_BASE (вместо GPIOH_BASE), и в двоичном виде формируем код на включение выключение соответствующего вывода порта, повторюсь: для включения используются младшие 16 бит слова, для выключения старшие 16 бит, нумерация выводов портов идет от 0, поэтому самый старший 15 бит по «счету» будет 16-ым
Остальная часть программы остается без изменений
скачать проект (Кто проверит на STM32F4 Discovery "+1" в репутацию от меня (у меня платы STM32F4 Discovery нет)
я надеюсь что после этих примеров вопросов как создать «мигалку» на любое другое подключение светодиодов к микроконтроллеру STM32F4xx у вас не возникнет.
Теперь стоит вникнуть что же все таки у нас получилось с точки зрения сгенерированного файла прошивки.
Теперь давайте посмотрим, как же выглядит наша программа на этапе компиляции после того как ее обработал компилятор и компоновщик
Первым блоком идет указание меток программы, как я уже говорил во второй публикации — код ARM всегда переносимый, поэтому адреса всегда указываются относительные (относительно PC)
Блок векторов прерываний, в наших программах он идет вначале секции .text
Дальше идет блок дизассемблированой программы с примененными кодами закодированных инструкций.
Именно здесь можно увидеть какие инструкции кодируются при помощи 16 бит, а какие требуют 32 бита.
Первое применение макроса превратилось в заданные в нем две команды загрузки верхних и нижних 16 бит значения размером 32 бита
Таким образом на загрузку одного 32-ух битного значения нам понадобилось 8 байт (64 бита) программы
Во второй части публикации я описывал инструкцию загрузки 32-ух битного значения в регистр LDR Rx,=#value32bit — давайте попробуем применить ее и посмотреть что получится:
откомпилируем проект (запустим make_project.bat), и посмотрим что получилось после компиляции (файл /compile/temp/main_text.lst) — кстати, вы уже наверное заметили что размер программы «каким то образом» уменьшился со 116 до 96 байт, все дело именно в использовании инструкций LDR Rx, =#value32bit
Я уберу весь код который не относится к этим инструкциям
формат строк: <cмещение адреса инструкции> : <код инструкции> <текст инструкции> ; <комментарий компилятора>
Рассмотрим команду расположенную под адресом 8
мы видим что в R0 загружается значение PC увеличенное на #52 (десятичное значение!), в примечании указывается что результирующий адрес будет 40 (это шестнадцатеричное значение)
проверим получившиеся значение:
после загрузки команды, в момент ее исполнения PC содержит в себе [адрес текущей инструкции+4], то есть PC = 0x08 + 0x04=0x0C, адрес данных для загрузки = PC + #52 = 0x0C+#52 = #64 (0x40)
Как видно из полученного в main_text.lst дампа компилятор автоматически все константы поместил в конец секции .text, на данном этапе нам важно заметить что фактически использование макроса mov32 не приносит никаких выгод ни с точки зрения наглядности программы, согласитесь что в коде программы строки читаются не хуже и не лучше друг друга
ни с точки зрения скорости исполнения (в комментариях к второй публикации мне указали на то что команда LDR с загрузкой из памяти будет выполнена так же за 2 такта), но выиграем в размере программы. С учетом того, что даже в нашей, прямо скажем микроскопической, программе мигания светодиодом постоянно приходиться использовать 32-ух битные значения констант (адреса регистров, значения для записи в регистры) — экономия может оказаться не на столько смехотворной как это кажется на первый взгляд…
Я не буду отдельно выкладывать архив с примером программы с использованием инструкций LDR — думаю с созданием этого примера вы справитесь самостоятельно.
Теперь хочу рассказать об еще одной «фиче» микроконтроллеров на платформе ARM — битбандинге (bitbanding)
Кратко — это возможность получить доступ к определенным битам обращаясь ячейкам расположенным в специальных областях адресного пространства. Доступ может быть и на чтение и на изменение бита.
В "PM0214 Cortex-M4 Programming manual" на стр. 27 приведена следующая схема распределения адресного пространства микроконтроллеров семейства STM32F4
![](https://habrastorage.org/r/w780q1/files/9f3/d6f/ea0/9f3d6fea0a0d436e988c95ad3f51e2e1.jpg)
Давайте разберемся со схемой:
Области bitband region имеют объем 1 мб
Области bitband alias — 32 мб
Как можно видеть из схемы распределения адресного пространства у микроконтроллеров STM32F4xx две области bitband region (связанные с ними bitband alias):
Теперь разберем формулу по которой сопоставляется адрес бита в bitband region адресу в bitband alias, она приведена на стр. 31 "PM0214 Cortex-M4 Programming manual"
![](https://habrastorage.org/r/w780q1/files/600/1a2/8dd/6001a28dd98746448b431167d2965f5d.jpg)
Для простоты запишем ее в одну строчку:
где,
Практическое применение bitband:
Возьмем нашу программу «мигалки»: при настройке режимов GPIOD мы сначала включаем этот порт устанавливая нужный нам бит в «1», делаем это строками кода:
Адрес PERIPH_BASE = 0x40000000 — то есть он укладывается в один из bitband region'ов
Адрес bitband_alias для этого bitband region равен 0x42000000 — это значение и используется в качестве bitband_base при обращении к регистрам расположенным в адресах 0x40000000 — 0x400FFFFF
А адрес byte_offset вычисляется так же как и адрес регистра при обычной записи в регистр настройки (в нашем случае RCC_BASE + RCC_AHB1ENR).
Таким образом, адрес бита подлежащего изменению, высчитывается по формуле:
где:
— PERIPH_BB_BASE — определено значение 0x42000000, определение находиться в файле stm32f40x.inc нашего проекта
![](https://habrastorage.org/r/w780q1/files/db1/9de/565/db19de5659854a49bcdc07ba400788ba.jpg)
— RCC_BASE + RCC_AHB1ENR — это тоже 2 константных значения которые мы уже вычисляли во второй части публикации
— RCC_AHB1ENR_GPIODEN_N — номер бита в регистре RCC_AHB1ENR. Этого определения в файле констант не было, я добавил самостоятельно, для того чтобы понимать что константа указывает не на значение, а лишь на порядковый номер бита в слове я всегда добавляю "_N" к имени константы.
![](https://habrastorage.org/r/w780q1/files/dcd/8b0/ada/dcd8b0adaeea468aae7b95777e4a86a9.jpg)
Получаем следующий код для включения GPIOD:
Установив в R0 адрес бита в bitband_alias, командой STR R1, [R0] мы записываем не значение 32-ух битного регистра R1 по адресу в регистре R0, а устанавливаем значение одного бита с номером 3 в регистре RCC_AHB1ENR.
При этом нам не нужно проводить операцию: чтение | логическое «ИЛИ» | запись, это все происходит на аппаратном уровне.
К слову, объединение трех операций в одну при bitbanding является важной при некоторых применениях, когда важна «атомарность» (неделимость) действия при изменению значения бита (обычно это оперирование различными «флагами» и «семафорами» в много- задачных или псевдо- многозадачных программах)
Давайте перепишем нашу программу «мигалка» с использованием метода bitband:
Не ожидали столько изменений? Давайте их разберем!
В начало программы я вынес присвоение значений двум регистрам R0 = 0 и R1 = 1 эти значения мы будем записывать при использовании метода bitband, присвоение командой MOV тоже произведено намеренно — при таком присвоении осуществляется загрузка 16-ти битного значения в младшие 16 бит регистра, с обнулением старших 16 бит. Таким образом R0 (в 32ух битах) у нас будет равен «0», а R1 (в 32ух битах) будет равен «1».
Далее идет уже разобранный нами блок включения GPIOD методом bitband, я использую регистр R2 поскольку R0 и R1 уже заняты нашими константами («0», «1» соответственно):
Устанавливаем режим работы 15 вывода GPIOD на выход, пока, чтобы не путать повествование, оставил процедуру прежней, хотя ее можно легко переписать на присвоение значения методом bitband тогда она сократиться до 2х строчек кода
Поскольку в цикле «мигания» адрес регистра управляющего светодиодом меняться не будет — я вынес это значение за пределы цикла. Так же в качестве регистра вывода я использую регистр GPIO_ODR (ищите в Reference manual описание по GPIOx_ODR), с адресацией 15-го бита в режиме bitband (к 15-му биту у нас подключен светодиод).
Цикл мигания светодиодом упрощается! теперь нам просто нужно записывать по адресу бита (методом bitband) значение «0» или «1» для включения светодиода, и вызывать между записью паузу
Для паузы я использую значение регистра R3 (R0, R1, R2 уже заняты)
Запустив компиляцию и сборку нашего проекта мы увидим размер программы — 76 байт (если помните, первоначальный вариант «весил» — 116 байт)
Этот проект можно скачать по ссылке
ВНИМАНИЕ !
Поскольку у меня нет платы STM32F4 Discovery все примеры данной публикации выложены «как есть», без предварительной проверки на работоспособность. В случае если что то не заработало — пишите: посмотрим, послушаем, разберемся (с) «Берегись автомобиля».
В порядке тренировки можете попробовать еще оптимизировать по размеру программу «мигалка» и поделиться результатами в комментариях к статье (можно использовать любые инструкции процессора).
На этом третью публикацию закончим.
продолжение STM32F4: GNU AS: Настраиваем среду компиляции (Часть 4)
Обсуждение статьи доступно на платформе VK: vk.com/topic-200545792_46642025
Ссылки на прошлые публикации:
STM32F4: GNU AS: Программирование на ассемблере (Часть 1)
STM32F4: GNU AS: Мигаем светодиодом (Оживление) (Часть 2)
В комментариях никто не отписался о том что ему удалось запустить «мигалку» на STM32F4 Discovery — значит либо не пробовали, либо не удалось. Исправим эту мелочь:
1) Как я уже писал в прошлой публикации для нашего примера разница между моей отладочной платой и STM32F4 Discovery фактически только в других выводах к которым подключены светодиоды. То есть используется другой порт GPIOx и другой выход этого порта.
Узнать куда же все таки подключены светодиоды можно из документации. Я использовал Discovery kit for STM32F407/417 lines.
Информация о подключениях есть как минимум в трех местах:
- в параграфе 4.4 LEDs, стр. 16
- в таблице с названием Table 5. MCU pin description versus board function стр. 21-32, нужная нам информация на стр. 30
- на листе Figure 16. Peripherals, стр. 40
Таким образом все становиться очевидным «до противного»: 4 светодиода подключены к GPIOD выводам 12, 13, 14, 15.
Как говорится выбираем любой, пусть в «мигалке» используется светодиод синего цвет подключенный к GPIOD 15
2) Изменяем программу для использования GPIOD (вместо GPIOH) и вывода 15 (вместо 2)
Первое что мы должны сделать — это включить тактирование GPIOD, тут даже не буду делать новый скриншот, новое значение для настройки есть на старом:
![image](https://habrastorage.org/files/3a8/7a1/f53/3a87a1f533fc415ba083dbb1ceca2d83.jpg)
выделено значение для GPIOH, а для GPIOD нужно использовать: RCC_AHB1ENR_GPIODEN — разница с уже написанным кодом программы ранее в одной букве!!!
Reset:
@ включим тактирование GPIOD
MOV32 R0, PERIPH_BASE + RCC_BASE + RCC_AHB1ENR @ адрес
MOV32 R1, RCC_AHB1ENR_GPIODEN @ значение
LDR R2, [R0] @ прочитали значение регистра
ORR R1, R1, R2 @ логическое, побитовое ИЛИ: R1= R1 ИЛИ R2
STR R1, [R0] @ запись R1 по адресу указанному в R0
Теперь необходимо провести настройку вывода 15 GPIOD на выход: тут мы делаем небольшое такое «открытие» — дело в том что каждый GPIO настраивается одним и тем же набором регистров с одними и теми же значениями(!), и именно этим вызвана такая «странная» на первый взгляд конструкция вычисления адреса того или иного регистра
Фактически вместо константы с именем GPIO_MODER_MODER2_0 используем GPIO_MODER_MODER15_0 и не забываем в расчете адреса регистра MODER указывать смещение GPIOD_BASE (раньше там стояло GPIOH_BASE)
@ установим режим вывода 15 порта GPIOD "на выход"
MOV32 R0, PERIPH_BASE + GPIOD_BASE + GPIO_MODER @ адрес
MOV32 R1, GPIO_MODER_MODER15_0 @ значение
LDR R2, [R0] @ прочитали значение регистра
ORR R1, R1, R2 @ логическое, побитовое ИЛИ: R1= R1 ИЛИ R2
STR R1, [R0] @ запись R1 по адресу указанному в R0
Аналогичным образом изменяются блоки собственно самого «мигания»: изменяем базу GPIO на GPIOD_BASE (вместо GPIOH_BASE), и в двоичном виде формируем код на включение выключение соответствующего вывода порта, повторюсь: для включения используются младшие 16 бит слова, для выключения старшие 16 бит, нумерация выводов портов идет от 0, поэтому самый старший 15 бит по «счету» будет 16-ым
BLINK_LOOP:
@ включим светодиод
MOV32 R0, PERIPH_BASE + GPIOD_BASE + GPIO_BSRR @ адрес
MOV32 R1, 0x8000 @ значение в двоичном виде 1000 0000 0000 0000
STR R1, [R0] @ запись R1 по адресу указанному в R0
BL DELAY @ пауза
@ выключим светодиод
MOV32 R0, PERIPH_BASE + GPIOD_BASE + GPIO_BSRR @ адрес
MOV32 R1, 0x8000 << 16 @ значение во второе полуслово
STR R1, [R0] @ запись R1 по адресу указанному в R0
Остальная часть программы остается без изменений
Программа полностью
@GNU AS
@ Настройки компилятора
.syntax unified @ тип синтаксиса
.thumb @ тип используемых инструкций Thumb
.cpu cortex-m4 @ микроконтроллер
.include "stm32f40x.inc" @ определения микроконтроллера
@ макрос псевдокоманды MOV32
.macro MOV32 regnum,number
MOVW \regnum,:lower16:\number
MOVT \regnum,:upper16:\number
.endm
@ таблица векторов прерываний
.section .text
.word 0x20020000 @ Вершина стека
.word Reset+1 @ Вектор сброса
Reset:
@ включим тактирование GPIO_D
MOV32 R0, PERIPH_BASE + RCC_BASE + RCC_AHB1ENR @ адрес
MOV32 R1, RCC_AHB1ENR_GPIODEN @ значение
LDR R2, [R0] @ прочитали значение регистра
ORR R1, R1, R2 @ логическое, побитовое ИЛИ: R1= R1 ИЛИ R2
STR R1, [R0] @ запись R1 по адресу указанному в R0
@ установим режим GPIO_D pin_15
MOV32 R0, PERIPH_BASE + GPIOD_BASE + GPIO_MODER @ адрес
MOV32 R1, GPIO_MODER_MODER15_0 @ значение
LDR R2, [R0] @ прочитали значение регистра
ORR R1, R1, R2 @ логическое, побитовое ИЛИ: R1= R1 ИЛИ R2
STR R1, [R0] @ запись R1 по адресу указанному в R0
BLINK_LOOP:
@ включим светодиод
MOV32 R0, PERIPH_BASE + GPIOD_BASE + GPIO_BSRR @ адрес
MOV32 R1, 0x8000 @ значение 1000 0000 0000 0000
STR R1, [R0] @ запись R1 по адресу указанному в R0
BL DELAY @ пауза
@ выключим светодиод
MOV32 R0, PERIPH_BASE + GPIOD_BASE + GPIO_BSRR @ адрес
MOV32 R1, 0x8000 << 16 @ значение
STR R1, [R0] @ запись R1 по адресу указанному в R0
BL DELAY @ пауза
B BLINK_LOOP @ делаем цикл
DELAY:
MOV32 R2, 0x00100000 @ повтор цикла 0x0010 0000 раз.
Delay_loop:
SUBS R2, R2, 1
BNE Delay_loop
BX LR
скачать проект (Кто проверит на STM32F4 Discovery "+1" в репутацию от меня (у меня платы STM32F4 Discovery нет)
я надеюсь что после этих примеров вопросов как создать «мигалку» на любое другое подключение светодиодов к микроконтроллеру STM32F4xx у вас не возникнет.
Теперь стоит вникнуть что же все таки у нас получилось с точки зрения сгенерированного файла прошивки.
В новом проекте применяется немного усовершенствованный скрипт компиляции и сборки проекта, например в каталоге /compile/temp вы можете увидеть не только файлы компиляции c расширением ".о" и файл прошивки ".elf", но и справочные:
- labels.lst — файл с описанием всех меток с их значениями после сборки компоновщиком (раньше я его называл линковщиком)
- sections.lst — файл с описанием секций проекта
- main_text.lst — файл скомилированных исходников секции .text
В основном, когда что-то идет не так как хотелось бы, я использую файлы labels.lst — для проверки значений констант, адресов меток, и main_text.lst для контроля за сгенерированным кодом прошивки
Теперь давайте посмотрим, как же выглядит наша программа на этапе компиляции после того как ее обработал компилятор и компоновщик
Первым блоком идет указание меток программы, как я уже говорил во второй публикации — код ARM всегда переносимый, поэтому адреса всегда указываются относительные (относительно PC)
compile\temp\sys.o: file format elf32-littlearm
SYMBOL TABLE:
00000000 l d .text 00000000 .text
00000008 l .text 00000000 Reset
00000038 l .text 00000000 BLINK_LOOP
00000066 l .text 00000000 DELAY
0000006e l .text 00000000 Delay_loop
Блок векторов прерываний, в наших программах он идет вначале секции .text
Disassembly of section .text:
00000000 <Reset-0x8>:
0: 20020000 .word 0x20020000
4: 00000009 .word 0x00000009
Дальше идет блок дизассемблированой программы с примененными кодами закодированных инструкций.
00000008 <Reset>:
8: f643 0030 movw r0, #14384 ; 0x3830
c: f2c4 0002 movt r0, #16386 ; 0x4002
10: f240 0108 movw r1, #8
14: f2c0 0100 movt r1, #0
18: 6802 ldr r2, [r0, #0]
1a: ea41 0102 orr.w r1, r1, r2
1e: 6001 str r1, [r0, #0]
20: f640 4000 movw r0, #3072 ; 0xc00
24: f2c4 0002 movt r0, #16386 ; 0x4002
28: f240 0100 movw r1, #0
2c: f2c4 0100 movt r1, #16384 ; 0x4000
30: 6802 ldr r2, [r0, #0]
32: ea41 0102 orr.w r1, r1, r2
36: 6001 str r1, [r0, #0]
00000038 <BLINK_LOOP>:
38: f640 4018 movw r0, #3096 ; 0xc18
3c: f2c4 0002 movt r0, #16386 ; 0x4002
40: f248 0100 movw r1, #32768 ; 0x8000
44: f2c0 0100 movt r1, #0
48: 6001 str r1, [r0, #0]
4a: f000 f80c bl 66 <DELAY>
4e: f640 4018 movw r0, #3096 ; 0xc18
52: f2c4 0002 movt r0, #16386 ; 0x4002
56: f240 0100 movw r1, #0
5a: f2c8 0100 movt r1, #32768 ; 0x8000
5e: 6001 str r1, [r0, #0]
60: f000 f801 bl 66 <DELAY>
64: e7e8 b.n 38 <BLINK_LOOP>
00000066 <DELAY>:
66: f240 0200 movw r2, #0
6a: f2c0 0210 movt r2, #16
0000006e <Delay_loop>:
6e: 3a01 subs r2, #1
70: d1fd bne.n 6e <Delay_loop>
72: 4770 bx lr
Именно здесь можно увидеть какие инструкции кодируются при помощи 16 бит, а какие требуют 32 бита.
Первое применение макроса превратилось в заданные в нем две команды загрузки верхних и нижних 16 бит значения размером 32 бита
8: f643 0030 movw r0, #14384 ; 0x3830
c: f2c4 0002 movt r0, #16386 ; 0x4002
Таким образом на загрузку одного 32-ух битного значения нам понадобилось 8 байт (64 бита) программы
Во второй части публикации я описывал инструкцию загрузки 32-ух битного значения в регистр LDR Rx,=#value32bit — давайте попробуем применить ее и посмотреть что получится:
Программа с использованием инструкции LDR
@GNU AS
@ Настройки компилятора
.syntax unified @ тип синтаксиса
.thumb @ тип используемых инструкций Thumb
.cpu cortex-m4 @ микроконтроллер
.include "stm32f40x.inc" @ определения микроконтроллера
@ макрос псевдокоманды MOV32 нам теперь не нужен, я убрал его
@ таблица векторов прерываний
.section .text
.word 0x20020000 @ Вершина стека
.word Reset+1 @ Вектор сброса
Reset:
@ включим тактирование GPIO_D
LDR R0, =(PERIPH_BASE + RCC_BASE + RCC_AHB1ENR) @ адрес
LDR R1, =RCC_AHB1ENR_GPIODEN @ значение
LDR R2, [R0] @ прочитали значение регистра
ORR R1, R1, R2 @ логическое, побитовое ИЛИ: R1= R1 ИЛИ R2
STR R1, [R0] @ запись R1 по адресу указанному в R0
@ установим режим GPIO_D pin_15
LDR R0, =(PERIPH_BASE + GPIOD_BASE + GPIO_MODER) @ адрес
LDR R1, =GPIO_MODER_MODER15_0 @ значение
LDR R2, [R0] @ прочитали значение регистра
ORR R1, R1, R2 @ логическое, побитовое ИЛИ: R1= R1 ИЛИ R2
STR R1, [R0] @ запись R1 по адресу указанному в R0
BLINK_LOOP:
@ включим светодиод
LDR R0, =(PERIPH_BASE + GPIOD_BASE + GPIO_BSRR) @ адрес
LDR R1, =0x8000 @ значение 1000 0000 0000 0000
STR R1, [R0] @ запись R1 по адресу указанному в R0
BL DELAY @ пауза
@ выключим светодиод
LDR R0, =(PERIPH_BASE + GPIOD_BASE + GPIO_BSRR) @ адрес
LDR R1, =(0x8000 << 16) @ значение
STR R1, [R0] @ запись R1 по адресу указанному в R0
BL DELAY @ пауза
B BLINK_LOOP @ делаем цикл
DELAY:
LDR R2, =0x00100000 @ повтор цикла 0x0010 0000 раз.
Delay_loop:
SUBS R2, R2, 1
BNE Delay_loop
BX LR
откомпилируем проект (запустим make_project.bat), и посмотрим что получилось после компиляции (файл /compile/temp/main_text.lst) — кстати, вы уже наверное заметили что размер программы «каким то образом» уменьшился со 116 до 96 байт, все дело именно в использовании инструкций LDR Rx, =#value32bit
Я уберу весь код который не относится к этим инструкциям
. . .
00000008 <Reset>:
8: 480d ldr r0, [pc, #52] ; (40 <Delay_loop+0x8>)
a: 490e ldr r1, [pc, #56] ; (44 <Delay_loop+0xc>)
. . .
14: 480c ldr r0, [pc, #48] ; (48 <Delay_loop+0x10>)
16: 490d ldr r1, [pc, #52] ; (4c <Delay_loop+0x14>)
. . .
00000020 <BLINK_LOOP>:
20: 480b ldr r0, [pc, #44] ; (50 <Delay_loop+0x18>)
22: 490c ldr r1, [pc, #48] ; (54 <Delay_loop+0x1c>)
. . .
2a: 4809 ldr r0, [pc, #36] ; (50 <Delay_loop+0x18>)
2c: 490a ldr r1, [pc, #40] ; (58 <Delay_loop+0x20>)
. . .
36: 4a09 ldr r2, [pc, #36] ; (5c <Delay_loop+0x24>)
. . .
3e: 0000 .short 0x0000
. . .
40: 40023830 .word 0x40023830
44: 00000008 .word 0x00000008
48: 40020c00 .word 0x40020c00
4c: 40000000 .word 0x40000000
50: 40020c18 .word 0x40020c18
54: 00008000 .word 0x00008000
58: 80000000 .word 0x80000000
5c: 00100000 .word 0x00100000
формат строк: <cмещение адреса инструкции> : <код инструкции> <текст инструкции> ; <комментарий компилятора>
Рассмотрим команду расположенную под адресом 8
8: 480d ldr r0, [pc, #52] ; (40 <Delay_loop+0x8>)
мы видим что в R0 загружается значение PC увеличенное на #52 (десятичное значение!), в примечании указывается что результирующий адрес будет 40 (это шестнадцатеричное значение)
проверим получившиеся значение:
после загрузки команды, в момент ее исполнения PC содержит в себе [адрес текущей инструкции+4], то есть PC = 0x08 + 0x04=0x0C, адрес данных для загрузки = PC + #52 = 0x0C+#52 = #64 (0x40)
Запоминаем! после загрузки кода инструкции из программной памяти (не пишу FLASH потому что программа может быть загружена и в CCM_RAM — вспоминаем текст первой публикации(!) ) — значение PC увеличивается на 4 вне зависимости от того 16-ти битная или 32-ух битная инструкция была загружена !
Как видно из полученного в main_text.lst дампа компилятор автоматически все константы поместил в конец секции .text, на данном этапе нам важно заметить что фактически использование макроса mov32 не приносит никаких выгод ни с точки зрения наглядности программы, согласитесь что в коде программы строки читаются не хуже и не лучше друг друга
MOV32 R0, 0x10101010
LDR R0, =0x10101010
ни с точки зрения скорости исполнения (в комментариях к второй публикации мне указали на то что команда LDR с загрузкой из памяти будет выполнена так же за 2 такта), но выиграем в размере программы. С учетом того, что даже в нашей, прямо скажем микроскопической, программе мигания светодиодом постоянно приходиться использовать 32-ух битные значения констант (адреса регистров, значения для записи в регистры) — экономия может оказаться не на столько смехотворной как это кажется на первый взгляд…
Я не буду отдельно выкладывать архив с примером программы с использованием инструкций LDR — думаю с созданием этого примера вы справитесь самостоятельно.
Теперь хочу рассказать об еще одной «фиче» микроконтроллеров на платформе ARM — битбандинге (bitbanding)
Кратко — это возможность получить доступ к определенным битам обращаясь ячейкам расположенным в специальных областях адресного пространства. Доступ может быть и на чтение и на изменение бита.
В "PM0214 Cortex-M4 Programming manual" на стр. 27 приведена следующая схема распределения адресного пространства микроконтроллеров семейства STM32F4
![](https://habrastorage.org/files/9f3/d6f/ea0/9f3d6fea0a0d436e988c95ad3f51e2e1.jpg)
Давайте разберемся со схемой:
- Bitband region — область адресного пространства микроконтроллера где находиться контролируемый или проверяемый бит. По другому говоря бит которым мы реально хотим изменить или состояние которого хотим прочитать должен находиться в этом диапазоне адресов
- Bitband alias — область адресного пространства проецируемая на bitband region. Говоря русским языком, при обращении к адресам из этой области можно проверить состояние бита из области bitband region или записав данные в эту область можно изменить состояние бита данных из области bitband region
Области bitband region имеют объем 1 мб
Области bitband alias — 32 мб
Как можно видеть из схемы распределения адресного пространства у микроконтроллеров STM32F4xx две области bitband region (связанные с ними bitband alias):
- в адресах 0x2000 0000 — 0x200F FFFF — то есть эта область покрывает все адреса SRAM1 и SRAM2 (вспоминаем адреса SRAM из первой части публикации!)
- в адресах 0x4000 0000 — 0x400F FFFF — эта область покрывает адреса регистров настройки периферии микроконтроллера + все пространство backup RAM
Теперь разберем формулу по которой сопоставляется адрес бита в bitband region адресу в bitband alias, она приведена на стр. 31 "PM0214 Cortex-M4 Programming manual"
![](https://habrastorage.org/files/600/1a2/8dd/6001a28dd98746448b431167d2965f5d.jpg)
Для простоты запишем ее в одну строчку:
bit_word_adr=bitband_base+ (byte_offset * 32) + bit_number * 4
где,
- bitband_base — адрес bitband_alias
- byte_offset — смещение адреса регистра относительно начала bitband_alias
- bit_number — номер бита к которому нужно получить доступ
Практическое применение bitband:
Возьмем нашу программу «мигалки»: при настройке режимов GPIOD мы сначала включаем этот порт устанавливая нужный нам бит в «1», делаем это строками кода:
@ включим тактирование GPIO_D
LDR R0, =(PERIPH_BASE + RCC_BASE + RCC_AHB1ENR) @ адрес
LDR R1, =RCC_AHB1ENR_GPIODEN @ значение
LDR R2, [R0] @ прочитали значение регистра
ORR R1, R1, R2 @ логическое, побитовое ИЛИ: R1= R1 ИЛИ R2
STR R1, [R0] @ запись R1 по адресу указанному в R0
Адрес PERIPH_BASE = 0x40000000 — то есть он укладывается в один из bitband region'ов
Адрес bitband_alias для этого bitband region равен 0x42000000 — это значение и используется в качестве bitband_base при обращении к регистрам расположенным в адресах 0x40000000 — 0x400FFFFF
А адрес byte_offset вычисляется так же как и адрес регистра при обычной записи в регистр настройки (в нашем случае RCC_BASE + RCC_AHB1ENR).
Таким образом, адрес бита подлежащего изменению, высчитывается по формуле:
BIT_ADR=PERIPH_BB_BASE + (RCC_BASE + RCC_AHB1ENR) * 32 + RCC_AHB1ENR_GPIODEN_N * 4
где:
— PERIPH_BB_BASE — определено значение 0x42000000, определение находиться в файле stm32f40x.inc нашего проекта
![](https://habrastorage.org/files/db1/9de/565/db19de5659854a49bcdc07ba400788ba.jpg)
— RCC_BASE + RCC_AHB1ENR — это тоже 2 константных значения которые мы уже вычисляли во второй части публикации
— RCC_AHB1ENR_GPIODEN_N — номер бита в регистре RCC_AHB1ENR. Этого определения в файле констант не было, я добавил самостоятельно, для того чтобы понимать что константа указывает не на значение, а лишь на порядковый номер бита в слове я всегда добавляю "_N" к имени константы.
![](https://habrastorage.org/files/dcd/8b0/ada/dcd8b0adaeea468aae7b95777e4a86a9.jpg)
Получаем следующий код для включения GPIOD:
@ включим тактирование GPIO_D
LDR R0, =(PERIPH_BB_BASE + (RCC_BASE + RCC_AHB1ENR) * 32 + RCC_AHB1ENR_GPIODEN_N * 4) @ адрес бита
MOV R1, 1 @ значение для бита
STR R1, [R0] @ запись R1 по адресу указанному в R0
Установив в R0 адрес бита в bitband_alias, командой STR R1, [R0] мы записываем не значение 32-ух битного регистра R1 по адресу в регистре R0, а устанавливаем значение одного бита с номером 3 в регистре RCC_AHB1ENR.
При этом нам не нужно проводить операцию: чтение | логическое «ИЛИ» | запись, это все происходит на аппаратном уровне.
К слову, объединение трех операций в одну при bitbanding является важной при некоторых применениях, когда важна «атомарность» (неделимость) действия при изменению значения бита (обычно это оперирование различными «флагами» и «семафорами» в много- задачных или псевдо- многозадачных программах)
Давайте перепишем нашу программу «мигалка» с использованием метода bitband:
@GNU AS
@ Настройки компилятора
.syntax unified @ тип синтаксиса
.thumb @ тип используемых инструкций Thumb
.cpu cortex-m4 @ микроконтроллер
.include "stm32f40x.inc" @ определения микроконтроллера
@ макрос псевдокоманды MOV32 нам теперь не нужен, я убрал его
@ таблица векторов прерываний
.section .text
.word 0x20020000 @ Вершина стека
.word Reset+1 @ Вектор сброса
Reset:
MOV R0, 0 @ Значение 0, будет использоваться для bitband
MOV R1, 1 @ значение 1, будет использоваться для bitband
@ включим тактирование GPIO_D
LDR R2, =(PERIPH_BB_BASE + (RCC_BASE + RCC_AHB1ENR) * 32 + RCC_AHB1ENR_GPIODEN_N * 4) @ адрес
STR R1, [R2] @ запись R1 ("1") по адресу бита указанному в R2
@ установим режим GPIO_D pin_15
LDR R2, =(PERIPH_BASE + GPIOD_BASE + GPIO_MODER) @ адрес
LDR R3, =GPIO_MODER_MODER15_0 @ значение
LDR R4, [R2] @ прочитали значение регистра
ORR R3, R3, R4 @ логическое, побитовое ИЛИ
STR R3, [R2] @ запись обновленного значения в GPIOD_MODER
LDR R2, =(PERIPH_BB_BASE + (GPIOD_BASE + GPIO_ODR) * 32 + 15*4) @ адрес бита
BLINK_LOOP:
@ включим светодиод
STR R1, [R2] @ запись R1 ("1") по адресу указанному в R2
BL DELAY @ пауза
@ выключим светодиод
STR R0, [R2] @ запись R0 ("0") по адресу указанному в R2
BL DELAY @ пауза
B BLINK_LOOP @ делаем цикл
DELAY:
LDR R3, =0x00100000 @ повтор цикла 0x0010 0000 раз.
Delay_loop:
SUBS R3, R3, 1
BNE Delay_loop
BX LR
Не ожидали столько изменений? Давайте их разберем!
В начало программы я вынес присвоение значений двум регистрам R0 = 0 и R1 = 1 эти значения мы будем записывать при использовании метода bitband, присвоение командой MOV тоже произведено намеренно — при таком присвоении осуществляется загрузка 16-ти битного значения в младшие 16 бит регистра, с обнулением старших 16 бит. Таким образом R0 (в 32ух битах) у нас будет равен «0», а R1 (в 32ух битах) будет равен «1».
MOV R0, 0 @ Значение 0, будет использоваться для bitband
MOV R1, 1 @ значение 1, будет использоваться для bitband
Далее идет уже разобранный нами блок включения GPIOD методом bitband, я использую регистр R2 поскольку R0 и R1 уже заняты нашими константами («0», «1» соответственно):
@ включим тактирование GPIO_D
LDR R2, =(PERIPH_BB_BASE + (RCC_BASE + RCC_AHB1ENR) * 32 + RCC_AHB1ENR_GPIODEN_N * 4) @ адрес
STR R1, [R2] @ запись R1 ("1") по адресу бита указанному в R2
Устанавливаем режим работы 15 вывода GPIOD на выход, пока, чтобы не путать повествование, оставил процедуру прежней, хотя ее можно легко переписать на присвоение значения методом bitband тогда она сократиться до 2х строчек кода
@ установим режим GPIO_D pin_15
LDR R2, =(PERIPH_BASE + GPIOD_BASE + GPIO_MODER) @ адрес
LDR R3, =GPIO_MODER_MODER15_0 @ значение
LDR R4, [R2] @ прочитали значение регистра
ORR R3, R3, R4 @ логическое, побитовое ИЛИ
STR R3, [R2] @ запись обновленного значения в GPIOD_MODER
Поскольку в цикле «мигания» адрес регистра управляющего светодиодом меняться не будет — я вынес это значение за пределы цикла. Так же в качестве регистра вывода я использую регистр GPIO_ODR (ищите в Reference manual описание по GPIOx_ODR), с адресацией 15-го бита в режиме bitband (к 15-му биту у нас подключен светодиод).
LDR R2, =(PERIPH_BB_BASE + (GPIOD_BASE + GPIO_ODR) * 32 + 15*4) @ адрес бита
Цикл мигания светодиодом упрощается! теперь нам просто нужно записывать по адресу бита (методом bitband) значение «0» или «1» для включения светодиода, и вызывать между записью паузу
BLINK_LOOP:
@ включим светодиод
STR R1, [R2] @ запись R1 ("1") по адресу указанному в R2
BL DELAY @ пауза
@ выключим светодиод
STR R0, [R2] @ запись R0 ("0") по адресу указанному в R2
BL DELAY @ пауза
B BLINK_LOOP @ делаем цикл
Для паузы я использую значение регистра R3 (R0, R1, R2 уже заняты)
DELAY:
LDR R3, =0x00100000 @ повтор цикла 0x0010 0000 раз.
Delay_loop:
SUBS R3, R3, 1
BNE Delay_loop
BX LR
Запустив компиляцию и сборку нашего проекта мы увидим размер программы — 76 байт (если помните, первоначальный вариант «весил» — 116 байт)
Этот проект можно скачать по ссылке
ВНИМАНИЕ !
Поскольку у меня нет платы STM32F4 Discovery все примеры данной публикации выложены «как есть», без предварительной проверки на работоспособность. В случае если что то не заработало — пишите: посмотрим, послушаем, разберемся (с) «Берегись автомобиля».
В порядке тренировки можете попробовать еще оптимизировать по размеру программу «мигалка» и поделиться результатами в комментариях к статье (можно использовать любые инструкции процессора).
На этом третью публикацию закончим.
продолжение STM32F4: GNU AS: Настраиваем среду компиляции (Часть 4)