ARM. Учебный Курс. SysTick — Системный таймер

Продолжаем потрошить кортексы М3. Есть у них у всех, вне зависимости от производителя, такая штука как системный таймер — SysTick. Это часть ядра. Тупейший и примитивный таймер. Он ничего не умеет кроме как генерировать прерывание в заданном промежутке времени. Используется обычно во всяких RTOS для проворачивания диспетчера. К тому же его прерывание имеет высокий приоритет.
 

Краткое описание
Сам таймер 24 разрядный. И тикает вниз от предзагруженного значения до нуля, после чего перезагружается вновь и генерирует прерывание. Управляется он четырьмя регистрами:

 
Таблица из ARM Cortex M3 Reference Manual
 

SYST_CSR — SysTick Control and Status Register
 
Регистр управления и статуса. Несмотря на то, что он 32 битный заняты там всего 4 бита :)
 


 

  • Бит 0 — ENABLE — бит разрешающий таймеру считать. 1 можно, 0 — нельзя. Когда мы ставим ENABLE в 1 то таймер автоматически загружает свои счетные регистры значением из регистра SYST_RVR и начинает тикать вниз.
  • Бит 1 — TICKINT — разрешение прерывания. Когда этот бит 1, то на обнулении будет прерывание таймера.
  • Бит 2 — CLKSOURCE — откуда брать тики. Варианта два 0 — внешнее тактирование эталонного генератора, 1 — с частоты процессора.
  • Бит 16 — COUNTFLAG — флаг отсчета. Первый раз ставится в 1 после первого обнуления. При чтении, как я понял, автоматом обнуляется. Возвращает 1 если с последнего его чтения таймер перешел через ноль. Позволяет отследить были ли переполнения. Удобно.

 

SYST_RVR — SysTick Reload Value Register
Регистр предзагрузки. Именно отсюда берет таймер свое значение для перезагрузки при обнулении. Фактически вот сюда и нужно грузить требуемое число задержки. Регистр 32 разрядный, но используются только первые 24 бита.
Класть туда можно любое число в диапазоне 0x00000001-0x00FFFFFF. Можно и 0 положить, но ничего не будет. Т.к. у таймера весь экшн происходит с перехода из 0x00000001 в 0x00000000. А на ноль и получается ноль. Разве что COUNTFLAG вскочит. Только и всего. Поэтому значение SYST_RVR должно быть N-1 от желаемого количества тактов. Т.е. если надо получить прерывание на каждые 10000 тактов, то кладем 9999.
 

SYST_CVR — SysTick Current Value Register
Это, собственно, и есть счетный регистр. Тут оно тикает! В первых трех байтах. Есть у этого регистра особенность одна забавная. Из него можно только читать. А вот запись любого значения сразу его обнуляет. С обнулением флага COUNTFLAG.
Т.е. если надо занулить таймер — пиши сюда :)
 

SYST_CALIB — SysTick Calibration Value Register
Это, как ясно из названия, регистр калибровки. Его можно только читать, и возвращает он следующее:

  • SKEW — Показывает что записано в TENMS 0 — там реальыне калибровочные константы. 1 — какой то мусор который не имеет значения. Ну или ноль.
  • NOREF — показывает есть ли у девайса эталонная тактовая частота. 0 — есть, 1 — нет. Это зависит от производителя, позаботился ли он о такой штуке :) Если эталонных часов нет, то бит CLKSOURCE из регистра SYST_CSR читается как 1 и его нельзя изменить.
  • TENMS — калибровочная константа. Содержит значение для 10мс задержки. Как я понял, для эталонного генератора. Которого может и не быть.

 

