
- •Оглавление
- •Предисловие
- •Почему я написал книгу?
- •Для кого эта книга?
- •Как использовать эту книгу?
- •Как организована книга?
- •Об авторе
- •Ошибки и предложения
- •Поддержка книги
- •Как помочь автору
- •Отказ от авторского права
- •Благодарность за участие
- •Перевод
- •Благодарности
- •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
Управление DMA |
255 |
9.2.3. DMA_HandleTypeDef в HAL для L0/L4
Так как микроконтроллеры STM32L0/L4 принимают другую терминологию для указания пары поток/канал (они принимают терминологию канал/запрос), DMA_HandleTypeDef отражает эту разницу в своих HAL. Тем не менее, мы не будем повторять здесь полный рассказ. Нужно иметь в виду только три момента:
•Поле DMA_HandleTypeDef.Instance – это номер канала, и он может принимать зна-
чения DMA1_Channel1..DMA1_Channel7;
•Поле DMA_HandleTypeDef.Init.Request является линией запроса, и оно может при-
нимать значения DMA_REQUEST_0..DMA_REQUEST_11;
•эта реализация DMA не поддерживает режим FIFO и режим пакетной передачи.
9.2.4. Как выполнять передачи в режиме опроса
После того, как мы сконфигурировали канал/поток DMA, нам нужно сделать несколько других манипуляций:
•сконфигурировать адреса памяти и периферийного порта;
•указать объем данных, которые мы собираемся передать;
•подготовить контроллер DMA;
•включить режим DMA в соответствующем периферийном устройстве.
HAL абстрагирует первые три пункта, используя
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength);
в то время как четвертый пункт зависит от периферии, и мы должны обратиться к техническому описанию именно нашего микроконтроллера. Однако, как мы увидим позже, HAL также абстрагирует этот пункт (например, если мы используем соответствующую функцию HAL_UART_Transmit_DMA() при конфигурации UART в режим DMA).
Теперь у нас есть все элементы для того, чтобы увидеть полностью работающее приложение. В следующем примере мы просто отправим строку через периферийное устройство UART2 в режиме DMA. Соответствующие шаги:
•UART2 конфигурируется с использованием модуля HAL_UART, как мы видели в предыдущей главе.
•Канал DMA1 (или пара канал/поток DMA1 для плат Nucleo на основе STM32F4) сконфигурирован на передачу типа память-в-периферию (см. таблицу 12).
•Соответствующий канал готов к выполнению передачи, и UART включен в режиме DMA.

