- •Оглавление
- •Предисловие
- •Почему я написал книгу?
- •Для кого эта книга?
- •Как использовать эту книгу?
- •Как организована книга?
- •Об авторе
- •Ошибки и предложения
- •Поддержка книги
- •Как помочь автору
- •Отказ от авторского права
- •Благодарность за участие
- •Перевод
- •Благодарности
- •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
Разработка IoT-приложений |
728 |
Функция начинает проверку, равно ли состояние сокета SOCK_ESTABLISHED: это означает, что удаленный узел установил соединение с нашим устройством. И, напротив, если сокет не находится в режиме прослушивания (строка 66), возможно, удаленный узел закрыл соединение: поэтому нам нужно снова сконфигурировать сокет в режиме прослушивания, вызвав функцию RetargetInit(). Если удаленный узел установил соединение, мы можем начать отправку буфера ptr через соединение TCP/IP.
Функция _read() практически не отличается от функции _write(). Полный исходный код см. в примерах книги. Чтобы использовать данный модуль, нам просто нужно определить макрос RETARGET_TCP на уровне проекта и, в конечном итоге, удалить макрос
OS_USE_SEMIHOSTING.
Чтобы установить соединение с устройством, пользователи Linux и MacOS могут использовать команду telnet, а пользователи Windows могут использовать программуэмулятор терминала, например putty.
26.2.4. Настройка HTTP-сервера
Модуль Internet/httpServer обеспечивает полную реализацию HTTP-сервера, построенного на уровне Ethernet. Данный модуль позволяет вам настроить HTTP-сервер за несколько шагов, особенно если вам нужно просто обработать статический контент (то есть простые веб-страницы, на которых не нужно динамически обрабатывать данные).
Функция
void httpServer_init(uint8_t * tx_buf, uint8_t * rx_buf, uint8_t cnt, uint8_t * socklist);
используется для конфигурации модуля HTTP. Она принимает два указателя, tx_buf и rx_buf, на два буфера памяти, используемых для хранения данных, которыми обменивается HTTP-сервер. В данных массивах должно быть достаточно места для хранения заголовков HTTP. Фактически, при доступе к веб-странице браузеру необходимо обменяться с веб-сервером несколькими «базовыми» сообщениями, определенными протоколом HTTP. Эти сообщения занимают несколько сотен байт, и по этой причине минимально допустимый размер буферов tx_buf и rx_buf равен 1024 Байт21. Параметр cnt сообщает модулю HTTP, сколько сокетов W5500 он может использовать, а параметр socklist используется для передачи точного списка доступных сокетов.
Например, следующий фрагмент кода инициализирует HTTP-сервер, передавая два буфера, каждый размером 1024 Байт, и массив, содержащий список сокетов, используемых для обработки HTTP-запросов:
#define DATA_BUF_SIZE 1024 #define MAX_HTTPSOCK 5
uint8_t RX_BUF[DATA_BUF_SIZE], TX_BUF[DATA_BUF_SIZE];
uint8_t socknumlist[] = {0, 1, 2, 3, 4};
...
httpServer_init(TX_BUF, RX_BUF, MAX_HTTPSOCK, socknumlist);
...
21 Можно уменьшить размер двух буферов, поскольку библиотека HTTP может разделить весь поток HTTP на более мелкие части, но это увеличит время передачи.
Разработка IoT-приложений |
729 |
После того, как HTTP-сервер настроен с точки зрения сети, нам нужно сообщить ему о содержимом, которое ему нужно обслуживать (HTML-страницы, изображения и т. д.). Это можно сделать двумя способами: один подходит для действительно небольших и ограниченных приложений, а другой – для более сложных и структурированных вебприложений.
Воспользовавшись функцией:
void reg_httpServer_webContent(uint8_t * content_name, uint8_t * content);
мы можем связать с заданным ресурсом (например, с файлом index.html) массив байт для отправки через сокет в браузер. Параметр content_name соответствует имени ресурса, а параметр content – массиву, содержащему байты, образующие ресурс.
Почему HTTP-серверу для выполнения своей работы требуется нечто большее, чем просто свободный сокет? Это фундаментальная концепция, которую следует учитывать при разработке встроенных веб-приложений, поэтому мы остановимся на ней несколько слов.
Современные веб-приложения достаточно сложны. Обычно сайт состоит из нескольких ресурсов:
•HTML-страницы, содержащие фактическое содержимое веб-приложения;
•изображения, украшающие содержимое HTML и, в некоторых случаях, составляющие часть содержимого веб-страницы;
•Файлы CSS и Javascript, которые конфигурируют отображение страницы и ее функциональные возможности.
Когда веб-браузер обращается к веб-сайту, он начинает загрузку главной HTMLстраницы (которая соответствует файлу index.html, если не указан иной). Эта страница анализируется почти сразу (некоторые браузеры могут начать синтаксический анализ страниц, если они получают самые первые байты), и, если она содержит ссылки на другие веб-ресурсы, браузер начинает загружать их практически параллельно. Этот одновременный доступ к ресурсам веб-сайта подразумевает, что несколько сокетов открываются браузером по отношению к HTTP-серверу (протокол HTTP не имеет состояния и определяет, что для каждого веб-ресурса должен быть выполнен отдельный запрос к серверу). Для настоящего веб-сервера, такого как Apache или NGIX, предназначенного для работы на мощной машине, это не составляет проблемы. Такие серверные приложения предназначены для обработки даже тысяч одновременных подключений. Более того, используемое мощное оборудование позволяет обслуживать контент даже за несколько миллисекунд, в зависимости от скорости соединения. Сокеты открываются и закрываются менее чем за секунду.
Для веб-сервера, работающего на полностью встроенной платформе, доступ к одновременным ресурсам – вещь, которую необходимо тщательно охарактеризовать. Каждый сокет потребляет несколько аппаратных ресурсов, и для устройства WIZnet максимальное количество сокетов ограничено. Это означает, что мы не можем организовать приложение по своему усмотрению, и некоторые современные фреймворки (например, Bootstrap, Angular JS и т. д.) часто необходимо переорганизовывать при использовании на встроенном устройстве (иногда нам приходится вообще избегать их).
Разработка IoT-приложений |
730 |
Например, чтобы отправить простую HTML-страницу, мы можем написать следующий код:
const char webpage[] = "<html> <head>
<title>Simple Web Page</title> </head>
<body>
<h1>Hello World!</h1> </body>";
reg_httpServer_webContent((uint8_t*)"index.html", webpage);
...
У этого подхода есть несколько подводных камней. Прежде всего, веб-страница встроена в код прошивки. Это означает, что содержимое HTML добавляется к самой прошивке, увеличивая весь бинарный образ.
Во-вторых, каждый раз, когда мы что-то меняем в веб-контенте, нам нужно перекомпилировать весь бинарный образ. Для больших и структурированных веб-приложений этот подход непрактичен.
Второй подход заключается в написании модуля HTTP таким образом, чтобы он находил и извлекал статический контент из устройства памяти. Например, мы можем изменить его код так, чтобы он загружал веб-ресурсы из Flash-памяти. Именно это мы и сделаем в следующем примере, где будет использоваться библиотека FatFs для извлечения веб-контента, хранящегося на внешней SD-карте.
Когда HTTP-сервер правильно настроен, мы можем начать обслуживать запросы, поступающие от удаленных узлов. По умолчанию эта операция выполняется в режиме опроса путем вызова следующей функции:
void httpServer_run(uint8_t seqnum);
Данная функция принимает индекс, соответствующий идентификатору сокета, хранящемуся в параметре socklist, переданном в функцию httpServer_init(). Для каждого зарегистрированного сокета эта функция проверяет состояние соответствующего сокета и выполняет конечный автомат HTTP в соответствии с текущим состоянием сокета. Например, если переданный сокет открыт и находится в режиме прослушивания, функция httpServer_init() проверяет, установил ли удаленный узел соединение. Весь протокол HTTP и конечный автомат обрабатываются модулем HTTP, при этом нет необходимости знать детали реализации, если нам не нужно делать с ним что-то более сложное.
Наконец, модуль HTTP использует некоторые внутренние задержки, основанные на общей единице временного интервала (тике). Функция:
httpServer_time_handler();
должна вызываться из таймера, сконфигурированного на истечение каждые 1 мс (ISR таймера SysTick – подходящее место для вызова этой функции).
Разработка IoT-приложений |
731 |
26.2.4.1. Веб-осциллограф
Из-за ограниченных аппаратных ресурсов большинства микроконтроллеров STM32, оснащающих шестнадцать платам Nucleo, данный пример был протестирован только на микроконтроллерах STM32F401RE, STM32F411RE и STM32F446RE.
А сейчас мы рассмотрим более полный пример, который показывает, как использовать ИС W5500 и модуль ioLibrary_Driver22 для создания сложных и структурированных приложений. В этом примере мы будем использовать несколько периферийных устройств STM32 для создания чего-то вроде веб-осциллографа, показанного на рисунке 7. Подключив источник сигнала к одному из входов периферийного устройства АЦП, мы можем увидеть сигнал соответствующей формы с простым доступом к веб-консоли, используя обычный браузер. Видео, показывающее, как работает осциллограф, можно посмотреть здесь23.
Рисунок 7: Интерфейс веб-осциллографа
В примере используется несколько популярных и современных веб-фреймворков для структурирования пользовательского интерфейса: Bootstrap24, jQuery25 и D3js26. Описание этих фреймворков выходит за рамки книги, и предполагается, что читатель имеет достаточные знания о наиболее распространенных и современных методах веб-разра- ботки.
Приложение состоит из двух основных частей: «основная» часть, отвечающая за ана- лого-цифровое преобразование с выбранного входа АЦП (по умолчанию IN0), и часть,
22Библиотека ioLibrary_Driver примера из данной главы не является официальной библиотекой, предоставляемой WIZnet. Автор книги внес несколько изменений в модуль HTTP для повышения его надежности, производительности и гибкости. Например, данная модифицированная версия может обслуживать контент, хранящийся на SD-карте, с помощью модуля FatFs или на ПК разработчика с использованием полуохостинга ARM.
23https://youtu.be/fjtLQJDJ_04
24http://getbootstrap.com/
25https://jquery.com/
26https://d3js.org/
Разработка IoT-приложений |
732 |
которая обслуживает HTTP-запросы с помощью модуля ioLibrary_Driver. Предполагается, что все веб-ресурсы размещены на SD-карте, доступ к которой осуществляется с помощью модуля FatFs и SPI-совместимого драйвера, разработанного автором книги. Однако для упрощения процесса разработки приложение также может обслуживать контент с ПК разработчика, используя вызовы полуохостинга ARM и обычные функции стандартной библиотеки Си.
Следующий фрагмент кода относится к функции main(). Чтобы получить сигнал, который изменяется во времени (например, синусоидальный 50 Гц сигнал), нам необходимо выполнять АЦП преобразование через равные промежутки времени. Поэтому мы используем таймер TIM227 для управления периферийным устройством ADC1, которое сконфигурировано для работы с DMA в циклическом режиме: так таймер будет запускать преобразование непрерывно. Таким образом, АЦП запускается в режиме DMA (строка 145), а преобразованные значения сохраняются в массиве _adcConv. Семафор adcSem, созданный в строке 140, будет использоваться для управления доступом к массиву _adcConv, который содержит преобразованные значения АЦП. Его роль будет лучше объяснена позже.
Имя файла: src/ch25/main-ex2.c
135int main(void) {
136HAL_Init();
137Nucleo_BSP_Init();
139osSemaphoreDef(adcSem);
140adcSemID = osSemaphoreCreate(osSemaphore(adcSem), 1);
142MX_ADC1_Init();
143MX_TIM2_Init();
144HAL_TIM_Base_Start(&htim2);
145HAL_ADC_Start_DMA(&hadc1, (uint32_t*)_adcConv, 200);
147 |
MX_SPI1_Init(); |
148 |
|
149#if defined(_USE_SDCARD_) && !defined(OS_USE_SEMIHOSTING)
150SD_SPI_Configure(SD_CS_GPIO_Port, SD_CS_Pin, &hspi1);
151MX_FATFS_Init();
152
153if(f_mount(&diskHandle, "0:", 1) != FR_OK) {
154#ifdef DEBUG
155asm("BKPT #0");
156#else
157while(1) {
158HAL_Delay(500);
159HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
160}
161#endif //#ifdef DEBUG
162}
163
164 #ifdef OS_USE_TRACE_ITM
27 Представленный здесь исходный код относится к плате Nucleo-F401RE.
Разработка IoT-приложений |
733 |
165/* Вывод содержимого SD через порт ITM */
166TCHAR buff[256];
167strcpy(buff, (char*)L"/");
168scan_files(buff);
169#endif //#ifdef OS_USE_TRACE_ITM
170
171 #endif //#if defined(_USE_SDCARD_) && !defined(OS_USE_SEMIHOSTING)
172
173osThreadDef(w5500, SetupW5500Thread, osPriorityNormal, 0, 512);
174osThreadCreate(osThread(w5500), NULL);
175
176osKernelStart();
177/* Сюда никогда не придем, но на всякий случай... */
178while(1);
179}
Если установлен глобальный макрос _USE_SDCARD_, и мы не используем полухостинг ARM (поэтому глобальный макрос OS_USE_SEMIHOSTING не установлен), это означает, что веб-ресурсы (файлы HTML, изображения и файлы сценариев CSS/JS) хранятся на карте MicroSD, вставленной в слот W5500 Shield. Поэтому инициализируется библиотека FatFs (строки [150:151]) и монтируется первый раздел (строка 153). Более того, если мы используем отладчик, способный считывать стимулы ITM, мы выводим на SWV-консоли содержимое SD-карты, вызывая процедуру scan_files() (которая была показана в Главе
25).
Наконец, запускается поток SetupW5500Thread(), который отвечает за конфигурацию ИС W5500 и обработку входящих HTTP-запросов. Его код достаточно прост, и он показан ниже.
Имя файла: src/ch25/main-ex2.c
181void SetupW5500Thread(void const *argument) {
182UNUSED(argument);
183
184/* Конфигурация модуля W5500 */
185IO_LIBRARY_Init();
186
187/* Конфигурация сервера HTTP */
188httpServer_init(TX_BUF, RX_BUF, MAX_HTTPSOCK, socknumlist);
189reg_httpServer_cbfunc(NVIC_SystemReset, NULL);
190
191/* Запуск обработки сокетов */
192while(1) {
193for(uint8_t i = 0; i < MAX_HTTPSOCK; i++)
194httpServer_run(i);
195/* Вводим задержку в 1 мс просто за тем, чтобы другие потоки
196* с таким же или более низким приоритетом могли выполняться */
197osDelay(1);
198}
199}
Разработка IoT-приложений |
734 |
Поток начинает конфигурацию как ИС W5500, так и библиотеки ioLibrary_Driver, вызывая функцию IO_LIBRARY_Init() (показано ниже). Затем конфигурируется HTTP-сервер (строка 188), и бесконечный цикл вызывает функцию httpServer_run() для каждого сокета, выделенного для HTTP-сервера.
Имя файла: src/ch25/main-ex2.c
79void IO_LIBRARY_Init(void) {
80uint8_t runApplication = 0, dhcpRetry = 0, phyLink = 0, bufSize[] = {2, 2, 2, 2, 2};
81wiz_NetInfo netInfo;
82
83reg_wizchip_cs_cbfunc(cs_sel, cs_desel);
84reg_wizchip_spi_cbfunc(spi_rb, spi_wb);
85reg_wizchip_spiburst_cbfunc(spi_rb_burst, spi_wb_burst);
86reg_wizchip_cris_cbfunc(vPortEnterCritical, vPortExitCritical);
88wizchip_init(bufSize, bufSize);
90 |
ReadNetCfgFromFile(&netInfo); |
91 |
|
92/* Ожидание подключения кабеля ETH */
93do {
94ctlwizchip(CW_GET_PHYLINK, (void*) &phyLink);
95osDelay(10);
96} while(phyLink == PHY_LINK_OFF);
97
98if(netInfo.dhcp == NETINFO_DHCP) { /* Режим DHCP */
99DHCP_init(DHCP_SOCK, RX_BUF);
100
101while(!runApplication) {
102switch(DHCP_run()) {
103case DHCP_IP_LEASED:
104case DHCP_IP_ASSIGN:
105case DHCP_IP_CHANGED:
106getIPfromDHCP(netInfo.ip);
107getGWfromDHCP(netInfo.gw);
108getSNfromDHCP(netInfo.sn);
109getDNSfromDHCP(netInfo.dns);
110runApplication = 1;
111break;
112case DHCP_FAILED:
113dhcpRetry++;
114if(dhcpRetry > MAX_DHCP_RETRY)
115{
116netInfo.dhcp = NETINFO_STATIC;
117DHCP_stop(); // при перезапуске повторный вызов DHCP_init()
118#ifdef _MAIN_DEBUG_
119printf(">> DHCP %d Failed\r\n", my_dhcp_retry);
120Net_Conf();
121Display_Net_Conf(); // вывод статического netinfo на последовательный порт
122#endif
Разработка IoT-приложений |
735 |
123dhcpRetry = 0;
124asm("BKPT #0");
125}
126break;
127default:
128break;
129}
130}
131}
132wizchip_setnetinfo(&netInfo);
133}
Функция IO_LIBRARY_Init() отвечает за правильную конфигурацию микросхемы W5500. Она начинается с конфигурация функций, используемых для обмена данными по шине SPI (строки [83:88], как показано в первом примере данной главы). Затем, в строке 90, функция ReadNetCfgFromFile() используется для получения конфигурации сети из файла, хранящегося на SD-карте. Этот файл называется net.cfg и должен иметь следующую структуру:
1NODHCP
20:11:22:33:44:55
3192.168.1.165
4255.255.255.0
5192.168.1.1
68.8.8.8
Первая строка может принимать значения (NODHCP and DHCP) и указывает, конфигурироваться ли сетевому IP статически или динамически. Вторая строка соответствует MACадресу, а следующие четыре строки соответствуют IP-адресу устройства, маске подсети, сетевому шлюзу и первичному DNS. При чтении содержимого этого файла автоматически конфигурируется сетевой интерфейс. Пользователь может изменять параметры сети через специальную веб-страницу, как показано на рисунке 8.
Рисунок 8: Веб-страница, используемая для настройки параметров сети
Разработка IoT-приложений |
736 |
Как только сетевые настройки извлечены из файла конфигурации, функция IO_LIBRARY_Init() входит в бесконечный цикл, ожидая подключения кабеля LAN к порту RJ45 (строки [93:96]). Когда это происходит, функция запускает процедуру обнаружения DHCP, если сетевой интерфейс сконфигурирован в режиме DHCP. Наконец, в строке 132 сетевой интерфейс настраивается согласно параметрам, хранящимся в файле net.cfg, или параметрам, полученным DHCP-сервером в той же сети.
Остальная часть приложения в основном состоит из HTTP-сервера. Когда сокет устанавливает соединение с удаленным узлом, процедура httpServer_run() вызывает функцию http_process_handler(), которая отвечает за обработку входящих HTTP-запросов.
Эта функция начинает анализ HTTP-метода запроса (GET, POST, PUT и т. д.). Здесь нас интересует способ обработки метода GET.
Имя файла: Middlewares/ioLibrary_Driver/Internet/httpServer/httpServer.c
531case METHOD_GET :
532get_http_uri_name(p_http_request->URI, uri_buf);
533uri_name = uri_buf;
534
535// Если URI – «/», ответить index.html.
536if (!strcmp((char *)uri_name, "/")) strcpy((char *)uri_name, INITIAL_WEBPAGE);
537if (!strcmp((char *)uri_name, "m")) strcpy((char *)uri_name, M_INITIAL_WEBPAGE);
358if (!strcmp((char *)uri_name, "mobile")) strcpy((char *)uri_name, MOBILE_INITIAL_WEBPAGE
359// Проверка запрошенных типов файлов (включая HTML, TEXT, GIF, JPEG и др.)
540 find_http_uri_type(&p_http_request->TYPE, uri_name); 541
542#ifdef _HTTPSERVER_DEBUG_
543printf("\r\n> HTTPSocket[%d] : HTTP Method GET\r\n", s);
544printf("> HTTPSocket[%d] : Request Type = %d\r\n", s, p_http_request->TYPE);
545printf("> HTTPSocket[%d] : Request URI = %s\r\n", s, uri_name);
546#endif
547
548if(p_http_request->TYPE == PTYPE_CGI)
549{
550content_found = http_get_cgi_handler(uri_name, pHTTP_TX, &file_len);
551if(content_found && (file_len <= (DATA_BUF_SIZE-(strlen(RES_CGIHEAD_OK)+8))))
552{
553send_http_response_cgi(s, http_response, pHTTP_TX, (uint16_t)file_len);
554}
555else
556{
557send_http_response_header(s, PTYPE_CGI, 0, STATUS_NOT_FOUND);
558}
559}
560else
561{
562// Нахождение зарегистрированного пользователем индекса для веб-контента
563if(find_userReg_webContent(uri_buf, &content_num, &file_len))
564{
565content_found = 1; // Веб-контент, найденный во Flash-памяти кода
566content_addr = (uint32_t)content_num;
Разработка IoT-приложений |
737 |
567HTTPSock_Status[get_seqnum].storage_type = CODEFLASH;
568}
569// Запрос не CGI, запрошен веб-контент на «SD-карте» или в «данных Flash-памяти»
570#if defined(_USE_SDCARD_) && !defined(OS_USE_SEMIHOSTING)
571#ifdef _HTTPSERVER_DEBUG_
572printf("\r\n> HTTPSocket[%d] : Searching the requested content\r\n", s);
573#endif
574if((fr = f_open(&HTTPSock_Status[get_seqnum].fs, (const char *)uri_name, FA_READ))==0)
575{
576content_found = 1; // файл открыт успешно
577
578file_len = f_size(&HTTPSock_Status[get_seqnum].fs);
579HTTPSock_Status[get_seqnum].file_len = file_len;
580strcpy(HTTPSock_Status[get_seqnum].file_name, uri_name);
581HTTPSock_Status[get_seqnum].storage_type = SDCARD;
582}
583#elif defined(OS_USE_SEMIHOSTING)
584// Не запрос CGI, веб-контент, полученный через ARM Semihosting
585char *base_path = OS_BASE_FS_PATH;
586char *path;
587
588 path = malloc(sizeof(char)*strlen(base_path)+strlen(uri_name));
589strcpy(path, base_path);
590strcpy(path+strlen(base_path), uri_name);
592HTTPSock_Status[get_seqnum].fs = fopen((const char *)path,"r");
593if(HTTPSock_Status[get_seqnum].fs != NULL) {
594content_found = 1; // файл открыт успешно
596fseek(HTTPSock_Status[get_seqnum].fs, 0L, SEEK_END);
597file_len = ftell(HTTPSock_Status[get_seqnum].fs);
598HTTPSock_Status[get_seqnum].file_len = file_len;
599fseek(HTTPSock_Status[get_seqnum].fs, 0L, SEEK_SET);
600strcpy(HTTPSock_Status[get_seqnum].file_name, uri_name);
601HTTPSock_Status[get_seqnum].storage_type = SDCARD;
602}
603}
Функция get_http_uri_name() в строке 532 извлекает URL-адрес, запрошенный клиентским приложением. Если этот URL-адрес равен только «/», это означает, что браузер запрашивает URL-адрес по умолчанию, который соответствует файлу index.html. Вызов функции find_http_uri_type() в строке 541 определяет Content-Type, связанный с запрошенным URL. Content-Type является производным от расширения файла. Например, Content-Type файла, заканчивающегося на .gif, устанавливается на PTYPE_GIF.
Если Content-Type – CGI28 (строка 549), то вызов функции http_get_cgi_handler() определяет создание динамического содержимого. Позже мы разберем, как устроена данная
28 Общий интерфейс шлюза (Common Gateway Interface, CGI) – это стандартизованный протокол, используемый для взаимодействия «серверных приложений», который динамически обрабатывает запросы, поступающие от клиентов. Исторически CGI были введены для динамической генерации веб-контента. В
Разработка IoT-приложений |
738 |
функция. Для всех других зарегистрированных типов содержимого Content-Type (см.
полный список в реализации find_http_uri_type()) функция http_process_handler()
начинает поиск запрошенного ресурса (строка 561). Прежде всего, функция проверяет, было ли зарегистрировано содержимое с помощью функции reg_httpServer_webContent(). В этом случае содержимое автоматически извлекается из Flash-памяти и отправляется в браузер. Если содержимое не хранится во Flash-памяти микроконтроллера и установлен макрос _USE_SDCARD_, тогда функция проверяет, сохранено ли запрошенное содержимое на карте MicroSD (строки [575:584]). Для доступа к запрошенному файлу используется API-интерфейс FatFs. Если вместо этого включен полуохостинг ARM, то для получения файла с ПК разработчика используются стандартные процедуры Си (строки
[586:605]).
Функция http_get_cgi_handler() отвечает за создание динамического содержимого вебприложения (например, данных, отобранных периферийным устройством ADC). Функция написана так, что она запрашивает доступ к динамическим страницам /adc.cgi и /network.cgi. Давайте начнем со второго.
Имя файла: Middlewares/ioLibrary_Driver/Internet/httpServer/httpUtil.c
22extern ADC_HandleTypeDef hadc1;
23extern uint16_t adcConv[100], _adcConv[200];
24extern TIM_HandleTypeDef htim2;
25extern osSemaphoreId adcSemID;
27uint8_t http_get_cgi_handler(uint8_t * uri_name, uint8_t * buf, uint32_t * file_len)
28{
29uint8_t ret = HTTP_FAILED;
30uint16_t len = 0;
32if(strcmp((const char*)uri_name, "adc.cgi") == 0) {
33char *pbuf = (char*)buf;
35/* Вычисление текущей частоты TIM2 */
36uint32_t freq = HAL_RCC_GetPCLK2Freq() / (((htim2.Init.Prescaler + 1) *
37 |
(htim2.Init.Period + 1))); |
38 |
pbuf += sprintf(pbuf, "{\"f\":%lu,\"d\":[", freq); |
39 |
|
40/* Ожидание завершения HAL_ADC_ConvCpltCallback()
41или HAL_ADC_HalfConvCpltCallback() */
42osSemaphoreWait(adcSemID, osWaitForever);
43for(uint8_t i = 0; i < 100; i++)
44pbuf += sprintf(pbuf, "%.2f,", adcConv[i]*0.805);
45osSemaphoreRelease(adcSemID);
46
47sprintf(--pbuf, "]}");
48*file_len = strlen((char*)buf);
50return HTTP_OK;
настоящее время эта форма серверной обработки в веб-приложениях заменена множеством веб-фрейм- ворков, построенных с использованием динамических и более мощных скриптовых языков, таких как
PHP, Python и Ruby.
Разработка IoT-приложений |
739 |
51
52} else if(strcmp((const char*)uri_name, "network.cgi") == 0) {
53wiz_NetInfo ni;
54wizchip_getnetinfo(&ni);
55sprintf((char*)buf, "{\"ip\":\"%d.%d.%d.%d\","
56 |
"\"nm\":\"%d.%d.%d.%d\"," |
57 |
"\"gw\":\"%d.%d.%d.%d\"," |
58 |
"\"dns\":\"%d.%d.%d.%d\"," |
59 |
"\"dhcp\":\"%d\"}", ni.ip[0], ni.ip[1], ni.ip[2], ni.ip[3], |
60 |
ni.sn[0], ni.sn[1], ni.sn[2], ni.sn[3], |
61 |
ni.gw[0], ni.gw[1], ni.gw[2], ni.gw[3], |
62 |
ni.dns[0], ni.dns[1], ni.dns[2], ni.dns[3], |
63 |
ni.dhcp); |
64*file_len = strlen((char*)buf);
65return HTTP_OK;
66}
67
68 if(ret) *file_len = len;
Страница network.cgi выполняет простую функцию: она возвращает текущие сетевые настройки на страницу /net-work.html, которая, в свою очередь, выполняет вызов AJAX на страницу /network.cgi. Просто чтобы быть уверенным, что все читатели понимают этот вопрос, предположим, что ваша Nucleo доступна по IP-адресу 192.168.1.165, тогда переход по URL-адресу http://192.168.1.165/network.cgi29 в вашем веб-браузере даст вам следующий результат:
{"ip":"192.168.1.165","nm":"255.255.255.0","gw":"192.168.1.1","dns":"8.8.8.8","dhcp":"1"}
Он соответствует настройкам сети, возвращаемым в формате JSON. Когда браузер обращается к динамической странице /adc.cgi, приложение возвращает текущую частоту TIM2 и отобранные данные АЦП в формате JSON. Строки [32:52] отвечают за данную операцию. Функция http_get_cgi_handler() начинает вычислять частоту таймера в строке 36. Эта информация будет использоваться веб-приложением для построения данных на графике. Строки [42:45] представляют «сложную часть» функции.
Преобразование АЦП выполняется в циклическом режиме DMA. Преобразование происходит само по себе, и эта операция выполняется таймером TIM2. Если таймер работает очень быстро, доступ к массиву _adcConv[] может привести к условиям гонки: его содержимое может быть изменено, в то время как http_get_cgi_handler() преобразует его в строку в строке 44. Приложение организовано следующим образом: половина содержимого массива _adcConv[] копируется в массив adcConv[] при вызове процедур
HAL_ADC_ConvHalfCpltCallback() и HAL_ADC_ConvCpltCallback(), как показано ниже.
Имя файла: src/ch25/main-ex2.c
Разработка IoT-приложений |
740 |
264osSemaphoreRelease(adcSemID);
265}
266}
267
268void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
269UNUSED(hadc);
270 |
|
271 |
if(osSemaphoreWait(adcSemID, 0) == osOK) { |
272 |
memcpy(adcConv, _adcConv+100, sizeof(uint16_t)*100); |
273 |
osSemaphoreRelease(adcSemID); |
274 |
} |
275 |
} |
|
|
|
Когда вызывается функция HAL_ADC_ConvHalfCpltCallback(), в массиве _adcConv[] хра- |
|
нится сто значений: поэтому мы копируем первую половину в массив adcConv[], размер |
|
которого равен 100. Когда вызывается другой обратный вызов, мы копируем вторую по- |
|
ловину. Перед тем, как две процедуры обратного вызова скопируют содержимое мас- |
|
сива _adcConv[], они пытаются получить семафор adcSem. Если он доступен, они выпол- |
|
няют копирование, в противном случае это означает, что http_get_cgi_handler() уже по- |
|
лучил его и выполняется преобразование массива adcConv[]. Это решение предотвра- |
|
щает создание условий гонки, несмотря на то что оно не самое быстрое. |
|
Функция http_get_cgi_handler() обрабатывает все GET-запросы к сценариям CGI. Анало- |
|
гичным образом функция http_post_cgi_handler() обрабатывает все POST-запросы к |
|
сценариям CGI. |
|
Имя файла: Middlewares/ioLibrary_Driver/Internet/httpServer/httpUtil.c |
|
|
72 |
uint8_t http_post_cgi_handler(uint8_t * uri_name, st_http_request * p_http_request, \ |
73 |
uint8_t *buf, uint32_t * file_len) |
74{
75uint8_t ret = HTTP_OK;
76uint16_t len = 0;
77uint8_t *param = p_http_request->URI;
79if(strcmp((const char *)uri_name, "sf.cgi") == 0) {
80param = get_http_param_value((char*)p_http_request->URI, "f");
81if(param != p_http_request->URI) {
82/* Пользователь хочет изменить частоту дискретизации АЦП. Останавливаем преобразование */
83HAL_ADC_Stop_DMA(&hadc1);
84HAL_TIM_Base_Stop(&htim2);
86/* Получение текущей частоты TIM2 */
87uint32_t cfreq = HAL_RCC_GetPCLK2Freq() / (((htim2.Init.Prescaler + 1) *
88 |
(htim2.Init.Period + 1))), nfreq = 0; |
89 |
|
90if(*param == '1')
91nfreq = cfreq * 2;
92else
93nfreq = cfreq / 2;
95htim2.Init.Prescaler = 0;
Разработка IoT-приложений |
741 |
96htim2.Init.Period = 1;
97/* Повторяем цикл, пока не достигнем желаемой частоты. На частотах ниже 30 Гц
98этот алгоритм в значительной степени неэффективен */
99while(1) {
100cfreq = HAL_RCC_GetPCLK2Freq() / (((htim2.Init.Prescaler + 1) *
101 |
(htim2.Init.Period + 1))); |
102if (nfreq < cfreq) {
103if(++htim2.Init.Period == 0) {
104 |
htim2.Init.Prescaler++; |
105 |
htim2.Init.Period++; |
106}
107} else {
108 |
break; |
109}
110}
111HAL_TIM_Base_Init(&htim2);
112HAL_TIM_Base_Start(&htim2);
113HAL_ADC_Start_DMA(&hadc1, (uint32_t*)_adcConv, 200);
115sprintf((char*)buf, "OK");
116len = strlen((char*)buf);
117 |
} |
118 |
|
119}
120else if(strcmp((const char *)uri_name, "network.cgi") == 0) {
121wiz_NetInfo netInfo;
122wizchip_getnetinfo(&netInfo);
124param = get_http_param_value((char*)p_http_request->URI, "dhcp");
125if(param != 0) {
126netInfo.dhcp = NETINFO_DHCP;
127} else {
128netInfo.dhcp = NETINFO_STATIC;
130param = get_http_param_value((char*)p_http_request->URI, "ip");
131if(param != 0)
132inet_addr_((u_char*)param, netInfo.ip);
133else
134return HTTP_FAILED;
136param = get_http_param_value((char*)p_http_request->URI, "sn");
137if(param != 0)
138inet_addr_((u_char*)param, netInfo.sn);
139else
140return HTTP_FAILED;
142param = get_http_param_value((char*)p_http_request->URI, "gw");
143if(param != 0)
144inet_addr_((u_char*)param, netInfo.gw);
145 else
Разработка IoT-приложений |
742 |
146 return HTTP_FAILED;
147
148param = get_http_param_value((char*)p_http_request->URI, "dns");
149if(param != 0)
150inet_addr_((u_char*)param, netInfo.dns);
151else
152return HTTP_FAILED;
153}
154if(!WriteNetCfgInFile(&netInfo))
155sprintf((char*)buf, "FAILED");
156else
157sprintf((char*)buf, "OK");
158
159/* Изменение параметров сети */
160wizchip_setnetinfo(&netInfo);
161len = strlen((char*)buf);
162}
163
164 if(ret) *file_len = len;
Эта функция предназначена для обслуживания двух динамических страниц: /sf.cgi и /network.cgi. Вторая обрабатывает HTML-форму, когда пользователь изменяет сетевые настройки (посмотрите файл network.html). А страница /sf.cgi обрабатывает изменение частоты TIM2. Когда пользователь нажимает на значки «увеличить/уменьшить масштаб», браузер выполняет запрос к странице /sf.cgi, передавая значение 1 для увеличения частоты и 0 для ее уменьшения.
Остальная часть нашего примера приложения посвящена HTML, CSS и JavaScript. Файлы index.html и network.html содержат весь необходимый код для построения графика при помощи библиотеки D3js, а также для отображения и изменения сетевых настроек.
Чтобы использовать данный пример, вы можете просто скопировать содержимое подкаталога src/ch25/webpages на SD-карту. В качестве альтернативы вы можете использовать полухостинг ARM после того, как был установлен макрос OS_BASE_FS_PATH с полным путем к src/ch25/webpages в файловой системе вашего ПК. Например, если вы работаете
в Windows, для OS_BASE_FS_PATH можно указать путь C:/STM32Toolchain/projects/nucleof401RE/src/ch25/webpages.