ARM: как быстро начать писать на ассемблере Печать
Добавил(а) microsin   

Поначалу 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.