- •Оглавление
- •Предисловие
- •Почему я написал книгу?
- •Для кого эта книга?
- •Как использовать эту книгу?
- •Как организована книга?
- •Об авторе
- •Ошибки и предложения
- •Поддержка книги
- •Как помочь автору
- •Отказ от авторского права
- •Благодарность за участие
- •Перевод
- •Благодарности
- •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
Универсальные асинхронные последовательные средства связи |
223 |
при этом для его установки в MacOS X введите: |
|
$ sudo port install kermit
После завершения установки переключитесь на Eclipse и перейдите в Help → Eclipse Marketplace…. В текстовом поле Find введите «terminal». Через некоторое время должен появиться плагин TM Terminal, как показано на рисунке 7. Нажмите кнопку Install и следуйте инструкциям. По запросу перезапустите Eclipse.
Чтобы открыть панель терминала, вы можете просто нажать Ctrl+Alt+T или перейдите в меню Window → Show View → Other… и найдите представление Terminal. Появится приглашение командной строки. Прежде чем мы сможем подключиться к VCP Nucleo, мы должны определить соответствующее устройство по пути /dev. Обычно в UNIXподобных системах последовательные USB-устройства отображаются с именем устройства, аналогичным /dev/tty.usbmodem1a1213. Посмотрите на вашу папку /dev. Получив имя файла устройства, вы можете запустить инструмент kermit и выполнить команды, показанные ниже, в консоли kermit:
$ kermit
C-Kermit 9.0.302 OPEN SOURCE:, 20 Aug 2011, for Mac OS X 10.9 (64-bit) Copyright (C) 1985, 2011,
Trustees of Columbia University in the City of New York. Type ? or HELP for help.
(/Users/cnoviello/) C-Kermit>set line /dev/tty.usbmodem1a1213 (/Users/cnoviello/) C-Kermit>set speed 38400 /dev/tty.usbmodem1a1213, 38400 bps
(/Users/cnoviello/) C-Kermit>set carrier-watch off (/Users/cnoviello/) C-Kermit>c
Connecting to /dev/tty.usbmodem1a1213, speed 38400 Escape character: Ctrl-\ (ASCII 28, FS): enabled Type the escape character followed by C to get back,
or followed by ? to see other options.
----------------------------------------------------
Чтобы избежать повторного ввода вышеуказанных команд при каждом запуске kermit, вы можете создать файл с именем /.kermrc внутри своего домашнего каталога и поместить в него вышеуказанные команды. kermit загрузит эти команды автоматически, когда будет выполнен.
Теперь вы можете сбросить Nucleo. Консоль управления, которую мы запрограммировали с использованием библиотеки HAL_UART, должна появиться в окне консоли последовательного порта, как показано на рисунке 10.
8.4. UART-связь в режиме прерываний
Давайте снова рассмотрим первый пример данной главы. Что с ним не так? Поскольку наша микропрограмма задумана только выполнять эту простую задачу, нет ничего плохого в использовании UART в режиме опроса. Микроконтроллер, по существу, блокируется, ожидая пользовательского ввода (значение тайм-аута HAL_MAX_DELAY блокирует HAL_UART_Receive() до тех пор, пока один символ не будет отправлен по UART). Но что,
Универсальные асинхронные последовательные средства связи |
224 |
если наша микропрограмма должна выполнять другие ресурсоемкие операции в режиме реального времени?
Предположим, мы перестроили main() из первого примера следующим образом:
38while (1) {
39opt = readUserInput();
40processUserInput(opt);
41if(opt == 3)
42goto printMessage;
43
44performCriticalTasks();
45}
В этом случае мы не можем блокировать выполнение функции processUserInput(), ожидающей выбор пользователя, и мы должны указать гораздо более короткое значение
тайм-аута для функции HAL_UART_Receive(), иначе performCriticalTasks() никогда не бу-
дет выполнена. Однако это может привести к потере важных данных, поступающих от периферийного устройства UART (помните, что интерфейс UART имеет буфер размером один байт).
Для решения данной проблемы HAL предлагает другой способ обмена данными через периферийное устройство UART: режим прерываний. Чтобы использовать этот режим, мы должны решить следующие задачи:
•Разрешить прерывание USARTx_IRQn и реализовать соответствующую ISR
USARTx_IRQHandler().
•Вызвать обработчик HAL_UART_IRQHandler() внутри USARTx_IRQHandler(): он будет
выполнять все действия, связанные с управлением прерываниями, генерируемыми периферийным устройством UART13.
•Использовать функции HAL_UART_Transmit_IT() и HAL_UART_Receive_IT() для об-
мена данными по UART. Данные функции также активируют режим прерываний периферийного устройства UART: таким образом, периферийное устройство установит соответствующую линию прерывания в контроллере NVIC, так что ISR будет вызываться при возникновении события.
•Перестроить код нашего приложения для работы с асинхронными событиями.
Прежде чем мы перестроим код из первого примера, лучше взглянуть на доступные прерывания UART и на то, как спроектированы процедуры HAL.
8.4.1.Прерывания, относящиеся к UART
Каждое периферийное устройство USART микроконтроллера STM32 предоставляет прерывания, перечисленные в таблице 6. Эти прерывания включают в себя IRQ, связанные
ис передачей данных, и с ошибками связи. Их можно разделить на две группы:
•IRQ, генерируемые во время передачи: «передача завершена» (Transmission Complete), «готов к отправке» (Clear to Send) или «регистр передаваемых данных пуст»
(Transmit Data Register Empty).
13 Если мы используем CubeMX для разрешения USARTx_IRQn в разделе NVIC Configuration (как показано в Главе 7), он автоматически выполнит вызов HAL_UART_IRQHandler() из ISR.
Универсальные асинхронные последовательные средства связи |
225 |
•IRQ, сгенерированные во время приема: «обнаружение незанятой линии» (Idle Line detection), «ошибка вследствие переполнения» (Overrun error), «регистр принимаемых данных не пуст» (Receive Data Register not Empty), «ошибка при проверке четности» (Parity error), «обнаружение разрыва линии связи» (LIN break detection), «флаг шума», англ. Noise Flag (только в многобуферной связи) и «ошибка кадрирования», англ. Framing Error (только в многобуферной связи).
Таблица 6: Список прерываний, относящихся к USART
Событие прерывания |
Флаг события |
Бит управления разрешением |
|
|
|
Регистр передачи данных пуст |
TXE |
TXEIE |
Установка флага Clear To Send (CTS) |
CTS |
CTSIE |
Передача завершена |
TC |
TCIE |
Принятые данные готовы к чтению |
RXNE |
RXNEIE |
Обнаружена ошибка переполнения |
ORE |
RXNEIE |
Обнаружена незанятая линия |
IDLE |
IDLEIE |
Ошибка при проверке четности |
PE |
PEIE |
Установка флага разрыва линии |
LBD |
LBDIE |
Установка флага шума, возникнове- |
NF или ORE или FE |
EIE |
ние ошибок переполнения и кадри- |
|
|
рования в многобуферной связи |
|
|
Эти события генерируют прерывание, если установлен соответствующий бит управле-
ния разрешением прерывания, англ. Enable Control Bit (третий столбец таблицы 6). Однако микроконтроллеры STM32 спроектированы таким образом, чтобы все эти IRQ были связаны только с одной ISR для каждого периферийного устройства USART (см. рисунок 1114). Например, USART2 определяет только USART2_IRQn в качестве IRQ для всех прерываний, генерируемых данным периферийным устройством. Пользовательский код должен анализировать соответствующий флаг события (Event Flag), чтобы определить, какое прерывание сгенерировало запрос.
Рисунок 11: Как события прерываний от USART связаны с одним и тем же вектором прерывания
14 Рисунок 11 взят из справочного руководства по STM32F030 (RM0390).
Универсальные асинхронные последовательные средства связи |
226 |
CubeHAL разработан так, чтобы автоматически делать эту работу за нас. Пользователь предупреждается о генерации прерываний благодаря серии функций обратного вызова, вызываемых HAL_UART_IRQHandler(), которые должны вызываться внутри ISR.
С технической точки зрения не так много различий между передачей по UART в режиме опроса и в режиме прерываний. Оба метода передают массив байтов, используя регистр данных UART (Data Register, DR) по следующему алгоритму:
•Для передачи данных поместите байт в регистр USART->DR и дождитесь, пока флаг
«Регистр передаваемых данных пуст» (Transmit Data Register Empty, TXE) не станет истинным.
•Для получения данных дождитесь, пока флаг «Принятые данные готовы к чтению» (Received Data Ready to be Read, RXNE), не примет значение истины, и затем сохраните содержимое регистра USART->DR в памяти приложения.
Разница между этими двумя методами заключается в том, что они ожидают завершения
передачи данных. В режиме опроса функции HAL_UART_Receive()/HAL_UART_Transmit() раз-
работаны так, что они ожидают установки соответствующего флага события для каждого байта, который мы хотим передать. В режиме прерываний функции HAL_UART_Receive_IT()/HAL_UART_Transmit_IT() разработаны таким образом, что они не ожидают завершения передачи данных, а выполняют только «грязную работу» в процедуре ISR, помещая новый байт в регистр DR или загружая его содержимое в память приложения, когда генерируется прерывание RXNEIE/TXEIE15.
Для передачи последовательности байтов в режиме прерываний HAL определяет функцию:
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size);
где:
•huart: это указатель на экземпляр структуры UART_HandleTypeDef, рассмотренной
нами ранее, который идентифицирует и конфигурирует периферийное устрой-
ство UART;
•pData: это указатель на массив, длина которого равна параметру Size и содержит
последовательность байтов, которые мы собираемся передать; функция не будет блокировать микроконтроллер, ожидая передачу данных, и передаст управление основному потоку, как только завершит конфигурацию UART.
И наоборот, для получения последовательности байтов по USART в режиме прерываний HAL предоставляет функцию:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size);
15 По этой причине передача последовательности байтов в режиме прерываний не очень полезна, если скорость обмена данными слишком высока или когда нам приходится очень часто передавать большой объем данных. Поскольку передача каждого байта происходит быстро, ЦПУ будет перегружено прерываниями, генерируемыми UART для каждого передаваемого байта. Для непрерывной передачи больших последовательностей байтов на высокой скорости лучше всего использовать режим DMA, как мы увидим в следующей главе.
Универсальные асинхронные последовательные средства связи |
227 |
где: |
|
•huart: это указатель на экземпляр структуры UART_HandleTypeDef, рассмотренной
нами ранее, который идентифицирует и конфигурирует периферийное устрой-
ство UART;
•pData: это указатель на массив, длина которого по крайней мере равна параметру Size, содержащий последовательность байтов, которую мы собираемся получить.
Функция не будет блокировать микроконтроллер, ожидая прием данных, и передаст управление основному потоку, как только завершит конфигурацию UART.
Теперь мы можем приступить к перестроению первого примера.
Имя файла: src/main-ex2.c
37/* Разрешение прерываний от USART2 */
38HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
39HAL_NVIC_EnableIRQ(USART2_IRQn);
40
41printMessage:
42printWelcomeMessage();
44while (1) {
45opt = readUserInput();
46if(opt > 0) {
47processUserInput(opt);
48if(opt == 3)
49goto printMessage;
50}
51performCriticalTasks();
52}
53}
54
55int8_t readUserInput(void) {
56int8_t retVal = -1;
57
58if(UartReady == SET) {
59UartReady = RESET;
60HAL_UART_Receive_IT(&huart2, (uint8_t*)readBuf, 1);
61retVal = atoi(readBuf);
62}
63return retVal;
64}
65
66
67uint8_t processUserInput(int8_t opt) {
68char msg[30];
69
70if(!(opt >=1 && opt <= 3))
71return 0;
72
73sprintf(msg, "%d", opt);
74HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
Универсальные асинхронные последовательные средства связи |
228 |
75 HAL_UART_Transmit(&huart2, (uint8_t*)PROMPT, strlen(PROMPT), HAL_MAX_DELAY); 76
77switch(opt) {
78case 1:
79HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
80break;
81case 2:
82sprintf(msg, "\r\nUSER BUTTON status: %s",
83HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET ? "PRESSED" : "RELEASED");
84HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
85break;
86case 3:
87return 2;
88};
89
90return 1;
91}
92
93void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle) {
94/* Установить флаг передачи: передача завершена */
95UartReady = SET;
96}
Как видно из приведенного выше кода, первым шагом является разрешение USART2_IRQn и назначение ему приоритета16. Затем мы определяем соответствующую ISR в файле stm32xxxx_it.c (здесь не показана) и добавляем в нее вызов функции HAL_UART_IRQHandler(). Оставшаяся часть файла примера посвящена перестроению функций readUserInput() и processUserInput() для работы с асинхронными событиями.
Функция readUserInput() теперь проверяет значение глобальной переменной UartReady. Если она равна SET, это означает, что пользователь отправил символ на консоль управления. Этот символ содержится в глобальном массиве readBuf. Затем функция вызывает HAL_UART_Receive_IT() для получения другого символа в режиме прерываний. Когда readUserInput() возвращает значение больше 0, вызывается функция processUserInput(). Наконец, определяется функция HAL_UART_RxCpltCallback(), которая автоматически вызывается HAL при получении одного байта: она просто устанавливает глобальную переменную UartReady, которая, в свою очередь, используется readUserInput(), как было показано ранее.
Важно уточнить, что функция HAL_UART_RxCpltCallback() вызывается только тогда, когда получены все байты, указанные параметром Size, переданным в функцию HAL_UART_Re-
ceive_IT().
А как насчет функции HAL_UART_Transmit_IT()? Она работает аналогично HAL_UART_Receive_IT(): данная функция передает следующий байт в массив каждый раз, когда генерируется прерывание «Регистр TXE пуст». Тем не менее, при ее вызове необходимо со-
16 Пример предназначен для STM32F4. Пожалуйста, обратитесь к примерам книги для вашей конкретной
Nucleo.
Универсальные асинхронные последовательные средства связи |
229 |
блюдать особую осторожность. Поскольку функция возвращает управление вызывающей стороне, как только она заканчивает конфигурацию UART, последующий вызов той же функции завершится неудачно и вернет значение HAL_BUSY.
Предположим, что вы перестроили функцию printWelcomeMessage() из предыдущего примера следующим образом:
void printWelcomeMessage(void) {
HAL_UART_Transmit_IT(&huart2, (uint8_t*)"\033[0;0H", strlen("\033[0;0H")); HAL_UART_Transmit_IT(&huart2, (uint8_t*)"\033[2J", strlen("\033[2J")); HAL_UART_Transmit_IT(&huart2, (uint8_t*)WELCOME_MSG, strlen(WELCOME_MSG)); HAL_UART_Transmit_IT(&huart2, (uint8_t*)MAIN_MENU, strlen(MAIN_MENU)); HAL_UART_Transmit_IT(&huart2, (uint8_t*)PROMPT, strlen(PROMPT));
}
Приведенный выше код никогда не будет работать корректно, так как каждый вызов функции HAL_UART_Transmit_IT() намного быстрее, чем передача по UART, и последующие вызовы завершатся неудачно.
Если скорость не является строгим требованием для вашего приложения, а использование HAL_UART_Transmit_IT() ограничено несколькими частями вашего приложения, приведенный выше код можно перестроить следующим образом:
void printWelcomeMessage(void) {
char *strings[] = {"\033[0;0H", "\033[2J", WELCOME_MSG, MAIN_MENU, PROMPT};
for (uint8_t i = 0; i < 5; i++) {
HAL_UART_Transmit_IT(&huart2, (uint8_t*)strings[i], strlen(strings[i])); while (HAL_UART_GetState(&huart2) == HAL_UART_STATE_BUSY_TX ||
HAL_UART_GetState(&huart2) == HAL_UART_STATE_BUSY_TX_RX);
}
}
Здесь мы передаем каждую строку, используя HAL_UART_Transmit_IT(), но, прежде чем мы передадим следующую строку, мы ждем завершения передачи. Таким образом, это всего лишь вариант HAL_UART_Transmit(), так как у нас существует цикл активного ожидания при каждой передаче по UART.
Более элегантное и эффективное решение состоит в том, чтобы использовать временную область памяти, в которой хранятся последовательности байтов, и позволить ISR выполнять передачу. Очередь (queue) является лучшим вариантом для обработки событий с логикой FIFO (First In – First Out). Существует несколько способов реализовать очередь, используя как статическую, так и динамическую структуру данных. Если мы решим реализовать очередь с предопределенной областью памяти, кольцевой буфер (circular buffer) является структурой данных, подходящей для такого рода приложений.
Кольцевой буфер – это не что иное, как массив с фиксированным размером, в котором используются два указателя для отслеживания головы (head) и хвоста (tail) данных, нуждающихся в обработке. В кольцевом буфере первая и последняя позиции массива видны «прилегающими» (см. рисунок 12). По этой причине данная структура данных называется кольцевой. Кольцевые буферы также имеют важную особенность: если наше приложение имеет до двух одновременных потоков выполнения (в нашем случае, основной
Универсальные асинхронные последовательные средства связи |
230 |
поток, который помещает символы в буфер, и процедура ISR, которая отправляет эти символы по UART), они являются по сути потокобезопасными (thread safe), поскольку поток «потребителя» (в нашем случае ISR) будет обновлять только указатель хвоста, а «производитель» (основной поток) будет обновлять только указатель головы.
Рисунок 12: Кольцевой буфер, реализованный с использованием массива и двух указателей
Кольцевые буферы могут быть реализованы несколькими способами. Некоторые из них быстрее, другие более безопасны (то есть они добавляют накладные расходы, гарантирующие, что мы правильно обрабатываем содержимое буфера). Вы найдете простую и довольно быструю реализацию в примерах книги. Объяснение того, как он реализован в коде, выходит за рамки данной книги.
Используя кольцевой буфер, мы можем определить новую функцию передачи по UART следующим образом:
uint8_t UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t len) { if(HAL_UART_Transmit_IT(huart, pData, len) != HAL_OK) {
if(RingBuffer_Write(&txBuf, pData, len) != RING_BUFFER_OK) return 0;
}
return 1;
}
Функция выполняет всего лишь две операции: она пытается отправить буфер данных по UART в режиме прерываний; если происходит сбой функции HAL_UART_Transmit_IT() (что означает, что UART уже передает другое сообщение), то последовательность байтов помещается в кольцевой буфер.
Дело теперь за функцией HAL_UART_TxCpltCallback(), проверяющей наличие отложенных байт в кольцевом буфере: