Stm32 что можно сделать
Электроника для всех
Блог о электронике
Tag Archives: STM32
STM32. Контроллер внешней параллельной памяти FSMC
У микроконтроллеров собственной памяти мало, даже если говорить о каком-нибудь жирном прежирном Corteх, все равно: как волка ни корми, а у медведя, т.е. полноценного компьютера, толще. Поэтому практически все микроконтроллеры, в своем жирном исполнении так или иначе позволяют подцеплять к себе внешнюю параллельную память. Даже древний, как говно мамонта, АТ89С51 это умел. Что уж говорить про AVR и STM32.
Для такой памяти характерны две черты: наличие двух шин: адреса и данных, и разных там стробов. На чтение, запись, тактовых и прочих. А вся суть работы с ними предельно простая. Чтение — мы выставляем на ногах адресной шины (коих обычно от 16 до 32) адрес нужной ячейки, отдельной ногой указываем, что у нас чтение, дергаем стробом и на шине данных (8 или 16 бит, обычно) появляется желанные байты. Запись похожим образом, только тут на шину данных мы выкладываем то, что хотим записать, на адресную шину кладем адрес куда надо записать, расставляем контрольную линию в режим записи и дергаем стробом. Опа, данные в памяти. Поскольку тут не требуется совершать сложных логических действий, то это все работает очень быстро, а реализовать можно даже на логике рассыпной. Естественно, что в разных МК такие интерфейсы были всегда.
(далее…)
Контроллер прямого доступа к памяти (DMA) контроллера STM32
Работа с контроллером DMA
▌Что это?
Есть в современных контроллерах такой блок DMA — контроллер прямого доступа к памяти (ПДП). Штука очень простая, несмотря на умное название. Вся ее суть заключается в том, чтобы по команде от периферии или ядра взять и скопировать кусок памяти с одного места на другой.
Что с этим можно делать? Ой да много чего. Можно, задать в качестве источника адрес какой-либо периферии, скажем выходной регистр АЦП, а в качестве приемника адрес массива в ОЗУ, дать приказ DMA по команде завершения оцифровки АЦП хватать результат и пихать в массив. При этом DMA сам скопирует, сам увеличит адрес, а как заполнит буфер, то обнулится и начнет его переписывать по кругу заново.
И вот у нас, автоматически, без работы проца вообще, образуется циклический буфер приема данных с АЦП, которые мы можем тут же прогнать через любую цифровую обработку, да хоть через усреднение, и получить отфильтрованные уже данные. Или то же самое сделать с UART и заставить DMA аккуратно складывать входящие данные в кольцевой буфер.
А можно сделать и наоборот. Сказать DMA что мол вот тебе буфер, там лежит пара сотен байт, возьми и запихай их все в жерло UART и пойти по своим делам, а DMA трудолюбиво отправит в передачу весь буфер.
(далее…)
FreeRTOS. Пример
Обещал я как то пример программы похожей на реальность, но с использованием FreeRTOS. Обещанного, как водится, три года ждут. Правда прошло уже пять… ой… В общем, ужасно не люблю писать бесполезные примеры. А полезные, которые можно выложить, как то не попадались под руку. И вот оно, нашло таки.
В общем, суть задачи, управление несколькими шаговиками не приходя в сознание. Причем все должно вращаться одновременно и сигнализировать о приходе на точку, а также прерываться по требованию. Да это все можно сделать десятком разных способов. И программно и полуаппаратно. Как угодно, короче. А у меня будет на FreeRTOS. Надо же на чем-то показать 🙂
Итак, все будет просто и в лоб. Не буду перегружать пример излишними усложнениями, проверками, контролем ошибок и прочим. А то даже в официальном мануале такого любят навертеть, что человек который в это дело только вникает поглядит и закроет.
▌Железо
Ну тут все элементарно. Pinboard II с STM32F103C8T6. Да шаговые драйверы TB6600. У драйверов этих есть сигнал STEP сигнал DIR и сигнал EN. Подаем разрешение на EN, выбираем направление на DIR и гоним тактовые импульсы на STEP. И таких четыре драйвера.
▌Установка
Как ставить FreeRTOS описывалось в прошлой статье. С тех пор ничего не поменялось. Кинуть файлы в проект и подцепить их в среде. Элементарно. Также бегло пробегитесь по первой статье.
STM32 CubeMX и Pinboard II. Настройка и тестовый проект-моргалка
У меня тут последнее время часто спрашивают, можно ли через Pinboard II работать с CubeMX и у кого-то возникли даже проблемы с этой приблудой. Так что пришлось запилить видеопример простой моргалки сгенерированный в кубе. Ну, а раз это взлетело так просто, то и остальное должно тоже. Ну и в двух словах описал, что есть HAL, откуда он взялся и что собой заменил.
FreeRTOS для чайников. Краткое описание.
В цикле своих статей посвященных архитектуре программ я уже расписывал немного по этой теме, а также выкладывал рабочий проект своего диспетчера ОС, который я планировал превратить в постепенно в полноценную RTOS со всеми свистоперделками, но потом решил не изобретать велосипед и оставил его как есть, как легкое простое решение для совсем мелких МК.
▌FreeRTOS?
Почему именно она? Она популярна, она Free и она портирована на огромное количество архитектур, под нее существуют плагины для Keil и IAR и всякие примочки для PC. При этом она довольно легкая и функциональная.
Я не буду вам сейчас тут расписывать все эти прототипы функций, порядок записи, технические тонкости и прочее. Это все есть в технической документации и в замечательном цикле статей Андрей Курница, что был в журнале Компоненты и Технологии в 2011 году. PDF статьи вы найдете в конце.
Я лишь на пальцах и псевдокоде быстро распишу те инструменты которыми владеет FreeRTOS, чтобы когда вы будете читать более подробную документацию за деревьями не потеряли лес 🙂
Ну и все сказанное тут справедливо и для большинства других RTOS. Т.к. механизмы в целом все одни и те же и никто ничего нового еще не придумал.
ARM. Учебный курс. Внешние прерывания
Внешние прерывания активизируются по изменению логического уровня на ноге контроллера. Удобно для обработки срочных событий от внешней периферии, иногда на них делают обработку кнопок. Т.к. они могут будить контроллер из глубокой спячки.
У STM32 за внешние прерывания отвечает EXTI контроллер. Его основные возможности:
До 20 линий прерываний (в реальности несколько меньше, зависит от контроллера)
Независимая работа со всеми линиями. Каждой линии присвоен собственный статусный бит в спец регистре
Улавливает импульсы длительность которых ниже меньше периода частоты APB2
EXTI Может генерировать:
ARM Учебный курс. USART
Самый простой и наиболее часто использующийся в быту и производстве 🙂 Куда без него. О нем было много сказано тут, не буду повторяться, дам лишь ссылочки на старые статьи:
Вот и у STM32 он есть. Да не один, а целых дофига. В том контроллере, что стоит на Pinboard II в модуле STM32 — STM32F103C8T6 — их три. Обычно хватает и одного. Но три это же лучше! 🙂
Вообще у STM32 сей девайс навороченный и умеет не только байтики в терминалку слать в классических режимах (асинхронном, синхронном, мультипроцессорном), но и кое чего еще. В частности он может работать в
В общем, полный фарш. Расписывать все не буду. Только самое основное. Иначе это книгу написать можно.
ARM. Учебный Курс. SysTick — Системный таймер
Продолжаем потрошить кортексы М3. Есть у них у всех, вне зависимости от производителя, такая штука как системный таймер — SysTick. Это часть ядра. Тупейший и примитивный таймер. Он ничего не умеет кроме как генерировать прерывание в заданном промежутке времени. Используется обычно во всяких RTOS для проворачивания диспетчера. К тому же его прерывание имеет высокий приоритет.
▌Краткое описание
Сам таймер 24 разрядный. И тикает вниз от предзагруженного значения до нуля, после чего перезагружается вновь и генерирует прерывание. Управляется он четырьмя регистрами:
ARM. Учебный Курс. Прерывания и NVIC — приоритетный контроллер прерываний
Стандартной плюхой ядра Cortex M3 является NVIC — контроллер приоритетных векторных прерываний. Сейчас я разжую что это такое и с чем это едят.
Прерывания и события
Вообще, если пошерстить мануал, то везде при разборе периферии рассматриваются interrupt/event. Может сложиться ощущение, что это одно и то же. Но это не так.
Interrupt — это прерывание. При прерывании обычно программа пакует регистры в стек и бросается по вектору, а оттуда через JMP сигает уже в обработчик прерывания. В кортексах все немного не так. Тут вектора прерывания это просто вектор-адреса лежащие в нужном месте. В виде констант. А при прерывании прога не прыгает на вектор, а берет этот вектор-адрес и сразу же пихает его в програмный счетчик, тем самым переходит сразу на обработчик. Так быстрей, исчезает лишняя операция по переходу на вектор.
Event — это аппаратное событие. Опустел буфер UART — держи event, натикал таймер — еще один event. Событие может вызвать прерывание, может запустить какую-либо периферию, например пнуть DMA, чтобы оно выгрузило данные. Но событие далеко не всегда может вызвать прерывание. Каждое прерывание вызывается событием, но не каждое событие вызывает прерывание. Вот. Так что надо отличать.
Как и в AVR в STM32 существуют вектора прерываний. Это особые адреса, куда контроллер бросается если происходит прерывание. Они записаны в таблицу и располагаются вначале памяти. Впрочем, система гибкая и переконфигурировав NVIC можно засунуть ее куда угодно. Если вы пишите на Си, то по дефолту, в стартовом файле, вектора прерываний забиты затычками которые ведут в бесконечный цикл. Так что если вызывать прерывание не указав ему обработчик контроллер тупо повиснет. Что является максимизацией ошибки и это хорошо.
Определение текущей тактовой частоты при отладке
Тактовая частота это важнейший параметр, точное знание которого необходимо при расчете многих значений. Начиная от банальных выдержек, до генерации UART передачи.
И если даже на AVR, где тактовые режимы можно по пальцам одной руки пересчитать, и где все изначально предсказуемо работает по дефолту, прозрачно и явно, у многих новичков были проблемы с пониманием того, в каком режиме работает генератор.
То в STM32, где описание системы RCC занимает 35 страниц убористого текста, схема не влезает на экран, а куча библиотек, вроде CMSIS и SPL или визардов, наподобие того что есть в CoIDE, генерируют стартовый код, определить текущую тактовую частоту превращается в непростую задачу. Ее и будем решать.
Как же понять, что у нас получилось на SYSCLOCK после всех этих HSI, HSE, делителей, мультиплексоров и PLL умножителей?
(далее…)
Работа с STM32F10x Standard Peripherals Library
STM, для облегчения использования своих контроллеров родила монструозную библиотеку, которая позволяет обращаться с периферией не укуриваясь даташитами, а лишь бегло поглядывая в них. Правда код распухает и некоторые решения получаются далеко не оптимальные, перегруженные лишним кодом. Зато все получается очень читаемо и понятно.
Как это выглядит
Если вы пыталисьи курить STM32, то наверняка смотрели чужие исходники. И наверняка часто видели в них шнягу вида:
/* GPIOD Periph clock enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); /* Configure PD0 and PD2 in output pushpull mode */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOD, &GPIO_InitStructure);
Вот это и есть пример работы с STM32 Standard Peripherals Firmware Library, а точнее конфигурация вывода контроллера посредством стандартных функций этой библиотеки.
ARM. Учебный Курс. Порты GPIO
Над портами инженеры STM поубивались знатно. Такой прорвы настроек и возможных режимо я, честно говоря, даже не ожидал. Порты у STM32F1xx могут работать в режиме* :
Прошивка ARM Cortex M3 на примере STM32 и LPC1300
Готовую программу надо каким-либо образом запихать в контроллер. Для этого существует множество способов.
JTAG/SWD адаптер
Так как часто для отладки под ARM используется JTAG, то этот метод получается наверное самым популярным. Для этой цели используется какой-либо адаптер. Например я использую CoLinkEX так что показывать буду на его примере. Там все просто — подключаешь адаптер к контроллеру стандартным SWD или JTAG шлейфом. Через линии NRST/TDI/TDO/TCK/TMS для JTAG или через SWO/SWOCLK/SWDIO/NRST для SWD режима. На адаптере моей верси CoLinkEX оба эти разьема выведены на одну колодку, так что получается как бы сразу и JTAG и SWD соединение. А там какое надо такое и выбираешь. Особой разницы в отладке/прошивке между ними нет.
И прошиваешь либо из среды Keil.
Либо используя утилитку CoFlash oт CooCox.com (далее…)
ARM. Учебный курс. Тактовый генератор STM32
Нам разум дал стальные руки-крылья,
А вместо сердца — пламенный мотор.
В прошлых статьях, мы научились создавать проекты, настраивать их и даже научились моргать светодидом напрямую и с использованием функций стандартной библиотеки работы с периферией. Все это, конечно, хорошо но хочется чего-то большего… Но прежде чем приступить к более сложным примерам, хотелось бы поговорить об одной очень важной системе микроконтроллера. Речь идет о системе генерации тактовых частот, которая, выражаясь метафорически, является сердцем нашего микроконтроллера. Думаю, ни для кого не секрет, что работа всех остальных систем микроконтроллера зависит от того, получают они тактовые сигналы или нет и какой частоты эти сигналы. Если какие-то блоки не будут получать тактовых сигналов, то они просто не будут работать. Ну, а от частоты этих сигналов зависит скорость работы этих блоков.
В нашей программе управления светодиодом, мы практически не касались вопросов генерации тактовой частоты и микроконтроллер вроде бы работал а программа выполнялась, из сего некоторые могут сделать опрометчивый вывод, что раз работает, так не стоит туда и лезть.
(далее…)
STM32 от Булкина. Урок 1: Вводный, где мы немножко похулиганим.
Как и обещал, начинаю тему про STM32. Данный пост вводный, я расскажу о настройке рабочей среды, немножко поморгаем светодиодами, всковырнём (чуть-чуть) FreeRTOS. Ну а в следующей статье я сравню STM32 и Atmega, посмотрим, зачем вообще нам нужны ARM.
Статья рассчитана на тех, кто давно ходит вокруг да около STM32, но не знает, с какого бока подступиться. Некоторые моменты могут показаться сложными на первый взгляд, но надо поработать мозгами, уж простите =) Стоит только понять некоторые основополагающие вещи, как наступит просветление, уверяю вас!
Внимание! Много текста и картинок!
Для Пикабу я буду адаптировать статьи под обычную и дешёвую плату на STM32F103C8T6, например такую:
Их полно на Али, стоят около 120 рублей, можно заказывать пачками. Я заказал себе штук пять вариаций. По их приезду буду адаптировать больше своих статей сюда. У неё на борту 64-Кбайт Flash и 20-КБайт RAM. Вообще, чип довольно попсовый. Таймеров всего 4 штуки, периферия хиленькая. Но он стоит копейки, частота 72МГц, много оперативы, а значит можно неплохо развернуть FreeRTOS.
Также на Хабре и у себя в блоге я публикую статьи для оригинальной макетки STM32F3DISCOVERY, она основана на МК STM32F303VCT6 c 256-Кбайт Flash и 48-КБайт RAM в корпусе LQFP100. С ней гораздо интереснее работать.
Также вам понадобится программатор ST-Link, их также полно на Али, стоят от 150 руб с доставкой.
2. Среда разработки
Корпел несколько дней и родил аж две огромные статьи по настройке среды разработки под Linux Ubuntu 16.04 и MS Windows 10. Ниже я покажу, как настроить проект под нашу макетную плату и как подключить к ней светодиоды и кнопку.
Я не буду полностью адаптировать эти статьи, т.к. большие сложности с публикацией форматированного кода на Пикабу. Выкладывать скрины и ссылки на Gist лишний раз не хочется. Я буду отсылать к этим статьям, так что держите вкладки с ними открытыми.
Ну ладно. приступим!
Подключать будем по такой схеме (RESET и BOOT0/BOOT1 нарисовал до кучи)
Я считаю с этого места, что вы настроили среду разработки.
Для начала надо подключить ST-Link к нашему контроллеру. Делаем по схеме (слева программатор, справа контроллер):
Открываем STM32CubeMX и устанавливаем библиотеки для STM32F1 Help->Install New Libriaries, ставим галку Firmware Package for Family STM32F1, жмём Install Now:
Жмем New Project, в поле Part Number Search пишем STM32F103C8:
Щёлкаем два раза на чипе в нижней части экрана и попадаем в окно настройки.
Сначала настроим основные параметры.
— Включим резонатор на плате
— Затактируем системный таймер от TIM4
Теперь подключим кнопку и два светодиода:
— Аналогично щелкаем на PB0 и PB1, только выбираем GPIO_Output и называем LED1 и LED2:
В результате получим такую распиновку:
Откроем вкладку Clock Configuration.
— Отмечаем, что тактируемся от HSE и ставим частоту системной шины 72МГц:
Переходим во вкладку Configuration.
1. Настроим Кнопку. Жмём GPIO, выбираем кнопку и ставим сработку прерывания по обоим фронтам импульса, а также подтянем линию к питанию.
2. Включим прерывание на кнопку. Жмём NVIC и в строке EXTI line0 interrupt ставим обе галки
3. Настроим FreeRTOS
Жмём на FreeRTOS, во вкладке Config parameters выставляем TOTAL_HEAP_SIZE 4096 (это сколько памяти мы резервируем для всего FreeRTOS в целом)
Во вкладке Task and Queues добавляем три задачи:
— Task Name: buttonPress, Priority: osPriorityNormal, Entry Function: StartButtonTask
— Task Name: Led1, Priority: osPriorityNormal, Entry Function: StartLed1Task
— Task Name: Led2, Priority: osPriorityNormal, Entry Function: StartLed2Task
Во вкладке Timers and Semaphores добавим семафор, за который будут драться светодиоды:
Включим функцию vTaskDelayUntill во вкладке Include parameters:
Теперь укажем параметры проекта в Project->Settings из верхнего меню.
Указываем имя проекта в поле Project Name: PikabuLes1
Указываем путь в поле Project Location: ВАШ ПУТЬ, ГДЕ ХРАНИТЕ ПРОЕКТЫ
Выбираем Toolchain: SW4STM32
Не забываем во вкладке Code Generator включить “Add necessary libriary files as reference in the toolchain project configuration file”
Дальше, чтоб не выкладывать тут простыни кода, вы можете заменить три файла на аналогичные с моего репозитория на GitHub:
Загружаем и запускаем так, как написано в той же статье.
Можете и тупо клонировать репо, инструкцию выше я давал для понимания, как это делается.
Теперь расскажу, как это всё работает.
— ButtonTask: Занимается обработкой нажатий нашей кнопки
— Led1Task и Led2Task: занимаются светодиодами LED1 и LED2.
Для особого шика я добавил в эту связку бинарный семафор ledOnSemHandle. Суть его в том, что кто им владеет, тот и может управлять своим светодиодом. А кнопка, если перехватывает семафор, управляет обоими светодиодами.
Итак. При старте семафор свободен, состояние кнопки неизвестно. Кому повезёт, тот и хватает семафор функцией xSemaphoreTake. Соответственно задачи Led1Task или Led2Task ждут, когда семафор освободится.
Если нажимается кнопка, то задача ButtonTask ждёт, когда ей отдадут семафор. Когда она его получает, зажигает оба светодиода и держит семафор, пока не отпустить кнопку.
Дальше. Нажатия кнопки отбиваются прерыванием в Src/stm32f1xx_it.c в колбэке EXTI0_IRQHandler(). Самое главное вот в чём:
— Мы проверяем, что сработка была более 50мсек от предыдущего срабатывания (простая защита от дребезга)
— Проверяем состояние линии: нажата или отпущена кнопка
— Уведомляем задачу ButtonTask об изменившемся состоянии кнопки
— Задача ButtonTask в зависимости от состояния кнопки пытается перехватить семафор или наоборот отдаёт его.
Так, на этом всё на сегодня. Это вводная статья и тут куча моментов, на которых стоит остановиться подробнее. Об этом другой раз. Ну или пишите в комментах.
P.S. Баянометр ругается, но совпадений точно нет, контент на 100% уникальный.
Сообщество Ремонтёров
6.1K пост 35.6K подписчика
Правила сообщества
ЕСЛИ НЕ ХОТИТЕ, ЧТОБЫ ВАС ЗАМИНУСИЛИ НЕ ПУБЛИКУЙТЕ В ЭТОМ СООБЩЕСТВЕ ПРОСЬБЫ О ПОМОЩИ В РЕМОНТЕ, ДЛЯ ЭТОГО ЕСТЬ ВТОРОЕ СООБЩЕСТВО:
Посты с просьбами о помощи в ремонте создаются в дочернем сообществе: https://pikabu.ru/community/HelpRemont
К публикации допускаются только тематические статьи с тегом «Ремонт техники».
В сообществе строго запрещено и карается баном всего две вещи:
В остальном действуют базовые правила Пикабу.
А простой USB TTL не подойдёт?
Покажите, пожалуйста, проекты, под которые идёт такая техника.
Что это? Чем лучше ардуино?
Интересно. Пиши исчо!
На сколько я знаю, этот же stm32 используется в полетных контроллерах для квадрокоптеров.
автор, подскажи пожалуйста, с чего лучше начинать знакомство с микроконтроллерами? с ардуинки или с stm? тема очень интересная, но раньше не программировал, а хотелось бы научиться. сделать что то интересное хочется (ничего супер сложного, но и не банально диод-кнопка).
Ну по сути норм гайд, только вот в новой версии интерфейс другой и немного было сложнее, вот код генерируется уже 30 минут и я начинаю скучать по ардуинкам
Про кейл надо однозначно, ибо дебаггер реально крутой, все что хочешь на ходу и сразу видно реакцию платы.
В полку сохраненных постов прибыло. Будете за ардуино, о котором недавно писал AlexGyver.
Кто следующий? Я буду устанавливать сохранять все игры статьи
Я боюсь спросить ересь, но сам программирую атмегу, искал вакансии и нужны программисты на STM32, спросил в чем они программируют, сказали AVR, но авр студио не для STM, может я не правильно понял людей или они не то сказали?
Опа, хорошая тема, сейчас сам учу. Автор, надеюсь вы не остановитесь на двух-трёх статьях.
Будете ли в будущем упоминать работу с I2C и сенсорами типа mpü 6050?
Я извиняюсь, это че хоть такое то?
Во, не зря подписался. Как, кстати, моя схема в целом? Я всё жду отмашку чтобы плату разводить 🙂
STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32
Меня частенько упрекают, что я даю материал в слишком трудной манере и не по порядку. Спешу вас расстроить, я и дальше буду следовать этой логике.
Стерильно-приторных уроков “для чайников” по программированию МК, в том числе и по STM32, на просторах инета полно. Учат они чему-то? Не уверен. Они лишь дают возможность разобраться в какой-то функции, когда возникает потребность.
У меня цели другие: научить думать и пользоваться документацией. Это можно сделать только на живых и настоящих примерах. Только в бою, так сказать. Я стараюсь показывать не столько возможности, сколько путь от задумки до реализации. Стараюсь выстраивать логику процесса так, чтоб не дочитав до конца вы уже могли бы предположить, что получится или какие могут возникать проблемы.
Я практикующий радионженер. Я не занимаюсь DIY. Я стараюсь показывать вещи так, как их делать ПРАВИЛЬНО. Делать на совесть, а не тяп-ляп и так сойдёт. Одно дело, когда вы берёте чужую реализацию и не разбираясь суёте в свой проект. Другое дело, когда сами пишете библиотеку с настроением, мол, разбираться некогда. Как то работает и х** с ним. Это ужас ужасный!
Ладно, сегодня у нас интересная тема!
Пишем библиотеку сами для STM32
В комментах проскакивали панические настроения некоторых людей, что не хватает библиотек для каких-то вещей. И я, и некоторые читатели, пытались убедить, что ничего страшного в этом нет.
Я долго не мог придумать, что же такое взять в качестве примера из того, что у меня самого не реализовано. И с удивлением обнаружил, что у меня нет библиотеки для классического текстового LCD на Hitachi HD44780. Это 1-но, 2-х или 4-х строчные дисплеи до 20 символов на строку. Те самые, которые все так любят втыкать во все свои DIY.
Полазил по просторам и с ещё большим удивлением обнаружил, что все реализации для шины I2C основаны на дурацкой классической библиотеке Arduino LiquidCrystal_I2C. Ну, думаю, сам Бог велел!
Начнём с главного: чтения документации.
Как работает дисплей
Дисплей основан на старинном чипе от HITACHI HD44780. У него нет последовательного интерфейса, как, например, у ST7920. Тем не менее, он прост до безобразия.
Открываем даташит раздел “Interfacing to the MPU” и видим примерную диаграмму, как устроен обмен данными. Смотрим, смотрим и видим фигу. Но всё-таки что-то почерпнуть можно.
Базовый его режим 8-ми битный. Т.е. мы можем передавать ему 1 байт за раз. Для чего у него есть восемь ног DB0-DB7. Ещё у него используются 3 ноги:
E: выдаём строб (импульс), который сообщает дисплею, что на ногах DB0-DB7 выставлены нужные данные, мол, давай, считывай
RS: Сообщаем дисплею, что мы хотим передать или считать, команду или конфигурацию
R/W: Сообщаем дисплею, пишем мы данные или считываем
На схеме показывается 4-битный режим. Это когда мы используем 4 ноги DB4-DB7 вместо восьми и передаём два раза по 4 бита. Режим полезен там, где жалко отдавать лишние ноги у МК. Или для нашего расширителя портов на PCF8574.
Пытливый ум заметит, что сначала мы передаём старшие 4 бита, потом младшие. Также обратит внимание, что для передачи данных на ноге R/W должен быть 0, а для чтения 1.
Итак, как же выглядит передача данных в 8-битном режиме:
Для передачи команды дисплею, на ноге RS мы выставляем 0. Если надо передать символ, выставляем 1;
Если мы передаем команду или данные, то выставляем 0 на ноге R/W;
На ногах DB0-DB7, мы выставляем значения побитово того, что хотим передать;
Выдаём строб (импульс) на ноге E;
Документация рекомендует после строба считывать готовность дисплея к приёму следующей команды.
Как же выглядит передача данных в 4-битном режиме:
Для передачи команды дисплею, на ноге RS мы выставляем 0. Если надо передать символ, выставляем 1;
Если мы передаем команду или данные, то выставляем 0 на ноге R/W;
На ногах D4-D7 дисплея, мы выставляем значения старших 4-х бит, что хотим передать;
Выдаём строб (импульс) на ноге E;
На ногах D4-D7 дисплея, мы выставляем значения младших 4-х бит, что хотим передать;
Выдаём строб (импульс) на ноге E;
Документация рекомендует после двух стробов считывать готовность дисплея к приёму следующей команды.
Я тут накидал диаграмку, как передаются данные в 4-х битном режиме. Передаём два байта 0xA6 и 0xE9.
Обратите внимание, нельзя вот просто так взять и щёлкнуть стробом. Нужно помнить, что ширина строба и пауза между ними должны соответствовать паспортным данным. Идём в даташит и ищем что-то похожее на delay, timeout, execution time и т.д. Обязательно даются такие данные. Находим табличку “Table 6: Instructions” и видим, что на исполнение команды требуется от 37мкс до 41мкс. На возврат курсора в начало экрана требуется 1.52мс. Также при хаотичном листании документа в поисках информации, какая же должна быть пауза, находим в диаграмме “Figure 24: 4-Bit Interface” это:
When BF is not checked, the waiting time between instructions is longer than the execution instuction time. (See Table 6.)
Т.е. если мы не проверяем флаг занятости дисплея (почему объясню позже), то пауза должна быть больше, чем время исполнения инструкции. Т.о. я указал на диаграмме ширину строба 50мкс, интервал между парными стробами тоже в 50мкс, а интервал между данными 60мкс, для гарантии (китайские микрухи такие китайские).
Сами символы хранятся в таблицах, которые бывают Японскими или Кириллическими. На Али кириллицу хрен купишь, поэтому мы можем только загрузить в дисплей 8 собственных символов. Полностью это алфавит не покроет, но хоть что-то.
Как с ними работать, будем смотреть позже. Сейчас нас волнует подключение и протокол.
Подключение дисплея к шине I2C
Но нам вот жалко отдавать 7 ног МК (в 4-битном режиме) на дисплей. И кто-то взял и придумал копеешный модуль, который цепляет дисплей к I2C и сохраняет старый протокол.
Основан он на расширителе портов PCF8574. Вещь простая до безобразия. У него есть 8 ног, на которых мы можем выставлять 0 или 1. По I2C мы тупо шлём один байт на него, где каждый бит соответствует ноге. Либо тупо считываем такой же байт с текущим состоянием этих самых ножек.
Так вот модуль подключен по аналогичной схеме (я реализовывал это у себя на плате года два назад):
Пытливый ум, глядя на эту схему, задастся вопросом: А как же строб выдавать? Да ещё тайминги соблюдать. Да и вообще, как дрыгать ножками RS да R/W, чтоб не мешать данным и не сводить с ума дисплей? А вот тут и начинается самое интересное.
Ход мыслей такой. Давайте сначала заглянем в документацию PCF8574 и поищем там диаграмму по обмену данными. Находим прекрасную картинку:
Внимательно смотрим и видим, что состояние на ногах меняется сразу по окончании приёма байта от МК. Т.е. нам нужно передать данные и выставить ногу P2 в высокий уровень чтобы включить строб. Потом передать данные и выставить P2 уже в ноль, т.е. строб мы выключаем. А для этого нам надо разобраться, что такое шина I2C и с чем её едят.
Откровенно говоря, не люблю я её. Использую только там, где нет альтернативы. Скорость небольшая, ёмкость линии ограничена 400пФ, в результате длина линии очень маленькая. К тому же сама суть протокола имеет существенный недостаток, об этом позже. Для каждого готового устройства приходится вручную подбирать номиналы подтягивающих резисторов. В этом плане SPI гораздо удобнее и круче, хоть и требует минимум 3-х ног. Ладно, к сути.
Для понимания работы, надо сначала запомнить правила:
— Данные на линии SDA могут меняться только при низком уровне на линии SCL
— Пока на линии SCL высокий уровень, на линии SDA данные не меняются
— Утрируя, есть три состояния: СТАРТ, СТОП и передача данных.
— Формировать сигналы СТАРТ и СТОП может только ведущий, даже в случае приёма им данных от ведомого
— Адрес ведомого устройства состоит из 7-ми бит.
Т.о. для начала передачи данных ведомогу, ведущий формирует сигнал СТАРТ. Все ведомые устройства на линии начинают слушать. Затем ведущий выстреливает адрес ведомого, с которым он хочет поговорить и сажает SDA на ноль. Адрес этот, как видно по картинке выше, занимает старшие 7 бит, а последний бит задаёт читаем мы данные или пересылаем. Если устройство на линии есть, оно удержит линию SDA в низком уровне, это значит, что оно готово общаться. Тоже самое и по окончании приёма данных. По окончании передачи ведущий формирует сигнал СТОП.
Вот тут и кроется главная проблема шины I2C. После передачи данных, если ведомый занят, он может продолжать удерживать линию SDA. Ведомый также может удерживать и SCL, если он не успевает обрабатывать данные, т.е. ведомый может снижать скорость передачи данных. По стандарту, устройства должны управлять линиями по схеме Open Drain. И даже если какое-то устройство монопольно займёт линию, другое сможет её сбросить. Теоретически. На практике же, если, например, ведомый подвис и держит линию, а мы поднимаем её на ведущем, оживить ведомого порой можно только reset’ом. Там вообще такие бывают дичайшие комбинации, что однажды даже пришлось прокидывать отдельную линию RESET для ведомых устройств и периодически их дергать.
Итак. Более менее и в общих чертах мы разобрались с I2C. На wiki есть неплохая статья, да и вообще погуглите, шина непростая, я дал лишь общую информацию для понимания вопроса.
Приступаем к написанию библиотеки
Вот и настал момент, когда мы почти готовы написать первые строки кода. Давайте сначала посмотрим, как устроены другие библиотеки. Мы же ленивые и надеемся обойтись малой кровью.
Откроем классическую Arduino LiquidCrystal_I2C. Просто бегло пройдём по ней глазками. Не знаю, как у вас, у меня сразу глаз цепляется за несколько вещей:
— Используются аппаратные задержки
— Куча однотипных функций
— Нет никаких оптимизаций по экономии потребления памяти
— Нет контроля ошибок
— Нет вменяемых комментариев
Если мы просто пороемся на GitHub в поисках библиотек для STM32, почти все они будут на основе этой же LiquidCrystal_I2C. С теми же недостатками. Я не буду глубоко туда влезать, я просто сделаю всё по-своему.
Итак, составим требования к нашей библиотеке:
— Никаких аппаратных задержек
— Использовать DMA для передачи данных
— Минимум функций, максимально выносить всё в #define
— Максимально экономим память
— Каждое обращение к дисплею должно контролироваться
Для начала надо создать проект. Я уже написал инструкцию, как правильно настроить STM32CubeMX у себя в блоге, не буду повторяться тут. Полностью проект с уроком доступен в моем репо на GitHUB.
Отмечу только, что урок написан для отладочной платы на STM32F303VC. У меня сейчас нет под рукой STM32F103C8, так что всё проверял на STM32F3DISCOVERY. Но адаптировать под любую другую плату можно без особых проблем.
Дальше, конечно, мы можете взять готовую библиотеку, я её выложил на GitHub. Я вкратце напишу, что я делал.
Создадим два файла:
Для начала написания кода, нам бы вообще понять, что делать в реальности. Для этого нам надо сделать две вещи: включить дисплей и послать на него пару символов. Открываем даташит дисплея, ищем описание процедуры инициализации.
Отлично! Всё написано по шагам, с таймингами и даже биты указаны! Но мы любопытные и хотим сразу знать, что же битики значат, чтобы сразу заполнить заголовочный файл #define’ами. Вспоминаем про «Table 6: Instructions». Там прям идеально, с комментариями, расписаны все биты команд.
Открываем наш заголовочный файл и предварительно накидываем:
Это та самая нудная часть работы, о которой я говорил. Внимательно смотрим в табличку, двоичный код переводим в HEX. Поясню на примере:
Инструкция Display on/off control требует всегда выставленного бита DB3. Открываем калькулятор, вводим двоичное 1000 и получаем 0x08 HEX.
В самой инструкции есть три команды:
— Blinking of cursor position character
Калькулятором высчитываем их HEX и будем их потом суммировать с LCD_BIT_DISPLAY_CONTROL.
Биты RS, RW, E и Backlight относятся к PCF8574, так что не забываем прописать и их.
Позже аналогичным способом напишем и остальные #define.
Для тех, кто не знаком с таким стилем, не стоит пугаться, что различные названия с одним значением. На самом деле, вы как бы пишете ссылки для себя, которые удобно читать. Компилятор же подставит вместо этих названий их значения. Причем только те, которые вы реально используете в коде.
Но теперь мы задумались и пришли к выводу, что нам нужно где-то хранить те параметры, что мы уже отправляли на дисплей. Также надо хранить данные шины, параметры дисплея и прочее. Для этого мы создадим структуру:
Обратите внимание. В этом struct мы храним не саму структуру для I2C, а лишь указатель. Т.о. мы не дублируем данные и всегда под рукой их состояние.
Судя по алгоритму инициализации, первые этапы уникальны и можно реализовать их тупо отправляя данные через базовые функции HAL. Их мы реализуем в функции lcdInit().
Пора бы уже отправить какие-то данные на дисплей. Хорошим тоном будет сделать локальную низкоуровневую функцию, которая будет подготавливать данные для отправки и саму отправку. А мы лишь будем в неё закидывать состояние управляющих битов и байт данных.
Посмотрите реализацию в уже готовой библиотеке. В чём фишка. Для того, чтобы выдавать строб, мы дважды шлём первую партию 4-х бит. Третьим байтом шлём младшие 4-бит и закрываем строб.
И вот, что получается на деле:
При таком раскладе и скорости шины I2C в 100кбит, ширина строба
180мкс, пауза между стробами
90мкс, и пауза между парными стробами
Также можно сократить длинную паузу раза в два, для этого есть два способа:
— Использовать прерывания и циклом фигачить побайтово прямо в регистр. Но это постоянные прерывания с обработчиками, на больших данных будут блокировки. А я этого ой как не люблю. Я предпочитаю отправить данные через DMA и забыть о них. И начать заниматься другими делами, пусть МК сам разруливает.
— Либо создать большущий буфер на отправку, для 20 символьного дисплея это будет порядка 120 байт. Надо будет просто подготовить данные в буфере и отправить одним выстрелом в DMA. Но я решил экономить память.
Но нас интересует вопрос, я так ругал Ардуиновскую библиотеку, а есть ли выигрыш? А вот смотрите, что показывает LiquidCrystal_I2C:
У пытливого ума опять же возникает вопрос, а есть выигрыш на большой передаче? А вот смотрите, сверху STM32, а снизу LiquidCrystal_I2C, данные одинаковые, процедура инициализации тоже:
Итог: STM32 83мс, LiquidCrystal_I2C 122мс. Повторю, если использовать прерывания или готовый буфер вместо чистого DMA, можно получить ещё больший выигрыш, думаю вполне реально сократить это время до 60мс. Но надо ли? С таким дисплеем и его откликом это уже за гранью добра и зла =)
Что ещё интересного в библиотеке
Я написал одну единственную функцию, которая занимается командами. Это функция lcdCommand().
Конечно, вы можете использовать и запись типа if (command == LCD_DISPLAY), это также будет откомпилировано в бинарное дерево, но такой код читается хуже.
В результате мы получили возможность через #define определить прототипы функций, с коротким написанием и удобным чтением:
Почему не проверяем готовность дисплея, как в даташите
А потому, мои дорогие, что в случае с I2C это лишено смысла. Посмотрите на реальную передачу. На один только запрос уходит минимум 1 байт плюс ещё байт на адрес. Итого 180мкс. Для проверки готовности мы сначала должны выставить R/W в 1, потом еще щелкать стробами и внутри 1-го строба проверять бит BF на ноге DB7. Посчитали? Это при том, что по документации занят дисплей от 37мкс до 1,52мс. Проще просто использовать трюк с I2C.
Что можно придумать с русскими символами
Например, символ Д в HEX будет такой:
Соответственно загружаем его в CGRAM функцией:
и выводим функцией:
Ну а как просто вывести текст?
Это элементарно. Нужно просто выставить курсор и отправить код символа в дисплей. Он сдвинет курсор на следующую позицию автоматически, а мы следом шлём следующий символ. И т.д.
Код символа в C/С++ определяется, если взять его в одиночные кавычки, например, ‘B’. Либо просто перебором берём из строки &data[i]. Реализацию можете посмотреть в исходнике.
В готовом виде это:
Обратите внимание. Мы отправляем в функцию не строку, а указатель на массив uint8_t. Т.е. мы, во-первых, создаём строку в памяти, во-вторых, преобразуем строку в unsigned int, в-третьих, отправляем указатель на неё. Это, конечно, вариант для примера. В боевых устройствах использовать такую запись плохой тон. Т.к. во-первых, мы используем динамическое выделение памяти, что само по себе в условиях крайне её ограниченности не айс. Лучше стараться заранее выделить память под некоторые переменные. А во-вторых, приходится вручную пересчитывать размер строки. Так что хорошим тоном будет примерно так:
Немного о комментариях в коде
Призываю не слушать тех, кто заявляет, что очевидный код не нуждается в комментировании. В этом есть здравое зерно, правда есть несколько суровых НО.
Конечно, глупо комментировать каждую строчку. Но хорошим тоном, по моему опыту, являются:
Перед каждой функцией писать стандартный заголовок с тегами brief и note. В них стоит описать что это за функция и как она работает. Там же дать описания переменных и что она возвращает. Во многих современных редакторах есть плагин типа Docblockr. Просто перед функцией пишете /** и плагин сам создаёт отформатированный заголовок, вам нужно только дописать ручками несколько строк.
Давать отсылки на переменные из других файлов и документацию
Если алгоритмов для реализации несколько, напишите, почему выбрали конкретный. Сильно упрости общение с другими в будущем.
Добавляйте комменты для выделения этапов и всяких неочевидных вещей
Я сейчас дописываю документацию к библиотеке, читать её можно будет тут.
Невозможно описать и зацепить все моменты по такой теме. Честно говоря, когда я замахнулся, думал будет проще. Но вот писал статью полторы недели и всё ещё не чувствую её законченной.
Я постарался заострить внимание на ключевых моментах. Параллельно показать какие-то банальные фишки, которые многие боятся использовать. Например, указатели.
Вам же стоит открыть готовую библиотеку и посмотреть на её устройство собственными глазками. Она элементарная, проблем с чтением кода быть не должно. Но есть моменты, которые стоит соблюдать. Все их зацепить я не могу. Спрашивайте в комментах, постараюсь отвечать подробно.