Помогите пожалуйста убрать мерцание символов!

junior_developer
Offline
Зарегистрирован: 27.11.2017

Нашел такой интересный проект Шкала визуализации для аналоговых датчиков
Скетч рабочий. Единственное, что мне не понравилось - выводимая на экран переменная (точнее её символы) очень часто меняется. Эффект такой словно происходит мерцание! Можно ли как-то его убрать без функции delay();
Я вижу только два способа:
1. С помощью millis();
Пробовал вот так:

#include <Wire.h>                                        //  Подключаем библиотеку для работы с шиной I2C
#include <LiquidCrystal_I2C.h>                           //  Подключаем библиотеку для работы с LCD дисплеем по шине I2C
LiquidCrystal_I2C lcd(0x3F,16,2);                        //  Объявляем  объект библиотеки, указывая параметры дисплея (адрес I2C = 0x27, количество столбцов = 16, количество строк = 2) Если дисплей не отвечает, то измените адрес на 0x3F
uint8_t           symbol[8] = {31,31,31,31,31,31,31,31}; //  Определяем массив который содержит полностью закрашенный символ
const uint8_t     pinSensor = A0;                        //  Определяем константу для хранения номера аналогового входа с которого будут браться данные
uint16_t          valSensor;                             //  Объявляем  переменную для хранения данных с аналогового входа pinSensor
long previousMillis = 0;        
long interval = 1000;      
                                                         //
void setup(){                                            //
    lcd.init();                                          //  Инициируем работу с LCD дисплеем
    lcd.backlight();                                     //  Включаем подсветку LCD дисплея
    lcd.createChar(1, symbol);                           //  Загружаем символ из массива symbol в первую ячейку ОЗУ дисплея
}                                                        //  Так как символ был загружен в 1 ячейку, то для его отображения на дисплее нужно вывести символ с кодом 1
                                                         //  Если бы мы загрузили символ во 2 ячейку, то для его отображения на дисплее нужно было бы вывести символ с кодом 2
void loop(){ 
    unsigned long currentMillis = millis();
      //проверяем не прошел ли нужный интервал, если прошел то
  if(currentMillis - previousMillis > interval) {
    // сохраняем время  
    previousMillis = currentMillis;
    valSensor = analogRead(pinSensor);                   //  Читаем данные с аналогового входа pinSensor в переменную valSensor
    lcd.setCursor(6, 1); lcd.print("    ");              //  Устанавливаем курсор дисплея на 6 символ 1 строки и выводим 4 пробела начиная с той позиции, куда ранее был установлен курсор.
    lcd.setCursor(6, 1); lcd.print(valSensor);
  }
    //  Устанавливаем курсор дисплея на 6 символ 1 строки и выводим значение переменной valSensor на дисплей (начиная с той позиции, куда ранее был установлен курсор)
    lcd.setCursor(0, 0);                                 //  Устанавливаем курсор дисплея на 0 символ 0 строки для вывода шкалы, дальнейший вывод символов начнётся именно с этой позиции
    uint8_t j=map(valSensor,0,1023,0,17);                //  Определяем переменную j которой присваиваем значение valSensor преобразованное от диапазона 0...1023 к диапазону 0...17
    for(uint8_t i=0; i<16; i++){                         //  Выполняем цикл 16 раз для вывода шкалы из 16 символов начиная с позиции в которую ранее был установлен курсор
        lcd.write(j>i? 1:32);                            //  Выводим на дисплей символ по его коду, либо 1 (символ из 1 ячейки ОЗУ дисплея), либо 32 (символ пробела)
    }                                                    //  После вывода каждого символа, курсор дисплея сдвигается автоматически
}

По моему это не самый лучший вариант!

Второй вариант обновлять значение переменной на экране, только тогда, когда её значение существенно изменилось, например на +5 или - 5 (или больше).

Только я не знаю, какой функцией можно проверить выход переменной за определенные границы?
Или готовых функций нету?
Например текущее значение, считанное с датчика равно 95, как сделать так, чтобы на дисплей вывелось только 90 или 100. То есть изменение меньше, чем на 5, просто игнорировались? То есть цифры 94,93,92,91...  и  96,97,98,99 не выводились!

Нужно писать так:

