Вместо первой программы

Светодиодом мигать не будем. Нет у нас ни светика, ни портов. Голое ядро. Окунемся т.с. в чистый асм, чтобы проняться духом его. А что ближе к ядру? Правильно — ось. Ну всю ось мы пока писать не будем, а простенький диспетчер задач самое то. Диспетчер будет на кольцевом буфере. Итак, что нам нужно для кольца: индекс текущих входного и выходного элементов буфера, текущая длина цепочки и максимальный размер буфера. Имеем структуру:

task_inindex	dcw 0
task_outindex	dcw 0
task_lenindex	dcw 0
task_maxlen	dcw 0
task_cbuffer	space task_cbufferlen

space — такой удобный макро-оператор для заполнения области памяти, а task_cbufferlen — длина заполнения
Сразу прикинем макрос для этой структуры:

		macro
		cbuffer $name,$len
		dcw 0
		dcw 0
		dcw 0
		dcw 0
$name	space $len
		mend

Ну тут вроде все должно быть понятно. Макросы кейла рулят. Теперь нам нужны три основные (ну две) подпрограммы работы с нашей структурой: инициализация, сохранение элемента, извлечение элемента из буфера. Прикинем и их:

initcbuffer0			;инициализация кольцевого буфера
		push {lr}
		movs r2,#0
		strh r2,[r0,#0]		;inindex=0
		strh r2,[r0,#2]		;outindex=0
		strh r2,[r0,#4]		;lenindex=0
		strh r1,[r0,#6]		;maxlen=len_cbuffer
		pop {pc}
;----------------------------------------------------------
sendtocbuffer0			;помещаем байт в буфер
		push {lr}
		ldrh r2,[r0,#4]		;r2<-lenindex
		ldrh r3,[r0,#6]		;r3<-x_maxlen
		cmp r2,r3		;lenindex?=x_maxlen
		itt eq
		moveq r0,#1		;buffer overflow
		popeq {pc}		;exit
		adds r2,#1
		strh r2,[r0,#4]		;lenindex=lenindex+1
		ldrh r2,[r0,#0]		;r2<-inindex
		push {r2}
		adds r2,#1
		cmp r2,r3
		it eq
		moveq r2,#0
		strh r2,[r0,#0]		;inindex=(inindex+1)or(0)
		pop {r2}
		adds r0,#8
		strb r1,[r0,r2]		;data->[offset cbuffer + inindex]
		movs r0,#0		;r0<-0 if ok
		pop {pc}		;exit
;----------------------------------------------------------
loadfromcbuffer0		;извлекаем байт из буфера
		push {lr}
		ldrh r2,[r0,#4]		;r2<-lenindex
		tst r2,#0xFF
		itt eq
		moveq r0,#1		;r0<-1 if not ok (buffer empty)
		popeq {pc}		;exit
		subs r2,#1
		strh r2,[r0,#4]		;lenindex+=1
		ldrh r2,[r0,#2]		;r2<-outindex
		movs r1,r2		;r1<-outindex
		ldrh r3,[r0,#6]		;r3<-maxlen
		adds r1,#1
		cmp r1,r3
		it eq
		moveq r1,#0
		strh r1,[r0,#2]		;outindex=(outindex+1)or(0)
		adds r0,#8
		ldrb r1,[r0,r2]		;data<-[offset cbuffer + outindex]
		movs r0,#0		;r0<-0 if ok
		pop {pc}		;exit
;----------------------------------------------------------

Обратите внимание как мы входим в подпрограмму и выходим из нее. Прикол в том что у нас нет операторов типа call, ret и их вариаций. У нас есть хитрый оператор перехода BL, который перед переходом сохраняет адрес следующей инструкции (адрес возврата) не в стеке, как мы привыкли, а в спец регистре r14, он же LR, он же link register. Ну а дальше типичный выход через стек. Если не предполагаете вложеных подпрограмм, то lr в стек сохранять не нужно, а из подпрограммы можно выйти bx lr.
В подпрограммах вроде как проверяется несколько условий, а переходов нет. Мы применили т.н. блок условных инструкций — занятная штука. Оглавляет блок инструкция it <условие>. Затем идут условные инструкции до 4х, каждая с суффиксом условия. Но каждая же должна отметится в оглавлении т.е. если it eq то дальше компиллер ждет одну условную инструкцию с суфиксом eq. Пример здесь более красноречив:
ittee eq
moveq r1,#1; выполняется если равенство (Z=1)
moveq r2,r3; выполняется если равенство (Z=1)
movne r1,#0; выполняется если не равенство (Z=0)
movne r3,r2; выполняется если не равенство (Z=0)
Инструкции, которые по условию не должны выполняться, естественно тоже читаются, обрабатываются (вроде как nop). Зачем нам такое чудо. Наверно чтобы уменьшить количество переходов, на которых проявляется латентность флэша. Почему только 4 инструкции в блоке — тоже наверное дальше переход уже эффективнее.
У некоторых инструкций есть суффикс s, заставляющий выставлять флаги. Но, как видите, он поставлен и туда, где флаги нам и не нужны. Смысл этого в том, как я понял, что мы явно указываем компиллеру что инстукция 2х байтная а не 4х. Тут какая-то байда с выравниванием инструкций по слову. И если компиллеру нужно, то он все равно растянет инструкцию до 4х. (тут нужно еще разобраться)
В командах пересылки часто применяют суффиксы размерности операндов, напр. ldrb — читаем байт, strh — пишем полуслово (2байта) и т.д.
Но вернемся к нашей программе. Опять пишем макросы — куда без них.

		macro	;инициализация кольцевого буфера
		initcbuffer	$addr_cbuffer,$len_cbuffer
		ldr r0,=$addr_cbuffer -8
		movs r1,#$len_cbuffer
		bl initcbuffer0
		mend

		macro	;помещаем байт в буфер
		sendtocbuffer $addr_cbuffer,$data
		ldr r0,=$addr_cbuffer -8
		movs r1,#$data
		bl sendtocbuffer0
		mend

		macro	;извлекаем байт из буфера
		loadfromcbuffer $addr_cbuffer
		ldr r0,=$addr_cbuffer -8
		bl loadfromcbuffer0
		mend

Ну и наш страшный диспетчер:

processtask
		loadfromcbuffer	task_cbuffer
		cbnz r0,processtask_notask
		adr.w r0,branchtabletask
		tbh [r0,r1,lsl #1]
branchtabletask
		dci ((task0-branchtabletask)/2)
		dci ((task1-branchtabletask)/2)
		dci ((task2-branchtabletask)/2)
task0
		nop
		nop
		b processtask
task1
		nop
		nop
		b processtask
task2
		nop
		nop
		b processtask
processtask_notask
		b processtask

Читаем номер текущей задачи и если он есть (т.е. если наш флаг r0=0) — прыгаем на задачу. Еще одна хитрая инструкция cbz, cbnz проверяет на 0 но прыгает, зараза, только вперед. Ну и конечно (наш ответ С :) инструкция TBB, TBH аналог case, т.н. табличный переход по индексу. Чтобы не плодить страшных dci опять обратимся к макросу.

		macro
$label	task $addr_task
		dci (($addr_task-branchtabletask)/2)
		mend

Ну и теперь вся тестовая прога:

task_cbufferlen equ 8

  		AREA |header code|,CODE,READONLY
		ENTRY
		EXPORT __main
__main

		macro	;инициализация кольцевого буфера
		initcbuffer	$addr_cbuffer,$len_cbuffer
		ldr r0,=$addr_cbuffer -8
		movs r1,#$len_cbuffer
		bl initcbuffer0
		mend

		macro	;помещаем байт в буфер
		sendtocbuffer $addr_cbuffer,$data
		ldr r0,=$addr_cbuffer -8
		movs r1,#$data
		bl sendtocbuffer0
		mend

		macro	;извлекаем байт из буфера
		loadfromcbuffer $addr_cbuffer
		ldr r0,=$addr_cbuffer -8
		bl loadfromcbuffer0
		mend

		macro
$label	task $addr_task
		dci (($addr_task-branchtabletask)/2)
		mend

		macro
		cbuffer $name,$len
		dcw 0
		dcw 0
		dcw 0
		dcw 0
$name	space $len
		mend

		initcbuffer task_cbuffer,task_cbufferlen
		sendtocbuffer task_cbuffer,0x02
		sendtocbuffer task_cbuffer,0x01
		sendtocbuffer task_cbuffer,0x03
		sendtocbuffer task_cbuffer,0x05
		sendtocbuffer task_cbuffer,0x04

processtask
		loadfromcbuffer	task_cbuffer
		cbnz r0,processtask_notask
		adr.w r0,branchtabletask
		tbh [r0,r1,lsl #1]
branchtabletask
		task task0
		task task1
		task task2
		task task3
		task task4
		task task5

task0
		nop
		nop
		b processtask
task1
		nop
		nop
		b processtask
task2
		nop
		nop
		b processtask
task3
		nop
		nop
		b processtask
task4
		nop
		nop
		b processtask
task5
		nop
		nop
		b processtask

processtask_notask
		b processtask


initcbuffer0			;инициализация кольцевого буфера
		push {lr}
		movs r2,#0
		strh r2,[r0,#0]		;inindex=0
		strh r2,[r0,#2]		;outindex=0
		strh r2,[r0,#4]		;lenindex=0
		strh r1,[r0,#6]		;maxlen=len_cbuffer
		pop {pc}
;----------------------------------------------------------
sendtocbuffer0			;помещаем байт в буфер
		push {lr}
		ldrh r2,[r0,#4]		;r2<-lenindex
		ldrh r3,[r0,#6]		;r3<-x_maxlen
	        cmp r2,r3		;lenindex?=x_maxlen
		itt eq
		moveq r0,#1		;buffer overflow
		popeq {pc}		;exit
		adds r2,#1
		strh r2,[r0,#4]		;lenindex=lenindex+1
		ldrh r2,[r0,#0]		;r2<-inindex
		push {r2}
		adds r2,#1
		cmp r2,r3
		it eq
		moveq r2,#0
		strh r2,[r0,#0]		;inindex=(inindex+1)or(0)
		pop {r2}
		adds r0,#8
		strb r1,[r0,r2]		;data->[offset cbuffer + inindex]
		movs r0,#0		;r0<-0 if ok
		pop {pc}		;exit
;----------------------------------------------------------
loadfromcbuffer0		;извлекаем байт из буфера
		push {lr}
		ldrh r2,[r0,#4]		;r2<-lenindex
		tst r2,#0xFF
		itt eq
		moveq r0,#1		;r0<-1 if not ok (buffer empty)
		popeq {pc}		;exit
		subs r2,#1
		strh r2,[r0,#4]		;lenindex+=1
		ldrh r2,[r0,#2]		;r2<-outindex
		movs r1,r2		;r1<-outindex
		ldrh r3,[r0,#6]		;r3<-maxlen
		adds r1,#1
		cmp r1,r3
		it eq
		moveq r1,#0
		strh r1,[r0,#2]		;outindex=(outindex+1)or(0)
		adds r0,#8
		ldrb r1,[r0,r2]		;data<-[offset cbuffer + outindex]
		movs r0,#0		;r0<-0 if ok
		pop {pc}		;exit
;----------------------------------------------------------

		AREA |header data|,DATA,READWRITE
		cbuffer task_cbuffer,task_cbufferlen

		END

Ну и кто теперь скажет что этот асм не удобный? Все.

PS. Ну и как обычно: это не учебный курс а лишь попытка заинтересовать.
  • +4
  • 30 марта 2011, 13:54
  • psv

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

RSS свернуть / развернуть
Ну почему не интересует…
Благодаря Вашей статье гораздо удобней стало читать asm-овский код. Можно на нем и не писать, но читать его периодически приходится…
0
Очень даже интерисует, как раз и не хватает таких вот статей. Уже имею опыт использования авров, решил изучить кортексы. Источников информации крайне мало, статьи в интернете ориентированны на начинающих по типу «помигаем светодиодом», ну максимум «понажимаем на кнопку». А Вы как раз раскрывайте нормально введение в контроллеры для «продолжающих обучение» так сказать.

Так что желаю продолжения банкета!)
0
Интересует. Если молчим, это не значит, что неинтересно.
Просто читаем и осмысливаем…

Ничё, что за всех ответил? :)
0
А зачем конструкция push LR; pop PC? ЕМНИП, на ARM7 возврат выполнялся командой MOVS PC, LR (или ее разновидностью) — вся фишка LR собственно в том и состоит, чтобы обойтись в leaf-функциях без стека.
Наверно чтобы уменьшить количество переходов, на которых проявляется латентность флэша.
У ARM'а инструкции выполняются конвеером и при переходе нужно перезаполнение конвеера. Поэтому, если нужно условно исполнить 1-2 инструкции — выгодне их прогнать как NOP (он выполняется за так, а JMP — за количество тактов, равное количеству ступеней конвеера, в ARM7 их было 3).
Смысл этого в том, как я понял, что мы явно указываем компиллеру что инстукция 2х байтная а не 4х. Тут какая-то байда с выравниванием инструкций по слову.
Изначально у ARM применялись 32-битные команды (набор инструкций ARM). Они были трехадресные (dest, src1, src2), условные и имели флажок, показывающий меняет ли команда флаги (т.е. ADD R0, R1, R2 на флаги не влияла, а вот ADDS R0, R1, R2 влияла). Позже, для повышения плотности кода ввели дополнительный набор команд — Thumb. Эти команды были 16-битные, куда больше походили на традиционные наборы команд (двухадресные, условные только переходы, активное использование стека, все влияют на флаги) — но такой код выполнялся медленнее. В Cortex'ах применяется новый набор команд — Thumb 2. Он содержит как 16-битные команды (подмножество Thumb), так и 32-битные (подмножество ARM). Соответственно, не влияющая на флаги арифметическая команда есть только среди 32-битных, и для сокращения кода лучше использовать 16-битную флаговую версию.
0
  • avatar
  • Vga
  • 30 марта 2011, 15:10
тут push/pop конечно не нужен — просто как стандарт если будут вложения. За информацию спасибо
0
Статья хорошая, тема интересная, просто сейчас лично у меня нет времени заниматься еще и ARMами.
А то, что никому не интересно — так это не так и страшно — вот у моей последней статьи вообще коментов не было, и ничего))
0
Если нет коментов, это не значит, что статья не интересная. Вот эта например просто отличная статья, еще бы подобную почитал — про асм кортекса, но пока что кроме спасибо сказать нечего. Будет глупо, если 95% коментов будут типа «спасибо» ))
0
Класс!
А как кейл дружит с STM32f100
вот есть «халявная» stm32vldiscovery
хотел тоже ASM-ом заняться

и у нас в «промэлектронике»
stm32f100c4 — 47 руб — так и манит изучить stm32
0
Немного с глюками, но дружит. Отладка ЕМНИП на ура работает, а вот прошивать — только через отладку. Хотя для чисто прошивки можно и любой другой софт использовать.
0
f100 — это еортекс-м0, а f103 — это кортекс-м3 вроде. и если в этом я не ошибаюсь, то набор интсрукций у них сильно различается.
0
обе серии — Cortex-M3. А Cortex-M0 это stm32f0.
0
так точно
0
а где можно почитать описания команд на русском? интересует как раз IT
что за TEE у нее не понял :-( у меня в доке только IT с флагами и все :-(
0
  • avatar
  • WitGo
  • 08 февраля 2012, 10:27
как хорошо что я набрёл на ваши блоги
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.