ASM в STM32. Начало

Настоящим джедаям посвящается

О IDE

Попробовав Keil, уже не могу работать на чем-нибудь другом под арм. Мощный макро-язык, крутой отладчик. Поэтому далее все касательно Keil'а. Вот только мой любимый F100C4 отладчик не знает, да и с другими(например, F103) C4 не все в порядке. Может в свежих версиях уже поправили? Так что отладка на F103C8. Благо, разницы почти нет.

О сегментах

Для удобства и чтобы избежать некоторых проблем (например, с выравниванием) желательно делить программу минимум на следующие сегменты:
AREA RESET, DATA, READONLY — стартовый сегмент (стартап)
AREA |.text|, CODE, READONLY — программный сегмент
AREA |.text|,DATA,READONLY — сегмент данных (flash)
AREA |header data|,DATA,READWRITE — сегмент данных (ram)

О стартапе

Это банальная таблица векторов прерываний. Она несколько больше, чем у 8ми-битников. Располагается вначале программного флеша, т.е. с адреса 0x8000 0000. Порядок векторов расписан в Reference manual для конкретного семейства (100, 101, 102, 103 и т.д.) и подсемейства (connectivity line, XL-density и т.д.). Вот, для примера, таблица под STM32F100C4:
;----interrupt---------------------------------------------
;Vector table for STM32F100xx devices
;----M3--------------------------------
 	DCD     top_of_stack	;0000 Top of Stack
	DCD     Reset_Handler	;0004 Reset Handler
	DCD     Reset_Handler	;0008 NMI Handler
	DCD     Reset_Handler	;000C Hard Fault Handler
	DCD     Reset_Handler	;0010 MemManage Handler
	DCD     Reset_Handler	;0014 Bus Fault Handler
	DCD     Reset_Handler	;0018 Usage Fault Handler
	DCD     Reset_Handler	;001C Reserved
	DCD     Reset_Handler	;0020 Reserved
	DCD     Reset_Handler	;0024 Reserved
	DCD     Reset_Handler	;0028 Reserved
	DCD     Reset_Handler	;002C SVC Handler
	DCD     Reset_Handler	;0030 Debug Monitor Handler
	DCD     Reset_Handler	;0034 Reserved
  	DCD     Reset_Handler	;0038 PendSV Handler
	DCD     Reset_Handler	;003C SysTick Handler
;----32F100----------------------------
  	DCD     Reset_Handler	;0040 Window Watchdog
	DCD     Reset_Handler	;0044 PVD through EXTI Line detect
   	DCD     Reset_Handler	;0048 Tamper
	DCD     Reset_Handler	;004C RTC Wakeup through EXTIline interrupt
 	DCD     Reset_Handler	;0050 Flash global interrupt
  	DCD     Reset_Handler	;0054 RCC global interrupt
 	DCD     Reset_Handler	;0058 EXTI Line 0	(6)
	DCD     Reset_Handler	;005C EXTI Line 1	(7)
	DCD     Reset_Handler	;0060 EXTI Line 2	(8)
	DCD     Reset_Handler	;0064 EXTI Line 3	(9)
	DCD     Reset_Handler	;0068 EXTI Line 4	(10)
   	DCD     Reset_Handler	;006C DMA Channel 1	(11)
	DCD     Reset_Handler	;0070 DMA Channel 2
	DCD     Reset_Handler	;0074 DMA Channel 3
	DCD     Reset_Handler	;0078 DMA Channel 4
	DCD     Reset_Handler	;007C DMA Channel 5
	DCD     Reset_Handler	;0080 DMA Channel 6
	DCD     Reset_Handler	;0084 DMA Channel 7
  	DCD     Reset_Handler	;0088 ADC1 global interrupt	(18)
	DCD     Reset_Handler	;008C Reserved
	DCD     Reset_Handler	;0090 Reserved
	DCD     Reset_Handler	;0094 Reserved
	DCD     Reset_Handler	;0098 Reserved
   	DCD     Reset_Handler	;009C EXTI Line 9..5
	DCD     Reset_Handler	;00A0 TIM1 Break & TIM15 global interrupt
	DCD     Reset_Handler	;00A4 TIM1 Update & TIM16 global interrupt
	DCD     Reset_Handler	;00A8 TIM1 Trigger and Commutation
	DCD     Reset_Handler	;00AC TIM1 Capture Compare
	DCD     Reset_Handler	;00B0 TIM2 global interrupt	(28)
	DCD     Reset_Handler	;00B4 TIM3 global interrupt	(29)
	DCD     Reset_Handler	;00B8 TIM4 global interrupt	(30)
	DCD     Reset_Handler	;00BC I2C1 Event (31)
	DCD     Reset_Handler	;00C0 I2C1 Error (0)
	DCD     Reset_Handler	;00C4 I2C2 Event
	DCD     Reset_Handler	;00C8 I2C2 Error
  	DCD     Reset_Handler	;00CC SPI1 global interrupt	(3)
	DCD     Reset_Handler	;00D0 SPI2 global interrupt
	DCD     Reset_Handler	;00D4 USART1 global interrupt
	DCD     Reset_Handler	;00D8 USART2 global interrupt
	DCD     Reset_Handler	;00DC USART3 global interrupt
	DCD     Reset_Handler	;00E0 EXTI Line 15..10
	DCD     Reset_Handler	;00E4 RTC Alarm through EXTI Line
	DCD     Reset_Handler	;00E8 CEC global interrupt
	DCD     Reset_Handler	;00EC TIM12 global interrupt
	DCD     Reset_Handler	;00F0 TIM13 global interrupt
	DCD     Reset_Handler	;00F4 TIM14 global interrupt
 	DCD     Reset_Handler	;00F8 Reserved
	DCD     Reset_Handler	;00FC Reserved
 	DCD     Reset_Handler	;0100 FSMC global interrupt
 	DCD     Reset_Handler	;0104 Reserved
 	DCD     Reset_Handler	;0108 TIM5 global interrupt
	DCD     Reset_Handler	;010C SPI3 global interrupt
	DCD     Reset_Handler	;0110 USART4 global interrupt
	DCD     Reset_Handler	;0114 USART5 global interrupt
	DCD     Reset_Handler	;0118 TIM6 global and DACunderrun inter.s
	DCD     Reset_Handler	;011C TIM7 global interrupt
	DCD     Reset_Handler	;0120 DMA2 Channel1 global interrupt 
 	DCD     Reset_Handler	;0124 DMA2 Channel2 global interrupt
 	DCD     Reset_Handler	;0128 DMA2 Channel3 global interrupt
 	DCD     Reset_Handler	;012C DMA2 Channel4/5 global interrupt
 	DCD     Reset_Handler	;0130 DMA2 Channel5 global interrupt
