В рамках проекта All-Hardware мне довелось освоить работу с экраном на макетной плате LPC55S69-EVK от NXP. Пикантность ситуации в том, что эта плата стандартно поставляется без экрана, поэтому освоение работы включало еще и поиск экрана, который можно добыть в наших краях, и его подключение.
Во второй части статьи я расскажу о том, какие действия следует совершить, чтобы повторить свой подвиг на практике.
Но сначала выскажу все, что накипело за время работы.
Правда, все сказанное ниже является моим личным мнением и зачастую не совпадает с мнением руководства нашей компании.
Но у инженера-программиста, лично прошедшего через все это, вполне может быть свое мнение.
И вот оно.
Первая часть.
Изучать
Особенности конструкции и схемы
Так.В целом на вид все должно получиться довольно красиво.
Плата разработки имеет разъем, совместимый с Arduino Uno. И сделано это забавно.
Фактически разъемы двухрядные.
С Arduino Uno совместимы только внутренние ряды.
Внешние сами по себе.
Что может быть проще? Теперь возьмем совместимый с Arduino дисплей, просто поместим его на плату и воспользуемся какой-нибудь библиотекой, тележка и маленькая тележка которой есть на github. Конечно, у нас были подходящие дисплеи.
Вот его разъем:
Сбивает с толку то, что положения контактов шины данных D0 и D1 на этих двух платах не совпадают. Но если бы только это было единственной проблемой! Конечно, для реальной работы нужно понимать, какие порты контроллера подключены к соответствующим шинам.
Теперь возьмем описание макета.
О! А чтобы получить описание к макету на сайте производителя, необходимо зарегистрироваться.
Представьте себе: вы инженер, получивший задание подобрать оборудование для нового продукта.
Вы вошли в свою любимую поисковую систему.
И он дал вам то, что было открыто.
А вы выбрали из того, что дали.
Вы будете намеренно рыскать по закрытому дну бочки, специально регистрируясь? Я бы не стал.
Зачем тогда я пишу об этой раскладке? Я вам говорю, руководство поставило передо мной задачу внедрить конкретную плату в проект, в котором важен широкий ассортимент продукции.
И мне подарили эту плату, так как она была куплена специально для расширения поддерживаемого диапазона.
Иначе я бы ее не нашел.
Не люблю лишний раз где-то регистрироваться, когда вокруг столько всего доступно без регистрации! В любом случае.
Собственно, необходимый документ можно скачать у продавцов.
Тот самый мышелов.
Открываем и.
Нет. Это надо проиллюстрировать.
Таблицы с описанием распиновки разъема нет. Но есть рисунок.
Допустим, к портам PIO1_9 и PIO1_10 у меня особых претензий нет. А что за порты контроллера LED_RED_ARD и т.п.
всех цветов? Ну и ряд других ног подобного типа.
Такое ощущение, что автор документа не до конца понимал, зачем он это делает. Поэтому я просто скопировал названия схем со схемы.
Зачем эти цепи? Названия схем — это абстракция автора схемы, не более того.
Нам нужны имена соответствующих им ножек контроллера! Все это знают. Кроме автора документа.
Хорошо, поищем схему расположения.
В отличие от описания, скачать схему нельзя нигде, кроме как у производителя.
А производитель требует регистрации при каждом чихе.
В целом мне это все напоминает те времена, когда справочник Шайло был незаменимой, но и недостающей вещью.
Я его вынул, когда от него не было никакой пользы, так как тонкая логика была заменена программируемой логикой.
Но психологическая травма, полученная в детстве, заставила меня взяться за нее, когда люди начали продавать их в букинистические магазины.
И та же самая травма теперь не позволяет мне выбросить этот том, хотя место в шкафу освобождаю регулярно.
Но электронная промышленность страны, находившаяся в таких условиях, накрылась медным тазом.
Или инженеры NXP мечтают о том же? Что может быть секретом в дизайне макетной платы? Почему я не могу его увидеть без регистрации? Китайцы его украдут? Так что дело в продаже контроллеров! Чем больше инженеров владеют макетной платой, тем выше шанс, что контроллер войдет в продукт и будет приобретен в промышленных масштабах! Я не понимаю! Но продолжим.
Оказалось, что обычная параллельная шина данных разбросана по разным, непоследовательным битам двух разных портов GPIO. Вот шпаргалка, которую я составил для себя по нужным мне контактам:
У меня есть многострадальная статья, написанная в 2017 году, но до сих пор не вышедшая в свет, где я рассматриваю принципы работы библиотеки mcucpp Константина Чижова.
Там я вижу, как оптимально компилятор будет строить ассемблерный код по последовательным битам, по разрыву, по двум полубайтам на разных портах.
После чего останавливаюсь на словах, что ни один схемотехник не сможет сделать что-то более извращенное.
Но эта статья не о моей работе.
Моя - всего лишь тест. Поэтому мне не дали добро на ее публикацию.
Может в комментариях кто-то убедит руководство, что выкладывать стоит. Но тем не менее.
Когда я это писал, я даже не мог себе представить, что еще найдутся схемотехники, которые будут так хитро разбрасывать биты шины данных.
Не факт, что машина для смешивания шаров «Спортлото» сможет сделать то же самое! Но это не все! Как уже отмечалось выше, линии светодиодов висят на трёх битах шины данных.
Чтобы при работе с параллельной шиной была дискотека, наверное.
Почему их нельзя было повесить на другие ряды разъема? Видно, что разъем в два раза шире, чем его часть Arduino. Я бы не говорил всего вышеперечисленного, если бы не шелкография.
Маркировка параллельных шин нанесена на плате методом шелкографии.
Это не мне пришла в голову мысль, что такая шина существует, это заметил дизайнер.
Поддержка программного обеспечения
Теперь перейдем к поддержке программного обеспечения.Здесь все намного лучше.
В состав BSP входят сразу две библиотеки (бинарный код знаменитого emWin и исходные коды Littlevgl).
Эти библиотеки сопровождаются примерами прикладных программ.
Сервис All-Hardware был создан, чтобы дать программистам возможность ощутить работу с оборудованием, не покупая его.
Конечно, программистам желательно использовать стандартные библиотеки и примеры.
Если я предложу работать с экраном через какие-то мои библиотеки, пользователи скажут обо мне примерно то же самое, что я сказал о разработчиках железа в прошлом разделе.
Не нужно изобретать велосипед, если есть готовые решения! Но вот в чем проблема — все готовые решения предназначены для показа Экран Adafruit TFT LCD с крышкой Touch .
Подключается через порт SPI (тачскрин пока не рассматриваем — удаленно все равно не нажмешь).
То есть дисплей у нас с параллельной шиной хороший, но не подходящий.
Мало того, что его сложно реализовать, так он еще и будет совершенно несовместим с готовыми программными решениями, входящими в пакет BSP.
Выбор экрана
Где быстро получить Экран Adafruit TFT LCD с крышкой Touch в наших краях я его не нашел.Но мы поступили умнее.
Мы запросили на Яндекс Маркете дисплей с контроллером ILI9341 (как у Адафрутовского) и SPI-интерфейсом.
Был опубликован большой список.
Вариант с разъемом для Arduino и интерфейсом SPI оказался единственным в списке.
Вариантов с коротким разъемом SPI было много, но конструктивно его проще поставить на готовый разъем, чем городить там что-то.
Ну мы и заказали.
Он ехал из самого Красноярска.
Доставка была SDЭCom до квартиры, что очень кстати в условиях самоизоляции.
Пришло даже на день раньше заявленного.
А внутри посылки был точно такой же дисплей, как тот, который я показывал на фото в начале статьи.
С параллельной шиной.
Так что после этого доверяйте людям! Поскольку у проекта еще должен быть дедлайн, мы решили больше не экспериментировать с заказами, а взять что-нибудь с шиной SPI из того, что было в наличии.
И был дисплей от Raspberry Pi от WaveShare. В общем, у него есть свое достоинство – он широко распространен.
Кстати, позже выяснилось, что компания WaveShare также производит SPI-дисплеи с разъемом Arduino, но их контакты не полностью совместимы с Adafruit. На другой ноге есть сигнал C/D и есть вывод Reset_n. Но они доступны на Али Экспресс у многих продавцов.
Если кто-то ищет дисплей для себя, имейте это ввиду.
Но нам некогда было ждать доставку с Али Экспресс, поэтому дальше будем работать с дисплеем от Raspberry Pi.
Часть вторая.
Давайте начнем работу
Шаг 0: подключите оборудование
Если вы являетесь пользователем услуги «Все оборудование», вы можете пропустить этот шаг.С нашей стороны уже все сделано.
Но если вы подключаете плату дома, я вам расскажу, как это сделать.
Хорошо быть программистом! Намотал провода, а дальше уже конструкторам об этом беспокоиться.
На моем столе результат переключения выглядел так:
Шаг 1: отключите работу сенсорного экрана
Так.Войдите в MCUXpresso IDE и выберите «Файл» -> «Создать» -> «Импортировать пример SDK».
Далее выберите доску:
И для простоты изложения я выберу проект Litegl_example-> litegl_demo. Мне будет проще показать задачи для шага 3. Но вообще, конечно, в реальной жизни можно выбрать Littlegl_terminal — для экспериментов проще.
Собираем, запускаем.
Не работает. Это отлично.
Нам выдают фатальную ошибку после попытки открыть сенсорный экран.
И наш сенсорный экран отличается от того, который используется на плате Adafruit. Для целей этой статьи нам просто нужно удалить поддержку сенсорного экрана.
Как минимум, для проекта All-Hardware цели разобраться с этим не было, но с чисто человеческой точки зрения, наверное, уже понятно, что с продукцией NXP я не подружился.
Мне больше нравятся открытые вещи.
Поэтому я так и не освоил работу с ним.
Но поменьше лирики! Вот место, которое непосредственно приводит к краху программы:
А чтобы она не вызывалась, просто удалим из функции общий вызов инициализации тачскрина AppTask() .
На скриншоте ниже соответствующий вызов закомментирован:
Шаг 2: заменить инициализацию
Запускаем полученную программу.Больше не вылетает с ошибкой, но экран ничем не заполняется.
Дело в том, что библиотека предназначена для контроллера экрана ILI9341, но на самом деле, насколько я понимаю, стоит ILI9488 (или совместимая с ним).
Эти контроллеры довольно схожи по набору команд, но процесс их инициализации несколько отличается.
Теперь мы отредактируем функцию FT9341_Init() в файле fsl_ili9341.c .
Изначально это выглядит так (привожу скриншоты, чтобы вы тоже могли ориентироваться в дереве файлов):
Мы полностью удалим ее тело.
Вместо этого давайте введем следующий код:
Чтобы этот код работал, добавьте в заголовочный файл fsl_ili9341.h следующие объявления:static const uint8_t ILI9488_regValues[] = { 0x01, 0, 0xB1, 1, 0xA0, 0xB4, 1, 0x02, 0xC0, 2, 0x17, 0x15, 0xC1, 1, 0x41, 0xC5, 3, 0x00, 0x0A, 0x80, 0x36, 1, 0x48, 0x3A, 1, 0x55, 0xE9, 1, 0x00, 0xF7, 4, 0xA9 ,0x51, 0x2C, 0x82, 0x11, 0, TFTLCD_DELAY, 120, 0x29, 0, // */ }; SDK_DelayAtLeastUs(ILI9341_RESET_CANCEL_MS * 1000U, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY); int i = 0; while(i < sizeof(ILI9488_regValues)) { uint8_t r = ILI9488_regValues[i++]; uint8_t len = ILI9488_regValues[i++]; uint8_t d; if(r == TFTLCD_DELAY) { SDK_DelayAtLeastUs(len * 1000U, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY); } else { writeCommand(r); for (d=0; d<len; d++) { writeData(ILI9488_regValues[i++]); } } }
#define ILI9488_NOP 0x00
#define ILI9488_SOFT_RESET 0x01
#define ILI9488_READ_ID 0x04
#define ILI9488_READ_NUM_DSI_ERR 0x05
#define ILI9488_READ_STATUS 0x00
#define ILI9488_READ_PWR_MODE 0x0A
#define ILI9488_READ_MADCTL 0x0B
#define ILI9488_READ_PIXEL_FMT 0x0C
#define ILI9488_READ_IMG_MODE 0x0D
#define ILI9488_READ_SIGNAL_MODE 0x0E
#define ILI9488_READ_SELF_DIAG_RES 0x0F
#define ILI9488_SLEEP_IN 0x10
#define ILI9488_SLEEP_OUT 0x11
#define ILI9488_PARTIAL_MODE_ON 0x12
#define ILI9488_NORMAL_MODE_ON 0x13
#define ILI9488_INVERSION_OFF 0x20
#define ILI9488_INVERSION_ON 0x21
#define ILI9488_ALL_PIXEL_OFF 0x22
#define ILI9488_ALL_PIXEL_ON 0x23
#define ILI9488_OFF 0x28
#define ILI9488_ON 0x29
#define ILI9488_COL_ADDR_SET 0x2A
#define ILI9488_PAGE_ADDR_SET 0x2B
#define ILI9488_MEM_WRITE 0x2C
#define ILI9488_MEM_READ 0x2E
#define ILI9488_PARTIAL_AREA 0x30
#define ILI9488_VSCROLL_DEF 0x33
#define ILI9488_TEARING_EFFECT_LINE_OFF 0x34
#define ILI9488_TEARING_EFFECT_LINE_ON 0x35
#define ILI9488_MEM_ACCESS_CTRL 0x36
#define ILI9488_VSCROLL_START_ADDR 0x37
#define ILI9488_IDLE_MODE_OFF 0x38
#define ILI9488_IDLE_MODE_ON 0x39
#define ILI9488_INTERFACE_PIXEL_FMT 0x3A
#define ILI9488_MEM_WRITE_CONTINUE 0x3C
#define ILI9488_MEM_READ_CONTINUE 0x3E
#define ILI9488_WRITE_TEAR_SCAN_LINE 0x44
#define ILI9488_READ_TEAR_SCAN_LINE 0x45
#define ILI9488_WRITE_BRIGHTNESS_VAL 0x51
#define ILI9488_READ_BRIGHTNESS_VAL 0x52
#define ILI9488_WRITE_CTRL_VAL 0x53
#define ILI9488_READ_CTRL_VAL 0x54
#define ILI9488_WRITE_CONTENT_ADAPT_BRIGHTN_CTRL_VAL 0x55
#define ILI9488_READ_CONTENT_ADAPT_BRIGHTN_CTRL_VAL 0x56
#define ILI9488_WRITE_CABC_MIN_BRIGHTN 0x5E
#define ILI9488_READ_CABC_MIN_BRIGHTN 0x5F
#define ILI9488_READ_AUTO_BRIGHTN_CTRL_SELF_DIAG_RES 0x68
#define ILI9488_READ_ID1 0xDA
#define ILI9488_READ_ID2 0xDB
#define ILI9488_READ_ID3 0xDC
#define TFTLCD_DELAY 0xFF
Шаг 3: Редактирование функции установки координат
Этот шаг может потребоваться только для пользователей дисплеев WaveShare и не требуется для владельцев дисплеев с чистым разъемом SPI. Нет возможности проверить.А вот на имеющемся дисплее после внесения изменений в инициализацию экран стал что-то отображать.
Правда, это нечто сосредоточено в его верхней части.
Сразу после запуска программы вы можете увидеть, как там что-то мигает, а затем получается такая картина:
При трассировке можно увидеть, что вверху экрана происходит последовательное отображение того, что собственно должно растекаться по всей его высоте.
После долгих экспериментов я доказал, что проблемный код сосредоточен в теле функции.
DEMO_FlushDisplay() :
Там явно указаны координаты вывода, и вывод всегда происходит с координатами 0,0. Почему? После долгих экспериментов я сделал следующее предположение.
Вот формат команды COLADDR из документации контроллера ILI9488:
Обратите внимание, что аргументы передаются через младший байт, а биты 23:8 помечаются как XX.
Но из того же документа видно, что при использовании SPI это не важно.
Теперь посмотрим на схему отображения от WaveShare. И мы видим, что данные проходят через цикл параллельно.
Да Да! Точно! Именно параллельно! Но они этого не скрывают, схема находится в свободном доступе! Вот разъем для кабеля:
Все приводится в параллельную форму с помощью двух сдвиговых регистров:
А стробы формируются счетчиком и прочей логикой:
Когда мы отправляем данные с использованием DMA, сигнал CS не сбрасывается на протяжении всей передачи.
Есть мнение (я не вникал в детали столь трудночитаемой схемы, поэтому просто мнение), что если сбросить сигнал CS после восьми бит, то восьмибитное сообщение будет потеряно.
В противном случае – шестнадцатибитный.
В результате в нашей функции вместо четырех байтов с координатами в физический цикл пойдет два 16-битных слова, координаты в которых будут отформатированы некорректно.
Зная эту теорию, перепишем проблемную функцию следующим образом: static void DEMO_FlushDisplay(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t *color_p)
{
uint8_t data[4];
const uint8_t *pdata = (const uint8_t *)color_p;
uint32_t send_size = (x2 - x1 + 1) * (y2 - y1 + 1) * LCD_FB_BYTE_PER_PIXEL;
/*Column addresses*/
DEMO_SPI_LCD_WriteCmd(ILI9341_CMD_COLADDR);
DEMO_SPI_LCD_WriteData (x1 >> 8);
DEMO_SPI_LCD_WriteData (x1);
DEMO_SPI_LCD_WriteData (x2 >> 8);
DEMO_SPI_LCD_WriteData (x2);
/*Page addresses*/
DEMO_SPI_LCD_WriteCmd(ILI9341_CMD_PAGEADDR);
DEMO_SPI_LCD_WriteData (y1 >> 8);
DEMO_SPI_LCD_WriteData (y1);
DEMO_SPI_LCD_WriteData (y2 >> 8);
DEMO_SPI_LCD_WriteData (y2);
/*Memory write*/
DEMO_SPI_LCD_WriteCmd(ILI9341_CMD_GRAM);
DEMO_SPI_LCD_WriteMultiData(pdata, send_size);
lv_flush_ready();
}
И вот результат:
Шаг 4. Исправьте полярность часов и цвета
Что меня действительно смущает в картинке, так это цвета.Более того, когда я начал писать демо для проекта All-Hardware, оно оказалось несколько дальтоником.
Я выбрала синий или зеленый цвета – все было хорошо.
И если вы выбрали красный, вы получили черный.
Какая ерунда! Но я уже привык решать проблему с цветами не аналитически, а экспериментально.
Таблицу настройки я всегда вывожу на экран.
Имеет значение опыт работы на телевизионном заводе.
В целевой библиотеке функция DEMO_FlushDisplay() получает на вход блоки шириной экрана и высотой 40 строк.
Я обнаружил это, прослеживая его на третьем этапе.
Ну и замечательно.
Вставим туда код, который перезаписывает буфер в работающий модуль.
Сделаем на экране 16 вертикальных полос.
Первый будет иметь цвет 0x0001, второй — 0x0002, третий — 0x0004. И так далее — до 0x8000. И давайте посмотрим, какие реальные цвета это даст. Вот код, реализующий эту модификацию, встроенный в функцию: {
uint16_t *pdata16 = (uint16_t *)color_p;
int i;
uint32_t send_size16 = (x2 - x1 + 1) * (y2 - y1 + 1);
int wordsPerColor = (x2 - x1 + 1) / 16;
for (i=0;i<send_size16;i++)
{
Теги: #Программирование микроконтроллеров #Компьютерное оборудование #Системное программирование #Все-железо #Плата LPC55S69-EVK
-
Минибар Из Старого Телевизора
19 Oct, 24 -
Pycon Ukraine (23-24 Октября, Киев)
19 Oct, 24 -
Британнику Теперь Можно Редактировать.
19 Oct, 24