Вывод текста на 1602 LCD со скроллингом вверх

doleynikov
Offline
Зарегистрирован: 30.01.2015

Уважаемые Гуру,

помогите, пожалуйста, модифицировать библиотеку LiquidCristalLCD_I2C , чтобы при заполнении последней строки, или при поступлении возврата каретки в последней строке, экран скроллился на одну строку вверх. Я знаю, что нужно организовать экранный буффер из своей программы, сделать промежуточный слой, который будет прорисовывать экран. такое я делал. Но у меня нет возможности менять саму программу. Нужно заставить библиотеку так себя вести.

Если честно, у меня нет нужных знаний по С++ и учить его мне нехочется-неможется. Но с-подобные языки для меня не являются совсем непонятными. Так, что, если дадите правильный пинок (в виде небольшого примера, как переопределить print и write в библиотеке)  - я попробую удержать курс ;-).

Заранее спасибо ;-)

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

doleynikov пишет:

 у меня нет нужных знаний по С++ и учить его мне нехочется-неможется.

Ну ведь ты сам все понял? ЧТО именно ты написал на хоббийном форуме?

Пока нахер не послали, иди уж лучше сам в раздел "Ищу исполнителя".

doleynikov
Offline
Зарегистрирован: 30.01.2015

Уважаемый, я и сам послать могу, к сожалению. До сего момента на форумах такого ответа не встречал.

А по теме, я сделаю сам, но это будет долго. Может кто-то уже с таким связывался и имеет наработки.

Ну вот, как то так.

С наиглубочайшим уважением.

Sr.FatCat
Offline
Зарегистрирован: 19.02.2016

doleynikov, вообще-то wdrakula, прав. Задача простая, минут на 30-40. Но лично мне она без надобности и переспектив в ее использовании не вижу. Если есть желание потерпеть когда у меня или кого другого звезды сойдутся в охотку написать это - давайте пример использования и описание ожидаемого поведения класса.
Ну и кроме того, я бы не модифицировал библиотеку, а написал бы производный класс от LiquidCristalLCD_I2C

doleynikov
Offline
Зарегистрирован: 30.01.2015

Я не сомневаюсь, что задача простая. Я делал такое не в виде библиотеки, а в виде набора функций в программе. Я, просто не совсем понимаю, как модифицировать библиотеку. Ну , если честно, совсем не понимаю, как переопределить print в библиотеке, чтобы он применял все особенности обычного print, но делал скроллинг при выводе.

Поведение ожидается примерно таким. По умолчанию, вывод начинается с начала нижней строки. По ее заполнении, в зависимости от какого-то флага, например установленного wrap=1, строка копируется в верхнюю и очищается. Последующий вывод опять в начала строки. Обрабатываются коды "\n" и "\r", то есть, сдвиг экрана без смещения текущей позиции и смещение текущей позиции курсора в начало строки. Даже без реакции на backspace и beep.

сейчас ищу более-менее понятный пример, так как с плюсами у меня нелады, а учить ради одной задачи - не хочется.

В любом случае, если я ненароком кого-то обидел - прошу меня простить, я не хотел сказать ничего обидного, так получилось и я сожалею об этом.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

Ты никого не обидел.

Ты на хоббийном форуме написал, что тебе лень заниматься тем, чем тут увлекаются люди.

Переводится это таким образом, если ты, вдруг, настолько социально инфантилен, что не можешь предсказать реакцию людей:

"Я - занимаюсь делом, а Вы тут маятесь херней, на которую я свое драгоценное время тратить никак не могу.

Поскольку вам тут, по моему мнению, все равно, какой херней маяться, то напишите мне пару страниц кода."

===================================

Я напишу тебе, как нужно сделать, в надежде, что "лень заниматься" было сказано по инфантильной тупости подростка, который хочет быть "взрослым дядей", а не из чванства и пафоса.

Переопределить нужно единственную функцию write(). Она определена inline строчкой в начале cpp файла библиотеки.

Далее есть два способа, проще и сложнее.

1. проще, в функции write(), проверять на '\r' и '\n'. И переставлять курсор в первом случае, и читать нижнюю строку из памяти экрана с адреса 0x40 и писать ее с ардеса 0, в случае '\n'. Судя по произведенному впечатлению, чтение по и2с у тебя не получится. Может поймешь? Нужно так:  сперва пишешь в регистр, что тебе нужно, а потом читаешь из него. Нужно хорощо понимать, как именно работает модуль и2с для 1602.

