93

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32

Предисловие


Меня частенько упрекают, что я даю материал в слишком трудной манере и не по порядку. Спешу вас расстроить, я и дальше буду следовать этой логике.


Стерильно-приторных уроков “для чайников” по программированию МК, в том числе и по STM32, на просторах инета полно. Учат они чему-то? Не уверен. Они лишь дают возможность разобраться в какой-то функции, когда возникает потребность.


У меня цели другие: научить думать и пользоваться документацией. Это можно сделать только на живых и настоящих примерах. Только в бою, так сказать. Я стараюсь показывать не столько возможности, сколько путь от задумки до реализации. Стараюсь выстраивать логику процесса так, чтоб не дочитав до конца вы уже могли бы предположить, что получится или какие могут возникать проблемы.


Я практикующий радионженер. Я не занимаюсь DIY. Я стараюсь показывать вещи так, как их делать ПРАВИЛЬНО. Делать на совесть, а не тяп-ляп и так сойдёт. Одно дело, когда вы берёте чужую реализацию и не разбираясь суёте в свой проект. Другое дело, когда сами пишете библиотеку с настроением, мол, разбираться некогда. Как то работает и х** с ним. Это ужас ужасный!


Предыдущие статьи:


Настройка Sublime Text 3, SW4 и STM32CubeMX для разработки STM32 под Windows 10

Настройка Sublime Text 3, SW4 и STM32CubeMX для разработки STM32 под Linux

STM32 от Булкина. Урок 1: Вводный, где мы немножко похулиганим

STM32 от Булкина. Atmega и Arduino vs STM32 и HAL


Ладно, сегодня у нас интересная тема!


Пишем библиотеку сами для STM32


В комментах проскакивали панические настроения некоторых людей, что не хватает библиотек для каких-то вещей. И я, и некоторые читатели, пытались убедить, что ничего страшного в этом нет.


Написание библиотеки - это приятный и увлекательный процесс! Бывает нудноватым, когда приходится тупо копипастить какие-то значения из документации, но в целом, весь процесс даёт какой-то адреналин даже. Ну а когда ты с чувством удовлетворения взглянешь на свой труд - оргазм!


Я долго не мог придумать, что же такое взять в качестве примера из того, что у меня самого не реализовано. И с удивлением обнаружил, что у меня нет библиотеки для классического текстового LCD на Hitachi HD44780. Это 1-но, 2-х или 4-х строчные дисплеи до 20 символов на строку. Те самые, которые все так любят втыкать во все свои DIY.


Полазил по просторам и с ещё большим удивлением обнаружил, что все реализации для шины I2C основаны на дурацкой классической библиотеке Arduino LiquidCrystal_I2C. Ну, думаю, сам Бог велел!


Начнём с главного: чтения документации.


Как работает дисплей


Дисплей основан на старинном чипе от HITACHI HD44780. У него нет последовательного интерфейса, как, например, у ST7920. Тем не менее, он прост до безобразия.


Открываем даташит раздел “Interfacing to the MPU” и видим примерную диаграмму, как устроен обмен данными. Смотрим, смотрим и видим фигу. Но всё-таки что-то почерпнуть можно.

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Базовый его режим 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.

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Обратите внимание, нельзя вот просто так взять и щёлкнуть стробом. Нужно помнить, что ширина строба и пауза между ними должны соответствовать паспортным данным. Идём в даташит и ищем что-то похожее на 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 мы тупо шлём один байт на него, где каждый бит соответствует ноге. Либо тупо считываем такой же байт с текущим состоянием этих самых ножек.


Так вот модуль подключен по аналогичной схеме (я реализовывал это у себя на плате года два назад):

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Пытливый ум, глядя на эту схему, задастся вопросом: А как же строб выдавать? Да ещё тайминги соблюдать. Да и вообще, как дрыгать ножками RS да R/W, чтоб не мешать данным и не сводить с ума дисплей? А вот тут и начинается самое интересное.