▌Адреса и имена
Осталось залезть в CMSIS и поискать там описание и дефайны SysTick. Находятся быстро в core_cm3.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
typedef struct
{
  __IO uint32_t CTRL;                         /*!< Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t LOAD;                         /*!< Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t VAL;                          /*!< Offset: 0x08  SysTick Current Value Register      */
  __I  uint32_t CALIB;                        /*!< Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;
 
/* SysTick Control / Status Register Definitions */
#define SysTick_CTRL_COUNTFLAG_Pos         16                                             /*!< SysTick CTRL: COUNTFLAG Position */
#define SysTick_CTRL_COUNTFLAG_Msk         (1ul << SysTick_CTRL_COUNTFLAG_Pos)            /*!< SysTick CTRL: COUNTFLAG Mask */
 
#define SysTick_CTRL_CLKSOURCE_Pos          2                                             /*!< SysTick CTRL: CLKSOURCE Position */
#define SysTick_CTRL_CLKSOURCE_Msk         (1ul << SysTick_CTRL_CLKSOURCE_Pos)            /*!< SysTick CTRL: CLKSOURCE Mask */
 
#define SysTick_CTRL_TICKINT_Pos            1                                             /*!< SysTick CTRL: TICKINT Position */
#define SysTick_CTRL_TICKINT_Msk           (1ul << SysTick_CTRL_TICKINT_Pos)              /*!< SysTick CTRL: TICKINT Mask */
 
#define SysTick_CTRL_ENABLE_Pos             0                                             /*!< SysTick CTRL: ENABLE Position */
#define SysTick_CTRL_ENABLE_Msk            (1ul << SysTick_CTRL_ENABLE_Pos)               /*!< SysTick CTRL: ENABLE Mask */
 
/* SysTick Reload Register Definitions */
#define SysTick_LOAD_RELOAD_Pos             0                                             /*!< SysTick LOAD: RELOAD Position */
#define SysTick_LOAD_RELOAD_Msk            (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)        /*!< SysTick LOAD: RELOAD Mask */
 
/* SysTick Current Register Definitions */
#define SysTick_VAL_CURRENT_Pos             0                                             /*!< SysTick VAL: CURRENT Position */
#define SysTick_VAL_CURRENT_Msk            (0xFFFFFFul << SysTick_VAL_CURRENT_Pos)        /*!< SysTick VAL: CURRENT Mask */
 
/* SysTick Calibration Register Definitions */
#define SysTick_CALIB_NOREF_Pos            31                                             /*!< SysTick CALIB: NOREF Position */
#define SysTick_CALIB_NOREF_Msk            (1ul << SysTick_CALIB_NOREF_Pos)               /*!< SysTick CALIB: NOREF Mask */
 
#define SysTick_CALIB_SKEW_Pos             30                                             /*!< SysTick CALIB: SKEW Position */
#define SysTick_CALIB_SKEW_Msk             (1ul << SysTick_CALIB_SKEW_Pos)                /*!< SysTick CALIB: SKEW Mask */
 
#define SysTick_CALIB_TENMS_Pos             0                                             /*!< SysTick CALIB: TENMS Position */
#define SysTick_CALIB_TENMS_Msk            (0xFFFFFFul << SysTick_VAL_CURRENT_Pos)        /*!< SysTick CALIB: TENMS Mask */
/*@}*/ /* end of group CMSIS_CM3_SysTick */

 

Нам там нужны имена битов. Вот они: SysTick_CTRL_CLKSOURCE_Msk, SysTick_CTRL_TICKINT_Msk, SysTick_CTRL_ENABLE_Msk.
 

Конфигурация таймера на тик в 1ms, таким образом, будет выглядеть примерно так:

1
2
3
4
5
6
7
8
9
10
 
#define F_CPU 		72000000UL	// Тактовая у нас 72МГЦ
#define TimerTick  	F_CPU/1000-1	// Нам нужен килогерц
 
 SysTick->LOAD=TimerTick;		// Загрузка значения
 SysTick->VAL=TimerTick;		// Обнуляем таймеры и флаги. Записью, помните? 
 
 SysTick->CTRL=	SysTick_CTRL_CLKSOURCE_Msk |
                SysTick_CTRL_TICKINT_Msk   |
                SysTick_CTRL_ENABLE_Msk;

 

Обработчик прерывания SysTick
Где взять имя обработчика? Да из таблицы прерываний. Поглядеть, если кто не помнит, можно в STM32F10x.s
 

Вот ее кусочек:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
; Vector Table Mapped to Address 0 at Reset
 
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
 
__Vectors       DCD     __initial_sp              ; Top of Stack
                DCD     Reset_Handler             ; Reset Handler
                DCD     NMI_Handler               ; NMI Handler
                DCD     HardFault_Handler         ; Hard Fault Handler
                DCD     MemManage_Handler         ; MPU Fault Handler
                DCD     BusFault_Handler          ; Bus Fault Handler
                DCD     UsageFault_Handler        ; Usage Fault Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     SVC_Handler               ; SVCall Handler
                DCD     DebugMon_Handler          ; Debug Monitor Handler
                DCD     0                         ; Reserved
                DCD     PendSV_Handler            ; PendSV Handler
                DCD     SysTick_Handler           ; SysTick Handler

 

Имя есть. Осталось организовать прерывание:
 

1
2
3
4
5
6
//SysTick Interrupt
void SysTick_Handler(void)
{
// Тут будем делать нечто полезное. Например, ставить бит B.05
GPIOB->BSRR = GPIO_BSRR_BS5;	// Set bit
}

 

А сбрасывать его будет в главном цикле. В результате мы получим иголки с частотой 1Кгц которые хорошо видно осциллографом :)
 

