
- •Оглавление
- •Предисловие
- •Почему я написал книгу?
- •Для кого эта книга?
- •Как использовать эту книгу?
- •Как организована книга?
- •Об авторе
- •Ошибки и предложения
- •Поддержка книги
- •Как помочь автору
- •Отказ от авторского права
- •Благодарность за участие
- •Перевод
- •Благодарности
- •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

Таймеры |
357 |
Макрос __HAL_DBGMCU_UNFREEZE_TIMx() восстанавливает поведение по умолчанию (то есть таймер не останавливается во время точки останова).
Обратите внимание, что перед вызовом макроса __HAL_DBGMCU_FREEZE_TIMx()
компонент отладки микроконтроллера (MCU debug component, DBGMCU) дол-
жен быть включен вызовом макроса __HAL_RCC_DBGMCU_CLK_ENABLE().
11.4. Системный таймер SysTick
SysTick – это специальный таймер, встроенный в ядро Cortex-M, который предоставляется всеми микроконтроллерами STM32. Он в основном используется в качестве генератора временного отсчета для CubeHAL и операционной системы реального времени (если используется). Самое важное в таймере SysTick заключается в том, что, если он используется в качестве генератора временного отсчета для HAL, он должен быть сконфигурирован на генерацию исключения каждые 1 мс: обработчик исключений будет увеличивать счетчик системных тиков (глобальная, размером 32 бит и статическая переменная), к которому можно обратиться, вызвав процедуру HAL_GetTick().
SysTick – это 24-разрядный таймер нисходящего отсчета, тайктируемый шиной AHB (то есть он имеет ту же частоту, что и HCLK). Его тактовая частота может быть в конечном итоге разделена на 8 с помощью функции:
void HAL_SYSTICK_CLKSourceConfig(uint32_t CLKSource);
которая принимает параметры SYSTICK_CLKSOURCE_HCLK и SYSTICK_CLKSOURCE_HCLK_DIV8.
Частота обновления SysTick определяется начальным значением счетчика SysTick, который конфигурируется с помощью функции:
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb);
Чтобы сконфигурировать таймер SysTick таким образом, чтобы он генерировал событие обновления каждые 1 мс, и предполагая, что он тактируется с той же частотой, что и шина AHB, достаточно вызвать следующим образом:
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
Процедура HAL_SYSTICK_Config() также отвечает за включение таймера и разрешение его исключения SysTick_IRQn40. Приоритет исключения может быть сконфигурирован во время компиляции установкой символьной константы TICK_INT_PRIORITY в файле include/stm32XXxx_hal_conf.h или вызовом для исключения
SysTick_IRQn, как было показано в Главе 7.
40 Помните, что SysTick_IRQn является исключением, а не прерыванием, несмотря на то что принято называть его прерыванием. Это означает, что мы не можем использовать функцию HAL_NVIC_EnableIRQ(), чтобы разрешить его.

Таймеры |
358 |
Когда таймер SysTick достигает нуля, возникает исключение SysTick_IRQn и вызывается соответствующий обработчик. CubeMX уже предоставляет нам правильное тело функции, которое определяется следующим образом:
void SysTick_Handler(void) { HAL_IncTick(); HAL_SYSTICK_IRQHandler();
}
HAL_IncTick() автоматически увеличивает глобальный счетчик SysTick, в то время как HAL_SYSTICK_IRQHandler() содержит не что иное, как вызов процедуры HAL_SYSTICK_Callback(), являющейся необязательным обратным вызовом, который мы можем реализовать по желанию, чтобы получить уведомление об опустошении счетчика таймера.
Прочитайте внимательно
Избегайте использования медленного кода внутри процедуры HAL_SYSTICK_Callback(), иначе это может повлиять на генерацию временного отсчета. Это может привести к непредсказуемому поведению некоторых модулей HAL, которые зависят от точной генерации временного отсчета в 1 мс.
Кроме того, необходимо соблюдать осторожность при использовании HAL_Delay(). Эта функция обеспечивает точную задержку (в миллисекундах) на основе счетчика SysTick. Это подразумевает, что если HAL_Delay() вызывается из обслуживаемой периферийной ISR, то прерывание SysTick должно иметь более высокий приоритет (численно ниже), чем периферийное прерывание. В противном случае обработка вызванной ISR будет заблокирована (поскольку глобальный счетчик тиков никогда не увеличится).
Чтобы приостановить генерацию системного временного отсчета, можно использовать процедуру HAL_SuspendTick(), а для ее возобновления – процедуру HAL_ResumeTick().
11.4.1.Использование другого таймера в качестве источника системного временного отсчета
Таймер SysTick имеет только одно подходящее применение: в качестве генератора временного отсчета для HAL или необязательной ОСРВ. Поскольку тактовый сигнал SysTick нельзя легко предварительно масштабировать до более гибких частот отсчета, он не подходит для использования в качестве традиционного таймера. Также у него есть соответствующее ограничение, которое мы лучше проанализируем в Главе 23: он не подходит для использования в бестиковых режимах (tickless modes), предлагаемых некоторыми ОСРВ для приложений с пониженным энергопотреблением. По этой причине иногда важно использовать другой таймер (возможно, LPTIM) в качестве генератора системного временного отсчета. Наконец, как мы увидим в Главе 23, при использовании ОСРВ удобно разделять источник временного отсчета для HAL и для ОСРВ.
Последние выпуски программного обеспечения CubeMX позволяют легко использовать другой таймер вместо SysTick. Для этого перейдите в представление Pinout, затем