Настоящий вектор Reset_Handler, естественно, первый, а остальные — заглушки. Если понадобится какое-то прерывание — мы заменим его заглушку на необходимую нам метку.

О стеке

Для стека удобно сразу выделить область в раме:
stack_size	equ 0x100
top_of_stack	equ 0x20000000 +stack_size

		AREA |header data|,DATA,READWRITE
stack		space stack_size
О константах

Наиболее употребляемые константы — 0 и 1 удобно держать подготовленными в каких-нибудь регистрах (например, 0 в R10 а 1 в R11). Это ускоряет написание программы и сокращает ее размеры. Поэтому, сразу после метки сброса пишем:
Reset_Handler
	mov r10,#0
	mov r11,#1
Главное потом об этом не забывать. Можно переименовать для удобства сами регистры, например:
zero	RN R10	;const 0
one	RN R11	;const 1
data	RN R3   ;temp reg
А затем их проинициализировать:

        mov zero,#0
	mov one,#1
Критерии выбора регистров под константы просты: во-первых, лучше не трогать регистры, которые автоматически сохраняются при прерывании, т.е. r0, r1, r2, r3, r12; во-вторых, название регистра должно намекать на его содержимое. Поэтому альтернатив r10, r11 я не вижу.

О макросах

Макросы позволяют писать программы быстро и читабельно. Важно чтобы они были простыми для запоминания. Поскольку в нашем риск-процессоре частые операции чтение-модификация-запись, необходимо упростить рутину их написания с помощью макросов.
Удобнее писать loadb var(переменная), чем
mov32 r0,#var(переменная)
ldrb r1,[r0]
Главное запомнить используемые в макросе регистры, специализировать их и не менять специализацию в однотипных макросах. Для примера, группа макросов работы с байтовой переменной:
;loadb var0 - загрузка переменной var0 в r1
		macro
		loadb $base
		mov32 r0,#$base
		ldrb r1,[r0]
		mend

;saveb var0 - выгрузка переменной var0 из r1 
		macro
		saveb $base
		mov32 r0,#$base
		strb r1,[r0]
		mend

;testb var0 - проверка на 0
		macro
		testb $base
		mov32 r0,#$base
		ldrb r1,[r0]
		tst r1,r1
		mend

;clrb var0 - сброс в 0 (если r10=0)
		macro
		clrb $base
		mov32 r0,#$base
		strb r10,[r0]
		mend

;setb var0 - установка в 1 (если r11=1)
		macro
		setb $base
		mov32 r0,#$base
		strb r11,[r0]
		mend 

;incb var0 - увеличение переменной на 1
		macro
		incb $base
		mov32 r0,#$base
		ldrb r1,[r0]
		add r1,#1
		strb r1,[r0]
		mend

;decb var0 - уменьшение переменной на 1
		macro
		decb $base
		mov32 r0,#$base
		ldrb r1,[r0]
		sub r1,#1
		strb r1,[r0]
		mend
Также можно поступить и с переменными размером в полуслово и слово (loadh, loadw например). Макросы — очень мощный инструмент для ассемблерщика и пренебрегать им не стоит. Удобные макросы приближают скорость разработки ассемблерной программы к скорости написания в ЯВУ(+ улучшается читаемость программы). Для примера, настройка портов может выглядеть так:

		startportconfig portA,0
		pinconfig 0,out10push_pull
		pinconfig 1,out10push_pull
		pinconfig 2,out10push_pull
		pinconfig 6,inpull_up
		pinconfig 7,inpull_up
		endportconfig portA,0

		startportconfig portA,1
		pinconfig 11,out10push_pull_alt
		pinconfig 12,out10push_pull_alt
		pinconfig 13,out10push_pull_alt
		pinconfig 14,out10open_drain_alt
		pinconfig 15,out10open_drain_alt
		endportconfig portA,1
