LCD + энкодер

Abyss
Offline
Зарегистрирован: 12.05.2014

Приветствую всех. Возникла проблема.

Подключаю энкодер (KY-040) и экранчик 20*4. По отдельности все отлично работает. Экран все показывает, энкодер крутится и значения меняются. Но как только я объединяю два устройства в одном скетче - проблема. Энкодер начинает пропускать шаги, причем прилично. Понимаю, что как-то связано с прерываниями, но что сделать - не понимаю. Подскажите, пожалуйста.

Вот код:

//#include <OneWire.h>           // Библиотека для Dallas 1-wire
//#include <DallasTemperature.h> // Библиотека для DS18B20
#include <Wire.h>              // Библиотека для I2C
#include <LiquidCrystal_I2C.h> // Библиотека для LCD 

LiquidCrystal_I2C lcd(0x27,2,4); // Адрес модуля LCD

//#define ONE_WIRE_BUS 10
//OneWire oneWire(ONE_WIRE_BUS);
//DallasTemperature sensors(&oneWire);

// Используем энкодер с кнопкой
enum PinAssignments {
  encoderPinA = 2,   // Вращение вправо
  encoderPinB = 3,   // Вращение влево
  clearButton = 8    // Кнопка
};

volatile unsigned int encoderPos = 0;    // Счетчик для энкодера
unsigned int lastReportedPos = 1;        // Предыдущее значение энкодера для сравнения
static boolean rotating=false;           // Антидребезг

// Переменные направления вращения
boolean A_set = false;              
boolean B_set = false;


void setup() {

  pinMode(encoderPinA, INPUT_PULLUP); // Режим порта
  pinMode(encoderPinB, INPUT_PULLUP); 
  pinMode(clearButton, INPUT_PULLUP);

// Прерывания
  attachInterrupt(0, doEncoderA, CHANGE);
  attachInterrupt(1, doEncoderB, CHANGE);
  
// Инициализируем LCD
  lcd.init();
  lcd.backlight();
  lcd.home (); 

  Serial.begin(9600);  // Монитор порта
}

// Основной цикл
void loop() { 
  rotating = true;  // Инициализируем "антидребезг"

// Сравниваем состояние энкодера с предыдущим и если есть изменение - печатаем его  
  if (lastReportedPos != encoderPos) {
    Serial.print("Index:");
    Serial.println(encoderPos, DEC);
    lastReportedPos = encoderPos;
  }
// Обнуляем значение переменной по нажатию кнопки
  if (digitalRead(clearButton) == LOW )  {
    encoderPos = 0;
  }
  
// Выводим значение переменной на LCD
  lcd.setCursor ( 0, 0 );           
  lcd.print ("Index: ");   
  lcd.print (encoderPos);   
}

// Прерывание 1
void doEncoderA(){
  // Включаем "антидребезг"
  if ( rotating ) delay (10);  

  // Проверяем значение порта 
  if( digitalRead(encoderPinA) != A_set ) {  // debounce once more
    A_set = !A_set;

    // Узнаем направление вращения
    if ( A_set && !B_set ) 
      encoderPos += 1;

    rotating = false;  // выключаем "антидребезг"
  }
}

// Все то же самое для прерывания 2
void doEncoderB(){
  if ( rotating ) delay (1);
  if( digitalRead(encoderPinB) != B_set ) {
    B_set = !B_set;
    
    if( B_set && !A_set ) 
      encoderPos -= 1;

    rotating = false;
  }
}

Заранее спасибо.

Radjah
Offline
Зарегистрирован: 06.08.2014

Попробуй CyberLib использовать, чтобы от медленной digitalRead избавиться.

//**************READ Status Pins*************
#define D0_Read (PIND & B00000001)
#define D1_Read ((PIND & B00000010)>>1)
#define D2_Read ((PIND & B00000100)>>2)
#define D3_Read ((PIND & B00001000)>>3)
#define D4_Read ((PIND & B00010000)>>4)
#define D5_Read ((PIND & B00100000)>>5)
#define D6_Read ((PIND & B01000000)>>6)
#define D7_Read ((PIND & B10000000)>>7)

#define D8_Read (PINB & B00000001)
#define D9_Read ((PINB & B00000010)>>1)
#define D10_Read ((PINB & B00000100)>>2)
#define D11_Read ((PINB & B00001000)>>3)
#define D12_Read ((PINB & B00010000)>>4)
#define D13_Read ((PINB & B00100000)>>5)