Вот так выглядит полный код примера:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "stm32f10x.h"
#define F_CPU 		72000000UL
#define TimerTick	F_CPU/1000-1
 
void Delay(uint32_t Val);
 
//SysTick Interrupt
void SysTick_Handler(void)
{
GPIOB->BSRR = GPIO_BSRR_BS5;		// Set bit
}
 
int main(void)
{
SystemInit();
 
RCC->APB2ENR 	|= RCC_APB2ENR_IOPBEN;
// Config CRL
GPIOB->CRL	&= ~GPIO_CRL_CNF5;	// Clear CNF bit 5. Mode 00 - Push-Pull 
GPIOB->CRL 	|= GPIO_CRL_MODE5_0;	// Set bit MODE0 for pin 5. Mode 01 = Max Speed 10MHz
 
 
SysTick->LOAD=TimerTick;
SysTick->VAL=TimerTick;
SysTick->CTRL=	SysTick_CTRL_CLKSOURCE_Msk |
                SysTick_CTRL_TICKINT_Msk   |
                SysTick_CTRL_ENABLE_Msk;;
 while(1)
	{
	GPIOB->BSRR = GPIO_BSRR_BR5;		// Clear bit
	}
}

 

Собрал пример на PB II — ARM. Накинул джампер на B05. Зацепил щуп осциллографа, прошил контроллер…

 

И осциллограф радостно показал игольчатую картину на экране:

 

▌Функции CMSIS
У Таймера, раз он часть ядра, есть функция конфигурации в CMSIS. Описана она в core_cm3.h и выглядит так:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            	/* Reload value impossible */
 
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      	/* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  	/* set Priority for Cortex-M0 System Interrupts */
 
  SysTick->VAL   = 0;                                          	/* Load the SysTick Counter Value */
  SysTick->CTRL  = 	SysTick_CTRL_CLKSOURCE_Msk | 
                   	SysTick_CTRL_TICKINT_Msk   | 
                   	SysTick_CTRL_ENABLE_Msk; 		/* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                   /* Function successful */
}

То же самое, что и мы сделали вручную. Только есть проверка на корректность значения и обрезка лишних битов. Возвращает 1 если данные некорректные. Ну и еще там приоритет ставится если ядро М0 (это кстати хз зачем? У M0 же должна быть своя версия CMSIS?)
 

Так что таймер можно загрузить и следующей фигней:

1
SysTick_Config(TimerTick);

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

