Работа с дисплеем на базе HD44780

Итак, сегодня мы будем работать с дисплеем на базе популярного контроллера HD44780. Уже была статья про это на нашем сайте (вот она), там мы и команды обсудили, и с подключением разобрались, и пример написали и протестировали. Казалось бы что тут еще обсуждать? На самом деле есть что 😉 В комментариях к той статье попросили разобрать пример с использованием 4-х битной шины данных (как вы помните есть два варианта общения с дисплеем, а именно с использованием 8-ми битной шины, либо 4-х битной), так что сегодняшняя статья посвящена именно этому.

Теорию обсуждать я думаю не будем снова, все есть в уже упомянутой статье, так что сразу перейдем к особенностям работы в 4-х битном режиме. Хотя никаких особенностей то и нет по большому счету. Если в 8-ми битном режиме мы передаем всю команду целиком, используя выводы DB7-DB0, то в 4-х битном команда делится на две части и передается она соответственно по частям (в этом режиме задействуем только 4 вывода дисплея — DB7-DB4). Вот так вот все просто и понятно 😉

Довольно часто при работе с дисплеями проблема возникает в первую очередь с инициализацией, поэтому скачиваем документацию на используемый дисплей и ищем что-нибудь похожее на набор команд, которыми необходимо проинициализировать дисплей. Я, например, использую дисплей WH1602. Вот интересующая нас часть даташита на него:
Работа с дисплеем HD44780
Как видите, здесь как раз используется 4-х битная шина данных. То что надо!

Давайте теперь попробуем написать программу, которая что-нибудь выведет на дисплей. Я буду использовать микроконтроллер STM32F407, ну и, соответственно, плату STM32F4 Discovery. Итак, создаем как обычно новый проект, добавляем в него файлы библиотек CMSIS и SPL:
Создание нового проекта
Дисплей у меня подключен следующим образом:
RS – PC2
R/W – PB10
E – PB14
DB7 – PD2
DB6 – PC12
DB5 – PA8
DB4 – PA10
Определим все эти выводы следующим образом, чтобы сделать нашу программу более менее универсальной:

// Set RS port
#define MT_WH1602_RS_PORT               (GPIOC)
// Set RS pin
#define MT_WH1602_RS_PIN		(GPIO_Pin_2)
 
// Set RW port
#define MT_WH1602_RW_PORT		(GPIOB)	
// Set RW pin
#define MT_WH1602_RW_PIN		(GPIO_Pin_10)
 
// Set E port
#define MT_WH1602_E_PORT		(GPIOB)	
// Set E pin
#define MT_WH1602_E_PIN	  		(GPIO_Pin_14)
 
// Set DB7 port
#define MT_WH1602_DB7_PORT		(GPIOD)	
// Set DB7 pin
#define MT_WH1602_DB7_PIN		(GPIO_Pin_2)
 
// Set DB6 port
#define MT_WH1602_DB6_PORT		(GPIOC)	
// Set DB6 pin
#define MT_WH1602_DB6_PIN        	(GPIO_Pin_12)
 
// Set DB5 port
#define MT_WH1602_DB5_PORT		(GPIOA)
// Set DB5 pin
#define MT_WH1602_DB5_PIN	        (GPIO_Pin_8)
 
// Set DB4 port
#define MT_WH1602_DB4_PORT		(GPIOA)
// Set DB4 pin
#define MT_WH1602_DB4_PIN		(GPIO_Pin_10)

Также не забываем определить:

// Bit masks for different bits in byte
#define BIT_7_MASK	    (0x80)
#define BIT_6_MASK	    (0x40)
#define BIT_5_MASK	    (0x20)
#define BIT_4_MASK	    (0x10)
#define BIT_3_MASK	    (0x08)
#define BIT_2_MASK	    (0x04)
#define BIT_1_MASK	    (0x02)
#define BIT_0_MASK	    (0x01)

Теперь необходимо проинициализировать все ножки микроконтроллера, которые нам понадобятся:

GPIO_InitTypeDef MT_GPIOcfg;
 
