До сих пор мы, используя библиотеку 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, благодаря которому мы теперь можем отсчитывать строго определённые интервалы, а также научились добавлять функции для обработки различных прерываний.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Смотреть ВИДЕОУРОК (нажмите на картинку)
В коде что вы прилоожили есть несколько вайлов (к примеру while(READ_BIT(RCC->CR, RCC_CR_HSIRDY == RESET)) {} ) для чего они нужны, если и без них программа работает коректно?