Ход мыслей такой. Давайте сначала заглянем в документацию PCF8574 и поищем там диаграмму по обмену данными. Находим прекрасную картинку:

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Внимательно смотрим и видим, что состояние на ногах меняется сразу по окончании приёма байта от МК. Т.е. нам нужно передать данные и выставить ногу P2 в высокий уровень чтобы включить строб. Потом передать данные и выставить P2 уже в ноль, т.е. строб мы выключаем. А для этого нам надо разобраться, что такое шина I2C и с чем её едят.


Шина I2C


Откровенно говоря, не люблю я её. Использую только там, где нет альтернативы. Скорость небольшая, ёмкость линии ограничена 400пФ, в результате длина линии очень маленькая. К тому же сама суть протокола имеет существенный недостаток, об этом позже. Для каждого готового устройства приходится вручную подбирать номиналы подтягивающих резисторов. В этом плане SPI гораздо удобнее и круче, хоть и требует минимум 3-х ног. Ладно, к сути.


I2C - это асимметричная последовательная шина, использует две двунаправленные линии. Т.е. данные передаются последовательно в одном направлении. Обе линии подтянуты к питанию. Шина типа “Ведущий-Ведомый”. Теоретически возможно ситуация, когда хоть все устройства могут быть как ведущим, так и ведомым. На практике реализовать это почти невозможно.


Для понимания работы, надо сначала запомнить правила:


- Данные на линии SDA могут меняться только при низком уровне на линии SCL

- Пока на линии SCL высокий уровень, на линии SDA данные не меняются

- Утрируя, есть три состояния: СТАРТ, СТОП и передача данных.

- Формировать сигналы СТАРТ и СТОП может только ведущий, даже в случае приёма им данных от ведомого

- Адрес ведомого устройства состоит из 7-ми бит.


Сигнал СТАРТ - это перевод линии SDA в низкий уровень при высоком уровне линии SCL.


Сигнал СТОП - перевод линии SDA в высокий уровень также при высоком уровне SCL.


Т.о. для начала передачи данных ведомогу, ведущий формирует сигнал СТАРТ. Все ведомые устройства на линии начинают слушать. Затем ведущий выстреливает адрес ведомого, с которым он хочет поговорить и сажает 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. Я вкратце напишу, что я делал.


Создадим два файла:

Inc/lcd_hd44780_i2c.h
Src/lcd_hd44780_i2c.c

Для начала написания кода, нам бы вообще понять, что делать в реальности. Для этого нам надо сделать две вещи: включить дисплей и послать на него пару символов. Открываем даташит дисплея, ищем описание процедуры инициализации.

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Отлично! Всё написано по шагам, с таймингами и даже биты указаны! Но мы любопытные и хотим сразу знать, что же битики значат, чтобы сразу заполнить заголовочный файл #define'ами. Вспоминаем про "Table 6: Instructions". Там прям идеально, с комментариями, расписаны все биты команд.


Открываем наш заголовочный файл и предварительно накидываем:

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Это та самая нудная часть работы, о которой я говорил. Внимательно смотрим в табличку, двоичный код переводим в HEX. Поясню на примере:


Инструкция Display on/off control требует всегда выставленного бита DB3. Открываем калькулятор, вводим двоичное 1000 и получаем 0x08 HEX.


В самой инструкции есть три команды:


- Display on/off

- Cursor on/off

- Blinking of cursor position character


Калькулятором высчитываем их HEX и будем их потом суммировать с LCD_BIT_DISPLAY_CONTROL.


Биты RS, RW, E и Backlight относятся к PCF8574, так что не забываем прописать и их.


Позже аналогичным способом напишем и остальные #define.


Для тех, кто не знаком с таким стилем, не стоит пугаться, что различные названия с одним значением. На самом деле, вы как бы пишете ссылки для себя, которые удобно читать. Компилятор же подставит вместо этих названий их значения. Причем только те, которые вы реально используете в коде.


Но теперь мы задумались и пришли к выводу, что нам нужно где-то хранить те параметры, что мы уже отправляли на дисплей. Также надо хранить данные шины, параметры дисплея и прочее. Для этого мы создадим структуру:

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Обратите внимание. В этом struct мы храним не саму структуру для I2C, а лишь указатель. Т.о. мы не дублируем данные и всегда под рукой их состояние.


