Поначалу ARM довольно непривычный ассемблер (если переучиваться с x86, MCS51 или AVR). Но у него довольно простая и логичная организация, поэтому усваивается быстро.
Документации на русском языке по ассемблеру совсем мало, см. [1]. Второе, что хорошо может помочь - это, как ни странно, C-компилятор IAR Embedded Workbench for ARM (далее просто IAR EW ARM). Дело в том, что он со стародавних времен умеет (как и все уважающие себя компиляторы, впрочем) компилировать C-код в код ассемблера, который, в свою очередь, так же легко компилируется ассемблером IAR в объектный код. Поэтому нет ничего лучше написать простейшую функцию на C, скомпилировать её в ассемблер, и сразу станет понятно, какая команда ассемблера что делает, как передаются аргументы и как возвращается результат. Убиваете сразу двух зайцев - обучаетесь ассемблеру и заодно получаете информацию, как интегрировать ассемблерный код в проект на C. Я тренировался на функции подсчета CRC16, и в результате получил её полноценную версию на ассемблере.
Вот исходная функция на C (u16 означает unsigned short, u32 - unsigned int, u8 - unsigned char):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// файл crc16.c
u16 CRC16 (void* databuf, u32 size)
{
u16 tmpWord, crc16, idx;
u8 bitCnt;
#define CRC_POLY 0x1021;
crc16 = 0;
idx=0;
while (size!=0)
{
/* сложим по xor старший байт crc16 и входной байт */
tmpWord = (crc16 >> 8) ^ (*(((u8*)databuf)+idx));
/* результат запишем в старший байт crc16 */
tmpWord <<= 8;
crc16 = tmpWord + (0x00FF & crc16);
for (bitCnt=8;bitCnt!=0;bitCnt--)
{
/* проверим старший разряд аккумулятора CRC */
if (crc16 & 0x8000)
{
crc16 << = 1;
crc16 ^= CRC_POLY;
}
else
crc16 << = 1;
}
idx++;
size--;
}
return crc16;
}
|
Заставить генерировать код ассемблера IAR EW ARM очень легко. В опциях файла crc16.c (добавленного к проекту) поставил галку Override inherited settings, а потом на закладке List поставил 3 галки - Output assembler file, Include source и Include call frame information (хотя последнюю галку, наверное, можно не ставить - она генерит кучу ненужных CFI-директив). После компиляции получился файл папка_проекта \ ewp \ at91sam7x256_sram \ List \ crc16.s. Этот файл также легко можно добавить в проект, как и C-файл (он будет нормально компилироваться).
Конечно, когда я подсунул C-компилятору необрезанный вариант C-кода, то он мне выдал такой листинг ассемблера, что я ничего в нем не понял. Но когда выкинул из функции все C-операторы, кроме одного, стало понятнее. Потом шаг за шагом добавлял C-операторы, и вот в итоге что получилось:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
; файл crc16.s
NAME crc16
PUBLIC CRC16
CRC_POLY EQU 0x1021
SECTION `.text`:CODE:NOROOT(2)
ARM
// u16 CRC16 (void* databuf, u32 size)
;R0 - результат возврата, CRC16
;R1 - параметр size
;R2 - параметр databuf (он был при входе в R0)
;R3, R12 - временные регистры
CRC16:
PUSH {R3,R12} ;методом тыка выяснил, что R3 и R13 сохранять
; необязательно. Но решил сохранить на всякий
; случай.
MOVS R2,R0 ;теперь R2==databuf
MOV R3,#+0
MOVS R0,R3 ;crc16 = 0
CRC16_LOOP:
CMP R1, #+0 ;все байты обработали (size==0)?
BEQ CRC16_RETURN ;если да, то выход
LSR R3, R0, #+8 ;R3 = crc16>>8
LDRB R12, [R2] ;R12 = *databuf
EOR R3, R3, R12 ;R3 = *databuf ^ HIGH (crc16)
LSL R3, R3, #+8 ;R3 << = 8 (tmpWord << = 8)
AND R0, R0, #+255 ;crc16 &= 0x00FF
ADD R0, R0, R3 ;crc16 = tmpWord + (0x00FF & crc16)
MOV R12, #+8 ;bitCnt = 8
CRC16_BIT_LOOP:
BEQ CRC16_NEXT_BYTE ;bitCnt == 0?
TST R0,#0x8000 ;Еще не все биты обработаны.
BEQ CRC16_BIT15ZERO ;Проверяем старший бит crc16.
LSL R0,R0,#+1 ;crc16 << = 1
MOV R3, #+(LOW (CRC_POLY)) ;crc16 ^= CRC_POLY
ORR R3,R3,#+(HIGH(CRC_POLY) << 8) ;
EOR R0,R3,R0 ;
B CRC16_NEXT_BIT
CRC16_BIT15ZERO:
LSL R0,R0,#+1 ;crc16 << = 1
CRC16_NEXT_BIT:
SUBS R12,R12,#+1 ;bitCnt--
B CRC16_BIT_LOOP ;
CRC16_NEXT_BYTE:
ADD R2,R2,#+1 ;databuf++
SUBS R1,R1,#+1 ;size--
B CRC16_LOOP ;цикл по всем байтам
CRC16_RETURN:
POP {R3,R12} ;восстанавливаем регистры
BX LR ;выход из подпрограммы, R0==crc16
END
|
Компилятор C от IAR делает на удивление хороший код. Мне совсем мало удалось его оптимизировать. Выкинул только лишний временный регистр, который хотел использовать компилятор (он почему-то взял в качестве лишнего временного регистра LR, хотя R3 и R12 было достаточно), а также убрал пару лишних команд, проверяющих счетчики и выставляющих флаги (просто добавив суффикс S к нужным командам).
[Ссылки]
1. Архитектура и система команд RISС-процессоров семейства ARM site:gaw.ru. |