#define D14_Read (PINC & B00000001)
#define D15_Read ((PINC & B00000010)>>1)
#define D16_Read ((PINC & B00000100)>>2)
#define D17_Read ((PINC & B00001000)>>3)
#define D18_Read ((PINC & B00010000)>>4)
#define D19_Read ((PINC & B00100000)>>5)

 

roman2712@mail.ru
Offline
Зарегистрирован: 16.01.2014

дело не в скорости работы digitalRead. Дело скорее в delay(10)... ИМХО, лучше аппаратно антидребезг для энкодера сделать...

https://www.pjrc.com/teensy/td_libs_Encoder.html - посмотрите, там очень неплохая библиотечка была.

А как именно у вас пропуски происходят ? быстро крутите, а знечение не меняется ?

 

Abyss
Offline
Зарегистрирован: 12.05.2014

Аппаратный пока нет возможности сделать, но про данный метод в курсе, спасибо.

А пропуски: Кручу на один "щелчок", а значение меняется на 5.. или на 3.. или на -2..

 

Abyss
Offline
Зарегистрирован: 12.05.2014

Вообще, если вставить вывод на экран внутрь проверки изменения сигнала, то все отлично. Но мне еще температуру постоянно мониторить, поэтому не совсем поможет.

roman2712@mail.ru
Offline
Зарегистрирован: 16.01.2014

К сожалению не знаю точно, какая у вас задача, но если вам необходимо на 1 шелчек энкодера изменять значение на 1 еденицу, то:

1) прерывание на переход из 0 в 1 для выхода А

2) в прерывании делайте антидребезг, после этого читайте выход Б (Б можно уже без антидребезга, так как он 100% уже стабилен). Если Б = 1 то увеличте переменную энкодера, если Б = 0 то уменьшите.

 

Abyss
Offline
Зарегистрирован: 12.05.2014

Задача банальна. Меню с использованием энкодера с кнопкой и 4-х строчного LCD

roman2712@mail.ru
Offline
Зарегистрирован: 16.01.2014

Упростите код как я писал, должно взлететь...

roman2712@mail.ru
Offline
Зарегистрирован: 16.01.2014

В общем я провери код. У ТС не срабатывает антидребезг. 

Рабочий упрощенный код (протестировано). Я понимаю, что delay() в прерывании не очень желателен, однако это самый простой способ. главное прарывание на момент delay() отключить.

#include <LiquidCrystalRus.h>
LiquidCrystalRus lcd(12, 11, 7, 6, 5, 4);

volatile int encoder_register = 0;

void setup() {
  pinMode(2,INPUT_PULLUP);
  pinMode(3,INPUT_PULLUP);
  attachInterrupt(1, encoder, RISING);
  lcd.begin(20, 4);
}

void loop() {
  lcd.setCursor(0, 0);
  lcd.print(encoder_register);
  lcd.print("   ");
}

void encoder() {
  detachInterrupt(1);
  delay(10);
  if (digitalRead(3)){
    if(digitalRead(2)) ++encoder_register;
    else --encoder_register;
  }
  attachInterrupt(1, encoder, RISING);
}



 

Abyss
Offline
Зарегистрирован: 12.05.2014

Не работает этот код, к сожалению. Так и проскакивает значения. 0-2-4-6-7-9-11-13-15 и т.д. Я думаю проблема именно в том, что вывод на экран тоже использует прерывания.

#include <LiquidCrystal_I2C.h> // Библиотека для LCD 
#include <OneWire.h>           // Библиотека для Dallas 1-wire
#include <Wire.h>              // Библиотека для I2C

LiquidCrystal_I2C lcd(0x27,2,4); // Адрес модуля LCD

#define ONE_WIRE_BUS 10
#define D2_Read ((PIND & B00000100)>>2)
#define D3_Read ((PIND & B00001000)>>3)

OneWire oneWire(ONE_WIRE_BUS);

volatile int encoder_register = 0;

void setup() {
  pinMode(2,INPUT_PULLUP);
  pinMode(3,INPUT_PULLUP);
  attachInterrupt(1, encoder, RISING);
  lcd.init();
  lcd.backlight();
  lcd.home (); 
}

void loop() {
  lcd.setCursor(0, 0);
  lcd.print(encoder_register);
  lcd.print("   ");
}

void encoder() {
  noInterrupts();
  delay(1);
  if (D3_Read){
    if(D2_Read) ++encoder_register;
    else --encoder_register;
  }
  interrupts();
}

 

roman2712@mail.ru
Offline
Зарегистрирован: 16.01.2014

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

