Библиотека hd44780 4 строки по 20 символов для stm32

Библиотека для LCD на базе контроллера HD44780

Писал я как-то про подключение этих дисплеев к Arduino. Но сейчас моё кремниевое сердце принадлежит STM32, а с имеющимися дисплеями работать хочется. Как-то раз мне понадобилось визуально отладить алгоритм для этих МК — я собрался духом и сел писать библиотеку.

Писать с нуля, постоянно копошась в даташите, мне не хотелось, поэтому я решил портировать ардуиновскую библиотеку LiquidCrystal на STM32F10x, для начала.

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

В результате более чем недельного программерского задротства

получилась кросс-платформенная библиотека, которую я, не мудрствуя лукаво, назвал банально — HD44780. Её исходники открыты под и (). Рассмотрим библиотеку поподробнее.

Так как я хотел добиться максимальной портируемости кода и лёгкой его подстройки под разные семейства МК, я отделил логику работы библиотеки от непосредственной работы с выводами конкретных МК, и реализовал это известными методами абстракции — интерфейсами и драйверами. Что это такое и как работает? Библиотека работает с GPIO через интерфейс — набор функций.

Каждая функция интерфейса принимает в качестве аргументов указатель на интерфейс, идентификатор вывода дисплея (HD44780_PIN_RS, HD44780_PIN_DP5 и т.п.

) и остальные аргументы, необходимые для выполнения операции над GPIO:/* Пара объявлений для красоты */ struct HD44780_GPIO_Interface_Struct; typedef struct HD44780_GPIO_Interface_Struct HD44780_GPIO_Interface; /* Интерфейс GPIO: configure(), write() и read() */ struct HD44780_GPIO_Interface_Struct { HD44780_Result (*configure)(HD44780_GPIO_Interface *interface, HD44780_Pin pin, HD44780_PinMode mode); HD44780_Result (*write)(HD44780_GPIO_Interface *interface, HD44780_Pin pin, HD44780_PinState value); HD44780_Result (*read)(HD44780_GPIO_Interface *interface, HD44780_Pin pin, HD44780_PinState *value); };Скорее всего, у вас возник закономерный вопрос: на кой нужно функции, члену интерфейса, передавать указатель на этот интерфейс? А сделано это потому, что в библиотеке любой драйвер GPIO представляет собой структуру, первым членом которой является как раз такой интерфейс, поэтому можно спокойно приводить указатель на драйвер к указателю на интерфейс — таким образом, драйвер может «прикинуться» интерфейсом. Гляньте на объявление драйвера GPIO для STM32F10x:/* Структура, описывающая одну ножку МК для драйвера */ typedef struct { GPIO_TypeDef *gpio; uint16_t pinmask; } HD44780_STM32F10x_Pin; /* Массив пинов обёрнут в структуру для более строгой проверки типов данных компилятором. Сам массив будет заполнять пользователь библиотеки, указывая GPIO, которые он отдаст драйверу. */ typedef struct { HD44780_STM32F10x_Pin pins[HD44780_PINS_AMOUNT]; } HD44780_STM32F10x_Pinout; /* А вот и драйвер, содержащий интерфейс, распиновку и обработчик ошибок. */ typedef struct { HD44780_GPIO_Interface interface; HD44780_STM32F10x_Pinout pinout; HD44780_AssertFn assert_failure_handler; } HD44780_STM32F10x_GPIO_Driver;Теперь, если заставить библиотеку работать с GPIO только через указатель на HD44780_GPIO_Interface, можно подсунуть ей указатель на экземпляр драйвера, просто приведя последний к HD44780_GPIO_Interface*. А библиотека вообще не знает, с чем на самом деле она работает — может, там не GPIO, а толпа китайцев, замыкающих контакты по вызову функции интерфейса. Налицо реализация типичной парадигмы ООП — полиморфизма. Кто там говорил, что C — не объектно-ориентированный язык?Драйвер, в свою очередь, реализует функции интерфейса и в них приводит указатель на интерфейс обратно к указателю на драйвер. Получается, что библиотеке видна только «верхушка» драйвера, а оставшаяся часть видна только драйверу. Вот и инкапсуляция нарисовалась. Дополню своё объяснение схемой:а также кодом одной из функций драйвера:static HD44780_Result stm32f10x_default_pin_write( HD44780_GPIO_Interface *interface, HD44780_Pin pin, HD44780_PinState value) { HD44780_STM32F10X_RETURN_ASSERT(interface != NULL, HD44780_RESULT_ERROR); HD44780_STM32F10x_GPIO_Driver *driver = (HD44780_STM32F10x_GPIO_Driver*)interface; HD44780_STM32F10x_Pin *hw_pin = &driver->pinout.pins[pin]; HD44780_STM32F10X_RETURN_ASSERT(hw_pin != NULL, HD44780_RESULT_ERROR); HD44780_STM32F10X_RETURN_ASSERT(hw_pin->gpio != NULL, HD44780_RESULT_ERROR); GPIO_WriteBit(hw_pin->gpio, hw_pin->pinmask, (value == HD44780_PINSTATE_LOW ? Bit_RESET : Bit_SET)); return HD44780_RESULT_OK; }С такой организацией доступа к GPIO драйвера полностью отделены от библиотеки, так что если у вас есть кучка китайцев, можете написать драйвер и для них, не меняя код библиотеки (: Окей, пора браться за дело, но перед этим проясню один момент: библиотека использует пользовательскую функцию для создания задержек, которую вам нужно написать. Я предпочитаю реализовывать её при помощи таймера SysTick. Функция должна принимать число микросекунд и делать соответствующую задержку (или дольше). Конечно, delay()-ориентированное программирование не есть гуд, но потом ниже я объясню, как с этим жить. И да, в настройках проекта поставьте стандарт языка C99 (флаг -std=c99).#include #include #include #include #include /* Подключаем библиотеку и драйвер GPIO */ #include “hd44780.h” #include “hd44780_stm32f10x.h” void init_lcd(void); void delay_microseconds(uint16_t us); uint32_t uint32_time_diff(uint32_t now, uint32_t before); void hd44780_assert_failure_handler(const char *filename, unsigned long line); HD44780 lcd; HD44780_STM32F10x_GPIO_Driver lcd_pindriver; volatile uint32_t systick_ms = 0; int main(void) { SysTick_Config(SystemCoreClock / 1000); init_lcd(); while (1) { /* Каждую секунду выводим счётчик на экран и увеличиваем его */ static uint32_t lcd_update_ms = 0; if (uint32_time_diff(systick_ms, lcd_update_ms) >= 1000) { lcd_update_ms = systick_ms; static unsigned counter = 0; const size_t buf_size = lcd.columns_amount + 1; char buf[buf_size]; snprintf(buf, buf_size, “%d”, counter); ++counter; hd44780_clear(&lcd); hd44780_write_string(&lcd, buf); } } } void SysTick_Handler(void) { ++systick_ms; } void init_lcd(void) { /* Распиновка дисплея */ const HD44780_STM32F10x_Pinout lcd_pinout = { { /* RS */ { GPIOA, GPIO_Pin_6 }, /* ENABLE */ { GPIOA, GPIO_Pin_5 }, /* RW */ { GPIOA, GPIO_Pin_4 }, /* Backlight */ { NULL, 0 }, /* DP0 */ { NULL, 0 }, /* DP1 */ { NULL, 0 }, /* DP2 */ { NULL, 0 }, /* DP3 */ { NULL, 0 }, /* DP4 */ { GPIOA, GPIO_Pin_3 }, /* DP5 */ { GPIOA, GPIO_Pin_2 }, /* DP6 */ { GPIOA, GPIO_Pin_1 }, /* DP7 */ { GPIOA, GPIO_Pin_0 }, } }; /* Настраиваем драйвер: указываем интерфейс драйвера (стандартный), указанную выше распиновку и обработчик ошибок GPIO (необязателен). */ lcd_pindriver.interface = HD44780_STM32F10X_PINDRIVER_INTERFACE; /* Если вдруг захотите сами вручную настраивать GPIO для дисплея (зачем бы вдруг), напишите здесь ещё (библиотека учтёт это): lcd_pindriver.interface.configure = NULL; */ lcd_pindriver.pinout = lcd_pinout; lcd_pindriver.assert_failure_handler = hd44780_assert_failure_handler; /* И, наконец, создаём конфигурацию дисплея: указываем наш драйвер, функцию задержки, обработчик ошибок дисплея (необязателен) и опции. На данный момент доступны две опции – использовать или нет вывод RW дисплея (в последнем случае его нужно прижать к GND), и то же для управления подсветкой. */ const HD44780_Config lcd_config = { (HD44780_GPIO_Interface*)&lcd_pindriver, delay_microseconds, hd44780_assert_failure_handler, HD44780_OPT_USE_RW }; /* Ну, а теперь всё стандартно: подаём тактирование на GPIO, инициализируем дисплей: 16×2, 4-битный интерфейс, символы 5×8 точек. */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); hd44780_init(&lcd, HD44780_MODE_4BIT, &lcd_config, 16, 2, HD44780_CHARSIZE_5x8); } void delay_microseconds(uint16_t us) { SysTick->VAL = SysTick->LOAD; const uint32_t systick_ms_start = systick_ms; while (1) { uint32_t diff = uint32_time_diff(systick_ms, systick_ms_start); if (diff >= ((uint32_t)us / 1000) + (us % 1000 ? 1 : 0)) break; } } uint32_t uint32_time_diff(uint32_t now, uint32_t before) { return (now >= before) ? (now – before) : (UINT32_MAX – before + now); } void hd44780_assert_failure_handler(const char *filename, unsigned long line) { (void)filename; (void)line; do {} while (1); } Максимальная суммарная задержка при вызове функции библиотеки при такой реализации delay_microseconds() может составлять до 20 мс, что может быть критичным. С другой стороны, реализация полностью асинхронной библиотеки — это задача нетривиальная, а в данном случае ещё и сложная. Да и использовать такую библиотеку будет не очень-то просто.

Но выход есть — использовать операционную систему реального времени (RTOS). Есть о том, как подружить STM32 и FreeRTOS — свободную и открытую RTOS для микроконтроллеров. Она очень популярна, функциональна и поддерживает тонны различных МК, включая STM32. Так вот, в ней есть функция vTaskDelay(), которая замораживает не всю программу, как это было бы без RTOS, а только ту задачу (поток), в которой была вызвана функция. В этом случае реализация delay_microseconds() будет заключаться в вызове vTaskDelay(), и задержки будут влиять только на ту задачу, которая работает с LCD.

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

Итак, список функций:HD44780_Result hd44780_init(HD44780 *display, HD44780_Mode mode, const HD44780_Config *config, uint8_t columns, uint8_t rows, HD44780_CharSize charsize); HD44780_Result hd44780_write_char(HD44780 *display, char c); HD44780_Result hd44780_write_string(HD44780 *display, const char *s); HD44780_Result hd44780_clear(HD44780 *display); HD44780_Result hd44780_home(HD44780 *display); HD44780_Result hd44780_scroll_left(HD44780 *display); HD44780_Result hd44780_scroll_right(HD44780 *display); HD44780_Result hd44780_left_to_right(HD44780 *display); HD44780_Result hd44780_right_to_left(HD44780 *display); HD44780_Result hd44780_create_char(HD44780 *display, uint8_t code, const uint8_t *charmap); HD44780_Result hd44780_move_cursor(HD44780 *display, uint8_t column, uint8_t row); HD44780_Result hd44780_display_on(HD44780 *display); HD44780_Result hd44780_display_off(HD44780 *display); HD44780_Result hd44780_blink_on(HD44780 *display); HD44780_Result hd44780_blink_off(HD44780 *display); HD44780_Result hd44780_cursor_on(HD44780 *display); HD44780_Result hd44780_cursor_off(HD44780 *display); HD44780_Result hd44780_autoscroll_on(HD44780 *display); HD44780_Result hd44780_autoscroll_off(HD44780 *display); HD44780_Result hd44780_backlight_on(HD44780 *display); HD44780_Result hd44780_backlight_off(HD44780 *display);Функция hd44780_create_char() создаёт символ с заданным ASCII-кодом от 0 до 7, используя битовое описание символа в виде массива из 8 байтов, каждый из которых кодирует строку из 5 точек. Пользоваться ей нужно так:uint8_t arnie[8] = { HD44780_MAKE_5BITS(1,0,1,0,1), HD44780_MAKE_5BITS(1,1,1,1,1), HD44780_MAKE_5BITS(1,0,1,1,1), HD44780_MAKE_5BITS(1,1,1,1,1), HD44780_MAKE_5BITS(1,1,1,1,1), HD44780_MAKE_5BITS(0,0,1,1,1), HD44780_MAKE_5BITS(1,1,1,1,0), HD44780_MAKE_5BITS(0,0,0,0,0), }; hd44780_create_char(&lcd, 1); hd44780_write_string(&lcd, “Here is Arnie: “); hd44780_write_char(&lcd, '1');Разумеется, символы можно встраивать в строки через escape-последовательности, но тут нужно иметь ввиду, что код 0 является завершением строки, так что такой символ придётся печатать отдельно, как в примере. Кстати, для просмотра созданных символов не обязательно запускать код — достаточно в редакторе сделать поиск текста «1» и воспользоваться функцией подсветки результатов поиска (:Ещё одно замечание по этой функции — она уносит курсор куда-то за пределы экрана, и это не баг библиотеки, а поведение самого контроллера дисплея. Судя по всему, там просто система команд так организована. Так что лучше создавайте символы до того, как будете что-то печатать. Функция hd44780_clear() не только очищает экран, но и перемещает курсор в начало, как это делает hd44780_home(). На данный момент я написал и протестировал драйвера GPIO для STM32F10x, STM32F2xx, STM32F4xx и STM32L1xx. Так что все STM32 охвачены, кроме ещё не поступивших в продажу STM32F0 на базе архитектуры Cortex-M0. Ну, тут я немножко лукавлю: драйвер для STM32F2xx я тестировал на STM32F4DISCOVERY, т.к. МК этой серии у меня нет (голый кристалл не в счёт). Но результатам можно верить, т.к. STM32F2xx совместимы по периферии с STM32F4xx, и более того — код для STM32F2xx таки заработал на вышеуказанной плате, так что должен работать и с настоящим STM32F2xx. Был у меня лёгкий порыв написать ещё дровишку для AVR, но угас, когда я вспомнил об Arduino, с которого портировал библиотеку. Впрочем, если кому сильно нужно, можете попросить меня или написать сами по образцу.

Сами драйвера лежат в директории drivers, а в директорию examples я положил все 4 примера для STM32, на которых тестировал работу дисплея, в виде своих проектов для Eclipse. Впрочем, не обязательно импортировать проект в Eclipse — достаточно в консоли зайти в /Debug и выполнить команду make (GNU ARM Eclipse plugin создаёт makefile'ы для конфигураций Debug, Release и т.п.)

Надеюсь, библиотека вам пригодится, несмотря на сравнительную сложность её применения (:

Источник: http://robocraft.ru/blog/algorithm/785.html

Как подключить ЖКИ на HD44780

В этой статье я хочу рассказать и показать Как подключить и использовать символьный жидко-кристаллический индикатор совместно с контроллером STM32F103.

На днях у меня появился такой вот индикатор:

Это символьный жидко-кристаллический индикатор QC2004A на 4 строки и 20 символов в строке, о чем явно свидетельствует модель. К тому же индикатор имеет подсветку и является контрастным, т.е. символы светятся белым, а фон синий.

Управляется матрица установленным на плате контроллером KS0066, это аналог HD44780.

Плата имеет 16 контактов: 2 для питания индикатора (1 и 2 пин), 1 для выставления контрастности (3 пин), 3 для управления шиной данных и режимом контроллера (4 – RS – данные или инструкции; 5 – R/W – запись или чтение с ЖКИ; 6 – E – отправка данных по шине), 8 контактов шины данных (т.е. шина восьми битная) и 2 контакта для питания подсветки.

Размеры и немного больше информации по ЖКИ вот тут.

Стоит сказать о шине данных и подключении.

Если вам требуется только выводить информацию на индикатор, то потребуется подключить питание, подключить RS и E, при это пин R/W запаять на минус, подключить 4 (D4…D7) или 8 (D0…D7) контактов данных, т.е.

использовать 8ми или 4х битную шину. Хочется отметить, что контроллер позволяет передавать данные по четырем проводам, вместо восьми, хотя данные все равно передаются восьми битные, только тетрадами (по 4 бита 2 раза).

R/W пин используется для того, чтобы выбрать направление передачи данных в ЖКИ или из ЖКИ. Это может быть нужно, чтобы считывать данные с индикатора, положение курсора и т.п. Используется крайне редко.

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

  • 1 пин ЖКИ к GND
  • 2 пин ЖКИ к +5V
  • 3 пин ЖКИ к GND
  • 4 пин ЖКИ к PA0
  • 5 пин ЖКИ к GND
  • 6 пин ЖКИ к PA1
  • 11 пин ЖКИ к PA2
  • 12 пин ЖКИ к PA3
  • 13 пин ЖКИ к PA4
  • 14 пин ЖКИ к PA5
  • 15 пин ЖКИ к +5V
  • 16 пин ЖКИ к GND

Все функции по работе с ЖКИ для STM32 вынесены в отдельную библиотеку, т.е. ее просто необходимо подключить будет настроить и заголовочном файле (*.h) порты и пины и можно использовать. Настройка пинов в hd44780_driver.h:

#define LCD_PORT GPIOA#define LCD_CD 0  // RS#define LCD_EN 1  // E#define LCD_DB4 2#define LCD_DB5 3#define LCD_DB6 4#define LCD_DB7 5

После этого добавляем библиотеку в проект:

#include “hd44780_driverhd44780_driver.h”

И можно пользоваться. В конце статьи прикреплен архив проектом в IDE CooCox.

Кратко рассмотрим функции:

// Инициализирует индикатор, запускаем один раз перед началом работыvoid lcd_init();// Отправляет байт как команду (COMMAND) или как данные (DATA)void lcd_send(uint8_t byte, dat_or_comm dc);// Устанавливает режим обмена данными по 4м проводамvoid lcd_set_4bit_mode(void);// Устанавливает состояние индикатора: вкл/выкл ЖКИ,  вкл/выкл курсор, вкл/выкл мигание курсораvoid lcd_set_state(lcd_state state, cursor_state cur_state, cursor_mode cur_mode);// Очищает ЖКИvoid lcd_clear(void);// Выводит строкуvoid lcd_out(char * txt);// Задает позицию курсора. С того места будет вывод на ЖКИvoid lcd_set_xy(uint8_t x, uint8_t y);// Задает пользовательский символvoid lcd_set_user_char(uint8_t char_num, uint8_t * char_data);

Немного подробнее теперь.  Функция

void lcd_set_state(lcd_state state, cursor_state cur_state, cursor_mode cur_mode);
  • lcd_state: LCD_ENABLE или LCD_DISABLE
  • cursor_state: CURSOR_ENABLE или CURSOR_DISABLE
  • cursor_mode: BLINK (мигать) или NO_BLINK (не мигать)
void lcd_set_xy(uint8_t x, uint8_t y);

X – это номер символа в строке, счет от 0. Y – номер строки, счет от 0.

Применение пользовательского символа бывает полезно, если нет кириллицы, как в моем случае (с Китая индикатор), либо если нужен спец символ типа значка батарейки и т.п.

uint8_t user_char[8];user_char[0]=0b01110;user_char[1]=0b10001;user_char[2]=0b10001;user_char[3]=0b10001;user_char[4]=0b11111;user_char[5]=0b11111;user_char[6]=0b11111;user_char[7]=0b00000;// Первый параметр – номер символа в памяти ЖКИ, второй параметр – массив с символомlcd_set_user_char(0, user_char);// Применение:lcd_send(0, DATA);

Стоит отметить, что символ записывается в память ЖКИ не навсегда, а только пока питание есть. Т.е. после выключения этого символа уже не будет в памяти. Поэтому все свои символы пишем в ЖКИ при инициализации, а потом основная работа уже.

Вот собственно и все, что может пригодиться для использования индикаторов на HD44780. Стоит лишь смотреть в даташитах на адресацию памяти ЖКИ, чтобы подправить функцию перехода lcd_set_xy. И конечно, библиотеку не составит труда переделать под другие семейства STM32.

Прикрепления:

  1. Проект в CooCox
  2. Даташит на QC2004A
  3. Полезная статья

Вопросы пишите в комментарии.

Источник: http://www.how.net.ua/2014/06/kak-podklyuchit-zhki-na-hd44780/

HD44780-совместимый LCD дисплей и STM32

HD44780 совместимые LCD алфавитно-цифровые модули очень удобны для применения во многих хобби-проектах. Они недороги и бывают разных размеров и цветов. Наиболее распространены 2-х строчные модули с 16 символами в строке (16х2). Другими популярными размерами являются: 8х2, 16х1, 20х2, 20х4 и 40х2.

Для моего проекта «Агат-7» в качестве дисплея эмулятора дисковода больше всего подошел 20х4 модуль. Его и будем использовать в качестве примера. Большинство модулей рассчитаны на напряжение 5В, хотя бывают и 3.3В модули.

Последние как правило вдвое дороже и реже встречаются, поэтому я предпочитаю использовать 5В модели и ставить буферную микросхему,  если управляющий микроконтроллер работает на 3.3В (как например STM32L), т.к. она стоит всего порядка $1.

Для управления контроллером используется 3 пина: RS, R/W и E. Данные передаются по 8-битной шине, хотя есть опция работы по 4-битной шине (байт передается в два приема). Таким образом, от микроконтроллера требуется от 7 до 11 ног для управления модулем.

8-битная шина имеет выше скорость и как следствие меньше загружает микроконтроллер. Кроме того, она проще в програмировании.

https://www.youtube.com/watch?v=XEBVHUNOdx8

Так же есть два способа соблюдения интервалов между командами — таймер и запрос готовности. Во втором случае модуль передает сигнал готовности по старшему биту шины данных. При использовании 3.3В микроконтроллеров первый способ предпочтительнее, т.к.

позволяет использовать дешевые однонаправленные буферы-преобразователи «3.3В -> 5В» для сигналов. Кроме того, при первом способе нам не требуется использование R/W сигнала, что экономит одну ногу.

Я предпочитаю использовать первый способ в сочетании с 8-битной шиной, что требует 10 ног микроконтроллера.

Модули поставляются с различными наборами символов, поэтому при покупке следует иметь это ввиду, если планируется использовать кирилицу.

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

Так же в модулях есть возможность запрограмировать несколько собственных символов.

Расположение пинов может отличаться в модулях различных производителей, но наиболее типичным является такое:

    1. GND
    2. Vcc (+5V)
    3. Настройка контрастности (Vo)
    4. RS
    5. R/W
    6. Enable (E)
    7. Bit 0 (младший для 8-ми битного интерфейса)
    8. Bit 1
    9. Bit 2
    10. Bit 3
    11. Bit 4 (младший для 4-х битного интерфейса)
    12. Bit 5
    13. Bit 6
    14. Bit 7 (старший)
    15. Питание подсветки для дисплеев с подсветкой (анод)
    16. Питание подсветки для дисплеев с подсветкой (катод)

Обычно подсветку следует подключать через резистор 50-100 ом. Настройка контрастности осуществляется подстроечным резистором (обычно 10-20К), подключенным как делитель напряжения (A и B к питанию 5В, а W к пину Vo управления контрастностью).

Я покажу пример подключения такого дисплея к микроконтроллеру STM32L152R6, который я использую в своем проекте «АГАТ-7» в качестве эмулятора дисковода. Схема подключения такова:

Для начала инициализируем контроллер и пины. Кроме того, нам понадобится функция задержки в микросекундах и милисекундах. Я выполнил ее на встроенном таймере TIM6, хотя можно использовать и простой цикл, рассчитав его время выполнения:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include “stm32l1xx.h”
#include “stm32l1xx_conf.h”
#include “stm32l1xx_gpio.h”
#include “stm32l1xx_exti.h”
#include “stm32l1xx_syscfg.h”
#include “misc.h”
 
#include
#include
#include
#include
 
void delay_us(uint16_t usv)                 // Delay in us
{
  TIM6->PSC=32-1;
  TIM6->ARR=usv;
  TIM6->DIER = TIM_DIER_UIE;
  TIM6->SR &= ~TIM_SR_UIF;
  TIM6->CR1 &= ~TIM_CR1_ARPE;
  TIM6->SR = 0x00;
  TIM6->CR1 |= TIM_CR1_CEN;
 
  while (TIM6->SR == 0)
  {}
}
 
void delay_ms(uint16_t msv) // Delay in ms
{
 uint16_t i;
 for(i=0; iCR|=RCC_CR_HSION;
  while (!(RCC->CR & RCC_CR_HSIRDY));             // Wait until it's stable
  RCC->CFGR|=RCC_CFGR_PLLDIV_0 | RCC_CFGR_PLLMUL_0;   // Switch to HSI as SYSCLK
  RCC->CR|=RCC_CR_PLLON;                              // Turn PLL on
  while (!(RCC->CR & RCC_CR_PLLRDY));             // Wait PLL to stabilise
 
  //Setting up flash for high speed
  FLASH->ACR=FLASH_ACR_ACC64;
  FLASH->ACR|=FLASH_ACR_LATENCY;
  FLASH->ACR|=FLASH_ACR_PRFTEN;
 
  RCC->CFGR|=RCC_CFGR_SW_1 | RCC_CFGR_SW_0;           // Set PLL as SYSCLK
  RCC->CR&=~RCC_CR_MSION;                         // Turn off MSI
 
  //Enabling clock for GPIOB & GPIOC
  RCC->AHBENR|=RCC_AHBENR_GPIOCEN;
  GPIOC->MODER = (uint32_t) 0x5555000;
  GPIOC->OTYPER &= (uint16_t) 0xFF;
  GPIOC->OSPEEDR = (uint16_t) 0xAAAA000;
  GPIOC->ODR = (uint32_t) 0;
 
  RCC->AHBENR|=RCC_AHBENR_GPIOBEN;
  GPIOB->MODER = (uint32_t) 0x155000;
  GPIOB->OTYPER&=~(GPIO_OTYPER_OT_8 | GPIO_OTYPER_OT_9 | GPIO_OTYPER_OT_10 | GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7);
  GPIOB->OSPEEDR|=(GPIO_OSPEEDER_OSPEEDR8_1 | GPIO_OSPEEDER_OSPEEDR9_1 | GPIO_OSPEEDER_OSPEEDR10_1);
  GPIOB->ODR = (uint16_t) 0;
 
  RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;               // TIM6 timer clock
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
}

Довольно часто при общении с модулем требуется коротко «мигнуть» сигналом E, поэтому удобно написать подпрограмму “lcd_nybble”, которая это делает. Следует иметь ввиду, что длительность импульса в вашем проекте может отличаться от моей. Это зависит от выбранного модуля и скорости работы микроконтроллера:

1
2
3
4
5
6
7
void lcd_nybble()
{
 char i;
 GPIOB->BSRRL=GPIO_BSRR_BS_10;
 for(i=0; iBSRRH=GPIO_BSRR_BS_10;          //Clock enable: falling edge
}

Для подачи команды в модуль надо выставить команду на пины данных, пины RS и R/W обнулить и «мигнуть» сигналом E. Команды требуют время для выполнения во время которого модуль недоступен. Поэтому используются задержки.

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

Моя функция “lcd_command” выглядит так:

1
2
3
4
5
6
7
8
9
10
11
12
void lcd_command(uint16_t lcmnd)
{
  uint16_t temp_port;
 
  delay_us(40);
  temp_port = GPIOC->ODR & ((uint16_t) 0xC03F);
  GPIOC->ODR = temp_port | (lcmnd BSRRH=GPIO_BSRR_BS_9;             //R/W=LOW : Write
  lcd_nybble();
  if (lcmnd BSRRH = (uint16_t) 0x3FC0;                    // Reset all ports
  GPIOB->BSRRH = (uint16_t) 0x7C0;                     // Reset all ports
  delay_us(40000);                                     // Wait >15 msec after power is applied
  GPIOC->BSRRL = GPIO_BSRR_BS_10 | GPIO_BSRR_BS_11;    // put 0x30 on the output port delay_us(5000);                                      // must wait 5ms, busy flag not available lcd_nybble();                                        // command 0x30 = Wake up
  delay_us(160);                                       // must wait 160us, busy flag not available
  lcd_nybble();                                        // command 0x30 = Wake up #2
  delay_us(160);                                       // must wait 160us, busy flag not available
  lcd_nybble();                                        // command 0x30 = Wake up #3
  delay_us(160);
  GPIOC->BSRRL = (uint16_t) 0xE00;
  lcd_nybble();
  delay_us(160);
  lcd_command((uint16_t) 0x10);
  lcd_command((uint16_t) 0x0C);
  lcd_command((uint16_t) 0x06);
}

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

Еще нам понадобятся команды для вывода строки и символа. Мои команды «lcd_write» и «lcd_char» выглядят так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**********************************************************/
void lcd_write(char ldata[])
{
  uint16_t temp_port;
  char k=0;
 
  GPIOB->BSRRL=GPIO_BSRR_BS_8;                 //RS=HIGH : send data
  GPIOB->BSRRH=GPIO_BSRR_BS_9;                 //R/W=LOW : Write
 
  while(ldata[k] != 0)
  {
    delay_us(40);
    temp_port = GPIOC->ODR & ((uint16_t) 0xC03F);
    GPIOC->ODR = temp_port | (ldata[k] BSRRH=GPIO_BSRR_BS_9;                //R/W=LOW : Write
  delay_us(40);
  temp_port = GPIOC->ODR & ((uint16_t) 0xC03F);
  GPIOC->ODR = temp_port | (ldata

Источник: http://www.electronicsfun.net/RU/archives/382

STM32: Бібліотека для роботи з LCD WH1602(4) або іншим сумісним дисплеєм з контролером HD44780

Ця бібліотека є вільним програмним забезпеченням і може вільно розповсюджується та змінюватись. Я не автор бібліотеки, а вніс зміни щодо автоматизації налаштувань, вніс зміни щодо більшої абстракції, а також усунув недолік, який псував все задоволення від роботи з цією бібліотекою, це випадкове “сміття” на екрані при увімкненні живлення.

Щоб прибрати “сміття” потрібно було перезавантажувати пристрій кнопкою “reset”. Бібліотека доволі вдала, як на мене, і має великий спектр функцій, щоб задовольнити будь які потреби, у відтворені інформації на дисплей типу WH1602 (WH1604) з контролером HD44780. Ця бібліотека створювалась початково, як кросплатформена для AVR, PIC, ARM.

Чи збереглась кросплатформеність перевірити не маю змоги. Але на мікроконтролерах STM32F1xx і STM32F4xx перевірено – працює. В мережі можна зустріти різні варіації цієї бібліотеки, але набір функцій один і той самий. Зроблені мною зміни спрощують налаштування бібліотеки. В принципі налаштовувати взагалі нічого не потрібно.

Ну може зовсім трішечки 🙂

Схема підключення

Схема підключення на малюнку. Підключати будемо по 4-х провідниковій схемі з заземленим сигналом W/R. Це означає що нам потрібно під'єднати до мікроконтролера шість дротів від дисплея. Такі як D4, D5, D6, D7, E, RS.

До яких саме виводів мікроконтролеру під'єднувати дисплей, це вже вирішувати вам самим. Бібліотека допускає підключення кожного виводу дисплея на різні порти і будь які піни обраного/обраних порту/портів. Під'єднайте до дисплею живлення, це ніжка 2 – “+5 Вольт”. Загальний мінус на ніжки дисплею 1 та 5.

Та до ніжки дисплею під номером 3 резистивний дільник з підстроєчним резистором 10к для регулювання контрасту інформації, яка висвічується на екрані. Якщо ваш дисплей має підсвічування екрану, то можна ще подати живлення на підсвічування, це 15 і 16 ніжки дисплею. Живлення подати напряму, або через транзисторний ключ, який керується з ніжки мікроконтролеру.

Тоді підсвічування можна вмикати та вимикати програмно, або навіть регулювати яскравість підсвічування налаштувавши ніжку, яка керує ключем підсвічування, як PWM (ШИМ).

Щоб бібліотека працювала правильно потрібно всі виводи, якими буде з'єднано мікроконтролер з екраном WH1602, обізвати в програмі CubeMX за принципом:

  • Enable (E) – LCD_E
  • RS – LCD_RS
  • D4 – LCD_D4
  • D5 – LCD_D5
  • D6 – LCD_D6
  • D7 – LCD_D7

Обираєте зручні для вас, або вільні виводи мікроконтролеру. Виставляєте їх в режим “GPIO_Output” та називаєте за принципом, як я зазначив вище. Приклад на малюнку. Червоними зонами позначив потрібне для підключення LCD:

Оголошення назв і режиму виводів для підключення LCD в CubeMX

Щодо підключення LCD в CubeMX то це все. Генеруєте код для свого мікроконтролеру та засобу (середовища)  розробки:

Генеруємо код

Якщо користуєтесь засобом розробки CooCox CoIDE, то як імпортувати проект створений CubeMX до CooCox'у в цій статті Імпорт проекту в CooCoxIDE з CubeMX. Якщо середовищем розробки зі списку, що пропонує CubeMX, то ви маєте вже готовий проект на виході. Тепер я переходжу до CooCox CoIDE, а ви до свого середовища розробки. Постараюсь показати сам принцип без прив'язки до конкретного засобу розробки.

  1. Завантажте архів бібліотеки “hd44780.zip” собі на диск. В архіві знаходиться тека “hd44780” з підтеками “inc” та “src”. В теці “inc” файли “lcd_cfg.h” – всілякі налаштування та попередні установки, “hd44780.h” – макроси, дефайни, прототипи функцій. В теці “src” файл “hd44780.c” – з функціями бібліотеки.

     

  2. Копіюємо файли бібліотеки до проекту. Можна скопіювати самі файли бібліотеки до проекту. Заголовні файли “h” у теку “inc” проекту, а сирцеві файли “c” до теки “src” проекту. Або скопіювати в корінь проекту всю теку бібліотеки “hd44780”.
  3. Додати бібліотеку до проекту.

    Ці файли треба додати до проекту вже в самому засобі розробки яким ви користуєтесь. Як це зробити залежить від вашого засобу розробки. Наприклад, в CooCox CoIDE, я просто перетягую теку бібліотеки “hd44780” до теки проекту і тисну “Оk” в діалоговому вікні, що з'являється.

    Після цих маніпуляцій бібліотеку під'єднано до проекту.

Додавання бібліотеки до проекту

Як у вас інший засіб розробки, наприклад “Atolic True Studio” чи “System Workbench for STM32” то просто додайте (скопіюйте) файли бібліотеки, заголовні “h” до теки проекту “inc”, а сирцеві “c” до теки “src” проекту. В засобі розробки оновіть проект через контекстне меню “refresh”, або кнопкою “F5”. Додані файли з'являться в дереві проекту.

Оновлення проекту

Тепер, коли на рівні файлів все під'єднано як слід, треба в коді додати лиш один рядок в головний файл “main.c”:

Наш проект згенеровано CubeMX то цей рядок потрібно вставити в місце між рядками:

1
2
3
/* USER CODE BEGIN Includes */
#include “hd44780.h”
/* USER CODE END Includes */

А також потрібно до файлу бібліотеки “lcd_cfg.h” додати рядок з заголовним файлом драйверу “HAL” чи “SPL”, який ви використовуєте.

А непотрібні рядки з “include” закоментувати чи видалити. Я працюю з мікроконтролерами сотої серії “stm32f1xx” і використовую драйвер “HAL”, то в мене буде рядок в файлі “lcd_cfg.h”:
Наприклад, як би я використовував SPL бібліотеки, то в файлі “lcd_cfg.h” знадобився б такий рядок:
Оце і все. Можна компілювати проект для перевірки.

Як зроблено все правильно то компілятор скомпілює проект без помилок.
Бібліотека для своєї роботи і зручності користувача – має ряд функцій. Внутрішні функції розглядати не будемо, а розглянемо ті функції, що потрібні нам для виводу на дисплеї різноманітної інформації.

  • lcdInit – функція початкової ініціалізації дисплею;
  • lcdClrScr – функція очищення дисплею, курсор в початкове положення;
  • lcdReturn – функція повернення курсору в початкове положення;
  • lcdSetMode – функція встановлення режимів: дисплей увімк/вимк, курсор блимає увімк/вимк, курсор увімк/вимк;
  • lcdGoto – функція встановлює курсор в певне місце: рядок, стовпець;
  • lcdPuts – функція виводу текстового рядка;
  • lcdPutc – функція виводу окремого символу типу char;
  • lcdLoadChar – функція завантаження до пам'яті дисплею користувацького символу;
  • lcdDrawChar – функція малювання на дисплеї користувацького символу;
  • lcdBackSpace – функція зміщення курсору на один символ ліворуч і його знищення;
  • lcdScroll – функція зміщення всього тексту на один стовпець в потрібному напрямку (ліворуч або праворуч);
  • cursorShift – функція зміщення курсору на позицію праворуч чи ліворуч без знищення символу, що вже зображений. Призначена для редагування/виправлення тексту;
  • lcdItos – функція виводу на екран числа типу integer;
  • lcdFtos – функція виводу на екран числа типу float;
  • lcdNtos – функція виводу на екран числа типу integer з певним числом розрядів. Зайві розряди відсікаються. Розряди, яких не вистачає заповнюються нулями. Для фіксованого розташування на екрані;
  • lcdDrawBar – функція малювання індикатору виконання;
  • lcdClrBar – функція очищення індикатору виконання.

В файлі “lcd_cfg.h”

//——————————-
// DEFAULT CONFIGURATIONS
//——————————-
#define DEFAULT_DISPLAY_CONFIG DISPLAY_CONFIG_4bit_2L_5x8
#define DEFAULT_ENTRY_MODE ENTRY_MODE_INC_NO_SHIFT
#define DEFAULT_VIEW_MODE VIEW_MODE_DispOn_BlkOff_CrsOff
#define USE_BUSY_FLAG 0u /* 1 (true) or 0 (false) */

  • дисплей під'єднано по 4-х бітному режиму, на 2 або 4 рядки, з шрифтом 5х8 пікселів;
  • зліва направо, без зсуву видимої частини DDR RAM пам'яті;
  • дисплей увімкнено, блимання вимкнуто, курсор вимкнуто;
  • чи використовується сигнал дисплею R/W, в нашому випадку – ні, тому 0.

#define MCU_FREQ_VALUE SystemCoreClock/1000000 /* MHz. Minimal value = 1 MHz */
В змінній SystemCoreClock зберігається частота MCU в Герцах, ділимо на мільйон, отримуємо в мегаГерцах. Для правильних таймінгів дисплею.
//——————————-
// SET FORMATTED OUTPUT OPTIONS
//——————————-
#define USE_FORMATTED_OUTPUT 1u /* 1 (true) or 0 (false) */
#define TAB_SPACE 4u /* 1 .. 255 */

  • використовувати символи форматування /t, /n, /r в рядках з текстом – 1, не використовувати – 0;
  • довжина табуляції – 4.

//——————————-
// PROGRESS BAR OPTIONS
//——————————-
#define USE_PROGRESS_BAR 0u /* 1 (true) or 0 (false) */
#define USE_REGRESS_BAR 1u /* 1 (true) or 0 (false) */
#define PROGRESS_BAR_LINE LCD_2nd_LINE /* Select lcd line: 1, 2, 3, 4, … */
#define PROGRESS_BAR_HEIGHT 5u /* in pixel: 1(min), 2, 3, 4, 5, 6, 7, 8(max) */
#define PROGRESS_BAR_WIDTH 10u /* Number of chars in lcd line: 1, 2, .. , 8, 16, 20 */

  • використовувати індикатор виконання, що прогресує – 1, ні – 0;
  • використовувати індикатор виконання, що регресує – 1, ні -0;
  • індикатор виконання буде в другому рядку індикатора;
  • висота індикатора виконання в пікселях;
  • ширина індикатора в знакомісцях.

Замість того, щоб пояснювати в подробицях кожну функцію бібліотеки, це ви можете і самі знайти в коментарях самої бібліотеки, або в просторах інтернету. Наприклад тут. Краще приведу програму, яка наочно демонструє можливості бібліотеки.

Як вже з самого початку ми генерували проект за допомоги генератору коду ініціалізації CubeMX, то і приклад пишемо у визначених CubeMX ділянках коду.

На початку програми в файлі “main.c”, до функції “main” оголошуємо прототип функції для імітації друкування тексту з клавіатури. Сама функцію розташуємо вже після головної функції “main”:

/* USER CODE BEGIN PFP */
/* Private function prototypes ———————————————–*/
void imitationPrinting(char *ptr);
/* USER CODE END PFP */
Далі в тілі функції “main” до початку безкінечного циклу в блоці 1 – оголошуємо всі змінні які знадобляться для демонстрації:
/* USER CODE BEGIN 1 */ uint32_t SystemFreq = HAL_RCC_GetHCLKFreq(); char str[17] = {}; float pi = 3.14; int t = 123; unsigned char men1[FONT_HEIGHT] = {0x0e,0x0e,0x04,0x1f,0x04,0x1a,0x01,0x00}; unsigned char men2[FONT_HEIGHT] = {0x07,0x07,0x0a,0x07,0x02,0x05,0x09,0x00}; /* USER CODE END 1 */
Ініціалізуємо наш дисплей в ділянці позначену як “USER CODE BEGIN 2”:
/* USER CODE BEGIN 2 */
lcdInit(); // ініціалізація дисплею
/* USER CODE END 2 */
В безкінечному циклі пишемо нашу демонстраційну програму де я постарався використати всі функції бібліотеки, та написав коментар до кожного рядка:
/* USER CODE BEGIN WHILE */ while (1) { // очищення дисплею lcdClrScr(); // встановлення позиції курсору lcdGoto(LCD_1st_LINE,1); //друкуємо текстовий рядок з переносом на інший рядок lcdPuts(“CooCox CoIDE
Freq MCU “); // друкуємо частоту мікроконтролеру в Mhz lcdItos(SystemFreq/1000000); lcdPuts(” Mhz”); // чекаємо 3 секунди HAL_Delay(3000); /*
* В циклі зміщуємо все що на екрані на 16 позицій праворуч
*/ for (int i = 0; i

Источник: https://stm32withoutfear.blogspot.com/2016/10/stm32-lcd-wh16024-hd44780.html

Spec Project – Запуск LCD H44780 на STM32

Источник: http://spec-project.ucoz.ru/index/zapusk_lcd_h44780_na_stm32/0-9

Работаем с Lcd дисплеем WH1602 в STM32 f4 discovery

Хотя может ещё быть вариант с пинами в два ряда сбоку… Управляется такой дисплей очень просто. Есть два режима: по 8 проводной шине данны и по 4ёх проводной. Будем пользоваться первой системой т.к. она проще.

Другая не намного сложнее (данные нужно слать тетрадами), но отвлечёт от всей “магии”))

 Распиновка дисплея такая:

Обратите внимание, что дисплей питается от 5В в то время как контроллеры stm32 от 3,3В.

Упростим себе задачу и не будем считывать флаг занятости с устройства. Заменим эту функцию задержкой в коде. Следовательно пин 5 (RW) суем на землю.

ГРАБЛИ 1!! Пин 3 – регулировка контрастности дисплея. Ложим её на землю видим закрашенные квадраты (таким образом можно проверить целостность дисплея). Для нормального вывода туда нужно напряжения 0,7-0,9В.

Делается это либо переменным резистором либо делителем напряжения.

Теперь разберёмся с тем как управлять. Каждая буква имеет свой код. Для латинского алфавита этот код совпадает с кодом символа (char), для русского можно создать массив с нужными кодами. Смотрите даташит. Присоединили 8 пинов данных к ногам контроллера. Рекомендую это делать к соответствующим ногам чтобы сразу писать в регистр без сдвигов данных.

т.е.

DB7 — PC7
DB6 — PC6

и так далее…
Это всё существенно упрощает и даёт нам разбираться.

При старте дисплей нужно инициализировать послав несколько наборов данных, перед этим настроив ноги контроллера как выводы.

void lcd_init_gpio() {
    RCC_AHB1PeriphClockCmd(LCD_RCC_GPIO,ENABLE);
    GPIO_InitTypeDef init;
    init.GPIO_Mode = GPIO_Mode_OUT;
    init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_10 | GPIO_Pin_12;
    init.GPIO_OType = GPIO_OType_PP;     GPIO_Init(LCD_PORT,&init);
}

Теперь посылаем данные.

void lcd_init() {
    lcd_init_gpio();
    int del = 99999;
    GPIO_ResetBits(LCD_PORT, LCD_RS_Pin);  // rs = 0     delay(del);
    lcd_write_data(0b00110000); //init1     delay(del);
    lcd_write_data(0b00110000); //init2     delay(del);
    lcd_write_data(0b00110000); //init3     delay(del);
    lcd_write_data(0b00111000); // function set  8bit 2line 5×8 dots     delay(del);
    lcd_write_data(0b00001111); // display on + cursor underline + blinking     delay(del);
    lcd_write_data(0b00000001); //clear     delay(del);
    lcd_write_data(0b00000110); //entry mode set     delay(del);
    lcd_write_data(0b00000010); // return to home     delay(del);
    GPIO_SetBits(LCD_PORT,LCD_RS_Pin);  //rs = 1
}

Обратите внимание, что пин RS нужно установить в ноль, чтобы контроллер дисплея воспринял управляющие команды. Иначе он решит что это данные. Теперь о функции записи данных. она посылает данные на указанные в ноги.

Я соблюдал номера ног и номера разрядов данных, поэтому меня интересует только порт на который подключен дисплей.

void lcd_write_data(u16 data) {
    GPIO_SetBits(LCD_PORT,data | LCD_E_Pin);
    delay(0xFFFF);
    GPIO_ResetBits(LCD_PORT,LCD_E_Pin | data);
}

Мы “выставляем” данные на ноги порта соединённого с lcd и устанавливаем пин E в 1.
Ждём пока данные “зарегистрируются” и устанавливаем E в ноль. Остальные пины тоже сбрасываем.

Вот и всё. Уже сейчас можно выводить порции данных на дисплей.
Но мы ещё опишем функцию для перемещения курсора:

void lcd_set_cursor(int line,int pos) {
    pos |= 0b10000000;
    if (line == 1) {
        pos += 0x40;
    }
    lcd_write_cmd(pos);
}

line – номер строки (0 или 1)
pos – позиция. тоже от 0 до 16.

Так как коды символов совпадают, можно также написать функцию для вывода строк:

void lcd_write_str(char*str) {
    do {
        lcd_write_data(*str);
    }while(*++str);
}

Задержку я сделал просто циклом:

void delay(unsigned int s){
    while(–s > 0) {
        __NOP();
    }
}

Для работы также нужно определить константы порта и пинов RS,E
У меня вот так:

#define LCD_PORT GPIOC
#define LCD_RCC_GPIO RCC_AHB1Periph_GPIOC
#define LCD_E_Pin GPIO_Pin_12
#define LCD_RS_Pin GPIO_Pin_10

Вот собственно архив с либой.

зы не претендую на самый крутой код и прочее. Даю гарантию работоспособности в Eclipse + st-util + stm32f4discovery + wh1602b

Доказательства:

Источник: http://blablacode.ru/mikrokontrollery/295

Ссылка на основную публикацию
Adblock
detector
",css:{backgroundColor:"#000",opacity:.6}},container:{block:void 0,tpl:"
"},wrap:void 0,body:void 0,errors:{tpl:"
",autoclose_delay:2e3,ajax_unsuccessful_load:"Error"},openEffect:{type:"fade",speed:400},closeEffect:{type:"fade",speed:400},beforeOpen:n.noop,afterOpen:n.noop,beforeClose:n.noop,afterClose:n.noop,afterLoading:n.noop,afterLoadingOnShow:n.noop,errorLoading:n.noop},o=0,p=n([]),h={isEventOut:function(a,b){var c=!0;return n(a).each(function(){n(b.target).get(0)==n(this).get(0)&&(c=!1),0==n(b.target).closest("HTML",n(this).get(0)).length&&(c=!1)}),c}},q={getParentEl:function(a){var b=n(a);return b.data("arcticmodal")?b:(b=n(a).closest(".arcticmodal-container").data("arcticmodalParentEl"),!!b&&b)},transition:function(a,b,c,d){switch(d=null==d?n.noop:d,c.type){case"fade":"show"==b?a.fadeIn(c.speed,d):a.fadeOut(c.speed,d);break;case"none":"show"==b?a.show():a.hide(),d();}},prepare_body:function(a,b){n(".arcticmodal-close",a.body).unbind("click.arcticmodal").bind("click.arcticmodal",function(){return b.arcticmodal("close"),!1})},init_el:function(d,a){var b=d.data("arcticmodal");if(!b){if(b=a,o++,b.modalID=o,b.overlay.block=n(b.overlay.tpl),b.overlay.block.css(b.overlay.css),b.container.block=n(b.container.tpl),b.body=n(".arcticmodal-container_i2",b.container.block),a.clone?b.body.html(d.clone(!0)):(d.before("
"),b.body.html(d)),q.prepare_body(b,d),b.closeOnOverlayClick&&b.overlay.block.add(b.container.block).click(function(a){h.isEventOut(n(">*",b.body),a)&&d.arcticmodal("close")}),b.container.block.data("arcticmodalParentEl",d),d.data("arcticmodal",b),p=n.merge(p,d),n.proxy(e.show,d)(),"html"==b.type)return d;if(null!=b.ajax.beforeSend){var c=b.ajax.beforeSend;delete b.ajax.beforeSend}if(null!=b.ajax.success){var f=b.ajax.success;delete b.ajax.success}if(null!=b.ajax.error){var g=b.ajax.error;delete b.ajax.error}var j=n.extend(!0,{url:b.url,beforeSend:function(){null==c?b.body.html("
"):c(b,d)},success:function(c){d.trigger("afterLoading"),b.afterLoading(b,d,c),null==f?b.body.html(c):f(b,d,c),q.prepare_body(b,d),d.trigger("afterLoadingOnShow"),b.afterLoadingOnShow(b,d,c)},error:function(){d.trigger("errorLoading"),b.errorLoading(b,d),null==g?(b.body.html(b.errors.tpl),n(".arcticmodal-error",b.body).html(b.errors.ajax_unsuccessful_load),n(".arcticmodal-close",b.body).click(function(){return d.arcticmodal("close"),!1}),b.errors.autoclose_delay&&setTimeout(function(){d.arcticmodal("close")},b.errors.autoclose_delay)):g(b,d)}},b.ajax);b.ajax_request=n.ajax(j),d.data("arcticmodal",b)}},init:function(b){if(b=n.extend(!0,{},a,b),!n.isFunction(this))return this.each(function(){q.init_el(n(this),n.extend(!0,{},b))});if(null==b)return void n.error("jquery.arcticmodal: Uncorrect parameters");if(""==b.type)return void n.error("jquery.arcticmodal: Don't set parameter \"type\"");switch(b.type){case"html":if(""==b.content)return void n.error("jquery.arcticmodal: Don't set parameter \"content\"");var e=b.content;return b.content="",q.init_el(n(e),b);case"ajax":return""==b.url?void n.error("jquery.arcticmodal: Don't set parameter \"url\""):q.init_el(n("
"),b);}}},e={show:function(){var a=q.getParentEl(this);if(!1===a)return void n.error("jquery.arcticmodal: Uncorrect call");var b=a.data("arcticmodal");if(b.overlay.block.hide(),b.container.block.hide(),n("BODY").append(b.overlay.block),n("BODY").append(b.container.block),b.beforeOpen(b,a),a.trigger("beforeOpen"),"hidden"!=b.wrap.css("overflow")){b.wrap.data("arcticmodalOverflow",b.wrap.css("overflow"));var c=b.wrap.outerWidth(!0);b.wrap.css("overflow","hidden");var d=b.wrap.outerWidth(!0);d!=c&&b.wrap.css("marginRight",d-c+"px")}return p.not(a).each(function(){var a=n(this).data("arcticmodal");a.overlay.block.hide()}),q.transition(b.overlay.block,"show",1*")),b.overlay.block.remove(),b.container.block.remove(),a.data("arcticmodal",null),n(".arcticmodal-container").length||(b.wrap.data("arcticmodalOverflow")&&b.wrap.css("overflow",b.wrap.data("arcticmodalOverflow")),b.wrap.css("marginRight",0))}),"ajax"==b.type&&b.ajax_request.abort(),p=p.not(a))})},setDefault:function(b){n.extend(!0,a,b)}};n(function(){a.wrap=n(document.all&&!document.querySelector?"html":"body")}),n(document).bind("keyup.arcticmodal",function(d){var a=p.last();if(a.length){var b=a.data("arcticmodal");b.closeOnEsc&&27===d.keyCode&&a.arcticmodal("close")}}),n.arcticmodal=n.fn.arcticmodal=function(a){return e[a]?e[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?void n.error("jquery.arcticmodal: Method "+a+" does not exist"):q.init.apply(this,arguments)}}(jQuery)}var debugMode="undefined"!=typeof debugFlatPM&&debugFlatPM,duplicateMode="undefined"!=typeof duplicateFlatPM&&duplicateFlatPM,countMode="undefined"!=typeof countFlatPM&&countFlatPM;document["wri"+"te"]=function(a){let b=document.createElement("div");jQuery(document.currentScript).after(b),flatPM_setHTML(b,a),jQuery(b).contents().unwrap()};function flatPM_sticky(c,d,e=0){function f(){if(null==a){let b=getComputedStyle(g,""),c="";for(let a=0;a=b.top-h?b.top-h{const d=c.split("=");return d[0]===a?decodeURIComponent(d[1]):b},""),c=""==b?void 0:b;return c}function flatPM_testCookie(){let a="test_56445";try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(a){return!1}}function flatPM_grep(a,b,c){return jQuery.grep(a,(a,d)=>c?d==b:0==(d+1)%b)}function flatPM_random(a,b){return Math.floor(Math.random()*(b-a+1))+a}
");let k=document.querySelector(".flat_pm_modal[data-id-modal=\""+a.ID+"\"]");if(-1===d.indexOf("go"+"oglesyndication")?flatPM_setHTML(k,d):jQuery(k).html(b+d),"px"==a.how.popup.px_s)e.bind(h,()=>{e.scrollTop()>a.how.popup.after&&(e.unbind(h),f.unbind(i),j())}),void 0!==a.how.popup.close_window&&"true"==a.how.popup.close_window&&f.bind(i,()=>{e.unbind(h),f.unbind(i),j()});else{let b=setTimeout(()=>{f.unbind(i),j()},1e3*a.how.popup.after);void 0!==a.how.popup.close_window&&"true"==a.how.popup.close_window&&f.bind(i,()=>{clearTimeout(b),f.unbind(i),j()})}f.on("click",".flat_pm_modal .flat_pm_crs",()=>{jQuery.arcticmodal("close")})}if(void 0!==a.how.outgoing){let b,c="0"==a.how.outgoing.indent?"":" style=\"bottom:"+a.how.outgoing.indent+"px\"",e="true"==a.how.outgoing.cross?"":"",f=jQuery(window),g="scroll.out"+a.ID,h=void 0===flatPM_getCookie("flat_out_"+a.ID+"_mb")||"false"!=flatPM_getCookie("flat_out_"+a.ID+"_mb"),i=document.createElement("div"),j=jQuery("body"),k=()=>{void 0!==a.how.outgoing.cookie&&"false"==a.how.outgoing.cookie&&h&&(jQuery(".flat_pm_out[data-id-out=\""+a.ID+"\"]").addClass("show"),j.on("click",".flat_pm_out[data-id-out=\""+a.ID+"\"] .flat_pm_crs",function(){flatPM_setCookie("flat_out_"+a.ID+"_mb",!1)})),(void 0===a.how.outgoing.cookie||"false"!=a.how.outgoing.cookie)&&jQuery(".flat_pm_out[data-id-out=\""+a.ID+"\"]").addClass("show")};switch(a.how.outgoing.whence){case"1":b="top";break;case"2":b="bottom";break;case"3":b="left";break;case"4":b="right";}jQuery("body > *").eq(0).before("
"+e+"
");let m=document.querySelector(".flat_pm_out[data-id-out=\""+a.ID+"\"]");-1===d.indexOf("go"+"oglesyndication")?flatPM_setHTML(m,d):jQuery(m).html(e+d),"px"==a.how.outgoing.px_s?f.bind(g,()=>{f.scrollTop()>a.how.outgoing.after&&(f.unbind(g),k())}):setTimeout(()=>{k()},1e3*a.how.outgoing.after),j.on("click",".flat_pm_out .flat_pm_crs",function(){jQuery(this).parent().removeClass("show").addClass("closed")})}countMode&&(flat_count["block_"+a.ID]={},flat_count["block_"+a.ID].count=1,flat_count["block_"+a.ID].click=0,flat_count["block_"+a.ID].id=a.ID)}catch(a){console.warn(a)}}function flatPM_start(){let a=flat_pm_arr.length;if(0==a)return flat_pm_arr=[],void jQuery(".flat_pm_start, .flat_pm_end").remove();flat_body=flat_body||jQuery("body"),!flat_counter&&countMode&&(flat_counter=!0,flat_body.on("click","[data-flat-id]",function(){let a=jQuery(this),b=a.attr("data-flat-id");flat_count["block_"+b].click++}),flat_body.on("mouseenter","[data-flat-id] iframe",function(){let a=jQuery(this),b=a.closest("[data-flat-id]").attr("data-flat-id");flat_iframe=b}).on("mouseleave","[data-flat-id] iframe",function(){flat_iframe=-1}),jQuery(window).on("beforeunload",()=>{jQuery.isEmptyObject(flat_count)||jQuery.ajax({async:!1,type:"POST",url:ajaxUrlFlatPM,dataType:"json",data:{action:"flat_pm_ajax",data_me:{method:"flat_pm_block_counter",arr:flat_count}}})}).on("blur",()=>{-1!=flat_iframe&&flat_count["block_"+flat_iframe].click++})),flat_userVars.init();for(let b=0;bflat_userVars.textlen||void 0!==a.chapter_sub&&a.chapter_subflat_userVars.titlelen||void 0!==a.title_sub&&a.title_subc&&cc&&c>d&&(b=flatPM_addDays(b,-1)),b>e||cd||c-1!=flat_userVars.referer.indexOf(a))||void 0!==a.referer.referer_disabled&&-1!=a.referer.referer_disabled.findIndex(a=>-1!=flat_userVars.referer.indexOf(a)))&&(c=!0),c||void 0===a.browser||(void 0===a.browser.browser_enabled||-1!=a.browser.browser_enabled.indexOf(flat_userVars.browser))&&(void 0===a.browser.browser_disabled||-1==a.browser.browser_disabled.indexOf(flat_userVars.browser)))){if(c&&void 0!==a.browser&&void 0!==a.browser.browser_enabled&&-1!=a.browser.browser_enabled.indexOf(flat_userVars.browser)&&(c=!1),!c&&(void 0!==a.geo||void 0!==a.role)&&(""==flat_userVars.ccode||""==flat_userVars.country||""==flat_userVars.city||""==flat_userVars.role)){flat_pm_then.push(a),flatPM_setWrap(a),flat_body.hasClass("flat_pm_block_geo_role")||(flat_body.addClass("flat_pm_block_geo_role"),flatPM_ajax("flat_pm_block_geo_role")),c=!0}c||(flatPM_setWrap(a),flatPM_next(a))}}}let b=jQuery(".flatPM_sticky");b.each(function(){let a=jQuery(this),b=a.data("height")||350,c=a.data("top");a.wrap("
");let d=a.parent()[0];flatPM_sticky(this,d,c)}),debugMode||countMode||jQuery("[data-flat-id]:not([data-id-out]):not([data-id-modal])").contents().unwrap(),flat_pm_arr=[],jQuery(".flat_pm_start, .flat_pm_end").remove()}
В данной статье хочу поделиться опытом в сопряжении контроллера STM32F103RBT6 и простенького LCD дисплея на основе контроллера H44780. 
Подключать его не сложно, вот простенькая схема.

Здесь мы рассматриваем четырех проводную систему подключения, это нам позволит сэкономить порты микроконтроллера 

Как подключить питание можно посмотреть в Datasheet здесьПротокол обмена и принципы работы можно посмотреть здесь и здесь.

Теперь когда мы все подключили, начнем это дела запускать.

Для начала нам понадобиться библиотека для работы с дисплеем, 

качаем ее здесь для STM32F1xx или здесь для STM32F051 так же не забываем про Delay

В Header файле нам надо всего лишь указать к каким портам подключен порт служебных линий и порт данных

/*************************************************

 * Здесь ручками установим порты подключения LCD *

 *************************************************/

#define PORT_RS_RW_EE                         GPIOC                                      //Порт подключения служебных линий

#define PORT_DATA                                 GPIOC                                      //Порт подключения шины данных 4bit

#define INT_PORT_RS_RW_EE                   RCC_APB2Periph_GPIOC           //Порт подключения служебных линий, шины тактирования

#define INT_PORT_DATA                           RCC_APB2Periph_GPIOC           //Порт подключения шины данных 4bit, шины тактирования

#define RS                                               GPIO_Pin_0                              //Служебная шина RS

#define RW                                              GPIO_Pin_1                              //Служебная шина RW

#define EE                                               GPIO_Pin_2                              //Служебная шина E

#define D4                                               GPIO_Pin_3                             //Шина данных D4

#define D5                                               GPIO_Pin_4                             //Шина данных D5

#define D6                                               GPIO_Pin_5                             //Шина данных D6

#define D7                                               GPIO_Pin_6                             //Шина данных D7

/*********************************************************************************************************/

Теперь простенький код для LCD

LCD_INIT(); //Инициализация LCD

LCD_PUT(“Hello World”);         //Вывод на дисплей Hello World

Да, не забываем инициализировать дисплей

Вот собственно, что получилось. 

Если вы посмотрите header файл там можно будет найти следующие функции

/*******************************Функции для работы с дисплеем*********************************************/

void PORT_INIT(void);                                       //Инициализация Портов работы с LCD

void LCD_INIT(void);                                         //Инициализирует LCD: 2строки по 16 символов, 4bit шина данных,

void LCD_CLEAR(void);                                      //Очистка экрана

void LCD_GOTO(uint8_t x, uint8_t y);                   //Перевод курсора в нужное положение

void LCD_PUT(char *STR);                                 //Вывод на экран Символов

void LCD_COMMAND(uint8_t byte);                      //Отправка каманд по 4бита

void WRIGHT_HALF_BYTE(uint8_t byte);               //Запись полубайта

void LCD_SEND_DATA(uint8_t data);                  //отправка данных в LCD (код ASCII)

/********************************************************************************************************/

Вот и все что хотел сказать по этой теме, если есть вопросы, пишите.