если прописать соответствующие макросы (и константы):

		macro
		startportconfig $port,$reg
		ldr r0,=$port
		if $reg=0
		ldr r1,[r0,#pcrl]		;read pins0-7 config
		else 
		ldr r1,[r0,#pcrh]		;read pins8-15 config
		endif 
		mend

		macro
		endportconfig $port,$reg
		if $reg=0
		str r1,[r0,#pcrl]		;write pins0-7 config
		else
		str r1,[r0,#pcrh]		;write pins8-15 config
		endif
		mend

		macro
		pinconfig $pin,$config
		movs r2,#$config
		if $pin=0
		bfi r1,r2,#cnfpin0,#4
		elif $pin=1
		bfi r1,r2,#cnfpin1,#4
		elif $pin=2
		bfi r1,r2,#cnfpin2,#4
		elif $pin=3
		bfi r1,r2,#cnfpin3,#4
		elif $pin=4
		bfi r1,r2,#cnfpin4,#4
		elif $pin=5
		bfi r1,r2,#cnfpin5,#4
		elif $pin=6
		bfi r1,r2,#cnfpin6,#4
		elif $pin=7
		bfi r1,r2,#cnfpin7,#4
		elif $pin=8
		bfi r1,r2,#cnfpin0,#4
		elif $pin=9
		bfi r1,r2,#cnfpin1,#4
		elif $pin=10
		bfi r1,r2,#cnfpin2,#4
		elif $pin=11
		bfi r1,r2,#cnfpin3,#4
		elif $pin=12
		bfi r1,r2,#cnfpin4,#4
		elif $pin=13
		bfi r1,r2,#cnfpin5,#4
		elif $pin=14
		bfi r1,r2,#cnfpin6,#4
		elif $pin=15
		bfi r1,r2,#cnfpin7,#4		 
		endif
		mend
Или вывод на 20x4 LCD может быть такой:
printlcd 0, 5, text0
если макрос:

	macro
	printlcd $line,$pos,$string
		if $line=0
			mov32 r1,#(128+0+$pos)
			else
			if $line=1
				mov32 r1,#(128+40+$pos)
				else
				if $line=2
					mov32 r1,#(128+20+$pos)
					else
					if $line=3
						mov32 r1,#(128+60+$pos)
					endif
				endif
			endif
		endif
		mov32 r0,#$string
		bl printx
		mend
Естественно, голову макрос не заменит :)

О константах периферии

STM32 богат на всякие вкусности, что поначалу даже пугает. Поэтому поступаем как обычно — делим большое на куски. Если используем какой-то периферийный блок (например, порты), расписываем его регистры (и константы для них) например так:
;----------------------------------------------------------
;----GPIO--------------------------------------------------
;----------------------------------------------------------
gpioa			equ 0x40010800	;port A section
portA			equ 0x40010800
gpiob			equ 0x40010c00	;port B section
portB			equ 0x40010c00
gpioc			equ 0x40011000	;port C section
portC			equ 0x40011000
gpiod			equ 0x40011400	;port D section
portD			equ 0x40011400
gpioe			equ 0x40011800	;port E section
portE			equ 0x40011800
gpiof	  	        equ 0x40011c00	;port F section
portF			equ 0x40011c00
gpiog		        equ 0x40012000	;port G section
portG		        equ 0x40012000
;--------------------------------------
;gpio registers
pcrl		equ 0x00	;Port configuration register low
pcrh		equ 0x04	;Port configuration register high
pidr		equ 0x08	;Port input data register
podr		equ 0x0c	;Port output data register
pbsrr		equ 0x10	;Port bit set/reset register
pbrr		equ 0x14	;Port bit reset register
plckr		equ 0x18	;Port configuration lock register

out10m			equ 2_0001	;режим выхода 10Мгц макс
out2m			equ 2_0010	;режим выхода 2Мгц макс
out50m			equ 2_0011	;режим выхода 50Мгц макс

inanm			equ 2_0000	;для mode0 аналоговый вход
infloat			equ 2_0100	;для mode0 вход в третьем состоянии (состояние после сброса)
inpull_up		equ 2_1000	;для mode0 вход с подтяжкой pxodr=1 - pull-up / pxodr=0 - pull-down

out10push_pull		equ 2_0001	;для mode1-3 двухтактный выход
out10open_drain		equ 2_0101	;для mode1-3 выход с открытым стоком
out10push_pull_alt	equ 2_1001	;для mode1-3 двухтактный выход альтернативный
out10open_drain_alt	equ 2_1101	;для mode1-3 выход с открытым стоком альтернативный

out2push_pull		equ 2_0010	;для mode1-3 двухтактный выход
out2open_drain		equ 2_0110	;для mode1-3 выход с открытым стоком
out2push_pull_alt	equ 2_1010	;для mode1-3 двухтактный выход альтернативный
out2open_drain_alt	equ 2_1110	;для mode1-3 выход с открытым стоком альтернативный

out50push_pull		equ 2_0011	;для mode1-3 двухтактный выход
out50open_drain		equ 2_0111	;для mode1-3 выход с открытым стоком
out50push_pull_alt	equ 2_1011	;для mode1-3 двухтактный выход альтернативный
out50open_drain_alt	equ 2_1111	;для mode1-3 выход с открытым стоком альтернативный

cnfpin0			equ	0
cnfpin1			equ	4
cnfpin2			equ	8
cnfpin3			equ	12
cnfpin4			equ	16
cnfpin5			equ	20
cnfpin6			equ	24
cnfpin7			equ	28
cnfpin8			equ	0
cnfpin9			equ	4
cnfpin10	        equ	8
cnfpin11		equ	12
cnfpin12		equ	16
cnfpin13		equ	20
cnfpin14		equ	24
cnfpin15		equ	28
;----------------------------------------------------------
Варианты могут быть и другие (например, определение сразу абсолютного адреса регистра, а не как выше — относительно блока). Главный критерий — удобство применения этих констант в ваших макросах. Блоки макросов лучше собирать в отдельный файл и подключать к программе командой GET имя_equ.asm.

О псевдооператорах

Важно знать какой код получаем от псевдооператоров (ldr, mov32 и др.) и их особенности. Об этом хорошо рассказывает кейл (достаточно посмотреть код в отладчике и почитать ейный хелп). Эти знания реально экономят время и нервы (особенно если программа больше 4kB).

О суффиксах

Применение суффиксов условного исполнения команд значительно облегчает написание кода, но не увлекайтесь. Есть соблазн написать всю обработку части условия условными командами и выйти из подпрограммы. Однако это не только замедляет подпрограмму, но и при изменении условия заставляет переделывать весь «условный» код.

О подпрограммах

Следите за LR!!! (особенно если много макросов).

О глюках и ошибках

Какой бы мистической ни казалась реакция программы, всегда есть рациональное объяснение. Проверьте: тип переменной; куда возвращаемся из подпрограммы (прерывания); нет ли ошибки в макросе; сбросили ли флаг прерывания; изменяет ли команда те флаги, которые мы потом проверяем; в той ли очередности вернули из стека и т.д. и т.п.

О процессоре

На мой взгляд, идеальный процессор для асма и радиолюбителя это STM32F100C4. Он забит периферией больше чем его собратья из серий «покруче», частоты в 24Мгц достаточно для большинства радиолюбительских устройств, 16kB flash + 4kB ram тоже, 48 ног и ЦЕНА! конечно. Реальная замена соответствующих восьмибитников.

О программаторе

Пока пользуюсь STM32L-Discovery (u-link забросил — не удобно).

Итого

Процесс разработки устройства на асме STM32 ничем не отличается от того же на avr или stm8. Время вхождения, конечно, больше, т.к. необходимо осваивать немного более сложную периферию. Зато писать на асме STM32 и быстрее и удобнее чем, например, под avr (ИМХО).

И напоследок, заготовка под асм-проект для STM32F100C4.
  • +5
  • 09 февраля 2012, 12:46
  • psv
  • 1
Файлы в топике: newstart.zip

Комментарии (83)

RSS свернуть / развернуть
Вопрос. А кроме кейла существуют ли IDE поддерживающие написание программ на асм? Я что-то не встречал.
0
А IAR разве не?
0
я пробовал в CoIDE но там gnu as…
после «обычных» intel-ассемблеров синтаксис gnu as (в формате AT&T) выводит из себя :-(
мучался неделю, но в итоге сейчас перехожу на armasm (в Keil он используется)

p.s. писал на асме под AVR вообще не задумывался об синтаксисе самого компилятора, настолько он был привычен (там тоже синтаксис intel)…
0
asm зачем? для флейма все эти инкрементирования — в самый раз, конечно.
0
  • avatar
  • bzzz
  • 09 февраля 2012, 14:39
Все зависит от задач, хотя иногда и просто чтобы потешить себя…
0
потешить — это можно, конечно. хотя я как-то перестал понимать в чем тут «тешь». писал в свое время «нортен коммандеры» и прочую белиберду на макро для УКНЦ…
0
Что-то все эти макросы наводят меня на вопрос, а не окажется ли программа на С компактнее и быстрее той, что написана на ассемблере?
0
  • avatar
  • Vga
  • 09 февраля 2012, 23:47
Никак нет, макрос же не выполняется а лишь подставляет в программу кусок кода, подходящего по условию.
0
Вот именно что подставляет. Код при этом резво пухнет и я думаю, умный оптимизатор легко на этом обойдет ассемблерщика.
0
Типичный выбор — макрос или подпрограмма
0
Если макросы необдуманно использовать, то разумеется.
А иногда макрос можно применить просто для более читаемого исходника в конкретном проекте. AVR-овский пример:
.MACRO led_on
SBI PORTD,5
.ENDM
....
led_on  ; просто используем более короткую и понятную запись,
led_off ; не жертвуя ничем
0
#define
0
Я бы слово иногда заменил на всегда. Макрос — это читабельная форма подготовки данных для подпрограмм. Обязательный этап как для асма так и для С. Просто для С этот этап несколько длиннее.
0
Смотри как бы тебя последователи С++ не услышали :)
0
Да я же не наезжаю. Просто констатирую, что передаю данные через регистры.
0
Забросил в STM32
0
кстати файл вложенный в статью у меня не скомпилировался

нужно таблицу векторов объявить так:
		AREA    RESET, DATA, READONLY
;		GET equ32F100C4.asm
;		GET macro32F100C4.asm
;----interrupt---------------------------------------------
;Vector table for STM32F100xx devices
;----M3--------------------------------
__Vectors
		EXPORT	__Vectors
после этого компилятор видит таблицу векторов и размещает ее по правильному адресу

ну и код тоже нужно объявить так
	AREA    |.text|, CODE, READONLY
    	ENTRY
	ALIGN
Reset_Handler
		EXPORT Reset_Handler

после этого все компилируется нормально…
0
  • avatar
  • WitGo
  • 13 февраля 2012, 08:13
или это был файл под какой то другой компилятор ассемблера?

тогда поделитесь! :-)
0
  • avatar
  • WitGo
  • 13 февраля 2012, 08:26
Файл не какой-нибудь экспериментальный. На его основе с десяток проектов сделано. Все компилится. Вы наверно сишный стартап подключили или еще что.
0
Врядли там С стартап. Функцию надо экспортировать, что бы она была видна вне модуля. Если функция не экспортирована, то компилятор её может спокойно покосить, т.к. на неё нет ссылок. Естественно линкер потом и недоумевает, мол: «говорят привязать, а ничего не дали»…
0
Когда пользовался сишным стартапом — да, делал эспорты-импорты. Теперь, когда один файл куда экпортировать?
0
Просто насколько я понял логику armasm экспортировать нужно все что должно быть видно снаружи от блока PROC ENDP
В какой то мере это даже удобно, метки циклом, ветвлений- можно в разных блоках одинаково называть!!! (например в AVR при росте кода больше какого то размера я специально вводил в имя метки префикс модуля чтобы небыло пересечений по именам меток) Здесь очень даже удобно!
написали модуль — вывели метку по которой к нему обращаться (Команда EXPORT LABEL)

может быть зря пишу, но коль уж докопался напишу что у меня получилось
имеем код:
    PROC    MAIN
LABEL1 
    EXPORT LABEL1
    MOV32    R1    , #LABEL1
LABEL2
    MOV32    R2    , #LABEL2
... 
    ENDP
    
    MOV    R3    , #LABEL1    ; это допустимая команда, LABEL1 видна !
    MOV    R4    , #LABEL2 ; это не допустимая команда LABEL2 не экспортирована для видимости
0
Пока в это далеко не влазил, нужно попробовать( + чтобы делить программу по файлам).
0
А вот это имхо не правильно. лучше несколькими файлами делать. а то портянки кода (тем более асм) это звиздец. инклудить конечно тоже можно, но… (что-то в этом не правильно тоже).
Однако следует отметить, что если вы экспортирвали функцию, то она будет в бинарнике всегда, даже если не используется.
0
Однако следует отметить, что если вы экспортирвали функцию, то она будет в бинарнике всегда, даже если не используется.
Гм. А как же смартлинк?
0
что за смартлинк? исключение не использумого кода?
Так указание экспорта явно говорит что «эта функция будет использоваться кем то из вне» и она больше не может быть исключена. В том же экзешнике виндовом спокойно могут быть экспортируемые функции, и их адреса могут получить скажем дллки через GetProcAddress.
В принципе такой способ защиты от исключения мне следовало указать в своих статьях про библиотеки, но как-то не сообразил в тот момент.
Другой момент, что меня интересует, как задать экспортируемую из модуля функцию, которая может быть исключена на стадии линковки. Или правильнее будет: Как сказать линкеру не линкосать неиспользуемые экспортируемые функции. А то кроме экспорта метки я ничего не нашел.
0
что за смартлинк? исключение не использумого кода?
Именно.
Так указание экспорта явно говорит что «эта функция будет использоваться кем то из вне» и она больше не может быть исключена.
Не совсем. Он указывает, что функция видна извне модуля (точнее, объектного файла). После линковки бинарного файла никаких «извне» уже не будет — это же не DLL.
Впрочем, учитывая, что линкуется ELF — возможно, указывается экспортирование из бинарного файла. Но для МК оно смысла не имеет — ELF все равно будет сконвертирован в бинарную прошивку, а динамическое связывание в эмбеде встречается весьма редко (если это не встроенный линукс или винда, разумеется).
0
Впрочем, учитывая, что линкуется ELF — возможно, указывается экспортирование из бинарного файла.
С другой стороны, в GCC (и вообще в С/С++) список экспорта из исполняемого файла обычно задается линкеру — скажем, в виде .def файла, где перечислены метки, которые должен экспортировать слинкованный модуль (Win PE, ELF и т.п.). А компилятору/ассемблеру задается именно список символов, экспортируемых из объектного файла, т.е. доступных линкеру для связывания с другими объектниками.
0
Вот хорошо что вы вспомнили за GCC. На сколько я помню в никсах экспортируются все функции имеющиеся в библиотеке. Их (функции) никак не надо помечать в коде. В Виндах же для этого применяются ключивые слова типа declspec с dllexport (что дополнительно может указывать компилятору на необходимость применения того или иного соглашения, так во всяком случае задумывалось). И def файл по сути не является обязательным. его можно указывать для экспорта с иным именем, нежели применено в коде.
0
Я и говорю что в больших системах экспотировать можно не только из библиотеки. то что для МК не имеет смысла это да (но тоже не всегда, опять же как я писал про библиотеки). Т.е. при сборке ELF файла получается всё верно — функция экспортируется значит должна быть. А вот конвертор ELF->BIN уже не умеет оптимизировать (и слава богу).
Другое дело почему в Си мы можем написать просто
void delay(int);
получить доступ к функции из другого модуля и без всяких экспортов и исключений. А если не ссылаемся, то происходит её выбрасывание. Как такого добистя в асме? какое в нём средство «полуэкспорта»?
0
Другое дело почему в Си мы можем написать просто
Потому что в С все символы по умолчанию экспортируются. Там напротив, нужно указывать «символ не экспортируется» ключевым словом static.

Насчет того, как конкретно реализуется экспорт в С/С++ DLL — я, честно говоря, не знаю точно. Хотя ЕМНИП, без .def у меня из DLL вообще ничего не экспортировалось. Как вариант — расширение declspec(dllexport) добавляет некую пометку для линкера «эту функцию нужно экспортировать из DLL».

По идее, в ассемблере экспортирование (как правило словом в духе PUBLIC, GLOBAL, etc) экспортирует символ именно из объектного файла, и, соотвественно, если линкер умный и этот символ не используется — то линкер его может выкинуть. Но за точным ответом все же следует обратиться к ману.

Вообще, со смартлинком в стандартном процессе раздельной компиляции довольно туго. По крайней мере FreePascal для нормальной поддержки SL компилирует каждую функцию исходника в отдельный .o, а GCC-AVR — компилирует каждую функцию в отдельную секцию. Только у дельфи все просто и нативно, но он модульный, а не раздельный. Так что на предмет поддержки умной линковки тоже надо курить маны.
0
Корни этой «тугости» растут из формата объектного файла. Ну и изначального предположения, что все функции лежат в отдельных файлах (в сорсах старых юнихов, кстати, так оно и было). Второе предположение — программа собирается как «собственно программа + либа». В таком режиме смарт линковка получается автоматом без дополнительных телодвижений. Но жизнь показала, что оба предположения, в большинстве случаев, ошибочны. Отсюда, собственно, и грабли.
Раздельные секции и навороты типа declspec это уже гораздо более поздние усовершенствования, причем оба связаны с изменением формата объектника.

P.S. Долго пользовался обоими вариантами экспорта, но так и не смог решить, какой из них более неудобный…
0
Понятно, что программу нужно делить. Но пока я морально не готов переписывать файл констант :)
0
А сишные файлы описаний не работают с ассемблером?
И неужели нету готовых описаний?
0
Готовых не встречал.
Сишные не подходят.
0
Пока пользуюсь STM32L-Discovery (u-link забросил — не удобно). Но уж если жаловаться, то скажу что в зависимости от фазы луны над тихоокеанским островом Флинт, Discovery то отлично работает без ресета, то только с ним, то постоянно отваливается при любом раскладе. Т.е. в целом я доволен.
читается — просто уматно. общий негативный отзыв, и вердикт
Т.е. в целом я доволен.
повергают моск в некий когнитивный диссонанс… %)