Судя по алгоритму инициализации, первые этапы уникальны и можно реализовать их тупо отправляя данные через базовые функции HAL. Их мы реализуем в функции lcdInit().


Пора бы уже отправить какие-то данные на дисплей. Хорошим тоном будет сделать локальную низкоуровневую функцию, которая будет подготавливать данные для отправки и саму отправку. А мы лишь будем в неё закидывать состояние управляющих битов и байт данных.


Посмотрите реализацию в уже готовой библиотеке. В чём фишка. Для того, чтобы выдавать строб, мы дважды шлём первую партию 4-х бит. Третьим байтом шлём младшие 4-бит и закрываем строб.


И вот, что получается на деле:

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

При таком раскладе и скорости шины I2C в 100кбит, ширина строба ~180мкс, пауза между стробами ~90мкс, и пауза между парными стробами ~530мкс. По идее, и я так думал предварительно, можно не удлинять строб на два байта, обойтись одним. Но на деле оказалось, что 90мкс мало для ширины строба, но достаточно для паузы между стробами. Похоже, что кварц в дисплее работает на более низкой частоте, чем положено по даташиту. Как говорил - Китай такой Китай =( А может мой дисплей дурит.


Также можно сократить длинную паузу раза в два, для этого есть два способа:


- Использовать прерывания и циклом фигачить побайтово прямо в регистр. Но это постоянные прерывания с обработчиками, на больших данных будут блокировки. А я этого ой как не люблю. Я предпочитаю отправить данные через DMA и забыть о них. И начать заниматься другими делами, пусть МК сам разруливает.


- Либо создать большущий буфер на отправку, для 20 символьного дисплея это будет порядка 120 байт. Надо будет просто подготовить данные в буфере и отправить одним выстрелом в DMA. Но я решил экономить память.


Но нас интересует вопрос, я так ругал Ардуиновскую библиотеку, а есть ли выигрыш? А вот смотрите, что показывает LiquidCrystal_I2C:

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Комментарии излишни. Но ведь я учу вас критическому мышлению, не так ли? Что если оптимизировать код библиотеки Ардуино? Да! И я уверен, получится значительно улучшить параметры передачи. Делайте, думаю стотыщпятьсот людей вам скажут спасибо. Ведь я к чему это всё говорю. К тому, что в этом и есть беда Ардуино - такой вот код, где никто не думает об оптимизациях. Причём ведь делает вид, что всё согласно даташиту. Например, зачем нужны задержки между передачами в 50мкс, если в реальности между стробами 500мкс??


У пытливого ума опять же возникает вопрос, а есть выигрыш на большой передаче? А вот смотрите, сверху STM32, а снизу LiquidCrystal_I2C, данные одинаковые, процедура инициализации тоже:

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост
STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Итог: STM32 83мс, LiquidCrystal_I2C 122мс. Повторю, если использовать прерывания или готовый буфер вместо чистого DMA, можно получить ещё больший выигрыш, думаю вполне реально сократить это время до 60мс. Но надо ли? С таким дисплеем и его откликом это уже за гранью добра и зла =)


Что ещё интересного в библиотеке


Я написал одну единственную функцию, которая занимается командами. Это функция lcdCommand().


Она занимается как установкой параметров, так и снятием. В качестве входных параметров, у неё команда и флаг - снять или выставить команду. Оба параметра - это нумерованные списки LCDCommands и LCDParamsActions.


Обратите внимание, никаких if/else. Всё сделано на Switch/Case. Несмотря на то, что их аж три штуки и приходится как минимум дважды проверять команду, работает это невероятно быстро. И причина тому - Бинарное дерево, в которое компилятор транслирует наш код. По сути, там всего два узла, так что поиск происходит за несколько тактов.


Конечно, вы можете использовать и запись типа if (command == LCD_DISPLAY), это также будет откомпилировано в бинарное дерево, но такой код читается хуже.


В результате мы получили возможность через #define определить прототипы функций, с коротким написанием и удобным чтением:

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

