Вступление

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

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

Посмотрим, что о данном таймере написано в документации.
Процессор имеет 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. northbear
    09.04.2015 19:54
    +1

    Спасибо. Вставьте ссылки на предыдущие статьи…


    1. Vadimatorikda Автор
      10.04.2015 03:08
      +1

      Спасибо, исправил.


  1. Amomum
    09.04.2015 20:43
    +1

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

    Полезнее было бы рассмотреть работу с родной периферией миландра в каком-нибудь более-менее реалистичном сценарии. Чтение ADC через DMA, например.


    1. Vadimatorikda Автор
      10.04.2015 03:10
      +1

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

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

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


      1. Amomum
        10.04.2015 10:07
        +1

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

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


    1. northbear
      10.04.2015 18:14
      +1

      Как раз в образовательных целях написание своей функции работы с таймером полезней, чем использование библиотечной… Использование библиотечной функции ничего кроме знания названия этой функции новичку не даст…


      1. Amomum
        10.04.2015 22:41

        Я очень сомневаюсь, что знание о стандартизированных регистрах ценнее, чем знание о стандартизированной библиотечной функции.


        1. northbear
          12.04.2015 06:59
          +2

          Зря сомневаетесь. Это в обычном каком-нибудь веб-программировании можно не знать ассемблер и принципов работы железа. А для работы с микроконтроллерами — это основа основ. При отладке и поиске багов без знания устройства кода на низшем уровне никак не обойдешься.
          Нисколько не умаляю важности знания библиотек для дальнейшей работы. Но всему своё время…


          1. Amomum
            12.04.2015 14:13

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


            1. northbear
              13.04.2015 05:25

              И не надо биты запоминать. На то документация и дана. Важно понимать принцип. Этого документацией не заменишь…


  1. grossws
    09.04.2015 20:47
    +1

    SysTick->LOAD |= ...;
    
    выглядит странно, не находите?

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


    1. Vadimatorikda Автор
      10.04.2015 03:12
      +1

      SysTick->LOAD |= ...;
      выглядит странно, не находите?

      Ошибка. Спасибо, исправил.


      1. Vadimatorikda Автор
        10.04.2015 03:24
        +1

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

        Заменил код. Спасибо. В будущем буду выкладывать в png.


  1. vertu77
    09.04.2015 23:43
    +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-х.
    Мигание можно реализовать через таймер несколькими способами
    — с использованием аппаратного прерывания таймера. Состояние светодиода изменяется непосредственно в теле прерывания
    — с использованием аппаратного прерывания таймера. В основное тело программы передается метка наступления события (вариант автора статьи).
    — настройками таймера в режиме ШИМ. При этом не используются ни прерывания, ни код в основном теле программы.


    1. nephrael
      10.04.2015 00:37

      Они в этом микроконтроллере достаточно навороченные, число регистров конфигурации гораздо больше 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. vertu77
        10.04.2015 12:59
        +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.
        Ничто не мешает сделать несколько конечных автоматов и переключаться между ними — получается подобие ОСРВ.


    1. Vadimatorikda Автор
      10.04.2015 03:23

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