ну а конструктивно и по факту — жлинк мне еще не отказывал в спаривании. с процом, а не то что подумали озабоченные слои сообщества… ;)
0
убрал
0
Еще один глупый вопрос, но чтото в доке по armasm никак не соображу где это посмотреть: как компилятору указать что мне нужно взять младшие\старшие 16 бит константы?
чтото вроде upper16\lower16? (с gnuas писал upper16: lower16: ) а здесь как?
0
  • avatar
  • WitGo
  • 13 февраля 2012, 19:00
Не знаю, может макросом как-то.
0
можно так:
; запись младшей части DD (....XXXX) в Rn
MACRO
mov_low_dd $reg, $val
movw.w $reg,#($val & 0x0000FFFF)
MEND

; запись старшей части DD (XXXX....) в Rn
MACRO
mov_high_dd $reg, $val
movt.w $reg,#(($val & 0xFFFF0000):SHR:16)
MEND
0
Вот только зачем?
0
psv не внимательно читаешь -
Еще один глупый вопрос ....
:))
0
Да я у него и спашиваю
0
2 WitGo — посмотрите во что превращается команда mov32 после ассемблирования и «глупые вопросы» исчезнут сами собой…
0
«после» мне не нужно,
мне нужно директиву ассемблера которая это задает… так как не во всех задачах нужно грузить константу в младшие 16 бит
и это не mov32 (зачем мне лишние инструкции?)