А вообще, совет. Там, где у вас чётко обозначенные варианты, использовать switch/case, там, где необходимо сравнивать величины, использовать if/else. И не стесняйтесь нумерованных списков enum - они занимают очень мало памяти. Компилятор сам подбирает тип, но всё также, как с обычными целочисленными переменными, чем больше список, тем больше разрядность.


Почему не проверяем готовность дисплея, как в даташите


А потому, мои дорогие, что в случае с I2C это лишено смысла. Посмотрите на реальную передачу. На один только запрос уходит минимум 1 байт плюс ещё байт на адрес. Итого 180мкс. Для проверки готовности мы сначала должны выставить R/W в 1, потом еще щелкать стробами и внутри 1-го строба проверять бит BF на ноге DB7. Посчитали? Это при том, что по документации занят дисплей от 37мкс до 1,52мс. Проще просто использовать трюк с I2C.


Что можно придумать с русскими символами


У нас есть только возможность загрузить своих 8 символов. Я с этим сталкивался и, скажу, это нелегкий выбор =) Для этого в дисплее есть доступный EPROM на 8 ячеек. Каждая в каждую ячейку можно записать символ из 8 строк по 5 точек в каждой. Соответственно, это массив из 8 байт, где младшие 5 бит и есть наши точки. На самом деле, последняя строка - это курсор, так что, если уж соответсвовать стандартам, на символ можно использовать 5х7 точек. Вот схема из даташита (Example of Correspondence between EPROM Address Data and Character Pattern (5 × 8 Dots)):

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Например, символ Д в HEX будет такой:

uint8_t symD[8] = { 0x07, 0x09, 0x09, 0x09, 0x09, 0x1F, 0x11 }; // Д

Соответственно загружаем его в CGRAM функцией:

lcdLoadCustomChar(0, &symD);

и выводим функцией:

lcdPrintChar(0);



Ну а как просто вывести текст?


Это элементарно. Нужно просто выставить курсор и отправить код символа в дисплей. Он сдвинет курсор на следующую позицию автоматически, а мы следом шлём следующий символ. И т.д.


Код символа в C/С++ определяется, если взять его в одиночные кавычки, например, 'B'. Либо просто перебором берём из строки &data[i]. Реализацию можете посмотреть в исходнике.


В готовом виде это:

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Обратите внимание. Мы отправляем в функцию не строку, а указатель на массив uint8_t. Т.е. мы, во-первых, создаём строку в памяти, во-вторых, преобразуем строку в unsigned int, в-третьих, отправляем указатель на неё. Это, конечно, вариант для примера. В боевых устройствах использовать такую запись плохой тон. Т.к. во-первых, мы используем динамическое выделение памяти, что само по себе в условиях крайне её ограниченности не айс. Лучше стараться заранее выделить память под некоторые переменные. А во-вторых, приходится вручную пересчитывать размер строки. Так что хорошим тоном будет примерно так:

STM32 от Булкина. Урок 2: Пишем библиотеку сами для STM32 Stm32, Урок, Длиннопост

Немного о комментариях в коде


Призываю не слушать тех, кто заявляет, что очевидный код не нуждается в комментировании. В этом есть здравое зерно, правда есть несколько суровых НО.


Конечно, глупо комментировать каждую строчку. Но хорошим тоном, по моему опыту, являются:


