Счетчики. Вариант 4 - Энергонезависимый счетчик готовой продукции

23.07.2017 01:11

jazon



        Всем доброго... В этой статье мы будем знакомиться с прототипом счетчика готовой продукции. В прототипе реализована возможность записи подсчитанного количества единиц продукта в энергонезависимую память EEPROM. Итак, какие компоненты мы использовали в данной реализации? Давайте перечислим их, это:

  1.  За основу взята плата Arduino Nano с чипом ATMEGA 328P
  2. Вспомогательный шилд I/O Wireless Shield for Nano
  3. Акриловые щитки для шилда(Своего рода корпус)
  4. Дисплей LCD1602 с конвертором I2C
  5. Две тактовые кнопки(Зеленая и красная)
  6. Соединительные провода
  7. Корректная библиотека для работы с LCD1602 + I2C
  8. Желание сделать интересную и полезную вещь своими руками

        Вообще, за основу можно взять любую плату Arduino, будь то Mega, UNO, и др. Главное условие - наличие в чипе энергонезависимой памяти EEPROM. Что еще? Думаю что схема тут не будет лишней !!!


 

        Очень неплохо будет собрать прототип где нибудь на стенде, что мы и сделали. Хотя, в общем то можно его собрать уже в готовый корпус, если есть желание... Посмотрев видео ниже, вы сами решите какой вам удобен вариант, у нас он всё равно собран на стенде. Немного о кнопках:

  • Зеленая кнопка. Предназначена для имитации датчика, через который проходит подсчитываемый продукт. Вместо кнопки можно использовать любой другой датчик(лично мы использовали кнопку - просто за неимением нужного нам датчика), будь то индуктивный, емкостной, или даже контактный. Всё будет зависеть от типа выпускаемой продукции, здесь необходимо ясно и четко понимать как  единица продукта, проходящая по конвейеру(или производственной линии) будет взаимодействовать с датчиком. Также необходимо согласовать выходной сигнал датчика с платой Arduino.
  • Красная кнопка. Предназначена для записи/обнуления значения количества единиц прошедшего продукта в энергонезависимую память EEPROM. Однократное кратковременное нажатие красной кнопки приводит к записи в память EEPROM значения прошедшего количества единиц за смену(суммируется значение хранящееся в памяти EEPROM и значение прошедших за смену единиц продукта), однократное удержание в течение приблизительно 10-ти секунд приводит к полному обнулению значения как в памяти EEPROM так и количества прошедших за смену единиц продукта.  
  • Важным моментом будет включение в программу функции по подавлению дребезга кнопок и фильтру ложных срабатываний, так как кнопки механические, и по умолчанию к ним прилагается такой нехилый дребезг контактов. То же самое касается и контактных концевых датчиков, всё это оборудование даёт дребезг контактов который придется учитывать и отфильтровывать. Но этот момент уже реализован в листинге скетча №2, вообще тема подавления дребезга заслуживает отдельной статьи, возможно скоро она будет выпущена.

        Принцип работы прототипа тоже нужно расписать поподробнее... Итак, после подачи питания на дисплей будет выведено сообщение о том что память EEPROM используется. Это произойдет потому что в скетче предусмотрена единократная операция подготовки участка памяти EEPROM для хранения будущего значения общего количества единиц продукта. Обычно каждая ячейка памяти EEPROM заполнена значением 255, это конечно в том случае если память EEPROM никогда не использовалась и не перезаписывалась с конкретно определённой целью. Но если в ваших проектах всё же когда то использовалась память EEPROM, ниже следующий скетч поможет привести её в первоначальное состояние(заполнить все ячейки значением 255). Здесь главное задать размер памяти EEPROM в строке #define SIZE_OF_EEPROM

Листинг скетча №1
#include "EEPROM.h"

#define SIZE_OF_EEPROM 1024

void setup()
{
  for(int i = 0; i < SIZE_OF_EEPROM; i ++)
    EEPROM.write(i , 255);
}