//**************************************************************************************************
void MT_Init()
{
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
 
    // Initialize all pins connected to the WH1602 module
 
    GPIO_StructInit(&MT_GPIOcfg);
    MT_GPIOcfg.GPIO_Pin = MT_WH1602_RS_PIN;
    MT_GPIOcfg.GPIO_Mode = GPIO_Mode_OUT;
    MT_GPIOcfg.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MT_WH1602_RS_PORT, &MT_GPIOcfg);
 
    GPIO_StructInit(&MT_GPIOcfg);
    MT_GPIOcfg.GPIO_Pin = MT_WH1602_RW_PIN;
    MT_GPIOcfg.GPIO_Mode = GPIO_Mode_OUT;
    MT_GPIOcfg.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MT_WH1602_RW_PORT, &MT_GPIOcfg);
 
    GPIO_StructInit(&MT_GPIOcfg);
    MT_GPIOcfg.GPIO_Pin = MT_WH1602_E_PIN;
    MT_GPIOcfg.GPIO_Mode = GPIO_Mode_OUT;
    MT_GPIOcfg.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MT_WH1602_E_PORT, &MT_GPIOcfg);
 
    GPIO_StructInit(&MT_GPIOcfg);
    MT_GPIOcfg.GPIO_Pin = MT_WH1602_DB7_PIN;
    MT_GPIOcfg.GPIO_Mode = GPIO_Mode_OUT;
    MT_GPIOcfg.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MT_WH1602_DB7_PORT, &MT_GPIOcfg);
 
    GPIO_StructInit(&MT_GPIOcfg);
    MT_GPIOcfg.GPIO_Pin = MT_WH1602_DB6_PIN;
    MT_GPIOcfg.GPIO_Mode = GPIO_Mode_OUT;
    MT_GPIOcfg.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MT_WH1602_DB6_PORT, &MT_GPIOcfg);
 
    GPIO_StructInit(&MT_GPIOcfg);
    MT_GPIOcfg.GPIO_Pin = MT_WH1602_DB5_PIN;
    MT_GPIOcfg.GPIO_Mode = GPIO_Mode_OUT;
    MT_GPIOcfg.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MT_WH1602_DB5_PORT, &MT_GPIOcfg);
 
    GPIO_StructInit(&MT_GPIOcfg);
    MT_GPIOcfg.GPIO_Pin = MT_WH1602_DB4_PIN;
    MT_GPIOcfg.GPIO_Mode = GPIO_Mode_OUT;
    MT_GPIOcfg.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MT_WH1602_DB4_PORT, &MT_GPIOcfg);
} 
 
 
 
//**************************************************************************************************

С микроконтроллером разобрались, теперь нужно проинициализировать дисплей. Для этого реализуем следующие функции (в соответствии с рекомендуемой последовательностью команд из даташита):

//**************************************************************************************************
// Функция для реализации задержки
void MT_Delay(uint32_t us) 
{
    volatile uint32_t i;
    RCC_ClocksTypeDef rcc;
 
    RCC_GetClocksFreq (&rcc);
    i = (rcc.HCLK_Frequency/10000000)*us;
 
    for (; i != 0; i--);
}
 
// Строб импульс
void MT_DataReadWrite()
{
    GPIO_SetBits(MT_WH1602_E_PORT, MT_WH1602_E_PIN);
    MT_Delay(2);
    GPIO_ResetBits(MT_WH1602_E_PORT, MT_WH1602_E_PIN);
} 
 
// И наконец функции для инициализации дисплея
void MT_FunctionSet8bit()
{
    GPIO_ResetBits(MT_WH1602_RS_PORT, MT_WH1602_RS_PIN);
    GPIO_ResetBits(MT_WH1602_RW_PORT, MT_WH1602_RW_PIN);
    GPIO_ResetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    GPIO_ResetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    GPIO_SetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    GPIO_SetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
 
    MT_DataReadWrite();
}
 
void MT_FunctionSet4bit(bool N, bool F)
{
    GPIO_ResetBits(MT_WH1602_RS_PORT, MT_WH1602_RS_PIN);
    GPIO_ResetBits(MT_WH1602_RW_PORT, MT_WH1602_RW_PIN);
    GPIO_ResetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    GPIO_ResetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    GPIO_SetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    GPIO_ResetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
 
    MT_DataReadWrite();
    MT_Delay(100);
 
    if (N == 1)
    {
        GPIO_SetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    }
    else
    {
	GPIO_ResetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    }
 
    if (F == 1)
    {
        GPIO_SetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    }
    else
    {
        GPIO_ResetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    }
    GPIO_ResetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    GPIO_ResetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
 
    MT_DataReadWrite();
}
 
