Прерываемые приложения
Простой код приложения, управляемого прерыванием
Теперь, когда мы увидели многие части кода, пришло время собрать его воедино. Вот код для простого приложения GPIO, управляемого прерываниями. Если вы следите за развитием событий, создайте новый проект на основе проекта примера hello, как обычно, и скопируйте следующий код, чтобы заменить содержимое файла hello.c.
Запустите приложение, чтобы убедиться, что оно работает правильно.
/****************************************************************************** Red-V Thing Plus Interrupt_Demo, автор Эдуардо Корпеньо Для управления используется встроенный светодиод и две внешние кнопки. Одна кнопка управляется опросом, а другая - прерываниями. Дата: 20 июня 2022 г. Разработано с использованием Freedom Studio v4.18.0.2021-04-1 на Windows 10 ЛИЦЕНЗИЯ: Этот код выпущен под лицензией MIT License (http://opensource.org/licenses/MIT) ******************************************************************************/ #include <metal/gpio.h> //include GPIO library, https://sifive.github.io/freedom-metal-docs/apiref/gpio.html #include <stdint.h> #include <metal/csr.h> // Macro Functions #define Red_V_enable_output(x) *((uint32_t *) 0x10012008) |= (1< <(x)) #define Red_V_set_pin(x) *((uint32_t *) 0x1001200C) |= (1<< (x)) #define Red_V_clear_pin(x). *((uint32_t *) 0x1001200C) &= ~(1< <(x)) #define Red_V_read_pin(x) (*((uint32_t *) 0x10012000) & (1< <(x)) #define Red_V_enable_pullup(x) *((uint32_t *) 0x10012010) |= (1< <(x)) #define Red_V_GPIO_set_ie(x) *((uint32_t *) 0x10012020) |= (1< <(x)) #define Red_V_GPIO_clear_flag(x) *((uint32_t *) 0x10012024) |= (1< <(x)) #define Red_V_PLIC_GPIO_set_priority(pin,p) *((uint32_t *) (0x0C000020+4*(pin))) = (p) #define Red_V_PLIC_clear_ie() *((uint32_t *) 0x0C002000) = 0;\ *((uint32_t *) 0x0C002004) = 0 #define Red_V_PLIC_set_ie1(x) *((uint32_t *) 0x0C002000) |= (1< <(x)) #define Red_V_PLIC_set_ie2(x) *((uint32_t *) 0x0C002004) |= (1< <(x)) #define Red_V_PLIC_claim *((uint32_t *) 0x0C200004) void gpio_isr(void) __attribute__((interrupt, aligned(64))); void gpio_isr(){ uint32_t plic_id; Red_V_set_pin(5); //Включить светодиод Red_V_GPIO_clear_flag(1); // Level 3: Очистить флаг GPIO0_1 plic_id = Red_V_PLIC_claim; // Level 2: Объявить прерывание GPIO Red_V_PLIC_claim = plic_id; } int main (void){ struct metal_gpio *gpio_0; // Создать экземпляр GPIO gpio_0 = metal_gpio_get_device(0); // Пины устанавливаются при инициализации, поэтому мы должны отключить его, когда используем его как вход/выход metal_gpio_disable_input(gpio_0, 5); // Устанавливаем gpio как выход //metal_gpio_enable_output(gpio_0, 5); Red_V_enable_output(5); // Пины имеют более одной функции, убедитесь, что мы отключаем все подключенное metal_gpio_disable_pinmux(gpio_0, 5); metal_gpio_enable_input(gpio_0, 0); // включить вход 0 metal_gpio_enable_input(gpio_0, 1); // включить вход 1 metal_gpio_disable_output(gpio_0, 0); // отключить вывод 0 metal_gpio_disable_output(gpio_0, 1); // отключить вывод 1 metal_gpio_disable_pinmux(gpio_0, 0); // отключение альтернативных функций для вывода 0 metal_gpio_disable_pinmux(gpio_0, 1); // отключение альтернативных функций для вывода 1 Red_V_enable_pullup(0); // включение подтяжки для вывода 0 Red_V_enable_pullup(1); // включение подтяжки для вывода 1 // Конфигурация прерываний // Level 3: GPIO0_1 Falling Edge Interrupt Enable Red_V_GPIO_set_ie(1); Red_V_GPIO_clear_flag(1); // Level 2: Настройка ПЛИС для GPIO0 Red_V_PLIC_GPIO_set_priority(1,7); // Pin 1, priority 7 // Level 2: ПЛИС (IE1, bit 9) для GPIO0_1 Red_V_PLIC_clear_ie(); // Отключить все остальные прерывания Red_V_PLIC_set_ie1(9); // Включить GPIO0_1 // Level 1: Включение прерываний с MIE в mstatus[3] volatile uintptr_t saved_config; METAL_CPU_GET_CSR(mstatus,saved_config); saved_config |= (0x1U< <3); METAL_CPU_SET_CSR(mstatus,saved_config); // Level 1: Установка базового вектора mtvec METAL_CPU_SET_CSR(mtvec,&gpio_isr)); // Level 1: Включение прерываний с MEIE в mie[11] METAL_CPU_GET_CSR(mie,saved_config); saved_config |= (0x1U< <11); METAL_CPU_SET_CSR(mie,saved_config); while(1){ if(Red_V_read_pin(0) == 0) // считать входной пин 0 Red_V_clear_pin(5); // Выключить светодиод } return 0; // Недоступный код }
Приложение "Мигание и яркость", управляемое прерыванием
Наконец, мы готовы исправить комбинированное приложение "Мигание и яркость" с помощью прерываний.
Для кнопок, управляющих ШИМ, мы будем продолжать использовать тот же опрос с блокирующими циклами и вызовами функций блокирующей задержки.
Теперь, для части мигания, мы будем использовать периодическое прерывание от устройства PWM2 с частотой 1 мс. Это можно сделать, настроив модуль PWM2 на генерацию сигнала 1 кГц на любом канале, но отключив все его выходы, чтобы можно было использовать пины GPIO, поскольку нас интересует только периодическое прерывание. Сигнал прерывания мы будем использовать для канала 0, который содержит период ШИМ-сигналов, которые будет генерировать модуль. Это ровно 1 миллисекунда.
Каждую миллисекунду обработчик прерывания будет увеличивать счетчик, и когда он достигнет 500, он выключит бортовой светодиод. Когда он достигнет 1000, он включит бортовой светодиод и сбросит его в 0. Это позволит эффективно мигать светодиодом с частотой 1 Гц.
Напомним, что устройство ШИМ позволяет нам генерировать прерывания всякий раз, когда любой из его компараторов выдает 1. То есть, всякий раз, когда таймер достигает своего значения сравнения.
Именно для этого в схеме устройства ШИМ используются сигналы ip. Обратите внимание, что в модулях ШИМ нет регистров разрешения прерываний:
![Схема каждого ШИМ-устройства в микроконтроллере FE310 (Изображение из руководства пользователя FE310-G002, воспроизведено с разрешения компании SiFive, Inc.)](/EDI/13_02_25_2/1739398847-5454/tutorial/1372/objects/6/files/05-13.jpg)
Рис. 5.13. Схема каждого ШИМ-устройства в микроконтроллере FE310 (Изображение из руководства пользователя FE310-G002, воспроизведено с разрешения компании SiFive, Inc.)
Использование прерываний ШИМ в коде
Для уровней 1 и 2 мы будем использовать тот же код, что и в приложении, управляемом прерываниями GPIO, за исключением строки, в которой мы устанавливаем регистр mtvec для указания на подпрограмму обслуживания прерываний:
// Уровень 1: Установите базовый вектор mtvec METAL_CPU_SET_CSR(mtvec,&pwm2_isr);
ШИМ-устройством, используемым для периодического таймера, будет модуль PWM2, и для него мы также будем использовать макросы:
#define Red_V_PWM2_sticky() *((uint32_t *) 0x10035000) |= (1< <8) #define Red_V_PWM2_clear_flag(x) *(( uint32_t *) 0x10035000) &= ~(1< <((x)+28))
Опция sticky bits рекомендуется для гарантии того, что прерывания не будут забыты в ожидании выполнения обработчика. Также обратите внимание, что функция Red_V_PWM2_clear_flag() очищает биты ожидания прерывания, записывая в них ноль.
Поскольку в модулях ШИМ нет регистров разрешения прерываний, единственным кодом для конфигурирования прерываний на уровне 3 будет сброс флага:
// Уровень 3: нет регистра ie для каналов ШИМ. Red_V_PWM2_clear_flag(0); // Очистить флаг PWM2_0
Обработчик прерываний ШИМ
Здесь находится код обработчика прерывания ШИМ. Потратьте минуту, чтобы разобраться в нем.
void pwm2_isr(void) __attribute__((interrupt, aligned(64)))); void pwm2_isr(){ static uint32_t count = 0; uint32_t plic_id; count++; // Код мигания if(count == 500) Red_V_clear_pin(5); // Выключите светодиод if(count == 1000){ Red_V_set_pin(5); // Включите светодиод count = 0; } Red_V_PWM2_clear_flag(0); // Очистить флаг PWM2_0 plic_id = Red_V_PLIC_claim; // Уровень 2: Прерывание по GPIO Red_V_PLIC_claim = plic_id; }