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. Просто перед функцией пишете /** и плагин сам создаёт отформатированный заголовок, вам нужно только дописать ручками несколько строк.

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

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

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

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


Напоследок


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


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


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

Сообщество Ремонтёров

7.3K поста42.1K подписчиков

Добавить пост

Правила сообщества

ЕСЛИ НЕ ХОТИТЕ, ЧТОБЫ ВАС ЗАМИНУСИЛИ НЕ ПУБЛИКУЙТЕ В ЭТОМ СООБЩЕСТВЕ ПРОСЬБЫ О ПОМОЩИ В РЕМОНТЕ, ДЛЯ ЭТОГО ЕСТЬ ВТОРОЕ СООБЩЕСТВО:


Посты с просьбами о помощи в ремонте создаются в дочернем сообществе: https://pikabu.ru/community/HelpRemont

К публикации допускаются только тематические статьи с тегом "Ремонт техники".

В сообществе строго запрещено и карается баном всего две вещи:

1. Оскорбления.

2. Реклама.

В остальном действуют базовые правила Пикабу.

Вы смотрите срез комментариев. Показать все
Автор поста оценил этот комментарий

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

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

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

раскрыть ветку (4)
Автор поста оценил этот комментарий

На 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)
Автор поста оценил этот комментарий
Спасибо. Буду пробовать, хотя для меня это сложно.
раскрыть ветку (1)
Автор поста оценил этот комментарий

Надо только начать и делать аккуратно и последовательно. И предварительно составить небольшой план. Всё получится!

Автор поста оценил этот комментарий

Посмотри вот тут ещё какая-то библа STM32_HD44780 I2C library лежала https://trueimpostors.ucoz.ru/load/

Вы смотрите срез комментариев. Чтобы написать комментарий, перейдите к общему списку