- •Оглавление
- •Предисловие
- •Почему я написал книгу?
- •Для кого эта книга?
- •Как использовать эту книгу?
- •Как организована книга?
- •Об авторе
- •Ошибки и предложения
- •Поддержка книги
- •Как помочь автору
- •Отказ от авторского права
- •Благодарность за участие
- •Перевод
- •Благодарности
- •I Введение
- •1. Введение в ассортимент микроконтроллеров STM32
- •1.1. Введение в процессоры на базе ARM
- •1.1.1. Cortex и процессоры на базе Cortex-M
- •1.1.1.10. Внедренные функции Cortex-M в ассортименте STM32
- •1.2. Введение в микроконтроллеры STM32
- •1.2.1. Преимущества ассортимента STM32….
- •1.2.2. ….И его недостатки
- •1.3. Краткий обзор подсемейств STM32
- •1.3.1. Серия F0
- •1.3.2. Серия F1
- •1.3.3. Серия F2
- •1.3.4. Серия F3
- •1.3.5. Серия F4
- •1.3.6. Серия F7
- •1.3.7. Серия H7
- •1.3.8. Серия L0
- •1.3.9. Серия L1
- •1.3.10. Серия L4
- •1.3.11. Серия L4+
- •1.3.12. Серия STM32WB
- •1.3.13. Как правильно выбрать для себя микроконтроллер?
- •1.4. Отладочная плата Nucleo
- •2. Установка инструментария
- •2.1. Почему выбирают Eclipse/GCC в качестве инструментария для STM32
- •2.1.1. Два слова о Eclipse…
- •2.2. Windows – Установка инструментария
- •2.2.1. Windows – Установка Eclipse
- •2.2.2. Windows – Установка плагинов Eclipse
- •2.2.3. Windows – Установка GCC ARM Embedded
- •2.2.4. Windows – Установка инструментов сборки
- •2.2.5. Windows – Установка OpenOCD
- •2.2.6. Windows – Установка инструментов ST и драйверов
- •2.3. Linux – Установка инструментария
- •2.3.2. Linux – Установка Java
- •2.3.3. Linux – Установка Eclipse
- •2.3.4. Linux – Установка плагинов Eclipse
- •2.3.5. Linux – Установка GCC ARM Embedded
- •2.3.6. Linux – Установка драйверов Nucleo
- •2.3.7. Linux – Установка OpenOCD
- •2.3.8. Linux – Установка инструментов ST
- •2.4. Mac – Установка инструментария
- •2.4.1. Mac – Установка Eclipse
- •2.4.2. Mac – Установка плагинов Eclipse
- •2.4.3. Mac – Установка GCC ARM Embedded
- •2.4.4. Mac – Установка драйверов Nucleo
- •2.4.5. Mac – Установка OpenOCD
- •2.4.6. Mac – Установка инструментов ST
- •3. Hello, Nucleo!
- •3.1. Прикоснитесь к Eclipse IDE
- •3.2. Создание проекта
- •3.3. Подключение Nucleo к ПК
- •3.5. Изучение сгенерированного кода
- •4. Инструмент STM32CubeMX
- •4.1. Введение в инструмент CubeMX
- •4.1.1. Представление Pinout
- •4.1.2. Представление Clock Configuration
- •4.1.3. Представление Configuration
- •4.1.4. Представление Power Consumption Calculator
- •4.2. Генерация проекта
- •4.2.1. Генерация проекта Си при помощи CubeMX
- •4.2.2. Создание проекта Eclipse
- •4.2.3. Ручное импортирование сгенерированных файлов в проект Eclipse
- •4.3. Изучение сгенерированного кода приложения
- •4.3.1. Добавим что-нибудь полезное в микропрограмму
- •4.4. Загрузка исходного кода примеров книги
- •5. Введение в отладку
- •5.1. Начало работы с OpenOCD
- •5.1.1. Запуск OpenOCD
- •5.1.2. Подключение к OpenOCD Telnet Console
- •5.1.3. Настройка Eclipse
- •5.1.4. Отладка в Eclipse
- •5.2. Полухостинг ARM
- •5.2.1. Включение полухостинга в новом проекте
- •5.2.2. Включение полуохостинга в существующем проекте
- •5.2.3. Недостатки полухостинга
- •5.2.4. Как работает полухостинг
- •II Погружение в HAL
- •6. Управление GPIO
- •6.2. Конфигурация GPIO
- •6.2.1. Режимы работы GPIO
- •6.2.2. Режим альтернативной функции GPIO
- •6.2.3. Понятие скорости GPIO
- •6.3. Управление GPIO
- •6.4. Деинициализация GPIO
- •7. Обработка прерываний
- •7.1. Контроллер NVIC
- •7.1.1. Таблица векторов в STM32
- •7.2. Разрешение прерываний
- •7.2.1. Линии запроса внешних прерываний и контроллер NVIC
- •7.2.2. Разрешение прерываний в CubeMX
- •7.3. Жизненный цикл прерываний
- •7.4. Уровни приоритета прерываний
- •7.4.1. Cortex-M0/0+
- •7.4.2. Cortex-M3/4/7
- •7.4.3. Установка уровня прерываний в CubeMX
- •7.5. Реентерабельность прерываний
- •8. Универсальные асинхронные последовательные средства связи
- •8.1. Введение в UART и USART
- •8.2. Инициализация UART
- •8.3. UART-связь в режиме опроса
- •8.3.1. Установка консоли последовательного порта в Windows
- •8.3.2. Установка консоли последовательного порта в Linux и MacOS X
- •8.4. UART-связь в режиме прерываний
- •8.5. Обработка ошибок
- •8.6. Перенаправление ввода-вывода
- •9. Управление DMA
- •9.1. Введение в DMA
- •9.1.1. Необходимость DMA и роль внутренних шин
- •9.1.2. Контроллер DMA
- •9.2. Модуль HAL_DMA
- •9.2.1. DMA_HandleTypeDef в HAL для F0/F1/F3/L0/L1/L4
- •9.2.2. DMA_HandleTypeDef в HAL для F2/F4/F7
- •9.2.3. DMA_HandleTypeDef в HAL для L0/L4
- •9.2.4. Как выполнять передачи в режиме опроса
- •9.2.5. Как выполнять передачи в режиме прерываний
- •9.2.8. Разнообразные функции модулей HAL_DMA и HAL_DMA_Ex
- •9.3. Использование CubeMX для конфигурации запросов к DMA
- •10. Схема тактирования
- •10.1. Распределение тактового сигнала
- •10.1.1. Обзор схемы тактирования STM32
- •10.1.1.1. Многочастотный внутренний RC-генератор в семействах STM32L
- •10.1.3.1. Подача тактового сигнала от высокочастотного генератора
- •10.1.3.2. Подача тактового сигнала от 32кГц генератора
- •10.2. Обзор модуля HAL_RCC
- •10.2.1. Вычисление тактовой частоты во время выполнения
- •10.2.2. Разрешение Выхода синхронизации
- •10.2.3. Разрешение Системы защиты тактирования
- •10.3. Калибровка HSI-генератора
- •11. Таймеры
- •11.1. Введение в таймеры
- •11.1.1. Категории таймеров в микроконтроллере STM32
- •11.1.2. Доступность таймеров в ассортименте STM32
- •11.2. Базовые таймеры
- •11.2.1. Использование таймеров в режиме прерываний
- •11.2.2. Использование таймеров в режиме опроса
- •11.2.3. Использование таймеров в режиме DMA
- •11.2.4. Остановка таймера
- •11.3. Таймеры общего назначения
- •11.3.1.1. Режим внешнего тактирования 2
- •11.3.1.2. Режим внешнего тактирования 1
- •11.3.2. Режимы синхронизации ведущего/ведомого таймеров
- •11.3.2.1. Разрешение прерываний, относящихся к триггерной цепи
- •11.3.2.2. Использование CubeMX для конфигурации синхронизации ведущего/ведомого устройств
- •11.3.3. Программная генерация связанных с таймером событий
- •11.3.4. Режимы отсчета
- •11.3.5. Режим захвата входного сигнала
- •11.3.5.1. Использование CubeMX для конфигурации режима захвата входного сигнала
- •11.3.6. Режим сравнения выходного сигнала
- •11.3.6.1. Использование CubeMX для конфигурации режима сравнения выходного сигнала
- •11.3.7. Генерация широтно-импульсного сигнала
- •11.3.7.1. Генерация синусоидального сигнала при помощи ШИМ
- •11.3.7.2. Использование CubeMX для конфигурации режима ШИМ
- •11.3.8. Одноимпульсный режим
- •11.3.8.1. Использование CubeMX для конфигурации одноимпульсного режима
- •11.3.9. Режим энкодера
- •11.3.9.1. Использование CubeMX для конфигурации режима энкодера
- •11.3.10.1. Режим датчика Холла
- •11.3.10.2. Комбинированный режим трехфазной ШИМ и другие функции управления двигателем
- •11.3.10.3. Вход сброса таймера и блокировка регистров таймера
- •11.3.10.4. Предварительная загрузка регистра автоперезагрузки
- •11.3.11. Отладка и таймеры
- •11.4. Системный таймер SysTick
- •12. Аналого-цифровое преобразование
- •12.1. Введение в АЦП последовательного приближения
- •12.2. Модуль HAL_ADC
- •12.2.1. Режимы преобразования
- •12.2.1.1. Режим однократного преобразования одного канала
- •12.2.1.2. Режим сканирования с однократным преобразованием
- •12.2.1.3. Режим непрерывного преобразования одного канала
- •12.2.1.4. Режим сканирования с непрерывным преобразованием
- •12.2.1.5. Режим преобразования инжектированных каналов
- •12.2.1.6. Парный режим
- •12.2.2. Выбор канала
- •12.2.3. Разрядность АЦП и скорость преобразования
- •12.2.4. Аналого-цифровые преобразования в режиме опроса
- •12.2.6. Аналого-цифровые преобразования в режиме DMA
- •12.2.6.1. Многократное преобразование одного канала в режиме DMA
- •12.2.6.3. Непрерывные преобразования в режиме DMA
- •12.2.7. Обработка ошибок
- •12.2.8. Преобразования, управляемые таймером
- •12.2.9. Преобразования, управляемые внешними событиями
- •12.2.10. Калибровка АЦП
- •12.3. Использование CubeMX для конфигурации АЦП
- •13.1. Введение в периферийное устройство ЦАП
- •13.2. Модуль HAL_DAC
- •13.2.1. Управление ЦАП вручную
- •13.2.2. Управление ЦАП в режиме DMA с использованием таймера
- •13.2.3. Генерация треугольного сигнала
- •13.2.4. Генерация шумового сигнала
- •14.1. Введение в спецификацию I²C
- •14.1.1. Протокол I²C
- •14.1.1.1. START- и STOP-условия
- •14.1.1.2. Формат байта
- •14.1.1.3. Кадр адреса
- •14.1.1.4. Биты «Подтверждено» (ACK) и «Не подтверждено» (NACK)
- •14.1.1.5. Кадры данных
- •14.1.1.6. Комбинированные транзакции
- •14.1.1.7. Удержание синхросигнала
- •14.1.2. Наличие периферийных устройств I²C в микроконтроллерах STM32
- •14.2. Модуль HAL_I2C
- •14.2.1.1. Операции I/O MEM
- •14.2.1.2. Комбинированные транзакции
- •14.3. Использование CubeMX для конфигурации периферийного устройства I²C
- •15.1. Введение в спецификацию SPI
- •15.1.1. Полярность и фаза тактового сигнала
- •15.1.2. Управление сигналом Slave Select
- •15.1.3. Режим TI периферийного устройства SPI
- •15.1.4. Наличие периферийных устройств SPI в микроконтроллерах STM32
- •15.2. Модуль HAL_SPI
- •15.2.1. Обмен сообщениями с использованием периферийного устройства SPI
- •15.2.2. Максимальная частота передачи, достижимая при использовании CubeHAL
- •15.3. Использование CubeMX для конфигурации периферийного устройства SPI
- •16. Циклический контроль избыточности
- •16.1. Введение в расчет CRC
- •16.1.1. Расчет CRC в микроконтроллерах STM32F1/F2/F4/L1
- •16.2. Модуль HAL_CRC
- •17. Независимый и оконный сторожевые таймеры
- •17.1. Независимый сторожевой таймер
- •17.1.1. Использование CubeHAL для программирования таймера IWDG
- •17.2. Системный оконный сторожевой таймер
- •17.2.1. Использование CubeHAL для программирования таймера WWDG
- •17.3. Отслеживание системного сброса, вызванного сторожевым таймером
- •17.4. Заморозка сторожевых таймеров во время сеанса отладки
- •17.5. Выбор сторожевого таймера, подходящего для вашего приложения
- •18. Часы реального времени
- •18.1. Введение в периферийное устройство RTC
- •18.2. Модуль HAL_RTC
- •18.2.1. Установка и получение текущей даты/времени
- •18.2.1.1. Правильный способ чтения значений даты/времени
- •18.2.2. Конфигурирование будильников
- •18.2.3. Блок периодического пробуждения
- •18.2.5. Калибровка RTC
- •18.2.5.1. Грубая калибровка RTC
- •18.2.5.2. Тонкая калибровка RTC
- •18.2.5.3. Обнаружение опорного тактового сигнала
- •18.3. Использование резервной SRAM
- •III Дополнительные темы
- •19. Управление питанием
- •19.1. Управление питанием в микроконтроллерах на базе Cortex-M
- •19.2. Как микроконтроллеры Cortex-M управляют рабочим и спящим режимами
- •19.2.1. Переход в/выход из спящих режимов
- •19.2.1.1. «Спящий режим по выходу»
- •19.3. Управление питанием в микроконтроллерах STM32F
- •19.3.1. Источники питания
- •19.3.2. Режимы питания
- •19.3.2.1. Рабочий режим
- •19.3.2.2. Спящий режим
- •19.3.2.3. Режим останова
- •19.3.2.4. Режим ожидания
- •19.3.2.5. Пример работы в режимах пониженного энергопотребления
- •19.4. Управление питанием в микроконтроллерах STM32L
- •19.4.1. Источники питания
- •19.4.2. Режимы питания
- •19.4.2.1. Рабочие режимы
- •19.4.2.2. Спящие режимы
- •19.4.2.2.1. Режим пакетного сбора данных
- •19.4.2.3. Режимы останова
- •19.4.2.4. Режимы ожидания
- •19.4.2.5. Режим выключенного состояния
- •19.4.3. Переходы между режимами питания
- •19.4.4. Периферийные устройства с пониженным энергопотреблением
- •19.4.4.1. LPUART
- •19.4.4.2. LPTIM
- •19.5. Инспекторы источников питания
- •19.6. Отладка в режимах пониженного энергопотребления
- •19.7. Использование калькулятора энергопотребления CubeMX
- •20. Организация памяти
- •20.1. Модель организации памяти в STM32
- •20.1.1. Основы процессов компиляции и компоновки
- •20.2.1. Исследование бинарного ELF-файла
- •20.2.2. Инициализация секций .data и .bss
- •20.2.2.1. Пара слов о секции COMMON
- •20.2.3. Секция .rodata
- •20.2.4. Области Стека и Кучи
- •20.2.5. Проверка размера Кучи и Стека на этапе компиляции
- •20.2.6. Различия с файлами скриптов инструментария
- •20.3. Как использовать CCM-память
- •20.3.1. Перемещение таблицы векторов в CCM-память
- •20.4.1. Программирование MPU с использованием CubeHAL
- •21. Управление Flash-памятью
- •21.1. Введение во Flash-память STM32
- •21.2. Модуль HAL_FLASH
- •21.2.1. Разблокировка Flash-памяти
- •21.2.2. Стирание Flash-памяти
- •21.2.3. Программирование Flash-памяти
- •21.3. Байты конфигурации
- •21.3.1. Защита от чтения Flash-памяти
- •21.4. Дополнительные памяти OTP и EEPROM
- •21.5. Задержка чтения Flash-памяти и ускоритель ART™ Accelerator
- •21.5.1. Роль TCM-памятей в микроконтроллерах STM32F7
- •22. Процесс начальной загрузки
- •22.1.1. Программное физическое перераспределение памяти
- •22.1.2. Перемещение таблицы векторов
- •22.1.3. Запуск микропрограммы из SRAM с помощью инструментария GNU MCU Eclipse
- •22.2. Встроенный загрузчик
- •22.2.1. Запуск загрузчика из встроенного программного обеспечения
- •22.2.2. Последовательность начальной загрузки в инструментарии GNU MCU Eclipse
- •22.3. Разработка пользовательского загрузчика
- •22.3.2. Как использовать инструмент flasher.py
- •23. Запуск FreeRTOS
- •23.1. Введение в концепции, лежащие в основе ОСРВ
- •23.2.1. Структура файлов с исходным кодом FreeRTOS
- •23.2.1.2. Как импортировать FreeRTOS с использованием CubeMX и CubeMXImporter
- •23.3. Управление потоками
- •23.3.1. Состояния потоков
- •23.3.2. Приоритеты потоков и алгоритмы планирования
- •23.3.3. Добровольное освобождение от управления
- •23.3.4. Холостой поток idle
- •23.4. Выделение памяти и управление ею
- •23.4.1. Модель динамического выделения памяти
- •23.4.1.1. heap_1.c
- •23.4.1.2. heap_2.c
- •23.4.1.3. heap_3.c
- •23.4.1.4. heap_4.c
- •23.4.1.5. heap_5.c
- •23.4.2. Модель статического выделения памяти
- •23.4.3. Пулы памяти
- •23.4.4. Обнаружение переполнения стека
- •23.5. Примитивы синхронизации
- •23.5.1. Очереди сообщений
- •23.5.2. Cемафоры
- •23.5.3. Сигналы потоков
- •23.6. Управление ресурсами и взаимное исключение
- •23.6.1. Мьютексы
- •23.6.2. Критические секции
- •23.6.3. Обработка прерываний совместно с ОСРВ
- •23.7. Программные таймеры
- •23.7.1. Как FreeRTOS управляет таймерами
- •23.8. Пример из практики: Управление энергосбережением с ОСРВ
- •23.8.1. Перехват холостого потока idle
- •23.8.2. Бестиковый режим во FreeRTOS
- •23.9. Возможности отладки
- •23.9.1. Макрос configASSERT()
- •23.9.2. Статистика среды выполнения и информация о состоянии потоков
- •23.10. Альтернативы FreeRTOS
- •23.10.1. ChibiOS
- •23.10.2. ОС Contiki
- •23.10.3. OpenRTOS
- •24. Продвинутые методы отладки
- •24.1. Введение в исключения отказов Cortex-M
- •24.1.1.1. Как инструментарий GNU MCU Eclipse обрабатывает исключения отказов
- •24.1.1.2. Как интерпретировать содержимое регистра LR при переходе в исключение
- •24.1.2. Исключения отказов и их анализ
- •24.2.1. Представление Expressions
- •24.2.1.1. Мониторы памяти
- •24.2.2. Точки наблюдения
- •24.2.3. Режим Instruction Stepping Mode
- •24.2.4. Keil Packs и представление Peripheral Registers
- •24.2.5. Представление Core Registers
- •24.3. Средства отладки от CubeHAL
- •24.4. Внешние отладчики
- •24.4.1. Использование SEGGER J-Link для отладчика ST-LINK
- •24.4.2. Использование интерфейса ITM и трассировка SWV
- •24.5. STM Studio
- •24.6. Одновременная отладка двух плат Nucleo
- •25. Файловая система FAT
- •25.1. Введение в библиотеку FatFs
- •25.1.1. Использование CubeMX для включения в ваши проекты библиотеки FatFs
- •25.1.2. Наиболее важные структуры и функции FatFs
- •25.1.2.1. Монтирование файловой системы
- •25.1.2.2. Открытие файлов
- •25.1.2.3. Чтение и запись файла
- •25.1.2.4. Создание и открытие каталога
- •25.1.3. Как сконфигурировать библиотеку FatFs
- •26. Разработка IoT-приложений
- •26.2. Ethernet контроллер W5500
- •26.2.1. Как использовать шилд W5500 и модуль ioLibrary_Driver
- •26.2.1.1. Конфигурирование интерфейса SPI
- •26.2.1.2. Настройка буферов сокетов и сетевого интерфейса
- •26.2.2. API-интерфейсы сокетов
- •26.2.2.1. Управление сокетами в режиме TCP
- •26.2.2.2. Управление сокетами в режиме UDP
- •26.2.3. Перенаправление ввода-вывода на сокет TCP/IP
- •26.2.4. Настройка HTTP-сервера
- •26.2.4.1. Веб-осциллограф
- •27. Начало работы над новым проектом
- •27.1. Проектирование оборудования
- •27.1.1. Послойная разводка печатной платы
- •27.1.2. Корпус микроконтроллера
- •27.1.3. Развязка выводов питания
- •27.1.4. Тактирование
- •27.1.5. Фильтрация вывода сброса RESET
- •27.1.6. Отладочный порт
- •27.1.7. Режим начальной загрузки
- •27.1.8. Обратите внимание на совместимость с выводами…
- •27.1.9. …и на выбор подходящей периферии
- •27.1.10. Роль CubeMX на этапе проектирования платы
- •27.1.11. Стратегии разводки платы
- •27.2. Разработка программного обеспечения
- •27.2.1. Генерация бинарного образа для производства
- •Приложение
- •Принудительный сброс микроконтроллера из микропрограммы
- •B. Руководство по поиску и устранению неисправностей
- •Проблемы с установкой GNU MCU Eclipse
- •Проблемы, связанные с Eclipse
- •Eclipse не может найти компилятор
- •Eclipse постоянно прерывается при выполнении каждой инструкции во время сеанса отладки
- •Пошаговая отладка очень медленная
- •Микропрограмма работает только в режиме отладки
- •Проблемы, связанные с STM32
- •Микроконтроллер не загружается корректно
- •Невозможно загрузить микропрограмму или отладить микроконтроллер
- •C. Схема выводов Nucleo
- •Nucleo-F446RE
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F411RE
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F410RB
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F401RE
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F334R8
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F303RE
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F302R8
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F103RB
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F091RC
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F072RB
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F070RB
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-F030R8
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-L476RG
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-L152RE
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-L073R8
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •Nucleo-L053R8
- •Разъемы, совместимые с Arduino
- •Morpho-разъемы
- •D. Корпусы STM32
- •LFBGA
- •LQFP
- •TFBGA
- •TSSOP
- •UFQFPN
- •UFBGA
- •VFQFP
- •WLCSP
- •E. Изменения книги
- •Выпуск 0.1 – Октябрь 2015
- •Выпуск 0.2 – 28 октября 2015
- •Выпуск 0.2.1 – 31 октября 2015
- •Выпуск 0.2.2 – 1 ноября 2015
- •Выпуск 0.3 – 12 ноября 2015
- •Выпуск 0.4 – 4 декабря 2015
- •Выпуск 0.5 – 19 декабря 2015
- •Выпуск 0.6 – 18 января 2016
- •Выпуск 0.6.1 – 20 января 2016
- •Выпуск 0.6.2 – 30 января 2016
- •Выпуск 0.7 – 8 февраля 2016
- •Выпуск 0.8 – 18 февраля 2016
- •Выпуск 0.8.1 – 23 февраля 2016
- •Выпуск 0.9 – 27 марта 2016
- •Выпуск 0.9.1 – 28 марта 2016
- •Выпуск 0.10 – 26 апреля 2016
- •Выпуск 0.11 – 27 мая 2016
- •Выпуск 0.11.1 – 3 июня 2016
- •Выпуск 0.11.2 – 24 июня 2016
- •Выпуск 0.12 – 4 июля 2016
- •Выпуск 0.13 – 18 июля 2016
- •Выпуск 0.14 – 12 августа 2016
- •Выпуск 0.15 – 13 сентября 2016
- •Выпуск 0.16 – 3 октября 2016
- •Выпуск 0.17 – 24 октября 2016
- •Выпуск 0.18 – 15 ноября 2016
- •Выпуск 0.19 – 29 ноября 2016
- •Выпуск 0.20 – 28 декабря 2016
- •Выпуск 0.21 – 29 января 2017
- •Выпуск 0.22 – 2 мая 2017
- •Выпуск 0.23 – 20 июля 2017
- •Выпуск 0.24 – 11 декабря 2017
- •Выпуск 0.25 – 3 января 2018
- •Выпуск 0.26 – 7 мая 2018
Запуск FreeRTOS |
648 |
Какую инструкцию использовать для перехода в спящий режим?
Микроконтроллеры на базе Cortex-M предлагают две ассемблерные инструкции для перехода в режимы пониженного энергопотребления: WFI и WFE. Но какая из них больше подходит для вызова из перехвата потока idle? Инструкция WFI будет удерживать ядро микроконтроллера в выключенном состоянии до возникновения прерывания. Это может быть прерывание от таймера SysTick или другого периферийного устройства. Напротив, инструкция WFE является условной: она не переводит в спящий режим, если установлен регистр событий (WFI всегда переводит, а затем выводит из этого режима, если отложено прерывание, что приводит к потере нескольких тактовых циклов ЦПУ). Более того, она позволяет пробудить процессор, если мы используем события, связанные с заданным периферийным устройством вместо прерываний, в то время как он все еще может пробудиться в случае прерываний. По этим причинам инструкция WFE всегда предпочтительнее инструкции WFI в циклах потока idle.
Экономия энергии, которая может быть достигнута с помощью этого простого метода, ограничена необходимостью периодического выхода из и повторного перехода в режим пониженного энергопотребления для обработки прерываний от тиков (которые связаны с частотой опустошения таймера SysTick), как показано на рисунке 15. Кроме того, если частота прерывания от тика слишком высока, то затрачиваемые энергия и время на переход в и выход из режима пониженного энергопотребления для каждого тика перевешивают любой потенциальный выигрыш в экономии энергии для всех, кроме самых легких, режимов энергосбережения.
Рисунок 15: Влияние прерываний от SysTick на энергопотребление
По этим причинам совершенно нецелесообразно переходить в более глубокие спящие режимы, например, в режим останова. Кроме того, накладные расходы, связанные с переходом в и выходом из режима пониженного энергопотребления, влияют на надежность счетчика тиков, вызывая временные сдвиги, которые влияют на программные таймеры и вызывают задержки тайм-аута.
23.8.2. Бестиковый режим во FreeRTOS
Чтобы решить эти проблемы, FreeRTOS предлагает рабочий режим, называемый режи-
мом бестикового холостого потока idle, англ. tickless idle mode (или просто бестиковым
режимом), который останавливает периодическое прерывание от тика во время
Запуск FreeRTOS |
649 |
периодов работы потока idle. Продолжительность этих периодов произвольна: она может составлять несколько миллисекунд, секунд, минут или даже дней. Когда микроконтроллер выходит из режима пониженного энергопотребления, FreeRTOS выполняет корректирующую подстройку значения счетчика тиков при повторном запуске прерываний от тиков, если это необходимо (подробнее об этом скоро). Это означает, что FreeRTOS не останавливает таймер вообще: она просто конфигурирует таймер так, чтобы он достиг максимального периода обновления перед переполнением. Когда микроконтроллер снова пробуждается, ядро считывает значение счетчика таймера и вычисляет количество прошедших тиков в течение времени сна.
Например, предположим, что 16-разрядный таймер работает на частоте ядра SYSCLK в 48 МГц. Максимальные значения для регистров Period и Prescaler равны 0xFFFF. Таким образом, вместо того, чтобы сконфигурировать таймер так, чтобы он переполнялся через 1 мс, мы можем сконфигурировать его на переполнение после:
Событие обновления = |
48000000 |
90 с |
|
0xFFFF 0xFFFF |
|||
|
|
FreeRTOS предоставляет встроенную функциональность бестикового режима, которая включается определением макроса configUSE_TICKLESS_IDLE со значением 1 в файле FreeRTOSConfig.h. Встроенный бестиковый режим является платформозависимым: по этой причине он реализован в файле port.c. Встроенный бестиковый режим доступен для всех ядер Cortex-M, но он имеет одно важное ограничение: он реализован на таймере SysTick, потому что это единственный таймер, доступный во всех микроконтроллерах, базирующихся на данной архитектуре.
Но что в этом такого плохого? Таймер SysTick – это 24-разрядный таймер нисходящего отсчета, работающий на той же тактовой частоте, что и ядро. К сожалению, ее нельзя так просто предварительно масштабировать (prescaled), как у обычных таймеров STM32 (у него есть только одно значение предделителя, равное 8 во всех микроконтроллерах STM32). Например, для STM32F030, работающего на частоте 48 МГц, мы получим, применяя уравнение [1] из Главы 11, что таймер SysTick будет переполняться каждые:
Событие обновления = |
|
48000000 |
0,350 |
Гц 2,8 с |
|
8 |
0xFFFFFF |
||||
|
|
|
Поскольку мы не должны потерять событие переполнения вообще, в противном случае глобальный счетчик тиков будет скомпрометирован38, нам придется снова просыпаться, даже если нам не нужно делать чего-то значимого. Для большинства приложений с пониженным энергопотреблением это достаточно короткое время между двумя последовательными периодами сна.
Решение может представлять собой снижение частоты HCLK для дальнейшего увеличения периода переполнения, но мы должны уделить внимание слишком сильному снижению частоты ядра, поскольку, когда микроконтроллер выходит из режима пониженного энергопотребления для обслуживания прерывания, низкая частота HCLK может поставить под угрозу надежность системы. А увеличивать тактовую частоту из ISR – не разумное решение.
38 Как мы узнаем позже, при определенных обстоятельствах мы можем безопасно прекратить увеличение глобального счетчика тиков. Это может быть сделано, когда мы не собираемся использовать программные таймеры и тайм-ауты: если все потоки заблокированы или приостановлены на неопределенный срок, тогда можно полностью отключить генератор временного отсчета.
Запуск FreeRTOS |
650 |
Почему точность подсчета тиков так важна?
Точность подсчета глобальных тиков важна по двум основным причинам: для обеспечения одинакового кванта времени для всех готовых к выполнению потоков с одинаковым приоритетом (если разрешено вытеснение) и для обеспечения точных задержек тайм-аута (времени ожидания). На самом деле, несколько блокирующих процедур ОС позволяют указать максимальную задержку, которую мы готовы выжидать до выполнения операции. Тайм-ауты указываются в миллисекундах в API-интерфейсе CMSIS-RTOS, и они конвертируются базовой реализацией в тиках, зная, что тик обычно длится 1 мс для платформозависимого кода FreeRTOS под Cortex-M. Если мы указываем таймаут, меньший, чем osWaitForever, то важно, чтобы подсчет тиков был наиболее точным. Подсчет глобальных тиков также используется FreeRTOS для реализации программных таймеров.
Другим ограничением в использовании таймера SysTick является то, что его нельзя использовать в режимах останова, поскольку источник тактового сигнала HCLK в нем отключен. Это одно из типовых применений таймеров с пониженным энергопотреблением (LPTIM), предоставляемых большинством микроконтроллеров STM32L. Таймеры LPTIM, по сути, способны работать независимо от системного тактового сигнала: это позволяет использовать их даже в режимах останова.
По всем этим причинам сейчас мы собираемся предоставить пользовательскую реализацию функциональности режима бестикового холостого потока idle, который может быть предоставлен для любого платформозависимого кода FreeRTOS (включая те, которые предоставляют встроенную реализацию), определив configUSE_TICKLESS_IDLE со значением 2 в FreeRTOSConfig.h. Когда выбрана эта конфигурация, мы можем переопреде-
лить две функции FreeRTOS: void prvSetupTimerInterrupt()39 и void vPortSuppressTick-
sAndSleep(). Первая используется ядром для настройки таймера, используемого в качестве генератора тиков. Последняя автоматически вызывается ядром при выполнении некоторых условий (которые мы увидим позже), и мы можем перейти в режимы пониженного энергопотребления, задерживая или вообще приостанавливая периодические прерывания от таймера.
23.8.2.1.Схема для бестикового режима
Прежде чем мы углубимся в реальный исходный код, необходимый для реализации этих двух процедур, лучше взглянуть на их основную логику без попытки разобраться в деталях реализации.
1/* Переопределение определения vPortSetupTimerInterrupt() по умолчанию версией,
2которая конфигурирует другой таймер STM32 для генерации прерывания от тика. */
3void vPortSetupTimerInterrupt(void) {
4/* Масштабирование тактового сигнала, чтобы можно было получить более длительные
5бестиковые периоды, делением частоты HCLK на требуемую частоту тиков (обычно 1 мс). */
7htimx.Instance = TIMx;
8htimx.Init.Prescaler = PRESCALER_VALUE;
9htimx.Init.Period = PERIOD_VALUE
10 HAL_TIM_Base_Init(&htimx);
39 В платформозависимых кодах Cortex-M3/4 эта функция называется vPortSetupTimerInterrupt().
Запуск FreeRTOS |
651 |
11
12/* Разрешение прерываний TIMx. Должны выполняться с самым низким приоритетом прерыв-й */
13HAL_NVIC_SetPriority(TIMx_IRQn, configLIBRARY_LOWEST_INTERRUPT_PRIORITY, 0);
14HAL_NVIC_EnableIRQ(TIMx_IRQn);
15
16/* Запуск таймера */
17HAL_TIM_Base_Start_IT(&htimx);
18}
Первая процедура, которую мы собираемся переопределить, это vPortSetupTimerInterrupt(). Она всего лишь использует один из доступных таймеров STM32 в качестве генератора временного отсчета, конфигурируя правильные значения Period и Prescaler для достижения прерывания от тиков с частотой, равной 1 кГц. ISR таймера (показанная ниже) будет отвечать за инкрементирование глобального счетчика тиков.
Прочитайте внимательно
В Главе 10 мы увидели, что HAL предназначен для автоматического вызова SystemCoreClockUpdate() при изменении частоты HCLK. Это гарантирует нам, что прерывание от SysTick генерируется каждые 1 мс, даже если частота ядра изменяется. Если вместо этого мы используем другой таймер для подсчета тиков ОСРВ, то мы должны тщательно убедиться, что таймер переконфигурирован соответствующим образом, когда изменяется тактовая частота шины APB, к которой подключен таймер.
В следующих строках кода показана возможная реализация vPortSuppressTicsAndSleep(), которая вызывается, когда выполняются оба следующих условия:
1.Холостой поток idle – единственный поток, способный выполняться, поскольку все потоки приложения находятся либо в состоянии «заблокирован», либо в состо-
янии «приостановлен».
2.По крайней мере проходит n завершенных периодов тиков, прежде чем ядро выводит поток приложения из состояния «заблокирован», где n устанавливается мак-
росом configEXPECTED_IDLE_TIME_BEFORE_SLEEP в файле FreeRTOSConfig.h40.
Если указанные выше условия выполняются, то планировщик приостанавливается и вызывается функция vPortSuppressTicksAndSleep(), что позволяет нам временно подавлять прерывание от тика или задерживать его выполнение.
20/* Переопределение определения vPortSuppressTicksAndSleep() по умолчанию версией, которая
21использует другой таймер STM32 для вычисления того, как долго МК в состоянии сна */
22void vPortSuppressTicksAndSleep(TickType_t xExpectedIdleTime) {
23unsigned long ulLowPowerTimeBeforeSleep, ulLowPowerTimeAfterSleep;
24eSleepModeStatus eSleepStatus;
25
26 /* Чтение текущего времени из таймера, сконфигурированного
40 Это пользовательский параметр, который представляет собой дополнительную задержку перед началом процедуры подавления тиков. Поскольку данная процедура требует значительных вычислительных ресурсов и может вносить незначительные временные сдвиги в подсчет глобальных тиков, мы можем программно решить подождать как минимум n последовательных тиков, прежде чем начинать процедуру.
Запуск FreeRTOS |
652 |
27функцией vPortSetupTimerInterrupt() */
28ulLowPowerTimeBeforeSleep = __HAL_TIM_GET_COUNTER(TIMx);
30/* Остановка таймера, генерирующего прерывание от тика. */
31 HAL_TIM_Base_Stop_IT(TIMx); 32
33/* Переход в критическую секцию, которая запрещает все прерывания, способные
34вывести микроконтроллер из спящего режима. */
35__disable_irq();
36
37/* Убеждаемся, что все еще можно перейти в спящий режим. */
38eSleepStatus = eTaskConfirmSleepModeStatus();
39
40if (eSleepStatus == eAbortSleep) {
41/* Задача была переведена из Заблокированного состояния с тех пор, как был выполнен
42этот макрос, или переключение контекста удерживается отложенным. Не переходим в
43состояние сна. Перезапуск тика и выход из критической секции. */
44HAL_TIM_Base_Start_IT (TIMx)
45__enable_irq();
46} else {
47if (eSleepStatus == eNoTasksWaitingTimeout) {
48/* Нет никаких выполняющихся задач и нет задач, которые заблокированы
49до истечения тайм-аута. Предполагая, что приложению все равно, сместится ли время
50тика относительно календарного времени или нет, переходим в глубокий сон,
51из которого можно пробудиться только другим прерыванием. */
52StopMode();
53} else {
54/* Конфигурирование прерывания, выводящего микроконтроллер из состояния пониженного
55энергопотребления при необходимости следующего исполнения ядра. Прерывание
56должно генерироваться источником, который остается работоспособным,
57когда микроконтроллер находится в состоянии пониженного энергопотребления. */
58vSetWakeTimeInterrupt(xExpectedIdleTime);
59
60/* Переход в состояние пониженного энергопотребления. */
61SleepMode();
62
63/* Определение того, сколько времени микроконтроллер фактически находился в
64состоянии пониженного энергопотребления, которое будет меньше, чем
65xExpectedIdleTime, если микроконтроллер был выведен из режима пониженного
66энергопотребления из-за прерывания, отличного от сконфигурированного вызовом
67vSetWakeTimeInterrupt(). Обратите внимание, что планировщик приостанавливается
68до вызова vPortSuppressTicksAndSleep() и возобновляется при ее возврате.
69Поэтому другие задачи не будут выполняться, пока эта функция не завершится. */
70ulLowPowerTimeAfterSleep = __HAL_TIM_GET_COUNTER(TIMx);
71
72/* Корректировка подсчета тиков ядра для учета времени, которое
73микроконтроллер провел в состоянии пониженного энергопотребления. */
74vTaskStepTick( ulLowPowerTimeAfterSleep – ulLowPowerTimeBeforeSleep );
75}
76}
77
Запуск FreeRTOS |
653 |
78/* Выход из критической секции – это может быть возможным сделать сразу
79после вызова prvSleep(). */
80__enable_irq();
81
82/* Перезапуск таймера, генерирующего прерывание от тика. */
83HAL_TIM_Base_Stop_IT(TIMx);
84}
Процедура начинается с сохранения текущего значения счетчика таймера до его остановки. Все прерывания запрещаются для предотвращения условий гонки при переходе в критическую секцию путем вызова функции CMSIS __disable_irq(). Как было сказано ранее, vPortSetupTimerInterrupt() вызывается, когда планировщик приостановлен, но сработанное прерывание до того, как мы перейдем в критическую секцию в строке 35, может запросить ядро возобновить выполнение другого потока в состоянии «заблокирован»41. Вызывая eTaskConfirmSleepModeStatus(), мы можем узнать, нужно ли прервать процедуру подавления тиков, возобновив таймер. Если функция возвращает значение eAbortSleep, то мы перезапускаем таймер генератора тиков и немедленно выходим из критической секции, повторно разрешая все прерывания (строка 45). Если, напротив, функция возвращает значение eNoTasksWaitingTimeout, то это означает, что отсутствуют выполняющиеся потоки, нет программных таймеров42 или других потоков, заблокированных с определенным тайм-аутом. Поскольку в этом случае нет необходимости сохранять точность подсчета тиков (нет таймеров, нет запущенных потоков, нет тайм-аутов), мы можем перейти в режим останова, что приведет к тому, что тактирование таймера будет остановлено. Микроконтроллер выйдет из процедуры StopMode(), когда внешнее прерывание пробудит его.
Если, напротив, функция eTaskConfirmSleepModeStatus() возвращает значение eStandardSleep, соответствующее ветви else в строке 53, то мы можем спать в течение времени, равного параметру xExpectedIdleTime, соответствующему общему числу периодов тиков, прежде чем поток будет переведен обратно в состояние «готов к выполнению». Следовательно, значением параметра является время, в течение которого микроконтроллер может безопасно оставаться в состоянии пониженного энергопотребления, при этом прерывание от тика временно подавляется. ISR таймера пробудит микроконтроллер, выйдя из процедуры SleepMode(), и глобальный счетчик тиков будет скорректирован в строке 74.
23.8.2.2.Пользовательский алгоритм бестикового режима
Приведенный выше псевдокод представляет собой схему, которую все программисты могут использовать для реализации своего пользовательского бестикового режима. Например, если мы знаем, что наше программное обеспечение не использует программные таймеры и конечные тайм-ауты, то мы можем безопасно обрабатывать только случай режима глубокого сна.
41Это происходит потому, что данная процедура вызывается в IRQ с наименьшим возможным приоритетом, как было показано ранее. Таким образом, более привилегированный IRQ может возобновить выполнение другой заблокированной задачи.
42Обратите внимание: в нашем коде недостаточно использовать таймеры. Макрос configUSE_TIMERS в FreeRTOSConfig.h должен быть установлен в 0, в противном случае eTaskConfirmSleepModeStatus() никогда не возвращает значение eNoTasksWaitingTimeout.
Запуск FreeRTOS |
654 |
А теперь мы собираемся реализовать собственный пользовательский алгоритм бестикового режима, проанализировав реальный код, предназначенный для работы на микроконтроллере STM32F030. Обратитесь к примерам книги для других микроконтроллеров STM32, несмотря на то что их реализация практически такая же.
Имя файла: src/tickless-mode.c
7/* Вычисление того, сколько инкрементирований тактовыми импульсами составляют период тика.
8Поскольку мы используем предделитель, равный 1599, и предполагаем тактовую
9частоту равной 48 МГц, то согласно уравнению [1] в Главе 11 такое
10значение периода обеспечивает переполнение таймера, равное 1 мс. */
11static const uint32_t ulMaximumPrescalerValue = 1599;
12static const uint32_t ulPeriodValueForOneTick = 29;
13
14/* Содержит максимальное количество тиков, которое может быть подавлено - это,
15в основном, то, насколько далеко в будущем может быть сгенерировано прерывание без
16потери события переполнения вообще. Оно устанавливается во время инициализации. */
17static TickType_t xMaximumPossibleSuppressedTicks = 0;
18
19/* Флаг, установленный из прерывания от тика, чтобы позволить обработке сна знать,
20был ли выход из спящего режима из-за прерывания от тика или из-за другого прерывания. */
21static volatile uint8_t ucTickFlag = pdFALSE;
22
23/* Дескриптор HAL таймера TIM6 */
24TIM_HandleTypeDef htim6;
25
26 void xPortSysTickHandler( void );
27
28/* Переопределение определения vPortSetupTimerInterrupt() по умолчанию, которая
29определена как __weak в уровне платформозависимого кода FreeRTOS для Cortex-M0,
30версией, конфигурирующей TIM6 для генерации прерывания от тика. */
31void prvSetupTimerInterrupt(void) {
32uint32_t ulPrescalerValue;
33
34/* Разрешение подачи тактирования на TIM6. */
35__HAL_RCC_TIM6_CLK_ENABLE();
36
37/* Убеждаемся, что тактирование останавливается в режиме отладки. */
38__HAL_DBGMCU_FREEZE_TIM6();
39
40/* Конфигурирование таймера TIM6 */
41htim6.Instance = TIM6;
42htim6.Init.Prescaler = (uint16_t) ulMaximumPrescalerValue;
43htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
44htim6.Init.Period = ulPeriodValueForOneTick;
45HAL_TIM_Base_Init(&htim6);
46
47/* Разрешение прерываний TIM6. Должно выполняться с самым низким приоритетом прерыв-й */
48HAL_NVIC_SetPriority(TIM6_IRQn, configLIBRARY_LOWEST_INTERRUPT_PRIORITY, 0);
49HAL_NVIC_EnableIRQ(TIM6_IRQn);
50
Запуск FreeRTOS |
655 |
51HAL_TIM_Base_Start_IT(&htim6);
52/* См. комментарии, где объявлен xMaximumPossibleSuppressedTicks. */
53xMaximumPossibleSuppressedTicks = ((unsigned long) USHRT_MAX)
54/ ulPeriodValueForOneTick;
55}
56
57/* Функция обратного вызова, вызываемая HAL при переполнении TIM6. */
58void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
59if (htim->Instance == TIM6) {
60xPortSysTickHandler();
61
62/* В случае, если это первый тик со времени выхода микроконтроллера из режима
63пониженного энергопотребления. Период сконфигурирован vPortSuppressTicksAndSleep().
64Здесь значение перезагрузки сбрасывается до значения по умолчанию. */
65__HAL_TIM_SET_AUTORELOAD(htim, ulPeriodValueForOneTick);
66
67/* ЦПУ пробудилось из-за тика. */
68ucTickFlag = pdTRUE;
69}
70}
Первые две функции, которые мы собираемся проанализировать, касаются конфигурации таймера, используемого в качестве генератора тиков, и обработки соответствующего прерывания от переполнения. Функция prvSetupTimerInterrupt() автоматически вызывается FreeRTOS при вызове процедуры osKernelStart(). Она конфигурирует таймер TIM6 так, что он истекает каждые 1 мс. Соответствующее прерывание разрешено, и приоритет ISR установлен на самый низкий (помните, что, если нет необходимости в другом, всегда важно устанавливать ISR таймера с самым низким приоритетом). Обратный вызов HAL_TIM_PeriodElapsedCallback() просто увеличивает глобальный счетчик тиков на 1. Не думайте о командах в строках [65:68], поскольку они будут понятны позже.
Теперь мы собираемся проанализировать наиболее сложную часть: функцию vPortSuppressTicksAndSleep(). Мы разделим ее на блоки, чтобы было проще анализировать ее код. Настоятельно рекомендуется держать реальный код в IDE под рукой.
Имя файла: src/tickless-mode.c
78 void vPortSuppressTicksAndSleep(TickType_t xExpectedIdleTime) {
79uint32_t ulCounterValue, ulCompleteTickPeriods;
80eSleepModeStatus eSleepAction;
81TickType_t xModifiableIdleTime;
82const TickType_t xRegulatorOffIdleTime = 50;
84/* Убедимся, что значение перезагрузки TIM6 не переполняет счетчик. */
85if (xExpectedIdleTime > xMaximumPossibleSuppressedTicks) {
86xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
87 }
88
89/* Вычисление значения перезагрузки, необходимого для ожидания xExpectedIdleTime
90периодов тиков. */
Запуск FreeRTOS |
656 |
91 ulCounterValue = ulPeriodValueForOneTick * xExpectedIdleTime;
92
93/* Переход в критическую секцию, чтобы избежать условий гонки. */
94__disable_irq();
95
96/* Если переключение контекста отложено, тогда отказ от перехода в режим
97пониженного энергопотребления, так как переключение контекста могло быть
98отложено внешним прерыванием, которое требует обработки. */
99eSleepAction = eTaskConfirmSleepModeStatus();
100if (eSleepAction == eAbortSleep) {
101/* Повторное разрешение прерываний. */
102__enable_irq();
103return;
104} else if (eSleepAction == eNoTasksWaitingTimeout) {
105/* Остановка таймера TIM6 */
106HAL_TIM_Base_Stop_IT(&htim6);
107
108/* Определяемый пользователем макрос, который позволяет вставить сюда код приложения.
109Такой код приложения можно использовать для дальнейшего снижения энергопотребления
110путем отключения I/O, тактирования периферийных устройств, Flash-памяти и т. д. */
111configPRE_STOP_PROCESSING();
112
113/* Нет никаких выполняющихся задач и нет задач, которые заблокированы до истечения
114тайм-аута. Предполагая, что приложению все равно, сместится ли время тика
115относительно календарного времени или нет, переходим в глубокий сон, пробудиться
116из которого можно только (в этом демонстрационном случае) пользовательской кнопкой,
117нажимаемой на плате STM32L discovery. Если для точного отслеживания календарного
118времени приложению требуется время тиков, то для грубой корректировки можно
119использовать периферийное устройство RTC. */
120HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);
121
122/* Определяемый пользователем макрос, который позволяет вставить сюда код приложения.
123Такой код приложения можно использовать для отмены любых действий, предпринятых
124configPRE_STOP_PROCESSING(). В этой демонстрации configPOST_STOP_PROCESSING()
125используется для повторной инициализации тактирования, которое было
126отключено при переходе в режим ОСТАНОВА. */
127configPOST_STOP_PROCESSING();
128
129/* Перезапуск тиков. */
130HAL_TIM_Base_Start_IT(&htim6);
132/* Повторное разрешение прерываний. */
133__enable_irq();
134}
Функция начинает проверять, меньше ли ожидаемое время простоя, то есть временное окно, в котором мы можем безопасно остановить генерацию тиков, чем xMaximumPossi-
bleSuppressedTicks: это значение вычисляется внутри процедуры prvSetupTimerInter-
rupt() в соответствии с заданными значениями Prescaler и Period. Затем в строке 91 она вычисляет значение Period для использования, так что таймер переполнится по
Запуск FreeRTOS |
657 |
истечении времени xExpectedIdleTime. Чтобы избежать условий гонки, мы переходим в критическую секцию (строка 94) и вызываем eTaskConfirmSleepModeStatus(), чтобы решить, как действовать в процедуре подавления тиков. Если функция возвращает eNoTasksWaitingTimeout, тогда мы можем вообще остановить таймер TIM6 и перейти в режим останова до тех пор, пока событие или прерывание не пробудят микроконтроллер.
Имя файла: src/tickless-mode.c
135else {
136/* Остановка на мгновение TIM6. Время, в течение которого TIM6 будет остановлен, не
137учитывается в данной реализации (как это происходит в общей реализации), поскольку
138тактовый сигнал настолько медленный, что в любом случае он вряд ли будет
139остановлен на полном периоде отсчета. */
140HAL_TIM_Base_Stop_IT(&htim6);
141
142/* Перед сном флаг тика устанавливается ложным. Если он истинен при выходе
143из спящего режима, то, вероятно, был выход из спящего режима, поскольку тик
144был подавлен в течение всего периода xExpectedIdleTime. */
145ucTickFlag = pdFALSE;
146
147/* Отлов переполнения до следующего расчета. */
148configASSERT(ulCounterValue >= __HAL_TIM_GET_COUNTER(&htim6));
150/* Регулирование значения TIM6 для учета того, что текущий временной
151интервал уже частично завершен. */
152ulCounterValue -= (uint32_t) __HAL_TIM_GET_COUNTER(&htim6);
154/* Отлов переполнения/опустошения перед записью рассчитанного значения в TIM6. */
155configASSERT(ulCounterValue < ( uint32_t ) USHRT_MAX);
156configASSERT(ulCounterValue != 0);
158/* Обновление для использования рассчитанного значения переполнения. */
159__HAL_TIM_SET_AUTORELOAD(&htim6, ulCounterValue);
160__HAL_TIM_SET_COUNTER(&htim6, 0);
162/* Перезапуск таймера TIM6. */
163 HAL_TIM_Base_Start_IT(&htim6); 164
165/* Разрешение приложению определять некоторую обработку перед сном.
166Это стандартный макрос configPRE_SLEEP_PROCESSING(), описанный на
167веб-сайте FreeRTOS.org. */
168xModifiableIdleTime = xExpectedIdleTime;
169configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
170
171/* xExpectedIdleTime устанавливается в 0 с помощью configPRE_SLEEP_PROCESSING(),
172что означает, что определенный в приложении код уже выполнил инструкцию
173ожидания/сна. */
174if (xModifiableIdleTime > 0) {
175/* Используемый спящий режим зависит от ожидаемого времени простоя,
176поскольку чем глубже сон, тем дольше время пробуждения. См. комментарии
Запуск FreeRTOS |
658 |
177в верхней части main_low_power.c. Обратите внимание, что
178xRegulatorOffIdleTime установлен исключительно для удобства
179демонстрации и не предназначен быть оптимизированным значением. */
180if (xModifiableIdleTime > xRegulatorOffIdleTime) {
181/* Немного более сберегающий спящий режим с более долгим временем пробуждения. */
182HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
183} else {
184/* Немного более затратный спящий режим с более быстрым временем пробуждения. */
185HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
186}
187}
Если eTaskConfirmSleepModeStatus() возвращает eStandardSleep, то мы можем перейти в
спящий режим. Таймер останавливается, и в его Period устанавливается (в строке 159) значение, вычисленное ранее (в строке 91). configPRE_SLEEP_PROCESSING() – это макрос, который мы можем реализовать для предварительного выполнения операций перед спящим режимом (например, в некоторых микроконтроллерах STM32 требуется снизить тактовую частоту или мы могли бы использовать этот макрос для отключения ненужных периферийных устройств). Таким образом, мы можем перейти в спящий режим или в спящий режим с пониженным энергопотреблением в соответствии с вычисленным вре-
менем сна (в некоторых микроконтроллерах STM32, выходящих из спящего режима с пониженным энергопотреблением, требуется больше времени, поэтому они бесполезно тратят много энергии, если период сна слишком короткий).
Имя файла: src/tickless-mode.c
189/* Разрешение приложению определять некоторую обработку после сна.
190Это стандартный макрос configPOST_SLEEP_PROCESSING(), описанный на
191веб-сайте FreeRTOS.org. */
192configPOST_SLEEP_PROCESSING( xModifiableIdleTime );
193
194/* Повторное разрешение прерываний. Если таймер переполнился в течение
195этого периода, то это приведет к вызову TIM6_IRQHandler(). Поэтому
196глобальный счетчик тиков увеличивается на 1 и в переменной ulTickFlag
197устанавливается значение pdTRUE.
198Обратите внимание, что в примере для STM32L в официальном дистрибутиве
199FreeRTOS прерывания снова разрешаются после остановки TIM6. Это неправильно,
200поскольку это приводит к тому, что IRQ остается отложенным, несмотря на то что
201он был установлен. Таким образом, мы должны сначала повторно разрешить прерывания -
202это вызывает запуск отложенного IRQ TIM6 - а затем остановить таймер. */
203__enable_irq();
204
205/* Остановка TIM6. Опять же, время, в течение которого останавливается тактирование,
206здесь не учитывается (как это обычно бывает), тактовый сигнал настолько медленный,
207что в любом случае он вряд ли будет остановлен на полном периоде отсчета. */
208HAL_TIM_Base_Stop_IT(&htim6);
209
210if (ucTickFlag != pdFALSE) {
211/* Микроконтроллер пробудился таймером TIM6. Таким образом, мы
212отлавливаем переполнения перед следующим вычислением. */
213configASSERT(
Запуск FreeRTOS |
659 |
214 ulPeriodValueForOneTick >= (uint32_t ) __HAL_TIM_GET_COUNTER(&htim6)); 215
216/* Прерывание от тика уже выполнено, однако, поскольку эта функция
217вызывается с приостановленным планировщиком, фактическая обработка
218тика не будет происходить до тех пор, пока эта функция не будет завершена.
219Сбрасываем значение перезагрузки в то, что осталось от этого периода. */
220ulCounterValue = ulPeriodValueForOneTick
221- (uint32_t) __HAL_TIM_GET_COUNTER(&htim6);
222
223/* Отлов опустошения/переполнения до использования рассчитанного значения. */
224configASSERT(ulCounterValue <= ( uint32_t ) USHRT_MAX);
225configASSERT(ulCounterValue != 0);
226
227/* Использование рассчитанного значения перезагрузки. */
228__HAL_TIM_SET_AUTORELOAD(&htim6, ulCounterValue);
229__HAL_TIM_SET_COUNTER(&htim6, 0);
230
231/* Обработчик прерывания от тика уже отложит обработку тиков в ядре.
232Поскольку отложенный тик будет обработан, как только эта функция
233завершится, значение тика, обслуживаемое тиком, будет на единицу
234меньше, чем время, потраченное на сон. Фактический шаг тика
235появляется позже в этой функции. */
236ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
237} else {
238/* Что-то, кроме прерывания от тика, закончило сон.
239Сколько полных тиков прошло, пока процессор
240спал? */
241ulCompleteTickPeriods = ((uint32_t) __HAL_TIM_GET_COUNTER(&htim6))
242/ ulPeriodValueForOneTick;
243
244/* Проверка на переполнения/опустошения до следующего расчета. */
245configASSERT(
246((uint32_t ) __HAL_TIM_GET_COUNTER(&htim6)) >=
247(ulCompleteTickPeriods * ulPeriodValueForOneTick));
248
249/* Значение перезагрузки устанавливается в любую оставшуюся
250часть периода одного тика. */
251ulCounterValue = ((uint32_t) __HAL_TIM_GET_COUNTER(&htim6))
252- (ulCompleteTickPeriods * ulPeriodValueForOneTick);
253configASSERT(ulCounterValue <= ( uint32_t ) USHRT_MAX);
254if (ulCounterValue == 0) {
255/* Не осталось части периода одного тика. */
256ulCounterValue = ulPeriodValueForOneTick;
257ulCompleteTickPeriods++;
258}
259__HAL_TIM_SET_AUTORELOAD(&htim6, ulCounterValue);
260__HAL_TIM_SET_COUNTER(&htim6, 0);
261}
262
263 /* Перезапуск TIM6, чтобы он достиг значения перезагрузки. Значение
Запуск FreeRTOS |
660 |
264перезагрузки будет установлено равным значению, необходимому для генерации
265ровно одного периода тика до следующего выполнения прерывания TIM6. */
266HAL_TIM_Base_Start_IT(&htim6);
267
268/* Заводим тик вперед на количество периодов тиков, чтобы ЦПУ
269оставался в состоянии пониженного энергопотребления. */
270vTaskStepTick(ulCompleteTickPeriods);
271}
272}
Когда микроконтроллер выходит из спящего режима из-за переполнения таймера или из-за генерации другого прерывания, макрос configPOST_SLEEP_PROCESSING() позволяет нам выполнить необходимые операции, такие как восстановление некоторых периферийных устройств или увеличение тактовой частоты. А теперь имеет место сложная часть, и мы должны подробно объяснить участвующие в ее выполнении операции.
После того как микроконтроллер выходит из режима пониженного энергопотребления, ISR демаскируются путем выхода из критической секции (строка 203). Это приведет к вызову функции ISR TIM6_IRQHandler(), если мы вышли из спящего режима из-за пе-
реполнения таймера. Когда это происходит, вызывается функция HAL_TIM_PeriodElapsedCallback(): это приводит к тому, что для ucTickFlag устанавливается значение TRUE, а для Period таймера устанавливается стандартное значение (29). Если, напротив, микроконтроллер вышел из режима пониженного энергопотребления по другой причине (например, он был пробужден прерыванием UART_RX), ucTickFlag равен FALSE.
Код в строке 210 проверяет состояние ucTickFlag. Если он равен TRUE, то глобальный счетчик тиков увеличивается на значение, равное xExpectedIdleTime минус единица, поскольку счетчик тиков уже был увеличен на единицу с помощью процедуры HAL_TIM_PeriodElapsedCallback() (ISR вызывается, как только мы покидаем критическую секцию в строке 203). Если, напротив, он равен FALSE, то мы вычисляем, сколько времени микроконтроллер провел в спящем режиме, и соответственно увеличиваем счетчик
тиков.
Данный алгоритм может быть адаптирован в соответствии с вашими потребностями. Например, если вы работаете на платформе STM32L, то можете рассмотреть возможность использования таймера LPTIM в режиме останова, чтобы вы могли знать, сколько тиков истекло в этом режиме (обычные таймеры STM32 не работают в режиме останова).
Замечание о таймерах LPTIM
Я потратил много времени, пытаясь использовать таймер LPTIM в качестве генератора временного отсчета. Несмотря на то, что он работает как обычный таймер, я пришел к выводу, что таймеры LPTIM не подходят для использования в бестиковом режиме, поскольку они реализованы так, что считывание значения регистра счетчика (LPTIM->CNT) не надежно, особенно когда таймер выходит из более глубоких режимов пониженного энергопотребления. Это четко указано в официальной документации STM32 и по мнению автора книги является серьезным ограничением данного периферийного устройства.