Таймеры |
359 |
откройте пункт RCC в IP tree pane и выберите источник временного отсчета Timebase source, как показано на рисунке 35.
Рисунок 35: Как выбрать другой таймер в качестве источника системного временного отсчета
CubeMX сгенерирует дополнительный файл с именем stm32XXxx_hal_timebase_TIM.c, содержащий определение HAL_InitTick() (которая содержит весь необходимый код для инициализации таймера, так чтобы он переполнялся каждые 1 мс), HAL_Sus-
pendTick() и HAL_ResumeTick(), а также определение HAL_TIM_PeriodElapsedCallback(), ко-
торая содержит вызов процедуры HAL_IncTick(). Такое «переопределение» процедур HAL возможно благодаря тому, что эти функции определены как __weak внутри файлов с исходным кодом HAL.
11.5.Пример из практики: как точно измерить микросекунды с помощью
микроконтроллеров STM32
Иногда, особенно когда речь идет о протоколах обмена данными, не реализованных в аппаратном обеспечении периферийным устройством, нам необходимо точно измерять задержки в диапазоне от 1 до нескольких микросекунд. Это приводит к еще одному более общему вопросу: как точно измерять микросекунды в микроконтроллерах STM32?
Существует несколько способов сделать это, но некоторые методы более точны, а другие более универсальны среди различных микроконтроллеров и конфигураций тактового сигнала.
Давайте рассмотрим одного члена семейства STM32F4: STM32F401RE. Данный микроконтроллер может работать на частоте до 84 МГц, используя внутренний RC-генератор. Это означает, что каждую 1 мкс происходит 84 тактовых цикла. Таким образом, нам нужен способ подсчета 84 тактовых циклов, чтобы утверждать, что истекла 1 мкс (я предполагаю, что вы можете допустить 1% точность внутреннего тактового RC-генератора).
Довольно часто встречаются процедуры задержки, подобные следующей:
void delay1US() { |
|
|
#define |
CLOCK_CYCLES_PER_INSTRUCTION |
X |
#define |
CLOCK_FREQ |
Y // в МГц (например, 16 для 16 МГц) |
volatile int cycleCount = CLOCK_FREQ / CLOCK_CYCLE_PER_INSTRUCTION;
while (cycleCount--);
}
Таймеры |
360 |
Но как установить, сколько тактовых циклов требуется для вычисления одного шага инструкции while(cycleCount--)? К сожалению, ответ дать не просто. Давайте предположим, что cycleCount равен 1. Выполняя некоторые тесты (я объясню позже, как я их сделал), с отключенной оптимизацией компилятора (опция -O0 для GCC), мы сможем увидеть, что в этом случае для выполнения всей инструкции Си требуется 24 тактовых цикла. Как это возможно? Если мы дизассемблируем бинарный файл микропрограммы, то мы выясним, что наш оператор Си разворачивается в несколько ассемблерных инструкций:
... |
|
|
|
while(counter--); |
|
|
|
800183e: |
f89d 3003 |
ldrb.w r3, [sp, #3] |
|
8001842: |
b2db |
uxtb |
r3, r3 |
8001844: |
1e5a |
subs |
r2, r3, #1 |
8001846: |
b2d2 |
uxtb |
r2, r2 |
8001848: |
f88d 2003 |
strb.w r2, [sp, #3] |
|
800184c: |
2b00 |
cmp |
r3, #0 |
800184e: |
d1f6 |
bne.n 800183e <delay1US+0x3e> |
Кроме того, еще один источник задержки связан с извлечением инструкций из внутренней Flash-памяти микроконтроллера (которая сильно отличается у «недорогих» микроконтроллеров STM32 и более мощных, таких как STM32F4 и STM32F7 с ускорителем ART Accelerator, который рассчитан на нулевую задержку доступа к Flash-памяти). Таким образом, эта инструкция имеет «базовые затраты» в 24 тактовых цикла. Сколько потребуется тактовых циклов, если cycleCount равен 2? В этом случае микроконтроллеру потребуется 33 тактовых цикла, то есть 9 дополнительных тактовых циклов. Это означает, что если мы хотим простаивать в течение 84 тактовых циклов, cycleCount должен быть равен (84-24)/9, что составляет около 7. Таким образом, мы можем написать нашу функцию задержки в более общем виде:
void delayUS(uint32_t us) {
volatile uint32_t counter = 7*us; while(counter--);
}
Тестирование данной функции в этом коде:
while(1) { delayUS(1);
GPIOA->ODR = 0x0; delayUS(1); GPIOA->ODR = 0x20;
}
с помощью осциллографа, подключенного к выводу PA5, мы можем проверить, получаем ли мы задержку, которую ищем:

Таймеры |
361 |
Является ли этот способ задержки 1 мкс состоятельным? К сожалению, ответ – нет. Прежде всего, он работает хорошо только тогда, когда этот конкретный микроконтроллер (STM32F401RE) работает на полной скорости (84 МГц). Если мы решим использовать другую тактовую частоту, нам нужно изменить ее, выполнив тесты. Во-вторых, он подвергается оптимизации компилятора, как мы скоро увидим, и внутреннему кэшированию ЦПУ на D-Bus и I-Bus, доступному в некоторых микроконтроллерах STM32 (эти кэшпамяти можно в конечном итоге отключить, установив PREFETCH_ENABLE, INSTRUC-
TION_CACHE_ENABLE, DATA_CACHE_ENABLE в файле include/stm32XXxx_hal_conf.h).
Давайте включим оптимизацию GCC по размеру (-Os). Какие результаты мы получаем? В этом случае мы получаем, что функция delayUS() затрачивает всего 72 тактовых цикла ЦПУ, то есть ~850 нс. Осциллограф подтверждает это:
А что будет, если мы включим максимальную оптимизацию по скорости (-O3)? В этом случае у нас всего 64 тактовых цикла ЦПУ, то есть наша delayUS() длится всего ~750 нс. Тем не менее, эта проблема может быть решена с помощью специальных директив pragma компилятора GCC:
#pragma GCC push_options #pragma GCC optimize ("O0") void delayUS(uint32_t us) {
volatile uint32_t counter = 7*us; while(counter--);
}
#pragma GCC pop_options
Однако, если мы хотим использовать более низкую частоту ЦПУ или хотим перенести наш код на другой микроконтроллер STM32, нам все равно нужно снова повторить тесты и определить количество тактовых циклов эмпирически.

Таймеры |
362 |
Однако следует учитывать, что чем ниже частота ЦПУ, тем сложнее выполнить точную задержку в 1 мкс, поскольку количество тактовых циклов фиксировано для данной инструкции, но количество тактовых циклов на одну и ту же единицу времени меньше.
Так как же мы можем получить точную задержку в 1 мкс без проведения тестов, если мы изменим конфигурацию аппаратного обеспечения?
Один ответ может быть представлен установкой таймера, который переполняется каждые 1 мкс (просто установив его Period на частоту периферийной шины в МГц – например, для STM32F401RE нам нужно установить Period в (84 – 1)), и мы можем увеличивать глобальную переменную, которая отслеживает прошедшие микросекунды. Это то же самое, что и делает таймер SysTick для генерации временного отчета в HAL.
Однако такой подход нецелесообразен, особенно для низкоскоростных микроконтроллеров STM32. Генерация прерывания каждые 1 мкс (что в микроконтроллере STM32F0, работающем на полной скорости, будет означать каждые 48 тактовых циклов ЦПУ) приведет к перегруженности микроконтроллера, уменьшая общую степень многозадачности. Более того, обработка прерываний имеет отнюдь не ничтожные затраты (от 12 до 16 тактовых циклов), что повлияет на генерацию временного отсчета в 1 мкс.
Таким же образом, опрос таймера на значение его счетчика также нецелесообразен: много времени будет потрачено на проверку счетчика относительно начального значения, а обработка переполнения/опустошения таймера повлияет на генерацию временного отсчета.
Более надежное решение приходит из предыдущих тестов. Как я измерил такты процессора? Процессоры Cortex-M3/4/7 могут иметь необязательный модуль отладки, назы-
ваемый модулем трассировки и поддержки контрольных точек данных (Data Watchpoint and Tracing, DWT), который предоставляет точки наблюдения, трассировку данных и профилирование системы для процессора. Одним из регистров этого устройства является CYCCNT, который подсчитывает количество тактовых циклов, выполненных ЦПУ. Таким образом, мы можем использовать этот специальный модуль, доступный для подсчета количества тактовых циклов, выполненных микроконтроллером во время выполнения команды.
uint32_t cycles = 0;
/* Структура DWT определена в файле core_cm4.h */ DWT->CTRL |= 1 ; // включение счетчика
DWT->CYCCNT = 0; // сброс счетчика delayUS(1); cycles = DWT->CYCCNT;
cycles--; /* Вычитаем тактовый цикл, используемый для переноса содержимого CYCCNT в переменную тактовых циклов */
Используя DWT, мы можем построить более общую процедуру delayUS() следующим образом:

Таймеры |
363 |
#pragma GCC push_options #pragma GCC optimize ("O3") void delayUS_DWT(uint32_t us) {
volatile uint32_t cycles = (SystemCoreClock/1000000L)*us; volatile uint32_t start = DWT->CYCCNT;
do {
} while(DWT->CYCCNT - start < cycles);
}
#pragma GCC pop_options
Насколько точна данная функция? Если вас интересует наилучшая разрешающая способность при 1 мкс, эта функция вам не поможет, как показано на рисунке.
Наилучшая производительность достигается при более высоком уровне оптимизации компилятора. Как видите, для требуемой задержки в 1 мкс функция дает задержку около 1,22 мкс (на 22% медленнее). Однако, если нам нужно простаивать в течение 10 мкс, мы получаем реальную задержку в 10,5 мкс (на 5% медленнее), что ближе к тому, что мы хотим.
Начиная с задержки 100 мкс погрешность совсем незначительна.
Почему данная функция не так точна? Чтобы понять, почему эта функция менее точна по сравнению с другой, вам необходимо выяснить, что мы используем последовательность инструкций для проверки того, сколько тактовых циклов истекло с момента запуска функции (условия while). Эти инструкции затрачивают тактовые циклы ЦПУ как для обновления внутренних регистров ЦПУ содержимым регистра CYCCNT, так и для сравнения и ветвления. Однако преимущество этой функции заключается в том, что она автоматически определяет скорость процессора и работает «из коробки», особенно если мы работаем на более быстрых процессорах.
Таймеры |
364 |
Если вам нужен полный контроль над оптимизацией компилятора, наилучшая задержка в 1 мкс может быть достигнута с помощью этого макроса, полностью написанного на ассемблере:
#define delayUS_ASM(us) do { |
\ |
asm volatile ("MOV R0,%[loops]\n |
\ |
1: \n |
\ |
SUB R0, #1\n |
\ |
CMP R0, #0\n |
\ |
BNE 1b \t" |
\ |
: : [loops] "r" (16*us) : "memory" \ |
|
); |
\ |
} while(0) |
|
Это наиболее оптимизированный способ написания функции while(counter--). Выполняя тесты с осциллографом, я обнаружил, что задержка в 1 мкс может быть получена, когда микроконтроллер выполняет данный цикл 16 раз при 84 МГц. Однако данный макрос необходимо перестраивать, если скорость вашего процессора ниже, и имейте в виду, что, будучи макросом, он «расширяется» каждый раз, когда вы его используете, вызывая увеличение размера микропрограммы.