Управление DMA |
256 |
Таблица 12: Как каналы DMA USART_TX/USART_RX отображаются в микроконтроллерах, оснащающих платы Nucleo
В следующем примере, который предназначен для работы на Nucleo-F030 (см. примеры книги для других плат Nucleo), показано, как легко это сделать.
Имя файла: src/main-ex1.c
43MX_DMA_Init();
44MX_USART2_UART_Init();
46hdma_usart2_tx.Instance = DMA1_Channel4;
47hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
48hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
49hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
50hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
51hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
52hdma_usart2_tx.Init.Mode = DMA_NORMAL;
53hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
54 |
HAL_DMA_Init(&hdma_usart2_tx); |
55 |
|
56HAL_DMA_Start(&hdma_usart2_tx, (uint32_t)msg, (uint32_t)&huart2.Instance->TDR, \
57strlen(msg));
58// Включение UART в режиме DMA
59huart2.Instance->CR3 |= USART_CR3_DMAT;
60// Ожидание завершения передачи
61HAL_DMA_PollForTransfer(&hdma_usart2_tx, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
62// Отключение режима DMA в UART
63huart2.Instance->CR3 &= ~USART_CR3_DMAT;
64// Включение LD2
65HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
Управление DMA |
257 |
Переменная hdma_usart2_tx является экземпляром структуры DMA_HandleTypeDef, замеченной ранее. Здесь мы конфигурируем DMA1_Channel4 для выполнения передачи типа па- мять-в-периферию. Поскольку периферийное устройство USART имеет регистр передачи данных (Transmit Data Register, TDR) размером в один байт, мы конфигурируем DMA таким образом, чтобы периферийный адрес не увеличивался автоматически (DMA_PINC_DISABLE), в то же время мы хотим, чтобы адрес памяти источника автоматически увеличивался после каждой отправки байта (DMA_MINC_ENABLE). Как только конфигурация завершена, мы вызываем HAL_DMA_Init(), которая выполняет конфигурацию интерфейса DMA в соответствии с информацией, предоставленной в структуре hdma_usart2_tx.Init. Далее в строке 56 мы вызываем процедуру HAL_DMA_Start(), которая конфигурирует адрес памяти источника (то есть адрес массива msg), периферийный адрес пункта назначения (то есть адрес регистра USART2->TDR) и количество данных, которые мы собираемся передать. Теперь DMA готов к отправке, и мы начинаем передачу, устанавливая соответствующий бит периферийного устройства USART2, как показано в строке 59. Наконец, обратите внимание, что функция MX_DMA_Init() (вызывается в строке 43) использует макрос __HAL_RCC_DMA1_CLK_ENABLE() для включения контроллера DMA1 (помните, что почти каждый внутренний модуль STM32 должен быть включен с помо-
щью макроса __HAL_RCC_<ПЕРИФЕРИЙНОЕ_УСТРОЙСТВО>_CLK_ENABLE()).
Поскольку мы не знаем, сколько времени потребуется, чтобы завершить процедуру передачи, мы используем функцию:
HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma,
uint32_t CompleteLevel, uint32_t Timeout);
которая автоматически ожидает полного завершения передачи. Данный способ отправки данных в режиме DMA называется «режим опроса» в официальной документации ST. Как только передача завершена, мы отключаем режим DMA у UART2 и включаем светодиод LD2.
9.2.5. Как выполнять передачи в режиме прерываний
С точки зрения производительности передача через DMA в режиме опроса не имеет смысла, если только нашему коду не нужно ждать завершения передачи. Если наша цель состоит в том, чтобы улучшить общую производительность, нет никаких причин использовать контроллер DMA, при этом затрачивая много тактовых циклов ЦПУ, ожидающих завершения передачи. Таким образом, лучший вариант – подготовить DMA и позволить ему уведомить нас о завершении передачи. DMA может генерировать прерывания, связанные с действиями канала (например, DMA1 в микроконтроллере STM32F030 имеет один IRQ для канала 1, один – для каналов 2 и 3 и один – для каналов 4 и 5). Кроме того, доступны три независимых бита разрешения для разрешения IRQ
при половинной передаче, полной передаче и ошибке передачи.
DMA можно включить в режиме прерываний, выполнив следующие действия:
•определить три функции, выступающие в качестве процедур обратного вызова,
и передать их указателям на функции XferCpltCallback, XferHalfCpltCallback и XferErrorCallback в дескрипторе DMA_HandleTypeDef (это в порядке вещей – опре-
делять только те функции, которые нам интересны, при этом надо установить соответствующий указатель на NULL, в противном случае могут возникнуть странные ошибки);

Управление DMA |
258 |
•написать ISR для IRQ, связанного с каналом, который вы используете, и выполнить вызов HAL_DMA_IRQHandler(), передав указатель на дескриптор
DMA_HandleTypeDef;
•разрешить соответствующий IRQ в контроллере NVIC;
•использовать функцию HAL_DMA_Start_IT(), которая автоматически выполняет все необходимые для вас шаги конфигурации, передавая ей те же аргументы, что и
HAL_DMA_Start().
«Пуристы производительности» будут разочарованы тем, как HAL управляет прерываниями DMA. Фактически, он разрешает по умолчанию все доступные IRQ для используемого канала, даже если мы не заинтересованы в некоторых из них (например, мы можем не интересоваться захватом прерывания при половинной передаче). Если производительность для вас принципиальна, взгляните на код HAL_DMA_Start_IT() и измените его в соответствии с вашими потребностями. К сожалению, ST решила спроектировать HAL таким образом, чтобы он абстрагировал многие детали от пользователя за счет скорости.
Важно отметить кое-что об обратных вызовах XferCpltCallback, XferHalfCpltCallback и XferErrorCallback: их нужно устанавливать, когда мы используем DMA без посредничества CubeHAL. Разъясним эту концепцию.
Предположим, что мы используем UART2 в режиме DMA. Если мы сами осуществляем управление DMA, тогда можно определить эти процедуры обратного вызова и управлять необходимыми конфигурациями, связанными с прерываниями UART, каждый раз, когда происходит передача. Однако, если мы
используем процедуры HAL_UART_Trasmit_DMA()/HAL_UART_Receive_DMA(), то HAL
уже корректно определяет эти обратные вызовы, и нам не нужно их изменять. Вместо того, чтобы, например, захватить событие завершения DMA для UART, нам нужно определить функцию HAL_UART_RxCpltCallback(). Всегда обращайтесь к документации по HAL для периферийного устройства, которое вы собираетесь использовать в режиме DMA.
В следующем примере показано, как выполнить передачу DMA типа память-в-перифе- рию в режиме прерываний.
Имя файла: src/main-ex2.c
47hdma_usart2_tx.Instance = DMA1_Channel4;
48hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
49hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
50hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
51hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
52hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
53hdma_usart2_tx.Init.Mode = DMA_NORMAL;
54hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
55hdma_usart2_tx.XferCpltCallback = &DMATransferComplete;
56HAL_DMA_Init(&hdma_usart2_tx);
57
58/* Инициализация прерываний DMA */
59HAL_NVIC_SetPriority(DMA1_Channel4_5_IRQn, 0, 0);
60HAL_NVIC_EnableIRQ(DMA1_Channel4_5_IRQn);
61

