Прерывания в микроконтроллерах Миландр. Системный таймер.

На примере микроконтроллера К1986ВЕ92QI (MDR32F9Q2I) и отладочной платы LDM-K1986BE92QI рассмотрим применение прерываний и системного таймера, описанных в пунктах 12, 29 и 30 спецификации на МК.

В предыдущих примерах мы моргали светодиодом делая задержку пустым вращением цикла for. Это не есть хорошо, так как ресурсы процессора можно и нужно использовать для более полезных вещей.

Тут нам на помощь приходят прерывания - механизм вызова функции, которая вызывается по каким-то внешним или внутренним событиям. Таким образом, до прихода события, процессор может заниматься выполнением полезной работы, как только событие произойдет, процессор сохраняет свой контекст (значения) и выполняет функцию прерывания. По завершении выполнения функции прерывания, процессор продолжает выполнять свою полезную работу, как будто ничего его и не прерывало (ну кроме задержки на выполнения функции прерывания).

Для включения механизма прерываний нужно:
1) Вызывать функцию NVIC_EnableIRQ(), в качестве единственного параметра которой передать ей одно из предопределенных макроопределений с индексом прерывания.
2) Определить функцию предопределенного обработчика прерываний.
3) Настроить периферию МК, в приведенном ниже коде примера, это делается в функции init_SysTick() и там же выполняется пункт 1 - вызов NVIC_EnableIRQ().

Алгоритм поиска нужных наименований:
1) Вариант 1. В IDE Keil нажать Ctrl + F и в появившемся диалоговом окне поиска в поле "Find what:" вставить SysTick_IRQn, а в поле "Look in" выбрать в списке "Current Project" и нажимая кнопку "Find Next" поискать рядом подходящие прерывания и обработчики. Повторить поиск для "SysTick_Handler".
2) Вариант2. Воспользоваться таблицей ниже.

Таблица 1. Наименования для вызова всех имеющихся прерываний МК К1986ВЕ92QI (MDR32F9Q2I).

id Макроопределение прерывания
параметр NVIC_EnableIRQ()
Функция обработчика прерывания
__initial_sp
Reset_Handler
-14 NonMaskableInt_IRQn NMI_Handler
-13 HardFault_IRQn HardFault_Handler
-12 MemoryManagement_IRQn MemManage_Handler
-11 BusFault_IRQn BusFault_Handler
-9 (-10?) UsageFault_IRQn UsageFault_Handler
-5 SVCall_IRQn SVC_Handler
-4 DebugMon_Handler
-2 PendSV_IRQn PendSV_Handler
-1 SysTick_IRQn SysTick_Handler
0 CAN1_IRQn CAN1_IRQHandler
1 CAN2_IRQn CAN2_IRQHandler
2 USB_IRQn USB_IRQHandler
5 DMA_IRQn DMA_IRQHandler
6 UART1_IRQn UART1_IRQHandler
7 UART2_IRQn UART2_IRQHandler
8 SSP1_IRQn SSP1_IRQHandler
10 I2C_IRQn I2C_IRQHandler
11 POWER_IRQn POWER_IRQHandler
12 WWDG_IRQn WWDG_IRQHandler
14 Timer1_IRQn Timer1_IRQHandler
15 Timer2_IRQn Timer2_IRQHandler
16 Timer3_IRQn Timer3_IRQHandler
17 ADC_IRQn ADC_IRQHandler
19 COMPARATOR_IRQn COMPARATOR_IRQHandler
20 SSP2_IRQn SSP2_IRQHandler
27 BACKUP_IRQn BACKUP_IRQHandler
28 EXT_INT1_IRQn EXT_INT1_IRQHandler
29 EXT_INT2_IRQn EXT_INT2_IRQHandler
30 EXT_INT3_IRQHandler
31 EXT_INT4_IRQn EXT_INT4_IRQHandler

Пример программы использующей прерывание по системному таймеру для моргания светодиодом.


// Моргаем светодиодом по прерыванию
#include "MDR32F9Q2I.h" // Заголовок с наименованиями CMSIS

#define PIN_LED 3	// Номер вывода светодиода
#define PORT_LED MDR_PORTB	// Порт светодиода

void SysTick_Handler(void); // Прототип обработчика прерывания (чтобы убрать предупреждения Keil)

static void init_GPIO(void){ // Инициализация порта
  MDR_RST_CLK->PER_CLOCK |= (1 << 22); // Разрешение тактирования порта B (п.14.7.8)

  PORT_LED->OE |= (1 << PIN_LED); // Вход или выход. 0 - вход, 1 - выход (п.16.1.2)
  PORT_LED->ANALOG |= (1 << PIN_LED); // Аналог или цифра. 0 - аналоговый, 1 - цифровой (п.16.1.4) (п.4 т.2)
  PORT_LED->PWR |= (1 << PIN_LED * 2); // Передатчик. 0 - отключен, 1 - медленный, 2 - средний, 3 - быстрый (п.16.1.7)
}

