- •Оглавление
- •Предисловие
- •Почему я написал книгу?
- •Для кого эта книга?
- •Как использовать эту книгу?
- •Как организована книга?
- •Об авторе
- •Ошибки и предложения
- •Поддержка книги
- •Как помочь автору
- •Отказ от авторского права
- •Благодарность за участие
- •Перевод
- •Благодарности
- •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 |
617 |
задержки. Заблокированный поток может быть переведен в состояние «готов к выполнению», и, следовательно, он становится готовым быть спланированным к выполнению или быть в состоянии «приостановлен».
Во избежание недоразумений важно уточнить, что приостановленный или заблокированный поток требует вмешательства внешнего объекта для возврата в состояние «готов к выполнению».
23.3.2. Приоритеты потоков и алгоритмы планирования
В первом примере мы увидели, что каждый поток имеет приоритет. Но какие практические эффекты имеют приоритеты при выполнении потоков? Приоритеты влияют на алгоритм планирования, позволяя изменить порядок выполнения в случае, если поток с более высоким приоритетом переходит в состояние «готов к выполнению». Приоритеты являются фундаментальным аспектом ОСРВ и предоставляют фундаментные блоки для достижения коротких откликов на крайние сроки (deadlines). Важно подчеркнуть, что приоритет потока не связан с приоритетом IRQ.
Представьте, что вы разрабатываете панель управления машиной, которая потенциально может нанести травмы работникам в критических ситуациях. Обычно этот тип машин имеет кнопку аварийного останова. Эта кнопка может быть подключена к одному выводу микроконтроллера, и соответствующее прерывание может возобновить заблокированный поток, ожидающий этого события. Этот поток может быть предназначен для выключения двигателя или чего-то подобного и для перевода машины в безопасное состояние.
После срабатывания IRQ задача, выполняющаяся в этот момент, формально в состоянии «выполняется», но она неэффективно выполняется в ЦПУ, который обслуживает ISR. Вызывая надлежащие процедуры ОС, которые мы увидим позже, ОС переводит наш аварийный поток в режим «готов к выполнению», но мы должны быть уверены, что это будет первый поток, который будет выполнен. Приоритеты позволяют программистам различать отложенные действия от не отложенных.
FreeRTOS имеет определяемую пользователем систему приоритетов, которая дает большую степень гибкости в определении приоритетов. Самый низкий приоритет (который означает, что потоки с этим приоритетом всегда будут пропускать потоки с более высоким приоритетом, если они готовы к выполнению) равен нулю. Затем пользователь может назначить увеличивающиеся приоритеты более важным потокам, вплоть до максимального значения, определенного символьной константой configMAX_PRIORITIES, которая определена в файле FreeRTOSConfig.h.
Таблица 1: Фиксированные приоритеты, определенные в спецификации CMSIS-RTOS
Уровень приоритета |
Описание |
|
|
osPriorityIdle |
приоритет idle (самый низкий, соответствующий приоритету холо- |
|
стого потока) |
osPriorityLow |
низкий приоритет |
osPriorityBelowNormal |
приоритет ниже нормального |
osPriorityNormal |
нормальный приоритет (по умолчанию) |
osPriorityAboveNormal |
приоритет выше нормального |
osPriorityHigh |
высокий приоритет |
osPriorityRealtime |
приоритет реального времени (самый высокий) |
Запуск FreeRTOS |
618 |
Вместо этого CMSIS-RTOS имеет четко определенную схему приоритетов, состоящую из восьми уровней (приведенных в таблице 1), которые сопоставлены с приоритетами
FreeRTOS. Функция
osStatus osThreadSetPriority(osThreadId thread_id, osPriority priority);
позволяет изменить приоритет существующего потока, в то время как функция
osPriority osThreadGetPriority(osThreadId thread_id);
позволяет получить приоритет существующего потока.
Говорить о приоритетах потоков совершенно бессмысленно, не зная точного алгоритма планирования (scheduling policy), принятого ОСРВ. FreeRTOS предоставляет три различных алгоритма планирования, которые выбираются правильной комбинацией символь-
ных констант configUSE_PREEMPTION и configUSE_TIME_SLICING, оба определены в файле
FreeRTOSConfig.h. В таблице 2 показана комбинация из этих двух макросов для выбора требуемого алгоритма планирования.
Таблица 2: Как выбрать требуемый алгоритм планирования во FreeRTOS
configUSE_PREEMPTION |
configUSE_TIME_SLICING |
Алгоритм планирования |
|
|
|
1 |
1 |
Приоритетное вытесняющее планирование с |
|
|
квантованием времени |
1 |
0 или не определено |
Приоритетное вытесняющее планирование без |
|
|
квантования времени |
0 |
любое значение |
Кооперативное (совместное) планирование |
Давайте кратко познакомимся с этими алгоритмами.
•Приоритетное вытесняющее планирование с квантованием времени
(Prioritized preemptive scheduling with time slicing): это наиболее распростра-
ненный алгоритм, реализованный всеми ОСРВ, и он работает следующим образом. Каждый поток имеет фиксированный приоритет, который назначается при его создании. Планировщик никогда не изменит этот приоритет, но программист может переопределить другой приоритет, вызвав функцию osThreadSetPriority(). В этом режиме планировщик немедленно вытеснит выполняющийся по-
ток, если поток с более высоким приоритетом станет готовым к выполнению. Быть вытесненным означает быть недобровольно (без явной уступки (yielding) или блокировки) переведенным из состояния «выполняется» в состояние «готов к выполнению», чтобы позволить потоку с более высоким приоритетом стать выполняющимся. Квантование времени используется для распределения процессорного времени между потоками с одинаковым приоритетом, даже когда они оставляют управление, явно уступая или блокируясь. Когда поток «потребляет» свой временной интервал, планировщик выберет следующий готовый к выполнению поток в списке планирования (если имеется), назначив ему тот же временной интервал. Если доступных готовых к выполнению потоков нет, то планировщик помечает выполняющимся специальный поток с именем idle, который мы опишем далее. Временной интервал соответствует времени тика ОСРВ, которое по умолчанию равно 1 кГц, то есть 1 мс. Его можно изменить, сконфигурировав макрос configTICK_RATE_HZ и переставив частоту UEV таймера, используемого в качестве
Запуск FreeRTOS |
619 |
генератора временного отсчета. Настройка этого значения зависит от конкретного приложения, а также от скорости работы микроконтроллера. Чем медленнее работает микроконтроллер, тем медленнее должно быть время такта. Обычно значение в диапазоне от 100 Гц до 1000 Гц подходит для многих приложений.
•Приоритетное вытесняющее планирование без квантования времени
(Prioritized preemptive scheduling without time slicing)22: этот алгоритм прак-
тически идентичен предыдущему, за исключением того факта, что, как только поток переходит в состояние «выполняется», он освобождает ЦПУ только на добровольной основе (блокируясь, останавливаясь или уступая) или если поток с более высоким приоритетом переходит в состояние «готов к выполнению». Этот алгоритм сводит к минимуму влияние переключения контекста на общую производительность, так как количество переключений значительно сокращается. Тем не менее, плохо спроектированный поток может установить полный контроль над ЦПУ, вызывая непредсказуемое поведение всего устройства.
•Кооперативное планирование (Cooperative scheduling): при использовании этого алгоритма поток освобождает ЦПУ только на добровольной основе (блокируясь, останавливаясь или уступая). Даже если поток с более высоким приоритетом становится готовым к выполнению, ОС никогда не будет вытеснять текущий поток, и она будет перепланировать его снова в случае внешнего прерывания. Эта форма планирования возлагает всю ответственность на программиста, который должен тщательно проектировать потоки, как если бы он разрабатывал микропрограмму без использования ОСРВ.
Даже если мы используем приоритетное вытесняющее планирование с квантованием времени следует уделять особое внимание при назначении приоритетов потокам. Давайте рассмотрим этот пример.
Имя файла: src/main-ex2.c
13int main(void) {
14HAL_Init();
15
16 Nucleo_BSP_Init();
17
18osThreadDef(blink, blinkThread, osPriorityNormal, 0, 100);
19osThreadCreate(osThread(blink), NULL);
20
21osThreadDef(uart, UARTThread, osPriorityAboveNormal, 0, 100);
22osThreadCreate(osThread(uart), NULL);
23
24 osKernelStart();
25
26/* Бесконечный цикл */
27while (1);
28}
29
30void blinkThread(void const *argument) {
31while(1) {
32HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
22 Это алгоритм планирования по умолчанию, сконфигурированный CubeMX для микроконтроллеров
STM32F0/L0.
Запуск FreeRTOS |
620 |
33osDelay(500);
34}
35osThreadTerminate(NULL);
36}
37
38void UARTThread(void const *argument) {
39while(1) {
40HAL_UART_Transmit(&huart2, "UARTThread\r\n", strlen("UARTThread\r\n"), HAL_MAX_DELAY);
41}
На этот раз у нас есть два потока, один из которых мигает светодиодом LD2, а другой постоянно выводит по UART2 сообщение. UARTThread() создается с приоритетом выше, чем у blinkThread(). Запустив этот пример, вы увидите, что светодиод LD2 никогда не мигает. Это происходит потому, что UARTThread() предназначен для непрерывного выполнения чего-либо, и когда его квант времени истекает, он все еще находится в состоянии «готов к выполнению» и, имея более высокий приоритет, он перепланируется на выполнение. Это ясно доказывает, что приоритеты должны использоваться осторожно, чтобы предотвратить нехватку ресурсов (starving) у других процессов23.
23.3.3. Добровольное освобождение от управления
Выполняющийся поток может освободить управление (говорят, что он уступает управление), если программист знает, что бесполезно использовать тактовые циклы ЦПУ, вызывав функцию
osStatus osThreadYield(void);
Она вызывает переключение контекста, и следующий готовый к выполнению поток в списке планирования переводится в состояние «выполняется». osThreadYield() играет действительно важную роль, если алгоритмом планировщика является кооперативное планирование.
23.3.4. Холостой поток idle
ЦПУ никогда не останавливается, если мы не перейдем в один из режимов пониженного энергопотребления, предлагаемых микроконтроллерами STM32. Это означает, что, если все потоки в системе заблокированы или остановлены в ожидании внешних событий, нам нужен способ «делать что-то», ожидая, когда другие потоки снова станут активными. По этой причине все операционные системы предоставляют специальные задачи с именем idle, которые планируются во время неактивных состояний системы, и ее приоритет определяется как минимально возможный. По этой причине принято говорить, что самый низкий приоритет соответствует приоритету холостой задачи idle.
До версии 9 во FreeRTOS всякий раз, когда поток удалялся, память, выделенная системой FreeRTOS потоку, освобождалась потоком idle. Во FreeRTOS версии 9, если один поток удаляет другой поток, то память, выделенная системой FreeRTOS удаленному потоку,
23 В конкурентном программировании нехватка ресурсов (starvation) происходит, когда потоку постоянно отказывают в необходимых ресурсах для обработки его работы. Нехватка ресурсов обычно вызвана плохой синхронизацией между потоками, и даже неправильной схемой выделения приоритетов. Нехватка ресурсов – это нежелательное условие, которого ни один программист никогда не хотел бы достичь, а определение его происхождения иногда может стать кошмаром.
Запуск FreeRTOS |
621 |
освобождается немедленно. Однако, если поток удаляет себя сам, то память, выделенная системой FreeRTOS потоку, все еще освобождается потоком idle. Обратите внимание, что во всех случаях автоматически освобождаются только стек и блок управления задачами (TCB), которые были выделены операционной системой потоку.
Поток idle также играет важную роль в проектах с пониженным энергопотреблением, как мы узнаем позже в этой главе.
Слово о конкурентном программировании
Вы будете поражены фантастическими числами, представленными вам разработчиками операционных систем реального времени. Они скажут вам, что их ОС способна обрабатывать сотни тысяч потоков в секунду, демонстрируя потрясающую производительность переключения контекста.
Знайте, что на практике это то же самое, что и в разговоры в пабах.
Рисунок 11: Что обычно происходит, когда число потоков увеличивается слишком сильно
Я часто просматриваю проекты, которые мне присылают читатели этой книги (но иногда я видел проекты с таким же плохим подходом, сделанные профессионалами, верите вы или нет), где вы можете увидеть десятки потоков, возникающих в коде, которые не делают ничего значимого. Иногда вы также можете найти потоки, которые не делают ничего, кроме разветвления другого потока после сравнения.
Теоретики конкурентного программирования научат вас, что чем больше у вас параллельных потоков, тем больше у вас проблем. Управление потоками может быть очень сложным, и зачастую затраты на их синхронизацию превосходят преимущества их использования. Более того, та же самая операция порождения нового потока имеет отнюдь не малые затраты. То же самое относится и к переключению контекста.
Многопоточное программирование всегда должно выполняться с осторожностью, особенно во встраиваемых системах, где SRAM часто действительно ограничена. Помните: будьте проще.