Продолжаем тему поддержки программного I2C в контроллере ESP8266 и сегодня мы немного закрепим знания по данной теме, написав небольшую программу по работе с символьным дисплеем разрешением в 4 строки по 20 символов, подключенному по шине I2C к нашему контроллеру.
С данным переходником мы неоднократно уже работали, поэтому изучать нам тонкости его работы нет смысла, лишь напомню, что он преобразует цифровой код, поступающий последовательно по I2C в параллельный 8-разрядный код.
Так как наш дисплей не очень хорошо работает с питанием 3,3 вольта и ему просто не хватит контрастности, то питать мы его будем сквозь переходник от независимого источника 5 вольт, в качестве которого мы возьмём обычный DC-DC преобразователь, на вход которого подключим 12 вольт, а выход отрегулируем на 5 вольт. На всякий случай также ограничим немного ток, я ограничил до 1 ампера, благо позволяет мой преобразователь, чтобы при случайном неправильном подключении.
Вот так выглядят наши схематические соединения (дисплей показан уже в работе, в контроллере ESP8266 функционирует уже рабочий код, который мы и будем в данном уроке писать)
Также ценность данного урока будет не только в закреплении работы с шиной, но и в том, что такой дисплей нам ещё пригодится для мониторинга некоторой работы с контроллером, в частности работы на приём по шине UART.
Поэтому начнём.
Проект мы, как всегда, заново писать не будем, а сделаем его из проекта прошлого урока с именем I2C_EEPROM и назовём его I2C_LCD2004.
Откроем наш проект в Eclipse и, так как мы UART использовать не будем, то удалим подключение его библиотеки
#include «driver/uart.h»
Также уберём вот это в user_init
// Configure the UART
uart_init(BIT_RATE_115200, BIT_RATE_115200);
Вот этот код тоже удаляем
ets_delay_us(100000);
os_printf(«\r\n»);
//AT24C_WriteBytes (0x032A, wr_value, 20);
AT24C_ReadBytes(0x032A , rd_value, 20);
for(i=0;i<20;i++)
{
//os_printf(«%02X «,wr_value[i]);
os_printf(«%02X «,rd_value[i]);
}
os_printf(«\r\n»);
Также удалим пока весь код из бесконечного цикла.
Данные макросы и глобальные переменные тоже удалим
#define LED 2
#define I2C_REQUEST_WRITE 0x00
#define I2C_REQUEST_READ 0x01
#define SLAVE_OWN_ADDRESS 0xA0
//——————————————————
uint8_t rd_value[20] = {0};
uint8_t wr_value[20] = {0x14,0x13,0x12,0x11,0x10,
0x0F,0x0E,0x0D,0x0C,0x0B,
0x0A,0x09,0x08,0x07,0x06,
0x05,0x04,0x03,0x02,0x01};
Функции AT24C_WriteBytes и AT24C_ReadBytes удалим тоже, писать в шину мы будем не здесь.
Опустим ножки шины в функции user_init()
1 2 |
i2c_master_gpio_init(); I2C_MASTER_SDA_LOW_SCL_LOW(); |
Добавим файлы i2c_user.c и i2c_user.h в наш проект в соответствующие папки, как это делать мы давно знаем. Файлы данные будут такого содержания
1 2 3 4 5 6 7 8 9 |
#ifndef I2C_USER_H_ #define I2C_USER_H_ //------------------------------------------------ #include "osapi.h" #include "driver/i2c_master.h" //------------------------------------------------ void I2C_SendByteByADDR(uint8_t c,uint8_t addr); //------------------------------------------------ #endif /* I2C_USER_H_ */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include "i2c_user.h" //------------------------------------------------ void I2C_SendByteByADDR(uint8_t c,uint8_t addr) { uint16_t i; uint8_t ack; i2c_master_start(); //Transmit Address SLAVE i2c_master_writeByte(addr); ack = i2c_master_checkAck(); if(!ack) { os_printf("ADDR not ack\r\n"); } //Send data i2c_master_writeByte(c); i2c_master_send_ack(); i2c_master_stop(); } //------------------------------------------------ |
В файле реализации у нас находится только одна функция, которая отправляет байт в шину, как это делается мы знаем.
Аналогичным образом также добавим файлы для работы с дисплеем lcd.h и lcd.c пока следующего содержания
1 2 3 4 5 6 7 |
#ifndef LCD_H_ #define LCD_H_ //------------------------------------------------ #include "osapi.h" //------------------------------------------------ //------------------------------------------------ #endif /* LCD_H_ */ |
1 2 3 |
#include "lcd.h" #include "i2c_user.h" //------------------------------------------------ |
Подключим нашу библиотеку в файле main.c
1 2 |
#include "driver/i2c_master.h" #include "lcd.h" |
Также не забываем внести изменения в Makefile, так как сами файлы туда не добавятся (пока).
Вот эти строки вставим после сборки цели main.o
1 2 3 4 5 6 7 |
src/lcd.o: src/lcd.c inc/user_config.h @echo "CC src/lcd.o" @$(CC) -Iinc -I$(SDK_INC) -I$(SDK_INC)/json -I$(SDK_DRIVER_INC) $(CC_FLAGS) src/lcd.c -o src/lcd.o src/i2c_user.o: src/i2c_user.c inc/user_config.h @echo "CC src/i2c_user.o" @$(CC) -Iinc -I$(SDK_INC) -I$(SDK_INC)/json -I$(SDK_DRIVER_INC) $(CC_FLAGS) src/i2c_user.c -o src/i2c_user.o |
В сборке цели app_app.a также добавим объектные файлы здесь
build/app_app.a: src/main.o src/lcd.o src/i2c_user.o
здесь
@$(AR) cru build/app_app.a src/main.o src/lcd.o src/i2c_user.o
и в цели clean
@rm -v src/main.o src/lcd.o src/i2c_user.o build/app_app.a build/app.out build/app.out-0x00000.bin build/app.out-0x10000.bin
Попробуем собрать наш проект, если всё нормально, то движемся дальше.
В файле lcd.c добавим парочку глобальных переменных и функцию отправки байта в дисплей, чтобы каждый раз не писать адрес
1 2 3 4 5 6 7 8 9 10 |
#include "i2c_user.h" //------------------------------------------------ uint8_t portlcd=0; //ячейка для хранения данных порта микросхемы расширения uint32_t CUR_POS; //------------------------------------------------ void LCD_WriteByteI2CLCD(uint8_t bt) { I2C_SendByteByADDR(bt,0x4E); } //------------------------------------------------ |
В заголовочный файл lcd.h добавим несколько макросов для удобного управления служебными ножками дисплея
1 2 3 4 5 6 7 8 9 10 |
#include "osapi.h" //------------------------------------------------ #define e_set() LCD_WriteByteI2CLCD(portlcd|=0x04) ////установка линии Е в 1 #define e_reset() LCD_WriteByteI2CLCD(portlcd&=~0x04) //установка линии Е в 0 #define rs_set() LCD_WriteByteI2CLCD(portlcd|=0x01) //установка линии RS в 1 #define rs_reset() LCD_WriteByteI2CLCD(portlcd&=~0x01) //установка линии RS в 0 #define setled() LCD_WriteByteI2CLCD(portlcd|=0x08) //установка линии BL в 1 #define setwrite() LCD_WriteByteI2CLCD(portlcd&=~0x02) //установка линии RW в 0 #define setread() LCD_WriteByteI2CLCD(portlcd|=0x02) //установка линии RW в 1 //------------------------------------------------ |
Вернёмся в файл lcd.c и добавим там функцию передачи половины байта в дисплей
1 2 3 4 5 6 7 8 9 10 11 |
//------------------------------------------------ void sendhalfbyte(uint8_t c) { c<<=4; LCD_WriteByteI2CLCD((portlcd|=0x04)|c);//включаем линию E ets_delay_us(1); LCD_WriteByteI2CLCD(portlcd|c); LCD_WriteByteI2CLCD((portlcd&=~0x04)|c);//выключаем линию E ets_delay_us(1); } //------------------------------------------------ |
Также добавим функцию передачи целого байта с флагом, обозначающим то, что именно мы передаём в дисплей — команду или данные
1 2 3 4 5 6 7 8 9 10 |
//------------------------------------------------ void sendbyte(uint8_t c, uint8_t mode) { if(mode==0) rs_reset(); else rs_set(); uint8_t hc=0; hc=c>>4; sendhalfbyte(hc);sendhalfbyte(c); } //------------------------------------------------ |
Добавим также функцию инициализации, которая нам также знакома
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 |
//------------------------------------------------ void LCD_ini(void) { ets_delay_us(50000); LCD_WriteByteI2CLCD(0); setwrite();//запись ets_delay_us(100000); sendhalfbyte(0x03); ets_delay_us(4500); sendhalfbyte(0x03); ets_delay_us(4500); sendhalfbyte(0x03); ets_delay_us(4500); sendhalfbyte(0x02); sendbyte(0x28,0);//режим 4 бит, 2 линии (для нашего большого дисплея это 4 линии, шрифт 5х8 sendbyte(0x08,0);//дисплей пока выключаем ets_delay_us(1000); sendbyte(0x01,0);//уберем мусор ets_delay_us(2000); sendbyte(0x06,0);// пишем вправо ets_delay_us(1000); sendbyte(0x0C,0);//дисплей включаем (D=1), курсоры никакие не нужны sendbyte(0x02,0);//курсор на место sendbyte(0X80,0);//SET POS LINE 0 ets_delay_us(2000); setled();//подсветка } //------------------------------------------------ |
Добавим на данную функцию прототип в заголовочном файле и вызовем её в функции user_init файла main.c
1 2 |
I2C_MASTER_SDA_LOW_SCL_LOW(); LCD_ini(); |
Квадратики в двух строках у нас погаснут и дисплей очистится
Только по понятным причинам у нас подсветка дисплея будет мигать периодически. Это сторожевой таймер, с ним мы разберёмся, давайте перезагрузим его в бесконечном цикле
1 2 3 4 |
while(1) { ets_delay_us(100000); system_soft_wdt_feed(); |
Если мы теперь соберём код и прошьём контроллер, то мигание прекратится.
Также в функции добавим небольшой символьный массив
1 2 |
uint16_t i=0; char str01[6]; |
Вернёмся в lcd.c и добавим там для полного счастья ещё функции вывода строки на дисплей и позиционирования курсора после функции sendbyte
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_String(char* st) { uint8_t i=0; while(st[i]!=0) { sendbyte(st[i],1); i++; } } //------------------------------------------------ void LCD_SetPos(uint8_t x, uint8_t y) { switch(y) { case 0: CUR_POS = x|0x80; break; case 1: CUR_POS = (0x40+x)|0x80; break; case 2: CUR_POS = (0x14+x)|0x80; break; case 3: CUR_POS = (0x54+x)|0x80; break; } sendbyte(CUR_POS,0); } //------------------------------------------------ |
Данные функции нам тоже знакомы и в объяснении не нуждаются.
Не забыв добавить на данные функции прототипы в заголовочном файле, вернёмся в main.c и в функции user_init попробуем что-нибудь вывести на наш дисплей
1 2 3 4 5 6 7 8 9 |
LCD_ini(); ets_delay_us(100000); LCD_String("String 1"); LCD_SetPos(3,1); LCD_String("String 2"); LCD_SetPos(6,2); LCD_String("String 3"); LCD_SetPos(9,3); LCD_String("String 4"); |
Соберём код, прошьём контроллер и посмотрим результат работы нашей программы
Отлично, теперь посмотрим, как работает наш дисплей в динамике, для этого сначала добавим задержку в 2 секунды и очистим часть нижней строки, где у нас надпись
1 2 3 4 5 6 7 |
LCD_String("String 4"); ets_delay_us(1000000); system_soft_wdt_feed(); ets_delay_us(1000000); system_soft_wdt_feed(); LCD_SetPos(9,3); LCD_String(" "); |
А затем в бесконечном цикле будем показывать счётчик в 4 строке (задержка от слишком быстрого перебирания чисел у нас там уже есть)
1 2 3 4 5 6 7 |
while(1) { i++; if(i>65534) i=0; os_sprintf(str01,"%5d",i); LCD_SetPos(9,3); LCD_String(str01); |
Теперь у нас, как только пройдут 2 секунды после вывода четырёх строк на экран, в нижней строке побегут циферки, как мы и видели в начале урока
Итак, на данном уроке мы лишний раз закрепили свои знания по управлению интерфейсом I2C в микроконтроллере ESP8266, пусть реализованном программно, но тем не менее отлично работающим. Надеюсь, это многим в дальнейшем пригодится для разработки разнообразных устройств под управлением контроллером ESP8266, в которых используется символьный вывод.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU
Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266
Дисплей LCD 20×4 можно приобрести здесь Дисплей LCD 20×4
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Смотреть ВИДЕОУРОК (нажмите на картинку)
Спасибо! Только вопросик сформировался, как часто сбрасывать пса? А то у Вас в цикле раз в 0,1 сек, а в другом месте раз 1 сек.
Код для 44780 постоянно совершенствуется.