Микроконтроллеры

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Микроконтроллеры » STM32: Software » Программирование STM32 на ассемблере


Программирование STM32 на ассемблере

Сообщений 1 страница 9 из 9

1

Disclaimer )
Я не агитирую программировать STM32 на ассемблере и даже, наоборот, не советую.
IMHO, ассемблер интересен в трёх случаях:
1) Невозможно сделать на си
2) Разобраться как оно устроено
3) Just for fun
Поскольку первый случай сложный, а третий - тяжёлый, то дальше я излагаю, ориентируясь на второй )
Да, и речь только о Cortex-M{0,3,4}, т.е. о STM32{F|L}{0-4}

Основной мануал по ассемблеру для STM32 - st-шный Programming manual.
Он написан более понятным языком, чем st-шные RM, потому что является по сути пересказом армового Generic User Guide с учётом особенностей STM32.
Вот ссылки на тот и другой, например, для STM32F1
http://www.st.com/content/ccc/resource/ … 228163.pdf
http://infocenter.arm.com/help/topic/co … 3_dgug.pdf
Для других серий STM32 и ядер Cortex есть такие же свои.

Из софта требуются только два файла - две программы: as и objcopy, которые можно взять из любого дистрибутва gcc-arm-none-eabi, например, этого - https://developer.arm.com/open-source/g … /downloads
Ещё нужен какой-то дебаггер, например, Segger Ozone

Первая, самая минимальная программа (для любой серии STM32)

Код:
.syntax unified
.word 0x20001000
.word Reset+1
.word NMI+1, HardFault+1
.=0x200
Reset:
    movs r0, 0
10$:
    adds r0, 1
    b 10$
NMI:
HardFault:
    b .

.syntax unified - директива выбора варианта синтаксиса ассемблера (подробнее - https://sourceware.org/binutils/docs/as … 2dSet.html)

.word 0x20001000 - в самое первое слово программы, по нулевому адресу, помещается значение указателя стека (SP), которое будет устанавливаться при ресете (стек растёт в сторону уменьшения адресов, RAM начинается с 0x20000000).

.word Reset+1 - во второе слово программы (по адресу 4) помещается значение счётчика команд (PC), которое будет устанавливаться при ресете.
Младший бит не используется для адресации (реальный счётчик команд всегда чётный), а имеет специальное назначение.
В докортексовых армах использовались два пересекающихся набора инструкций - 32-битный (arm) и 16-битный (thumb), а младший бит значения PC использовался для переключения между ними.
Хотя в кортексах только один набор инструкций - thumb2 (thumb с дополнительными 32-битными инструкциями), а переключение бессмысленно и запрещено, тем не менее в младшем бите остался тот же переключатель и он должен всегда устанавливаться в единицу.

.word NMI+1, HardFault+1 - с третьего слова (с адреса 8) начинается область векторов прерываний (в кортексах вектор прерывания == адрес обработчика).
Минимально необходимы первые два: NMI - немаскируемое и HardFault - фатальный сбой.

Дальше всё просто
.=0x200 - указание ассемблеру размещать остальную программу с адреса 0x200, пропустив область векторов прерываний (просто для приличия, хотя это и не обязательно).
Reset: - сама "программа".
NMI: HardFault: - заглушка для прерываний.

Ассемблирование в объектный файл

Код:
as -mcpu=cortex-m{0,3,4} -o test.o test.s

Получение бинарника из объектного файла

Код:
objcopy -O binary test.o test.bin

Результат в Ozone
http://s4.uploads.ru/t/h0rKY.png

2

Я так понимаю, что этап линковки пропущен т.к. программа элементарная. в обычной жизни без линковщика ведь не обойтись ?

3

vt340
Добавь ещё реверсинг к стимула изучить АСМ...

4

Atomic-dm написал(а):

Я так понимаю, что этап линковки пропущен т.к. программа элементарная. в обычной жизни без линковщика ведь не обойтись ?

Да почему, можно и обойтись, если хочется )
Если не делать раздельной трансляции файлов, а объединять на уровне текста

head.s

Код:
.word 0x20001000
.word Reset+1
.word NMI+1, HardFault+1
.=0x200

test.s

