Pull to refresh

STM32F4: GNU AS: Мигаем светодиодом (Версия для STM32F4 Discovery, Оптимизация) (Часть 3)

Reading time 13 min
Views 15K
Ну что же, новогодние праздники кончились, и начались трудовые будни :-) Продолжим разбираться с ассемблером на примере микроконтроллера 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.
Информация о подключениях есть как минимум в трех местах:
  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)
Tags:
Hubs:
+12
Comments 16
Comments Comments 16

Articles