в I2C прерывания точно используются. Очень похоже что у вас срабатывает оба прерывания по фронту (2 и 3 нога). Попробуйте явно отключить прерывание на 2 ноге (detachInterrupt(0)), и в обработчике прерываний явно отключайте и включаете прерывания на 3 ноге. (detachInterrupt(1); и attachInterrupt(1, encoder, RISING);). Задержку до 10 поднимите. 

Abyss
Offline
Зарегистрирован: 12.05.2014

Увы...

roman2712@mail.ru
Offline
Зарегистрирован: 16.01.2014

 А может у вас энкодер 2 перехода в + на 1 клик дает ? 

vvadim
Онлайн
Зарегистрирован: 23.05.2012

Вот рабочий код, только мой дисплей 16х2. Для 20х4 заменить lcd.begin (16,2); на lcd.begin (20,4);

#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#define LCD_I2C_ADDR    0x20                 
#define LCD_BACKLIGHT_PIN   6
#define LCD_EN  4
#define LCD_RW  5
#define LCD_RS  6
#define LCD_D4  0
#define LCD_D5  1
#define LCD_D6  2
#define LCD_D7  3
LiquidCrystal_I2C       lcd(LCD_I2C_ADDR,LCD_EN,LCD_RW,LCD_RS,LCD_D4,LCD_D5,LCD_D6,LCD_D7);
int encoderPin1 = 2;
int encoderPin2 = 3;
volatile int lastEncoded = 0;
volatile long encoderValue = 0;
long lastencoderValue = 0;
int lastMSB = 0;
int lastLSB = 0;

void setup()
{
  lcd.begin (16,2);
  analogWrite(LCD_BACKLIGHT_PIN, 200);
  pinMode(encoderPin1, INPUT); 
  pinMode(encoderPin2, INPUT);
  digitalWrite(encoderPin1, HIGH); 
  digitalWrite(encoderPin2, HIGH);
  attachInterrupt(0, updateEncoder, CHANGE); 
  attachInterrupt(1, updateEncoder, CHANGE);
}

void loop()
{  
  lcd.setCursor( 0, 0);
  lcd.print(encoderValue);    
}

void updateEncoder()
{
  int MSB = digitalRead(encoderPin1);            
  int LSB = digitalRead(encoderPin2);            

  int encoded = (MSB << 1) |LSB;                 
  int sum  = (lastEncoded << 2) | encoded;       

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;

  lastEncoded = encoded; 
}

Правда обработка значений энкодера считает по 2 значения , но я в это не лез.

vvadim
Онлайн
Зарегистрирован: 23.05.2012

А вот leshak предложил работу с энкодером - четко показывает 

#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#define LCD_I2C_ADDR    0x20                 
#define LCD_BACKLIGHT_PIN   6
#define LCD_EN  4
#define LCD_RW  5
#define LCD_RS  6
#define LCD_D4  0
#define LCD_D5  1
#define LCD_D6  2
#define LCD_D7  3
LiquidCrystal_I2C       lcd(LCD_I2C_ADDR,LCD_EN,LCD_RW,LCD_RS,LCD_D4,LCD_D5,LCD_D6,LCD_D7);
#define PIN_A 2
#define PIN_B 3
#define PULSE_PIN_LEN 5                // минимальная длинна импульса в миллисекундах на которую мы обращаем внимание
volatile unsigned long failingTime = 0;
volatile bool flag = false;            // флаг что нужно вывести
volatile bool value_b = 0;
volatile byte prevA = 0;
volatile int encValue = 0;
volatile unsigned long pulseLen = 0;

void setup()
{
  lcd.begin (16,2);
  analogWrite(LCD_BACKLIGHT_PIN, 200);
  digitalWrite(PIN_A,HIGH);
  digitalWrite(PIN_B,HIGH);
  attachInterrupt(0, handler_a, CHANGE);
}

void loop()
{ 
  if(flag)
  {
    lcd.setCursor( 0, 0);
    lcd.print(encValue);    
    flag = false;
  } 
}

void handler_a()
{
  byte A = digitalRead(PIN_A);
  if(!flag)
  {                                   // пока не отчитались ничего больше не делаем
    if(prevA && !A)
    {                                 // фронт упал
      value_b = digitalRead(PIN_B);   // определили направление, но пока только "запомнили его"
      failingTime = millis();         // и запомнили когда мы "упали в ноль", начали отсчет длины импульса
    }
    if(!prevA && A && failingTime)
    {                                 // восходящий фронт и мы в режиме "отсчет времени импульса
      pulseLen = millis() - failingTime;
      if( pulseLen > PULSE_PIN_LEN)
      {                               // импульс бы достаточно длинный что-бы поверить тому что мы прочитали в его начале
        if(value_b)
          encValue++; 
        else encValue--;
        flag = true;                  // включаем пометку что нужно отчитаться в Serial
      }
      failingTime = 0;                // больше не ведем отсчет времени импульса
    }
  }
  prevA = A;
}

 