Перед каждой функцией писать стандартный заголовок с тегами brief и note. В них стоит описать что это за функция и как она работает. Там же дать описания переменных и что она возвращает. Во многих современных редакторах есть плагин типа Docblockr. Просто перед функцией пишете /** и плагин сам создаёт отформатированный заголовок, вам нужно только дописать ручками несколько строк.

Давать отсылки на переменные из других файлов и документацию

Если алгоритмов для реализации несколько, напишите, почему выбрали конкретный. Сильно упрости общение с другими в будущем.

Добавляйте комменты для выделения этапов и всяких неочевидных вещей

Я сейчас дописываю документацию к библиотеке, читать её можно будет тут.


Напоследок


Невозможно описать и зацепить все моменты по такой теме. Честно говоря, когда я замахнулся, думал будет проще. Но вот писал статью полторы недели и всё ещё не чувствую её законченной.


Я постарался заострить внимание на ключевых моментах. Параллельно показать какие-то банальные фишки, которые многие боятся использовать. Например, указатели.


Вам же стоит открыть готовую библиотеку и посмотреть на её устройство собственными глазками. Она элементарная, проблем с чтением кода быть не должно. Но есть моменты, которые стоит соблюдать. Все их зацепить я не могу. Спрашивайте в комментах, постараюсь отвечать подробно.

Дубликаты не найдены

62 комментария

по актуальности
+2
Прекрасно! Долой ардуинщиков, даёшь нормальные МК.

У меня есть вопрос: разве в первом варианте передачи строк используется динамическая память? Я всегда думал, что при таком подходе передается адрес строки (адрес той области памяти, где хранятся все константы и строки). И прямо оттуда осуществляется чтение.

Хотя я сейчас подумал, что как минимум в Гарвардской архитектуре необходимо сначала скопировать строку из памяти инструкций в память данных... Надо изучить вопрос...
раскрыть ветку 1
+1

На AVR'ах строки действительно сидят в памяти, загрузка где-то в crt0.S. Из-за этого весь трах с PSTR() и всякими printf_P().


На кортексах же я такого не вижу, если ф-ция берет `const char *`, то указатель будет в область на флеше (можно проверить по карте памяти).

+3

Наконец-то завезли нормальные уроки с описанием нюансов, в которых DIY гайдеры никогда не будут разбираться. Тема очень интересная.

раскрыть ветку 3
+5

Ну а я и не вижу смысла плодить клонов. Меня учили в МАИ по древним программам. Учили думать и работать с документацией. Остальное нарабатывается с опытом.

раскрыть ветку 2
+1

Древние программы не всегда значит плохие. Все-таки микроконтроллеры лучше изучать с низкого уровня. А то тут всплывают инженеры АСУТП, которые даже под ардуино программку написать не могут.

раскрыть ветку 1
+1

А часто на stm32 появляется смысл экономить ноги? Ну поставить примерно то же, но в LQFP-64 вместо LQFP-48, места на плате это займет не больше, чем доп. чип с обвязкой.

раскрыть ветку 4
+1

Бывают случаи, когда жёстко ограничен габаритами платы и корпуса. И вроде даже есть местечко, но дополнительные ноги - это ещё и дополнительные кондёры на питание, например. Ещё дополнительные дороги. А бывает, что на 2-х слойке сделать разводку без нескольких переходных отверстий на одну дорогу никак. Короче лишний гемор.


Что касается конкретного примера с дисплеем, не забывайте, что дисплей часто надо выносить далеко за пределы платы. У меня были варианты и метра на полтора. Попробуйте кинуть дополнительный шлейф только на дисплей проводов так 7 минимум, а то и на 11 в полном варианте. Конечно, I2C для таких длин вообще задница, но вполне юзабельно. Поэтому я давно в небольших дисплеях слез с символьных и использую графические 128х64 точек. Особенно это приятно, что там есть последовательная шина, которую можно к SPI подцепить вплоть до 10МБит. И Даже на 2-х метрах работает вообще без сбоев, даже в условиях сильных помех среди контакторов и частотников.


Ну и последнее. Я заранее стараюсь выбрать МК с запасом, чтоб оставалось десяток свободных ног и чтоб на этих ногах ещё и была возможность какую-то шину включить. Так, чтобы вот все ноги заняты и другой МК не воткнуть было лишь однажды, когда LQFP100 заменить на LQFP144 вот никак. Но ничего, выкрутились и даже получили лучшее решение - это модули расширения, которые цепляем по Modbus и RS485.

раскрыть ветку 3
0

А, кстати, что делать со свободными ногами? Подтянуть к питанию?

раскрыть ветку 2
+1

Камрад Булкин, спасибо за то, что нашёл время и сделал это непростое дело.

+1

Автор, ну без прерываний не так интересно, в них вся мякотка. Но гайд хорош, спору нет.

раскрыть ветку 8
+1

В таком серьёзном режиме с прерываниями работаю с RS485+Modbus ну и с фазовым контролем мощности. В остальных вещах стараюсь использовать DMA и выпуливать/получать всё одним пакетом.

раскрыть ветку 7
0

Ну а разве нельзя чтобы прерывание от и2ц само дергало дма без участия процессора?

раскрыть ветку 6
+1

После уроков для ардуино, типо скачайте скетч и запишите в в МК, это прямо новый уровень. Особенно с даташитами. Мне нравится такой формат уроков.

+1

#define FALSE TRUE

раскрыть ветку 4
+7

Счастливой отладки, с**ки =))

раскрыть ветку 1
0
))
+5

#define TRUE (rand() % 100 < 95) //Так веселее будет))

раскрыть ветку 1
0
хахаха, +))
0

ComradeBulkin!! Искреннее спасибо. Давно мечтал и о RTOS, и о таком подходе к HD44780.

И урок по FreeRTOS и библиотека по HD44780 запустились и прекрасно работают.

В порядке образования подскажите как можно в проекте FreeRTOS использовать вашу библиотеку HD44780 для управления двумя индикаторами HD44780+PCF8574 с разными адресами (к примеру 0x26, 0x27) на одной шине I2C?

раскрыть ветку 3
0

На C++ такое делается довольно просто, в C99 нужно потанцевать с бубном. В любом случае надо либу переделать слегка. Для C99 надо сделать отдельную структуру, в которой хранить параметры дисплея. И для каждой команды передавать эту структуру в функцию. Например:


LCDParams lcd1;
LCDParams lcd2;

lcdInit(&lcd1, &hi2c2, (uint8_t)0x26, (uint8_t)4, (uint8_t)20);
lcdInit(&lcd2, &hi2c2, (uint8_t)0x27, (uint8_t)4, (uint8_t)20);

Соответственно lcdInit надо переделать на работу со передаваемой структурой:

bool lcdInit(LCDParams *lcd, I2C_HandleTypeDef *hi2c, uint8_t address, uint8_t lines, uint8_t rows) {

    TickType_t xLastWakeTime;


    uint8_t lcdData = LCD_BIT_5x8DOTS;


    lcd.hi2c = hi2c;

    lcd.address = address << 1;

    lcd.lines = lines;

    lcd.columns = columns;

    lcd.backlight = LCD_BIT_BACKIGHT_ON;


    lcdCommandBuffer[0] = LCD_BIT_E | (0x03 << 4);

    lcdCommandBuffer[1] = lcdCommandBuffer[0];

    lcdCommandBuffer[2] = (0x03 << 4);


    /* И так далее... */

}