void MT_DisplayOnOff()
{
    GPIO_ResetBits(MT_WH1602_RS_PORT, MT_WH1602_RS_PIN);
    GPIO_ResetBits(MT_WH1602_RW_PORT, MT_WH1602_RW_PIN);
 
    GPIO_ResetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    GPIO_ResetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    GPIO_ResetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    GPIO_ResetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
 
    MT_DataReadWrite();
    MT_Delay(100);	
 
    GPIO_SetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    GPIO_SetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    GPIO_ResetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    GPIO_ResetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
 
    MT_DataReadWrite();
}
 
void MT_DisplayClear()
{
    GPIO_ResetBits(MT_WH1602_RS_PORT, MT_WH1602_RS_PIN);
    GPIO_ResetBits(MT_WH1602_RW_PORT, MT_WH1602_RW_PIN);
 
    GPIO_ResetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    GPIO_ResetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    GPIO_ResetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    GPIO_ResetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
 
    MT_DataReadWrite();	
    MT_Delay(100);
 
    GPIO_ResetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    GPIO_ResetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    GPIO_ResetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    GPIO_SetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
 
    MT_DataReadWrite();
}
 
 
 
//**************************************************************************************************

Кроме всего этого нам понадобится функция для записи данных в память дисплея для вывода их на экран:

//**************************************************************************************************
void MT_WriteData(uint8_t data)
{	
    GPIO_SetBits(MT_WH1602_RS_PORT, MT_WH1602_RS_PIN);
    GPIO_ResetBits(MT_WH1602_RW_PORT, MT_WH1602_RW_PIN);
 
    if (data & BIT_7_MASK)
    {
    	GPIO_SetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    }
    else
    {
	GPIO_ResetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    }
 
    if (data & BIT_6_MASK)
    {
	GPIO_SetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    }
    else
    {
	GPIO_ResetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    }
 
    if (data & BIT_5_MASK)
    {
	GPIO_SetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    }
    else
    {
	GPIO_ResetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    }
 
    if (data & BIT_4_MASK)
    {
    	GPIO_SetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
    }
    else
    {
    	GPIO_ResetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
    }
 
    MT_Delay(100);
    MT_DataReadWrite();
 
    if (data & BIT_3_MASK)
    {
    	GPIO_SetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    }
    else
    {
    	GPIO_ResetBits(MT_WH1602_DB7_PORT, MT_WH1602_DB7_PIN);
    }
 
    if (data & BIT_2_MASK)
    {
    	GPIO_SetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    }
    else
    {
    	GPIO_ResetBits(MT_WH1602_DB6_PORT, MT_WH1602_DB6_PIN);
    }
 
    if (data & BIT_1_MASK)
    {
    	GPIO_SetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    }
    else
    {
	GPIO_ResetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    }
 
    if (data & BIT_0_MASK)
    {
    	GPIO_SetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
    }
    else
    {
	GPIO_ResetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
    }
 
    MT_Delay(100);
    MT_DataReadWrite();
}
 
 
 
//**************************************************************************************************

Итак, все функции которые нам понадобятся готовы ) О том, как подаются команды на дисплей мы уже говорили в предыдущей статье про этот дисплей, обязательно посмотрите, если этого еще не сделали!;) А мы тем временем подошли к главному — к реализации функции main().

//**************************************************************************************************
int main(void)
{
    MT_Init();
    MT_Delay(1000);
 
    MT_FunctionSet8bit();
    MT_Delay(1000);
 
    MT_FunctionSet4bit(1, 1);
    MT_Delay(1000);
 
    MT_FunctionSet4bit(1, 1);
    MT_Delay(1000);
 
    MT_DisplayOnOff();
    MT_Delay(1000);
 
    MT_DisplayClear();
    MT_Delay(10000);
 
    MT_WriteData(0x34);
    MT_Delay(1000);
    MT_WriteData(0x20);
    MT_Delay(1000);
    MT_WriteData(0x62);
    MT_Delay(1000);
    MT_WriteData(0x69);
    MT_Delay(1000);
    MT_WriteData(0x74);
    MT_Delay(1000);
    MT_WriteData(0x20);
    MT_Delay(1000);
    MT_WriteData(0x6D);
    MT_Delay(1000);
    MT_WriteData(0x6F);
    MT_Delay(1000);
    MT_WriteData(0x64);
    MT_Delay(1000);
    MT_WriteData(0x65);
    MT_Delay(1000);    
 
    while(1)
    {
        __NOP();
    }
}
 
 
 
//**************************************************************************************************

Компилируем, прошиваем, запускаем! И вот он результат:
Работа с дисплеем
Как видим, все работает ) Поэтому на этом заканчиваем обсуждение 4-х битного режима передачи данных, до скорых встреч и новых статей!

