В мануале всё расписано вообще то.
Вкратце примерно так.
Если собираешься вызывать подпрограммы на ассемблере из си, надо в первую очередь изучить что такое call-clobbered registers и call-saved registers (их ещё называют volatile и non-volatile). Первые подпрограмма может произвольно изменять, вторые она обязана сохранять неизменными. Сохранять надо r2-r17, r28-r29. r1 перед выходом из подпрограммы надо обнулить.
Передача параметров. Параметры передаются в регистрах r25-r8, в регистре с бОльшим номером - старший байт, под каждый параметр выделяется количество регистров, кратное 2. Если передаётся параметр в 1 байт, один регистр из пары не используется. Например, если функция объявлена как func(char x), то x передаётся в r25. func(int x) - x передаётся в r24:r25 (в r25 старший байт), func(int x, char y) - x передаётся в r24:r25, y - в r23. func(сhar x, long y, char z, int k) - x передаётся в r24, y - в r20:r23, z - в r18, k - в r16:r17. Если r25-r8 недостаточно для всех аргументов, то "лишние" передаются через стэк. Например при вызове void a(uint32_t x, uint32_t y, uint32_t z, uint32_t i, uint32_t j) x будет в r22:r25, y - r18:r21, z - r14:r17, i - r10:r13, а стэк будет выглядеть так:
- Код: Выделить всё
; SP - это указатель стэка
SP:
SP+1: ret0 ; адрес возврата - младший байт
SP+2: ret1 ; адрес возврата - старший байт
SP+3: j0 ; j - младшие 8 бит
SP+4: j1
SP+5: j2
SP+6: j3 ; j - старшие 8 бит
Возврат результата. 8 бит в r24 (не r25!), 16 бит в r25:r24, до 32 бит в r22-r25, до 64 бит в r18-r25. Если возвращаемое значение более 64 бит, то к списку параметров в начале добавляется ещё один - 16-битный указатель на область памяти, в которую поместить результат (всегда в r24:r25, и изменять эти регистры в подпрограмме нельзя).