STM Урок 205. Assembler. Таймеры. Табличные переходы



Продолжаем освоение ассемблера для архитектуры ARM.

На данном занятии мы попробуем задействовать такой механизм, как аппаратные таймеры.

Что это такое, мы давно знаем и давно используем в своих проектах. Поэтому вдаваться в подробности устройства аппаратных таймеров в STM32 и их настроек мы не будем.

Также в данном уроке мы познакомимся с таким понятием в ассемблере как табличные переходы. Познакомимся мы с ним при написании нашего кода, а пока лишь скажу то, что табличные переходы нам помогут организовать ветвление кода в зависимости от значения аргумента подобно конструкции switch в языке C. Также они нам помогут удобно управлять уровнями одновременно нескольких ножек портов, так как в схему мы теперь включим целых 10 светодиодов, а вернее светодиодную планку с десятью светодиодами, как мы делали, например, в уроке 165 с использованием библиотеки CMSIS

 

 

Проект за основу мы возьмём из прошлого урока с именем ASM_SYSTICK01 и назовём его ASM_TIM2.

Откроем проект в Keil и откроем в нём файл main.s. Первым делом нам нужно будет настроить ножки портов, задействованные под светодиоды.

Напомню, какие это ножки и каких портов

 

  1. LED1GPIOA2
  2. LED2GPIOA3
  3. LED3GPIOA4
  4. LED4GPIOA5
  5. LED5GPIOA6
  6. LED6GPIOA7
  7. LED7GPIOB0
  8. LED8GPIOB1
  9. LED9GPIOB10
  10. LED10GPIOB11

 

Предлагаю вынести процедуру настройки ножек GPIO в отдельную подпрограмму, поэтому пока удалим настройку GPIO в main.s, тем более настройка порта C и его 13 ножки нам не потребуется

 

LDR R2, =(PERIPH_BB_BASE + (RCC_APB2ENR — PERIPH_BASE) * 32 + RCC_APB2ENR_IOPCEN_N * 4)
STR R1, [R2]

LDR R2, =(PERIPH_BB_BASE + (GPIOC_CRH — PERIPH_BASE) * 32 + GPIO_CRH_MODE13_0_N * 4)
STR R1, [R2]

LDR R2, =(PERIPH_BB_BASE + (GPIOC_ODR — PERIPH_BASE) * 32 + GPIO_ODR_ODR13_N * 4)
STR R1, [R2]

 

Из бесконечного цикла также удалим всё его тело.

В файл с константами stm32f103C8.asm тоже нужно будет добавить ряд новых

 

 

 

 

 

 

 

 

 

 

 

Процедуру инициализации GPIO добавим после процедуры START

 

 

В данной процедуре всё стандартно, настройки такие же как у наст были для ножки PC13.

Вызовем данную процедуру в процедуре START

 

 

А вот для работы с таймером мы создадим отдельный модуль. Добавим новый файл tim.s в группу user пока со стандартным содержимым

 

 

Добавим в данном файле переменную для пользовательского счётчика срабатываний таймера

 

 

Далее добавим процедуру инициализации, пока почти без кода

 

 

Произведём её экспорт и импортируем её в фале main.s, а затем вызовем её в процедуре START

 

 

С тем же самым таймером мы работали в уроке 168, поэтому нам будет написать процедуру инициализации, а впоследствии и процедуру обработки прерывания значительно легче.

Перейдём в файл tim.s и первым делом в процедуре TIM2_INIT организуем тактирование нашего таймера

 

 

Обнулим пользовательский счётчик

 

 

Произведём основные настройки делителя и также регистра с числом, до которого будет наш таймер считать

 

 

Включим глобальные прерывания таймера, иначе локальные без них работать не будут

 

 

Теперь разрешим прерывания от таймера по событию обновления счётчика

 

 

 

Запустим наш таймер

 

 

А вот светодиодами по очереди мы будем мигать в обработчике прерываний, который мы добавим выше процедуры прерывания, пока также практически без кода

 

 

Произведём также экспорт данной процедуры и импортируем её в main.s, в котором мы также должны добавить вектор данного прерывания

Чтобы вектор добавился в правильном месте мы должны до него добавить несколько адресов меток бесконечного цикла на случай срабатывания других прерываний

 

 

Вернёмся в файл tim.s обработчик прерывания TIM2_IRQHandler и сбросим флаг прерывания

 

 

Наш обработчик будет инкрементировать значение пользовательского счётчика от 0 до 9 и в зависимости от данного значения зажигать соответствующий светодиод, а предыдущий тушить.

Загрузим в регистры процессора адреса ODR второй ножки порта A и нулевой ножки порта B, вернее не сами адреса, а их алиасы для битбэндинга

 

 

В другой регистр загрузим значение нашего пользовательского счётчика

 

 

