Ссылки на прошлые публикации:
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, тут даже не буду делать новый скриншот, новое значение для настройки есть на старом:
выделено значение для 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 — давайте попробуем применить ее и посмотреть что получится:
@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
Давайте разберемся со схемой:
- 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"
Для простоты запишем ее в одну строчку:
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 нашего проекта
— RCC_BASE + RCC_AHB1ENR — это тоже 2 константных значения которые мы уже вычисляли во второй части публикации
— RCC_AHB1ENR_GPIODEN_N — номер бита в регистре RCC_AHB1ENR. Этого определения в файле констант не было, я добавил самостоятельно, для того чтобы понимать что константа указывает не на значение, а лишь на порядковый номер бита в слове я всегда добавляю "_N" к имени константы.
Получаем следующий код для включения 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)
Комментарии (15)
Mirn
14.01.2016 11:01+2написал на gcc за 10 минут тоже самое и сразу влезло в 52 байта
Program Size: text data bss dec hex filename 52 0 65536 65588 10034 Gate_Tester.elf
версия GNU Tools ARM Embedded 4.6 2012q4
ключи компилирования:
arm-none-eabi-gcc -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -Wall -ffunction-sections -g -Os -c…
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Wl -nostartfiles -g -Wl,-Map=Gate_Tester.map -Os -Wl,--gc-sections -LE:/gsm/src/Gate_Tester -Wl,-TE:/gsm/src/Gate_Tester/arm-gcc-link.ld -g -o Gate_Tester.elf ..\obj\stm32kiss_button.o
оптимизация по размеру -Os, LTO (оптимизация при линковке) нет в этой версии gcc
void min_Reset_Handler(); #define STACK_SIZE 0x00004000 /*!< Stack size (in Words) */ __attribute__ ((section(".co_stack"))) unsigned long minStack[STACK_SIZE]; __attribute__ ((section(".min_isr_vector"))) void (* const minVectors[])(void) = { (void *)&minStack[STACK_SIZE-1], /*!< The initial stack pointer*/ min_Reset_Handler, /*!< Reset Handler */ }; void min_Reset_Handler() { RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; GPIOD->MODER |= GPIO_MODER_MODER15_0; uint32_t counter; volatile uint32_t *led = (*(volatile uint32_t *)(PERIPH_BB_BASE + ((((uint32_t)&GPIOD->ODR) - PERIPH_BASE)*32) + 15*4)); while (true) { (*led) = counter >> 31; counter += 1024; } }
Disassembly of section .text: 08000000 <minVectors>: 8000000: fc ff 00 10 09 00 00 08 ........ 08000008 <min_Reset_Handler>: 8000008: 4b08 ldr r3, [pc, #32] ; (800002c <min_Reset_Handler+0x24>) ;RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; 800000a: 6b19 ldr r1, [r3, #48] ; 0x30 800000c: f041 0108 orr.w r1, r1, #8 8000010: 6319 str r1, [r3, #48] ; 0x30 ;GPIOD->MODER |= GPIO_MODER_MODER15_0; 8000012: f5a3 5330 sub.w r3, r3, #11264 ; 0x2c00 8000016: 6819 ldr r1, [r3, #0] 8000018: f041 4180 orr.w r1, r1, #1073741824 ; 0x40000000 800001c: 6019 str r1, [r3, #0] ;volatile uint32_t *led = (*(volatile uint32_t *)(PERIPH_BB_BASE + ((((uint32_t)&GPIOD->ODR) - PERIPH_BASE)*32) + 15*4)); 800001e: 4b04 ldr r3, [pc, #16] ; (8000030 <min_Reset_Handler+0x28>) ;uint32_t counter; 8000020: 681b ldr r3, [r3, #0] <min_Reset_Handler_while>: 8000022: 0fd1 lsrs r1, r2, #31 ; 8000024: 6019 str r1, [r3, #0] ; (*led) = counter >> 31; 8000026: f502 6280 add.w r2, r2, #1024 ; counter += 1024; 800002a: e7fa b.n 8000022 <min_Reset_Handler_while> 800002c: 40023800 .word 0x40023800 8000030: 424182bc .word 0x424182bc
или в сыром виде без моих камментов:VitGo
14.01.2016 12:47ну на счет 10 минут — что то «меня берут смутные сомнения» (с) Брилиантовая рука, но «ход» со сдвигом- однозначно интересен!!! я до такого простого и краткого решения не додумался…
тогда 38 байт! (только с позицией стека нужно уточнить, но помоему правильно)
Disassembly of section .text: 00000000 <AB_STACK-0x8>: 0: 00000008 .word 0x00000008 4: 00000015 .word 0x00000015 00000008 <AB_STACK>: 8: 00000001 .word 0x00000001 c: 4247060c .word 0x4247060c 10: 42418038 .word 0x42418038 00000014 <Reset>: 14: bc0e pop {r1, r2, r3} 16: 6019 str r1, [r3, #0] 18: 6011 str r1, [r2, #0] 0000001a <BLINK_LOOP>: 1a: 0fe1 lsrs r1, r4, #31 1c: f8c3 1284 str.w r1, [r3, #644] ; 0x284 20: f504 6480 add.w r4, r4, #1024 ; 0x400 24: e7f9 b.n 1a <BLINK_LOOP>
Бинарник:
Mirn
14.01.2016 13:15браво, но смысла нет кроме рекорда длинны и образовательного смысла для тех кто слаб в асме.
я своим постом хотел сказать что даже на мелких и ничтожно мелких задачах Си в прямых руках по всем параметрам на уровне асма.
А по потреблению времени — реально 10 минут, я дольше ответ оформлял.VitGo
14.01.2016 13:23ну все примеры смысла не имеют, но для понимания механизма очень полезны!
я вот про сдвиговую генерацию значения даже не додумался!!! более того — со сдвигами у меня туго — сидел и на бумажке рисовал сейчас :-))
в моем примере запись в R2 и в R3 местами поменять нужно!!!
кстати, на асме — на больших участках кода оптимизировать код проще — зачастую 5-10 строк асм-кода уже можно в подпрограммы выделять, да и частичное использование кода проще делать…
хотя конечно на асме тяжело писать — меня например сейчас напрягает то, что не знаю как делать изолированные метки :-(
в armasm метки кроме определенных директивами компилятора export/import видны только внутри файла исходника…
то есть шанса на пересечение в разных файлах нет…
а вот в GNU AS такой фичи не нашел :-((( вернее она там есть (по названию метки, не помню точно с «L» чтоли начинаться должны) — но указано (как я понял) как отмирающая что ли, и не во всех версиях реализована…Mirn
14.01.2016 15:36" на асме — на больших участках кода оптимизировать код проще" вот только человек с этим справляется хуже т.к. оптимизирует частности и не всегда и не везде, а компилятор всё и сразу, может быть в каких то мелочах похуже человека но в целом картина выглядит существенно более сбалансированной.
Как то надо было адаптировать gprs загрузчик с STM32F4xx на STM32F10x самый младший, при этом он был написан на Си и модульный, отдельно драйвер уарта, отдельно парсер команд, отдельно regexp для парсера, отдельно управлятор АТ командами и тд.
А надо было уменьшить в 2-3 раза размер флеша и озу.
При помощи простых операций не вмешиваясь в суть исходников на Си я это сделал вполне успешно, было потребление озу 24-30кбайт, стало 12кбайт. И вписался в 4к ОЗУ из первоначальных 12к.
А сколько времени займёт переписать на ассемблере около 10 тысяч строк Си кода? и точно результат влезет в 12к флеша? Не профукаем ли мы какие либо мелочи спустя лет десять?
Насчёт глобальных и локальных меток… помню лет 20 назад когда кодил на асме в турбоасме были директивы для этого, помоему это global и import если не ошибаюсь, попробуй поискать аналогичные.VitGo
16.01.2016 08:08Насчёт глобальных и локальных меток… помню лет 20 назад когда кодил на асме в турбоасме были директивы для этого, помоему это global и import если не ошибаюсь, попробуй поискать аналогичные
нашел, но что то не соображу как теперь cmd файл поправить :-(
нужно в цикле организовать перебор каталогов и файлов в них, потом в этом же цикле создать в отдельном каталоге такую же структуру каталогов (вот тут я что то уже голову сломал как это сделать) и запустить для каждого файла .asm компилятор чтобы он сделал .o файл…
а потом все получившиеся .o файлы подсунуть компановщику…
p.s. у меня пока не получается создать структуру каталогов :-(Mirn
16.01.2016 11:37а разве этим не makefile или Autotools должен заниматься?
в том же эклипсе (работает на makefile) всё делается автоматом независимо от структуры директорий и не требуя от пользователя никаких действийVitGo
16.01.2016 13:24makefile же не берется ниоткуда его и нужно написать… :-)
сейчас в принципе уже накидал, тестирую… очень интересная весчЬ получилась!!! наверное ее стоит выделить в публикацию
VitGo
Посидел сейчас, и получил размер программы 56 байт… меньше уже не знаю как сделать — мешает формат THUMB инструкций (требуется выравнивание к 4 байтам — и программа слишком маленькая чтобы было что менять) :-)
p.s. осталось только проверить на работоспособность (но это только дома)
VitGo
гм… 52 байта…
VitGo
ну и совсем неожиданные 44 байта, но этот вариант точно нужно проверить «на железе»…