void loop()
{

}   

        Что же, достаточно загрузить этот скетч всего один раз, далее можно загружать именно программу счетчика, но до неё мы еще дойдём ниже. Далее, после сообщения что память EEPROM используется будет отрисовка стандартного экрана, с двуми строками:

  • Строка Shift - Количество продукции прошедшей через датчик подсчета единиц продукта(у нас эту функцию выполняет зеленая кнопка)
  • Строка Total - Общее количество всей продукции прошедшей через датчик подсчета единиц продукта. Числовое значение как можно уже догадаться берется из энергонезависимого участка памяти EEPROM.

        Собрав данный прототип(по схеме конечно), можно его протестировать. К примеру, вот несколько тестов, которые можно в общем то увидеть посмотрев нижеследующее видео. Но обо всём по порядку... Итак:

  • Короткими нажатиями на зеленую кнопку, можно инкрементировать значение в строке Shift, это и будет значение количества единиц продукта прошедших через датчик подсчета за смену.
  • Записать значение из строки Shift в энергонезависимую память EEPROM можно коротким нажатием на красную кнопку, при этом значение в строке Total суммируется со значением в строке Shift, далее значение в строке Shift обнуляется.
  • При отключении/включении питания значение в строке Total сохраняется(EEPROM как никак), если значение в строке Shift не было до этого записано в EEPROM то оно не сохраняется.
  • Продолжительное удержание красной кнопки (около 10 секунд) приводит к полному обнулению участка памяти EEPROM где хранится общее значение подсчитанных единиц прошедшего продукта. При продолжительном удержании, на дисплее будет отрисовано соответствующее сообщение с отсчетом в секундах до обнуления памяти EEPROM.
  • Если при продолжительном удержании всё таки отпустить красную кнопку(до того как отсчет прекратится) - то операция обнуления будет отменена, и на дисплей снова будет выведены строки Shift и Total с количеством подсчитанных единиц продукта.

       Дальнейшее видео продемонстрирует работу скетча:

Ну и наконец, скетч, который был реализован для данного прототипа, сразу предупреждаем - скетч очень длинный(ну ооочень многа букафф), собрав прототип можете просто залить скетч в плату Arduino, его можно скачать архивом здесь, либо можно просто скопировать из окна браузера:

Листинг скетча №2
#include "EEPROM.h"
#include "Wire.h"
#include "LiquidCrystal_I2C.h"

#define INCRES 2
#define WRT_EEPROM 3
#define STATUS_EEPROM_ADDR 8
#define SIZE_OF_EEPROM 1024
#define COLS 16
#define ROWS 2
#define DISPLAY_ADDR 0x27
#define DELAY_RENDERING 40

LiquidCrystal_I2C LCD(DISPLAY_ADDR, COLS, ROWS);

//Функция печатания символов при отрисовке сообщения
void Show_Messages(char msg[], 
                      int msgLength, 
                        int delayRendering, int strPosition);

//Различные сообщения выводимые на дисплей
char EEPROM_Status[16] = "EEPROM Status: ";
char MemoryIsClean[16] = "Memory is clean.";
char MemoryIsUsed[16]  = "Memory used...";
char MemoryErased[16]  = "Memory erased...";
char WritingValue[16]  = "Writing a value";
char PreparingMem[16]  = "Preparing memory";
char ErasingAfter[16]  = "Erasing after";
 
char MSG_Shift[8] = "Shift   ";
char MSG_Total[8] = "Total   ";

//Переменные для хранения подсчитанных единиц продукта
unsigned long CurrentCounter = 0;
unsigned long EEPROM_Counter = 0;

//Переменные для отсчета перед операция обнуления EEPROM
unsigned long previousMillis = 0;
int resetCounter = 10;
int period = 1000;

//Переменные для подавления дребезга контактов кнопок
bool PressCurrEvent = false;
bool PressPrevEvent = false;

bool SensCurrEvent = false;
bool SensPrevEvent = false;

//Режимы работы
//0 - Стандартный режим, отображать подсчитанные единицы
//в строках Shift и Total
//1 - Режим обнуления памяти EEPROM 
short int Mode = 0;

void setup() 
{
  //Назначение портов
  pinMode(INCRES, INPUT);
  pinMode(WRT_EEPROM, INPUT);

  //Стандартные инструкции инициализации дисплея
  LCD.begin();
  LCD.clear();
  LCD.backlight();

  //Считать значение из EEPROM, либо обработать  
  //для последующей работы участок памяти
  ReadEEPROM();
}

void loop() 
{
  unsigned long currentMillis = millis();
  
  //Считываем событие с датчика, через который проходит продукт
  SensCurrEvent = Debounce(INCRES, SensPrevEvent);
  if(SensPrevEvent == false && SensCurrEvent == true)
  {
    if(CurrentCounter == 99999999)
      CurrentCounter = 0;
    else
      CurrentCounter ++;
  }
  SensPrevEvent = SensCurrEvent;

  //Считываем событие с кнопки записи/обнуления EEPROM
  PressCurrEvent = Debounce(WRT_EEPROM, PressPrevEvent);
  if(PressPrevEvent == false && PressCurrEvent == true)
  {
    WriteEEPROM();
  }
  PressPrevEvent = PressCurrEvent;

  // Далее работа с режимами
  switch(Mode)
  {
    //В режиме 0 показать основные параметры
    case(0):
      UpdateDisplay();
      break;
    //В режиме 1 вызов функции обнуления EEPROM
    case(1):
      EraseEEPROM();
      break;
  }
}

//Функция подавления дребезга контактов кнопок и фильтрации
//ложных срабатываний
boolean Debounce(int ScanPort, boolean last)
{
  boolean current = digitalRead(ScanPort);
  if(current != last)
  {
    delay(5);
    current = digitalRead(ScanPort); 
  }
  return current;
}

//Функция подготовки строк Shift и Total 
//к выводу их на дисплей со значениями
void UpdateDisplay()
{
  unsigned long CountValue = CurrentCounter;

  //Блок подготовки верхней строки дисплея
  LCD.setCursor(0, 0);
  LCD.print("Shift   ");
  for(int i = 15; i >= 8; i --)
  {
    if(CountValue == 0)
    {
      if(i == 15)
      {
        LCD.setCursor(i, 0);
        LCD.print(CountValue);
      }
      else
      {
        LCD.setCursor(i, 0);
        LCD.print(" ");
      }
    }  
    else
    {
      LCD.setCursor(i, 0);
      LCD.print(CountValue % 10);
      CountValue = CountValue / 10;
    }
  }
  
  //Блок подготовки нижней строки дисплея
  LCD.setCursor(0, 1);
  LCD.print("Total   ");
  CountValue = EEPROM_Counter;// + CurrentCounter;
  for(int i = 15; i >= 8; i --)
  {
    if(CountValue == 0)
    {
      if(i == 15)
      {
        LCD.setCursor(i, 1);
        LCD.print(CountValue);
      }
      else
      {
        LCD.setCursor(i, 1);
        LCD.print(" ");
      }
    }  
    else
    {
      LCD.setCursor(i, 1);
      LCD.print(CountValue % 10);
      CountValue = CountValue / 10;
    }
  }
}

//Функция получения и форматирования значения из 
//участка памяти EEPROM
unsigned long getEEPROMValue()
{
  unsigned long rValue = 0;
  unsigned long tValue = 10000000;

  for(int i = 0; i < 8; i ++)
  {
    rValue = (EEPROM.read(i) * tValue) + rValue;
    tValue = tValue / 10;
  }
  return rValue;
}

//Функция обнуления памяти EEPROM с отрисовкой 
//сообщений о статусе устройства 
void EraseEEPROM()
{
  LCD.setCursor(0, 1);
  LCD.print(ErasingAfter);
  LCD.setCursor(14, 1);
  LCD.print(resetCounter);
  LCD.setCursor(15, 1);
  LCD.print("s");
  
  unsigned long currentMillis = millis();

  if(digitalRead(WRT_EEPROM))
  {
    if(currentMillis - previousMillis > period)    
    {
      resetCounter --;
      if(resetCounter == 0)
      {
        LCD.clear();
        LCD.setCursor(0, 0);
        LCD.print(EEPROM_Status);
        Show_Messages(MemoryErased, strlen(MemoryErased), DELAY_RENDERING, 1);
        
        for(int i = 0; i < SIZE_OF_EEPROM; i ++)
          EEPROM.write(i , 255);
     
        ReadEEPROM();
        resetCounter = 10;
        Mode = 0;
      }
      previousMillis = currentMillis;
    }
  }
  else
  {
    resetCounter = 10;
    Mode = 0;
  }
}

//Функция записи/суммирования в память EEPROM текущего
//значения Shift
void WriteEEPROM()
{
  unsigned long CountValue = CurrentCounter + EEPROM_Counter;

  LCD.clear();

  Show_Messages(EEPROM_Status, strlen(EEPROM_Status), DELAY_RENDERING, 0);
  Show_Messages(WritingValue,  strlen(WritingValue),  DELAY_RENDERING, 1);
  delay(1500);
  
  for(int i = 7; i >= 0; i --)
  {
    EEPROM.write(i, CountValue % 10);
    CountValue = CountValue / 10;
  }

  CurrentCounter = 0;
  EEPROM_Counter = getEEPROMValue();

  if(digitalRead(WRT_EEPROM))
  {
    LCD.setCursor(0, 1);
    LCD.print("                ");
    delay(100);
    Mode = 1;
  }
}

//Функция первичного считывания участка памяти EEPROM
void ReadEEPROM()
{
  //Считать ячейку которая хранит статус записи EEPROM
  //может содержать два значения:
  //255 - ячейка и EEPROM никогда не перезаписывались
  //0 - ячейка и EEPROM уже включены в работу и содержат данные

  switch(EEPROM.read(STATUS_EEPROM_ADDR))
  {
    //Считать значение из EEPROM
    case(0):
      Show_Messages(EEPROM_Status, strlen(EEPROM_Status), DELAY_RENDERING, 0);
      Show_Messages(MemoryIsUsed,  strlen(MemoryIsUsed),  DELAY_RENDERING, 1);
      delay(1500);
      LCD.clear();
      break;
    //Подготовить EEPROM к работе
    case(255):
      for(int i = 0; i < 9; i ++)
        EEPROM.write(i , 0); 
      Mode = 0;
      break;
  }
  EEPROM_Counter = getEEPROMValue();
}

//Простая функция для отрисовки сообщений на дисплее
//в стиле фильма "Терминатор"
void Show_Messages(char msg[], int msgLength, int delayRendering, int strPosition)
{
  LCD.leftToRight();
  
  for(int j = 0; j < msgLength; j ++)
  {
    LCD.setCursor(j, strPosition);
    LCD.write(msg[j]);
    delay(delayRendering);
  }
}


Ну вот на этом как бы и всё, кому понравилось или кто то уже применил данный прототип в деле - пишите свои отзывы ниже в комментариях, также с уважением отнесёмся к вашей критике. Всем спасибо !!!



Расскажи о нас

Сообщение

Если у Вас есть опыт в работе с Arduino и собственно есть время для творчества, мы приглашаем всех желающих стать авторами статей публикуемых на нашем портале. Это могут быть как уроки, так и рассказы о ваших экспериментах с Arduino. Описание различных датчиков и модулей. Советы и наставления начинающим. Пишите и размещайте свои статьи в соответсвующей ветке форума.