Прерываемые приложения
Теперь давайте рассмотрим альтернативу, управляемую прерываниями.
void touch_screen_ISR(); // 100ms void GPS_ISR(); // 300ms void fuel_level_ISR(); // 200ms void velocity_ISR(); // 200ms void temperature_ISR(); // 100ms void door_light_ISR(); // 100ms void main(){ while(1) refresh_display(); // 200ms }
Обратите внимание, что в главном цикле вызывается только функция обновления дисплея. Остальные функции заменены обработчиками прерываний, также известными как подпрограммы обслуживания прерываний (ISR). Эти ISR занимают столько же времени, сколько и их аналоги, но они выполняются только тогда, когда возникает необходимость (открывается дверь, пользователь касается экрана, меняется уровень топлива и так далее). Это ответы на запросы ввода-вывода, а не команды на чтение или вывод чего-либо, как в подходе с опросами.
Необходимым условием эффективной работы прерываний является то, что запросы на прерывание должны поступать с гораздо меньшей частотой, чем запросы основного потока. Другими словами, прерывания должны возникать не постоянно, а время от времени, иначе они будут мешать выполнению основной задачи. Все, что постоянно требует внимания, должно находиться в основном цикле.
Здесь мы имеем возможную временную шкалу примера с прерыванием:
Поскольку запросы ввода-вывода происходят нечасто, основной цикл может работать без сбоев.
Объединение двух приложений
Возможно, вы не до конца уверены, что прерывания всегда лучше, чем опрос, поэтому давайте посмотрим, насколько плохим может быть приложение с опросом, если оно плохо спроектировано. Опросом можно злоупотреблять до такой степени, что несколько задач будут конкурировать за процессорное время, что приведет к плохому выполнению общей задачи.
Простым примером для иллюстрации этого является объединение двух приложений, которые мы уже видели ранее: Проект Blinky и Демонстрация управления яркостью светодиодов. Исходный код этих приложений будет объединен в один файл C. Это объединенное приложение будет использовать те же входные переключатели и светодиоды, что и демонстрация ШИМ. Вот принципиальная схема:
Кнопка 0 уменьшит яркость внешнего светодиода на ступень, а кнопка 1 увеличит ее на ступень.
На этот раз внешний светодиод будет иметь 5 уровней яркости, увеличивающихся с шагом в 25%, поэтому он также будет переходить от 0% к 100%. Тем временем, встроенный светодиод будет мигать с частотой 1 Гц независимо.
Это контакты ввода/вывода, которые мы будем использовать для подключения кнопок и внешнего светодиода:
Рис. 5.6. Виды сверху и снизу Red-V Thing Plus, показывающие контакты, которые будут использоваться в демонстрационном приложении для опроса (Изображение взято с сайта SparkFun Electronics, по лицензии CC BY 2.0)
Здесь представлены две фотографии фактических аппаратных соединений:
Комбинированный код приложения GPIO
Наша первая попытка использует простую адаптацию кода на языке C для обоих приложений, используя подход опроса.
Здесь приведен код приложения для опроса светодиодов. Если вы следите за развитием событий, создайте новый проект на основе проекта примера hello, как обычно, и скопируйте следующий код, чтобы заменить содержимое файла hello.c.
/****************************************************************************** Red-V Thing Plus LED_Polling demo, by Eduardo Corpe?o Using the built-in LED and two external buttons to control its brightness, while the on-board LED blinks at 1Hz independently. Date: June 20, 2022 Developed using Freedom Studio v4.18.0.2021-04-1 on Windows 10 LICENSE: This code is released under the MIT License (http://opensource.org/licenses/MIT) ******************************************************************************/ #include <metal/gpio.h> #include <metal/pwm.h> #include <stdint.h> #include <time.h> #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_enable_DS(x) * ((uint32_t * ) 0x10012014) |= (1 << (x)) #define Red_V_enable_XOR(x) * ((uint32_t * ) 0x10012040) |= (1 << (x)) // Пользовательская функция задержки void delay(int number_of_microseconds) { clock_t start_time = clock(); // Хранение времени запуска while (clock() < start_time + number_of_microseconds); } int main(void) { unsigned int dc = 50; // По умолчанию рабочий цикл равен 50% struct metal_gpio * gpio_0; // Экземпляр GPIO struct metal_pwm * pwm_1; // Экземпляр PWM gpio_0 = metal_gpio_get_device(0); // Конфигурация встроенного светодиода metal_gpio_disable_input(gpio_0, 5); Red_V_enable_output(5); metal_gpio_disable_pinmux(gpio_0, 5); metal_gpio_set_pin(gpio_0, 5, 1); // Конфигурация кнопок 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 // Конфигурация ШИМ. Мы будем использовать PWM1_1, который использует GPIO0_19. Red_V_enable_DS(19); // Сила высокого выхода Red_V_enable_XOR(19); // Инвертируем выход для активно-высокого ШИМа pwm_1 = metal_pwm_get_device(1); // Получение экземпляра ШИМ-устройства while (pwm_1 == NULL); // Остановка.Что-то пошло не так metal_pwm_enable(pwm_1); // Включить ШИМ1 metal_gpio_disable_pinmux(gpio_0, 20); // Отключить альтернативные функции для пина 20 metal_gpio_disable_pinmux(gpio_0, 21); // Отключить альтернативные функции для пина 21 metal_gpio_disable_pinmux(gpio_0, 22); // Отключить альтернативные функции для пина 22 // Установите частоту ШИМ1 на 1 кГц metal_pwm_set_freq(pwm_1, 1, 1000); // Установите частоту ШИМ1_1 на 1 кГц для светодиода metal_pwm_set_duty(pwm_1, 1, dc, METAL_PWM_PHASE_CORRECT_DISABLE); // Устанавливаем рабочий цикл для ШИМ1_1 metal_pwm_trigger(pwm_1, 1, METAL_PWM_CONTINUOUS); // Запуск в непрерывном режиме while (1) { // Кнопки и код ШИМ внешнего светодиода if (Red_V_read_pin(0) == 0) { // Чтение входного пина 0 dc = (dc > 0) ? dc - 25 : dc; // Шаги по 25% для светодиода metal_pwm_set_duty(pwm_1, 1, dc, METAL_PWM_PHASE_CORRECT_DISABLE); delay(50000); // Программное ожидание while (Red_V_read_pin(0) == 0); // Ждем, пока кнопка не будет отпущена delay(50000); // Программное ожидание } else if (Red_V_read_pin(1) == 0) { // Считывание входного пина 1 dc = (dc < 100) ? dc + 25 : dc; // Шаги по 25% для светодиода metal_pwm_set_duty(pwm_1, 1, dc, METAL_PWM_PHASE_CORRECT_DISABLE); delay(50000); // Программное ожидание while (Red_V_read_pin(1) == 0); // Ждем, пока кнопка будет отпущена delay(50000); // Программное ожидание } // Код мигающего светодиода Red_V_clear_pin(5); // Выключение встроенного светодиода delay(500000); // 500 мс в микросекундах Red_V_set_pin(5); // Включение встроенного светодиода delay(500000); // 500 мс в микросекундах } return 0; // Недостижимый код }