Продолжаем тему поддержки программного 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
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Многофункциональный переходник JTAG UART FIFO SPI I2C можно приобрести здесь CJMCU FT232H USB к JTAG UART FIFO SPI I2C
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Спасибо! Только вопросик сформировался, как часто сбрасывать пса? А то у Вас в цикле раз в 0,1 сек, а в другом месте раз 1 сек.
Код для 44780 постоянно совершенствуется.