2. сложнее, но не нужно читать из экрана. При каждом сеткурсор и выводе отслеживать место и заполнять буфер нижней строки, которой завести в библиотеке, также завести координаты курсора (достаточно адрес в памяти)

doleynikov
Offline
Зарегистрирован: 30.01.2015

Вот спасибо! Я не профессионал в электронике - просто ковыряюсь для своего удовольствия. Когда-то пытался искать, можно ли читать из i2c контроллера на 8574t или контроллера экрана 1602, но везде получал ответ, что у этих сущьностей читать память нельзя - только писать.  Вот читать даташиты я тоже не умею, но, получается , нужно это. Попробую по Вашей рекомендации.

doleynikov
Offline
Зарегистрирован: 30.01.2015

Вот, может кому пригодится! наконец нашел упоминание, что можно читать данные из дисплейной памяти таких дисплеев!

http://radiolaba.ru/microcotrollers/podklyuchenie-lcd-1602-po-i2c-interfeysu.html

Вывод - внимательнее читать, то что написано ;-)

Теперь буду пробовать что-то читать

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

doleynikov пишет:

учить его мне нехочется-неможется. 

Ну, тогда придётся платить тому, кому в своё время хотелось и моглось.

doleynikov
Offline
Зарегистрирован: 30.01.2015

Пробую читать видеопамать. Написал такое:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

class myLcd : public LiquidCrystal_I2C   // производный класс
{
  public:

    myLcd(int a, int b, int c) : LiquidCrystal_I2C (a, b, c) // inputS передается в конструктор с параметром класса FirstClass
    {}

    uint8_t readB (uint8_t addr) // возводит value в квадрат. Без спецификатора доступа protected эта функция не могла бы изменить значение value
    {
      uint8_t highnib = addr & 0xf0;
      uint8_t lownib = (addr << 4) & 0xf0;
      Wire.beginTransmission(0x27);
      Wire.write((highnib) | 0B00000011);
      Wire.write((lownib) | 0B00000011);
      Wire.endTransmission();
      Wire.beginTransmission(0x27);
      uint8_t ret = Wire.read();
      Wire.endTransmission();
      return ret;
    }
};


myLcd lcd(0x27, 20, 4); // set the LCD address to 0x27 for a 16 chars and 2 line display

void setup()
{
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();

}


void loop()
{
  Serial.println(lcd.readB(0));
  lcd.setCursor(0, 0);
  lcd.print("w");
  delay(1000);
  Serial.println(lcd.readB(0));
  lcd.setCursor(0, 0);
  lcd.print("a");
  delay(1000);


}

Все время выдает 255. Подозреваю, что где-то накосячил. Может поглядите профессиональным взором? Кстати, я слегка подправил библиотечку:

private методы вывел из private, так как не мог вызвать write4bits из своего расширения класса.

sadman41
Offline
Зарегистрирован: 19.10.2016

Почитайте статейки еще внимательней. В hd44780  еще есть так называемый busy flag, который нужно анализировать при проведении операций. А то я смотрю - прям без паузы начинаете сливать инфу с DDRAM. А между тем в табличке, что в статье по ссылке - паузы быть должны. 

doleynikov
Offline
Зарегистрирован: 30.01.2015

в табличке написано, что команда выполняется 43мкс, я сделал паузу в 1мс и ничего не поменялось. Наверное я косячу на более фендаментальном уровне.

ЗЫ: А режим я прнавильно задаю?
Wire.write((highnib) | 0B00000011);  // установил Rs и Rw

LCD точно работает. Я вижу меняющиеся 'a' и 'w' в первой позиции первой строки.

Sr.FatCat
Offline
Зарегистрирован: 19.02.2016

А я бы настоятельно рекомендовал не свзываться с чтением из памяти дисплея, а не жалеть 80байт памяти и хранить дубль буффера дисплея в ОЗУ, перерисовывая дисплей из буфера при переходе со стороки на строку. Тогда и базовый класс не пришлось бы трогать в библиотеке.

sadman41
Offline
Зарегистрирован: 19.10.2016

Вижу только :


 Wire.endTransmission();
 Wire.beginTransmission(0x27);
 uint8_t ret = Wire.read();

Никаких пауз после выставления адреса DDRAM не вижу.

doleynikov
Offline
Зарегистрирован: 30.01.2015

Спасибо, я , в конце-концов так и сделаю. Но сейчас действительно интересно, как читать эти значения. Попробую разобраться.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

прочитайте методы класса wire.

