Итак, сегодня мы будем работать с дисплеем на базе популярного контроллера HD44780. Уже была статья про это на нашем сайте (вот она), там мы и команды обсудили, и с подключением разобрались, и пример написали и протестировали. Казалось бы что тут еще обсуждать? На самом деле есть что )
В комментариях к той статье попросили разобрать пример с использованием 4-х битной шины данных (как вы помните есть два варианта общения с дисплеем, а именно с использованием 8-ми битной шины, либо 4-х битной), так что сегодняшняя статья посвящена именно этому.
Теорию обсуждать я думаю не будем снова, все есть в уже упомянутой статье, так что сразу перейдем к особенностям работы в 4-х битном режиме. Хотя никаких особенностей-то и нет по большому счету. Если в 8-ми битном режиме мы передаем всю команду целиком, используя выводы DB7-DB0, то в 4-х битном команда делится на две части и передается она соответственно по частям (в этом режиме задействуем только 4 вывода дисплея - DB7-DB4).
Довольно часто при работе с дисплеями проблема возникает в первую очередь с инициализацией, поэтому скачиваем документацию на используемый дисплей и ищем что-нибудь похожее на набор команд, которыми необходимо проинициализировать дисплей. Я, например, использую дисплей WH1602. Вот интересующая нас часть даташита на него:
Как видите, здесь как раз используется 4-х битная шина данных. То что надо!
Давайте теперь попробуем написать программу, которая что-нибудь выведет на дисплей. Я буду использовать микроконтроллер STM32F407, ну и, соответственно, плату STM32F4 Discovery. Итак, создаем как обычно новый проект, добавляем в него файлы библиотек CMSIS и SPL:
Дисплей у меня подключен следующим образом:
- RS – PC2
- R/W – PB10
- E – PB14
- DB7 – PD2
- DB6 – PC12
- DB5 – PA8
- DB4 – PA10
Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32, а также небольшая подборка на смежную тему из нового курса:
- Подключение дисплея на базе ST7735 к микроконтроллеру STM32.
- Дисплей на базе ST7735 и STM32. Вывод изображения.
- Дисплей на базе контроллера SSD1306. Библиотека для STM32.
- STM32 и семисегментный индикатор. Динамическая индикация.
Определим все эти выводы следующим образом, чтобы сделать нашу программу более универсальной:
// 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) { } } /***************************************************************************************/
Компилируем, прошиваем, запускаем, и вот он результат:
Как видим, все работает 👍 Поэтому на этом заканчиваем обсуждение 4-х битного режима передачи данных, до скорых встреч и новых статей!