STM Урок 167. CMSIS. STM32F1. SysTick. Прерывания
До сих пор мы, используя библиотеку CMSIS, использовали задержки исполнения кода с помощью пустых тактов, тем самым нам тяжело было даже примерно подсчитать заранее, сколько циклов потребуется для организации задержки на определённое время. Так как в прошлом занятии мы смогли добиться тактирования контроллера строго определённой частотой импульсов, то теперь нам эту задачу решить будет намного легче. А поможет нам в этом системный таймер SysTick, который присутствует во всех контроллерах STM32 и представляет собой простейший программируемый счётчик с обратным отсчётом до нуля. Как только данный счётчик досчитывает до нуля, генерируется прерывание, если оно конечно разрешено, а также устанавливается определённый флаг, который также можно отследить, тогда уже прерывания будет использовать необязательно.
Мы же в нашем проекте будем использовать именно прерывания, заодно и научимся ими пользоваться.
Разрабатывая свои проекты с использованием библиотек LL и HAL, мы постоянно пользовались системным таймером SysTick, сами того не замечая, так как его настройка производилась автоматически, а использование было в коде задержек. Теперь нам надо будет организовать всё это самим, поэтому потребуется некоторое знание регистров данного таймера и их битов.
Регистров у SysTick немного, причём управляющий регистр STK_CTRL (SysTick control and status register) мы уже подробно изучили в уроке 144 по режимам пониженного энергопотребления.
Откроем документацию к нашему ядру Programming Manual for STM32F10xxx/20xxx/21xxx/L1xxxx и откроем там описание регистров SysTick.
Следующий регистр — регистр значения, от которого таймер будет считать в обратном направлении. Данное число может быть в пределах от 1 до 0x00FFFFFF, так как наш таймер является 24-битным
Есть ещё один нюанс. Число надо заносить, уменьшенное на 1. Например, чтобы отсчитать 100 циклов, заносить в регистр нужно число 99.
Следующий регистр — регистр текущего значения системного таймера
В данном регистре находится то значение, до которого досчитал системный таймер, а, так как все биты регистра работают также и на запись, то мы можем заносить сюда и своё значение. Например, чтобы отсчёт нескольких заданных интервалов был точнее, то перед началом отсчёта желательно сюда занести значение, равное значению в STK_LOAD.
Следующий регистр — калибровочный

