Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Системный таймер (SysTick)

  • Tutorial
Вступление

В предыдущей статье мы помигали светодиодом, но сделали это не совсем правильно. Дело в том, что в качестве задержки мы использовали пустой цикл, который под каждый временной интервал приходится подбирать. Такой способ так же не подходит, когда нам нужны точные временные интервалы. Для решения данной проблемы в нашем микроконтроллере имеются сразу три полноценных таймера и один системный. Для начала поставим небольшую подзадачу. Нам нужно получить мигание светодиодом с интервалом в одну секунду. Задача очень простая и для этого нам вполне хватит системного таймера. Из-за своей легкости он идеально подходит для такого рода задач. Рассмотрим его по подробнее.

Изучение и настройка

Посмотрим, что о данном таймере написано в документации.
Процессор имеет 24-х разрядный системный таймер, SysTick, который считает вниз от загруженного в него значения до нуля; перезагрузка (возврат в начало) значения в регистр LOAD происходит по следующему фронту синхросигнала, затем счёт продолжается по последующему фронту.
Когда процессор остановлен для отладки, таймер не декрементируется.

Не густо, но нам вполне хватит. Пора настроить его. Взглянем на карту регистров.



Всего четыре регистра, но понадобятся нам всего два из них. Рассмотрим их поподробнее.



Это основной регистр настройки. Как мы видим, нам нужно выбрать источник тактового сигнала, разрешить прерывания и включить счетчик. Тактировать таймер мы будем от HCLK. По умолчанию на этом источнике частота тактирования ядра, то есть 8 Мгц. Прерывания нам будут нужны для создания собственной функции точной задержки. В виду того, что я не смог найти define-ы бит регистра, я решил прописать их самостоятельно.



Создаем отдельную функцию настройки таймера и вписываем в нее нашу конструкцию.

void Init_SysTick (void)
{
SysTick->CTRL |= CLKSOURCE|TCKINT|ENABLE;
}

Смотрим следующий регистр:



Именно сюда мы должны загрузить значение задержки. Чтобы сделать нашу функцию более универсальной, настроим таймер на прерывание раз в одну миллисекунду. Мы настроили наш таймер на тактирование от HCLK – это частота тактирования ядра микроконтроллера. По умолчанию она равна 8 Мгц = 8000000 тактов в секунду. В одной секунде тысяча миллисекунд => 8000000(количество тактов в секунду)/1000(количество миллисекунд в секунде) = количество тактов в одной миллисекунде. Но нужно не забыть отнять «1», как описано в примере выше. Выходит следующее.

SysTick->LOAD = (8000000/1000)-1;

Думаю, что данную настройку правильнее будет разместить перед включением таймера.

Итого, наша функция приобретает следующий вид.


Пишем функцию задержки.

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


Чуть пролистав вниз, мы можем увидеть длинный столбец.


Это так называемые «вектора прерываний». Когда появляется какое-то внешнее/внутреннее неотложное событие (прерывание), микроконтроллер прерывает выполнение основной программы, переходит к этой таблице и смотрит, куда ему нужно перейти дальше. Например, когда появляется прерывание от нашего таймера, он переходит к пункту с именем «SysTick_Handler». В случае, если вектор не прописан нами в программе (нет функции с таким именем) – контроллер игнорирует его и продолжает выполнение своей программы. Но если в программе есть функция с этим именем, то он переходит к ее выполнению.

Создадим в папке main функцию с именем SysTick_Handler. Далее объявим глобальную переменную 32-х битной разрядности. Для того, чтобы при «сжатии» проекта ее не трогал компилятор, перед ней добавляем «volatile». Если этого не сделать, то в будущем мы будем наблюдать различные ошибки. В самой функции прописываем условие: если переменная еще не равна нулю – отнять 1.

Таким образом, каждую миллисекунду контроллер будет бросать основную программу и проверять, если еще счетчик не пуст – отнять от него 1.

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


Данная функция принимает длительность задержки (в миллисекундах), копирует ее в переменную, из которой по прерываниям системного таймера функция отнимает 1 каждую миллисекунду. После того, как задержка исчерпана, программа продолжает свою работу. Данная задержка является далеко не самым лучшим вариантом траты процессорного времени. Но на начальных этапах ее хватает.

Теперь дело за малым. Заменить циклы нашими задержками.


На этом с системным таймером пока все. Исходник проекта можно взять здесь.
Разбираясь с системным таймером, я основывался на знаниях, полученных при изучении STM32 из данного видео-урока.
Поделиться публикацией