13 thoughts on “ARM. Учебный Курс. SysTick — Системный таймер”

  1. DI, можешь подробнее рассказать, как и с чем едят SysTick Calibration Value Register? Я так и не догнал что и как им калибруется, какое внешнее оборудование нужно (если нужно). Процедура калибровки генератора RTC расписана в мануале досконально, по сабжу есть только маловразумительная фраза

    10.1.2 SysTick calibration value register
    The SysTick calibration value is fixed to 18750, which gives a reference time base of 1 ms
    with the SysTick clock set to 18.75 MHz (HCLK/8, with HCLK set to 150 MHz).

    и всё

    1. Да фиг знает. Я никогда на калибровку не заморачивался. Может это просто закладка на возможность наличия некого эталонного генератора? Иначе я вообще не понимаю как и относительно чего он должен калиброваться.

    2. Похоже, что калибруется он в расчете на максимальную частоту SYSCLK. И calibration value указывается таким, чтобы записав его в LOAD получить интервал в 1 мс. Например в моем STM32F103 макс. частота = 72 МГц, и calibration value = 9000 (72’000’000 / 8 / 9’000 = 1000)

        1. Я могу ошибаться, но это самый регистр предусмотрен для того, чтобы можно было точно тикать с заданным интервалом. Дело в том, что там хранится число ээээ… колебаний для интервала в 10 ms, то есть как бы это калибровочное значение показывающее, сколько колебаний будет умещаться в заданном интервале времени. Ну, для масштабирования. Эммм.. В общем с объяснениями у меня всегда было туго, но попробую продолжить. Помните, как узнать диаметр намоточного провода, имея только миллиметровую линейку? Виток к витку мотаем, например, 10 мм проволоки прям по шкале линейки. Потом считаем витки и делим на 10 и получаем диаметр одной проволочки… То есть регистр, например, содержит число 65535. Делим на 10 ms и получаем, что в 1 ms помещается 6553.5 колебания. Для 1 ms это не имеет значения, а если мы считаем 1 секунду, то в одной секунде помещается ровно 6553500 колебаний… То есть не 6553000, а еще плюс пицот. (прошу не бить за -1 сразу сообразить трудно, чо там вычитать еще). Ну, вдруг у меня пришла в голову гениальная мысль организовать часики на этом тикании. Вот получается, что за сутки будет ровно 566 222 400 000 колебания. Если бы я считал, что 1 ms — это 65530 колебаний, то в сутках было бы 566 179 200 000 колебаний. То есть такие часы врали бы на 659 миллисекунд в сутки… Не?

          1. А 31 бит указывает, что значение генератора имеет референсное значение… Видимо, производитель там что-то указывает…. Хотя я над этим долго морщил лоб… Но почти не понял, что за референсное значение…

  2. Раскурил, кажется… У этого тика может быть два источника тактирования внутренний и внешний и этот бит, установлены производите лет показывает, что можно пользоваться внешним источником тактирования. Внутренний же HCLK…

  3. Я сегодня наткнулся на этот курс. возник вопрос
    void Delay(uint32_t Val); — получается ненужная строка как и SystemInit();
    Набросал пример в 5-том Кейле на 100RB ошибок нет, но в Дебаге на SysTick_Handler так и не выходит (хотя Current уменьшается от максимума до нуля — то есть таймер вроде как работает)
    Вопрос — так и должно быть?
    И вдогонку как можно использовать этот таймер во избежании дребезга контактов у кнопки

    1. Если тебе нужен просто Delay, то попробуй так. Во время ковыряний пришел к выводу, что этот таймер тупее, чем многие думают. Его даже инициализировать не нужно, работает сразу при подаче питания и прямо через делитель на 8 тактируется от частоты процессора.
      Не нужно никаких обработчиков прерываний или чего бы то не было еще.
      Тестил на 32f100RB.
      //Примечание: в SysTick->LOAD может быть записано максимум 2^24. Соответственно 5592405 мкс максимальная задержка.
      //На 24МГц минимум 5 мкс задержка.
      void Delay_us (uint32_t usec) //
      {
      usec*=3; // 1 мкс на 3 такта.
      SysTick->LOAD = usec; // Загрузка значения
      /*SysTick->VAL = usec;*/ // Обнуляем таймеры и флаги, хотя и так работает.
      SysTick->CTRL |= 0x00000001;
      while(!(SysTick->CTRL & 0x00010000)){/*Хатико*/}
      SysTick->CTRL &= 0x00010006;
      }

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

Ваш e-mail не будет опубликован.

Перед отправкой формы:
Human test by Not Captcha