if (valSensor >= (analogRead(pinSensor)+5) || valSensor <= (analogRead(pinSensor)-5) {
 lcd.setCursor(6, 1); lcd.print("    ");
 lcd.setCursor(6, 1); lcd.print(valSensor);
}

Или можно проще? Подскажите пожалуйста, кто в этом разбирается!

Mouflon
Offline
Зарегистрирован: 17.01.2018
Мне кажется что можно использовать что то типа такого.
 
 
//test_20180117-2.ino
#include <Math.h>

#define pinService A3

int currentValue;
int pregValue;

void setup() {
  	// put your setup code here, to run once:
	currentValue = 0;
	pregValue = 0;
}

void loop() {
	  
	currentValue =  round(analogRead(pinService) / 10) * 10;
	if( currentValue != pregValue ){
	    // Тут выводим на дисплей
	}
	pregValue = currentValue;

}

 

junior_developer
Offline
Зарегистрирован: 27.11.2017

А можно ли написать так:

if(abs(valSensor - (analogRead(pinSensor))) >=5) {
 lcd.setCursor(6, 1); lcd.print("    ");
 lcd.setCursor(6, 1); lcd.print(valSensor);
}

Или это неправильно?

Mouflon
Offline
Зарегистрирован: 17.01.2018
//test_20180117-2.ino
#include <Math.h>

#define pinService A3

int currentValue;
int prevValue;

void setup() {
  	// put your setup code here, to run once:
	currentValue = 0;
	prevValue = 0;
}

void loop() {
	  
	currentValue =  analogRead(pinService);
	if( abs( currentValue - prevValue)) >= 5 ){
		prevValue = currentValue;
		lcd.setCursor(6, 1); 
		lcd.print(currentValue);
		lcd.print("    ");
	}
}

 

junior_developer
Offline
Зарегистрирован: 27.11.2017

Спасибо!
И ещё вопрос к знатокам: как сделать такую же шкалу визуализации на две строки? То есть в зависимости от уровня с датчика сначала символами заполняется первая строка, а потом ещё вторая!