beginTransmission это передача, а чтение это requestFrom. Примеры есть в ИДЕ, в соотв. разделе.

важно понять, что вы совмещаете ДВА протокола:

1. 1602 и PCF8574.

По  и2с вы работаете с 8574, которая - просто и2с регистр - расширитель портов входа/выхода, а поверх этого вы работаете с экраном.

Поэтому проще всего представить себе, что никакого и2с нет, и вы работаете с 8-и битным дополнительным портом.

поэтому выставляем на этом порту правильно комбинацию для чтения, а потом читаем нужное, на нужных ногах. Помним, что по 4 бита, у нас экран в 4-х битном режиме.

doleynikov
Offline
Зарегистрирован: 30.01.2015
Вот этот кусок кода сейчас: 

   uint8_t readB (uint8_t addr) 
    {

      uint8_t highnib = addr & 0xf0;
      uint8_t lownib = (addr << 4) & 0xf0;
      Wire.beginTransmission(0x27);
      Wire.write((highnib) | 0B00000011);
      delay(1);
      Wire.write((lownib) | 0B00000011);
      Wire.endTransmission();
      delay(1);
      Wire.beginTransmission(0x27);
      uint8_t ret = Wire.read();
      Wire.endTransmission();
      return ret;
    }

 

doleynikov
Offline
Зарегистрирован: 30.01.2015
    uint8_t readB (uint8_t addr) 
    {

      uint8_t highnib = addr & 0xf0;
      uint8_t lownib = (addr << 4) & 0xf0;
      Wire.beginTransmission(0x27);
      Wire.write((highnib) | 0B00000011);
      delay(1);
      Wire.write((lownib) | 0B00000011);
      Wire.endTransmission();
      Wire.requestFrom(0x27, 1);
      while (!Wire.available()) {}
      uint8_t ret = Wire.read();
      return ret;
    }

Вывод изменился. теперь всегда '3'

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

для начала - советую забыть про библиотку экрана совсем, потом к ней вернетесь, а взять в сети библиотеку чсто для 8574, так будет удобнее.

 

таким образом, перед кажым чтением состояния регистра, нужно выдать высокий Е, а потом низкий Е.

Проверено, что работает так: все, что нужно и высокий Е выставляем в записи. ждем 1 мс, выставляеи низкий Е, читаем. Если читам много байт, а нам нужно 16 байт прочесть, то потом делаем такой повтор:

- выставить  высокий Е, делей(1), низкий Е, читаем сразу без делея. И так 32 раза. По 4 бита.

 

doleynikov
Offline
Зарегистрирован: 30.01.2015

E это тактирование? я не вижу как управлять тактированием в библиотечках. Или я чего-то не понял?

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

да, чего-то не понял. Е - в библиотеке называется En в весьма кривой комбинации write4bits() , см. ниже, он используется в функции  pulseEnable().

Можно очень упростить эту конструкцию. Тут на форуме была моя реализация софт и2с и 1602, там это немного изящнее сделано. Поищите на ветке про тиньку 13, может там... может я сам позже найду.

void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
	uint8_t highnib=value&0xf0;
	uint8_t lownib=(value<<4)&0xf0;
       write4bits((highnib)|mode);
	write4bits((lownib)|mode); 
}

void LiquidCrystal_I2C::write4bits(uint8_t value) {
	expanderWrite(value);
	pulseEnable(value);
}

void LiquidCrystal_I2C::expanderWrite(uint8_t _data){                                        
	Wire.beginTransmission(_Addr);
	printIIC((int)(_data) | _backlightval);
	Wire.endTransmission();   
}

void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
	expanderWrite(_data | En);	// En high
	delayMicroseconds(1);		// enable pulse must be >450ns
	
	expanderWrite(_data & ~En);	// En low
	delayMicroseconds(50);		// commands need > 37us to settle
} 

 

doleynikov
Offline
Зарегистрирован: 30.01.2015

Спасибо за искреннее желание помочь. Вероятно я сильно бестолковый, однако.

Я сделаю по совету уважаемого Sr.FatCat и перехвачу вывод. тем более, что переходов по экрану не будет. Для скролла нужно будет хранить только текущую (нижнюю) строку. Вернее даже только видимую ее часть. Это мне понять проще. и библиотеку терзать не нужно будет, убирая private.

ЗЫ: как оказалось, библиотеку все равно терзать придется - в переопределяемом методе используется приватный метод. Поэтому проще вывести метод send из блока private.