в общем коль никто не знает значить нет… странно, во всех компиляторах асма которые я видел до сих пор такой оператор был… :-(
0
Было бы не плохо подробнее расмотреть инициализацию портов ввода-вывода
да и как отправлять на нужные биты инфу.
0
Ну, в посте и приведен пример понтово-сишной настройки портов. А обращаться к пинам просто — прописываем макросы:
macro
setpin $port, $pin
mov32 r0,#((($port — 0x40000000) +0x10)*32)+($pin *4) +0x42000000
str one,[r0]
mend

macro
respin $port, $pin
mov32 r0,#((($port — 0x40000000) +0x10)*32)+(($pin +16) *4) +0x42000000
str one,[r0]
mend

и управляем:
setpin portA,1
respin portB,3
0
Здравствуйте. Возник еще один вопрос.
Как работать с прерываниями?
пытаюсь переписать код:
if(EXTI_GetITStatus(EXTI_Line1) != RESET){
ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 1, ADC_SampleTime_3Cycles);
ADC1->CR2 |= ADC_CR2_SWSTART;

}
EXTI_ClearITPendingBit(EXTI_Line1);
Собственно больше интересуют EXIT
0
Если вопрос по настройке внешнего прерывания, то делаем так:
-определяем вектора прерывания в таблице
DCD EXTI0_Handler ;0058 EXTI Line 0
DCD EXTI1_Handler ;005C EXTI Line 1
-настраиваем пины
startportconfig portA,0
pinconfig 0,infloat ;PA0 — input
pinconfig 1,infloat ;PA1 — input
endportconfig portA,0
-настраиваем внешние прерывания
;afio_exticr1 = 0x0000 reset value
setbit exti,exti_imr,0 ;exti0 interrupt not masked (PA0 for afio_exticr1 = 0x0000)
setbit exti,exti_rtsr,0 ;exti0 rising trigger enabled
setbit exti,exti_imr,1 ;exti1 interrupt not masked (PA1 for afio_exticr1 = 0x0000)
setbit exti,exti_rtsr,1 ;exti1 rising trigger enabled
-включаем внешние прерывания
ldr r0,=nvic
ldr r1,[r0,#nvic_iser0]
orr r1,#irq_exti0_ena ;exti0 interrupt enable
orr r1,#irq_exti1_ena ;exti1 interrupt enable
str r1,[r0,#nvic_iser0]
-расписываем прерывания
EXTI0_Handler PROC
setbit exti,exti_pr,0

bx lr
ENDP
EXTI1_Handler PROC
setbit exti,exti_pr,1

bx lr
ENDP
-отдыхаем
0
Спасибо, пригодиться.
Но речь шла именно об обработке. Понимаю что pin_idr мы настраиваем пины на вход и просматриваем его статус. Как допустим ждать прерывание? EXTI_ClearITPendingBit если не ошибаюсь очистка.
0
А зачем его ждать? Оно же прерывание:)
Если вы хотите отслеживать флаги в программе без запуска прерывания, тогда ищите их в даташите — я уже не помню.
0
Смысл в чем, есть 2 функции си(инициализация и тактирование) и ассемблер которая запускается по внешнему прерыванию. После прохождения и начинает обрабатывать входящие данные. Поэтому и нужно его ждать:)
0
Все равно не совсем понял. Почему нельзя настроить АЦП один раз в начале программы, а затем в прерывании exti запускать преобразования.
0
Ага. Я понял:)
Попробую объяснить:)
Несколькими строками на СИ
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3) != RESET)
{

}}

