- •Оглавление
- •Предисловие
- •Почему я написал книгу?
- •Для кого эта книга?
- •Как использовать эту книгу?
- •Как организована книга?
- •Об авторе
- •Ошибки и предложения
- •Поддержка книги
- •Как помочь автору
- •Отказ от авторского права
- •Благодарность за участие
- •Перевод
- •Благодарности
- •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
Организация памяти |
537 |
20.3. Как использовать CCM-память
Некоторые микроконтроллеры из семейств STM32F3/4/7 предоставляют дополнитель-
ную память SRAM, называемую памятью, связанную с ядром (Core Coupled Memory, CCM).
В отличие от обычной SRAM, эта память тесно связана с ядром Cortex-M. С этой областью памяти напрямую соединены как шина D-Bus, так и шина I-Bus (см. Рисунок 517), что позволяет выполнять состояние 0-ожиданий (0-wait state). И хотя вполне возможно хранить данные в этой памяти, такие как таблицы поиска и векторы инициализации, наилучшим использованием этой области является хранение критических и требующих большого объема вычислений процедур, которые могут выполняться в режиме реального времени. По этой причине в микроконтроллерах с CCM-памятью реализована
технология ускорения процедур (routine booster technology).
Рисунок 5: Прямое подключение ядра Cortex-M и SRAM CCM
Зачем использовать CCM-память для хранения кода вместо данных?
В Интернете довольно можно часто вычитать, что CCM-память может использоваться для хранения важных данных. Это гарантирует быстрый доступ к ним из ядра. Хоть в теории это и верно, но на практике это не дает преимуществ. Все микроконтроллеры STM32 с CCM-памятью также предоставляют SRAM, к которой можно обращаться при максимальной системной тактовой частоте с
17 Рисунок был составлен на основе того, что содержится в AN4296 от ST (http://www.st.com/web/en/resource/technical/document/application_note/DM00083249.pdf).
Организация памяти |
538 |
состоянием 0-ожиданий18. Более того, SRAM может быть доступно как ЦПУ, так и контроллеру DMA, а CCM-память – только ядру Cortex. Вместо этого, когда код расположен в SRAM CCM, а данные хранятся в обычном SRAM, ядро Cortex находится в оптимальной конфигурации для Гарвардской архитектуры, поскольку обеспечивает доступ с состоянием 0-ожиданий (без ожидания) для шины I-Bus (осуществляющей доступ к CCM-памяти) и для шины D-Bus (доступной параллельно для SRAM)19.
Однако очевидно, что если детерминированная производительность не важна для вашего приложения, и вам требуется дополнительное хранилище SRAM, то CCM-память является хорошим резервом для памяти данных.
Во всех микроконтроллерах STM32 с этой дополнительной памятью SRAM CCM отображается, начиная с адреса 0x1000 000020. Опять же, чтобы использовать ее, нам нужно определить эту область памяти внутри скрипта компоновщика следующим образом21:
/* организация памяти для STM32F334R8 */ MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 12K
CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 4K
}
Очевидно, что атрибут LENGTH должен отражать размер CCM-памяти для конкретного микроконтроллера STM32. Как только область памяти определена, мы должны создать специальную секцию в скрипте компоновщика:
.ccm : ALIGN(4) { *(.ccm .ccm*)
} >CCM
Чтобы переместить определенную процедуру в CCM-память, мы можем использовать ключевое слово GCC __attribute__, как было показано ранее для секции .isr_vector:
void __attribute__((section(".ccm"))) routine() {
...
}
18Некоторые микроконтроллеры STM32 предоставляют две памяти SRAM, одна из которых обеспечивает доступ с 0-ожиданием. Всегда сверяйтесь с техническим описанием по вашему микроконтроллеру.
19Имейте в виду, что для достижения полноценного параллельного доступа к SRAM, не должно быть никаких других ведущих устройств (например, DMA), соперничающих за доступ к SRAM через шинную матрицу.
20STM32F7 предоставляет специальный интерфейс тесно связанной памяти (Tightly Coupled Memory, TCM) с двумя отдельными шинами, которые соединяют ядро Cortex-M7 с Flash-памятью и SRAM. ITCM-RAM команд – это область только для чтения размером 16 КБ, доступная только ядром и отображаемая с адреса 0x0000 0000. DTCM-RAM данных представляет собой область размером 64 КБ, отображенную на адрес 0x2000 0000 и доступную всем ведущим устройствам на шине AHB из шинной матрицы AHB, но через
специальную ведомую шину AHB ядра. Обратитесь к справочному руководству по STM32F7 для получения дополнительной информации.
21 Конфигурация памяти относится к Nucleo-F334, которая вместе с Nucleo-F303 предоставляет CCM-память.
Организация памяти |
539 |
Если, наоборот, мы хотим хранить данные в CCM-памяти, то нам также нужно инициализировать их, как это было сделано для областей .bss и .data в обычной памяти SRAM. В этом случае нам нужен более полный скрипт компоновщика:
/* Используется кодом запуска (startup) для инициализации данных в CCM */ _siccm = LOADADDR(.ccm.data);
/* Секция инициализированных данных в CCM */
.ccm.data : ALIGN(4)
{
_sccmd = .;
*(.ccm.data .ccm.data*)
. = ALIGN(4); _eccmd = .;
} >CCM AT>FLASH
/* Секция неинициализированных данных в CCM */
.ccm.bss (NOLOAD) : ALIGN(4)
{
_sccmb = .; *(ccm.bss ccm.bss*)
. = ALIGN(4); _eccmb = .;
} >CCM
Здесь мы определяем две секции: .ccm.data, которая будет использоваться для хранения глобальных инициализированных данных в CCM, и .ccm.bss, используемая для хранения глобальных неинициализированных данных. Как и для обычной SRAM, потребуется
вызвать процедуры __initialize_data() и __initialize_bss() из процедуры _start():
...
__initialize_data(&_siccm, &_sccmd, &_eccmd);
__initialize_bss(&_sccmb, &_eccmb);
...
Затем, чтобы поместить данные в CCM, мы должны указать компилятору использовать ключевое слово attribute:
uint8_t initdata[] __attribute__((section(".ccm.data"))) = {0x1, 0x2, 0x3, 0x4}; uint8_t uninitdata __attribute__((section(".ccm.bss")));
20.3.1. Перемещение таблицы векторов в CCM-память
CCM-память также может использоваться для хранения процедур ISR, если переместить в нее всю таблицу векторов. Это может быть в особенности полезно для ISR, которые должны быть обработаны в кратчайшие сроки. Однако перемещение таблицы векторов требует дополнительных шагов, поскольку архитектура Cortex-M разработана таким образом, что таблица векторов начинается с адреса 0x0000 0004 (который соответствует адресу 0x0800 0004 внутренней Flash-памяти). Шаги, которые необходимо проделать, следующие:
Организация памяти |
540 |
•определить размещаемую в SRAM CCM таблицу векторов с помощью ключевого
слова __attribute__((section(".isr_vector_ccm"));
•определить обработчики исключений для требуемых исключений и ISR и поместить их в соответствующую секцию, используя ключевое слово
__attribute__((section(".ccm"));
•определить размещаемую во Flash-памяти минимальную таблицу векторов, состоящую из указателя MSP и адреса обработчика исключения сброса Reset и начинающуюся с адреса 0x0800 0000;
•переместить таблицу векторов в обработчике исключения сброса Reset копированием содержимого секции .ccm из Flash-памяти в SRAM.
Давайте начнем с определения размещаемой в SRAM CCM таблицы векторов. Определим файл с именем ccm_vector.c со следующим содержимым:
|
Имя файла: src/ccm_vector.c |
|
|
|
|
|
|
1 |
|
#include <stm32f3xx_hal.h> |
|
2 |
|
|
|
3 |
|
#define GPIOA_ODR |
((uint32_t*)(GPIOA_BASE + 0x14)) |
4 |
|
|
|
5 |
|
extern const uint32_t _estack; |
|
6 |
|
|
|
7 |
|
void SysTick_Handler(void); |
|
8 |
|
|
|
9 |
|
uint32_t *ccm_vector_table[] __attribute__((section(".isr_vector_ccm"))) = { |
|
10 |
|
(uint32_t *)&_estack, |
// указатель начала стека |
11 |
|
(uint32_t *) 0, |
// Reset_Handler не перемещаемый |
12(uint32_t *) 0,
13(uint32_t *) 0,
14(uint32_t *) 0,
15(uint32_t *) 0,
16(uint32_t *) 0,
17(uint32_t *) 0,
18(uint32_t *) 0,
19(uint32_t *) 0,
20(uint32_t *) 0,
21(uint32_t *) 0,
22(uint32_t *) 0,
23(uint32_t *) 0,
24(uint32_t *) 0,
25(uint32_t *) SysTick_Handler
26};
27
28void __attribute__((section(".ccm"))) SysTick_Handler(void) {
29*GPIOA_ODR = *GPIOA_ODR ? 0x0 : 0x20; // Вызывает мигание светодиода LD2
30}
Файл содержит всего лишь таблицу векторов, которая находится в секции
.isr_vector_ccm, и обработчик исключения SysTick, который находится в секции .ccm. Далее нам нужно перестроить скрипт компоновщика следующим образом:
Организация памяти |
541 |
Имя файла: src/ldscript6.ld
75/* Используется кодом запуска (startup) для загрузки ISR в CCM из FLASH */
76_slccm = LOADADDR(.ccm);
77
78.ccm : ALIGN(4)
79{
80_sccm = .;
81*(.isr_vector_ccm)
82*(.ccm)
83KEEP(*(.isr_vector_ccm .ccm))
85. = ALIGN(4);
86_eccm = .;
87} >CCM AT>FLASH
89/* Размер секции .ccm */
90_ccmsize = _eccm - _sccm;
Скрипт компоновщика не содержит ничего отличного от того, что мы уже видели. Определяется секция .ccm, и мы даем указание компоновщику сначала поместить в нее содержимое секции .isr_vector_ccm, а затем содержимое из секции .ccm, которая в нашем случае содержит всего лишь процедуру SysTick_Handler. Мы также даем указание компоновщику сохранить содержимое секции .ccm во Flash-памяти (используя директиву CCM AT>FLASH), в то время как адреса VMA секции .ccm привязываются к диапазону адресов CCM-памяти (то есть к начальному адресу 0x1000 0000).
Наконец, нам нужно вручную скопировать содержимое секции .ccm из Flash-памяти в CCM-память и переместить таблицу векторов. Эта работа снова выполняется в исключе-
нии Reset_Handler.
Имя файла: src/main-ex6.c
68/* Минимальная таблица векторов */
69uint32_t *vector_table[] __attribute__((section(".isr_vector"))) = {
70(uint32_t *)&_estack, // указатель начала стека
71(uint32_t *)_start // main в качестве Reset_Handler
72};
73
74void __attribute__ ((noreturn,weak))
75_start (void) {
76/* Копирование секции .ccm из FLASH-памяти (_slccm) в CCM-память */
77memcpy(&_sccm, &_slccm, (size_t)&_ccmsize);
78
79 __DMB(); // Это гарантирует, что запись в память завершена
80
81SCB->VTOR = (uint32_t)&_sccm; /* Перемещение таблицы векторов в 0x1000 0000 */
82SYSCFG->RCR = 0xF; /* Включение защиты от записи для CCM-памяти */
83
84 __DSB(); // Это гарантирует, что следующие команды используют новую конфигурацию
85
86 __initialize_data(&_sidata, &_sdata, &_edata);
Организация памяти |
542 |
87__initialize_bss(&_sbss, &_ebss);
88main();
89
90for(;;);
91}
92
93int main() {
94/* Разрешение подачи тактирования на периферийное устройство GPIOA */
95*RCC_APB1ENR |= 0x1 << 17;
96*GPIOA_MODER |= 0x400; // Установка MODER[11:10] = 0x1
97
98SysTick_Config(4000000); // Истекает каждые 0,5 с
99}
100
101void delay(uint32_t count) {
102while(count--);
103}
Строки [69:72] определяют минимальную таблицу векторов, используемую при сбросе ЦПУ. Она состоит только из указателя MSP и адреса исключения Reset_Handler, предоставленного процедурой _start(). Когда микроконтроллер сбрасывается, в строке 77 мы копируем содержимое секции .ccm из Flash-памяти (базовый адрес хранится в переменной _slccm) в CCM-память, а затем перемещаем всю таблицу векторов, назначая позицию массива ccm_vector_table в CCM-памяти регистру VTOR в блоке управления системой (System Control Block, SCB) – строка 81. Далее мы включаем защиту от записи во всей CCM-памяти, чтобы избежать нежелательных операций записи, которые могут повредить код.
CCM-память поделена на страницы размером 1 КБ. Каждый бит в регистре
RCR контроллера системной конфигурации (System Configuration Controller, SYSCFG) используется для установки защиты от записи индивидуально каждой странице (бит 1 устанавливает защиту для первой страницы, бит 2 устанавливает защиту для второй страницы и т. д.). Здесь мы защищаем от записи всю CCM-память микроконтроллера STM32F334, которая имеет CCM-память, состоящую из четырех страниц размером 1 КБ.
Важно отметить, что, если мы отключим запись для всей CCM-памяти, мы не сможем поместить в нее глобальные или статически выделенные переменные, иначе произойдет отказ. С другой стороны, помещение как кода, так и данных в CCM-память приводит к тому, что мы теряем преимущества, которые дает CCM-память, из-за одновременного доступа к одной и той же памяти как по шине D-Bus, так и по шине I-Bus (рассмотрев рисунок 5 вы можете убедиться, что CCM-память подключена лишь к одному ведущему порту шинной матрицы – порту M3; таким образом доступ из D-Bus и I-Bus организуется шинной матрицей).
Перемещение таблицы векторов не ограничивается CCM-памятью. Как мы увидим в Главе 22, этот метод также используется, когда микроконтроллер загружается из других источников, отличных от внутренней Flash-памяти. В
Организация памяти |
543 |
этом случае таблица векторов обычно помещается в SRAM, и затем ее необходимо переместить в другую память.
Перемещение таблицы векторов – возможность, недоступная в микроконтроллерах Cortex-M0, но доступная в Cortex-M0+. Как мы увидим в Главе 22, существует процедура, которая пытается справиться с этим ограничением.
20.4.Как использовать модуль MPU в микроконтроллерах STM32 на базе
Cortex-M0+/3/4/7
Помимо ядра Cortex-M0, все микроконтроллеры на базе Cortex-M могут дополнительно предоставлять модуль защиты памяти (Memory Protection Unit, MPU). И хорошая новость заключается в том, что все микроконтроллеры STM32 на базе этих ядер предоставляют его. MPU не следует путать с устройством управления памятью (Memory Management Unit, MMU) – продвинутым аппаратным компонентом, доступным в более производительных микропроцессорах, таких как Cortex-A, который в основном предназначен для преобразования адресов виртуальной памяти в физические.
MPU используется для защиты до восьми областей памяти, пронумерованных от 0 до 7. Они, в свою очередь, могут иметь восемь подобластей, если основная область составляет не менее 256 Байт. Подобласти имеют одинаковый размер и могут быть включены или отключены в соответствии с номером подобласти. MPU используется для того, чтобы сделать встроенную систему более надежной и безопасной, а в некоторых областях применения его использование является обязательным (например, в автомобильной и аэрокосмической промышленности). MPU может использоваться для:
•Запрета пользовательским приложениям повреждения данных, используемых критическими задачами (например, ядром операционной системы).
•Определения области памяти SRAM как неисполняемой для предотвращения атаки с внедрением кода.
•Изменения атрибутов доступа к памяти.
Если ядро ЦПУ нарушает определения доступа к определенной области памяти (например, пытается выполнить код из неисполняемой области), возникает исключение тяжелого отказа HardFault (или более конкретный отказ памяти Memory Fault, как мы увидим в Главе 24).
Области MPU могут покрывать все адресное пространство 4 ГБ, при этом они также могут перекрываться между собой. Характеристики области определяются двумя параметрами: типом области и ее атрибутами. Существует три типа памяти:
•Нормальная память: позволяет загрузку и сохранение байтов, полуслов и слов22 для их эффективной организации процессором (компилятор не знает о типах
22 Помните, что ядра Cortex-M0/0+ могут выполнять только выравнивание по словам.
Организация памяти |
544 |
областей памяти). Для области нормальной памяти загрузка/сохранение выполняется ЦПУ не обязательно в порядке, указанном в программе. Памяти SRAM и FLASH – два примера нормальной памяти.
•Память устройства: в пределах области памяти устройства загрузка и сохранение выполняются в строгой очередности. Это гарантирует установку регистров в правильном порядке, иначе это повлияет на поведение устройства.
•Строго упорядоченная память: все всегда выполняется в перечисленном программой порядке, при этом ЦПУ ожидает завершения выполнения инструкций загрузки/сохранения (эффективный доступ к шине), прежде чем выполнить следующую инструкцию в потоке программы. Это может привести к снижению производительности.
|
Таблица 2: Атрибуты области памяти |
Атрибут области |
Описание |
|
|
XN |
Никогда не исполнять |
AP |
Право доступа (см. таблицу 3) |
TEX |
Поле расширения типов (недоступно в Cortex-M0+) |
S |
Разделяемая |
C |
Кэшируемая |
B |
Буферизируемая |
SRD |
Включение/отключение подобласти |
SIZE |
Размер области памяти |
Каждая область памяти имеет восемь атрибутов, представленных в таблице 2:
•Никогда не исполнять (Execute never, XN): область памяти, отмеченная данным атрибутом, не позволяет исполнять программный код.
•Право доступа (Access Permission, AP): определяет права доступа к области памяти. Права устанавливаются как для привилегированного (например, ядра ОСРВ), так и для непривилегированного кода (например, отдельного потока). В таблице 3 перечислены все возможные комбинации.
•Поле расширения типов (Type Extension field, TEX), Кэшируемая (Cacheable, C) и Буферизируемая (Bufferable, B): эти поля используются для определения свойств кэширования области и, в некоторой степени, ее разделяемости (совместного использования). Они кодируются в соответствии с таблицей 4. Обратите внимание, что в ядрах Cortex-M0+ поле TEX всегда равно 0. Это потому, что ядра Cortex-M0+ поддерживают один уровень политики кэширования.
•Разделяемая (Shareable, S): это поле конфигурирует область разделяемой памяти. Система памяти обеспечивает синхронизацию данных между ведущими устройствами шины в системе, например, процессором с контроллером DMA. Строго упорядоченная память всегда разделяемая. Если несколько ведущих устройств шины могут получить доступ к неразделяемой области памяти, программное обеспечение должно гарантировать согласованность данных между ведущими устройствами шины. Это поле не поддерживается в архитектуре ARMv6-M и поэтому всегда устанавливается в 0 в процессорах Cortex-M0+.
•Включение/отключение подобласти (Subregion Enable/Disable, SRD): определяет,
включена ли конкретная подобласть. Отключение подобласти означает, что другая область, перекрывающая отключенный диапазон, будет учитываться при совпадении. Если нет другой включенной области, перекрывающей отключенную подобласть, то MPU генерирует отказ.
Организация памяти |
545 |
•Размер области памяти (SIZE): задает размер области памяти. Размер не может быть произвольным, но он может принимать значение из хорошо известного пула размеров областей (зависит от конкретного семейства STM32).
Таблица 3: Права доступа к области
Привилегированный |
Непривилегированный |
Описание |
|
|
|
Нет доступа |
Нет доступа |
Обращение к области приводит к отказу |
|
|
разрешения доступа |
RW (чтение/запись) |
Нет доступа |
Доступ только для привилегированного |
|
|
программного обеспечения |
RW (чтение/запись) |
RO (только чтение) |
Запись непривилегированным ПО вызовет |
|
|
отказ разрешения доступа |
RW (чтение/запись) |
RW (чтение/запись) |
Полный доступ к области |
Непредсказуемо |
Непредсказуемо |
ЗАРЕЗЕРВИРОВАНО |
RO (только чтение) |
Нет доступа |
Чтение только привилегированным ПО |
RO (только чтение) |
RO (только чтение) |
Только чтение, привилегированным и не- |
|
|
привилегированным ПО |
Как мы увидим в следующей главе, микроконтроллеры STM32F7 предоставляют встроенную кэш-память первого уровня (L1-cache). Для этих микроконтроллеров доступны следующие дополнительные атрибуты памяти:
•Кэшируемая/некэшируемая: означает, что выделенная область может быть кэширована или нет.
•Сквозная запись, без размещения записываемых данных (Write through with no write allocate): при совпадениях записывается в кэш и основную память, при несовпадениях обновляется блок в основной памяти без дублирования этого блока в кэш-памяти.
•Обратная запись, без размещения записываемых данных (Write-back with no write allocate): при совпадениях записывается в кэш, устанавливая грязный бит (dirty bit) для блока, основная память не обновляется. При несовпадении обновляется блок в основной памяти без его дублирования в кэш-памяти.
•Обратная запись, с размещением записываемых и считываемых данных
(Write-back with write and read allocate): при совпадениях записывается в кэш,
устанавливая грязный бит (dirty bit) для блока, основная память не обновляется. При несовпадении обновляется блок в основной памяти с его дублированием в кэш-памяти.
Таблица 4: Свойства кэширования и разделяемости областей
TEX |
C |
B |
Тип памяти |
Разделяемая |
Описание |
|
|
|
|
|
|
000 |
0 |
0 |
Строго упорядоченная |
Да |
Строго упорядоченная |
000 |
0 |
1 |
Память устройства |
Да |
Разделяемая память устройства |
000 |
1 |
0 |
Нормальная |
Зависит от бита S |
Сквозная запись, запись без выделения |
000 |
1 |
1 |
Нормальная |
Зависит от бита S |
Отложенная запись, запись без выделения |
001 |
0 |
0 |
Нормальная |
Зависит от бита S |
Не кэшируемая |
001 |
0 |
1 |
Зарезервировано |
Зарезервировано |
Зарезервировано |
001 |
1 |
0 |
Не определено |
Не определено |
Не определено |
001 |
1 |
1 |
Нормальная |
Зависит от бита S |
Отложенная запись, запись и чтение |
|
|
|
|
|
с выделением |
010 |
0 |
0 |
Память устройства |
Нет |
Неразделяемая память устройства |
010 |
0 |
1 |
Зарезервировано |
Зарезервировано |
Зарезервировано |