- •Оглавление
- •Предисловие
- •Почему я написал книгу?
- •Для кого эта книга?
- •Как использовать эту книгу?
- •Как организована книга?
- •Об авторе
- •Ошибки и предложения
- •Поддержка книги
- •Как помочь автору
- •Отказ от авторского права
- •Благодарность за участие
- •Перевод
- •Благодарности
- •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 |
622 |
23.4. Выделение памяти и управление ею
В двух предыдущих примерах мы начали использовать FreeRTOS, не слишком задумываясь о выделении памяти (memory allocation) потокам и другим структурам, используемым ОС. Единственное исключение представлено последним параметром, переданным макросу osThreadDef(), который соответствует размеру стека, зарезервированного для потока. FreeRTOS, однако, не только нуждается в достаточном количестве памяти для выделения потокам, но и также использует дополнительные части SRAM для выделения своим внутренним структурам (список TCB и т. д.). То же самое относится и к другим примитивам синхронизации, которые мы будем изучать позже, таким как семафоры и мьютексы. Откуда именно берется эта память?
Традиционно FreeRTOS реализовывала модель динамического выделения до выпуска 8.x. Это является важным ограничением, поскольку в некоторых областях применения динамическое выделение памяти категорически не рекомендуется или даже явно запрещено. Несмотря на то что, как мы скоро увидим, один из пяти динамических аллокаторов, реализованных FreeRTOS, отвечает большинству требований выделения памяти в этих областях применения, к сожалению, эта характеристика FreeRTOS препятствовала ее использованию при принятии этого ограничения. Начиная с последней версии 9.x, FreeRTOS реализует две модели выделения памяти: полностью статическую и полностью динамическую.
Для активации модели выделения памяти используются два макроса: configSUP-
PORT_STATIC_ALLOCATION и configSUPPORT_DYNAMIC_ALLOCATION. Оба они могут принимать
значения 0 или 1, чтобы отключить/включить соответствующую модель памяти. Важно подчеркнуть, что две модели памяти не являются взаимоисключающими: их можно использовать одновременно в соответствии с потребностями пользователя. Как мы увидим позже, две модели памяти вынуждают использовать отдельные API-интерфейсы.
23.4.1. Модель динамического выделения памяти
FreeRTOS реализует модель динамического выделения памяти, которая использует области SRAM для выделения всех внутренних структур ОС, включая TCB. По сравнению со статической моделью выделения динамическая имеет некоторые немаловажные преимущества:
•Выделение памяти происходит автоматически, в рамках API-функций ОСРВ.
•Разработчикам не нужно самим заботиться о выделении памяти.
•ОЗУ, используемое объектом ОСРВ, может быть повторно использовано, если объект был удален, что потенциально уменьшает максимальный объем ОЗУ приложения.
•Предоставляются API-функции ОСРВ для возврата информации об использовании кучи, что позволяет оптимизировать размер кучи.
•FreeRTOS предоставляет пять схем динамического выделения памяти, и они могут быть выбраны в соответствии с требованиями приложения.
•При создании объекта требуется меньше параметров функции.
Запуск FreeRTOS |
623 |
FreeRTOS не использует классические функции malloc() и free(), предоставляемые библиотекой среды выполнения Си24, потому что:
1.они используют много места в коде, увеличивая размер микропрограммы;
2.они не предназначены для обеспечения потокобезопасности;
3.они не являются детерминированными (время выполнения функции будет отличаться от вызова к вызову).
Таким образом, FreeRTOS предоставляет свою собственную схему динамического выделения для обработки необходимой памяти, но, поскольку существует несколько способов сделать это, каждая обладает своими преимуществами и компромиссами. FreeRTOS спроектирована так, что эта часть абстрагирована от остальной части ядра ОС, и она предоставляет пять различных схем выделения, которые пользователь может выбирать в зависимости от его конкретных потребностей. pvPortMalloc() и vPortFree() являются наиболее важными функциями, реализованными в каждой схеме, и их имя четко говорит о том, что они делают.
Эти пять схем не являются частью ядра FreeRTOS, но они являются частью уровня платформозависимого кода, и они реализованы в пяти файлах с исходным кодом Си, называющихся heap_1.c..heap_5.c и содержащихся в папке portable/MemMang. Скомпилировав один из этих файлов вместе с остальным кодом FreeRTOS, мы автоматически выбираем эту схему размещения для нашего приложения. Более того, мы можем в конечном итоге предоставить свою модель выделения, реализовав этот API-уровень (в худшем случае, нам нужно реализовать 5 функций) в соответствии с нашими конкретными потребностями.
23.4.1.1. heap_1.c
Многие встроенные приложения используют ОСРВ для логического разделения микропрограммы на блоки. Каждый блок имеет свои особенности, и часто он работает независимо от других блоков. Например, предположим, что вы разрабатываете устройство с TFT-дисплеем (возможно, контроллер современной посудомоечной машины). Обычно микропрограмма разделена на несколько потоков, один из которых отвечает за графическое взаимодействие (он обновляет отображение, печатая информацию и показывая потрясающие графические виджеты), а другие потоки отвечают за управление программой стирки (и, таким образом, за обработку датчиков, двигателей, насосов и т. д.). Эти приложения обычно имеют функцию main(), которая порождает потоки (как мы делали в предыдущих примерах), и почти ничего больше не инициализируя ОС, когда она начинает выполняться. Это означает, что аллокатор не должен учитывать какие-либо более сложные проблемы выделения, такие как детерминизм и фрагментация, и его можно упростить.
Аллокатор heap_1.c реализует очень простую версию pvPortMalloc() и не поддерживает vPortFree(). Приложения, которые никогда не удаляют поток или другие объекты ядра, такие как очереди, семафоры и т. д., подходят для использования этой схемы выделения памяти. Те области применения, где использование динамически выделенной памяти не рекомендуется, могут извлечь выгоду из этой схемы выделения, поскольку она предлагает детерминистский подход к управлению памятью, избегая фрагментации (поскольку память никогда не освобождается).
24 С одним заметным исключением в виде аллокатора heap_3.c, как мы скоро увидим.
Запуск FreeRTOS |
624 |
Аллокатор heap_1.c делит статически выделенный массив на маленькие порции, поскольку выполняются вызовы pvPortMalloc(). Это и в самом деле куча FreeRTOS. Общий размер этого массива (выраженный в байтах) определяется макросом configTOTAL_HEAP_SIZE в файле FreeRTOSConfig.h. Единственный компромисс с этой схемой выделения состоит в том, что, будучи целым массивом, выделенным во время компиляции, приложение будет потреблять много SRAM, даже если оно не использует его полностью. Это означает, что программисты должны тщательно выбрать правильное значение для
размера configTOTAL_HEAP_SIZE.
Стоит отметить важный момент. Память программ на Си традиционно разделена на две области: стек и куча. Говорят, что куча динамически растет во время выполнения и растет в противоположном направлении стека. Однако, как видите, аллокатор heap_1.c не имеет ничего общего с кучей всего приложения, так как он использует массив, объявленный как static, который расположен в секции .data, как мы узнали в Главе 20, для хранения объектов, нуждающихся в динамике. Несомненно, это форма динамического выделения, но она не связана с использованием функций malloc() и free(). Это означает, что мы можем безопасно использовать их в нашем приложении, даже если их использование не рекомендуется во встроенных приложениях.
23.4.1.2. heap_2.c
heap_2.c также работает путем деления статически выделенного массива, размер которого задается макросом configTOTAL_HEAP_SIZE. Он использует алгоритм наилучшего соответствия (best-fit algorithm) для выделения памяти и, в отличие от схемы выделения heap_1.c, позволяет освобождать память. Этот алгоритм считается устаревшим и не подходит для новых разработок. heap_4.c – лучшая альтернатива данному аллокатору. По этой причине мы не будем вдаваться в подробности того, как он работает. Если вы заинтересованы в нем, то можете обратиться к официальной документации FreeRTOS25.
23.4.1.3. heap_3.c
heap_3.c использует обычные функции Си malloc() и free() для выделения памяти. Это означает, что параметр configTOTAL_HEAP_SIZE не влияет на управление памятью, поскольку malloc() предназначена для самостоятельного управления кучей. Это означает, что нам нужно соответствующим образом сконфигурировать наши скрипты компоновщика, как показано в Главе 20. Кроме того, учтите, что реализация malloc() отличается от реализации, предоставляемой newlib-nano и обычной newlib. Тем не менее, более универсальная реализация, предоставляемая библиотекой newlib, требует гораздо больше
Flash-памяти.
heap_3.c делает malloc() и free() потокобезопасными, временно приостанавливая работу планировщика FreeRTOS. Для получения дополнительной информации о них обратитесь к официальной документации FreeRTOS26.
23.4.1.4. heap_4.c
heap_4.c работает аналогично heap_1.c и heap_2.c. То есть он использует статически выделенный массив, размер которого задается значением макроса configTOTAL_HEAP_SIZE,
25http://www.freertos.org/a00111.html#heap_2
26http://www.freertos.org/a00111.html#heap_3
Запуск FreeRTOS |
625 |
для хранения объектов, выделенных во время выполнения. Тем не менее, он имеет другой подход при выделении памяти. Фактически, он использует алгоритм первого соответствия (first fit algorithm), который объединяет смежные свободные блоки в один большой блок, снижая риск фрагментации памяти. Этот метод, обычно используемый сборщиком мусора (garbage collector) в языках с динамическим и автоматическим выделением памяти, также называется объединением (coalescing).
К сожалению, такое поведение аллокатора heap_4.c приводит к тому, что он не детерминирован: выделение/освобождение многих небольших объектов вместе с созданием/уничтожением потоков может привести к большой фрагментации, что требует больше вычислительной обработки для упаковки памяти. Более того, нет никакой гарантии, что алгоритм вообще избежит утечек памяти. Однако обычно он быстрее, чем самая стандартная реализация malloc() и free(), особенно тех, которые предоставляются библиотекой newlib-nano.
Подробное объяснение алгоритма heap_4.c выходит за рамки данной книги. Для получения дополнительной информации обратитесь к документации FreeRTOS27.
23.4.1.5. heap_5.c
heap_5.c использует тот же алгоритм, что и у аллокатора heap_4.c, но он позволяет разделить пул памяти между различными несмежными областями памяти. Это особенно полезно для микроконтроллеров STM32, предоставляющих контроллер FSMC, который позволяет прозрачно использовать внешние SDRAM для увеличения всей оперативной памяти. Программист может решить выделить какой-то интенсивно используемый поток во внутреннюю память SRAM (или в CCM-память, если она доступна), а затем использовать внешнюю SDRAM для менее значимых объектов, таких как семафоры и мьютексы.
Определив пользовательский скрипт компоновщика, можно выделить два пула в двух областях памяти, а затем использовать функцию vPortDefineHeapRegions() от FreeRTOS, чтобы определить их как пулы памяти. Однако это продвинутое использование ОС, которое мы не будем здесь подробно описывать. Если вам интересно, вы можете обратиться к превосходной книге создателя FreeRTOS Ричарда Барри «Освоение ядра реального времени FreeRTOS» (Mastering the FreeRTOS Real Time Kernel by Richard Barry).
23.4.1.6.Как использовать malloc() и связанные с ней функции Си с
FreeRTOS
Как было сказано ранее, за исключением схемы выделения heap_3.c, FreeRTOS не использует память кучи Си для ее выделения потокам и другим объектам. Таким образом, вы можете использовать malloc() и free() в своем приложении.
Если вместо этого вы хотите использовать процедуры pvPortMalloc() и vPortFree(), обеспечивая переносимость вашего кода, вы можете просто переопределить malloc() и free() следующим образом:
void *malloc (size_t size) { return pvPortMalloc(size);
}
27 http://www.freertos.org/a00111.html#heap_4