void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) != RESET) {

}}
Вобщем идет 2 прерывания: Один сигнал стандартно с частотой 5 Гц, 2й неизвестно придет или нет. Но если придет нужно его обработать.
После завершения идет очистка(Clear the EXTI line 1(or 3) pending bit).
0
А что проверяет
if(EXTI_GetITStatus(EXTI_Line3) != RESET)
???
Если мы уже в обработчике прерывания — значит прерывание произошло и нам осталось только сбросить флаг прерывания и выполнить свои действия.
0
Cигнал идет постоянный равный 1, а срабатывает только тогда, когда уходит в 0.
0
Ясно. Мы запутались в понятиях. У Вас нет прерываний и все действия происходят в основной программе. Вы ждете 0 на пине и включаете преобразование АЦП. (Тогда Вам и никакие exti не нужны — проверяйте себе пин на 0 и все.) Я Вам рассказываю как это сделать по прерыванию, т.е. мы настраиваем прерывание на внешний 0 и, когда этот 0 появляется — срабатывает прерывание, в котором уже ничего проверять не нужно, т.к. условие уже выполнено.
0
Спасибо.Я попробую разобраться. Репутацию к сожалению не могу повысить(статус не позволяет..)
0
Если честно, статья ниочем! Никаких пояснений внятных нет. Что откуда берется? Вот наприммер:
AREA |header data|,DATA,READWRITE
stack space stack_size
Как это понять, для чего это? Да и вообще какова структура построения программы? сейчас читаю Евстифеева книгу по архитектуре cortex ничего такого не видел. Было бы классно растолковать смысл всего этого макроассемблера в Keil. Ну или на крайняк ссылки приводить. На комментарии в коде автор явно поскупился. В реале по ассемблеру ARM заметен сильный дефицит, поэтому неплохо бы более подробно останавливаться на «мелочах». А в си лезть не хочу, т.к. предпочитаю глубоко вникать в то как работает ядро и периферия, а с си получу только поверхностные представления об этом.
0
  • avatar
  • Wypuk
  • 09 августа 2013, 18:10
сейчас читаю Евстифеева книгу по архитектуре cortex
Евстифеев книгу по cortex-ам написал? Что за книга?
0
Это последний опус на тему «Как я вникал в арм асм». Затем я благополучно перешел на С. Ковыряться в арм асме было весело и поучительно, но когда код достигает 60стр при 20кБ ну его на.
Я не особо комментировал код т.к. все более-менее описано в предыдущих статьях.
0
О первом:
AREA |header data|,DATA,READWRITE — описываем сегмент данных DATA (оператива ибо READWRITE)
stack space stack_size — выделим под объект stack (там стек развернётся) stack_size байт, чтоб линкёр оставил его нетронутым.

О втором:
1. Таблица векторов, чтоб контроллер знал, откуда ему ехать и как реагировать на всё это.
2. Сегмент кода и констант, READONLY: пишем все функции, которые мы хотим и статические неизменяемые данные. Сегментов можем насоздавать сколько угодно, линкёр по ним группирует объекты. Выполнение кода начинается с функции __main, если мы хотим пропустить инициализатор кейла или main, если мы хотим, чтоб библиотека инициализировалась.
Модули могут выставлять на обозрение символы с помощью ключевого слова EXPORT: EXPORT main
Модули могут подключать внешние символы из других модулей с помощью ключевого слова IMPORT: IMPORT button_Get
Доп. файлы подключаются с помощью директивы GET: GET led.inc
3. Сегмент данных: стек и переменные. Переменные можно объявлять и резервировать им память как размерный тип (DCB — 1 байт, DCW — 2 байта, DCD — 4 байта) и просто выделяя произвольный размер (space): stack space 0x400

Все символы получают адреса по порядку, учитывая размер каждого элемента, как функции, так и переменные. Константы от переменных отличаются лишь расположением в разных сегментах (READONLY против READWRITE). Модификатор сегмента NOINIT говорит библиотеке не трогать сегмент при запуске.

Это если кратко.
0
А как в Keil создать проект для ассемблера? Почему-то не дает выбора на Си писать или на Асме
0
Кейлу всё равно, что компилировать и асмировать — если вместо сишных файлов будут асмовские, это его не опечалит. Gj evjkxf За счёт чего, кстати, можно делать асмовские функции к сишному проекту. Главное, нужно указывать тип файла в свойствах как асмовский (если использовать нестандартные расширения вместо .s).
Не зря он добавляет только startup файл в пустой проект — дальше уже вороти что хочется.

Вот пример файла main.s (вместо main.c)
AREA    |.text|, CODE, READONLY

            EXPORT SystemInit
SystemInit  PROC
            BX LR
           
            ENDP

            EXPORT __main
__main      PROC

_mainloop
            B __mainloop

            ENDP
    END

Здесь библиотека стандартная не запускается, как и не чистится память и не инициализируются глобальные переменные — переход идёт прямо из startup_stm32fxxx.s минуя все промежуточные этапы (что можно вернуть, если надо).
0
Ну написал не он, он перевел и адаптировал. Называется «Ядро Cortex-M3 компании ARM» полное руководство. Автор оригинала Joseph Yiu «The deffinition guide to the ARM Cortex-M3»
0
  • avatar
  • Wypuk
  • 09 августа 2013, 19:15
А, понятно. Неплохая книга.
0
0
Вот еще пара вопросов.
1. В разделе «О стартапе» написано, что таблица векторов прерываний располагается вначале программного флеша, т.е. с адреса 0x8000 0000 — рзве не с 0x0800 0000?
2. Везде в неиспользуемых исключениях и прерываниях ставятся заглушки на Reset_handler. У меня по этому поводу есть сомнения. Т.е. если по какой-то неизвестной причине вызовется неиспользуемое прерывание, то программа будет выполняться с метки Reset_handler, а значит начнет заново все инициализировать и т.п. Не лучше делать к примеру так, чтобы из вызванного прерывания программа возвращалась туда откуда оно вызвалось. Если проводить аналогию с AVR то сделать что-то вроде reti.
Например так:

             DCD     top_of_stack          ; Top of Stack
             DCD     Reset_Handler         ; Reset Handler
             DCD     Zaglushka             ; NMI Handler
             DCD     Zaglushka             ; Hard Fault Handler
            .....
             DCD     Zaglushka             ; EXTI Line 0
             DCD     EXTI1_IRQHandler      ; EXTI Line 1
             DCD     Zaglushka             ; EXTI Line 2
             DCD     EXTI3_IRQHandler      ; EXTI Line 3

Zaglushka
             BX      LR
            .....
0
  • avatar
  • Wypuk
  • 15 августа 2013, 10:25
Нет, в стартапе сделано всё разумно — если уж ты инициализируешь прерывание, будь добр его обработчик написать. Если нет, оно висит в бесконечном цикле, чтоб можно было увидеть свою ошибку.

Неправильно это заменять символы, обозначающие адреса прерываний на Zaglushka. Захотите потом сделать какое-то прерывание, забитое Zaglushkой, то можете далеко не сразу догадаться, что о нём таблица векторов просто напросто ничего не знает.

