Сам таймер 24 разрядный. И тикает вниз от предзагруженного значения до нуля, после чего перезагружается вновь и генерирует прерывание. Источником тактирования является системная тактовая частота SYSCLK, либо та же частота, но поделенная на 8 – SYSCLK/8. Управляется он четырьмя регистрами: 0xE000E010 SYST_CSR RW SysTick control and status register 0xE000E014 SYSCT_RVR RW SysTick reload value register 0xE000E018 SYSCT_CVR RW SysTick current value register 0xE000E01C SYSCT_CALIB RO SysTick calibration value register *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: *------------------ File Begin -------------------* 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 */ *------------------ File End -------------------* Нам там нужны имена битов. Вот они: SysTick_CTRL_CLKSOURCE_Msk, SysTick_CTRL_TICKINT_Msk, SysTick_CTRL_ENABLE_Msk. Конфигурация таймера на тик в 1ms, таким образом, будет выглядеть примерно так: *------------------ File Begin -------------------* #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; *------------------ File End -------------------* *** Обработчик прерывания SysTick *** Где взять имя обработчика? Да из таблицы прерываний. Поглядеть, если кто не помнит, можно в STM32F10x.s Вот ее кусочек: *------------------ File Begin -------------------* ; 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 *------------------ File End -------------------* Имя есть. Осталось организовать прерывание: *------------------ File Begin -------------------* //SysTick Interrupt void SysTick_Handler(void) { // Тут будем делать нечто полезное. Например, ставить бит B.05 GPIOB->BSRR = GPIO_BSRR_BS5; // Set bit } *------------------ File End -------------------* А сбрасывать его будет в главном цикле. В результате мы получим иголки с частотой 1Кгц которые хорошо видно осциллографом :) Вот так выглядит полный код примера: *------------------ File Begin -------------------* #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 } } *------------------ File End -------------------* *** Функции CMSIS *** У Таймера, раз он часть ядра, есть функция конфигурации в CMSIS. Описана она в core_cm3.h и выглядит так: *------------------ File Begin -------------------* 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 */ } *------------------ File End -------------------* То же самое, что и мы сделали вручную. Только есть проверка на корректность значения и обрезка лишних битов. Возвращает 1 если данные некорректные. Ну и еще там приоритет ставится если ядро М0 (это кстати хз зачем? У M0 же должна быть своя версия CMSIS?) Так что таймер можно загрузить и следующей фигней: SysTick_Config(TimerTick); Для LPC все совершенно аналогично. До последней буковки можно сказать. Т.к. это фича не STM32, а ядра и описана в CMSIS. Т.е. едина для всех. =============================================================================== Для конфигурации таймера в файле core_cm3.h есть функция SysTick_Config(uint32_t ticks), в качестве аргумента которой передается коэффициент деления тактовой частоты для получения необходимой временной задержки. ~~~~~~~~~~~~~~~~~~~~ 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 */ } ~~~~~~~~~~~~~~~~~~~~ Это значение заносится в регистр перезагрузки. В самом начале выполняется проверка на то, что данная величина не выходит за максимальный предел, поскольку счетчик 24-разрядный, а передаваемый аргумент функции является 32-разрядным числом и необходимо об этом помнить. Далее в функции конфигурации SysTick_Config() задается уровень приоритета прерывания, обнуляется счетный регистр, разрешается генерация прерывания, задается источник тактирования и разрешается работа таймера – запускается счет. По умолчанию тактовая частота этого таймера будет равна системной тактовой частоте SYSCLK. Если же нужно задать частоту тактирования таймера как SYSCLK/8, то уже после этой функции инициализации можно вызвать функцию SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8), которая находится в файле misc.c. Для этого к проекту необходимо подключить файлы misc.c и misc.h. В данных примерах таймер настраивается на генерацию прерывания через “базовый” промежуток времени, равный 1 миллисекунде. Затем, уже с помощью программного цикла формируется задержка, равная 1 секунде. Через каждую секунду происходит попеременное зажигание/гашение светодиодов платы, здесь я не стал особо мудрить, поскольку основной задачей была настройка системного таймера для генерации прерываний через определенный интервал. Для задания длительности между двумя последующими прерываниями от таймера, в регистр перезагрузки необходимо записать значение, которое рассчитывается по простой формуле: * Reload Value = SysTick Counter Clock (Hz) x Desired Time base (s), где * Reload Value – величина перезагружаемого значения для таймера * SysTick Counter Clock (Hz) – тактовая частота таймера * Desired Time base (s) – желаемое время формируемой временной задержки между прерываниями Системная тактовая частота задается при начальной инициализации микроконтроллера. На плате STM32VL-DISCOVERY микроконтроллер конфигурируется на работу с внешним кварцем частотой 8 МГц, при этом его системная тактовая частота после начальной инициализации будет равна 24 МГц. На плате STM32L-DISCOVERY внешнего кварца нет и после начальной инициализации системная тактовая частота формируется внутренним источником MSI и равна 2,097 МГц. Расчет перезагружаемых значений приведен ниже, в комментариях примеров программ для этих плат. Код программы для платы STM32VL-DISCOVERY (контроллер STM32F100) ~~~~~~~~~~~~~~~~~~~~ #include "stm32f10x.h" static __IO uint32_t TimingDelay; void Delay_ms(__IO uint32_t nTime); GPIO_InitTypeDef GPIO_InitStruct; int main() { /*Вызов функции конфигурации системного таймера SysTick. Эта функция находится в файле core_cm3.h и: --Задает источник тактирования системного таймера (по умолчанию это SYSCLK = 24 МГц, другой возможный вариант - SysTick тактируется от SYSCLK/8); --Задает уровень приоритета прерывания; --Сбрасывает флаг ожидания прерывания, если он выставлен; --Заносит в нужный регистр перезагружаемое значение для декрементирующего счетчика, которое вычисляется по формуле: Reload Value = SysTick Counter Clock (Hz) x Desired Time base (s), для базовой задержки длительностью 1 мс получим величину Reload Value = 24 000 000 Гц х 0,001 с = 24 000 (Необходимо самостоятельно посчитать эту величину и вставить в качестве параметра при вызове функции); --Обнуляет счетчик --Запускает счет системного таймера*/ SysTick_Config(24000); //Включаем тактирование порта GPIOC RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //Конфигурируем выводы, к которым подключены светодиоды платы GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; //Выбираем выводы PC8, PC9 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //Максимальная скорость работы GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //Выход Push-Pull GPIO_Init(GPIOC, &GPIO_InitStruct); //Заносим заданные настройки в регистры порта while(1) { GPIO_ResetBits(GPIOC, GPIO_Pin_9); //Гасим зеленый LED GPIO_SetBits(GPIOC, GPIO_Pin_8); //Зажигаем синий LED Delay_ms(1000); //Временная задержка на 1 с GPIO_ResetBits(GPIOC, GPIO_Pin_8); //Гасим синий LED GPIO_SetBits(GPIOC, GPIO_Pin_9); //Зажигаем зеленый LED Delay_ms(1000); //Временная задержка на 1 с } } //Функция временной задержки void Delay_ms(__IO uint32_t nTime) { TimingDelay = nTime; while(TimingDelay != 0); } void TimingDelay_Decrement(void) { if (TimingDelay != 0x00) { TimingDelay--; } } //Обработчик прерывания системного таймера void SysTick_Handler(void) { TimingDelay_Decrement(); } ~~~~~~~~~~~~~~~~~~~~ Код программы для платы STM32L-DISCOVERY (контроллер STM32L152) ~~~~~~~~~~~~~~~~~~~~ #include "stm32l1xx.h" static __IO uint32_t TimingDelay; void Delay_ms(__IO uint32_t nTime); GPIO_InitTypeDef GPIO_InitStruct; int main() { /*Вызов функции конфигурации системного таймера SysTick. Эта функция находится в файле core_cm3.h и: --Задает источник тактирования системного таймера (по умолчанию это SYSCLK = 2.097 МГц, другой возможный вариант - SysTick тактируется от SYSCLK/8); --Задает уровень приоритета прерывания; --Сбрасывает флаг ожидания прерывания, если он выставлен; --Заносит в нужный регистр перезагружаемое значение для декрементирующего счетчика, которое вычисляется по формуле: Reload Value = SysTick Counter Clock (Hz) x Desired Time base (s), для базовой задержки длительностью 1 мс получим величину Reload Value = 2 097 000 Гц х 0,001 с = 2097 (Необходимо самостоятельно посчитать эту величину и вставить в качестве параметра при вызове функции); --Обнуляет счетчик --Запускает счет системного таймера*/ SysTick_Config(2097); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); //Включаем тактирование GPIOB //Конфигурируем выводы, к которым подключены светодиоды платы GPIO_InitStruct.GPIO_Pin = (GPIO_Pin_6 | GPIO_Pin_7); //Выбираем выводы PB6, PB7 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //Выводы порта в режиме выхода GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //Выход Push-Pull GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; //Выход без подтягивающих резисторов GPIO_InitStruct.GPIO_Speed = GPIO_Speed_40MHz; //Скорость порта максимальная GPIO_Init(GPIOB, &GPIO_InitStruct); //Заданные настройки сохраняем в регистрах GPIOB while(1) { GPIO_ResetBits(GPIOB, GPIO_Pin_7); //Гасим зеленый LED GPIO_SetBits(GPIOB, GPIO_Pin_6); //Зажигаем синий LED Delay_ms(1000); //Временная задержка на 1 с GPIO_ResetBits(GPIOB, GPIO_Pin_6); //Гасим синий LED GPIO_SetBits(GPIOB, GPIO_Pin_7); //Зажигаем зеленый LED Delay_ms(1000); //Временная задержка на 1 с } } //Функция временной задержки void Delay_ms(__IO uint32_t nTime) { TimingDelay = nTime; while(TimingDelay != 0); } void TimingDelay_Decrement(void) { if (TimingDelay != 0x00) { TimingDelay--; } } //Обработчик прерывания системного таймера void SysTick_Handler(void) { TimingDelay_Decrement(); } ~~~~~~~~~~~~~~~~~~~~ =============================================================================== *** Литература: *** Cortex-M3 Devices Generic User Guide Cortex-M3 Peripherals > System timer, SysTick 4.4. System timer, SysTick http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Babieigh.html ARM. Учебный Курс. SysTick — Системный таймер http://easyelectronics.ru/arm-uchebnyj-kurs-systick-sistemnyj-tajmer.html STM32. Системный таймер SysTick. http://chipspace.ru/stm32-systick/