И ещё при каждом цикле работы с дисплеем желательно организовать семафор. Брать его в начале, отдавать в конце, чтоб не получилось, что разные задачи начинают одновременно на одной шине слать данные в разные дисплеи.

раскрыть ветку 2
0
Спасибо. Буду пробовать, хотя для меня это сложно.
раскрыть ветку 1
0

Как там дела с уроками? Хотелось бы что-нибудь про FreeRTOS и битовые операции.

раскрыть ветку 2
0

К сожалению, в ближайшие несколько недель я вряд ли что-нибудь напишу. В процессе развода, сосредоточиться на статью не получается =(( Но несколько черновиков имею, планы тоже есть. Так что будет, но чуть позже.

раскрыть ветку 1
0
Ну, и об этом можно написать, тут такое хорошо заходит ))
Сейчас пишу библиотеку для ssd1326 (256х32, oled, 16 градаций серого) под FreeRTOS, память у него организована по 4 бита на "цвет" пикселя, по 2 пикселя на байт, в память можно только писать, как же я с ним запарился ) Пришлось придумать примитивный фрейм-буффер и передавать его по семафору в отдельной задаче. Вот только что победил корректную отрисовку на имеющемся фоне.
0
uint8_t, с чего бы это именно int? Может заглянуть в переопределение типов для процессора?
0
Слишком много приведений типов. Не надо так. Да и "const char *" уже будет во флеше. Просто делайте правильные типы.
И делать инлайны через препроцессор тоже так-себе. Есть же static inline.
раскрыть ветку 10
0