А вот дальше самое интересное.

Мы должны в зависимости от значения счётчика выполнить соответствующий участок кода, который не очень маленький, можно конечно написать серию условий и переходов, но есть более интересное и оптимальное решение — механизм табличных переходов.

Давайте пока пропустим использование данного механизма и напишем сами участки кодов с метками, иначе нам будет некуда переходить. Причём из этих участков по их окончанию мы должны также перейти в какую-то одну и ту же точку программы, поэтому пропустим и данные участки, добавим пока эту точку — метку и дальнейший код.

В данном коде мы будем инкрементировать значение пользовательского счётчика и сравнивать его с числом 10

 

 

Введём команду условия

 

 

Мы уже знаем из прошлого урока как работает данное условие. Правда, тогда мы использовали NE, которое проверяло, что флаг нуля для срабатывания условия должен быть сброшен. А вот EQ проверит здесь, что флаг нуля установлен.

И если он установлен, то есть результат операции равен нулю, а в случае сравнения результат совпал, то мы тогда занесём в регистр R4 ноль, то есть сбросим наш счётчик и затем занесём значение регистра R4 обратно в счётчик, а если флаг нуля не установится, то мы пропустим команду сброса счётчика и занесём значение неизменённого регистра R4 в переменную счётчика

 

 

Теперь вернёмся назад и добавим все наши 10 участков кода, отвечающие за установку высокого уровня соответствующей значению счётчика ножки порта и установки низкого уровня предыдущей

 

 

Понятное дело, что в последнем кейсе мы не используем переход, так как метка EXIT01 следует за последней его командой.

Осталось дело за малым: перейти в зависимости от значения счётчика в соответствующий кейс.

Для этого нам нужно будет познакомиться с командами табличных переходов

 

 

В качестве первого операнда используется регистр, в котором находится адрес таблицы для наших ветвей, если таблица адресов, а вернее смещений относительно текущего места, следует сразу же за командой TBB или TBH, то мы используем регистр PC и в данном случае адресом таблицы является адрес инструкции + 4.

Различие между инструкциями TBB и TBH во том, что первая работает со смещением не больше байта, то есть использует короткие смещения (в данном случае адрес перехода — это значение регистра Rm, умноженное на 2 плюс адрес начала таблицы), а вторая — со смещением не больше полуслова, также в данном случае обязательным является использование ещё и сдвига влево. В качестве регистра Rm не могут выступать регистры PC и SP. Команда LSL #1 производит сдвиг влево значения регистра Rm, которое затем и используется в качестве смещения.

Думаю, что с длинными смещениями мы тоже когда-нибудь столкнёмся, а пока введём первую команду в нашем коде

 

 

Мы отлично помним, что в регистре R4 на данный момент у нас содержится значение нашего счётчика.

Дальше добавим метку начала нашей таблицы переходов

 

 

А теперь добавим значение смещения перового перехода. Нам придётся его вычислять прямо в коде, так как при изменении любого участка кода, следующего до данного значения адрес перехода изменится. Так как мы знаем, что значение смещения — это расстояние в байтах между началом таблицы и адресом перехода, умноженное на 2, то в таблицу мы должны занести значение, наоборот, разделённое на 2. Поэтому наш первый переход будет выглядеть примерно вот таким образом

 

 

Аналогичным образом будут выглядеть и смещения остальных переходов

 

 

Вот и всё. Надеюсь, объяснил нормально. Хотя я, признаться, подобного объяснения нигде не нашел, даже в англоязычной документации (наверно искал плохо).

Соберём наш код, прошьём контроллер и увидим, что наши светодиоды побегут друг за другом

 

 

Таким образом, сегодня мы научились использовать аппаратные таймеры в наших программах, также закрепили свои знания в области использования механизма прерываний и также познакомились с такой интересной возможностью, как использование в программах табличных переходов.

Всем спасибо за внимание!

 

 

Предыдущий урок Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM Assembler. Таймеры. Табличные переходы

 

2 комментария на “STM Урок 205. Assembler. Таймеры. Табличные переходы
  1. A-Soft:

    А возможны ли в данном ассемблере многострочные комментарии?

    PS
    С ассемблером начинаю чётко и ясно понимать CMSIS и RM0008.
    Надо для себя многое комментировать чтобы не забыть.
    на keil.com не нашел (может искал плохо)

    /* */ не работает

    %ifdef COMMENT
    комментируемый код — не работает
    %endif

    COMMENT *
    комментарий — не работает
    *

  2. Сергей З:

    Дорогой Автор. Спасибо вам за такой чудесный и здравый ресурс по стм нигде в англоязычном инете я такого не встречал. У меня есть тоже несколько проектов могу с вами поделиться.
    Пожалуйста продолжайте это дело несите свет в массы. Искренне вам благодарен

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*