Урок 4: Таймеры в CH32

Таймеры — это одни из самых универсальных периферийных модулей микроконтроллера. Они используются для измерения времени, генерации ШИМ-сигналов, подсчета импульсов и многих других задач.

Типы таймеров в CH32V307

CH32V307 имеет несколько типов таймеров с различными возможностями.

  • TIM1, TIM8 — расширенные таймеры (16-бит, 4 канала PWM)
  • TIM2, TIM3, TIM4, TIM5 — универсальные таймеры (16/32-бит)
  • TIM6, TIM7 — базовые таймеры (только отсчет времени)
  • SysTick — системный таймер (для RTOS и задержек)

Базовая настройка таймера

Пример настройки таймера для генерации задержки.

#include "ch32v30x.h" void TIM3_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // Включение тактирования TIM3 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // Настройка базовых параметров // При частоте 72 МГц: Period=1000, Prescaler=71 // Частота = 72000000 / (71+1) / (999+1) = 1000 Гц (1 мс) TIM_TimeBaseStructure.TIM_Period = 999; // ARR TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // Запуск таймера TIM_Cmd(TIM3, ENABLE); }
  • TIM_Period (ARR) — значение автоперезагрузки
  • TIM_Prescaler (PSC) — делитель частоты
  • Формула: f = f_clock / (PSC + 1) / (ARR + 1)

Генерация ШИМ-сигнала

PWM (Pulse Width Modulation) используется для управления яркостью светодиодов, скоростью моторов и т.д.

#include "ch32v30x.h" void TIM2_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // Включение тактирования RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // Настройка PA0 как альтернативная функция (TIM2_CH1) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // Настройка таймера: частота ШИМ = 1 кГц TIM_TimeBaseStructure.TIM_Period = 999; TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // Настройка канала PWM TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 500; // Скважность 50% TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM2, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM2, ENABLE); TIM_Cmd(TIM2, ENABLE); } // Изменение скважности PWM void Set_PWM_DutyCycle(uint16_t duty) { // duty: 0-999 (0% - 100%) TIM_SetCompare1(TIM2, duty); }
  • TIM_OCMode_PWM1 — режим ШИМ
  • TIM_Pulse — значение сравнения (определяет скважность)
  • Скважность = (TIM_Pulse / TIM_Period) × 100%

Измерение частоты сигнала

Использование таймера для подсчета внешних импульсов.

#include "ch32v30x.h" uint16_t pulse_count = 0; void TIM4_Counter_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // PB6 - TIM4_CH1 (вход для подсчета импульсов) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, &GPIO_InitStructure); // Настройка таймера в режиме внешнего счетчика TIM_TimeBaseStructure.TIM_Period = 65535; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); // Настройка внешнего тактирования TIM_ETRClockMode2Config(TIM4, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0); TIM_Cmd(TIM4, ENABLE); } uint16_t Get_PulseCount(void) { return TIM_GetCounter(TIM4); } void Reset_PulseCount(void) { TIM_SetCounter(TIM4, 0); }
  • TIM_ETRClockMode2Config() — внешний источник тактирования
  • TIM_GetCounter() — чтение текущего значения счетчика
  • TIM_SetCounter() — сброс счетчика

Захват сигнала (Input Capture)

Измерение длительности или периода внешнего сигнала.

#include "ch32v30x.h" volatile uint16_t capture1 = 0, capture2 = 0; volatile uint8_t capture_done = 0; void TIM3_IC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA6 - TIM3_CH1 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 65535; TIM_TimeBaseStructure.TIM_Prescaler = 71; // 1 МГц TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // Настройка Input Capture TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0; TIM_ICInit(TIM3, &TIM_ICInitStructure); // Прерывание по захвату TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM3, ENABLE); } void TIM3_IRQHandler(void) { if(TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) { if(capture_done == 0) { capture1 = TIM_GetCapture1(TIM3); capture_done = 1; } else { capture2 = TIM_GetCapture1(TIM3); capture_done = 2; } TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); } }
  • TIM_ICPolarity_Rising — захват по переднему фронту
  • TIM_GetCapture1() — чтение захваченного значения
  • Разница capture2 - capture1 даёт период сигнала в микросекундах

Практический пример: Затухание LED

Плавное изменение яркости светодиода с помощью ШИМ.

#include "ch32v30x.h" int main(void) { uint16_t brightness = 0; int8_t direction = 1; SystemInit(); TIM2_PWM_Init(); while(1) { Set_PWM_DutyCycle(brightness); brightness += direction * 10; if(brightness >= 999) { brightness = 999; direction = -1; } else if(brightness <= 0) { brightness = 0; direction = 1; } Delay_Ms(20); } }
  • Яркость плавно увеличивается от 0% до 100%
  • Затем плавно уменьшается обратно
  • Цикл повторяется бесконечно
🏠 На главную