static void init_SysTick(void){ // Инициализация системного таймера SysTick  
  SysTick->CTRL = ( // Параметры системного таймера (п.12.1.1)
     (1 << 0x00) // Работа таймера. 0 - отключен, 1 - включен 
    |(1 << 0x01) // Разрешение прерывания по достижению счетчика. 0 - запрещено, 1 - разрешено
    |(1 << 0x02) // Источник тактового сигнала. 0 - LSI (внутренний RC 40 кГц), 1 - HCLK (системная частота)
  ); 
  SysTick->LOAD = 8000000 - 1; // Загружаем значение счетчика (для 1 секунды, исходя из тактовой частоты 8 МГц) (п.12.1.2)
  
  NVIC_EnableIRQ(SysTick_IRQn); // Разрешаем прерывание от SysTick
}

int main(void)
{
  init_GPIO(); // Инициализация порта
  init_SysTick(); // Инициализация системного таймера SysTick 

  while(1){ // Основной бесконечный цикл. В нем делаем полезную работу.
    
  }
}

void SysTick_Handler(void){ // Обработчик прерывания
  PORT_LED->RXTX ^= (1 << PIN_LED); // Инвертировать значение вывода (п.16.1.1)
}

Обработка нескольких временных отсчетов с помощью одного таймера

На практике, в программе, требуются множество различных временных отсчетов, а таймеров в МК не так много. Один из подходов это настроить таймер на достаточно маленький отсчет времени, например 1 мс, а в обработчике прерывания таймера проверять требуемые отсчеты времени использую счетчики или операцию деления по модулю.

В программе ниже я приведу оба этих метода. Следует помнить что в часе 3600 секунд, а в сутках 3600 * 24 = 86 400 секунд (или 86 400 000 миллисекунд). Максимальное значение счетчика типа unsigned int 4 294 967 295 и примерно через 49 суток непрерывной работы ПО возникнет переполнение счетчика - переход через максимальное значение и опять счет от нуля, в этот момент времени могут возникать неправильные интервалы времени. А при использовании в ПО значений счетчика, с последующим их вычитанием для получения интервала времени и более грубые ошибки.


// Моргаем четырьмя светодиодами с разной частотой по прерыванию системного таймера
#include "MDR32F9Q2I.h" // Заголовок с наименованиями CMSIS

// Макроопределения портов и выводов светодиодов. Если нужно будет их поменять, это достаточно будет сделать здесь, а не лазить по всему коду.
#define PIN_LED_1 0	// Номер вывода светодиода 1
#define PORT_LED_1 MDR_PORTB	// Порт светодиода 1
#define PIN_LED_2 1	// Номер вывода светодиода 2
#define PORT_LED_2 MDR_PORTB	// Порт светодиода 2
#define PIN_LED_3 2	// Номер вывода светодиода 3
#define PORT_LED_3 MDR_PORTB	// Порт светодиода 3
#define PIN_LED_4 3	// Номер вывода светодиода 4
#define PORT_LED_4 MDR_PORTB	// Порт светодиода 4

static uint32_t systick_counter = 1; // Счетчик миллисекунд. Ставим в 1 чтобы не было срабатывания при опереции % в первом прерывании. 
const static uint16_t TIME_LED_1 = 100; // Через сколько миллисекунд менять цвет светодиода 1
const static uint16_t TIME_LED_2 = 230; // Через сколько миллисекунд менять цвет светодиода 2
const static uint16_t TIME_LED_3 = 410; // Через сколько миллисекунд менять цвет светодиода 3
const static uint16_t TIME_LED_4 = 670; // Через сколько миллисекунд менять цвет светодиода 4

void SysTick_Handler(void); // Прототип обработчика прерывания (чтобы убрать предупреждения Keil)