Комментарии 17

    +1
    Спасибо. Вставьте ссылки на предыдущие статьи…
      +1
      Спасибо, исправил.
      +1
      Строго говоря, SysTick — это часть ядра Cortex M3, поэтому работа с ним в разных контроллерах на этом ядре практически идентична.
      По-хорошему, вам даже не обязательно было трогать регистры; в файле core_cm3.h уже есть готовая функция для его настройки — SysTick_Config.

      Полезнее было бы рассмотреть работу с родной периферией миландра в каком-нибудь более-менее реалистичном сценарии. Чтение ADC через DMA, например.
        +1
        Строго говоря, SysTick — это часть ядра Cortex M3, поэтому работа с ним в разных контроллерах на этом ядре практически идентична.
        Полезнее было бы рассмотреть работу с родной периферией миландра в каком-нибудь более-менее реалистичном сценарии. Чтение ADC через DMA, например.

        Я знаю. Он нужен для следующего урока, посвященного системам тактирования МК. Для наглядности.
        По-хорошему, вам даже не обязательно было трогать регистры; в файле core_cm3.h уже есть готовая функция для его настройки — SysTick_Config.

        Про функцию тоже знаю. Но куда приятнее настроить вручную. Чтобы знать наверняка, что все правильно.
          +1
          Но куда приятнее настроить вручную. Чтобы знать наверняка, что все правильно.

          Я бы сказал, чтобы было проще ошибиться.
          Дальше без SPL будет сложно; хотя в ней полно ошибок, код с регистрами пишется и читается значительно тяжелее.
          +1
          Как раз в образовательных целях написание своей функции работы с таймером полезней, чем использование библиотечной… Использование библиотечной функции ничего кроме знания названия этой функции новичку не даст…
            0
            Я очень сомневаюсь, что знание о стандартизированных регистрах ценнее, чем знание о стандартизированной библиотечной функции.
              +2
              Зря сомневаетесь. Это в обычном каком-нибудь веб-программировании можно не знать ассемблер и принципов работы железа. А для работы с микроконтроллерами — это основа основ. При отладке и поиске багов без знания устройства кода на низшем уровне никак не обойдешься.
              Нисколько не умаляю важности знания библиотек для дальнейшей работы. Но всему своё время…
                0
                Я не спорю, что знание ассемблера и принципов работы железа — основа (хотя современные тенденции от этого уводят изо всех сил).
                Но каждый бит в каждом регистре все равно не запомнишь.
                  0
                  И не надо биты запоминать. На то документация и дана. Важно понимать принцип. Этого документацией не заменишь…
          +1
          SysTick->LOAD |= ...;
          
          выглядит странно, не находите?

          И используйте, пожалуйста, png, а не jpg для скриншотов, то глаза вытекают от такого мыла и шума.
            +1
            SysTick->LOAD |= ...;
            выглядит странно, не находите?

            Ошибка. Спасибо, исправил.
              +1
              И используйте, пожалуйста, png, а не jpg для скриншотов, то глаза вытекают от такого мыла и шума.

              Заменил код. Спасибо. В будущем буду выкладывать в png.
            +1
            В Keil есть встроенная небольшая операционная система — RTX. Позволяет создать многозадачную систему с плюшками (события, мьютексы, приоритеты..). Таймер SysTick однозначно занимается под эту операционку, это основной таймер такой системы. Задача мигания светодиодом при использовании RTX могла бы вылядеть так:
            __task void f_LED (void) {
            while(1)
            { PORTС-RXTX!=1;
            os_dly_wait (1000);
            PORTС-RXTX&=~(1);
            os_dly_wait (1000);
            }

            В периоды ожидания os_dly_wait МК выполняет другие задачи.

            Для прикладных задач логичнее использовать таймеры общего назначения, внешние по отношению к ядру ARM. Они в этом микроконтроллере достаточно наворочанные, число регистров конфигурации гораздо больше 4-х.
            Мигание можно реализовать через таймер несколькими способами
            — с использованием аппаратного прерывания таймера. Состояние светодиода изменяется непосредственно в теле прерывания
            — с использованием аппаратного прерывания таймера. В основное тело программы передается метка наступления события (вариант автора статьи).
            — настройками таймера в режиме ШИМ. При этом не используются ни прерывания, ни код в основном теле программы.
              0
              Они в этом микроконтроллере достаточно навороченные, число регистров конфигурации гораздо больше 4-х.

              В данном МК их всего 3 и, в случае когда не требуется использование ОСРВ, лучше задействовать SysTick как один дополнительный таймер для вещей некритичных к точности (например моргание светодиодом, подсчёт периодов) с кратным всем периодам (у меня чаще всего SysTick считает миллисекунды), а внутри уже последовательно считать периоды для каждого таска:

              void SysTick_Handler(void)
              {
                  LED_Blink();
                  
                  static unsigned task1 = SECONDS(1);
                  
                  if(!(task1--))
                  {
                      LED_Blink();        
                      task1 = SECONDS(1);
                  }
              
                  static unsigned task2 = MILLISECONDS(254);
                  
                  if(!(task2--))
                  {
                      UART_Handler();        
                      task2 = SECONDS(1);
                  }
              }
              
                +1
                По количеству таймеров согласен — на них сэкономили.
                Про RTX и таймеры общего назначения упомянул для полноты картины, в рамках раздела — «Электроника для начинающих».
                Когда не использую ОСРВ, тоже применяю SysTick. Но немного по другому — глобальная переменная счетчика миллисекунд наращивается в прерывании SysTick. В основном теле программы использую case c конечными автоматами — примерно так

                int delay=500;
                int State=BEGIN;
                
                while (1) {
                   switch (State) {
                        case BEGIN:
                            LocalTimer=GlobalTimer+delay;
                            LedOn();    
                            State=LED_ON;      
                            break; 
                        case LED_ON:
                            if (LocalTimer>=GlobalTimer) {
                               LocalTimer=GlobalTimer+delay;
                               LedOff();    
                               State=LED_OFF;      
                            }
                            break;
                        case LED_OFF:
                            if (LocalTimer>=GlobalTimer) {
                               LocalTimer=GlobalTimer+delay;
                               LedOn();    
                               State=LED_ON;      
                            }
                            break;
                   }
                }


                Код упрощенный, например нет запрета прерываний (надо предотвратить попытку одновременного изменения GlobalTimer). Также есть проблема в ограничении длины GlobalTimer.
                Ничто не мешает сделать несколько конечных автоматов и переключаться между ними — получается подобие ОСРВ.
                0
                Про RTX спасибо. Буду изучать. Но сначала разберу всю периферию без ОС. Раньше пытался подружиться с FreeRTOS, но после недели безуспешных попыток передать двоичный симафор — сдался. Написал свою мини ОС в десяток другой строк и этого хватило.

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

              Самое читаемое