Урок 22
HAL. I2C. Переходник для LCD 20×4
Сегодня мы попробуем подключить символьный дисплей LCD 20×4 подключить через переходник по шине I2C. Это позволит нам сэкономить ножки портов, а также обойтись меньшим количеством проводов.
Данный переходник выглядит вот так и его разъём полностью совместим по ножкам с разъёмом дисплея
Это ничто иное, как преобразователь последовательного кода в параллельный на базе микросхемы PCF8574. Вот схема переходника (нажмите на картинку для увеличения изображения)
По схеме мы видим на какие именно ножки разъёма дисплея у нас идут ножки микросхемы.
Проект создаём из MYLCD80, называем его I2CLCD80.
Запускаем Cube. Так как проект был создан в более ранней версии Cube, то в ответ на диалог с тремя кнопками жмём «Migrate», чтобы проект наш адаптировался к новой версии.
Включим I2C1. Ничего нигде больше не трогаем, запоминаем лапки SDA и SCL, генерируем проект, добавляем в него файл lcd.c, настраиваем программатор, собираем код.
Откроем файл lcd.h и исправим там всё в соответствии с требованиями шины I2C.
Убираем включение ножек портов совсем, т.к. они у нас не используются и этим займётся микросхема
#include «stm32f4xx_hal.h»
//————————————————
#define d4_set() HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, GPIO_PIN_SET)
#define d5_set() HAL_GPIO_WritePin(GPIOD, GPIO_PIN_5, GPIO_PIN_SET)
#define d6_set() HAL_GPIO_WritePin(GPIOD, GPIO_PIN_6, GPIO_PIN_SET)
#define d7_set() HAL_GPIO_WritePin(GPIOD, GPIO_PIN_7, GPIO_PIN_SET)
#define d4_reset() HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, GPIO_PIN_RESET)
#define d5_reset() HAL_GPIO_WritePin(GPIOD, GPIO_PIN_5, GPIO_PIN_RESET)
#define d6_reset() HAL_GPIO_WritePin(GPIOD, GPIO_PIN_6, GPIO_PIN_RESET)
#define d7_reset() HAL_GPIO_WritePin(GPIOD, GPIO_PIN_7, GPIO_PIN_RESET)
Прежде чем раньше менять, нам потребуется функция, которая будет посылать определённые данные в переходник. Для того, чтобы нам её добавить, откроем файл lcd.h и напишем туда функцию
//————————————————
void WriteByteI2CLCD(uint8_t bt)
{
buf[0]=bt;
HAL_I2C_Master_Transmit(&hi2c1,(uint16_t) 0x4E, buf,1,1000);
}
//————————————————
Вернёмся в lcd.h
Всё остальное переделываем вот так на основании вышеупомянутой схемы
#define e_set() WriteByteI2CLCD(portlcd|=0x04) // установка линии E в 1
#define e_reset() WriteByteI2CLCD(portlcd&=~0x04) // установка линии E в 0
#define rs_set() WriteByteI2CLCD(portlcd|=0x01) // установка линии RS в 1
#define rs_reset() WriteByteI2CLCD(portlcd&=~0x01) // установка линии RS в 0
#define setled() WriteByteI2CLCD(portlcd|=0x08) // включение подсветки
#define setwrite() WriteByteI2CLCD(portlcd&=~0x02) // установка записи в память дисплея
Теперь подправим файл lcd.c
Напишем туда кое-какие дефайны
#include «lcd.h»
//—————————————-
extern I2C_HandleTypeDef hi2c1;
uint8_t buf[1]={0};
char str1[100];
uint8_t portlcd = 0; //ячейка для хранения данных порта микросхемы расширения
Затем изменим функцию задержки, сделаем её в микросекундах
//————————————————
__STATIC_INLINE void DelayMicro(__IO uint32_t micros)
{
micros *= (SystemCoreClock / 1000000) / 5;
/* Wait till done */
while (micros—);
}
//————————————————
Идём дальше по порядку.
Убираем функцию LCD_WriteData.
Убираем функции LCD_Command и LCD_Data, вместо них добавим другие функции
//————————————————
void sendhalfbyte(uint8_t c)
{
c<<=4;
e_set(); //включаем линию Е
DelayMicro(50);
WriteByteI2CLCD(portlcd|c);
e_reset(); //выключаем линию Е
DelayMicro(50);
}
//————————————————
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);
}
//————————————————
Далее подправим эту функцию
void LCD_Clear(void)
{
sendbyte(0x01, 0);
DelayMicro(1500);
}
Эту тоже подправим
void LCD_SendChar(char ch)
{
sendbyte(ch,1);
}
Данная функция подправится незначительно
void LCD_SetPos(uint8_t x, uint8_t y)
{
switch(y)
{
case 0:
sendbyte(x|0x80,0);
HAL_Delay(1);
break;
case 1:
sendbyte((0x40+x)|0x80,0);
HAL_Delay(1);
break;
case 2:
sendbyte((0x14+x)|0x80,0);
HAL_Delay(1);
break;
case 3:
sendbyte((0x54+x)|0x80,0);
HAL_Delay(1);
break;
}
}
Теперь вот эту
void LCD_ini(void)
{
HAL_Delay(15);
sendhalfbyte(0x03);
HAL_Delay(4);
sendhalfbyte(0x03);
DelayMicro(100);
sendhalfbyte(0x03);
HAL_Delay(1);
sendhalfbyte(0x02);
HAL_Delay(1);
sendbyte(0x28, 0); //4бит-режим (DL=0) и 2 линии (N=1)
HAL_Delay(1);
sendbyte(0x0C, 0); //включаем изображение на дисплее (D=1), курсоры никакие не включаем (C=0, B=0)
HAL_Delay(1);
sendbyte(0x6, 0); //курсор (хоть он у нас и невидимый) будет двигаться влево
HAL_Delay(1);
setled();//подсветка
setwrite();//запись
}
Вот эту незначительно
void LCD_String(char* st)
{
uint8_t i=0;
while(st[i]!=0)
{
sendbyte(st[i],1);
i++;
}
}
Прошиваем, смотрим.
Предыдущий урок Программирование МК STM32 Следующий урок
Техническая документация на микросхему PCF8574
Отладочную плату можно приобрести здесь STM32F4-DISCOVERY
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Смотреть ВИДЕОУРОК
Добрый день.
Отличный урок, но надо было особо заострить внимание на то, что для библиотеки HAL адрес внешнего устройства на шине I2C должен быть сдвинут влево на 1 бит. Не у всех переходничек имеет адрес 2F.
Специально просмотрел внимательно этот урок и первый урок по шине I2C: ни в одном об этом напрямую не говорится. Т.е., пока адрес совпадает с Вашим, все нормально. А мне пришлось покурить интернет, т.к. адрес у меня оказался 3F.
Причём сдесь 2F? Даташит читать надо внимательней, а не только в интернете лазить.
Очень даже может и причём.
Автор, конечно, молодец, что создаёт такие замечательные обучающие занятия. Но очень усложняют быстрому усвоению материала некоторые неточности. В видео можно увидеть один код, в статье немного другой, в исходниках третий. Ладно бы если это все сразу заработало. Но ведь нет. А мне, как начинающему, очень тяжело найти проблему и исправить её.
По поводу 2F и 3F о которых писал Михаил. В функции записи на дисплей мы видим значение 0x4E. Так, вот там в первых семи битах расположен адрес i2c устройства к которому мы обращаемся, а последний бит это флаг того отправляем мы его («0») или читаем («1»). Функция LCD_WriteByteI2CLCD только отправляет, поэтому последний бит «0». Адрес устройства у автора 0х27-> 0b0010'0111 (видимо, там использовалась микросхема PCF8574T). Смещаем это значение на одну позицию влево и последний бит устанавливаем в «0» (флаг отправки). Получаем: 0b0100'1110-> 0x4E.
У моей платы адрес 0х3F-> 0b0011'1111 (используется микросхема PCF8574AT). Смещаем влево, устанавливаем последний бит в «0», получаем: 0b0111'1110-> 0x7E. Т.е. в моем случае в функции LCD_WriteByteI2CLCD параметр 0x4E надо заменить на 0x7E. Иначе приемник i2c дисплея не поймет, что обращаются к нему.
void LCD_WriteByteI2CLCD(uint8_t bt)
{
buf[0]=bt;
//HAL_I2C_Master_Transmit(&hi2c1,(uint16_t) 0x4E, buf,1,1000);
HAL_I2C_Master_Transmit(&hi2c1,(uint16_t) 0x7E, buf,1,1000);
}
Чтобы линия E не отключалась одновременно с данными.
void sendhalfbyte(uint8_t c)
{
c<<=4;
e_set(); //включаем линию Е
DelayMicro(50);
WriteByteI2CLCD(portlcd|=c);
e_reset(); //выключаем линию Е
DelayMicro(50);
WriteByteI2CLCD(portlcd&=15);
}
Евгений, спасибо! заработало
Здравствуйте!
Фирма Bolymin выпускает мультиинтерфейсные индикаторы BC1602AI и
BC2004AI. Интерфейсы I2C, SPI, UART. Я привык работать с I2C.
Индикаторы пятивольтовые, но выводы STM32 по I2C толерантны к
напряжению 5 вольт. Сигналы завожу через резисторы 500 ом. Работает
уверенно. Эти индикаторы я считаю более выгодным и простым решением
чем обычные с переходником на I2C.
А как сделать чтобы показывалась кириллица. Хочется написать по-русски.
На данном дисплее без плясок с бубном не получится. Можно написать свои 8 символов и их использовать. А кириллицы здесь нет.
С кириллицей смотрите в сторону WinStar. Но там цена другая.
а на каком дисплее есть?
На Ардуино я выводил кириллицу
А, WinStar это дисплеи, а я думал это библиотека какая.
Добрый вечер. Спасибо за уроки. Проблемка в следующем у меня корректно отрабатывают функции LCD_SetChar и LCDString если явно указываю аргумент функции. Но стоит только начать передавать данные в виде массива . sprintf(str, «Name»);
LCDString(str);
Выводит на экран крякозабры (как будто выводит не символ массива а символ с адресом в системе ascii)
Для вывода строки задействована функция sprintf()
В связи с этим два вопроса:
1. Не нашел подключения стандартных библиотек. Но код компилируется, и работает…
2. Везде пишут, что sprintf отнимает много места во флеше.
Опишите, пожалуйста, по-подробнее использование функции вывода
Куча отличных книг по нормальному ANSI C без плюсов пригодится.
«Везде пишут, что sprintf отнимает много места во флеше» — шиза чья-то
Все доп ф-ции Си и 4кб не дают. Для STM32 подобная экономия абсурдна.
Передо мной проект с подключенной FreeRtos, Си либами мath,string,stdio,stdlib и почти полным HAL. И всего то =14кб на 2ой оптимизации.
Возьми себе stm32H750 480МГЦ за 300р, 1МБ оперативки.128флэш, опреционники, инструментальники, 16 битные таймера, JPEG кодеки итп
Подключи внешнюю flash, мб на 4 и не думай про «огрооомную» sprintf :))
(отредактировано, убрана внешняя ссылка)
Уважаемый Narod Stream, здравствуйте!
Ваш урок очень полезен, для начального понимания, но хочу отметить следующие минусы:
1. Передается максимум 1 байт на одну i2c сессию.
чтобы передать 1 байт на дисплей, нужно стартануть новую сессию i2c, передать start, потом передать адрес дисплея, потом передать байт, потом stop.
Таким образом, чтобы передать на дисплей 80 байт, нужно передать в действительности 2 байта в лучшем случае.
2. программе приходится ждать обмена с дисплеем, а это очень не хорошо. когда же программе заниматься своими полезными функциями ?
3. В библиотеке есть задержки в 50 микросекунд, оставшиеся видимо со времен библиотеки с подключением без переходника i2c. там они были нужны, здесь — однозначно нет. потому что посчитайте, сколько нужно микросекунд для передачи 8 бит в накопительный регистр, который в переходнике. Вы получите гораздо больше, чем 50 микросекунд.
4. Чтобы программа не ждала вывода на дисплей, нужно просто сделать, чтобы DMA выводил на дисплей из области памяти. В эту область памяти программа просто пишет, как в память дисплея. да, там один символ будет занимать 4 байта, но зато ускорение составило 5 раз. если Вам интересно, могу прислать Вам тестовую прогу с моей либой.
Спасибо за Ваши уроки, но выражу пожелание — докапывайтесь до самой сути, пожалуйста, оптимизируйте Ваши программы, прицчайте к оптимизации Ваших зрителей.
Спасибо.
Что то у меня не захотела эта библиотека работать. Поделитесь своей если есть возможность. Спасибо. gkreshtofсобакаgmail.com
Спасибо за урок. Подскажите что может быть. Наблюдаю проблему что экранчик что по 4битному подключению, что по I2C выводит инфу, но не всегда, причем часто выводит после касаний пальцами пинов. Похоже на наводки. Хотя везде уже навешал конденсаторов по питанию керамических, осцилограф показывает что все ОК. Не знаете в чем может быть проблемма?
Спасибо за оценку!
Может быть много причин этому траблу. В т.ч. подбирать задержки, также качество разное может быть
Всем привет! Подскажите , где копать , нет ошибок . Но заливаю прошивку в stm пишет . Cannot access target . Shutting down debug session.
Здравствуйте , пытаюсь написать программу в CUBE IDE для вывода данных АЦП на LCD экран . Проблема в том , что при сборке проекта выдается ошибка make: *** [makefile:41: final.elf] Error 1 , что это и как от нее избавиться ?