Не надо трогать стартап, там всё ок!
+1
Мы об одном и том же стартапе говорим? Я про то как сделал автор этой статьи. Все неиспользуемые вектора он заменил на Reset_Handler. Т.е. по любому такому прерыванию мы переходим на сброс. По моему, это тоже как не совсем хорошо.

Теперь к примеру о файле startup_stm32f10x_md.s в котором на каждое прерывание есть обработчик «B .» (т.е. зацикливание). Ну допустим при отладке действительно хорошо видеть таким образом ошибку если вдруг накосячил, а в реальном устройстве проц просто повиснет в этом бесконечном цикле… и что? тоже вроде как бы не выход.

Не пойму, почему это таблица векторов о адресе zaglushки ничего знать не будет? Есть адрес метки, а директивой DCD этот адрес помещается по адресу прерывания для соответствующего вектора. Соответственно прерывание если вызовется перейдет на эту метку. Не нравится Zaglushka… ладно, пусть для каждого прерывания будет свой обработчик вида BX LR, а не общий. Единственно размер кода увеличится.
0
Нет, я про официальный, а не предложенный=) Оба варианта, предложенные здесь, мне не нравятся.

В реальном устройстве попадёт в заглушку? Оо Это ж как накосячить надо, чтоб «забыть» описать используемое в алгоритме прерывание. Да ещё и при отладке в него не попасть ни разу.

Не, знать оно будет о Zaglushka, а не о, скажем, HardFault_Handler (из него вообще грех выходить, не обработав исключение) или EXTI0_IRQHandler. Написав в пользовательском коде void EXTI0_IRQHandler(void) {}, или то же на асме. Но вместо этого обработчика он так и будет скакать по Zaglushka. А программист будет искать ошибки, что ж он не так сделал, что прерывание не срабатывает никак.
0
Это же как надо писать программу, чтобы обработчик используемого прерывания забыть объявить в таблице прерываний (то бишь оставить заглушку на его месте)?
Насчет HardFault_Handler т.п. ты вероято прав, спорить не буду, т.к. мне пока далеко до этого.
Вообще я тут говорил о таком случае, чисто гипотетическом))) что если вдруг мы не используем прерывание какое-либо (все биты маскирования этого прерывания равны 0) и по какой-то совершенно необъяснимой причине вдруг мы на него нарвались, то мы сможем совершенно безболезненно из него выйти туда же откуда и пришли. Даже не знаю, вообще такое может случиться?
0
Чтоб включить прерывание, надо:
1. Разрешить генерацию события в периферийном устройстве;
2. Включить и настроить перифериёное устройство, чтоб оное событие могло произойти;
3. Разрешить прерывание в контроллере прерываний NVIC.

То есть «случайно», конечно, случиться такое может, но настолько косячный код долго не проработает в любом случае.
0
Это же как надо писать программу, чтобы обработчик используемого прерывания забыть объявить в таблице прерываний
Легко. В стартапе вся таблица уже объявлена и все вектора размещены. Заглушки тоже сделаны. Достаточно объявить внешний символ, чтобы его адрес подставился в таблицу.
Так делают все! Потому что логично и удобно и вручную в таблицу лезть не надо, чтоб что-то там менять.

Подменять, переименовывать или убирать из таблицы векторов символы в связи с этим несколько некорректно. Любой, кто будет читать/редактировать такой код, не будет об этом знать и весьма удивится непонятному поведению кода.
+2
Я правильно понимаю?… в стартапе есть к примеру объявленый вектор:

               .....
                DCD     HardFault_Handler          ; Hard Fault Handler
               .....
HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler          [WEAK]
                B       .
                ENDP

я не лезу в этот стартап, а в своем коде ставлю метку:

HardFault_Handler
                тут пишу какой-то код обработчика
                BX LR

Та метка которая в стартапе игнорируется, т.к. там стоит [WEAK], а программа выполняется в моем коде? А все прерывания которые я у себя не объявляю, обрабатываются в бесконечном цикле в файле стартапа? Так что-ли?
0
Именно. Главное, не забыть сделать EXPORT HardFault_Handler у себя.
0
можно прыгать по адресам, а не пометкам в кейле?
0
Если так ок, то да:
LDR     R0, =0x08000200
BX      R0
0
и после компиляции по адресу будет то что было?
0
Я ничего не понял? Почему там что-то другое должно оказаться?
0
тоесть он там самостоятельно ничего не оптимизирует?
0
Отвечайте под комментариями, а не в главной теме, так можете ответа и не дождаться, да и читать неудобно разорванную ветку.

Что он может наоптимизировать в числе? Но я всячески против использования чисел, а не именованных символов. Есть пара исключений, но это очень исключения.
0
Господа. Вопрос такой — нашел в Keil ARM Assembler User Guide следующий пример
; macro definition
MACRO; start macro definition
$label xmac $p1,$p2
; code
$label.loop1; code
; code
BGE $label.loop1
$label.loop2; code
BL $p1
BGT $label.loop2
; code
ADR $p2
; code
MEND; end macro definition
То есть в макросах можно как-то использовать метки и операторы условного перехода.
В реале при попытке употребить макрос, ессно, получаю ошибку о множественном объявлении меток в макросе.
С WHILE WEND и того хуже — при написании вот такого кода
GBLA count1
GBLA count2
MACRO
delay_ms $a
count1 SETA $a*0xA410

WHILE count1>= 0
count1 SETA count1-1
WEND
MEND
компиллятор вообще вешается. Хотя на той же странице юзергайда написано следующее
If you start any WHILE...WEND loops or IF...ENDIF
conditions within a macro, they must be closed before the MEND
directive is reached.
То бишь намёк на то, что и WHILE WEND в макросе употребим.
Есть ли у кого-нибудь опыт написания макросов с циклами и ветвлениями — буду благодарен, если поделитесь.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.