Ну что же, новогодние праздники кончились, и начались трудовые будни :-) Продолжим разбираться с ассемблером на примере микроконтроллера STM32F4

Ссылки на прошлые публикации:
STM32F4: GNU AS: Программирование на ассемблере (Часть 1)
STM32F4: GNU AS: Мигаем светодиодом (Оживление) (Часть 2)

В комментариях никто не отписался о том что ему удалось запустить «мигалку» на STM32F4 Discovery — значит либо не пробовали, либо не удалось. Исправим эту мелочь:


1) Как я уже писал в прошлой публикации для нашего примера разница между моей отладочной платой и STM32F4 Discovery фактически только в других выводах к которым подключены светодиоды. То есть используется другой порт GPIOx и другой выход этого порта.
Узнать куда же все таки подключены светодиоды можно из документации. Я использовал Discovery kit for STM32F407/417 lines.
Информация о подключениях есть как минимум в трех местах:
  1. в параграфе 4.4 LEDs, стр. 16
  2. в таблице с названием Table 5. MCU pin description versus board function стр. 21-32, нужная нам информация на стр. 30
  3. на листе Figure 16. Peripherals, стр. 40
    image

Таким образом все становиться очевидным «до противного»: 4 светодиода подключены к GPIOD выводам 12, 13, 14, 15.
Как говорится выбираем любой, пусть в «мигалке» используется светодиод синего цвет подключенный к GPIOD 15

2) Изменяем программу для использования GPIOD (вместо GPIOH) и вывода 15 (вместо 2)
Первое что мы должны сделать — это включить тактирование GPIOD, тут даже не буду делать новый скриншот, новое значение для настройки есть на старом:
image
выделено значение для 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", но и справочные:
  1. labels.lst — файл с описанием всех меток с их значениями после сборки компоновщиком (раньше я его называл линковщиком)
  2. sections.lst — файл с описанием секций проекта
  3. 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



Давайте разберемся со схемой:
  1. Bitband region — область адресного пространства микроконтроллера где находиться контролируемый или проверяем бит. По другому говоря бит которым мы реально хотим изменить или состояние которого хотим прочитать должен находиться в этом диапазоне адресов
  2. Bitband alias — область адресного пространства проецируемая на bitband region. Говоря русским языком, при обращении к адресам из этой области можно проверить состояние бита из области bitband region или записав данные в эту область можно изменить состояние бита данных из области bitband region

Области bitband region имеют объем 1 мб
Области bitband alias — 32 мб
Как можно видеть из схемы распределения адресного пространства у микроконтроллеров STM32F4xx две области bitband region (связанные с ними bitband alias):
  1. в адресах 0x2000 0000 — 0x200F FFFF — то есть эта область покрывает все адреса SRAM1 и SRAM2 (вспоминаем адреса SRAM из первой части публикации!)
  2. в адресах 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