|
Управление DMA |
259 |
62 |
HAL_DMA_Start_IT(&hdma_usart2_tx, (uint32_t)msg, |
\ |
63 |
(uint32_t)&huart2.Instance->TDR, |
strlen(msg)); |
64// Включение UART в режиме DMA
65huart2.Instance->CR3 |= USART_CR3_DMAT;
67/* Бесконечный цикл */
68while (1);
69}
70
71void DMATransferComplete(DMA_HandleTypeDef *hdma) {
72if(hdma->Instance == DMA1_Channel4) {
73// Отключение режима DMA в UART
74huart2.Instance->CR3 &= ~USART_CR3_DMAT;
75// Включение LD2
76HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
77}
9.2.6.Как выполнять передачи типа периферия-в-периферию
Официальная документация ST подробно описывает передачи типа периферия-в-перифе- рию с использованием DMA в микроконтроллерах F0/F1/F3/L0/L1/L4. Но, глядя на справочные руководства и демонстрационные проекты, представленные в CubeHAL, невозможно найти какой-либо вразумительный пример того, как использовать эту функцию. Даже в Интернете (и на официальном форуме ST) нет никаких подсказок о том, как ее использовать. Во-первых, я подумал, что очевидным следствием является тот факт, что периферийные устройства – это память, отображаемая в адресном пространстве 4 ГБ. Тогда передача типа периферия-в-периферию будет просто частным случаем передачи типа периферия-в-память. Вместо этого, выполнив некоторые тесты, я пришел к выводу, что эта функция требует, чтобы DMA был специально спроектирован для обеспечения запуска передач между различными периферийными устройствами. Проводя некоторые эксперименты, я обнаружил, что в микроконтроллерах F2/F4/F7/L1/L4 только контроллер DMA2 имеет полный доступ к матричной шине, и он является единственным (вместе с ядром Cortex), который может выполнять передачи типа периферия-в-перифе-
рию.
Данная функция может быть полезна, когда мы хотим обмениваться данными между двумя периферийными устройствами без вмешательства ядра Cortex. В следующем примере показано, как переключать светодиод LD2 платы Nucleo, отправляя последовательность сообщений от периферийного устройства UART214.
Имя файла: src/main-ex3.c
45hdma_usart2_rx.Instance = DMA1_Channel5;
46hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
47hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
48hdma_usart2_rx.Init.MemInc = DMA_MINC_DISABLE;
49hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
50hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
51hdma_usart2_rx.Init.Mode = DMA_CIRCULAR;
14 Пример предназначен для запуска на Nucleo-F030. Для плат Nucleo, основанных на микроконтроллерах F2/F4/L1/L4, пример разработан для работы с UART1, чьи запросы к DMA связаны с DMA2.

Управление DMA |
260 |
52hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;
53HAL_DMA_Init(&hdma_usart2_rx);
54
55 __HAL_RCC_DMA1_CLK_ENABLE(); 56
57HAL_DMA_Start(&hdma_usart2_rx, (uint32_t)&huart2.Instance->RDR, (uint32_t)&GPIOA->ODR, 1);
58// Включение UART в режиме DMA
59huart2.Instance->CR3 |= USART_CR3_DMAR;
На этот раз мы конфигурируем канал на передачу типа периферия-в-память, не увеличивая ни адрес источника – периферийного регистра (регистр данных UART), ни целевую ячейку памяти, которая в нашем случае является адресом регистра GPIOA->ODR. В конечном счете, канал сконфигурирован на работу в циклическом режиме: это приведет к тому, что все байты, передаваемые по UART, будут постоянно храниться в регистре
GPIOA->ODR.
Чтобы проверить пример, мы можем просто использовать следующий Python-скрипт:
Имя файла: src/uartsend.py
1#!/usr/bin/env python
2import serial, time
4SERIAL_PORT = "/dev/tty.usbmodem1a1213" #Пользователи Windows, замените на "COMx"
5ser = serial.Serial(SERIAL_PORT, 38400)
7while True:
8ser.write((0xff,))
9time.sleep(0.05)
10ser.write((0,))
11time.sleep(0.05)
13ser.close()
Этот код действительно говорит сам за себя. Мы используем модуль pyserial, чтобы открыть новое последовательное соединение в VCP Nucleo. Затем мы запускаем бесконечный цикл, который отправляет байты 0xFF и 0x0 поочередно. Это приведет к тому, что GPIOA->ODR примет одно и то же значение (то есть, первые восемь I/O будут поочередно становиться ВЫСОКИМИ и НИЗКИМИ), а светодиод LD2 платы Nucleo будет мигать. Как видите, ядро Cortex-M ничего не знает о том, что происходит между UART2 и перифе-
рией GPIOA.
9.2.7.Использование модуля HAL_UART для передачи в режиме
DMA
В Главе 8 мы не указали, как использовать UART в режиме DMA. Мы уже видели в предыдущих параграфах, как это сделать. Однако нам пришлось поиграть с некоторыми регистрами USART, чтобы включить периферийное устройство в режиме DMA.
Модуль HAL_UART разработан для абстрагирования от всех базовых аппаратных подробностей. Необходимые для его использования шаги, следующие:

Управление DMA |
261 |
•сконфигурировать канал/поток DMA, подключенный к UART, который вы собираетесь использовать, как было показано в данной главе;
•связать UART_HandleTypeDef с DMA_HandleTypeDef, используя __HAL_LINKDMA();
•разрешить прерывание DMA, связанное с используемым вами каналом/потоком, и вызвать процедуру HAL_DMA_IRQHandler() из соответствующей ISR;
•разрешить прерывание, связанное с UART, и вызвать процедуру
HAL_UART_IRQHandler() из соответствующей ISR (это действительно важно, не пропустите этот шаг15);
•Использовать функции HAL_UART_Transmit_DMA() и HAL_UART_Receive_DMA() для об-
мена данными по UART и быть готовым получить уведомление о завершении
передачи с помощью HAL_UART_RxCpltCallback().
Следующий код показывает, как получить три байта от UART2 в режиме DMA в микроконтроллере STM32F03016:
uint8_t dataArrived = 0;
int main(void) { HAL_Init();
Nucleo_BSP_Init(); //Конфигурация UART2
//Конфигурация канала 5 DMA1, который подключен к линии запроса UART2_RX hdma_usart2_rx.Instance = DMA1_Channel5;
hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_rx.Init.Mode = DMA_NORMAL; hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW; HAL_DMA_Init(&hdma_usart2_rx);
// Связывание дескриптора DMA с дескриптором UART2
__HAL_LINKDMA(&huart, hdmarx, hdma_usart2_rx);
/* Инициализация прерываний DMA */
HAL_NVIC_SetPriority(DMA1_Channel4_5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel4_5_IRQn);
/* Инициализация прерываний периферии */
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(USART2_IRQn);
// Получение трех байт от UART2 в режиме DMA
15Очень важно разрешить прерывание, связанное с UART, и вызвать процедуру HAL_UART_IRQHandler(), поскольку HAL спроектирован так, что ошибки, связанные с UART (например, ошибка проверки четности, ошибка вследствие переполнения и т. д.), могут возникать даже тогда, когда UART управляется в режиме DMA. Улавливая условие ошибки, HAL приостанавливает передачу через DMA и вызывает соответствующий обратный вызов ошибки, чтобы сообщить об условии ошибки уровню приложения.
16Организация кода инициализации DMA для других микроконтроллеров STM32 оставлена читателю в качестве упражнения.

Управление DMA |
262 |
uint8_t data[3]; HAL_UART_Receive_DMA(&huart2, &data, 3);
while(!dataArrived); //Ожидание прибытия данных от UART
/* Бесконечный цикл */ while (1);
}
//Этот обратный вызов автоматически вызывается HAL,когда передача через DMA завершена void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
dataArrived = 1;
}
void DMA1_Channel4_5_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_usart2_rx); //Это автоматически вызовет \
HAL_UART_RxCpltCallback()
}
Где именно вызывается HAL_UART_RxCpltCallback()? В предыдущих параграфах мы видели, что DMA_HandleTypeDef содержит указатель (названный XferCpltCallback) на функцию, которая вызывается процедурой HAL_DMA_IRQHandler(), когда передача через DMA была завершена. Однако, когда мы используем модуль HAL для выбранного периферийного устройства (в данном случае HAL_UART), нам не нужно предоставлять наши собственные обратные вызовы: они определяются внутри HAL, который использует их для выполнения своих действий. HAL предоставляет нам возможность определить наши соответствующие функции обратного вызова (HAL_UART_RxCpltCallback() для передач UART_RX в режиме DMA), которые будут автоматически вызываться HAL, как показано на рисунке 7. Это правило применяется ко всем модулям HAL.
Рисунок 7: Последовательность вызовов, сгенерированная HAL_DMA_IRQHandler()
Как видите, освоив работу контроллера DMA, очень просто использовать периферийное устройство с применением данного режима передачи.