vvadim
Онлайн
Зарегистрирован: 23.05.2012

Вот ещё в loop() небольшое изменение  -  стираем на дисплее старые значения


void loop()
{ 
  if(flag)
  {
    lcd.setCursor( 0, 0);
    lcd.print(encValue);  
    lcd.print("      ");
    flag = false;
  } 
}
Radjah
Offline
Зарегистрирован: 06.08.2014

Тогда проще через sprintf пропустить и всё время буфер выводить.

sva1509
Offline
Зарегистрирован: 07.12.2012

вот рабочий кусок кода опроса 2-ух энкодеров (взято из рабочего проэкта)


union {
        struct {
                unsigned a0:1;
                unsigned b0:1;
                unsigned a1:1;
                unsigned b1:1;
                unsigned bt2:1;
                unsigned bt3:1;
                unsigned m:2;
        } b;
        byte sig;
}mask,old;

uint8_t oldb;  // Старое нажатие

void myItoa(uint16_t i,char *a,uint8_t len);

extern Flags enFlags;
extern byte regim;

void encInit()
{
        DDRB = 0;
        PORTB = 0x3F; // занято 6 ног порта B
		DDRD &= 0x3F;
		PORTD |= 0xC0;
        old.sig = PINB & 0x3F;
		oldb = PIND >> 6;
        enFlags.en0 = 0;
        enFlags.way0 = 0;
        enFlags.en1 = 0;
        enFlags.way1 = 0;
        enFlags.bt0 = 0;
        enFlags.bt1 = 0;
        PCMSK0 = 0x3F;
		PCMSK2 = 0xC0;
        PCICR |= 5;
}
ISR(PCINT0_vect)
{
unsigned char c1;

        mask.sig = PINB & 0x3F;
        c1 = mask.sig ^ old.sig;
        if (c1 & 1) {
                if (mask.b.a0 ^ mask.b.b0) enFlags.way1 = 0; else enFlags.way1 = 1; // Определение направления вращения
                enFlags.en1 = 1; // флаг события - что энкодер повернулся
        }
        if (c1 & 4) {
                if (mask.b.a1 ^ mask.b.b1) enFlags.way0 = 0; else enFlags.way0 = 1;
                enFlags.en0 = 1;        
        }
        if (c1 & 16) {
                if (!mask.b.bt2) enFlags.bt2 = 1; // кнопка энкодера
        }
        if (c1 & 32) {
                if (!mask.b.bt3) enFlags.bt3 = 1;
        }
        old.sig = mask.sig;
}

 

dim@n78
Offline
Зарегистрирован: 28.11.2014
Вот мой скеч рабочий никаких пропусков у меня мега2650 кидать нужно пины на контакты прерывания
#include <Encoder.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

Encoder knobLeft(2, 3);
Encoder knobRight(18, 19);

LiquidCrystal_I2C lcd(0x27,16,2);


void setup() {
    lcd.init();               
   lcd.backlight();
   
}

long positionLeft  = -9999999;
long positionRight = -9999999;

void loop() {
  long newLeft, newRight;
  newLeft = knobLeft.read();
  newRight = knobRight.read();
  if (newLeft != positionLeft || newRight != positionRight) {
    lcd.setCursor(0, 0);
    lcd.print("Llne X: ");
    lcd.print(newLeft);
    lcd.setCursor(0, 1);
    lcd.print("Llne Z: ");
    lcd.print(newRight);
    
    positionLeft = newLeft;
    positionRight = newRight;
  }
  
  if (Serial.available()) {
    Serial.read();
    lcd.println("Reset both knobs to zero");
    knobLeft.write(0);
    knobRight.write(0);
  }
}

 

Radjah
Offline
Зарегистрирован: 06.08.2014

Ты идиот? За такой код надо пальцы ломать.

vvadim
Онлайн
Зарегистрирован: 23.05.2012

Жёстко ответил...

Gippopotam
Gippopotam аватар
Offline
Зарегистрирован: 12.09.2014

Radjah пишет:

Ты идиот? За такой код надо пальцы ломать.

Прокоментируй, пожалуйста.

Radjah
Offline
Зарегистрирован: 06.08.2014

А что тут комментировать?

Оп начал делать всё асинхронно через прерывания, а этот идиот предлагает вместо этого часто опрашивать порты, авось там чего повится и авось попадем в импульс. Любой минимальный дополнительный функционал приведет к неработоспособности этого говнокода.

Gippopotam
Gippopotam аватар
Offline
Зарегистрирован: 12.09.2014

Экий ты кровожадный...

 

dim@n78
Offline
Зарегистрирован: 28.11.2014

Radjah пишет:

Ты идиот? За такой код надо пальцы ломать.

Из-за таких как ты муд_____ков, у нас вся Рооссия в жопе каждый прыщ начинает понты корячить,а твои ответы равносильно таму "Как построить самолет? Сделать крылья машине!" а потом стоять и кричать что никто ничего не умеет делать!

dim@n78
Offline
Зарегистрирован: 28.11.2014

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

мож че посоветуете

 и тысячах путаются а потом сами становятся как на ком порту.

#define encoder0PinA 2
#define encoder0PinB 3
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
volatile long encoder0Pos;

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

  pinMode(encoder0PinA, INPUT);
  pinMode(encoder0PinB, INPUT);
  digitalWrite(encoder0PinA, HIGH);
  digitalWrite(encoder0PinB, HIGH);

  
  attachInterrupt(0, encoderPinChangeA, CHANGE);  // прерывание 0 (вывод 2)
  attachInterrupt(1, encoderPinChangeB, CHANGE);  // прерывание 0 (вывод 3)
  
  encoder0Pos = 0;  // положение энкодера для 0
}

void loop() {
  
  lcd.setCursor(0, 0);
  lcd.print(encoder0Pos);
  Serial.println(encoder0Pos);
      delay(100);
}

void encoderPinChangeA() {
  if (digitalRead(encoder0PinA) == HIGH) {

    if (digitalRead(encoder0PinB) == LOW) {
      encoder0Pos = encoder0Pos + 1;        
    }
    else {
      encoder0Pos = encoder0Pos - 1;        
    }
  }
  else 
  {
    if (digitalRead(encoder0PinB) == HIGH) {
      encoder0Pos = encoder0Pos + 1;       
    }
    else {
      encoder0Pos = encoder0Pos - 1;       
    }
  }
}

void encoderPinChangeB() {
  if (digitalRead(encoder0PinB) == HIGH) {
    if (digitalRead(encoder0PinA) == HIGH) {
      encoder0Pos = encoder0Pos + 1;        
    }
    else {
      encoder0Pos = encoder0Pos - 1;       
    }
  }
  else {
    if (digitalRead(encoder0PinA) == LOW) {
      encoder0Pos = encoder0Pos + 1;        
    }
    else {
      encoder0Pos = encoder0Pos - 1;      
    }
  }
}

 

dim@n78
Offline
Зарегистрирован: 28.11.2014

На лсд остаются иногда последние цыфры а показывает правельно

нашол проблему надо очистить от старых показаний lcd.print(" ")

Radjah
Offline
Зарегистрирован: 06.08.2014

Ой! А кто это там со стороны параши тяфкает?

Да это же димсобакансемдесятвосемь!

А под чем это он? Под лсд конечно! Да он код научился писать! Только грамоте разучился при этом. Какая незадача! А еще этот говнокодер в полите разбирается!

Только код у него всё равно говно, хотя воняет уже меньше.

Datak
Offline
Зарегистрирован: 09.10.2014

Radjah пишет:
всё равно говно, хотя воняет уже меньше.

Сдаётся мне, джентльмены, это был комплимент...

dim@n78
Offline
Зарегистрирован: 28.11.2014

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

Radjah
Offline
Зарегистрирован: 06.08.2014

Слив защитан. Следущий.

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

Radjah ты то чего до грубости дошел?

dim@n78
Offline
Зарегистрирован: 28.11.2014

И еще вопросик нужно ли делать подтяжку к земле на энкодерах оптических?

vvadim
Онлайн
Зарегистрирован: 23.05.2012

Шо за взаимные оскорбления. Знает-не знает, умеет-не умеет - не причина паскудить форум.

dim@n78
Offline
Зарегистрирован: 28.11.2014

Согласен.он сам зацепил

vvadim
Онлайн
Зарегистрирован: 23.05.2012

Ну если кто то и нахамил - можно и не ответить. Это единственный форум из всех, где я сижу, который без мата и скотства. Жалко будет, если и он опаскудится. Есть простая жизненая мудрость -БЕРЕГИ ТО ЧТО ЕСТЬ. ПОТЕРЯТЬ МОЖНО - ВЕРНУТЬ - НЕТ! Это касается и женщин и общения.

dim@n78
Offline
Зарегистрирован: 28.11.2014

Прошу у всех извинения!