В предыдущей части нашего урока мы познакомились с типами таймеров в STM32F1, а также с некоторыми регистрами таймеров, которые мы теперь будем использовать на практике в нашем проекте.
Теперь, накопив о таймерах столько знаний, можно перейти и к созданию проекта.
Проект создадим из проекта урока 146 с именем LL_BLINK01 и присвоим ему, соответственно, имя LL_TIM2.
Откроем проект в Cube MX и включим для начала таймер TIM2
Настроим параметры нашего таймера таким же образом, как мы делали это в уроке 5 по HAL на регулярное срабатывание через 100 милисекунд
Включим прерывания от таймера
Идём в раздел Project Manager, слева затем выбрав пункт Advanced Settings, и в настройках TIM2 также выберем вместо HAL библиотеку LL
Сгенерируем проект, откроем его в KEIL, настроим программатор на автоматическую перезагрузку контроллера и выставим уровень оптимизации в 0.
Перейдём в файл main.c и добавим функцию-обработчик события таймера TIM2
1 2 3 4 |
/* USER CODE BEGIN 4 */ void TIM2_Callback(void) { } |
Перейдём в файл stm32f1xx_it.c и создадим там прототип нашей функции
1 2 |
/* USER CODE BEGIN PFP */ void TIM2_Callback(void); |
Вызовем нашу функцию в обработчике
1 2 3 4 5 6 7 |
void TIM2_IRQHandler(void) { /* USER CODE BEGIN TIM2_IRQn 0 */ /* USER CODE END TIM2_IRQn 0 */ /* USER CODE BEGIN TIM2_IRQn 1 */ TIM2_Callback(); |
Вернёмся в файл main.c, оставим пока в покое нашу функцию, мы к ней вернёмся позже, а посмотрим, как инициализируется наш таймер, думаю, это многим будет интересно.
Для этого идём в функцию MX_TIM2_Init, пропустим включение тактирования периферии, до этого мы доберёмся при изучении библиотеки CMSIS, также пропустим включение глобальных прерываний и установку приоритетов, до них мы когда-то тоже дойдём.
Дальше мы видим, что у нас здесь происходит поначалу инициализация структуры для таймера
1 2 3 4 5 |
/* USER CODE END TIM2_Init 1 */ TIM_InitStruct.Prescaler = 199; TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; TIM_InitStruct.Autoreload = 35999; TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; |
Дальше вызывается функция LL_TIM_Init. Перейдём в её тело и посмотрим, что там происходит.
Сначала проверяется наличие заполненных параметров, а затем читается регистр CR1 таймера
1 |
tmpcr1 = LL_TIM_ReadReg(TIMx, CR1); |
Затем в данном регистре устанавливаются или сбрасываются биты DIR и CMS в соответствии с установками поля CounterMode структуры таймера (в нашем случае будут нули, то есть считаем вверх без всякого выравнивания)
1 |
MODIFY_REG(tmpcr1, (TIM_CR1_DIR | TIM_CR1_CMS), TIM_InitStruct->CounterMode); |
Затем устанавливается делитель в том же регистре, если он задействован
1 |
MODIFY_REG(tmpcr1, TIM_CR1_CKD, TIM_InitStruct->ClockDivision); |
Затем набранное значение записывается в настоящий регистр, так как до этого писали в виртуальный tmpcr1
1 |
LL_TIM_WriteReg(TIMx, CR1, tmpcr1); |
Затем из поля структуры Autoreload записываются данные в регистр ARR
1 |
LL_TIM_SetAutoReload(TIMx, TIM_InitStruct->Autoreload); |
Аналогично устанавливается предделитель в регистр PSC
1 |
LL_TIM_SetPrescaler(TIMx, TIM_InitStruct->Prescaler); |
Следующее условие IS_TIM_REPETITION_COUNTER_INSTANCE(TIMx) пропускается, так как у нас не таймер 1, а дальше устанавливается бит UG в регистре EGR, так как мы выбрали в Cube обработку событий Update Event
1 |
LL_TIM_GenerateEvent_UPDATE(TIMx); |
Возвращаемся назад на уровень выше в функцию MX_TIM2_Init, где далее очищается бит ARPE в регистре CR1
1 |
LL_TIM_DisableARRPreload(TIM2); |
Далее устанавливается источник тактирования таймера в регистре SMCR путём установки или сброса битов ECE и SMS согласно данным макроса во втором входящем аргументе, в нашем случае LL_TIM_CLOCKSOURCE_INTERNAL, судя по установкам которого мы данные биты сбрасываем
1 |
LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); |
Далее в регистре CR2 устанавливаются или сбрасываются биты поля MMS также согласно установкам макроса, в нашем случае устанавливается бит MMS1, что означает выбор режима Update
1 |
LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_UPDATE); |
И в заключении настройки таймера отключается режим MASTER/SLAVE сбросом бита MSM в регистре SMCR
1 |
LL_TIM_DisableMasterSlaveMode(TIM2); |
Переходим в функцию main, где сначала включаем прерывания путём установки бита UIE в регистре DIER
1 2 |
LED6_OFF();LED7_OFF();LED8_OFF();LED9_OFF();LED10_OFF(); LL_TIM_EnableIT_UPDATE(TIM2); |
И далее непосредственно запускаем таймер, то есть заставляем его считать тики посредством установки бита CEN в регистре CR1
1 2 |
LL_TIM_EnableIT_UPDATE(TIM2); LL_TIM_EnableCounter(TIM2); |
Я специально не стал удалять код из бесконечного цикла для того, чтобы увидеть то, что если мы сейчас соберём код и прошьём контроллер, то у нас данный код выполняться не будет и светодиоды у нас теперь не бегут, даже если мы нажмём кнопку
А происходит это потому, что наш таймер считает, прерывания генерируются, флаг прерывания устанавливается, но никто его не сбрасывает и следующее прерывание висит на очереди и, соответственно дальнейший код не выполняется.
Поэтому перейдём в наш обработчик и первым делом очистим наш флаг, обработав условие его установки
1 2 3 4 5 6 |
void TIM2_Callback(void) { if(LL_TIM_IsActiveFlag_UPDATE(TIM2)) { LL_TIM_ClearFlag_UPDATE(TIM2); } |
Теперь, если мы проверим работу кода в бесконечном цикле, то он будет отлично работать. Затем можем смело удалить весь пользовательский код из бесконечного цикла.
Добавим глобальный счётчик тиков таймера
1 2 |
#define LED10_OFF() LL_GPIO_ResetOutputPin(GPIOB, LL_GPIO_PIN_11) uint8_t tim2_count; |
Обнулим его в main()
1 2 |
/* USER CODE BEGIN 2 */ tim2_count = 0; |
В обработчике после сброса флага UIF в регистре SR проинкрементируем наш счётчик и, если он достиг 10, то сбросим его, так как у нас 10 светодиодов
1 2 3 |
LL_TIM_ClearFlag_UPDATE(TIM2); tim2_count++; if(tim2_count>9) tim2_count=0; |
И до этих действий мы, в зависимости от того, до скольких сосчитал наш счётчик, зажжём соответствующий светодиод, погасив предыдущий
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
LL_TIM_ClearFlag_UPDATE(TIM2); switch(tim2_count) { case 0: LED10_OFF(); LED1_ON(); break; case 1: LED1_OFF(); LED2_ON(); break; case 2: LED2_OFF(); LED3_ON(); break; case 3: LED3_OFF(); LED4_ON(); break; case 4: LED4_OFF(); LED5_ON(); break; case 5: LED5_OFF(); LED6_ON(); break; case 6: LED6_OFF(); LED7_ON(); break; case 7: LED7_OFF(); LED8_ON(); break; case 8: LED8_OFF(); LED9_ON(); break; case 9: LED9_OFF(); LED10_ON(); break; } |
Соберём код, прошьём контроллер, чтобы по бегущим огонькам убедиться, что наш таймер работает корректно
Таким образом, сегодня мы научились работать с таймером, используя библиотеку LL, конечно не со всем функционалом таймеров, которые необъятны, а лишь с частью из него. Но, тем не менее, эта часть послужит, надеюсь, неплохим уроком для дальнейшего совершенствования навыков программирования контроллеров. Также мы изучили подробно немалое количество регистров таймера контроллера STM32F103.
Всем спасибо за внимание!
Предыдущая часть Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Смотреть ВИДЕОУРОК (нажмите на картинку)
если я не ошибаюсь, срабатывание счетчика происходит каждые 100 мс а не 100 мкс, как вы написали