где,
  1. bitband_base — адрес bitband_alias
  2. byte_offset — смещение адреса регистра относительно начала bitband_alias
  3. 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)


  1. VitGo
    13.01.2016 10:54

    Посидел сейчас, и получил размер программы 56 байт… меньше уже не знаю как сделать — мешает формат THUMB инструкций (требуется выравнивание к 4 байтам — и программа слишком маленькая чтобы было что менять) :-)

    p.s. осталось только проверить на работоспособность (но это только дома)


    1. VitGo
      13.01.2016 10:56

      гм… 52 байта…


      1. VitGo
        13.01.2016 11:41

        ну и совсем неожиданные 44 байта, но этот вариант точно нужно проверить «на железе»…


  1. 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 
    

    или в сыром виде без моих камментов:
    image


    1. 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>
      

      Бинарник:


      1. VitGo
        14.01.2016 13:06

        помоему регистр перепутал на запись значения,… дома проверю :-)


      1. Mirn
        14.01.2016 13:15

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


        1. VitGo
          14.01.2016 13:23

          ну все примеры смысла не имеют, но для понимания механизма очень полезны!

          я вот про сдвиговую генерацию значения даже не додумался!!! более того — со сдвигами у меня туго — сидел и на бумажке рисовал сейчас :-))

          в моем примере запись в R2 и в R3 местами поменять нужно!!!

          кстати, на асме — на больших участках кода оптимизировать код проще — зачастую 5-10 строк асм-кода уже можно в подпрограммы выделять, да и частичное использование кода проще делать…

          хотя конечно на асме тяжело писать — меня например сейчас напрягает то, что не знаю как делать изолированные метки :-(
          в armasm метки кроме определенных директивами компилятора export/import видны только внутри файла исходника…
          то есть шанса на пересечение в разных файлах нет…
          а вот в GNU AS такой фичи не нашел :-((( вернее она там есть (по названию метки, не помню точно с «L» чтоли начинаться должны) — но указано (как я понял) как отмирающая что ли, и не во всех версиях реализована…


          1. Mirn
            14.01.2016 15:36

            " на асме — на больших участках кода оптимизировать код проще" вот только человек с этим справляется хуже т.к. оптимизирует частности и не всегда и не везде, а компилятор всё и сразу, может быть в каких то мелочах похуже человека но в целом картина выглядит существенно более сбалансированной.

            Как то надо было адаптировать gprs загрузчик с STM32F4xx на STM32F10x самый младший, при этом он был написан на Си и модульный, отдельно драйвер уарта, отдельно парсер команд, отдельно regexp для парсера, отдельно управлятор АТ командами и тд.
            А надо было уменьшить в 2-3 раза размер флеша и озу.
            При помощи простых операций не вмешиваясь в суть исходников на Си я это сделал вполне успешно, было потребление озу 24-30кбайт, стало 12кбайт. И вписался в 4к ОЗУ из первоначальных 12к.
            А сколько времени займёт переписать на ассемблере около 10 тысяч строк Си кода? и точно результат влезет в 12к флеша? Не профукаем ли мы какие либо мелочи спустя лет десять?

            Насчёт глобальных и локальных меток… помню лет 20 назад когда кодил на асме в турбоасме были директивы для этого, помоему это global и import если не ошибаюсь, попробуй поискать аналогичные.


            1. VitGo
              16.01.2016 08:08

              Насчёт глобальных и локальных меток… помню лет 20 назад когда кодил на асме в турбоасме были директивы для этого, помоему это global и import если не ошибаюсь, попробуй поискать аналогичные

              нашел, но что то не соображу как теперь cmd файл поправить :-(

              нужно в цикле организовать перебор каталогов и файлов в них, потом в этом же цикле создать в отдельном каталоге такую же структуру каталогов (вот тут я что то уже голову сломал как это сделать) и запустить для каждого файла .asm компилятор чтобы он сделал .o файл…
              а потом все получившиеся .o файлы подсунуть компановщику…

              p.s. у меня пока не получается создать структуру каталогов :-(


              1. Mirn
                16.01.2016 11:37

                а разве этим не makefile или Autotools должен заниматься?
                в том же эклипсе (работает на makefile) всё делается автоматом независимо от структуры директорий и не требуя от пользователя никаких действий


                1. VitGo
                  16.01.2016 13:24

                  makefile же не берется ниоткуда его и нужно написать… :-)

                  сейчас в принципе уже накидал, тестирую… очень интересная весчЬ получилась!!! наверное ее стоит выделить в публикацию


              1. grossws
                16.01.2016 14:53

                Посмотрите на waf, он выглядит крайне правильно.


      1. beeruser
        15.01.2016 01:44

        А зачем тут сдвиг, если значение всё равно берётся из младшего бита? Так?

        1: str r4,[r3, #644]
        adc r4,r4,#2048
        b 1b


        1. beeruser
          15.01.2016 02:05

          Запостил а потом подумал.
          Имел в виду конечно же ADCS, которого в thumb2 в такой форме нет.