Урок 3: Прерывания в CH32

Прерывания позволяют микроконтроллеру быстро реагировать на внешние события без постоянного опроса состояния. Это ключевой механизм для создания эффективных программ реального времени.

Что такое прерывания?

Прерывание — это механизм, который останавливает текущее выполнение программы и переключается на обработчик прерывания (ISR - Interrupt Service Routine).

  • Асинхронность — прерывания возникают в любой момент
  • Приоритеты — более важные прерывания могут прерывать менее важные
  • Быстрая реакция — программа отвечает на события немедленно

Типы прерываний в CH32

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

  • EXTI — внешние прерывания от GPIO
  • Timer — прерывания от таймеров
  • USART — прерывания от последовательного порта
  • ADC — прерывания от АЦП
  • DMA — прерывания от контроллера DMA

Внешние прерывания (EXTI)

Настройка прерывания от кнопки.

#include "ch32v30x.h" volatile uint8_t button_pressed = 0; void EXTI_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // Включаем тактирование RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // Настройка PA0 как вход GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStructure); // Подключаем PA0 к линии EXTI0 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); // Настройка EXTI линии EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); // Настройка NVIC NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } // Обработчик прерывания void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0) != RESET) { button_pressed = 1; EXTI_ClearITPendingBit(EXTI_Line0); } }
  • EXTI_Trigger_Falling — прерывание по спадающему фронту
  • NVIC_Init() — настройка контроллера прерываний
  • EXTI_ClearITPendingBit() — обязательно! Сброс флага прерывания

Приоритеты прерываний

В RISC-V CH32 используется двухуровневая система приоритетов.

// Настройка приоритетов NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 0-15 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 0-15 // Меньшее число = выше приоритет // PreemptionPriority - может прервать другое прерывание // SubPriority - очередность при одинаковом PreemptionPriority
  • PreemptionPriority — приоритет вытеснения (0 - наивысший)
  • SubPriority — подприоритет при равных PreemptionPriority
  • Прерывание с более высоким приоритетом может прервать текущее

Прерывания от таймера

Генерация периодических событий с помощью таймера.

#include "ch32v30x.h" volatile uint32_t tick_count = 0; void TIM2_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // Настройка таймера на 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_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // Включаем прерывание по обновлению TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // Настройка NVIC NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM2, ENABLE); } // Обработчик прерывания таймера void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { tick_count++; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }
  • TIM_IT_Update — прерывание при переполнении счетчика
  • Период = (PSC + 1) × (ARR + 1) / частота_тактирования
  • volatile — важно для переменных, изменяемых в ISR

Правила работы с прерываниями

Важные принципы при использовании прерываний.

  • Быстрота — обработчик должен выполняться максимально быстро
  • Volatile — используйте volatile для общих переменных
  • Сброс флагов — всегда очищайте флаг прерывания
  • Не блокировать — избегайте задержек и циклов ожидания в ISR
  • Минимум вычислений — сложные операции переносите в main()

Пример: Обработка кнопки с подавлением дребезга

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

#include "ch32v30x.h" volatile uint8_t button_event = 0; uint32_t last_interrupt_time = 0; void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0) != RESET) { uint32_t current_time = tick_count; // Подавление дребезга (50 мс) if(current_time - last_interrupt_time > 50) { button_event = 1; last_interrupt_time = current_time; } EXTI_ClearITPendingBit(EXTI_Line0); } } int main(void) { SystemInit(); EXTI_Config(); TIM2_Config(); while(1) { if(button_event) { button_event = 0; // Обработка нажатия кнопки GPIO_WriteBit(GPIOE, GPIO_Pin_11, !GPIO_ReadOutputDataBit(GPIOE, GPIO_Pin_11)); } } }
  • Используется таймер для отсчета времени
  • Дребезг подавляется проверкой времени между прерываниями
  • Обработка события выполняется в main(), а не в ISR
🏠 На главную