Продолжим освоение ассемблера для архитектуры ARM.
В предыдущих уроках мы использовали задержки исполнения кода с помощью пустых циклов, тем самым нам тяжело было даже примерно подсчитать заранее, сколько циклов потребуется для организации задержки на определённое время. Так как в прошлом занятии мы смогли добиться тактирования контроллера строго определённой частотой импульсов, то теперь нам эту задачу решить будет намного легче. А поможет нам в этом системный таймер SysTick, который присутствует во всех контроллерах STM32 и представляет собой простейший программируемый счётчик с обратным отсчётом до нуля. Как только данный счётчик досчитывает до нуля, генерируется прерывание, если оно конечно разрешено, а также устанавливается определённый флаг, который также можно отследить, тогда уже прерывания будет использовать необязательно.
С системным таймером мы также работали уже в уроке 167 с использованием библиотеки CMSIS, поэтому задача наша ещё более упростится, так как практически мы знаем все регистры и биты данной периферии и также некий алгоритм действий работы с данным таймером у нас уже есть.
Как всегда, на данном уроке мы изучим ещё несколько ассемблерных команд.
Схема наша также не изменилась
А проект для урока мы сделаем из проекта прошлого урока с именем ASM_BLINK01_RCC и присвоим ему имя ASM_SYSTICK01.
Откроем наш проект в Keil и добавим в файл с константами ещё несколько
1 2 3 4 |
FLASH_ACR EQU 0x40022000 SYSTICK_BASE EQU 0xE000E010 SCB_BASE EQU 0xE000ED00 |
1 2 3 4 5 6 7 8 9 10 |
FLASH_ACR_LATENCY_2_N EQU 1 STK_CTRL_CLKSOURSE EQU 0x00000004 STK_CTRL_TICKINT EQU 0x00000002 STK_CTRL_ENABLE EQU 0x00000001 STK_CTRL EQU 0 STK_LOAD EQU 4 SCB_SHPR3 EQU 0x20 STK_VAL EQU 0x00000008 |
Также создадим и подключим в качестве нового модуля ещё один ассемблерный файл с именем utils.s, в котором для начала также подключим наш файл с константами
1 2 3 4 5 |
GET stm32f103C8.asm ALIGN END |
Ещё в данном файле мы объявим область, которая будет доступна не только для чтения, но и для записи. Там будут храниться переменные (правда в данном случае только одна)
1 2 |
GET stm32f103C8.asm AREA __data, DATA, READWRITE |
С помощью данной переменной мы будем считать срабатывания нашего системного таймера, заранее занося в неё определённое значение в милисекундах и в обработчике прерывания Systick декрементируя данное значение, а уже в процедуре задержки смотреть, не досчитали ли мы до нуля.
Объявим эту переменную в нашей секции
1 2 3 |
AREA __data, DATA, READWRITE SYSTICK_COUNTER DCD 0 |
Далее пойдёт код, поэтому объявим соответствующую область
1 2 3 |
DCD 0 AREA __utils,CODE,READONLY |
Далее добавим процедуру инициализации системного таймера, в которой мы также сохраним в стеке значения регистров, которые будут в ней использоваться и по окончанию их вернём оттуда обратно в регистры
1 2 3 4 5 6 7 |
AREA __utils,CODE,READONLY SYSTICK_START PROC PUSH {R0, R1 ,LR} POP {R0, R1 ,PC} ENDP |
Объявим данную процедуру внешней
1 2 3 |
ALIGN EXPORT SYSTICK_START |
А в main.s её подключим
1 2 |
EXTERN SYSCLK72_START EXTERN SYSTICK_START |
И затем вызовем
1 2 |
BL SYSCLK72_START BL SYSTICK_START |
Вернёмся в utils.s и в нашей процедуре проинициализируем счётчик системного таймера с тем расчетом, чтобы он, досчитывал до данного значения ровно за 1 милисекунду
1 2 3 4 |
PUSH {R0 ,R1 ,LR} LDR R0 , =SYSTICK_BASE LDR R1 , =72000 - 1 STR R1 , [R0 , #STK_LOAD] |
В последней строке мы в качестве адреса не просто используем регистр, в котором содержится базовый адрес, но ещё и смещение, которое прибавляется к значению данного адреса, которое используется в квадратных скобках во втором параметре.
Сбросим счётчик
1 2 3 4 |
STR R1, [R0 , #STK_LOAD] LDR R1, [R0 , #STK_VAL] BIC R1, R1, #0x00FFFFFF STR R1, [R0 , #STK_VAL] |
Прерываниям от системного таймера установим самый низкий приоритет (15-й)
1 2 3 4 5 |
STR R1, [R0 , #STK_VAL] LDR R0, =SCB_BASE LDR R1, [R0, #SCB_SHPR3] ORR R1, R1, #0xF0 << 24 STR R1, [R0, #SCB_SHPR3] |
Настроим управляющий регистр, настроив в качестве источника неразделённую на 8 (оригинальную) системную частоту, разрешив прерывания и включив собственно счётчик
1 2 3 4 5 6 |
STR R1, [R0, #SCB_SHPR3] LDR R0, =SYSTICK_BASE LDR R1, [R0 , #STK_CTRL] ORR R1, R1, #(STK_CTRL_CLKSOURSE + STK_CTRL_TICKINT + STK_CTRL_ENABLE) STR R1, [R0 , #STK_CTRL] |
Если мы теперь соберём код и прошьём контроллер, то наш светодиод мигать перестанет, так как мы включили прерывания, а обработчик не написали, мало того, у нас нет таблицы векторов прерываний (адресов их обработчиков), которая начинается в начале кода, вместо неё идёт код, и когда возникнет прерывание, то адрес обработчика будет браться прямо из кодового сегмента и приведёт непонятно куда. Мы с вами видели подобную таблицу в startup в наших проектах, написанных на C, подобную таблицу нам надо будет сгандобить и в нашем проекте.
Не обязательно её размещать всю, но надо разместить хотя бы ту часть которая идёт от её начала до вектора прерывания, которое мы хотим обрабатывать.
Поэтому расположим нашу таблицу перед секцией кода в main.s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
DCD Start DCD int_vect_terminator DCD int_vect_terminator DCD int_vect_terminator DCD int_vect_terminator DCD int_vect_terminator DCD 0 DCD 0 DCD 0 DCD 0 DCD int_vect_terminator DCD int_vect_terminator DCD 0 DCD int_vect_terminator DCD ISR_SYSTICK |
В качестве ненужных нам пока адресов у нас будут нули, а в качестве адресов тех прерываний, попав в которые мы будем уходить в бесконечный цикл, будем использовать соответствующую метку. Самым последним в таблице у нас будет адрес метки, с которой будет располагаться подпрограмма обработчика прерываний от SYSTICK.
Добавим для начала метку для перезагрузки контроллера и соответственно сразу же и безусловный переход на неё, чтобы процессор крутился на одном месте, если случайно попадёт не в то прерывание
1 2 3 4 |
EXTERN SYSTICK_START int_vect_terminator B int_vect_terminator |
Теперь идём в utils.s, в котором добавим процедуру для обработки прерывания от системного таймера ниже процедуры SYSTICK_START
1 2 3 4 |
ISR_SYSTICK PROC BX LR ENDP |
Как вы, надеюсь, заметили, мы уже не сохраняем в стек никакие регистры, так как механизм обработки прерываний устроен так, что сохранение регистров возврат их происходит автоматически.
Экспорт и импорт данной процедуры производится аналогично предыдущим, поэтому данный процесс я уже показывать не буду.
Если мы теперь соберём код и прошьём контроллер, то у нас по-прежнему будет мигать светодиод, так как нам никакие флаги сбрасывать не нужно, главное, чтобы был обработчик и чтобы в таблице прерываний в нужном месте был его адрес.
В данном обработчике мы будем декрементировать наш пользовательский счётчик и узнавать, не досчитал ли он до нуля.
Для начала сравним с нулём значение данного счётчика
1 2 3 4 |
ISR_SYSTICK PROC LDR R1, =SYSTICK_COUNTER LDR R0, [R1] CMP R0, #0 |
Теперь, если значение не равно нулю, то мы должны декрементировать данное значение, а если равно — то просто выйти из обработчика.
Здесь мы можем применить условную конструкцию.
Условная конструкция выглядит следующим образом
Суффиксы x,y.z необязательны. Сейчас расскажу, зачем они нужны. Если мы не будем использовать суффикс, то мы в теле условия сможем использовать только одну ассемблерную команду, но только этого не всегда достаточно для условных конструкций.
Для начала давайте попробуем обойтись без суффиксов. Сначала идёт команда самого условия
1 2 |
CMP R0, #0 IT NE |
Условие NE в команде IT означает то, что если означает что условие наше будет истинно тогда, когда флаг Z будет равен нулю, то есть результат предшествующей команды не будет нулевым.
В данном случае мы декрементируем значение нашего счётчика
1 2 |
IT NE SUBNE R0, R0, #1 |
Причём мы обязательно либо повторяем условный суффикс в команде, либо используем обратный (в нашем случае будет EQ). Таковы требования условных конструкций. Выглядит немного запутанно, но постепенно привыкнем, так как использовать такие конструкции приходится довольно-таки часто.
Но только данной команды нам будет недостаточно, так как мы продекрементировали только значение регистра, а нам ведь надо данное значение обратно в переменную записать.
Поэтому для того, чтобы использовать две команды, то нам будет нужно в команде условной конструкции добавить ещё один суффикс
ITT NE
Ещё одна T потребует теперь от нас ещё одной команды, пока мы её не добавим, у нас будет ошибка сборки. Здесь вместо T мы можем использовать E, которая будет означать то, что вторая команда будет выполняться в противном случае, то есть в случае, если условие не выполнится (если сравнить с бейсиком, то IF THEN ELSE).
Добавим вторую команду
1 2 |
SUBNE R0, R0, #1 STRNE R0, [R1] |
Теперь данные две команды будут выполняться тогда, когда счётчик наш будет иметь ненулевое значение.
Вот и весь код обработчика.
Теперь нам нужно будет написать подпрограмму для задержки в милисекундах. Условимся, что значение задержки мы перед вызовом подпрограммы будем помещать в регистр R0.
Добавим процедуру задержки ниже процедуры SYSTICK_START
1 2 3 4 5 |
SYSTICK_DELAY PROC PUSH {R1, R2, LR} POP {R1, R2, PC} ENDP |
Сбросим в данной подпрограмме сначала счётчик системного таймера
1 2 3 4 |
PUSH {R1, R2, LR} MOV R1, #0 LDR R2, =SYSTICK_BASE STR R0, [R2, #STK_VAL] |
Запишем значение из регистра R0 (значение милисекунд для задержки) в наш пользовательский счётчик
1 2 3 |
STR R0, [R2, #STK_VAL] LDR R1, =SYSTICK_COUNTER STR R0, [R1] |
Добавим метку
1 2 |
STR R0, [R1] delay_loop1 |
Далее мы будем в цикле забирать значение из нашей переменной, которая будет постоянно декрементироваться в обработчике прерывания, и, пока она не будет в нуле, мы будем крутиться в данном цикле, а как только достигнет нуля — выйдем из цикла
1 2 3 4 |
delay_loop1 LDR R0, [R1] ORRS R0, R0, #0 BNE delay_loop1 |
Произведём экспорт нашей процедуры и импортируем её в main.s, в который затем перейдём и изменим код процедуры DELAY, которая примет после этого следующий вид
1 2 3 4 5 6 |
DELAY PROC PUSH {R0, LR} MOV R0, #500 BL SYSTICK_DELAY POP {R0, PC} ENDP |
Теперь наша задержка будет ровно (ну или почти ровно) 500 милисекунд.
Соберём код, прошьём контроллер и проверим результат работы нашего кода.
Светодиод теперь мигает раз в секунду
Итак, на данном уроке мы научились использовать системный таймер SysTick, благодаря которому мы теперь можем отсчитывать строго определённые интервалы, а также научились обрабатывать прерывания и использовать условные конструкции.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добавить комментарий