То есть розрядность уже не 16, а 32!

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

   
    lcd.setCursor(0, 0);       
    uint8_t j=map(valSensor,0,1023,0,32);                
    for(uint8_t i=0; i<32; i++){    
        lcd.write(j>i? 1:32);   

Как сделать перенос, во вторую строку, если значение з датчика будет больше половины? Нужно создать ещё одну переменную для строки?
То есть написать

uint8_t line 0;
lcd.setCursor(0, line);

а потом добавить проверку

if (i>15) line = 1;
else line = 0;

Пробовал писать и так:
 

for(uint8_t i=0; i<32; i++){
		uint8_t line;
		if (j<16) line = 0;
		else line = 1;
lcd.setCursor(0, line);		
        lcd.write(j>i? 1:32);                           

И так:

    if (j<16) line = 0;
    else line = 1;
    lcd.setCursor(0, line);
    for(uint8_t i=0; i<32; i++){
    lcd.write(j>i? 1:32);    

Однако все равно работает неправильно! Подскажите пожалуйста, что здесь неправильно?

Mouflon
Offline
Зарегистрирован: 17.01.2018

Что то типа такого

void printRow(uint8_t rowNum, uint8_t value)
{
	lcd.setCursor(0,rowNum);
	for(int i=0; i<value; i++){
		lcd.print('X');
	}
}

void setup()
{
	lcd.init();                      // инициализируем дисплей
	lcd.backlight();
	lcd.setCursor(0,0);

	uint8_t value = 17;
	uint8_t r1 = ( value > 16 ? 16 : value );
	uint8_t r2 = value - 16;

	printRow(0, r1);
	printRow(1, r2);
}

void loop()
{
}

 

junior_developer
Offline
Зарегистрирован: 27.11.2017

Попробовал таким способом:
 

uint8_t Val_line_1 = ( j > 16 ? 16 : j ); // значение для первой строки
uint8_t Val_line_2 = j - 16;             // для второй

  printLine(0, Val_line_1);         // вывод первой строки символов с помощью функции
  printLine(1, Val_line_2);        // и второй

Вот код :

#include <Wire.h>                                        
#include <LiquidCrystal_I2C.h>                           
LiquidCrystal_I2C lcd(0x3F,16,2);                        
uint8_t           symbol[8] = {31,31,31,31,31,31,31,31}; //  Определяем массив который содержит полностью закрашенный символ
const uint8_t     pinSensor = A0;                        
uint16_t          valSensor;                          

void printLine (uint8_t line, uint8_t Val_Line){

      lcd.setCursor(0,line);
      for(int i=0; i < 16; i++){
      lcd.write(Val_Line > i ? 1:32);
   }
  }

void setup(){                                            
    lcd.init();                                          
    lcd.backlight();                                     
    lcd.createChar(1, symbol);                      
}                                                        
                                                         
void loop(){                                           
    
    uint8_t j=map(valSensor,0,1023,0,32);                
  uint8_t Val_line_1 = ( j > 16 ? 16 : j );
  uint8_t Val_line_2 = j - 16;
  printLine(0, Val_line_1);
  printLine(1, Val_line_2);
 }

Впечатление такое, что в функцию вывода не передается значение переменной! Или передается что-то не то! Посмотрите пожалуйста, кому не сложно! Заранее спасибо!
В выводе через Serial обнаружил очень странный глюк! Откуда берется это число? И как оно может быть больше, чем переменная j ?

Наконец-то исправил код! Теперь он выглядит так:

#include <Wire.h>                                        
#include <LiquidCrystal_I2C.h>                           
LiquidCrystal_I2C lcd(0x3F,16,2);                        
uint8_t           symbol[8] = {31,31,31,31,31,31,31,31}; //  Определяем массив который содержит полностью закрашенный символ
const uint8_t     pinSensor = A0;                        
uint16_t          valSensor;                          
uint8_t j;

void printLine (uint8_t line, uint8_t Val_Line){

      lcd.setCursor(0,line);
      for(int i=0; i < 16; i++){
      lcd.write((Val_Line > i) ? 1:32);
      Serial.print("Val_Line = "); Serial.println(Val_Line);
   //   delay(20);
   }
  }

void setup(){ 
    Serial.begin(9600);                                           
    lcd.init();                                          
    lcd.backlight();                                     
    lcd.createChar(1, symbol);                      
}                                                        
                                                         
void loop(){                                           
   valSensor= analogRead(pinSensor);
   Serial.print("valSensor = "); Serial.println(valSensor);
   j=map(valSensor,0,1023,0,32);                
  uint8_t Val_line_1 = ((j > 16) ? 16 : j );
  uint8_t Val_line_2 = ((j > 16) ? (j-16) : 0 );
  
  Serial.print("j = "); Serial.println(j);
  Serial.print("Val_line_1 = "); Serial.println(Val_line_1);
  //delay(100);
  printLine(0, Val_line_1);
  printLine(1, Val_line_2);
 }

Попробовал. Вроде бы работает, но я не уверен, что написан он правильно! Подскажите пожалуйста, может его можно как-то улучшить?
 

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

Можно улучшить. Производите вывод на экран только если отображаемое значение изменилось (увеличилось или уменьшилось). 

Или, например, используйте четыре собственных изображения: закрашено 1/4, 2/4, 3/4 и 4/4 знакоместа. Немного придется с математикой посидеть, но шкала будет иметь больше градаций и влезет в одну строку LCD.

Mouflon
Offline
Зарегистрирован: 17.01.2018

Опять таки, как вариант оптимизации можно рассмотреть вот такое: 
 

#include <Wire.h>                                        
#include <LiquidCrystal_I2C.h>                           
LiquidCrystal_I2C lcd(0x3F,16,2);                        

#define     pinSensor A0
//#define 	C_DEBUG	  1

uint8_t		symbol[8] = {31,31,31,31,31,31,31,31}; //  Определяем массив который содержит полностью закрашенный символ
uint16_t    valSensor = 0;
uint16_t    valSensorPrev = 0;
uint8_t		j = 0;


void printLine (uint8_t line, uint8_t Val_Line){

	lcd.setCursor(0,line);
	for(int i=0; i < 16; i++){
		lcd.write((Val_Line > i) ? 1:32);
#ifdef C_DEBUG		
		Serial.print("Val_Line = "); 
		Serial.println(Val_Line);
#endif
	}
}

void setup(){ 
#ifdef C_DEBUG		
    Serial.begin(9600);                                           
#endif    
    lcd.init();                                          
    lcd.backlight();                                     
    lcd.createChar(1, symbol);                      
}                                                        
                                                         
void loop(){                                           
   valSensor= analogRead(pinSensor);
   if(valSensorPrev != valSensor){
		valSensorPrev = valSensor;
		j=map(valSensor,0,1023,0,32);                

#ifdef C_DEBUG		
		Serial.print("valSensor = "); 
		Serial.println(valSensor);
		Serial.print("j = "); 
		Serial.println(j);
#endif		
		lcd.clear();
		printLine(0, (j > 16) ? 16 : j);
		printLine(1, (j > 16) ? (j-16) : 0);
   }
 }

 

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

lcd.clear(), к сожалению, не поможет оптимизации, а тормознет процесс.

Mouflon
Offline
Зарегистрирован: 17.01.2018

Или, например, используйте четыре собственных изображения: закрашено 1/4, 2/4, 3/4 и 4/4 знакоместа.

К сожалению знакогенератор 5x8, так что делить надо на 5, а не на 4. но и в этом случае будет не очень выглядеть из за промежутка между символами. Тут надо топикстартеру решать что ему нужно.

Mouflon
Offline
Зарегистрирован: 17.01.2018

Это не готовое решение, а пример того, как можно сделать. Я бы на месте топикстартера закрашивал слева на право, а гасил кубики в обратном направлении. и только те, которые надо.

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

Mouflon пишет:

К сожалению знакогенератор 5x8, так что делить надо на 5, а не на 4. но и в этом случае будет не очень выглядеть из за промежутка между символами. Тут надо топикстартеру решать что ему нужно.

Согласен, выглядеть будет так себе. Однако логичней, чем на две строчки растягивать. Кстати, средний столбец знакоместа можно не красить, будет дырок побольше, повеселей.