Биты данного регистра несут исключительно информационную нагрузку и доступны только для чтения.
Кратко об их назначении
NOREF (NOREF flag): бит эталонной частоты
0 — эталонная частота присутствует
1 — эталонная частота отсутствует.
SKEW (SKEW flag): является ли значение в битовом поле TENMS точным
0 — значение в битовом поле TENMS точное
1 — значение в битовом поле TENMS либо неточное, либо не задано.
TENMS[23:0] (Calibration value): Калибровочное значение.
Указывает значение калибровки, когда счетчик SysTick работает на частоте HCLK max / 8 в качестве внешних часов. Значение зависит от продукта. Когда HCLK запрограммирован на максимальной частоте, период SysTick равен 1 мс.
В принципе, с системным таймером мы немного разобрались.
Что касается прерываний, то мы с ними работаем постоянно. Единственное то, что их обработчики уже были сгенерированы и мы их сами не добавляли. Поэтому мы сегодня должны будем понять, как добавить обработчик того или иного прерывания, вернее даже не как добавить, а какое имя его функции присвоить, чтобы мы в него при возникновении прерывания точно попали и где это имя искать. Всё это мы узнаем на этапе составления нашего проекта, к которому мы сейчас же и приступим.
Проект был сделан из проекта прошлого урока с именем CMSIS_RCC и новое имя ему было присвоено CMSIS_SYSTICK. Как делать проект из другого, мы теперь хорошо знаем.
Откроем в проекте файл main.c и добавим ещё одну функцию инициализации системного таймера
|
1 2 3 4 5 |
//---------------------------------------------------------- void SysTick_Init(void) { } //---------------------------------------------------------- |
Объявим макрос со значением системной частоты тактирования
|
1 2 3 4 |
#define LED10_OFF() CLEAR_BIT(GPIOB->ODR,GPIO_ODR_ODR11) //---------------------------------------------------------- #define SYSCLOCK 72000000U //---------------------------------------------------------- |
Зададим период с тем расчётом, чтобы наш системный таймер отсчитывал интервалы в 1 милисекунду
|
1 2 3 |
void SysTick_Init(void) { MODIFY_REG(SysTick->LOAD,SysTick_LOAD_RELOAD_Msk,SYSCLOCK / 1000 - 1); |
Сбросим счётчик
|
1 2 |
MODIFY_REG(SysTick->LOAD,SysTick_LOAD_RELOAD_Msk,SYSCLOCK / 1000 - 1); CLEAR_BIT(SysTick->VAL, SysTick_VAL_CURRENT_Msk); |
Настроим управляющий регистр, настроив в качестве источника неразделённую на 8 (оригинальную) системную частоту, разрешив прерывания и включив собственно счётчик
|
1 2 |
CLEAR_BIT(SysTick->VAL, SysTick_VAL_CURRENT_Msk); SET_BIT(SysTick->CTRL, SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk); |
Вот и вся настройка системного таймера. Не забываем эту функцию вызвать в main()
|
1 2 |
SetSysClockTo72(); SysTick_Init(); |
Теперь осталось дело за малым — научиться этим таймером пользоваться. Настроен от так, что прерывания генерируются с периодом в 1 мс.
Если мы сейчас соберём проект и прошьём его, то у нас перестанет бегать светодиод, так как мы обязаны добавить функцию для обработки прерывания, иначе будет её вызов, а самой функции нет, и программа перестанет работать.
Имена обработчиков различных видов прерываний описаны в файле startup_stm32f10x_md.s. Это ассемблерный файл, с которого стартует наша программа и в нём есть секция RESET, являющаяся областью памяти только для чтения в ней и находятся адреса векторов прерываний, которые и будут искаться по имени функций-обработчиков этих прерываний. Найдём там строчку с именем функции-обработчика прерываний от SysTick
DCD SysTick_Handler ; SysTick Handler
Вот это и есть имя нашего обработчика.
Добавим его в main.c
|
1 2 3 4 5 |
//---------------------------------------------------------- void SysTick_Handler(void) { } //---------------------------------------------------------- |
Неважно, что данный обработчик пока пустотелый, и если мы теперь соберём и прошьём наш код, то программа опять будет работать.
Добавим глобальную переменную для пользовательского счётчика, значение которой будет декрементироваться каждый тик нашего системного таймера. Нужно это для подсчёта задержек
|
1 2 |
__IO uint32_t tmpreg; __IO uint32_t SysTick_CNT = 0; |
Продекрементируем значение данной переменной в обработчике
|
1 2 3 |
void SysTick_Handler(void) { if(SysTick_CNT > 0) SysTick_CNT--; |
Далее добавим функцию для задержки в милисекундах
|
1 2 3 4 5 |
//---------------------------------------------------------- void delay_ms(uint32_t ms) { } //----------------------------------------------------------- |
Проинициализируем счётчик в данной функции
|
1 2 3 |
void delay_ms(uint32_t ms) { MODIFY_REG(SysTick->VAL,SysTick_VAL_CURRENT_Msk,SYSCLOCK / 1000 - 1); |
Также проинициализируем пользовательский счётчик обратного отсчёта
|
1 2 |
MODIFY_REG(SysTick->VAL,SysTick_VAL_CURRENT_Msk,SYSCLOCK / 1000 - 1); SysTick_CNT = ms; |
И теперь ждём, когда наш счётчик досчитает до нуля
|
1 2 |
SysTick_CNT = ms; while(SysTick_CNT) {} |
Теперь осталось нам лишь заменить наши неопределённые задержки в бесконечном цикле функции main() на новые — осознанные
LED10_OFF(); LED1_ON(); delay_ms(1000);
LED1_OFF(); LED2_ON(); delay_ms(1000);
LED2_OFF(); LED3_ON(); delay_ms(1000);
LED3_OFF(); LED4_ON(); delay_ms(1000);
LED4_OFF(); LED5_ON(); delay_ms(1000);
LED5_OFF(); LED6_ON(); delay_ms(1000);
LED6_OFF(); LED7_ON(); delay_ms(1000);
LED7_OFF(); LED8_ON(); delay_ms(1000);
LED8_OFF(); LED9_ON(); delay_ms(1000);
LED9_OFF(); LED10_ON(); delay_ms(1000);
Теперь светодиоды будут бежать с периодом ровно в 1 секунду

Итак, на данном уроке мы научились использовать системный таймер SysTick, благодаря которому мы теперь можем отсчитывать строго определённые интервалы, а также научились добавлять функции для обработки различных прерываний.
Всем спасибо за внимание!
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)





В коде что вы прилоожили есть несколько вайлов (к примеру while(READ_BIT(RCC->CR, RCC_CR_HSIRDY == RESET)) {} ) для чего они нужны, если и без них программа работает коректно?