Читатель нашего блога Михаил (mishadesh) создал отличную библиотеку для работы с LCD и предложил написать статью для демонстрации ее возможностей. Собственно, сегодня именно об этом и пойдет речь 😉 Разберем, какие реализованы функции, а также в конце статьи будет выложен пример для работы с дисплеем.
Как обычно начнем с обсуждения железа… А тут на самом деле и не о чем говорить. Как и в первой статье, посвященной работе с дисплеями (ссылка), мы будем использовать отладочную плату Mini STM32. Собственно, подключение дисплея, основные команды для записи данных, последовательность инструкций для инициализации – все это там есть =) Поэтому сейчас переходим сразу к обсуждению библиотеки для работы с графическими дисплеями.
Вот полный список функций с пояснениями:
void LCD_PutPixel( uint16_t x, uint16_t y, uint16_t color ); |
Функция принимает 3 аргумента – координаты точки (x и y), а также цвет и меняет цвет пикселя, расположенного по указанным координатам.
void LCD_SetOrient( TOrientation orientation ); |
Следующая функция, как видно из ее названия, меняет ориентацию экрана. Возможно два положения экрана, соответственно два возможных значения параметра orientation:
Идем дальше:
uint16_t LCD_GetMaxX ( void ); uint16_t LCD_GetMaxY ( void ); |
Две похожие функции – одна возвращает максимальное значение координаты по оси x, другая – по оси y. Функции учитывают текущее положение экрана.
Переходим к функциям рисования 😉 И первая на очереди:
uint8_t LCD_DrawChar(uint16_t x, uint16_t y, uint8_t c, uint16_t textColor, uint16_t backColor, uint8_t isTransparent); |
Функция отрисовывает на графическом дисплее символ, располагая его по переданным в функцию координатам, а также задавая его цвет. Начертание символа соответствует шрифту, определенному в файле font.c (файл идет в составе библиотеки).
Из функции LCD_DrawChar() плавно вытекает следующая функция:
void LCD_DrawString(char* s, uint16_t x, uint16_t y, uint16_t color, uint16_t backColor, uint8_t isTransparent); |
Тут понятно и без лишних слов 😉 Функция печатает на LCD строку текста. Базой для этой функции является предыдущая – LCD_DrawChar().
Помимо символов и текста, конечно же, необходимо иметь возможность нарисовать основные графические примитивы, например линию или круг. Для этого реализовано следующее:
void LCD_drawLine ( int x1, int y1, int x2, int y2, uint16_t color); void LCD_DrawRect ( int x1, int y1, int x2, int y2, uint16_t color, uint8_t filled ); void LCD_DrawEllipse(uint16_t X1, uint16_t Y1, uint16_t R, uint16_t color); |
Для рисования линии нужно передать в функцию координаты начальной точки, координаты конечной точки, а также нужный цвет. Для прямоугольника – координаты верхнего левого угла и координаты правого нижнего угла (!). Последний параметр filled – определяет, необходимо ли выполнять заливку фигуры. Единица – значит да, фигура будет закрашена выбранным цветом, ноль – будет нарисован только контур фигуры. С этим понятно ) Осталась только окружность – функция DrawEllipse(). Здесь вместо координат начала и конца (верхнего/нижнего углов) передаем в качестве аргументов центр окружности и радиус.
Ну и напоследок еще одна функция:
void LCD_FillScr(uint16_t color); |
Функция позволяет залить экран сплошным цветом.
Все перечисленные функции реализованы в файле GUI_DRV.c.
Помимо них библиотека включает в себя функции для записи данных в дисплей ( LCD_DRIVER.c) а также уже упомянутые шрифты (font.c). Как видите, все четко отсортировано по разным файлам, так что в принципе все очень понятно, поэтому давайте перейдем к практическому примеру!
Вот ссылка на полный проект с примером – LCD Example
Давайте разбираться! Идем в файл main.c… Не буду приводить полный код функций инициализации периферии и дисплея, все это можно посмотреть непосредственно в файле, либо в предыдущей статье, ссылка на которую была в начале этой статьи 😉 Функция main():
int main(void) { initPeriph(); initFSMC(); initLCD(); delay(10000); LCD_FillScr(0xFFFF); delay(100); LCD_SetOrient(Orientation_Album); delay(100); LCD_DrawString("Библиотека для LGDP4532",30,30,0x888F,0x0000,0); LCD_DrawRect(100,100,200,200,0x0000,0); LCD_DrawRect(120,120,180,180,0xFF00,1); LCD_DrawEllipse(150,150,50,0xF000); while (1) { } } |
Начинаем с инициализации, закрашиваем экран белым цветом и устанавливаем альбомную ориентацию экрана. И теперь переходим к отрисовке графики )
Выводим на экран строку, а также два прямоугольника и круг. Результат налицо:
Очевидно, что все работает отлично 😉
Итак, на этом на сегодня заканчиваем, огромное спасибо Михаилу за проделанную работу и приведенные материалы. Вот контакты автора библиотеки:
Skype – mishadesh
Mail – [email protected]
На этом все, спасибо за внимание, до скорых встреч!
microtechnics.ru
Одним из средств представления информации в удобном пользователю виде является TFT дисплей. Мне в руки попался модуль TFT экрана на базе контроллера ST7735.Внешний вид модуля представлен на изображении ниже:
Описание выводов модуля дисплея:
• VCC,GND — Линии питания дисплея. VCC необходимо подключить к + источника питания (3.3-5В), GND к земле.
• LED — Вывод подсветки микроконтроллера, можно подключить напрямую к VCC.
• SCK — Тактовый вывод контроллера. Подключать к соответствующему выводу SCK микроконтроллера.
• SDA — Вывод данных контроллера. Подключать к выводу MOSI микроконтроллера.
• CS — Вывод выбора контроллера. Подключать к выводу CS микроконтроллера
• RESET — Вывод сброса контроллера. Подключать к любому выводу микроконтроллера.
• A0 — Вывод выбора команд/данных контроллера. Подключать к любому выводу контроллера. По логическому уровню на данном выводе,контроллер дисплея понимает что ему передается данные или команда.
Для управления модулем необходимо инициализировать SPI микроконтроллера, выполнить сброс дисплея, далее необходимо послать модулю команды инициализации(такие как программный reset, выбор гаммы цветов, включение дисплея и.т.д.), и лишь потом можно подавать ему команды и данные для вывода изображения.
Передача данных и команд контроллеру ST7735, ведется по линии SDA.Перед этим необходимо выставить соответствующий логический уровень на выводе A0,который будет означать, передаем ли мы данные или команду. Вывод изображения на дисплей ведется путем записи цвета в память дисплея.Цвет передается 2 байтами, и кодируется по формату 565.Файл с описанием основных цветов можете скачать ЗДЕСЬ.
Основными командами используемыми при работе с контроллером являются команды CASET,RASET и RAMWR.
При помощи команд CASET и RASET можно задать область отрисовки. То есть, мы выбираем ту часть дисплея в которую последовательно будем выводить пиксели изображения.
Команда RAMWR является командой записи в ОЗУ контроллера дисплея.Данную команду необходимо передавать перед началом передачи кодов цветов дисплею.
Перечень и описание всех команд можете смотреть в описании контроллера, расположенное ЗДЕСЬ
Решая задачу подключения данного модуля к микроконтроллеру STM32, была разработана библиотека предоставляющая все функции для быстрой и удобной работы с дисплеем. Проверена и отлажена она на контроллере STM32F103C8T6. Приводить её полный текст не вижу особого смысла, поэтому объясню как начать с ней работу,и описание основных функций.
Первым делом открываем файл st7735.h. В нем определены основные параметры библиотеки. Рассмотрим самые необходимые:
LCD_SPI — Номер SPI по которому будет производится обмен данными с дисплеем
CS_LOW,CS_HIGH — Дефайны устанавливающие низкий и высокий уровень линии CS соответственно. При смене CS вывода(если вы будете использовать другую ножку контроллера) следует изменить эти дефайны для обнуления и установки выбранной вами ножки.
Теперь перейдем к файлу st7735.c.
Первым делом при смене выводов контроллера которые будут оперировать дисплеем редактируйте функцию ST7735_Init в которой инициализируется вся периферия которая будет участвовать в работе с дисплеем. Остальные функции в редактировании не нуждаются (В большинстве случаев).
Опишу все функции библиотеки:
• ST7735_write — передает байт по SPI к дисплею, и ждет сброса флага BSY
• ST7735_Send — передает байт по SPI к дисплею, без ожидания сброса флагу BSY.Используется для передачи большого объема данных к дисплею
• ST7735_SendData — Устанавливает 1 на выводе A0 дисплея, тем самым сообщая ему о том что далее будут следовать данные, и передает байт данных при помощи функции ST7735_write
Следующие 3 функции используются для работы с контроллером ПДП(Почитать о нем можете ЗДЕСЬ). И последние 3 функции используются для организации временных задержек.
Данная библиотека является свободнораспостраняемой. Однако её публикация на других сайтах, а так же использование в коммерческих проектах возможно только с письменного разрешения администрации сайта MKPROG.RU.Помните что ваши неправомерные действия могут преследоваться по закону.
Скачать библиотеку.
В следующей статье будет приведен пример работы с данной библиотекой.
Все ваши замечания и предложения оставляйте в комментариях! Спасибо за внимание!
mkprog.ru
Пришло время обсудить замечательную плюшку микроконтроллеров STM32 – а именно модуль FSMC. Это практически незаменимая вещь при работе с внешней памятью, либо, например, с графическим дисплеем. Собственно, с дисплеем то мы и будем играться, разбираясь с FSMC.
Но для начала, как обычно, немного теории. Итак, FSMC реализует параллельный интерфейс обмена данными между различными устройствами. Короче говоря — просто параллельная шина 😉 Используя FSMC при работе с внешней памятью, мы получаем возможность включить внешнюю память в адресное пространство микроконтроллера. Что это дает? А то, что обращение к внешней памяти значительно упрощается — необходимо просто обращаться к ОЗУ микроконтроллера по определенным заданным адресам. То есть все ритуальные танцы с временными диаграммами, таймингами и прочим модуль FSMC берет на себя. Мы просто пишем данные по адресу — а FSMC дергает линии данных, полностью осуществляя непосредственную работу с подключаемым устройством.
Схожим образом работает все это дело и при подключении дисплеев. Но все-таки тут все несколько иначе. Пусть у нас есть дисплей, у которого есть следующие выводы:
DB[17:0] – 18 линий для передачи данных (не забываем, что тут у нас параллельная передача данных, а не последовательная)
Также есть выводы для разрешения записи/чтения — туда мы должны выдавать строб-импульсы в определенной последовательности
Кроме того, у дисплея есть выводы chip select’а, reset, полно всего короче )
И всем этим хозяйством нужно рулить ) Вот тут то нам и поможет FSMC. И не просто поможет, а всю работу возьмет на себя! Итак, пусть мы уже подключили дисплей как надо, написали программу для FSMC STM32. Как же нам обращаться с дисплеем то? А опять все очень просто. Точно также, как с внешней памятью, мы будем всего лишь пихать байты по определенному адресу. А FSMC будет в это время лихорадочно дергать линиями данных, следить за временными интервалами, передавать все остальные нужные дисплею сигналы. А чтобы разделить передачу данных и команд мы должны записывать байты по разным адресам. И тут же встает вопрос – а какие именно адреса, и чем они определяются.
У дисплея есть вывод для выбора — данные/команда. То есть по состоянию этого вывода дисплей решает, что именно сейчас к нему прилетит. А у FSMC есть шина адреса и шина данных. Так вот этот вывод дисплея подключается к какому-нибудь пину шины адреса. И если мы подключили его, например, к 16 выводу шины адреса, то записав какой-нибудь байт по любому(!) адресу с нулевым 16 битом мы подадим дисплею команду. Вот, смотрите, пример небольшой.
Пусть как уже решили подключен 16 бит шины адреса. Берем адрес из доступных FSMC – 0x60000000. Видим, что в этом значении 16 бит равен нулю, а значит, когда мы запишем значение по этому адресу, FSMC подаст дисплею сигнал на запись, также сообщит дисплею, что сейчас будет команда, ну и, конечно же, выдаст на шину сами данные. А если мы запишем значение по адресу 0х60010000 (16 бит — единица!) то FSMC все разрулит и передаст дисплею данные. Вот так все просто )
Кстати, очень важный момент. В 16-битном режиме работы FSMC 16 бит шины адреса соответствует 17-му биту адреса (то есть 0х60020000). Ну естественно, все остальные адреса также смещены на 1 бит в этом режиме.
По идее работа с дисплеем и с внешней памятью с точки зрения программиста выглядит одинаково, но на деле все не так. При работе с дисплеем никакая память никуда не «проецируется», мы условно пишем данные по адресам, но это всего лишь дает FSMC сигнал о том, что пора начинать действовать 😉
Давайте потихоньку переходить к делу.
Для работы с FSMC будем по традиции использовать Standard Peripheral Library. Там все аналогично любой другой периферии, разве что настроек побольше ) Так что об этом не будем особо разговаривать — лучше на практике при написании программы посмотрим как и что там настраивается.
Что у нас в плане железа…
Испытывать FSMC я буду при помощи платы MiniSTM32 (про нее была уже статейка). Там уже установлен дисплей, так что никаких лишних телодвижений не потребуется. Вот как дисплей подключен:
Но вообще есть серьезные опасения, что китайцы нарисовали схему коряво, поскольку тут можно найти явно абсурдные вещи )
Вообще, если посмотреть на распиновку конкретного дисплея и на выводы FSMC микроконтроллера, то там очень хорошо видно, что они довольно точно соответствуют друг другу.
Итак, что же будем делать то в качестве примера…Давайте разберемся как окрашивать дисплей в определенный цвет. Сначала зальем экран красным, потом зеленым и потом синим (RGB). Короче получить мы должны мигающий дисплей, на котором сменяют друг друга три цвета 😉
Данные будем передавать в 16-битном режиме.
Красному цвету соответствует – 111111 000000 000000
Зеленому – 000000 111111 000000
Синему – 000000 000000 111111
Как мы тут видим на 18 бит цвета приходятся 16 бит данных, в итоге получаем следующее:
Для красного – 0xF800
Для зеленого – 0x07E0
Для синего – 0x001F
Все очень просто, давайте напишем программу. Сразу скажу, шаманская инициализация дисплея взята из кошмарных китайских примеров программ, которые шли вместе с платой 😉
/*******************************************************************/ #include "stm32f10x_gpio.h" #include "stm32f10x_fsmc.h" #include "stm32f10x_rcc.h" #include "stm32f10x.h" /*******************************************************************/ // Определяем адреса, по которым будем записывать данные // Для записи данных #define LCD_DATA ((uint32_t)0x60020000) // Для записи команд #define LCD_REG ((uint32_t)0x60000000) /*******************************************************************/ // Простенькая функция задержки void delay(uint32_t delayTime) { uint32_t i; for(i = 0; i < delayTime; i++); } /*******************************************************************/ // Так мы будем писать команды в регистры LCD void writeLCDCommand(unsigned int reg,unsigned int value) { *(uint16_t *) (LCD_REG) = reg; *(uint16_t *) (LCD_DATA) = value; } /*******************************************************************/ // А так данные.. void writeLCDData(unsigned int data) { *(uint16_t *) (LCD_DATA)= data; } /*******************************************************************/ void initAll() { FSMC_NORSRAMInitTypeDef fsmc; FSMC_NORSRAMTimingInitTypeDef fsmcTiming; GPIO_InitTypeDef gpio; // Включаем тактирование портов RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE , ENABLE); // И тактирование FSMC RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE); // Инициализация пинов, задейстованных в общении по FSMC gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_14 | GPIO_Pin_15; gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOD, &gpio); gpio.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOE, &gpio); gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOD, &gpio); // Здесь у нас Reset gpio.GPIO_Pin = GPIO_Pin_1 ; GPIO_Init(GPIOE, &gpio); // CS gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Pin = GPIO_Pin_7; GPIO_Init(GPIOD, &gpio); // RS gpio.GPIO_Pin = GPIO_Pin_11 ; GPIO_Init(GPIOD, &gpio); // CS -> 1 // Reset -> 0 // RD -> 1 // RW -> 1 GPIO_SetBits(GPIOD, GPIO_Pin_7); GPIO_ResetBits(GPIOE, GPIO_Pin_1); GPIO_SetBits(GPIOD, GPIO_Pin_4); GPIO_SetBits(GPIOD, GPIO_Pin_5); // Настройка FSMC fsmcTiming.FSMC_AddressSetupTime = 0x02; fsmcTiming.FSMC_AddressHoldTime = 0x00; fsmcTiming.FSMC_DataSetupTime = 0x05; fsmcTiming.FSMC_BusTurnAroundDuration = 0x00; fsmcTiming.FSMC_CLKDivision = 0x00; fsmcTiming.FSMC_DataLatency = 0x00; fsmcTiming.FSMC_AccessMode = FSMC_AccessMode_B; fsmc.FSMC_Bank = FSMC_Bank1_NORSRAM1; fsmc.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; fsmc.FSMC_MemoryType = FSMC_MemoryType_NOR; fsmc.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b; fsmc.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable; fsmc.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low; fsmc.FSMC_WrapMode = FSMC_WrapMode_Disable; fsmc.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState; fsmc.FSMC_WriteOperation = FSMC_WriteOperation_Enable; fsmc.FSMC_WaitSignal = FSMC_WaitSignal_Disable; fsmc.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable; fsmc.FSMC_WriteBurst = FSMC_WriteBurst_Disable; fsmc.FSMC_ReadWriteTimingStruct = &fsmcTiming; fsmc.FSMC_WriteTimingStruct = &fsmcTiming; FSMC_NORSRAMInit(&fsmc); FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); } /*******************************************************************/ void initLCD() { // Глобальный Reset дисплея GPIO_ResetBits(GPIOE, GPIO_Pin_1); delay(0x0FFFFF); GPIO_SetBits(GPIOE, GPIO_Pin_1 ); delay(0x0FFFFF); // Пляски с бубном от китайских товарищей writeLCDCommand(0x0000,0x0001); delay(10); writeLCDCommand(0x0015,0x0030); writeLCDCommand(0x0011,0x0040); writeLCDCommand(0x0010,0x1628); writeLCDCommand(0x0012,0x0000); writeLCDCommand(0x0013,0x104d); delay(10); writeLCDCommand(0x0012,0x0010); delay(10); writeLCDCommand(0x0010,0x2620); writeLCDCommand(0x0013,0x344d); delay(10); writeLCDCommand(0x0001,0x0100); writeLCDCommand(0x0002,0x0300); writeLCDCommand(0x0003,0x1030); writeLCDCommand(0x0008,0x0604); writeLCDCommand(0x0009,0x0000); writeLCDCommand(0x000A,0x0008); writeLCDCommand(0x0041,0x0002); writeLCDCommand(0x0060,0x2700); writeLCDCommand(0x0061,0x0001); writeLCDCommand(0x0090,0x0182); writeLCDCommand(0x0093,0x0001); writeLCDCommand(0x00a3,0x0010); delay(10); // Настройки гаммы writeLCDCommand(0x30,0x0000); writeLCDCommand(0x31,0x0502); writeLCDCommand(0x32,0x0307); writeLCDCommand(0x33,0x0305); writeLCDCommand(0x34,0x0004); writeLCDCommand(0x35,0x0402); writeLCDCommand(0x36,0x0707); writeLCDCommand(0x37,0x0503); writeLCDCommand(0x38,0x1505); writeLCDCommand(0x39,0x1505); delay(10); // Включение дисплея writeLCDCommand(0x0007,0x0001); delay(10); writeLCDCommand(0x0007,0x0021); writeLCDCommand(0x0007,0x0023); delay(10); writeLCDCommand(0x0007,0x0033); delay(10); writeLCDCommand(0x0007,0x0133); } /*******************************************************************/ int main() { initAll(); initLCD(); while(1) { int i; // Начальный и конечный адреса по горизонтали writeLCDCommand(0x0050, 0); writeLCDCommand(0x0051, 239); // Начальный и конечный адреса по вертикали writeLCDCommand(0x0052, 0); writeLCDCommand(0x0053, 319); writeLCDCommand(32, 0); writeLCDCommand(33, 0); *(uint16_t *) (LCD_REG) = 34; // Красный for (i = 0; i < 76800; i++) { writeLCDData(0xF800); } delay(0x0FFFFF); // Зеленый for (i = 0; i < 76800; i++) { writeLCDData(0x07E0); } delay(0x0FFFFF); //Синий for (i = 0; i < 76800; i++) { writeLCDData(0x001F); } delay(0x0FFFFF); } } /*******************************************************************/ |
Магическое число 76800 – количество точек дисплея. Все остальное вроде бы понятно, с настройками FSMC тоже ясно, как всегда в SPL все поля структуры названы логично и адекватно их функции.
После прошивки программы в контроллер дисплей начинает исправно подмигивать. Чего, собственно, и добивались, так что на этом все, скоро попробуем залить в дисплей какое-нибудь изображение )
microtechnics.ru
Поскольку телевизор был уже разобран, и пользы от него не было не было ни какой, я решил попробовать подключить к нему контроллер.
Как подключить модуль LCD с большим разрешением и без видеопамяти к контроллеру — под катом.
Электроника телевизора состоит из 2 плат — на одной расположены видео-АЦП, видеопроцессор, тюнер, DC-DC и прочая обвязка. Эта плата нам не нужна. Вторая плата с маркировкой CPWBX0043TPZZ в данном случае важнее — на ней расположен контроллер LCD и инвертор для питания ламп подсветки. В гугле встречал упоминания, что точно такую же плату можно найти в портативном DVD плеере. Контроллером дисплея является микросхема LZ9JG17 (в даташите эта микросхема названа Timing Control IC). ВАЖНО: интерфейс у LCD модуля на данной микросхеме 18-bit RGB, и у данного LCD модуля нет встроенной видеопамяти.
Опишу поподробней работу данного интерфейса. Данные передаются в модуль по параллельной 18-битной шине(по 6 бит на цвет), по каждому тактовому сигналу на экране отображается новый пиксель, цвет которого соответствует коду на шине данных. Для того, чтобы пиксели отображались на экране в нужных местах, используются сигналы горизонтальной(строчной) и вертикальной (кадровой) синхронизации.
Перед началом передачи новой строки необходимо установить нужный уровень на линии горизонтальной синхронизации, послать определенное число тактовых импульсов, изменить уровень, и отправить еще определенное число тактовых импульсов, только после чего можно отправлять данные.
Вид уровня и количество тактов берутся из даташита на контроллер дисплея.
Ниже приведена временная диаграмма сигналов для LZ9JG17: clk — тактовый, HSY — горизонтальной синхронизации, DATA — данных.
По какой-то причине в реальности 216 тактовых импульсов отсчитывались от 2 фронта импульса HSY. Для правильной работы индикатора нужно выводить данные о всех 800 видимых пикселях — иначе нормального изображения не добиться.
С импульсом вертикальной синхронизации все похоже — для начала формирования нового кадра нужно установить на линии вертикальной синхронизации нужный уровень, отправить на индикатор определенное число линий, изменить уровень, отправить еще определенное число линий, и только после этого можно передавать данные, которые будут реально отображаться на экране. Частота импульсов VSY — это частота обновления изображения.
Ниже приведена временная диаграмма сигналов для LZ9JG17: VSY — вертикальной синхронизации, HSY — горизонтальной синхронизации, DATA — данных.
Поскольку видеопамяти у модуля LCD нет, то данные на индикатор нужно передавать постоянно. Как оказалось, для нормальной работы индикатора нужно обновлять изображение с частотой не менее 15 гц.
Микроконтроллер способен справиться с этой задачей, однако для достижения необходимой скорости передачи данных и формирования тактового сигнала нужно использовать SPI, тактовый сигнал при этом берется с вывода SCK, а данные — c MOSI. Поскольку вывод MOSI только один, то изображение возможно формировать только черно-белое, все входы данных модуля нужно запараллелить и соединить с MOSI. В данном случае, при передаче одного байта через SPI на экране будут обновляться 8 пикселей. Частота передачи данных через SPI может доходить до половины тактовой частоты контроллера. Таким образом, для управления модулем LCD требуется всего 4 линии — clk, DATA, VSY, HSY.
У контроллера дисплея есть и другие входы, но, к счастью, их использовать не надо — они отвечают за экзотические настройки контроллера, вроде зеркального отображения изображения, и подтянуты в контроллере к нужным уровням так, что модуль работает в наиболее часто используемом режиме.
Для хранения одного кадка потребуется (800*480)/8 = 48000 байт. К сожалению, у контроллера STM32-DISCOVERY всего 8 килобайт ОЗУ. Поэтому на DISCOVERY возможно формировать только текст. При размере знакоместа 8×16 на экране можно расположить 100×30 знакомест — то потребует 3000 байт ОЗУ.
По поводу подключения в модулю. Модуль LCD соединялся с основной платой телевизора через 40-пиновй шлейф, к выводам которого не подпаятся. Однако рядом с разъемом шлейфа на плате модуля имеются круглые тестовые площадки достаточно большого размера, чтобы к ним можно было припаять провод. Поскольку даташит на контроллер LCD у меня был, то прозвонив мультиметром выводы LZ9JG17 и тестовые площадки, я определил назначение тестовых площадок модуля. С цепями питания еще проще — при подключенной плате телевизора измерил напряжения на наиболее толстых дорожках рядом с разъемом, и определил, что где. Модуль требует 5В 0.1А для питания основных цепей и 12В 0.8А для инвертора. Напряжение сигналов, подаваемых на модуль — 3.3В, так как LZ9JG17 питается именно от 3.3 В. Источник 3.3 вольт есть на самом модуле. Сигнал управления на инвертор подается с еще одного вывода разъема, для того включить подсветку, нужно подать на него 3.3В.
Фотография участка модуля LCD с распиновкой.
Подписи соответствуют подписям выводов LZ9JG17 в даташите. Площадки HENAB, HRVC, HRVC остаются не подключенными.
Что интересно, диод на плате установили китайцы — когда я в первый раз разбирал телевизор, отверстия под винты были закрыты заводской наклейкой.
Вывод VSY модуля у меня соединяется с выводом PC5 платы DISCOVERY, HSY — с PC4, clk — с PA5(SPI_SCK), запараллеленные входы данных — с PA7(SPI_MOSI).
Опыт показал, что вывод clk нужно соединить с землей через конденсатор 18пФ, иначе возникают артефакты изображения.
Фотография конструкции в сборе:
Теперь о программе. Она написана в IAR. Информация у меня передается в SPI через DMA. Используется двойная буферизация — в то время, пока данные из одного буфера передаются на индикатор, другой буфер заполняется данными программой.
При вызове функции start_line() на линии HSY устанавливается высокий уровень, в SPI два раза отправляется байт, что дает 16 тактовых импульсов, после чего на HSY устанавливается низкий уровень. Горизонтальный импульс синхронизации сформирован. Остается отправить на индикатор 216 тактовых импульсов, что соответствует 27 байтам, после чего отправить 100 байт данных. Для этого DMA конфигурируется на передачу 127 байт; адрес, откуда DMA будет брать данные, определяется как адрес нулевого элемента нужного буфера минус 27. После конфигурирования DMA запускается, при этом устанавливается флаг разрешения заполнения нового буфера. Буфер должен быть заполнен раньше, чем DMA закончит передавать данные, поэтому оптимизация в компиляторе должна быть включена.
По завершению передачи DMA создает прерывание. Обработчик прерывания вновь вызывает функцию start_line(), после чего начинается передача новой линии. Если все линии переданы, то start_line() не вызывается, и устанавливается флаг окончания передачи кадра.
Формирование вертикального импульса синхронизации аналогично формированию горизонтального. При вызове функции lcd_new_frame() линии VSY устанавливается высокий уровень, вызывается функция start_line(), после чего программа ожидает завершения передачи линии через DMA. Эта оперция повторяется 6 раз, после чего на VSY устанавливается низкий уровень. После вызова start_line() линии начинают передаваться автоматически и функция lcd_new_frame() завершается. Вызывается lcd_new_frame() программно, после того как программа обнаруживает, что все линии переданы.
Фотография готового устройства:
Работает LCD модуль стабильно, мерцание экрана не заметно. В принципе, подобным образом к DISCOVERY можно подключить любой экран с RGB интерфейсом, лишь бы был даташит на контроллер LCD и индикатор мог работать на соответствующей частоте развертки(у меня 17 Гц).
Позже хочу подключить модуль к более мощному контроллеру с 64 Кбайт памяти, что позволит организовать настоящую видеопамять и выводить на экран любую графику, и сделать что-то вроде настенного календаря-будильника с wifi, что позволит показывать прогноз погоды из интернета, синхронизировать данные будильника с компьютером, показывать заметки, сделанные на компьютере. Эдакий шаг к умному дому.
we.easyelectronics.ru
Всем доброго дня!
Сегодня мы продолжим обсуждать работу с графическим дисплеем и на очереди у нас touchscreen.
В этой статье я не буду рассказывать про устройство дисплея и про то, как работает контроллер touchscreen’а. Сегодня рассмотрим библиотеку для работы с ним, а также конкретный пример, чтобы увидеть результат наших трудов. В общем, данная статья будет продолжением предыдущей: вот она.
Итак, для обмена данными с контроллером дисплея мы будем использовать интерфейс SPI, соответственно, необходимо его проинициализировать:
void TouchInit(void) { GPIO_InitTypeDef GPIO_InitStruct; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init (GPIOA, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init (GPIOA, &GPIO_InitStruct); RCC_APB2PeriphClockCmd (RCC_APB2Periph_SPI1, ENABLE); SPI_Cmd (SPI1, DISABLE); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init (SPI1, &SPI_InitStructure); SPI_CalculateCRC(SPI1, DISABLE); SPI_Cmd (SPI1, ENABLE); SPI_SSOutputCmd (SPI1, DISABLE); SPI_NSSInternalSoftwareConfig (SPI1, SPI_NSSInternalSoft_Set); GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_Pin = TOUCH_CS_PIN; GPIO_Init(TOUCH_CS_PORT, &GPIO_InitStruct); T_DCS(); GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6; GPIO_Init (GPIOB, &GPIO_InitStruct); diffX = diffY = 0; dispSizeX = dispSizeY = 0; } |
Как видите за всю конфигурацию в библиотеке touchscreen’а отвечает функция TouchInit(). В принципе тут все понятно, настраиваем нужные выводы микроконтроллера, включаем тактирование, ну и, конечно, задаем параметры обмена данными по SPI.
Но на этом подготовка к работе с дисплеем не заканчивается. Вторым шагом является калибровка параметров touchscreen’а. За это отвечает функция TouchCalibrate(). Ее полный код я приводить не буду, ссылка на скачивание полной библиотеки и готового примера будет в конце статьи, все можно будет увидеть в исходниках 😉
Итак, все настроено и готово к работе!
Осталось обсудить основную функцию, с помощью которой мы и будем работать с LCD. И это функция:
bool TouchReadXY (uint16_t *px, uint16_t *py, bool isReadCorrected); |
Эта функция позволит нам узнать координаты точки касания дисплея, с которыми мы уже в дальнейшем будем работать. Для получения этих данных необходимо передать в функцию адреса переменных, в которых будут сохранены координаты x и y.
С обсуждением на этом заканчиваем, давайте переходить к практическому примеру. Вот полный код функции main():
int main(void) { initPeriph(); initFSMC(); initLCD(); delay(10000); LCD_FillScr(0xFFFF); delay(100); LCD_SetOrient(Orientation_Portrait); delay(100); TouchInit(); TouchSetScreenSize(240,320); TouchCalibrate(); LCD_FillScr(0xFFFF); while (1) { TouchReadXY(&X,&Y,true); LCD_PutPixel(X,Y,0x0000); } } |
Давайте разберемся, что тут происходит.
Для начала вызываем функции инициализации дисплея, которые мы обсуждали в предыдущей статье. Следом за ними функции конфигурации touchscreen’а. Кроме того, вызываем функцию TouchSetScreenSize() с параметрами, соответствующими размерам нашего дисплея. Эти данные нужны для работы драйвера touchscreen’а. А в бесконечном цикле реализуем что-то вроде рисовалки – считываем координаты касания и в этом месте рисуем точку. Таким образом, проводя пальцем по дисплею можно рисовать линии, фигуры итд.
Результат работы нашей программы:
На этом на сегодня заканчиваем, еще раз большое спасибо mishadesh за материалы и за работу над графической библиотекой!
В завершение как и обещал привожу проект для работы с touchscreen’ом, а также напоминаю контакты автора библиотеки =)
Проект – Touch_Project
Skype — mishadesh
Mail – [email protected]
microtechnics.ru
Зачастую контроллеру приходится сообщать пользователю какие-либо данные, и далеко не всегда можно обойтись одними лишь цифрами. Для отображения текстовой информации как правило применяют LCD индикаторы на базе контроллера HD44780. Не смотря на то, что подобный дисплеев очень много все они управляются одинаково так как контроллер в них стоит один и соответственно система команд у них одна, так же они имеют одинаковый набор ног. В этой статье мы попробуем подключить такой дисплей к контроллеру установленному на STM32vl Discovery. Сам дисплей выглядит следующим образом:
Прежде чем писать программу которая будет рулить дисплеем нам потребуется разобраться какие у него есть выводы. Их не так много:
DB0…DB7 — Выводы через которые контроллер получает/передаёт данные и командыE — Строб. Когда на всех линиях данных установлены нужные логические уровни, необходимо кратковременно подать на него логическую единицу, а потом снова ноль. Только после такого дёрганья ногой дисплей считает данные (или команду) с ножек DB0…DB7
RW — Состояние этой ноги сообщает дисплею, что мы хотим с ним делать: 1 — читать из дисплея, 0 писать в дисплей. Вообще чтение из дисплея — вещь совсем не нужная на мой взгляд, именно поэтому я никогда не подключаю этот вывод к контроллеру а просто сажаю его на землю. Таким образом режим записи в дисплей включен постоянно.
RS — Управляя состоянием этой ноги — мы сообщаем дисплею что хотим ему передать: Если ноль то команда, если единица то данные.V0 — Вывод управления контрастностью дисплея. обычно сюда подключают переменный резистор (как делитель напряжения) и вращая его настраивают необходимую контрастность. Если его не подключить, то изображение (в 99%) не появится вообще. Хотя у меня был один дисплей который не требовал подключения такого резистора. VSS — ну тут думаю всё понятно, земля. VCC — напряжение питания, обычно 5 вольт.A — анод светодиодной подсветки.K — катод светодиодной подсветки.Иногда подсветка уже имеет токоограничивающий резистор, и можно подключать эти два вывода напрямую к питанию, а иногда резистора нет и его нужно добавить. В противном случае подсветка может испустить тот самый волшебный дымок. Выводов многовато и почти все из них должны управляться нашим контроллером. Таким образом мы должны задействовать аж 10 ног! (DB0…DB7, RS и E). В принципе у STM32 нет такой большой проблемы со свободными ногами, как например у AVR или PIC, но всё равно жалко их тратить, да и куча проводов от дисплея это не гуд. Я веду к тому, что есть возможность так же полноценно управлять дисплеем используя всего четыре линии данных (DB4…DB7) вместо восьми. Поэтому, в данной статье будет рассмотрен именно этот режим работы. Но сначала много теории без которой мы далеко не уйдем. Я конечно понимаю, что на эту тему было написано достаточно кода и мануалов, но свой изобретенный велосипед помог мне лучше понять и запомнить как работать с дисплеем на контроллере HD44780. Начать стоит с того, что у дисплея есть память аж два вида памяти в которые мы можем что-то записать:
1. DDRAM. Когда мы записываем в эту память данные то всё что мы туда записали появляется на экране. Всего у контроллера 80 байт такой памяти, а сам дисплей может отображать 32 символа (16 символов * 2 строки) в один момент времени. Возникает вопрос: а какие 32 символа из 80 будут отображаться на экране? По каким адресам DDRAM мы должны писать что-то чтоб увидеть это на экране? Ответ в картинке ниже:
Из рисунка видно, что в желтенькое «окошко» размером 16х2 попали ячейки памяти с адресами c 0x00 по 0x0F и с 0x40 по 0x4F. Весь прикол в том что это самое «окошко» можно программно сдвигать. А это значит, что для того чтоб быстро менять содержимое дисплея, нужно сначала заполнить «видеопамять» нужными данными а потом дать команду на сдвиг окошка после чего на экране отобразится то, что было скрыто за его пределами. Вот такая вот немудрёная вещь, честно говоря мне эта фича еще ни разу не пригодилась и я думаю что и не пригодится.
2. CGRAM. Эта память используется для хранения пользовательских символов. Таблица символов у HD44780 конечно достаточно большая, каких только закорючек в ней нет, но бывает так, что требуется изобразить на экране нечто экзотическое и вот тут-то CGRAM нас и выручает. В эту таблицу можно записать 8 пользовательских символов, причем все они расположены в этой памяти непрерывно, один за другим. Каждый символ отображаемый на дисплее, имеет разрешение 5х8 пикселей. Это означает, что для того чтоб изобразить 1 символ нам потребуется 8 байт, причем 3 старших байта не играют роли (ширина то всего 5 пикселей). Для того чтоб вывести на экран символ предопределённый пользователем нужно отправить в дисплей символ с кодом от 0 до 7.
Если мы сразу после включения питания начнем что-то писать в DDRAM, то скорее всего на экране мы ничегошеньки не увидим, так как нужно предварительно произвести инициализацию, для этого нам нужно знать команды дисплея. Как правило все дисплеи которые считаются HD44780-совместимыми могут выполнять вот такие команды:
DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | Описание команды |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | Очистить дисплей, курсор домой |
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | Курсор домой |
0 | 0 | 0 | 0 | 0 | 1 | I/D | SH | Настройка сдвига экрана/курсора |
0 | 0 | 0 | 0 | 1 | D | CUR | BLN | Вкл/выкл экран и курсор |
0 | 0 | 0 | 1 | S/C | R/L | X | X | Разрешает сдвиг дисплея или курсора |
0 | 0 | 1 | DL | N | F | X | X | Установка разрядности, числа строк и размера символа |
0 | 1 | ASG | ASG | ASG | ASG | ASG | ASG | Установка адреса SGRAM |
1 | ADD | ADD | ADD | ADD | ADD | ADD | ADD | Установка адреса DDRAM |
Как видно из таблицы, некоторые биты в командах могут изменяться пользователем, осталось только разобраться какой бит за что отвечает: I/D — инкремент/декремент адреса DDRAM. 1 — инкремент (увеличение), 0 — декремент (уменьшение). Говоря по-русски: Если бит установлен, то счётчик адреса будет увеличиваться на единицу всякий раз когда мы пихаем символ в дисплей. За счёт этого для вывода какого-либо слова нам не нужно самостоятельно задавать позицию для вывода каждого символа, каждый новый символ будет выводится в следующую по порядку позицию. Ну а если бит сброшен, то счётчик адреса будет не увеличиваться на 1 а наоборот уменьшаться. К чему такое извращение не ясно, пользы ни какой. SH — Если бит установлен, то сдвиг дисплея разрешен. Всякий раз записывая символ в память, то самое «окошко» будет сдвигаться на один символ. D — Если бит установлен то содержимое DDRAM отображается экраном, если сброшен то очищается экран (но не память). Таким образом, если бит не установлен, на экране мы вообще ничего не увидим.CUR и BLN — Управляют отображением курсора на экране. Есть 4 комбинации этих бит:CUR=0 BLN=0 — курсор не отображаетсяCUR=0 BLN=1 — курсор не отображается, но мигает всё знакоместо (черным квадратом)CUR=1 BLN=0 — курсор есть и он не мигаетCUR=1 BLN=1 — курсор есть + мигает всё знакоместоS/C — Определяет что будет сдвигаться курсор (0) или дисплей (1)R/L — Определяет направление сдвига: Вправо 1, влево 0.DL — Бит определяет разрядность шины данных: 1 — 8 бит, 0 — 4 битаN — Задает число используемых строк. Бит сброшен — одна строка, установлен — две строкиF — Задает размер символа. Бит сброшен — размер 5 на 8 точек, бит установлен 5 на 10 точек. Как правило бит всегда сброшен, индикаторы поддерживающие сразу два размера символов встречаются крайне редко. ASG — биты задающие адрес по которому будем писать в SGRAM память.ADD — биты задающие адрес по которому будем писать в DDRAM память. Выглядит конечно немного запутанно, но постепенно все прояснится. Особенно если проверять эти команды на практике и побольше экспериментировать. Таблица показывает, что каждая команда у нас восьмибитная, а дисплей подключен к контроллеру по четырем проводам (напомню, что задействованны линии DB4…DB7). Может возникнуть вполне уместный вопрос: Как передать 8-ми битную команду по 4-м проводам? Для этого мы должны сначала передать старший полубайт команды а затем младший. Ну и конечно не забываем своевременно дергать выводы E и RS. Полный алгоритм передачи команды выглядит так:
Теперь когда мы имеем представление о выводе команд, можно попробовать произвести начальную инициализацию дисплея. Первым делом нужно установить разрядность и тут есть нюанс. Контроллер перейдет в 4-х битный режим только после того как мы установим на ногах DB7..DB4 значение 0010 и дрыгнем ногой E. При этом на остальных ногах дисплея (DB3..DB0) может быть вообще что угодно, они ведь болтаются в воздухе. Таким образом будет выполнена команда 0010ХХХХ где Х это может быть 1 или 0 (непредсказуемо). Естественно после дрыга ногой E, дисплей перейдет в 4-х битный режим но вот что там записалось в биты N и F неизвестно. Поэтому нужно задать их еще раз, в 4-х битном режиме это стало возможно. Таким образом полная процедура перевода дисплея в 4-х битный режим, включения двух строк и использования символов размером 5 на 8 точек будет выглядеть так:
Следующий обязательный шаг инициализации — заставить дисплей отображать содержимое DDRAM. За это как вы помните отвечает бит D. По желанию можно заодно и курсор включить (соответствующими битами). Затем нужно выполнить команду позволяющую установить бит I/D в единицу. Чтоб каждый новый символ у нас писался вслед за другим. После этого нам необходим очистить экран и установить курсор в начало верхней строки. После этой минимальной инициализации мой дисплей стал принимать символы и отображать их на экране. Передача символа осуществляется абсолютно точно также как и команды, за исключением того, что перед началом передачи RS нужно установить в единицу. Без этого дисплей не поймет. что мы шлём данные а не команду. И собственно всё, передали 16 символов и все они появились в верхней строке. Начали передачу 17-го и он записался в следующий по порядку адрес который находится за пределами видимости (вспомним про «окошко»). А чтоб 17-й символ у нас появился в первой позиции второй строки на нужно установить адрес DDRAM равный 0x40 (см картинку выше). Для этого у нас есть простая команда которая так и называется — Установка адреса DDRAM. Старший бит единица, а остальные биты задают адрес. В итоге мы должны отправить команду 0xC0 чтоб курсор переместился куда мы хотим. Теперь поговорим о пользовательских символах. Как я уже отметил выше, для вывода любого из восьми пользовательских символов, мы должны отправить (в режим передачи данных, а не команд разумеется) число от нуля до семи которое соответствует желаемому символу. Нарисовать символ в памяти SGRAM это дело не хитрое. Первым делом мы должны сказать дисплею что нам нужно писать данные в память SGRAM по нужному адресу. Для этого выполняем команду которая так и называется — Установка адреса SGRAM. Лично у меня возник вопрос, а в какие адреса вообще что писать? Методом проб и ошибок был рожден алгоритм описанный ниже. Для начала определимся символ с каким номером (от 0 до 7) мы хотим создать. Пусть это будет номер 5. Вычисляем адрес для этого 8 умножаем на 5. Получается 101000. Устанавливаем в единицу шестой бит. В результате получаем 1101000. Передаем это в качестве команды. Теперь нужно передать в режиме данных 8 байт (которые и будут нашим новым символом). Не забываем, что символ у нас 5 на 8. Поэтому старшие три бита каждого байта будут отсечены! Для примера я изобразил полупустую батарейку:
00001110
00010001
00010001
00010001
00011111
00011111
00011111
00011111
После того как все 8 байт записались необходимо вернуться в режим работы с DDRAM (используя последнюю команду из таблички). Если вы всё-таки дочитали до этого момента, значит вам действительно интересно узнать как работать с этим дисплеем. Практическая часть будет в следующей статье, а пока переваривайте это.
easystm32.ru
Пока он шел я изучал документацию на драйвер, который им управляет, а на форумах все чаще встречал вопрос типа «Если STM32 имеет встроенный контроллер дисплея, то для чего нужен драйвер?». Все дело в том, для управления жидкими кристаллами, а точнее затворами полевиков, которые ими управляют, необходимо два напряжения VGH и VGL, 19V и -5.5V соответственно. Сам МК такие напряжения выдать не может, он лишь посылает сигналы драйверу согласно интерфейсу RGB, а драйвер дублирует их с необходимым напряжением.
Дисплей пришел, при подключении вопросов не возникло. Запустить дисплей тоже не составило труда, но нормально работать он не хотел. Суть проблемы заключалась в том, что при использовании двух слоев, изображение на дисплее периодически дергалось. Еще раз перечитав AN4861 понял, что проблема в пиксельной частоте.
Дело в том, что дисплей, купленный в Китае имеет разрешение 1024×600, и для того, чтобы изображение на дисплее обновлялось 60 раз в секунду пиксельная частота должна быть в диапазоне от 45 до 63 MHz.
А вывод из сложившейся ситуации можно сделать следующий, перед покупкой дисплея надо разобраться хватит ли пропускной способности оперативки для работы с ним.
hubstub.ru