Код:
.syntax unified
.include "head.s"
Reset:
    movs r0, 0
10$:
    adds r0, 1
    b 10$
NMI:
HardFault:
    b .

5

dosikus написал(а):

vt340
Добавь ещё реверсинг к стимула изучить АСМ...

Имеешь в виду "анализ исключительно в исследовательских целях"? )
Попадает под случай "Разобраться как оно устроено"

6

По закону жанра дальше должна быть мигалка )

Поскольку потребуется обращаться к регистрам периферии, и не по числовым же адресам к ним обращаться, то вот файлы определений символических имён регистров периферии (в архиве 43 файла, сгенерированные из всех 43-х SVD-файлов CMSIS)
https://yadi.sk/d/naqKh4e03EDEJU/STM32-Assembler/ SFR.zip

Пример мигалки для STM32F10x

Код:
.syntax unified
.word 0x20001000
.word Reset+1
.word NMI+1, HardFault+1
.=0x3C
.word SysTick+1
.=0x200

Начало такое же как в прошлой программе, плюс вектор системного таймера SysTick.
SysTick дальше будет настроен на период 1 мс.

Обработчик прерывания SysTick.

Код:
SysTick:
    add r8, 1
    tst r8, 0x100
    ite eq
    streq r10, [r9]
    strne r11, [r9]
    bx lr

R8 - счётчик, инкрементируется каждую миллисекунду.
R9 - адрес GPIO BSRR.
R10, R11 - значения GPIO BSRR для вкл. и выкл. светодиода.

Что здесь есть интересного? Во-первых выход из прерывания (bx lr).
Внешне выход из прерывания не отличается от выхода из подпрограммы - тот же безусловный преход по адресу из регистра LR.
Однако, если при вызовах подпрограмм стек не трогается (адрес возврата просто сохраняется в LR), то при прерываниях в стек автоматически загружаются восемь слов (R0-R3, R12, адрес возврата, PSR, LR).
Соответственно, при возврате они должны выгружаться обратно, но где спец команда для этого?
Фокус в том, что при прерываниях в LR заносится не адрес возврата, как при вызове подпрограмм, а спец код, который процессор распознаёт при загрузке в счётчик команд и делает выгрузку восьми слов из стека вместо этого.

Ещё из интересного - условный блок (ite eq и две следующие команды).
Внутри условного блока каждая команда либо выполняется, либо пропускается в зависимости от условия, что позволяет обходиться без ветвлений и меток.
Кол-во команд в условном блоке задаётся в ассемблере кол-вом букв "t" (then) и "e" (else) в заголовке блока.

Лирическое отступление )
Cortex - RISC процессор, операции с памятью - только загрузка-сохранение в регистр, составных команд нет, данные могут быть только в слове команды, а места там мало.
Поэтому оптимальный стиль - работа с массивами, структурами, таблицами, когда длинный базовый адрес загружается в регистр один раз, и дальше уже всё идёт косвенной адресацией с короткими смещениями.
В таком стиле компилирует си, под такой стиль заточены сишные headers периферии, так что мигалка тоже будет стильной )

Таблицы данных для мигалки.

Код:
.align
data:
.word 0, GPIOB_BSRR, 0x10000000, 0x1000  @ PB12

.word RCC_APB2ENR, 0, 8            @ GPIOB
.word GPIOB_CRH, 0xF0000, 0x30000  @ PB12 push-pull
.word STK_LOAD, -1, 8000           @ 1 ms
.word STK_CTRL, -1, 7
.word 0

Директива .align - выравнивание по границе 32-битных слов (подробнее - https://sourceware.org/binutils/docs/as/Align.html).
В первой строке начальные значения для R8-R11 для SysTick.
Дальше идёт таблица инициализации периферии - в каждой строке по три слова: адрес, маска и значение.

Последний фрагмент.

Код:
Reset:
    adr r0, data
    ldm r0!, {r8-r11}
10$:
    ldm r0!, {r1-r3}
    cbz r1, 20$
    ldr r4, [r1]
    bics r4, r2
    orrs r4, r3
    str r4, [r1]
    b 10$
20$:
    b .

NMI:
HardFault:
    b .
.include "stm32f103xx.s"

Команды ldm - загрузка серии слов из памяти сразу в несколько регистров, в R0 - адрес, "!" ещё и дополнительно инкрементирует R0 на 4*{кол-во регистров}, так что в следующий раз будет загрузка следующей серии слов.
Сначала из первой строки таблицы загружаются начальные значения R8-R11.
Затем для каждой следующей строки в R1-R3 загружаются адрес, маска и значение, проделывается чтение по адресу (ldr), "и" по дополнению к маске (bics), "или" по значению (orrs) и запись по адресу (str) - в общем всё так же как и на си, всё как обычно.

Директива .include с файлом определений периферии в самом конце программы - ассемблеру всё равно, он многопроходный, а для листинга удобнее когда 100500 строк определений в конце, после кода (опция для генерации листинга: -ahls={имя}.lst)

7

Небольшое развитие темы в сторону применения ассемблера для того, что невозможно сделать на си.
Способ получения бинарника без линковки, который я описывал в первом посте, не только упрощает компиляцию, но и даёт возможность раздельной компиляции и раздельной загрузки частей программы.
Бинарники, которые при этом получаются, можно загружать в произвольные места флэша МК в призвольном количестве, т.к. у кортексов практически полностью позиционно-независимый код.
А продвинутый дебаггер, например OpenOCD, позволяет подгружать такие бинарники прямо во время отладки - http://openocd.org/doc/html/Flash-Commands.html

8

Кодировка 16-битных инструкций thumb
Эксклюзив, в таком виде публикуется впервые )

Код:
00000iiiiimmmddd    lsls  Rd,Rm,#imm5
00001iiiiimmmddd    lsrs  Rd,Rm,#imm5
00010iiiiimmmddd    asrs  Rd,Rm,#imm5

0001100mmmnnnddd    adds  Rd,Rn,Rm
0001101mmmnnnddd    subs  Rd,Rn,Rm
0001110iiinnnddd    adds  Rd,Rn,#imm3
0001111iiinnnddd    subs  Rd,Rn,#imm3

00100dddiiiiiiii    movs  Rd,#imm8
00101nnniiiiiiii    cmp   Rn,#imm8
00110dddiiiiiiii    adds  Rd,#imm8
00111dddiiiiiiii    subs  Rd,#imm8

0100000000mmmddd    ands  Rd,Rm
0100000001mmmddd    eors  Rd,Rm
0100000010mmmddd    lsls  Rd,Rm
0100000011mmmddd    lsrs  Rd,Rm
0100000100mmmddd    asrs  Rd,Rm
0100000101mmmddd    adcs  Rd,Rm
0100000110mmmddd    sbcs  Rd,Rm
0100000111mmmddd    rors  Rd,Rm
0100001000mmmnnn    tst   Rn,Rm
0100001001nnnddd    rsbs  Rd,Rn
0100001010mmmnnn    cmp   Rn,Rm
0100001011mmmnnn    cmn   Rn,Rm
0100001100mmmddd    orrs  Rd,Rm
0100001101nnnddd    muls  Rd,Rn
0100001110mmmddd    bics  Rd,Rm
0100001111mmmddd    mvns  Rd,Rm

01000100dmmmmddd    add   Rd,Rm
01000101nmmmmnnn    cmp   Rn,Rm
01000110dmmmmddd    mov   Rd,Rm
010001110mmmm000    bx    Rm
010001111mmmm000    blx   Rm

01001tttllllllll    ldr   Rt,label

0101000mmmnnnttt    str   Rt,[Rn,Rm]
0101001mmmnnnttt    strh  Rt,[Rn,Rm]
0101010mmmnnnttt    strb  Rt,[Rn,Rm]
0101011mmmnnnttt    ldrsb Rt,[Rn,Rm]
0101100mmmnnnttt    ldr   Rt,[Rn,Rm]
0101101mmmnnnttt    ldrh  Rt,[Rn,Rm]
0101110mmmnnnttt    ldrb  Rt,[Rn,Rm]
0101111mmmnnnttt    ldrsh Rt,[Rn,Rm]

