середа, 2 березня 2016 р.

Таймер SysTick

Тактування SysTick. Клікабельно.
Про цей таймер не раз писав раніше, та й інші автори про нього не забували. Однак в цьому блозі про нього завжди говорилося в глибині великих простирадл тексту, вирішив винести в окремий пост.

SysTick timer 

SysTick timer -- простий 24-бітний таймер, частина ядра Cortex M, тому присутній у відповідних мікроконтролерах всіх виробників. Належить до підсистеми контролера переривань, NVIC.


Ключовим його елементом є 24-бітний лічильник, який зменшується ("декрементивний") та вміє автоперезавантажуватися (що це значить -- трішки нижче). Може тактуватися або системною тактовою частотою SYSCLK, або 1/8 від неї, SYSCLK/8. Регістри для роботи з цим таймером наступні:
  • SysTick Current Value Register, VAL -- поточне значення лічильника. Коли досягає нуля, генерується переривання. 
  • SysTick Reload Value Register, LOAD -- значення, яке завантажуватиметься в Current Value Register після того, як він занулився (це і є автоперезавантаженням).
  • SysTick Control and Status Register, CTRL -- регістр керування та статусу. Використовуються наступні біти: COUNTFLAG (16) -- 1, якщо лічильник досягав нуля з того часу, як його останній раз читали, очищається в нуль, коли його читають або коли очищається лічильник VAL; CLKSOURCE (2) -- зовнішнє (0) або внутрішнє (1) джерело тактового сигналу. TICKINT (1) -- якщо 1, дозволено генерацію переривання, ENABLE (0) -- якщо 1, таймер "дозволено" і він відраховує "тіки".
  • SysTick Calibration Value Register, CALIB -- регістр калібрування, може використовуватися, щоб добитися однакових інтервалів між перериваннями, згенерованими SysTick на різних мікроконтролерах сімейства.
Карта регістрів та їх значення при ввімкненні, з офіційної документації. Базова адреса -- 0xE000E000.
(c) STMicroelectronics
 CMSIS має структуру для роботи із ним, SysTick, описану в cmsis.h (лише фрагменти, що стосуються таймера):

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;

#define SCS_BASE (0xE000E000)                              
/*!< System Control Space Base Address */
#define SysTick_BASE (CS_BASE +  0x0010)                      
/*!< SysTick Base Address  

#define SysTick ((SysTick_Type *)       SysTick_BASE)     
/*!< SysTick configuration struct      */

Цей таймер -- хороший приклад того, як організована CMSIS. Регістри таймера відображаються один за одним у пам'ять, за базовою адресою, яка розраховується як сума базової адреси системної області (див. вище карту пам'яті) та зміщення у ній області регістрів SysTick.
Розташування регістрів у пам'яті та робота із ними засобами С.
(c) "The Definitive Guide  to the ARM® Cortex-M3", 2nd edition.

Крім того, для зручності, CMSIS надає функцію SysTick_Config(uint32_t ticks), яка ініціалізує таймер, задає частоту генерації переривань (знаючи системну частоту, можна її привести, хоча і не дуже точно, до одиниць реального часу) та запускає відлік:

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            
  /* Перевіряємо, чи значення не завелике -- таймер 24-бітний
  * SysTick_LOAD_RELOAD_Msk -- 24 двійкові одинички */

  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      
  /* Ініціалізуємо регістр перезавантаження */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  
  /* Встановлюємо пріоритет переривання, засобами NVIC */
  SysTick->VAL   = 0;                                          
  /* Задаємо початкове значення лічильника */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk;                    
  /* Дозволяємо переривання та запускаємо таймер */
  return (0);                                                  
}

Зрозуміло, що для досягнення бажаної затримки (в секундах), слід встановити:

 ticks = системна частота (в герцах) * час (в секундах)

Тільки не забуваємо, що таймер лише 24-бітний, тому, наприклад, для STM32VLDISCOVERY, з частотою 24МГц, більше 0.7 секунди (8*0.7, якщо тактувати на 1/8 частоти) відрахувати він не зможе. Це обмеження легко обійти програмно -- використати свій лічильник, який змінюватиметься в процедурі обробки переривання.

Головне призначення таймера -- побудова диспетчера операційної системи, але іноді зручно мати аж 24-бітний таймер.

Обробник переривання

Може виглядати якось так:

volatile uint32_t curTicks;

void SysTick_Handler(void) {
    curTicks++;       /* Зроблено ще один тік */
}

Функція із іменем SysTick_Handler замінить функцію-обробник переривання за замовчуванням, оголошений як слабкий (weak) символ -- вони заміняються, якщо символ з таким же іменем трапиться в програмі.. Для проектів на C++ пам'ятаємо обгорнути оголошення SysTick_Handler в extern "C", щоб name mangling його не зіпсувало, інакше все буде компілюватися та лінкуватися успішно, ОДНАК обробник не викликатиметься -- у нього буде інше ім'я зв'язування і дефолтне weak-оголошення не заміститься:

extern "C"{
void SysTick_Handler(void) {
    // Тіло функції
}
}

 Специфікатор volatile змушує компілятор читати змінну із пам'яті кожного разу, коли до неї відбувається звертання -- натяк йому, що вона може змінюватися "самовільно", в даному випадку -- із обробника переривання.

Калібрування

Ядро ARM Cortex-M може підтримувати можливість калібрування відліку часу. Регістр  CALIB (що належить NVIC і стосується SysTick) містить поля SKEW та TENMS:

Регістр NVIC, що стосуються SysTick. Взято із "The Definitive Guide  to the ARM® Cortex-M3", 2nd edition.
TENMS містить кількість тіків, що відповідають 10 мілісекунд -- для відліку цього інтервалу слід просто в регістр RELOAD завантажити TENMS. Однак, це значення коректне лише тоді, коли SKEW == 0. Означення цього поля хитре -- воно рівне 1, якщо "Calibration value is not exactly 10 ms", вміст  TENMS "не точно відповідає 10 мс". На жаль, для нашого контролера, воно містить 9000, що навіть не схоже на правильне 24 мкс/такт * 1000 * 10= 240000. (Не залишає мене підозра, що я щось плутаю, але навіть експериментально воно не працює як слід). Тому ця можливість нам не доступна. 

Код для тестування (дужки важливі --- & має менший пріоритет, ніж !=):

printf("Noref: %lu, Skew: %lu, TENMS: %lu\n",
 (SysTick->CALIB & SysTick_CALIB_NOREF) != 1,
 (SysTick->CALIB & SysTick_CALIB_SKEW) != 1,
 SysTick->CALIB & SysTick_CALIB_TENMS
);

Результат для STM32VLDiscovery:

Noref: 0, Skew: 1, TENMS: 9000

Подробиці див. згаданий "The Definitive Guide  to the ARM® Cortex-M3" та офіційну документацію на мікроконтролери.

Детальніше, традиційно, див. літературу:

Немає коментарів:

Дописати коментар