- •Оглавление
- •Предисловие
- •Почему я написал книгу?
- •Для кого эта книга?
- •Как использовать эту книгу?
- •Как организована книга?
- •Об авторе
- •Ошибки и предложения
- •Поддержка книги
- •Как помочь автору
- •Отказ от авторского права
- •Благодарность за участие
- •Перевод
- •Благодарности
- •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
I2C |
413 |
Таблица 1: Доступность периферийных устройств I²C в микроконтроллерах, оснащающих все шестнадцать плат Nucleo
Теперь мы готовы рассмотреть, как использовать API-интерфейсы CubeHAL для программирования данного периферийного устройства.
14.2. Модуль HAL_I2C
Для программирования периферийного устройства I²C CubeHAL объявляет структуру Си I2C_HandleTypeDef, которая определена следующим образом:
typedef struct { |
|
|
|
I2C_TypeDef |
*Instance; |
/* Базовый адрес регистров I²C |
*/ |
I2C_InitTypeDef |
Init; |
/* Параметры I²C-связи |
*/ |
uint8_t |
*pBuffPtr; |
/* Указатель на буфер передачи I²C |
*/ |
uint16_t |
XferSize; |
/* Размер передачи I²C |
*/ |
__IO uint16_t |
XferCount; |
/* Счетчик передачи I²C |
*/ |
DMA_HandleTypeDef |
*hdmatx; |
/* Параметры дескриптора DMA I²C Tx |
*/ |
DMA_HandleTypeDef |
*hdmarx; |
/* Параметры дескриптора DMA I²C Rx |
*/ |
HAL_LockTypeDef |
Lock; |
/* Блокировка объекта I²C |
*/ |
I2C |
|
|
|
414 |
__IO HAL_I2C_StateTypeDef |
State; |
/* Состояние работы I²C |
*/ |
|
__IO HAL_I2C_ModeTypeDef |
Mode; |
/* |
Режим I²C-связи |
*/ |
__IO uint32_t |
ErrorCode; |
/* |
Код ошибки I²C |
*/ |
} I2C_HandleTypeDef; |
|
|
|
|
Давайте проанализируем наиболее важные поля данной структуры Си.
•Instance (экземпляр): это указатель на дескриптор I²C, который мы будем исполь-
зовать. Например, I2C1 является дескриптором первого периферийного устройства I²C.
•Init: экземпляр структуры Си I2C_InitTypeDef, используемой для конфигурации
периферийного устройства. Мы рассмотрим ее более подробно в ближайшее время.
•pBuffPtr: указатель на внутренний буфер, используемый для временного хране-
ния данных, передаваемых на периферийное устройство I²C и с него. Он используется, когда I²C работает в режиме прерываний и данный буфер не должен изменяться из пользовательского кода.
•hdmatx, hdmarx: указатель на экземпляры структуры DMA_HandleTypeDef, используе-
мые, когда периферийное устройство I²C работает в режиме DMA.
Конфигурация периферийного устройства I²C выполняется с использованием экземпляра структуры Си I2C_InitTypeDef, которая определена следующим образом:
typedef struct { |
|
|
uint32_t ClockSpeed; |
/* Задает тактовую частоту. |
*/ |
uint32_t DutyCycle; |
/* Задает рабочий цикл I²C режима fast mode. |
*/ |
uint32_t OwnAddress1; |
/* Задает собственный адрес первого устройства. |
*/ |
uint32_t OwnAddress2; |
/* Задает собственный адрес второго устройства, |
|
|
если выбран режим двойной адресации. |
*/ |
uint32_t AddressingMode; /* Определяет, выбран 7-разрядный или 10-разрядный |
|
|
|
режим адресации. |
*/ |
uint32_t DualAddressMode; |
/* Определяет, выбран ли режим двойной адресации. |
*/ |
uint32_t GeneralCallMode; |
/* Определяет, включен ли режим общего вызова. |
*/ |
uint32_t NoStretchMode; |
/* Определяет, отключен ли режим удержания |
|
|
синхросигнала. |
*/ |
} I2C_InitTypeDef; |
|
|
Функции наиболее важных полей данной структуры Си.
•ClockSpeed: в этом поле задается скорость интерфейса I²C, и она должна соответ-
ствовать скоростям шины, определенным в спецификациях I²C (режимы standard mode, fast mode и т. д.). Однако точное значение данного поля также зависит от значения DutyCycle, как мы увидим далее. Максимальное значение для этого поля для большинства микроконтроллеров STM32 составляет 400000 (400 кГц), что означает, что микроконтроллеры STM32 поддерживают режимы вплоть до fast mode. Микроконтроллеры STM32F0/F3/F7/L0/L4 составляют исключение из этого правила (см. таблицу 1) и поддерживают также режим fast mode plus (1 МГц). В этих других микроконтроллерах поле ClockSpeed заменяется другим, называемым Timing. Значение конфигурации для поля Timing вычисляется по-другому, и мы не будем его рассматривать здесь. ST предоставляет
I2C |
415 |
специальное руководство по применению (AN423513), в котором объясняется, как вычислить точное значение для этого поля в соответствии с требуемой скоростью шины I²C. Тем не менее, CubeMX может сгенерировать для вас правильное значение конфигурации.
Таблица 2: Характеристики линий SDA и SCL для устройств шины I²C в режимах standard, fast, и fast-mode plus
•DutyCycle (рабочий цикл): это поле, которое доступно только в тех микроконтрол-
лерах, которые не поддерживают режим скорости обмена данными fast mode plus, задает соотношение между tLOW и tHIGH линии SCL шины I²C. Может принимать значения I2C_DUTYCYCLE_2 и I2C_DUTYCYCLE_16_9, чтобы указать рабочий цикл, равный 2:1 и 16:9. Выбирая заданный режим синхронизации, мы можем поделить периферийный тактовый сигнал для достижения желаемой тактовой частоты I²C. Чтобы лучше понять роль данного параметра конфигурации, нам нужно рассмотреть некоторые фундаментальные концепции шины I²C. В Главе 11 мы увидели,
что рабочий цикл (или коэффициент заполнения) – это процент от одного периода времени (например, 10 мкс), в течение которого сигнал активен. Для каждой из скоростей шины I²C спецификация I²C точно определяет минимальные значения tLOW и tHIGH. Таблица 2, взятая из UM10204 от NXP14, показывает значения tLOW и tHIGH для конкретной скорости обмена данными (значения выделены желтым цветом в таблице 2). Отношение этих двух значений является рабочим циклом, который не зависит от скорости обмена данными. Например, период 100 кГц соответствует 10 мкс, но tHIGH + tLOW из таблицы 2 составляет менее 10 мкс (4 мкс + 4,7 мкс = 8,7 мкс). Таким образом, соотношение фактических значений может варьироваться, если соблюдаются минимальные значения времени tLOW и tHIGH (4,7 мкс и 4 мкс соответственно). Смысл этих соотношений состоит в том, чтобы проиллюстрировать, что тайминги I²C различны для разных режимов I²C. Это не обязательные соотношения, которые должны соблюдаться периферийными устройствами I²C STM32. Например, tHIGH = 4 мкс и tLOW = 6 мкс составят соотношение, равное 0,67, которое по-прежнему совместимо с таймингами режима standard mode (100 кГц) (поскольку tHIGH = 4 мкс и tLOW > 4.7 мкс, а их сумма равна 10 мкс). Периферийные устройства I²C в микроконтроллерах STM32 определяют следующие рабочие циклы (соотношения). Для режима standard mode это соотношение составляет 1:1. Это означает, что tLOW = tHIGH = 5 мкс. Для режима fast mode мы можем использовать два соотношения: 2:1 или 16:9. Соотношение 2:1 означает, что 4 мкс (= 400 кГц) получаются при tLOW = 2,66 мкс и tHIGH = 1,33 мкс, и оба значения выше, чем значения, указанные в таблице 2 (0,6 мкс и 1,3 мкс). Соотношение 16:9 означает, что 4 мкс получаются при tLOW = 2,56 мкс и tHIGH = 1,44 мкс, и оба значения все еще выше, чем указано в таблице 2. Когда использовать
13 http://www.st.com/content/ccc/resource/technical/document/application_note/de/14/eb/51/75/e3/49/f8/DM00074956.pdf/files/DM00074956.pdf/jcr:content/translations/en.DM00074956.pdf
14 http://www.nxp.com/documents/user_manual/UM10204.pdf
I2C |
416 |
соотношение 2:1 вместо 16:9 и наоборот? Это зависит от периферийного тактового сигнала (PCLK1). Соотношение 2:1 означает, что 400 МГц достигаются путем деления источника тактового сигнала на 3 (1 + 2). Это означает, что PCLK1 должен быть кратным 1,2 МГц (400 кГц * 3). Использование соотношения 16:9 означает, что мы делим PCLK1 на 25. Это означает, что мы можем получить максимальную частоту шины I²C, когда PCLK1 кратен 10 МГц (400 кГц * 25). Таким образом, правильный выбор рабочих циклов зависит от действующей частоты шины APB1 и требуемой (максимальной) частоты линии SCL I²C. Важно подчеркнуть, что несмотря на то что частота линии SCL ниже 400 кГц (например, используя отношение 16:9 при частоте PCLK1, равной 8 МГц, мы можем достичь максимальной скорости обмена данными, равной 360 кГц), мы все равно удовлетворяем требования спецификации режима I²C fast mode (верхний предел 400 кГц).
•OwnAddress1, OwnAddress2: периферийное устройство I²C в микроконтроллерах
STM32 может использоваться для разработки как ведущих, так и ведомых I²C-устройств. При разработке ведомых I²C-устройств в поле OwnAddress1 можно указать адрес ведомого I²C-устройства: периферийное устройство автоматически определяет данный адрес на шине I²C и автоматически запускает все связанные события (например, оно может генерировать соответствующее прерывание, чтобы код микропрограммы мог начать новую транзакцию на шине). Периферийное устройство I²C поддерживает 7- или 10-разрядную адресацию, а также
режим 7-разрядной двойной адресации (7-bit dual addressing mode): в этом случае мы можем указать два отдельных 7-разрядных адреса ведомого устройства, чтобы устройство могло отвечать на запросы, отправленные на оба адреса.
•AddressingMode: это поле может принимать значения I2C_ADDRESSINGMODE_7BIT или
I2C_ADDRESSINGMODE_10BIT для указания режима 7- или 10-разрядной адресации со-
ответственно.
•DualAddressMode: это поле может принимать значения I2C_DUALADDRESS_ENABLE или I2C_DUALADDRESS_DISABLE для включения/отключения режима 7-разрядной двойной
адресации.
•GeneralCallMode: Общий вызов (General Call) – это своего рода широковещательная
адресация в протоколе I²C. Специальный адрес ведомого I²C-устройства – 0x0000 000 – используется для отправки сообщения всем устройствам на одной шине. Общий вызов является необязательной функцией, и, установив в данном поле значение I2C_GENERALCALL_ENABLE, периферийное устройство I²C будет генерировать события при получении адреса общего вызова. Мы не будем рассматривать этот режим в данной книге.
•NoStretchMode: это поле, которое может принимать значения I2C_NOSTRETCH_ENABLE
или I2C_NOSTRETCH_DISABLE, используется для отключения/включения необязательного режима удержания синхросигнала (обратите внимание, что, установив его в I2C_NOSTRETCH_ENABLE, вы отключите режим удержания синхросигнала). Для получения дополнительной информации об этом дополнительном режиме I²C см. UM10204 от NXP15 и справочное руководство по вашему микроконтроллеру.
Как обычно, для конфигурации периферийного устройства I²C мы используем:
HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c);
15 http://www.nxp.com/documents/user_manual/UM10204.pdf
I2C |
417 |
которая принимает указатель на экземпляр I2C_HandleTypeDef, рассмотренный ранее.
14.2.1.Использование периферийного устройства I²C в
режиме ведущего
Теперь мы собираемся проанализировать основные процедуры, предоставляемые CubeHAL для использования периферийного устройства I²C в режиме ведущего. Для выполнения транзакции по шине I²C в режиме записи CubeHAL предоставляет функцию:
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
где:
•hi2c: это указатель на экземпляр структуры I2C_HandleTypeDef, рассмотренный ра-
нее, который идентифицирует периферийное устройство I²C;
•DevAddress: это адрес ведомого устройства, длина которого может быть 7- или 10-
разрядной в зависимости от конкретной ИС;
•pData: это указатель на массив, размер которого равен параметру Size и содержит
последовательность байтов, которые мы собираемся передать;
•Timeout: представляет собой максимальное время, выраженное в миллисекундах, в течение которого мы будем ждать завершения передачи. Если передача не завершается в течение заданного времени ожидания, функция прерывает свое выполнение и возвращает значение HAL_TIMEOUT; в противном случае она возвращает значение HAL_OK, если не возникает других ошибок. Кроме того, мы можем передать тайм-аут, равный HAL_MAX_DELAY (0xFFFF FFFF), чтобы неопределенно долго ждать завершения передачи.
Для выполнения транзакции в режиме чтения мы можем использовать следующую функцию:
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
Обе предыдущие функции выполняют транзакцию в режиме опроса. Для транзакций на основе прерываний, мы можем использовать функции:
HAL_StatusTypeDef |
HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, |
|
|
uint16_t DevAddress, uint8_t *pData, uint16_t Size); |
\ |
HAL_StatusTypeDef |
HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, |
|
|
uint16_t DevAddress, uint8_t *pData, uint16_t Size); |
Данные функции работают так же, как и другие процедуры, описанные в предыдущих главах (например, те, которые касаются передачи UART в режиме прерываний). Чтобы использовать их правильно, нам нужно разрешить соответствующую ISR и выполнить вызов процедуры HAL_I2C_EV_IRQHandler(), которая, в свою очередь, вызывает
HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c), чтобы оповестить о завершении передачи в режиме записи, или HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c),
чтобы оповестить о завершении передачи в режиме чтения. За исключением семейств
I2C |
418 |
STM32F0 и STM32L0, периферийное устройство I²C во всех микроконтроллерах STM32 использует отдельное прерывание для оповещения об ошибках (взгляните на таблицу векторов, связанную с вашим микроконтроллером). По этой причине в соответствующей ISR нам нужно вызвать HAL_I2C_ER_IRQHandler(), который, в свою очередь, вызывает
HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) в случае ошибки. Существует десять раз-
личных обратных вызовов, вызываемых CubeHAL. В таблице 3 перечислены все из них, вместе с ISR, которые вызывают обратный вызов.
Таблица 3: Доступные обратные вызовы CubeHAL при работе периферийного устройства I²C в режиме прерываний или DMA
Обратный вызов |
Вызываемая ISR |
Описание |
|
|
|
HAL_I2C_MasterTxCpltCallback() |
I2Cx_EV_IRQHandler() |
Оповещает о том, что передача от |
|
|
ведущего к ведомому завершена |
|
|
(периферийное устройство работает |
|
|
в режиме ведущего). |
HAL_I2C_MasterRxCpltCallback() |
I2Cx_EV_IRQHandler() |
Оповещает о том, что передача от |
|
|
ведомого к ведущему завершена |
|
|
(периферийное устройство работает |
|
|
в режиме ведущего). |
HAL_I2C_SlaveTxCpltCallback() |
I2Cx_EV_IRQHandler() |
Оповещает о том, что передача от |
|
|
ведомого к ведущему завершена |
|
|
(периферийное устройство работает |
|
|
в режиме ведомого). |
HAL_I2C_SlaveRxCpltCallback() |
I2Cx_EV_IRQHandler() |
Оповещает о том, что передача от |
|
|
ведущего к ведомому завершена |
|
|
(периферийное устройство работает |
|
|
в режиме ведомого). |
HAL_I2C_MemTxCpltCallback() |
I2Cx_EV_IRQHandler() |
Оповещает о том, что передача от |
|
|
ведущего устройства к внешней па- |
|
|
мяти завершена (вызывается, только |
|
|
когда используются процедуры |
|
|
HAL_I2C_Mem_xxx() и периферийное |
|
|
устройство работает в режиме веду- |
|
|
щего). |
HAL_I2C_MemRxCpltCallback() |
I2Cx_EV_IRQHandler() |
Оповещает о том, что передача из |
|
|
внешней памяти к ведущему |
|
|
устройству завершена (вызывается |
|
|
только тогда, когда используются |
|
|
процедуры HAL_I2C_Mem_xxx() и пе- |
|
|
риферийное устройство работает в |
|
|
режиме ведущего). |
HAL_I2C_AddrCallback() |
I2Cx_EV_IRQHandler() |
Оповещает о том, что ведущее |
|
|
устройство разместило адрес пери- |
|
|
ферийного ведомого устройства на |
|
|
шине (периферийное устройство ра- |
|
|
ботает в режиме ведомого). |
I2C |
419 |
Таблица 3: Доступные обратные вызовы CubeHAL при работе периферийного устройства I²C в режиме прерываний или DMA (продолжение)
Обратный вызов |
Вызываемая ISR |
Описание |
|
|
|
HAL_I2C_ListenCpltCallback() |
I2Cx_EV_IRQHandler() |
Оповещает о том, что режим про- |
|
|
слушивания завершен (это происхо- |
|
|
дит, когда выдается STOP-условие и |
|
|
периферийное устройство работает |
|
|
в режиме ведомого – подробнее об |
|
|
этом позже). |
HAL_I2C_ErrorCallback() |
I2Cx_ER_IRQHandler() |
Оповещает о возникновении |
|
|
ошибки (периферийное устройство |
|
|
работает как в режиме ведущего, так |
|
|
и в режиме ведомого). |
HAL_I2C_AbortCpltCallback() |
I2Cx_ER_IRQHandler() |
Оповещает о том, что сработало |
|
|
STOP-условие и транзакция I²C была |
|
|
прервана (периферийное устройство |
|
|
работает как в режиме ведущего, так |
|
|
и в режиме ведомого). |
Наконец, функции:
HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c,
uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c,
uint16_t DevAddress, uint8_t *pData, uint16_t Size);
позволяют выполнять транзакции I²C с использованием DMA.
Для создания законченных и полностью работоспособных примеров нам необходимо внешнее устройство, способное взаимодействовать через шину I²C, поскольку платы Nucleo не предоставляют такую периферию. По этой причине мы будем использовать внешнюю память EEPROM: 24LCxx. Это довольно популярное семейство последовательных EEPROM, которые стали своего рода стандартом в электронной промышленности. Они дешевы (обычно стоят несколько десятков центов), выпускаются в различных корпусах (от «старых» корпусов THT P-DIP до современных и компактных корпусов WLCP), они обеспечивают хранение данных более 200 лет, а отдельные страницы памяти могут быть перезаписаны более 1 миллиона раз. Более того, многие производители интегральных схем имеют свои собственные совместимые версии (ST также предоставляет собственный набор EEPROM, совместимых с 24LCxx). Данная память настолько же популярна, как и таймеры 555, и я уверен, что она будет актуальна в течение многих лет.
Рисунок 6: Схема выводов EEPROM 24LCxx с корпусом PDIP-8
I2C |
420 |
Наши примеры будут основаны на модели 24LC64, которая является памятью EEPROM на 64 Кбит (это означает, что память может хранить 8 КБ или, если вы предпочитаете, 8192 Байта). Схема выводов версии PDIP-8 показана на рисунке 6. A0, A1 и A2 используются для установки LSB-битов адреса I²C, как показано на рисунке 7: если один из этих выводов притянут к земле, то соответствующий бит установлен в 0; если он подтянут к VDD, то бит устанавливается в 1. Если все три вывода подключены к земле, то адрес I²C соответствует 0xA0.
Рисунок 7: Как формируется адрес 24LCxx на шине I²C
Вывод WP – это вывод защиты от записи: если он подключен к земле, мы можем записывать в отдельные ячейки памяти. Напротив, при подключении к VDD операции записи не имеют никакого эффекта. Поскольку периферийное устройство I2C1 подключено к одним и тем же выводам на всех платах Nucleo, на рисунке 8 показан правильный способ подключения памяти EEPROM 24LCxx к Arduino-совместимому разъему всех шестнадцати плат Nucleo.
Прочитайте внимательно
Микроконтроллеры STM32F1 не предоставляют возможность подтягивания линий SDA и SCL. Их GPIO должны быть сконфигурированы как с открытым стоком (open-drain). Таким образом, вы должны добавить два дополнительных резистора для подтяжки линий I²C. Сопротивление между 4 кОм и 10 кОм является достоверным значением.
Как было сказано ранее, EEPROM на 64 Кбит имеет 8192 адреса в диапазоне от 0x0000 до 0x1FFF. Запись отдельного байта выполняется отправкой по шине I²C адреса EEPROM: старшей половины адреса ячейки памяти, за которой следует младшая половина, и значения, которое нужно сохранить в этой ячейке, закрывая транзакцию STOP-условием.
Рисунок 8: Как подключить Nucleo к памяти EEPROM 24LCxx
I2C |
421 |
Предполагая, что мы хотим сохранить значение 0x4C в ячейке памяти 0x320, на рисунке 9 показана правильная последовательность транзакций. Адрес 0x320 поделен на две части: первая часть, равная 0x3, передается первой, а младшая часть, равная 0x20, передается сразу после первой. Затем отправляются данные для хранения. Мы также можем отправить несколько байт в одной транзакции: внутренний счетчик адресов автоматически инкрементируется с каждым отправленным байтом. Это позволяет нам сократить время транзакции и увеличить общую пропускную способность.
Рисунок 9: Как выполнить операцию записи в память EEPROM 24LCxx
Бит ACK, установленный EEPROM на шине I²C после последнего отправленного байта, не означает, что данные были эффективно сохранены в памяти. Отправленные данные хранятся во временном буфере, поскольку ячейки памяти EEPROM стираются постранично, а не по отдельности. Вся страница (которая состоит из 32 Байт) обновляется при каждой операции записи, а переданные байты сохраняются только в конце этой операции. В течение времени стирания каждая команда, отправленная в EEPROM, будет игнорироваться. Чтобы определить, когда операция записи была завершена, нам нужно использовать опрос подтверждения (acknowledge polling). Он включает в себя отправку ведущим устройством START-условия, за которым следует адрес ведомого устройства плюс управляющий байт для команды записи (бит R/W установлен в 0). Если устройство все еще занято циклом записи, ACK не будет возвращаться. Если ACK не возвращается, бит START и управляющий байт должны быть отправлены повторно. Если цикл завершен, устройство вернет ACK, и ведущее устройство сможет продолжить отправку следующей команды чтения или записи.
Операции чтения инициируются так же, как и операции записи, за исключением того, что бит R/W управляющего байта установлен в 1. Существует три основных типа операций чтения: чтение текущего адреса, произвольное чтение и последовательное чтение. В данной книге мы сосредоточим наше внимание только на режиме произвольного чтения, оставляя читателю ответственность за углубление в другие режимы.
Операции произвольного чтения позволяют ведущему устройству получать доступ к любой ячейке памяти случайным образом. Чтобы выполнить данный тип операции чтения, адрес памяти должен быть отправлен первым. Это достигается отправкой адреса памяти в 24LCxx как часть операции записи (бит R/W устанавливается в «0»). Как только адрес памяти отправлен, ведущее устройство генерирует RESTART-условие (повторное START-условие) после ACK16. Это завершает операцию записи, но не раньше, чем будет установлен внутренний счетчик адресов. Затем ведущее устройство снова выдает адрес ведомого устройства, но на этот раз с битом R/W, установленным в 1. Затем 24LCxx выдаст ACK и передаст 8-битное слово данных. Ведущее устройство не будет подтверждать передачу и генерирует STOP-условие, которое заставляет EEPROM прекратить передачу (см. рисунок 10). После команды произвольного чтения внутренний счетчик адресов будет указывать на адрес ячейки памяти, следующей сразу за той, что была прочитана ранее.
16 Память EEPROM 24LCxx спроектирована таким образом, что она работает одинаково, даже если мы завершим транзакцию с помощью STOP-условия, а затем немедленно запустим новую в режиме чтения. Такая гибкость позволит нам организовать первый пример этой главы, как мы увидим через некоторое время.
I2C |
422 |
Рисунок 10: Как выполнить операцию произвольного чтения с EEPROM 24LCxx
Наконец мы готовы организовать законченный пример. Создадим две простые функ-
ции с именами Read_From_24LCxx() и Write_To_24LCxx(), которые позволяют записы-
вать/читать данные из памяти 24LCxx, используя CubeHAL. Затем мы проверим эти процедуры, просто сохранив строку в EEPROM, а затем прочитав ее обратно: если исходная строка равна той, которая считана из EEPROM, то светодиод LD2 Nucleo начнет мигать.
Имя файла: src/main-ex1.c
14int main(void) {
15const char wmsg[] = "We love STM32!";
16char rmsg[20];
17
18HAL_Init();
19Nucleo_BSP_Init();
21 MX_I2C1_Init();
22
23Write_To_24LCxx(&hi2c1, 0xA0, 0x1AAA, (uint8_t*)wmsg, strlen(wmsg)+1);
24Read_From_24LCxx(&hi2c1, 0xA0, 0x1AAA, (uint8_t*)rmsg, strlen(wmsg)+1);
26if(strcmp(wmsg, rmsg) == 0) {
27while(1) {
28HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
29HAL_Delay(100);
30}
31}
32
33while(1);
34}
35
36/* Функция инициализации I2C1 */
37static void MX_I2C1_Init(void) {
38GPIO_InitTypeDef GPIO_InitStruct;
40/* Разрешение тактирования периферии */
41 |
__HAL_RCC_I2C1_CLK_ENABLE(); |
42 |
|
43hi2c1.Instance = I2C1;
44hi2c1.Init.ClockSpeed = 100000;
45hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
46hi2c1.Init.OwnAddress1 = 0x0;
47hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
48hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
49hi2c1.Init.OwnAddress2 = 0;
50hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
51hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
52
53 GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
I2C |
423 |
54GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
55GPIO_InitStruct.Pull = GPIO_PULLUP;
56GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
57GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
58HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
59
60HAL_I2C_Init(&hi2c1);
61}
Давайте проанализируем приведенный выше фрагмент кода, начиная с процедуры MX_I2C1_Init(). Она разрешает тактирование периферийного устройства I2C1, чтобы мы могли программировать его регистры. Затем мы устанавливаем скорость шины (в нашем случае 100 кГц, и в этом случае параметр DutyCycle игнорируется, поскольку рабочий цикл зафиксирован на соотношении 1:1, когда шина работает на скоростях ниже или равных 100 кГц). Затем мы конфигурируем выводы PB8 и PB9 так, чтобы они действовали в качестве линий SCL и SDA соответственно.
Процедура main() очень проста: она сохранит строку "We love STM32!" в ячейке памяти по адресу 0x1AAA; затем строка считывается из EEPROM и сравнивается с исходной. Здесь нужно пояснить, почему мы сохраняем и считываем строку в буфере размером, равным strlen(wmsg)+1. Это потому, что процедура Си strlen() возвращают длину строки без символа конца строки ('\0'). Без сохранения этого символа и последующего чтения его из EEPROM strcmp() в строке 26 не сможет вычислить точную длину строки.
Имя файла: src/main-ex1.c
63HAL_StatusTypeDef Read_From_24LCxx(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, \
64uint16_t MemAddress, uint8_t *pData, uint16_t len) {
65HAL_StatusTypeDef returnValue;
66uint8_t addr[2];
67
68/* Вычисляем MSB и LSB части адреса памяти */
69addr[0] = (uint8_t) ((MemAddress & 0xFF00) >> 8);
70addr[1] = (uint8_t) (MemAddress & 0xFF);
71
72/* Сначала отправляем адрес ячейки памяти, откуда начинаем считывать данные */
73returnValue = HAL_I2C_Master_Transmit(hi2c, DevAddress, addr, 2, HAL_MAX_DELAY);
74if(returnValue != HAL_OK)
75return returnValue;
76
77/* Далее мы можем получить данные из EEPROM */
78returnValue = HAL_I2C_Master_Receive(hi2c, DevAddress, pData, len, HAL_MAX_DELAY);
80return returnValue;
81}
83HAL_StatusTypeDef Write_To_24LCxx(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, \
84uint16_t MemAddress, uint8_t *pData, uint16_t len) {
85HAL_StatusTypeDef returnValue;
86uint8_t *data;
I2C |
424 |
88/* Сначала мы выделяем временный буфер для хранения адреса памяти пункта
89* назначения и данных для сохранения */
90data = (uint8_t*)malloc(sizeof(uint8_t)*(len+2));
91
92/* Вычисляем MSB и LSB части адреса памяти */
93data[0] = (uint8_t) ((MemAddress & 0xFF00) >> 8);
94data[1] = (uint8_t) (MemAddress & 0xFF);
95
96/* И копируем содержимое массива pData во временный буфер */
97memcpy(data+2, pData, len);
98
99/* Теперь мы готовы передать буфер по шине I2C */
100returnValue = HAL_I2C_Master_Transmit(hi2c, DevAddress, data, len + 2, HAL_MAX_DELAY);
101if(returnValue != HAL_OK)
102return returnValue;
103
104 free(data);
105
106/* Ждем, пока EEPROM эффективно сохранит данные в памяти */
107while(HAL_I2C_Master_Transmit(hi2c, DevAddress, 0, 0, HAL_MAX_DELAY) != HAL_OK);
109return HAL_OK;
110}
Теперь мы можем сосредоточить наше внимание на двух процедурах для использования EEPROM 24LCxx. Обе они принимают одни и те же параметры:
•адрес ведомого I²C-устройства памяти EEPROM (DevAddress);
•адрес ячейки памяти, с которой начинается сохранение/считывание данных
(MemAddress);
•указатель на буфер памяти, используемый для обмена данными с EEPROM
(pData);
•объем данных для сохранения/чтения (len);
Функция Read_From_24LCxx() начинает вычислять две половины адреса памяти (MSB и LSB части). Затем она отправляет эти две части по шине I²C, используя процедуру HAL_I2C_Master_Transmit() (строка 73). Как было сказано ранее, память 24LCxx спроектирована так, чтобы она устанавливала во внутренний счетчик адресов значение переданного адреса. Таким образом, мы можем запустить новую транзакцию в режиме чтения, чтобы извлечь объем данных из EEPROM (строка 78).
Функция Write_To_24LCxx() делает практически то же самое, но несколько иным способом. Она должна соответствовать протоколу 24LCxx, описанному на рисунке 9, который немного отличается от протокола на рисунке 8. Это означает, что мы не можем использовать две отдельные транзакции для адреса ячейки памяти и данных для хранения, оба этих действия должны быть объединены в одну уникальную транзакцию I²C , По этой причине мы используем временный динамический буфер (строка 90), который содержит обе половины адреса памяти плюс данные для хранения в EEPROM. Мы можем выполнить транзакцию по шине I²C (строка 100), а затем подождать, пока EEPROM завершит передачу данных в ячейку памяти (строка 107).