01100iiiiinnnttt    str   Rt,[Rn,#imm5]
01101iiiiinnnttt    ldr   Rt,[Rn,#imm5]
01110iiiiinnnttt    strb  Rt,[Rn,#imm5]
01111iiiiinnnttt    ldrb  Rt,[Rn,#imm5]
10000iiiiinnnttt    strh  Rt,[Rn,#imm5]
10001iiiiinnnttt    ldrh  Rt,[Rn,#imm5]
10010tttiiiiiiii    str   Rt,[SP,#imm8]
10011tttiiiiiiii    ldr   Rt,[SP,#imm8]

10100dddllllllll    adr   Rd,label
10101dddiiiiiiii    add   Rd,SP,#imm8

101100000xxxxxxx
101100001iiiiiii    sub   SP,#imm7
10110001xxxxxxxx
1011001000mmmddd    sxth  Rd,Rm
1011001001mmmddd    sxtb  Rd,Rm
1011001010mmmddd    uxth  Rd,Rm
1011001011mmmddd    uxtb  Rd,Rm
10110011xxxxxxxx
1011010lrrrrrrrr    push  {regs}
1011011xxxxxxxxx

1011100xxxxxxxxx
1011101000mmmddd    rev   Rd,Rm
1011101001mmmddd    rev16 Rd,Rm
1011101010xxxxxx
1011101011mmmddd    revsh Rd,Rm
10111011xxxxxxxx
1011110prrrrrrrr    pop   {regs}
10111110iiiiiiii    bkpt  #imm8
10111111xxxxxxxx

11000nnnrrrrrrrr    stm   Rn!,{regs}
11001nnnrrrrrrrr    ldm   Rn!,{regs}

11010000llllllll    beq   label
11010001llllllll    bne   label
11010010llllllll    bcs   label
11010011llllllll    bcc   label
11010100llllllll    bmi   label
11010101llllllll    bpl   label
11010110llllllll    bvs   label
11010111llllllll    bvc   label
11011000llllllll    bhi   label
11011001llllllll    bls   label
11011010llllllll    bge   label
11011011llllllll    blt   label
11011100llllllll    bgt   label
11011101llllllll    ble   label
11011110xxxxxxxx
11011111iiiiiiii    svc   #imm8

11100lllllllllll    b     label  

11101xxxxxxxxxxx
1111xxxxxxxxxxxx

9

Подключение отдельно и независимо скомпилированного ассемблерного бинарника к сишной программе:

Код:
#include "bios.h"
void SystemInit() {
    (*bios_init)();
}
volatile int c;
void SysTick_Handler() {
    (*bios_pb12)(c++ & 0x100);
}
int main() {
    for (;;);
}

Хедер ассемблерных ф-ций bios.h:

Код:
#define BIOS 0x8001000
void (*const bios_init) (void) = (void (*)) BIOS+1;
void (*const bios_pb12) (int)  = (void (*)) BIOS+5;

0x8001000 - это начальный адрес ассемблерного бинарника, точки входа ф-ций размещаются с интервалом в четыре байта.
Код ассемблерного бинарника почти тот же самый, что был в примере выше:

Код:
.syntax unified
b.w init  @ void (void)
b.w pb12  @ void (int)
init:
    adr r12, .+8
    b.w _set
    .word RCC_APB2ENR, 0, 8  @ GPIOB
    .word GPIOB_CRH, 0xF0000, 0x30000  @ PB12 push-pull
    .word STK_LOAD, -1, 8000  @ 1 ms
    .word STK_CTRL, -1, 7
    .word 0
pb12:
    adr r12, .+8
    b.w _pin
    .word GPIOB_BSRR, 0x10000000, 0x1000  @ PB12

_pin:
    ldm r12, {r1, r2, r3}
    cmp r0, 0
    ite eq
    streq r2, [r1]
    strne r3, [r1]
    bx lr    
_set:
    ldm r12!, {r1, r2, r3}
    cbz r1, 10$
    ldr r0, [r1]
    bics r0, r2
    orrs r0, r3
    str r0, [r1]
    b _set
10$:
    bx lr 
.include "stm32f103xx.s"

Ассемблерный код позиционно независим, бинарник может размещаться в любом месте.


Вы здесь » Микроконтроллеры » STM32: Software » Программирование STM32 на ассемблере