static void init_GPIO(void){ // Инициализация порта
  MDR_RST_CLK->PER_CLOCK |= (1 << 22); // Разрешение тактирования порта B (п.14.7.8)

  PORT_LED_1->OE |= (1 << PIN_LED_1); // Вход или выход. 0 - вход, 1 - выход (п.16.1.2)
  PORT_LED_1->ANALOG |= (1 << PIN_LED_1); // Аналог или цифра. 0 - аналоговый, 1 - цифровой (п.16.1.4) (п.4 т.2)
  PORT_LED_1->PWR |= (1 << PIN_LED_1 * 2); // Передатчик. 0 - отключен, 1 - медленный, 2 - средний, 3 - быстрый (п.16.1.7)
  
  PORT_LED_2->OE |= (1 << PIN_LED_2); // Вход или выход. 0 - вход, 1 - выход (п.16.1.2)
  PORT_LED_2->ANALOG |= (1 << PIN_LED_2); // Аналог или цифра. 0 - аналоговый, 1 - цифровой (п.16.1.4) (п.4 т.2)
  PORT_LED_2->PWR |= (1 << PIN_LED_2 * 2); // Передатчик. 0 - отключен, 1 - медленный, 2 - средний, 3 - быстрый (п.16.1.7)
  
  PORT_LED_3->OE |= (1 << PIN_LED_3); // Вход или выход. 0 - вход, 1 - выход (п.16.1.2)
  PORT_LED_3->ANALOG |= (1 << PIN_LED_3); // Аналог или цифра. 0 - аналоговый, 1 - цифровой (п.16.1.4) (п.4 т.2)
  PORT_LED_3->PWR |= (1 << PIN_LED_3 * 2); // Передатчик. 0 - отключен, 1 - медленный, 2 - средний, 3 - быстрый (п.16.1.7)
  
  PORT_LED_4->OE |= (1 << PIN_LED_4); // Вход или выход. 0 - вход, 1 - выход (п.16.1.2)
  PORT_LED_4->ANALOG |= (1 << PIN_LED_4); // Аналог или цифра. 0 - аналоговый, 1 - цифровой (п.16.1.4) (п.4 т.2)
  PORT_LED_4->PWR |= (1 << PIN_LED_4 * 2); // Передатчик. 0 - отключен, 1 - медленный, 2 - средний, 3 - быстрый (п.16.1.7)
}

static void init_SysTick(void){ // Инициализация системного таймера SysTick  
  SysTick->CTRL = ( // Параметры системного таймера (п.12.1.1)
     (1 << 0x00) // Работа таймера. 0 - отключен, 1 - включен 
    |(1 << 0x01) // Разрешение прерывания по достижению счетчика. 0 - запрещено, 1 - разрешено
    |(1 << 0x02) // Источник тактового сигнала. 0 - LSI (внутренний RC 40 кГц), 1 - HCLK (системная частота)
  ); 
  SysTick->LOAD = 8000 - 1; // Загружаем значение счетчика (для 1 миллисекунды, исходя из тактовой частоты 8 МГц) (п.12.1.2)
  
  NVIC_EnableIRQ(SysTick_IRQn); // Разрешаем прерывание от SysTick
}

int main(void)
{
  init_GPIO(); // Инициализация порта
  init_SysTick(); // Инициализация системного таймера SysTick 

  while(1){ // Основной бесконечный цикл. В нем делаем полезную работу.
    
  }
}

void SysTick_Handler(void){ // Обработчик прерывания
  static uint16_t cnt_led_1 = 0; // Счетчик миллисекунд для светодиода 1
  static uint16_t cnt_led_2 = 0; // Счетчик миллисекунд для светодиода 2
  
  cnt_led_1++; // Инкрементируем счетчик светодиода 1
  cnt_led_2++; // Инкрементируем счетчик светодиода 2
  
  if(cnt_led_1 == TIME_LED_1){ // Настало время смены цвета светодиода 1
    PORT_LED_1->RXTX ^= (1 << PIN_LED_1); // Меняем цвет светодиода 1 (п.16.1.1)
    cnt_led_1 = 0; // Сбрасываем счетчик
  }
  
  if(cnt_led_2 == TIME_LED_2){ // Настало время смены цвета светодиода 2
    PORT_LED_2->RXTX ^= (1 << PIN_LED_2); // Меняем цвет светодиода 2 (п.16.1.1)
    cnt_led_2 = 0; // Сбрасываем счетчик
  }
  
  if(systick_counter % TIME_LED_3 == 0){ // Настало время смены цвета светодиода 3
    PORT_LED_3->RXTX ^= (1 << PIN_LED_3); // Меняем цвет светодиода 3 (п.16.1.1)
  }
  
  if(systick_counter % TIME_LED_4 == 0){ // Настало время смены цвета светодиода 3
    PORT_LED_4->RXTX ^= (1 << PIN_LED_4); // Меняем цвет светодиода 3 (п.16.1.1)
  }
  
  systick_counter++; // Инкрементируем счетчик миллисекунд
}
2024-04-26



Понравилась страница?
Добавить в закладки
Или поделиться!

Связанные темы

Управление тактовой частотой микроконтроллера Миландр К1986ВЕ92QI (MDR32F9Q2I)
Работа с портами ввода-вывода (GPIO) микроконтроллера Миландр на CMSIS
Прерывания в микроконтроллерах Миландр. Системный таймер.
Настройка среды разработки для микроконтроллеров Миландр (К1986ВЕ92QI)
Прошивка микроконтроллера К1986ВЕ92QI на отладочной плате LDM-K1986BE92QI через UART и SWD с использованием ST-LINK
Работа с UART микроконтроллера К1986ВЕ92QI (MDR32F9Q2I)