Понравилась статья? Поделись с друзьями!

Работа с дисплеем на базе HD44780: 27 комментариев
  1. Спасибо большое за статью. А не могли бы вы написать статью про динамическую индикацию с таким же примером? Кажется всё просто, а как в железо зашил так и началось по мимо нужного разряда ещё соседний подсвечивается. Думал проблема в частоте или малой задержке, но поигравшись с ними я ничего странного не обнаружил и положительного эффекта не получил.

      • АЦП измеряет напряжение, эту информацию необходимо выводить на 3-х разрядный семисегментный светодиодный индикатор. Динамическая индикация на AVR у меня работала без проблем, а вот когда решил перейти на STM начались чудеса. Сейчас просто пытаюсь вывести любое фиксированное число не прибегая пока к АЦП, но и это не получается. В перспективе АЦП будет измерять 4-5 параметров, какие-то выводить на индикатор, а по каким-то формировать определённый код на своих ножках. Вот на этом форуме (http://forum.cxem.net/index.php?showtopic=126568) я задавал вопрос, но полезного ответа так и не получил.

        • Что-то не нашел у себя никакого сегментного индикатора..( Если получится куплю на недельке, попробую

  2. Есть вопрос по схеме, выводы дисплея подключаются напрямую к контроллеру,дисплей запитывать от 3.3 или от 5 В от платы?

  3. Error[Pe020]: identifier «BIT_4_MASK» is undefined C:\Program Files (x86)\IAR Systems\Embedded Workbench 6.5\arm\examples\ST\STM32F2xx\IAR-STM32F207ZG-SK\LCD\Firmware\src\main.c 243
    В IAR не компилируется. Подскажите пожалуйста куда копать?

      • Можно добавить прям в main.c:

        #define BIT_7_MASK    (0x80)
        #define BIT_6_MASK    (0x40)
        #define BIT_5_MASK    (0x20)
        #define BIT_4_MASK    (0x10)
        #define BIT_3_MASK    (0x08)
        #define BIT_2_MASK    (0x04)
        #define BIT_1_MASK    (0x02)
        #define BIT_0_MASK    (0x01)
  4. При переключении на следующий разряд, нужно его принудительно гасить, выставляя 0.

  5. Автор, спасибо большое за статью.
    но мой индикатор работал не стабильно. После долгих ковыряний выснилось, что дело в неправильном формировании задержки.
    Эту строку пришлось сделать так.

    i = (rcc.HCLK_Frequency / 8000000) * us;

  6. Добрый день
    По вашей программе у меня на STM32F205 индикатор 1602А запускался каждый 2-й раз но после удаления строк :
    GPIO_ResetBits(MT_WH1602_DB5_PORT, MT_WH1602_DB5_PIN);
    GPIO_ResetBits(MT_WH1602_DB4_PORT, MT_WH1602_DB4_PIN);
    в конце функции void MT_FunctionSet4bit(bool N, bool F)
    и изменении
    MT_FunctionSet4bit(1, 1);
    MT_Delay(1000);

    MT_FunctionSet4bit(1, 1);
    MT_Delay(1000);
    на
    MT_FunctionSet4bit(0, 0);
    MT_Delay(1000);

    MT_FunctionSet4bit(0, 0);
    MT_Delay(1000);
    все стало ОК

  7. Здравствуйте, подскажите что поправить в библиотеке чтобы пример заработал с F3Discovery?

  8. Добрый день! Помогите разобраться пробую подключить дисплей WH1602 к stm32f100RB порты перенастроил, но компилятор ругается на строку void MT_FunctionSet4bit(bool N, bool F)
    точнее main.c:133:31: error: expected ‘)’ before ‘N’ подскажите что нужно подправить? В си только начинаю разбираться.

    и еще заменил строку
    MT_GPIOCfg.GPIO_Mode = GPIO_Mode_Out;
    на
    MT_GPIOCfg.GPIO_Mode = GPIO_Mode_Out_PP;
    потому что компилятор ругался на неправильный параметр GPIO_Mode_Out, порт надо настраивать в Push-Pull или Open Drain?

    • Ошибка скорее всего из-за типа данных bool.

      А по поводу порта — у stm32f100rb просто немного другие значения возможные в SPL. В данном случае надо Push-Pull использовать.

  9. Кто подскажет: планирую этот пример использовать как библиотеку для работы с HD44780? И как лучше реализовать: вставкой перед main или как отдельный файл, прикрепив в проекте и просто вызывая функцию вывода на экран?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *