MSP430 манипулируем стеком

У новичка в прграммировании рано или поздно, возникает вопрос: "Что такое стек?".
Наиболее наглядным примером, для понимания, на мой взгляд, есть программа на языке ассемблера , которая просто добавляет данные в стек.
Стек - это структура данных присущая всем микропроцессорным устройствам. Чаще всего принцип работы стека сравнивают со стопкой тарелок: чтобы взять вторую сверху, нужно снять верхнюю. Часто стек называют магазином — по аналогии с магазином патронов в огнестрельном оружии (стрельба начнётся с патрона, заряженного последним).
Зачем нужен стек ?
Вы вряд ли будете писать программы, которые не будет использовать функции (подпрограммы). При вызове функции в стек копируется адрес для возврата после окончания выполнения данной подпрограммы. По окончании её выполнения адрес возвращается из стека в счетчик команд и программа продолжает выполняться с места после функции.
Также в стек необходимо помещать регистры, которые используются в данной подпрограмме (в языках высокого уровня этим занимается компилятор).
Все вышесказанное характерно для так называемого аппаратного стека. Надеюсь вы догадываетесь, что такая структура данных (LIFO - last in, first out) полезна далеко не только при работе на низком уровне. Часто возникает необходимость хранить данные в таком порядке (например известный алгоритм разбора арифметических выражений основан на работе со стеком), тогда программисты реализуют программный стек.
Как это работает?
Давайте разберем работу со стеком на примере контроллеров семейства MSP430.
В MSP430 стек основан на предекрементной схеме. Т.е. перед тем как вы записываете данные в стек он уменьшает адрес вершины стека (верхней тарелки). Бывает также постдекрементный/постинкрементный (вычитание/добавление вершины стека происходит после записи данных) и прединкрементный (перед записью адрес вершины увеличивается).
Если стек увеличивает свой адрес при записи данных, говорят о стеке растущем вверх, если же уменьшает - вниз. За хранения адреса вершины стека отвечает регистр SP.
Как видите адрес вершины по умолчанию у нас 0x0A00.
Рассмотрим вот такую программу:
PUSH #0123h ; Помещение числа 0123h на вершину стека (TOS) ; копируем данные из памяти MOV.W &0x0A00, R5 MOV.W &0x09FE, R6 ; пишем еще два числа PUSH #9250h PUSH #0000h ; выводим данные из стека POP R8 POP R9 POP R10
Что делает эта программа?
Командой PUSH мы помещаем данные 0123h в стек. Казалось бы этой командой мы запишем 0123h в память по адресу 0x0A00, но мы ведь помним, что стек у нас предекрементный. Поэтому сначала адрес уменьшается на 2 (0x0A00 - 2 = 0x09FE) и в ячейку с полученным адресом записываются данные.
Вот так выглядела память изначально:
После выполнения команды PUSH (красным выделены изменения):
Итак данные записались.
Проверим так ли это выполнив две команды пересылки (mov). Сначала получим данные из ячейки 0x0A00 и запишем их в регистр R5, а затем запишем в регистр R6 данные из ячейки 0x09FE.
После этого в регистрах будет данные:
Далее запишем еще два числа в стек, после чего будем из доставать при помощи команды POP. После выполнения ещё двух команд PUSH память в стеке будет выглядеть так:
При выполнении команд POP вершина стека будет увеличиваться на 2 при каждой команде, а в регистры R8-10 попадут данные: 0x0000, 0x9250 и 0x0123 соответственно.
При добавлении других данные память (которая все еще содержит данные, выведенные из стека) будет заполнена новыми значениями.
Проиллюстрировать работу со стеком можно так (слева на право):
Изначально адресом стека был 0x0A00, в нем хранились 0000. При выполнении PUSH верхушкой стека стала ячека ниже (с адресом 0x09FE) и в неё записались данные. С каждой следующей командой верхушка находиться ниже в памяти. При выполнении команды POP картина обратная.
; MSP430 Assembler Code Template для применения с TI Code Composer Studio ; Эта программа демонстрирует использование стека. ; Rob Frohne November, 2015 ; Эта программа предназначена для того, чтобы поэкспериментировать со стеком! ;------------------------------------------------------------------------------- .cdecls C,LIST,"msp430.h" ; Подключаем заголовочный файл вашего микроконтроллера ;------------------------------------------------------------------------------- .def RESET ; Экспорт точки входа программы, чтобы сделать ее известной линкеру. ;------------------------------------------------------------------------------- .text ; Ассемблирование в память программ. .retain ; Переопределение ELF, условная компоновку и сохранение текущего раздела. .retainrefs ; И сохранение любых разделов, которые имеют ссылки на текущий раздел. ;------------------------------------------------------------------------------- RESET mov.w #__STACK_END,SP ; Инициализация указателя стека StopWDT mov.w #WDTPW|WDTHOLD,&WDTCTL ; Остановка watchdog timer ;------------------------------------------------------------------------------- ; Основной цикл здесь ;------------------------------------------------------------------------------- mov.w #0xffff,r6 mov.w #0x000f,r7 loop1 push r6 dec r7 jnz loop1 mov.w #0x000f,r7 loop2 pop r6 dec r7 jnz loop2 mov.w #0x0004,r4 mov.w #0x0005,r5 mov.b #0xff06,r6 call #subroutine1 mov.w r4,&0x43f2 jmp $ subroutine1 push.w r4 push.w r5 push.b r6 mov.w #0x4444,r4 mov.w #0x5555,r5 mov.w #0x6666,r6 pop.b r6 pop.w r5 pop.w r4 ret ;------------------------------------------------------------------------------- ; Определение указателя стека ;------------------------------------------------------------------------------- .global __STACK_END .sect .stack ;------------------------------------------------------------------------------- ; Векторы прерываний ;------------------------------------------------------------------------------- .sect ".reset" ; MSP430 RESET Vector вектор сброса .short RESET