Спасибо. Хотя я не очень понимаю, о чём конкретно речь. Просто делать правильные типы не всегда получается, особенно в коде на 15к строк (помимо HAL). Бывают сложные моменты отладки и я предпочитаю знать точное место, где определён тип переменной и разрядность.


Если речь про #define, то да, можно писать и в виде 0x08U. И компилятор уже сам выберет разрядность unsigned int. Я предпочитаю жёстко задавать. Просто дело привычки, мне так визуально легче потом читать свой же код.


static inline очень спорный момент. Не вижу смысла не использовать #define на функции в конкретном примере. Всё жёстко типизировано, юзеру ничего не отдаётся. Сделано просто для удобства. Или вы не об этом?

раскрыть ветку 9
0
Я и про определения констант для команд, писать `(uint8_t)0xab` явно избыточно, но если все же так делать, то нужно брать приведение типа в скобки, дабы избежать возможных проблем. Т.е. например должно быть `((uint8_t) (1<<7))`.


Переопределение типа для строк тоже не добавляет читабельности. Вообще стоит стараться писать так, чтобы приведений типов было минимум. Конечно допустимо приводить (void*) к нужной структуре для generic кода, или расширять переменную для вычислений


А static inline более идиоматически  выглядит для такого кода. При чем такой простой вариант наверняка будет расценен как always inline.

раскрыть ветку 8
0
Я тут с телефона смотрю, и не совсем удобно на гитхаб лазить постоянно. Каким образом организованы задержки типа 50мкс?
раскрыть ветку 3
+1

Отправка одного байта по I2C на скорости 100кбит занимает порядка 90мкс. Это минимальная задержка, которую можно сделать, если отправить команду на включить ногу и следом выключить ногу. Принципиально уменьшить нельзя, если не менять скорость I2C. Задержка 50мкс - это на схеме, где я в принципе объясняю, как работает дисплей и какие задержки стоит делать, если управлять им напрямую, без модуля I2C.

раскрыть ветку 2
0
Благодарю. Ещё раз пробежал глазами и увидел про медленный кварц в контроллере дисплея.
Просто я последние пару лет на ПЛИС работаю. Там с таймингами все прозрачнее.
раскрыть ветку 1
0

Со времени покупки на ебее демоплаты с rbt6 использую в качестве базы SPL, но последнее время появляются мысли перейти на HAL. Как это проще сделать? )

раскрыть ветку 7
0

Ну мне так кажется, что с чистого листа. Новый проект сразу начинать на HAL. А старые поддерживать на SPL.

раскрыть ветку 6
0
Сгенерил Кубом проект, помигал лампой, потом начал добавлять свою библиотеку под ILI9328 и редактировать ее под HAL, и при компиляции посыпались ошибки о повторном объявлении всего, что находится в дефайне STM32F10X_MD в stm32f10x.h, а также нескольких макросов до него. Шапка хедера библиотеки сделал полностью аналогично вашей, но чего ему надо то?
раскрыть ветку 5
0

Таки написал библиотеку. Почти что специально для меня. :-)


А я тут сайт сделал по своему проекту. Не, не подумай, просто курсовой проект. Я сам сделал сервер (под потолком, на проводках висит малинка), поднял, зарегистрировал домен и прочие телодвижения для поднятия сайта с 0 на своём железе. Мне просто оригинальная тематика была нужна и я сделал такую.

http://intelamp.ru

раскрыть ветку 1
0

Ну вы меня подтолкнули. Иначе я бы чо-нить другое написал, что мне нужнее =) Этот LCD мне неинтересен в качестве применения в моих устройствах. Но так уж, дабы сноровку не терять. Разминка перед большим проектом, что у меня сейчас.

0
Rust пробовали? Говорят неплохо заходит на мк.
раскрыть ветку 1
0

Нет, не пробовал. Я пока не смотрю в сторону от C, он пока что дефолтный язык для STM32. Мне хватает своих танцев с бубном =)

Похожие посты
Похожие посты закончились. Возможно, вас заинтересуют другие посты по тегам: