Menu arduino dengan rotary encoder

Rotary encoder (shaft encoder) adalah komponen pengukuran arah putaran. Komponen ini memiliki dua pin keluaran yang menghasilkan sinyal pulsa yang diproses dahulu untuk mendapatkan arah putarannya.

Sinyal rotary encoder:

 

Rotary encoder tersedia dalam bermacam type, dan tipe yang digunakan dalam program ini ada type potensio rotary encoder.

Sinyal keluaran dari type ini terlihat dapat digambarkan sbb:

komponen aplikasi sketch menu rotary encoder berbasis arduino:

  1. Arduino Uno
  2. LCD 16×2 backpack I2C
  3. Momentary Rotary Encoder

Skema arduino menu menggunakan rotary encoder:

 

Sketch menu arduino ini menggunakan external interrupt. untuk arduino uno hanya bisa menggunakan pin 2 dan 3, sedangkan arduino mega bisa menggunakan pin 2, 3, 18, 19, 20 dan 21.

program/aplikasi arduino menu arduino dengan lcd I2c 16×2 menggunakan rotary encoder:

 
#define pinRotaryEncoderCLK     2
#define pinRotaryEncoderDT      3
#define pinRotaryEncoderSwitch  4

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

LiquidCrystal_I2C lcd(0x3f, 16, 2); // coba juga alamat 0x27 (tergantung seri back pack)

uint8_t maskSensorA;
uint8_t maskSensorB;
uint8_t *pinSensorA;
uint8_t *pinSensorB;
volatile bool encoderAFlag = 0;
volatile bool encoderBFlag = 0;

int8_t nilaiEncoder = 0;
int nilaiSetting[4];
byte setMode;

void setup() {
  Serial.begin(9600);
  Serial.println("Menu rotary encoder");
  Serial.println("https://www.project.semesin.com/");
  Serial.println();

  pinMode(pinRotaryEncoderCLK, INPUT_PULLUP);
  pinMode(pinRotaryEncoderDT, INPUT_PULLUP);
  pinMode(pinRotaryEncoderSwitch, INPUT_PULLUP);

  Wire.begin();
  Wire.beginTransmission(0x3F);
  if (Wire.endTransmission())
  {
    lcd = LiquidCrystal_I2C(0x27, 16, 2);
  }
  lcd.begin();
  lcd.backlight();

  attachInterrupt(digitalPinToInterrupt(pinRotaryEncoderCLK), encoderARising, RISING);
  attachInterrupt(digitalPinToInterrupt(pinRotaryEncoderDT), encoderBRising, RISING);

  maskSensorA  = digitalPinToBitMask(pinRotaryEncoderCLK);
  pinSensorA = portInputRegister(digitalPinToPort(pinRotaryEncoderCLK));
  maskSensorB  = digitalPinToBitMask(pinRotaryEncoderDT);
  pinSensorB = portInputRegister(digitalPinToPort(pinRotaryEncoderDT));

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("A:0     C:0");
  lcd.setCursor(0, 1);
  lcd.print("B:0     D:0");
}

void loop() {
  if (nilaiEncoder != 0)
  {
    Serial.println(nilaiEncoder);
    nilaiSetting[setMode] += nilaiEncoder;
    nilaiEncoder = 0;

    lcd.setCursor(((setMode / 2) * 8) + 2, setMode % 2);
    lcd.print(nilaiSetting[setMode]);
    lcd.print(" ");
  }
  if (!digitalRead(pinRotaryEncoderSwitch))
  {
    delay(50);
    setMode = (setMode + 1) % 4;
    while (!digitalRead(pinRotaryEncoderSwitch));
    Serial.println(setMode);
  }
}
void encoderARising() {
  if ((*pinSensorB & maskSensorB) && encoderAFlag)
  {
    nilaiEncoder = -1;
    encoderAFlag = false;
    encoderBFlag = false;
  }
  else
  {
    encoderBFlag = true;
  }

}

void encoderBRising() {
  if ((*pinSensorA & maskSensorA) && encoderBFlag)
  {
    nilaiEncoder = 1;
    encoderAFlag = false;
    encoderBFlag = false;
  }
  else
  {
    encoderAFlag = true;
  }
}

library yang digunakan:

penggunaan aplikasi sketch menu rotary encoder:

  1. tekan knob untuk memilih menu setting
  2. putar knob ke untuk mengganti nilai setting

Dokumentasi menu rotary encoder lcd i2c:

Menu dan submenu LCD TFT dengan arduino

Sistem menu bertingkat (menu dan sub menu) adalah sistem pilihan/pengaturan yang terstruktur dalam kelompok-kelompok (sub menu).

Menu arduino juga bisa diterapkan pada lcd TFT (2.4″). Keunggulan sistem menu ini adalah pengaturan item menu bisa dengan mudah dimodifikasi, karena disusun dalam ‘struct’.

 
komponen yang digunakan:

  1. Arduino Mega 2560
  2. LCD TFT 2.4″

Pengaturan menu

menu dalam program ini menggunakan struktur :

struct Menu
{
  byte tipe;
  void *variabel;
  uint16_t nilaiMin;
  uint16_t nilaiMax;
  void *subMenu;
};

struktur menu harus dibuat mengikuti struktur diatas yaitu:

  1. tipe dapat berupa UInt8, UInt16, Float, textDropDown, subMenu, dll
  2. variabel merupakan alamat dari nilai setting sesuai tipe yang diberikan
  3. nilaiMin dan nilaiMax merupakan batas setting.
  4. subMenu alamat struktur menu apabila tipenya adalah subMenu.

coding arduino menu tft lcd:

#define LCD_CS                A3
#define LCD_CD                A2
#define LCD_WR                A1
#define LCD_RD                A0

#define LCD_RESET             A4

#define YP                    A1
#define XM                    A2
#define YM                    7
#define XP                    6

#define jumlahLevelMenu       2
#define panjangTextMenu       17
#define jumlahMenuDalamSatuLayar         4

#define lebarKolom1           210
#define tengahKolom1          (lebarKolom1/2)
#define tinggiBaris1          197
#define lebarKolom2           106
#define tengahKolom2          (214 + (lebarKolom2/2))
#define ringXPos              0
#define ringYPos              0
#define ringRadius            100

#define TS_MINX 130
#define TS_MINY 141
#define TS_MAXX 920
#define TS_MAXY 935

#define TS_MINPRESSURE 10
#define TS_MAXPRESSURE 1000

//========================================================
#include <Adafruit_GFX.h>
#include <SPFD5408_Adafruit_TFTLCD.h>
#include <SPFD5408_TouchScreen.h>

#define BLACK   0x0000
#define WHITE   0xFFFF
#define RED     0xF800
#define GREEN   0x07E0
#define BLUE    0x001F
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define GREY    0x2108

#define RED2RED 0
#define GREEN2GREEN 1
#define BLUE2BLUE 2
#define BLUE2RED 3
#define GREEN2RED 4
#define RED2GREEN 5

enum MenuMode
{
  UInt8,
  UInt16,
  Float,
  textDropDown,
  subMenu,
  commandSettingPabrik,
};

struct Menu
{
  byte tipe;
  void *variabel;
  uint16_t nilaiMin;
  uint16_t nilaiMax;
  void *subMenu;
};

struct MenuIndex
{
  byte index;
  char *menutext;
  Menu *menu;
  byte showIndex;
  byte menuLength;
  char *dropDown;
  byte dropDownLength;
};

struct Setting
{
  byte lampu;
  byte alarm;
  byte kipas;
  byte kontras;
  byte kecerahan;
  byte suhuSet;
  byte kelembabanSet;
  byte rollerMode;
  long rollerJeda;
  long rollerDurasi;
  long istirahatMode;
  long istirahatkipas;
  long istirahatJeda;
  long istirahatDurasi;
  byte lampuLatar;
  byte humidifier;
  long rollerSebelumnya;
  long istirahatSebelumnya;
  byte tombol;
};

struct TouchScreenKode
{
  uint16_t x1;
  uint16_t y1;
  uint16_t x2;
  uint16_t y2;
  byte kode;
};

//variabel
bool aktif;
float suhu;
float kelembaban;

uint16_t intensitasCahaya;
uint16_t aliranUdara;
uint16_t levelSuara;
uint16_t warnaLampu;

uint8_t jam;
uint8_t menit;
uint8_t detik;
uint8_t hari;
uint8_t tanggal;
uint8_t bulan;
uint8_t tahun;

Setting setting;

//Dropdown menu
const char aktifText[][panjangTextMenu] PROGMEM =
{
  "Tidak",
  "Ya",
};
const char pilihanBatalLanjut[][panjangTextMenu] PROGMEM  =
{
  "Batal",
  "Lanjut",
};
const char pilihanHidupMati[][panjangTextMenu] PROGMEM  =
{
  "Hidup",
  "Mati",
};
const char pilihanHidupMatiAuto[][panjangTextMenu] PROGMEM  =
{
  "Hidup",
  "Mati",
  "Auto",
};

const char pilihanKipas[][panjangTextMenu] PROGMEM =
{
  "Hidup",
  "Mati",
  "Humidity",
  "Lampu",
};

const char pilihanAlarm[][panjangTextMenu] PROGMEM =
{
  "Temp",
  "roll",
  "istrh",
  "Mati"
};

const char menuStrWaktu[][panjangTextMenu] PROGMEM =
{
  "1. Jam",
  "2. Menit",
  "3. Detik",
  "4. Tanggal",
  "5. Bulan",
  "6. Tahun",
};

//Sub menu
//tipe            variabel  nilaiMin nilaiMax submenu  jumlahBaris
const Menu menuWaktu[] =
{
  {UInt8         , &jam     , 1     , 24    , 0 },
  {UInt8         , &menit   , 0     , 59    , 0 },
  {UInt8         , &detik   , 0     , 59    , 0 },
  {UInt8         , &tanggal , 1     , 31    , 0 },
  {UInt8         , &bulan   , 1     , 12    , 0 },
  {UInt8         , &tahun   , 0     , 99    , 0 },
};

const char menuStrPeralatan[][panjangTextMenu] PROGMEM =
{
  "1. Kipas",
  "2. Alarm",
  "3. Kontras",
  "4. Kecerahan",
  "5. Lampu Latar",
  "6. Bunyi Tmbol",
};

const Menu menuPeralatan[] =
{
  {textDropDown  , &setting.kipas      , 0     , (sizeof(pilihanKipas) / sizeof(pilihanKipas[0]) - 1)                , &pilihanKipas         },
  {textDropDown  , &setting.alarm      , 0     , (sizeof(pilihanAlarm) / sizeof(pilihanAlarm[0]) - 1)                  , &pilihanAlarm         },
  {UInt8         , &setting.kontras    , 0     , 99                                                                  , 0                     },
  {UInt8         , &setting.kecerahan  , 0     , 99                                                                  , 0                     },
  {textDropDown  , &setting.lampuLatar , 0     , (sizeof(pilihanHidupMatiAuto) / sizeof(pilihanHidupMatiAuto[0]) - 1)  , &pilihanHidupMatiAuto },
  {textDropDown  , &setting.tombol     , 0     , (sizeof(pilihanHidupMati) / sizeof(pilihanHidupMati[0]) - 1)          , &pilihanHidupMati     },
};

const char menuStrRoller[][panjangTextMenu] PROGMEM =
{
  "1. Mode",
  "2. Jeda (jam)",
  "3. Lama(menit)",
};

const Menu menuRoller[] =
{
  {textDropDown  , &setting.rollerMode      , 0     , (sizeof(pilihanHidupMati) / sizeof(pilihanHidupMati[0])) - 1     , &pilihanHidupMati  },
  {UInt8         , &setting.rollerJeda      , 1     , 24                                                               , 0                  },
  {UInt8         , &setting.rollerDurasi    , 1     , 59                                                               , 0                  },
};

const char menuStrIstirahat[][panjangTextMenu] PROGMEM =
{
  "1. Istirahat",
  "2. Kipas",
  "3. Jeda (jam)",
  "4. Lama(menit)",
};

const Menu menuIstirahat[] =
{
  {textDropDown  , &setting.istirahatMode    , 0     , (sizeof(pilihanHidupMati) / sizeof(pilihanHidupMati[0])) - 1    , &pilihanHidupMati },
  {textDropDown  , &setting.istirahatkipas   , 0     , (sizeof(pilihanHidupMati) / sizeof(pilihanHidupMati[0])) - 1    , &pilihanHidupMati },
  {UInt8         , &setting.istirahatJeda    , 10    , 23                                                              , 0                 },
  {UInt8         , &setting.istirahatDurasi  , 1     , 59                                                              , 0                 },
};

//Menu utama
const char menuStrUtama[][panjangTextMenu] PROGMEM  =
{
  "1.Waktu",
  "2.Peralatan",
  "3.Temperatur",
  "4.Kelembaban",
  "5.Roller",
  "6.Istirahat",
  "7.Set. pabrik",
};

const Menu menuUtama[] =
{
  {subMenu                , &menuStrWaktu          , 0    , sizeof(menuWaktu) / sizeof(menuWaktu[0])                    , &menuWaktu          },
  {subMenu                , &menuStrPeralatan      , 0    , sizeof(menuPeralatan) / sizeof(menuPeralatan[0])            , &menuPeralatan      },
  {UInt8                  , &setting.suhuSet       , 0    , 99                                                          , 0                   },
  {UInt8                  , &setting.kelembabanSet , 0    , 99                                                          , 0                   },
  {subMenu                , &menuStrRoller         , 0    , sizeof(menuRoller) / sizeof(menuRoller[0])                  , &menuRoller         },
  {subMenu                , &menuStrIstirahat      , 0    , sizeof(menuIstirahat) / sizeof(menuIstirahat[0])            , &menuIstirahat      },
  {commandSettingPabrik   , 0                      , 0    , sizeof(pilihanBatalLanjut) / sizeof(pilihanBatalLanjut[0])  , &pilihanBatalLanjut },
};
//========================================================



TouchScreenKode tcKodeIdle[] = {
  {305, 730, 191, 943, 'M'},//menu utama
  {670, 716, 319, 928, 'T'},
  {867, 717, 724, 931, 'W'},
};
TouchScreenKode tcKodeMenu[] = {
  {745, 180, 692, 660, '1'},
  {634, 180, 554, 660, '2'},
  {511, 180, 442, 660, '3'},
  {390, 180, 316, 660, '4'},
  {263, 189, 176, 424, '<'},
  {263, 444, 190, 667, '>'},
  {305, 730, 191, 943, 'B'},
};

TouchScreenKode tcKodeEdit[] = {
  {416, 297, 327, 547, 'S'},
  {263, 189, 176, 424, '-'},
  {263, 444, 190, 667, '+'},
  {305, 730, 191, 943, 'B'},
};


MenuIndex menuIndex[jumlahLevelMenu];

long millismenuText;
int8_t levelMenu = -1;
bool menuEntriNilai;
char *judulMenu;
byte judulMenuTampil;
byte menu_Value8;
uint16_t menu_Value16;
float menu_ValueFloat;

float suhuSebelumnya;
float kelembabanSebelumnya;

Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

byte lebarText;
byte ukuranText;

bool statusLampu;
bool statusKipas;
bool statusHumidifier;
bool rollerStatus;
bool istirahatStatus;

long millisRoller;
long millisIstirahat;

bool alarmStatus;
bool alarmPinStatus;

void setup()
{
  Serial.begin(9600);
  Serial.println("Menu dan submenu menggunakan LCD TFT 2.4\" berbasis arduino");
  Serial.println("https://www.project.semesin.com/");
  Serial.println();
  
  tft.reset();
  tft.begin(0x9341);
  tft.setRotation(1);

  idle();

  suhu = 32;
  kelembaban = 65;

  ambilDefault();
}

void loop()
{
  char tcKode = 0;
  if (levelMenu == -1)
  {
    tcKode = ambilKodeTouchScreen((TouchScreenKode*)&tcKodeIdle, sizeof(tcKodeIdle) / sizeof(TouchScreenKode));
  }
  else if (menuEntriNilai)
  {
    tcKode = ambilKodeTouchScreen((TouchScreenKode*)&tcKodeEdit, sizeof(tcKodeEdit) / sizeof(TouchScreenKode));
  }
  else
  {
    tcKode = ambilKodeTouchScreen((TouchScreenKode*)&tcKodeMenu, sizeof(tcKodeMenu) / sizeof(TouchScreenKode));
  }
  if (tcKode)
  {
    switch (tcKode)
    {
      case 'M':
        levelMenu++;
        menu_Display();
        break;
      case '>':
        menuIndex[levelMenu].showIndex++;
        if (menuIndex[levelMenu].showIndex > (menuIndex[levelMenu].menuLength - (jumlahMenuDalamSatuLayar / 2)))
        {
          menuIndex[levelMenu].showIndex = 0;
        }
        menu_ShowItem();
        break;
      case '<':
        if (menuIndex[levelMenu].showIndex == 0)
        {
          menuIndex[levelMenu].showIndex = (menuIndex[levelMenu].menuLength - (jumlahMenuDalamSatuLayar / 2));
        }
        else
        {
          menuIndex[levelMenu].showIndex--;
        }
        menu_ShowItem();
        break;
      case '1':
      case '2':
      case '3':
      case '4':
        menuIndex[levelMenu].index = menuIndex[levelMenu].showIndex + (tcKode - '1');
        levelMenu++;
        menu_Display();
        break;
      case 'B':
        menuEntriNilai = false;
        levelMenu--;
        menu_Display();
        break;
      case 'S':
        menu_EditSelesai();
        break;
      case '-':
        menu_KurangNilai();
        break;
      case '+':
        menu_TambahNilai();
        break;

    }
  }
}

void menu_Display()
{
  if (levelMenu == -1)
  {
    idle();
    menuIndex[levelMenu].dropDownLength = 0;
  }
  else if (levelMenu == 0)
  {
    menuIndex[levelMenu].index = 0;
    menuIndex[levelMenu].menutext = (char*)menuStrUtama;
    menuIndex[levelMenu].menu = menuUtama;
    menuIndex[levelMenu].showIndex = 0;
    menuIndex[levelMenu].menuLength = sizeof(menuUtama) / sizeof(menuUtama[0]);
    menuIndex[levelMenu].dropDownLength = 0;

    tft.fillRect(0, tinggiBaris1 + 4, 208, 35, BLACK);

    ukuranText = 3;
    tft.setFont();
    tft.setTextColor (WHITE, BLACK);

    tft.fillRect(0, 0, lebarKolom1, tinggiBaris1, BLACK);
    tft.setTextSize (ukuranText);
    lebarText = 10 * ukuranText * 6;
    tft.setCursor(tengahKolom1 - (lebarText / 2), 0);
    tft.print("MENU UTAMA");

    menu_TcMenu();
    menu_ShowItem();
  }
  else
  {
    char buf[panjangTextMenu];
    Menu menuLama = menuIndex[levelMenu - 1].menu[menuIndex[levelMenu - 1].index];

    ukuranText = 3;
    tft.setFont();
    tft.setTextColor (WHITE, BLACK);

    tft.fillRect(0, 0, lebarKolom1, ukuranText * 8, BLACK);
    tft.setTextSize (ukuranText);

    copyFlashString(buf, menuIndex[levelMenu - 1].menutext + (menuIndex[levelMenu - 1].index * panjangTextMenu));

    lebarText = strlen(buf) * ukuranText * 6;
    tft.setCursor(tengahKolom1 - (lebarText / 2), 0);
    tft.print(buf);

    switch (menuLama.tipe)
    {
      case UInt8:
      case textDropDown:
        menu_Value8 = *(uint8_t*)menuLama.variabel;
        menuEntriNilai = true;
        break;
      case UInt16:
        menu_Value16 = *(uint16_t*)menuLama.variabel;
        menuEntriNilai = true;
        break;
      case Float:
        menu_Value16 = *(float*)menuLama.variabel;
        menuEntriNilai = true;
        break;
      case subMenu:
        menuIndex[levelMenu].index = 0;
        menuIndex[levelMenu].menutext = menuLama.variabel;
        menuIndex[levelMenu].menu = menuLama.subMenu;
        menuIndex[levelMenu].showIndex = 0;
        menuIndex[levelMenu].menuLength = menuLama.nilaiMax;
        menuIndex[levelMenu].dropDownLength = 0;

        menu_TcMenu();
        menu_ShowItem();
        break;
      case commandSettingPabrik:
        menu_Value8 = 0;
        menuEntriNilai = true;
        break;
    }
    if (menuEntriNilai)
    {
      menu_TcEdit();
      menu_ShowNilai();
    }
  }
}
void menu_tcIdle()
{
  tft.fillRect(210, 200, 110, 40, YELLOW);
  ukuranText = 3;
  tft.setFont();
  tft.setTextColor (BLACK, YELLOW);
  tft.setTextSize (ukuranText);
  lebarText = 4 * ukuranText * 6;
  tft.setCursor(tengahKolom2 - (lebarText / 2), 208);
  tft.print("MENU");

}
void menu_TcMenu()
{
  tft.fillRect(0, tinggiBaris1 + 4, (lebarKolom1 / 2) - 2, 36, YELLOW);
  tft.fillRect((lebarKolom1 / 2) - 2, tinggiBaris1 + 4, 4, 40, BLUE);
  tft.fillRect((lebarKolom1 / 2) + 2, tinggiBaris1 + 4, (lebarKolom1 / 2) - 2, 36, YELLOW);
  tft.fillRect(lebarKolom1 + 4, tinggiBaris1 + 4, 106, 36, YELLOW);

  ukuranText = 3;
  tft.setFont();
  tft.setTextColor (BLACK, YELLOW);
  tft.setTextSize (ukuranText);
  lebarText = 1 * ukuranText * 6;
  tft.setCursor(((lebarKolom1 / 2) - lebarText) / 2, 208);
  tft.print("<");

  tft.setCursor((((lebarKolom1 / 2) - lebarText) / 2) + (lebarKolom1 / 2), 208);
  tft.print(">");


  tft.setTextSize (ukuranText);
  lebarText = 5 * ukuranText * 6;
  tft.setCursor(tengahKolom2 - (lebarText / 2), 208);
  tft.print("BALIK");
}

void menu_TcEdit()
{
  tft.fillRect(0, tinggiBaris1 - 44, lebarKolom1, 44, BLACK);
  tft.fillRect((lebarKolom1 / 4) - 4, tinggiBaris1 - 44 - 4, (lebarKolom1 / 2) + 8, 36 + 8, BLUE);
  tft.fillRect((lebarKolom1 / 4), tinggiBaris1 - 44, (lebarKolom1 / 2), 36, YELLOW);

  tft.fillRect(0, tinggiBaris1 + 4, (lebarKolom1 / 2) - 2, 36, YELLOW);
  tft.fillRect((lebarKolom1 / 2) - 2, tinggiBaris1 + 4, 4, 40, BLUE);
  tft.fillRect((lebarKolom1 / 2) + 2, tinggiBaris1 + 4, (lebarKolom1 / 2) - 2, 36, YELLOW);
  tft.fillRect(lebarKolom1 + 4, tinggiBaris1 + 4, 106, 36, YELLOW);

  ukuranText = 3;
  lebarText = 3 * ukuranText * 6;
  tft.setFont();
  tft.setTextColor (BLACK, YELLOW);
  tft.setTextSize (ukuranText);
  tft.setCursor((lebarKolom1 - lebarText) / 2, tinggiBaris1 - 40);
  tft.print("Set");

  lebarText = 1 * ukuranText * 6;
  tft.setCursor(((lebarKolom1 / 2) - lebarText) / 2, 208);
  tft.print("-");

  tft.setCursor((((lebarKolom1 / 2) - lebarText) / 2) + (lebarKolom1 / 2), 208);
  tft.print("+");


  tft.setTextSize (ukuranText);
  lebarText = 5 * ukuranText * 6;
  tft.setCursor(tengahKolom2 - (lebarText / 2), 208);
  tft.print("BALIK");
}
void menu_ShowItem()
{
  char buf[panjangTextMenu];
  byte indexMenuMulai;

  ukuranText = 3;
  tft.fillRect(0, ukuranText * 8, lebarKolom1, tinggiBaris1 - ukuranText * 8, BLACK);

  ukuranText = 2;
  tft.setTextSize (ukuranText);
  tft.setFont();
  tft.setTextColor (WHITE, BLACK);

  byte menuItemSize = min(jumlahMenuDalamSatuLayar, menuIndex[levelMenu].menuLength - menuIndex[levelMenu].showIndex);

  for ( byte i = 0; i < menuItemSize; i++)
  {
    byte showIndex = menuIndex[levelMenu].showIndex + i;
    tft.setTextSize (ukuranText);
    tft.setCursor(0, (i * ukuranText * 20) + 50);
    copyFlashString(buf, menuIndex[levelMenu].menutext + (showIndex * panjangTextMenu));
    tft.print(buf);

    char *alamat;

    switch (menuIndex[levelMenu].menu[showIndex].tipe)
    {
      case UInt8:
        itoa(*(uint8_t*)menuIndex[levelMenu].menu[showIndex].variabel, buf, 10);
        lebarText = strlen(buf) * ukuranText * 6;
        tft.setCursor(lebarKolom1 - (lebarText + 10), (i * ukuranText * 20) + 50);
        tft.print(buf);
        break;
      case UInt16:
        itoa(*(uint16_t*)menuIndex[levelMenu].menu[showIndex].variabel, buf, 10);
        lebarText = strlen(buf) * ukuranText * 6;
        tft.setCursor(lebarKolom1 - (lebarText + 10), (i * ukuranText * 20) + 50);
        tft.print(buf);
        break;
      case Float:
        dtostrf(*(float*)menuIndex[levelMenu].menu[showIndex].variabel, 6, 2, buf);
        lebarText = strlen(buf) * ukuranText * 6;
        tft.setCursor(lebarKolom1 - (lebarText + 10), (i * ukuranText * 20) + 50);
        tft.print(buf);
        break;
      case textDropDown:
        copyFlashString(buf, menuIndex[levelMenu].menu[showIndex].subMenu + (*(uint8_t*)menuIndex[levelMenu].menu[showIndex].variabel * panjangTextMenu));
        lebarText = strlen(buf) * ukuranText * 6;
        tft.setCursor(lebarKolom1 - (lebarText + 10), (i * ukuranText * 20) + 50);
        tft.print(buf);
        break;
      case subMenu:
        lebarText = 1 * ukuranText * 6;
        tft.setCursor(lebarKolom1 - (lebarText + 10), (i * ukuranText * 20) + 50);
        tft.print('>');
        break;
      case commandSettingPabrik:
        break;
    }
  }
}

void menu_ShowNilai()
{
  char buf[panjangTextMenu];

  tft.fillRect(0, 50, lebarKolom1, 100, BLACK);
  Menu menuEdit = menuIndex[levelMenu - 1].menu[menuIndex[levelMenu - 1].index];
  switch (menuEdit.tipe)
  {
    case UInt8:
      itoa(menu_Value8, buf, 10);
      break;
    case UInt16:
      itoa(menu_Value16, buf, 10);
      break;
    case Float:
      dtostrf(menu_ValueFloat, 6, 2, buf);
      break;
    case textDropDown:
    case commandSettingPabrik:
      copyFlashString(buf, menuEdit.subMenu + (menu_Value8 * panjangTextMenu));
      break;
  }
  ukuranText = 5;
  tft.setFont();
  tft.setTextColor (WHITE, BLACK);
  tft.setTextSize (ukuranText);
  lebarText = strlen(buf) * ukuranText * 6;

  tft.setCursor((lebarKolom1 - lebarText) / 2, 70);
  tft.print(buf);
}

void menu_TambahNilai()
{
  Menu menuEdit = menuIndex[levelMenu - 1].menu[menuIndex[levelMenu - 1].index];
  switch (menuEdit.tipe)
  {
    case UInt8:
    case textDropDown:
    case commandSettingPabrik:
      if (menu_Value8 < menuEdit.nilaiMax)
      {
        menu_Value8++;
      }
      else
      {
        menu_Value8 = menuEdit.nilaiMin;
      }
      break;
    case UInt16:
      if (menu_Value16 < menuEdit.nilaiMax)
      {
        menu_Value16++;
      }
      else
      {
        menu_Value16 = menuEdit.nilaiMin;
      }
      break;
    case Float:
      if (menu_Value8 < menuEdit.nilaiMax)
      {
        menu_ValueFloat += 0.1;
      }
      else
      {
        menu_ValueFloat = menuEdit.nilaiMin;
      }
      break;
  }
  menu_ShowNilai();

}
void menu_KurangNilai()
{
  Menu menuEdit = menuIndex[levelMenu - 1].menu[menuIndex[levelMenu - 1].index];
  switch (menuEdit.tipe)
  {
    case UInt8:
    case textDropDown:
    case commandSettingPabrik:
      if (menu_Value8 != menuEdit.nilaiMin)
      {
        menu_Value8--;
      }
      else
      {
        menu_Value8 = menuEdit.nilaiMax;
      }
      break;
    case UInt16:
      if (menu_Value16 != menuEdit.nilaiMax)
      {
        menu_Value16--;
      }
      else
      {
        menu_Value16 = menuEdit.nilaiMax;
      }
      break;
    case Float:
      if (menu_Value8 != menuEdit.nilaiMax)
      {
        menu_ValueFloat -= 0.1;
      }
      else
      {
        menu_ValueFloat = menuEdit.nilaiMax;
      }
      break;
  }
  menu_ShowNilai();
}
void menu_EditSelesai()
{
  Menu menuEdit = menuIndex[levelMenu - 1].menu[menuIndex[levelMenu - 1].index];

  switch (menuEdit.tipe)
  {
    case UInt8:
    case textDropDown:
      *(uint8_t*)menuEdit.variabel = menu_Value8;
      break;
    case UInt16:
      *(uint8_t*)menuEdit.variabel = menu_Value16;
      break;
    case Float:
      *(uint8_t*)menuEdit.variabel = menu_ValueFloat;
      break;
    case commandSettingPabrik:
      ambilDefault();
      break;
  }
  menuEntriNilai = false;

  tft.fillRect(0, 50, lebarKolom1, 100, BLACK);
  ukuranText = 3;
  tft.setFont();
  tft.setTextColor (WHITE, BLACK);
  tft.setTextSize (ukuranText);
  lebarText = strlen("Tersimpan") * ukuranText * 6;

  tft.setCursor((lebarKolom1 - lebarText) / 2, 70);
  tft.print("Tersimpan");

  delay(1000);

  levelMenu--;
  menu_Display();

}
void idle()
{
  tft.fillScreen(BLACK);
  tft.setFont();

  menu_tcIdle();
}

char ambilKodeTouchScreen(TouchScreenKode *tcKode, byte jumlahArea)
{
  TSPoint p = ts.getPoint();
  char returnValue = 0;
  if ((p.z > TS_MINPRESSURE ) && (p.z < TS_MAXPRESSURE))
  {
    for (byte i = 0; i < jumlahArea; i++)
    {
      if ((tcKode[i].x1 > p.x) && (tcKode[i].x2 < p.x) && (tcKode[i].y1 < p.y) && (tcKode[i].y2 > p.y))
      {
        byte tsCounter;
        do
        {
          p = ts.getPoint();
          if ((p.z < TS_MINPRESSURE ) || (p.z > TS_MAXPRESSURE))
          {
            tsCounter++;
          }
          else
          {
            tsCounter = 0;
          }
        }
        while (tsCounter < 20);
        returnValue = tcKode[i].kode;
        break;
      }
    }
  }

  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);

  return returnValue;
}
void ambilDefault()
{
  setting.lampu = 2;
  setting.alarm = 0;
  setting.kipas = 2;
  setting.kontras = 55;
  setting.kecerahan = 100;
  setting.suhuSet = 30;
  setting.kelembabanSet = 80;
  setting.rollerMode = 0;
  setting.rollerJeda = 1;
  setting.rollerDurasi = 1;
  setting.istirahatMode = 0;
  setting.istirahatkipas = 0;
  setting.istirahatJeda = 1;
  setting.istirahatDurasi = 1;
  setting.lampuLatar = 0;
  setting.tombol = 0;
  setting.humidifier = 2;
}
byte copyFlashString(char* buf, const char* alamat)
{
  char c;
  byte l = 0;
  while (c = pgm_read_byte(alamat++))
  {
    *buf++ = c;
    l++;
  }
  *buf = 0;
  return l;

}

 
library yang digunakan :

  1. Adafruit_GFX_Library.zip
  2. SPFD5408.zip

Konversi font dmd ke gfx (c font vertikal ke horizontal)

Metode menyimpanan data font memiliki perbedaan antara vendor library.

Font Library DMD

DMD, DMD2, DMD3 menggunakan font dengan metode vertikal seperti ditunjukkan dalam diagram berikut:

Font library GFX

Font yang dikembangkan oleh adafruit dalam GFX menggunakan metode mendatar seperti diagram berikut :

Untuk keperluan konversi font dari dmd ke gfx bisa dilakukan dengan metode invert.

Coding berikut ditulis dengan bahasa php, jadi harus menggunakan web server seperti xampp:

<? php
$fontHorizontal = "";
$fontVertikal = "";
$tinggi = 0;
$lebar = 0;
$charCount = 0;
if (isset($_GET['konversiKeHorizontal']))
{
  $fontVertikal = $_GET['fontVertikal'];
  $tinggi = $_GET['tinggi'];
  $lebar = $_GET['lebar'];
  $charCount = $_GET['jumlahKarakter'];


  $verticalHex = str_getcsv ($fontVertikal, ',');
  for ($i = 0; $i < sizeof($verticalHex); $i++)
  {
    $verticalByte[$i] = hexDec($verticalHex[$i]);
  }

  for ($cc = 0; $cc < $charCount; $cc++)
  {
    $horizontalByte = array();
    for ($i = 0; $i < ($tinggi*$lebar / 8); $i++)
    {
      array_push($horizontalByte, 0);
    }

    $bit = 0;
    $byte = 0;
    $indexHorizontal = 0;
    $byteMask = 1;

    for ($x = 0; $x < $lebar; $x++)
    {
      for ($y = 0; $y < $tinggi; $y++)
      {
        $x2 = $x;
        $y2 = $y;
        if ($y >= 8)
        {
          $x2 += ((int)($y / 8) * $lebar);
          $y2 = $y % 8;
        }
        $indexVertical = ($x2 * 8) + $y2 + ($cc*$lebar*$tinggi);

        $indexVerticalByte = (int)($indexVertical / 8);
        $indexVerticalBit = $indexVertical % 8;


        $indexHorizontal = ($y * $lebar ) + $x;
        $indexHorizontalByte = (int)($indexHorizontal / 8);
        $indexHorizontalBit = $indexHorizontal % 8;

        $verticalBit = $verticalByte[$indexVerticalByte] & pow(2, $indexVerticalBit);
        if ($verticalBit)
        {
          $horizontalByte[$indexHorizontalByte] |= pow(2, 7 - $indexHorizontalBit);
        }
      }
    }
    for ($i = 0; $i < sizeof($horizontalByte); $i++)
    {
      $fontHorizontal . = "0x";
      if ($horizontalByte[$i] < 0x10)
      {
        $fontHorizontal . = '0';
      }

      $fontHorizontal . = decHex($horizontalByte[$i]);
      $fontHorizontal . = ', ';
    }
    $fontHorizontal . = '&#13;&#10;';
  }
}
?>

<html>
  <head><title>font Invert ( vertical to horizontal) - semesin.com</title>
  </head>
  <body>
    <h4>Konversi font DMD menjadi font GFX</h4><br>
    <a href="https://www.project.semesin.com/">https://www.project.semesin.com/</a>
    <form action = "" method = "get">
      Font vertikal : <br><textarea name = "fontVertikal" rows = "10" cols = "100%"> <? php echo $fontVertikal ?> < / textarea > <br>
      Tinggi : <input type = "text" name = "tinggi" value = "<?php echo $tinggi ?>"><br>
      Lebar : <input type = "text" name = "lebar" value = "<?php echo $lebar; ?>"><br>
      Jumlah : <input type = "text" name = "jumlahKarakter" value = "<?php echo $charCount; ?>"><br>
      <input type = "submit" name = "konversiKeHorizontal" value = "Konversi ke Horizontal">
    </form>
    Font horizontal : <br><textarea rows = "10" cols = "100%"> <? php echo $fontHorizontal ?> < / textarea > <br>
  </body >
</html >

 

tampilan konversi font dmd ke gfx:

Konversi masehi ke Pasaran Jawa dan Hijriah dengan metode Julian Date

Julian Date

Julian date adalah tetapan bilangan penanggalan matahari yang dihitung semenjak siang tanggal 1 Januari 4713 SM.

Penanggalan Hijriah

Tanggal 1 Muharram tahun pertama sama dengan bilangan Julian 1948439.5.

Penanggalan Jawa

Sistem Pasaran Jawa memadukan metode penanggalan Hindu, Hijriah dan Julian. Keistimewaan lainnya adalah memiliki siklus pekan, siklus pasaran, siklus wuku, siklus windu.

Sketch program konversi masehi ke pasaran jawa dan Hijriah:

#define epochHijriah          1948439.5f //math.harvard.edu
#define tambahKurangHijriah   0

struct TanggalDanWaktu
{
  uint8_t detik;
  uint8_t menit;
  uint8_t jam;
  uint8_t hari;
  uint8_t tanggal;
  uint8_t bulan;
  uint8_t tahun;
};
struct Tanggal
{
  uint8_t tanggal;
  uint8_t bulan;
  uint16_t tahun;
};
struct TanggalJawa
{
  uint8_t pasaran;
  uint8_t wuku;
  uint8_t tanggal;
  uint8_t bulan;
  uint16_t tahun;
};
struct JamDanMenit
{
  uint8_t jam;
  uint8_t menit;
};

uint8_t jumlahHariPerBulan[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
char namaBulanMasehi[][10] = {"Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober",
                            "November", "Desember"
                           };
char namaHariMasehi[][7] = {"Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jum'at", "Sabtu",};
char namaBulanHijriah[][14] = {"Muharram", "Safar", "Rabiul awal", "Rabiul akhir", "Jumadil awal", "Jumadil akhir", "Rajab",
                             "Sya'ban", "Ramadhan", "Syawal", "Dzulkaidah", "Dzulhijjah"
                            };
char namaBulanJawa[][12] = { "Sura", "Sapar", "Mulud", "Bakda Mulud", "Jumadilawal", "Jumadilakir", "Rejeb", "Ruwah", "Pasa",
                           "Sawal", "Sela", "Besar"
                         };
char namaWukuJawa[][13] = {"Shinta", "Landhep", "Wukit", "Kurantil", "Tala", "Gumbreg", "Warigalit", "Warigagung", "Julungwangi",
                         "Sungsang", "Galungan", "Kuningan", "Langkir", "Mandasia", "Julungpujut", "Pahang", "Kuruwelut", "Mrakeh", "Tambir",
                         "Madangkungan", "Maktal", "Wuye", "Manahil", "Prangbakat", "Bala", "Wungu", "Wayang", "Kulawu", "Dhukut", "Watugunung"
                        };
char namaHariPasaran[][7] = {"Legi", "Pahing", "Pon", "Wage", "Kliwon"};

TanggalDanWaktu tanggalMasehi;
Tanggal tanggalHijriah;
TanggalJawa tanggalJawa;
JamDanMenit waktuMagrib;

void setup() {
  Serial.begin(9600);
  Serial.println("Konversi Penanggalan Masehi ke Hijriah (metode Julian)");
  Serial.println("https://www.project.semesin.com/");
  Serial.println();
}

void loop() {
  waktuMagrib = {18, 12};

  uint32_t jumlahHari;
  double jumlahHariMatahari;

  tanggalMasehi.jam = 12;
  tanggalMasehi.menit = 0;
  tanggalMasehi.detik = 0;

  tanggalMasehi.tanggal = 8;
  tanggalMasehi.bulan = 9;
  tanggalMasehi.tahun = 18;

  tanggalMasehi.hari = hariDariTanggal(tanggalMasehi);
  masehiKeHijriah(tanggalMasehi, waktuMagrib, tambahKurangHijriah, tanggalHijriah, tanggalJawa);
  printKonversi(tanggalMasehi, tanggalHijriah, tanggalJawa);

  tanggalMasehi.tanggal = 9;
  tanggalMasehi.bulan = 9;
  tanggalMasehi.tahun = 18;

  tanggalMasehi.hari = hariDariTanggal(tanggalMasehi);
  masehiKeHijriah(tanggalMasehi, waktuMagrib, tambahKurangHijriah, tanggalHijriah, tanggalJawa);
  printKonversi(tanggalMasehi, tanggalHijriah, tanggalJawa);

  tanggalMasehi.tanggal = 10;
  tanggalMasehi.bulan = 9;
  tanggalMasehi.tahun = 18;

  tanggalMasehi.hari = hariDariTanggal(tanggalMasehi);
  masehiKeHijriah(tanggalMasehi, waktuMagrib, tambahKurangHijriah, tanggalHijriah, tanggalJawa);
  printKonversi(tanggalMasehi, tanggalHijriah, tanggalJawa);

  tanggalMasehi.tanggal = 11;
  tanggalMasehi.bulan = 9;
  tanggalMasehi.tahun = 18;

  tanggalMasehi.hari = hariDariTanggal(tanggalMasehi);
  masehiKeHijriah(tanggalMasehi, waktuMagrib, tambahKurangHijriah, tanggalHijriah, tanggalJawa);
  printKonversi(tanggalMasehi, tanggalHijriah, tanggalJawa);

  tanggalMasehi.tanggal = 12;
  tanggalMasehi.bulan = 9;
  tanggalMasehi.tahun = 18;

  tanggalMasehi.hari = hariDariTanggal(tanggalMasehi);
  masehiKeHijriah(tanggalMasehi, waktuMagrib, tambahKurangHijriah, tanggalHijriah, tanggalJawa);
  printKonversi(tanggalMasehi, tanggalHijriah, tanggalJawa);

  while (1);
}

double get_julian_date(Tanggal tanggal)
{
  if (tanggal.bulan <= 2)
  {
    tanggal.tahun -= 1;
    tanggal.bulan += 12;
  }

  double a = floor(tanggal.tahun / 100.0);
  double b = 2 - a + floor(a / 4.0);

  if (tanggal.tahun < 1583)
    b = 0;
  if (tanggal.tahun == 1582) {
    if (tanggal.bulan > 10)
      b = -10;
    if (tanggal.bulan == 10) {
      b = 0;
      if (tanggal.tanggal > 4)
        b = -10;
    }
  }

  return floor(365.25 * (tanggal.tahun + 4716)) + floor(30.6001 * (tanggal.bulan + 1)) + tanggal.tanggal + b - 1524.5;
}

double konversiTanggalHijriahKeJulianDate(uint16_t tahun, uint8_t bulan, uint8_t tanggal)
{
  return (epochHijriah + tanggal + ceil(29.5 * (bulan - 1)) + (354L * (tahun - 1)) + floor((3 + (11 * tahun)) / 30)) - 1;
}

void masehiKeHijriah(TanggalDanWaktu masehi, JamDanMenit waktuSholatMagrib, int8_t koreksiHijriah, Tanggal &hijriah, TanggalJawa &jawa)
{
  uint16_t sisaHari;
  double julianDate = get_julian_date({masehi.tanggal, masehi.bulan, masehi.tahun + 2000});

  uint16_t menitMagrib = waktuSholatMagrib.jam * 60  + waktuSholatMagrib.menit;
  uint16_t menitSekarang = masehi.jam * 60 + masehi.menit;

  if (menitSekarang >= menitMagrib)
  {
    julianDate++;//Pergantian hari hijrah pada magrib
  }

  julianDate = floor(julianDate) + 0.5;

  Tanggal tanggalHijriah;

  hijriah.tahun = floor(((30 * (julianDate - epochHijriah)) + 10646) / 10631);
  hijriah.bulan = min(12.0, ceil((julianDate - (29 + konversiTanggalHijriahKeJulianDate(hijriah.tahun, 1, 1))) / 29.5) + 1);
  hijriah.tanggal = (julianDate - konversiTanggalHijriahKeJulianDate(hijriah.tahun, hijriah.bulan, 1)) + 1;

  long julianLong = (long)julianDate;
  jawa.pasaran = ((julianLong + 1) % 5); //0 = legi
  jawa.wuku = (((julianLong + 65) % 210) / 7); //0 = Shinta
  jawa.tanggal = hijriah.tanggal;
  jawa.bulan = hijriah.bulan;
  jawa.tahun = hijriah.tahun + 512;

}
uint8_t hariDariTanggal(TanggalDanWaktu tanggalDanWaktu) {
  uint16_t jumlahHari = tanggalDanWaktu.tanggal;
  for (uint8_t i = 1; i < tanggalDanWaktu.bulan; ++i)
    jumlahHari += jumlahHariPerBulan[i-1];
  if (tanggalDanWaktu.bulan > 2 && tanggalDanWaktu.tahun % 4 == 0)
    ++jumlahHari;
  jumlahHari += (365 * tanggalDanWaktu.tahun) + ((tanggalDanWaktu.tahun + 3) / 4) - 1;

  return ((jumlahHari + 6) % 7) + 1; // 1 Januari 2000 hari sabtu = 7
}

void printKonversi(TanggalDanWaktu tanggalMasehi, Tanggal tanggalHijriah, TanggalJawa tanggalJawa)
{
  Serial.print("Masehi :\t");
  Serial.print(namaHariMasehi[tanggalMasehi.hari-1]);
  Serial.print(", ");
  Serial.print(tanggalMasehi.tanggal);
  Serial.print(" ");
  Serial.print(namaBulanMasehi[tanggalMasehi.bulan - 1]);
  Serial.print(" ");
  Serial.println(tanggalMasehi.tahun + 2000);

  Serial.print("Hijriah :\t");
  Serial.print(tanggalHijriah.tanggal);
  Serial.print(" ");
  Serial.print(namaBulanHijriah[tanggalHijriah.bulan - 1]);
  Serial.print(" ");
  Serial.println(tanggalHijriah.tahun);

  Serial.print("Pasaran Jawa :\t");
  Serial.print(tanggalJawa.tanggal);
  Serial.print(" ");
  Serial.print(namaHariPasaran[tanggalJawa.pasaran]);
  Serial.print(", ");
  Serial.print(namaWukuJawa[tanggalJawa.wuku]);
  Serial.print(", ");
  Serial.print(namaBulanJawa[tanggalJawa.bulan - 1]);
  Serial.print(" ");
  Serial.println(tanggalJawa.tahun);
  Serial.println();
}

 

Mengirim data detektor kebakaran dari arduino ke internet dengan antarmuka code igniter

Data sensor arduino

Sensor adalah instrumen atau komponen yang mampu mendeteksi perubahan kondisi objek dalam jangkauannya. Pengukuran besaran perubahan tersebut harus diubah dahulu menjadi bentuk digital sehingga dapat diproses oleh perangkat digital lainnya.

Data variabel ini bisa dimonitoring secara lokal melalui layar monitor maupun global melalui internet.

Hal yang mendukung keandalan sistem monitoring :

  1. realtime, yaitu data yang ditampilkan merupakan kondisi masa yang singkat, misalnya di perbarui setiap 1 detik.
  2. Data memiliki identitas seperti lokasi, waktu.
  3. Data yang diterima memiliki mekanisme penyaringan sehingga data yang ditampilkan terjamin.

Sensor detektor kebakaran

Terdapat beberapa Indikasi kebakaran yaitu :

  1. Api dideteksi dengan sensor flame
  2. Suhu dibaca dengan sensor suhu
  3. Asap dibaca dengan sensor asap (smoke detector)

Dalam hal pencegahan kebakaran, ketiga variabel ini terus menerus dimonitoring secara lokal.

Monitoring detektor kebakaran ini secara global juga diperlukan untuk memberikan informasi kepada pihak terkait.

Dalam contoh ini ketiga (modul) sensor ini digunakan bersama arduino dan akan dikirimkan ke mysql server.

Akses informasi sensor dengan codeIgniter

Informasi data saat ini dapat dengan mudah diakses dimanapun, namun dengan menggunakan codeIgniter diperoleh beberapa keuntungan yaitu :

  1. Pengembangan lebih mudah dengan baris program yang dapat disederhanakan.
  2. Aksesibilitas dapat dengan mudah dikontrol.
  3. Perlindungan terhadap server terutama server data lebih terjamin.

Skema Monitoring detektor kebakaran dengan codeIgniter

Komponen yang digunakan :

  1. Arduino Uno
  2. ESP8266
  3. Flame detector
  4. Sensor asap MQ7
  5. Sensor suhu dht11
  6. Relay
  7. Buzzer

Sketch / koding monitoring detektor kebakaran esp8266 – WebServer.

#include "WiFiEsp.h"
#include <SoftwareSerial.h>
#include "DHT.h"
 
char ssid[] = "****";        // Isi dengan nama profil Wifi
char pass[] = "********";            // password wifi
char server[] = "x.x.x.x";

long waktuMintaData = 1000; //minta data setiap 1000ms

#define pinFlame    A0
#define pinMQ       A1
#define pinDHT      A2
#define pinBuzzer   8
#define pinRelay    9

float setSuhu = 31.0;

String Respon = "";
long waktuMulai;
bool responDariServer = false;
bool prosesKirimDataKeServer = false;
 
WiFiEspClient client;
int status = WL_IDLE_STATUS;

SoftwareSerial wifi(2,3);
DHT dht(pinDHT, DHT11);
 
void setup()
{
  pinMode(pinFlame, INPUT_PULLUP);
  pinMode(pinMQ, INPUT_PULLUP);
  pinMode(pinDHT, INPUT_PULLUP);
  pinMode(pinBuzzer, OUTPUT);
  digitalWrite(pinRelay, HIGH);
  pinMode(pinRelay, OUTPUT);

  Serial.begin(115200);
 
  wifi.begin(115200);
  WiFi.init(&wifi);
 
  // check for the presence of the shield
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue
    while (true);
  }
 
  // attempt to connect to WiFi network
  while ( status != WL_CONNECTED) {
    Serial.print("Attempting to connect to WPA SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network
    status = WiFi.begin(ssid, pass);
  }
 
  // you're connected now, so print out the data
  Serial.println("You're connected to the network");
   
  printWifiStatus();
  dht.begin();
  waktuMulai = millis();
}
 
void loop()
{
  float suhu = dht.readTemperature();
//print status
  Serial.println();
  Serial.print("Api = ");
  Serial.println(digitalRead(pinFlame));
  Serial.print("Asap = ");
  Serial.println(digitalRead(pinMQ));
  Serial.print("suhu = ");
  Serial.println(suhu);
  
  if(!digitalRead(pinFlame) && !digitalRead(pinMQ) && (suhu < setSuhu))
  {
    digitalWrite(pinRelay, HIGH);
    digitalWrite(pinBuzzer, LOW);
    
  }
  if(digitalRead(pinFlame))
  {
    digitalWrite(pinRelay, LOW);
    digitalWrite(pinBuzzer, HIGH);
  }
  if(digitalRead(pinMQ))
  {
    digitalWrite(pinRelay, LOW);
    digitalWrite(pinBuzzer, HIGH);
  }
  if(suhu >= setSuhu)
  {
    digitalWrite(pinBuzzer, HIGH);
  }
  //kirim data
  if(waktuMintaData < millis() - waktuMulai)
  {
    waktuMulai = millis();
    prosesKirimDataKeServer = kirimDataKeServer();
  }

  while (client.available()) 
  {
    char c = client.read();
    Respon += c;
  }

  Serial.print("prosesKirimDataKeServer = ");
  Serial.println(prosesKirimDataKeServer);

  if (!client.connected() && prosesKirimDataKeServer) {
    Serial.println("Disconnecting from server...");
    client.stop();
    responDariServer = true;
    prosesKirimDataKeServer = false;
  }

  // penanganan data yang diterima dari server
  if(responDariServer)
  {
    responDariServer = false;
    Serial.println(Respon);
    int posisiData = Respon.indexOf("\r\n\r\n");
    String Data = Respon.substring(posisiData+4);
    Data.trim();
 
    String variabel;
    String nilai;
 
    Serial.println("Data dari server");
    posisiData = Data.indexOf('=');
    if(posisiData > 0)
    {
      variabel = Data.substring(0,posisiData);
      nilai = Data.substring(posisiData+1);
   
      //===========Penanganan respon disini
      if(variabel == "setSuhu")
      {
        setSuhu = nilai.toFloat();
      }
//      Serial.print(variabel);
//      Serial.print(" = ");
//      Serial.println(nilai);
    }
    Respon = "";
  }
}

bool kirimDataKeServer()
{
  Serial.println();
  Serial.println("Starting connection to server...");
  // if you get a connection, report back via serial
  if (client.connect(server, 80)) {
    Serial.println("Connected to server");
    // Make a HTTP request
 
    client.print("GET /index.php/databaseArduino/dariArduino");
    client.print("?Api=");
    client.print(digitalRead(pinFlame));
     
    client.print("&Asap=");
    client.print(digitalRead(pinMQ));

    client.print("&Suhu=");
    client.print(dht.readTemperature());
     
    client.println(" HTTP/1.1");
    client.print("Host: ");
    client.println(server);
    client.println("Connection: close");
    client.println();
    return true;
  }
  return false;
}
 
void printWifiStatus()
{
  // print the SSID of the network you're attached to
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());
 
  // print your WiFi shield's IP address
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);
 
  // print the received signal strength
  long rssi = WiFi.RSSI();
  Serial.print("Signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
 
  IPAddress gateway = WiFi.gatewayIP();
  Serial.print("gateway:");
  Serial.print(gateway);
  Serial.println(" ");
}

program codeIgniter untuk monitoring data sensor arduino:

databaseArduino.php

<?php
class databaseArduino extends CI_Controller {
  public function __construct() {
    parent::__construct();
  }

  public function dariBrowser() {
    $this->load->model('Model_databaseArduino');
    $this->Model_databaseArduino->salinDataDariBrowser();
    $data['dataSensor'] = $this->Model_databaseArduino->ambilDataDariArduino();
    $data['dataParameter'] = $this->Model_databaseArduino->ambilDataDariBrowser();

    $this->load->view("data_sensor", $data);
  }

    public function dariArduino() {
    $this->load->model('Model_databaseArduino');
    $this->Model_databaseArduino->salinDataDariArduino();
    $data['dataParameter'] = $this->Model_databaseArduino->ambilDataDariBrowser();

    $this->load->view("data_parameter", $data);
  }
}
?>

Model_databaseArduino.php

<?php
class Model_databaseArduino extends CI_Model {

  public $title;
  public $content;
  public $date;

  public function ambilDataDariArduino()
  {
    $this->load->database();
    $query = $this->db->query("SELECT * FROM (
    SELECT * FROM `data_sensor` ORDER BY `nomor` DESC LIMIT 10
    ) sub
    ORDER BY `nomor` ASC");
    $this->db->close();  
    return $query->result();
  }

  public function salinDataDariArduino()
  {
    date_default_timezone_set('Asia/Jakarta'); # add your city to set local time zone
    $now = date('Y-m-d H:i:s');

    $this->load->database();
    $this->db->set('waktu', $now);
    $this->db->set('api', $this->input->get('Api'));
    $this->db->set('asap', $this->input->get('Asap'));
    $this->db->set('suhu', $this->input->get('Suhu'));
    $this->db->insert('data_sensor');
    $this->db->close();
  }

  public function ambilDataDariBrowser()
  {
    $this->load->database();
    $query = $this->db->query("SELECT * FROM `data_parameter` ORDER BY `nomor` DESC LIMIT 1");
    $this->db->close();  
    return $query->row();
  }

  public function salinDataDariBrowser()
  {
    date_default_timezone_set('Asia/Jakarta'); # add your city to set local time zone
    $now = date('Y-m-d H:i:s');

    $this->load->database();
    $this->db->set('waktu', $now);
    $this->db->set('setSuhu', $this->input->post('setSuhu'));
    $this->db->insert('data_parameter');
    $this->db->close();  
  }
}
?>

data_sensor.php

<?php
  defined('BASEPATH') OR exit('No direct script access allowed');

  if(isset($dataParameter))
  {
    echo "setSuhu=";
    echo $dataParameter->setSuhu;
  }
?>

data_parameter.php

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Welcome to CodeIgniter</title>

  <style type="text/css">

  ::selection { background-color: #E13300; color: white; }
  ::-moz-selection { background-color: #E13300; color: white; }

  body {
    background-color: #fff;
    margin: 40px;
    font: 13px/20px normal Helvetica, Arial, sans-serif;
    color: #4F5155;
  }

  a {
    color: #003399;
    background-color: transparent;
    font-weight: normal;
  }

  h1 {
    color: #444;
    background-color: transparent;
    border-bottom: 1px solid #D0D0D0;
    font-size: 19px;
    font-weight: normal;
    margin: 0 0 14px 0;
    padding: 14px 15px 10px 15px;
  }

  code {
    font-family: Consolas, Monaco, Courier New, Courier, monospace;
    font-size: 12px;
    background-color: #f9f9f9;
    border: 1px solid #D0D0D0;
    color: #002166;
    display: block;
    margin: 14px 0 14px 0;
    padding: 12px 10px 12px 10px;
  }

  #body {
    margin: 0 15px 0 15px;
  }

  p.footer {
    text-align: right;
    font-size: 11px;
    border-top: 1px solid #D0D0D0;
    line-height: 32px;
    padding: 0 10px 0 10px;
    margin: 20px 0 0 0;
  }

  #container {
    margin: 10px;
    border: 1px solid #D0D0D0;
    box-shadow: 0 0 8px #D0D0D0;
  }
  </style>
</head>
<body>



<!--
<form action="/form/data_submitted" method="get">
User Name: <input type="text" name="u_name" placeholder="Please Enter User Name" class="input_box">
<br>
User email: <input type="text" name="u_email" placeholder="Please Enter Email Address" class="input_box">
<input type="submit" value="Submit" class="submit">
</form>
-->

<?php

// echo $this->input->post('setSuhu');

echo form_open('databaseArduino/dariBrowser');
if(isset($dataParameter->setSuhu))
{
  echo form_input('setSuhu', $dataParameter->setSuhu);
}
else
{
  echo form_input('setSuhu', '30.0');
}
echo form_submit('suhuSubmit', 'Set Temperatur');
echo form_close();

echo "<br>";
echo "<strong>Data pembacaan sensor</strong>";
echo "<br>";
echo "<table>";

  echo "<tr>";
  echo "<td width='50'>Nomor</td>";
  echo "<td width='200'>Waktu</td>";
  echo "<td width='50'>Api</td>";
  echo "<td width='50'>Asap</td>";
  echo "<td width='50'>Suhu</td>";
  echo "</tr>";

  foreach ($dataSensor as $row)
{
  echo "<tr>";
  echo "<td>".$row->nomor."</td>";
  echo "<td>".$row->waktu."</td>";
  echo "<td>".$row->api."</td>";
  echo "<td>".$row->asap."</td>";
  echo "<td>".$row->suhu."</td>";
  echo "</tr>";
}
echo "</table>";
?>
</body>
</html>

file server : htdocs.zip

Trik pemograman arduino

Clock 1Hz

Clock 1Hz atau penanda detik tanpa rtc

long millisDetik;

void setup() {
  millisDetik = millis();
}

void loop() {
  if(millisDetik != millis() / 1000L)
  {
    millisDetik = millis() / 1000L;

    // Kode

  }
}

Menghemat RAM menggunakan flash string

Arduino memiliki memory ram yang kecil, salah satu langkah penghematan adalah dengan memasukkan konstanta text / string ke dalam flash seperti kode berikut:

  Serial.println(F("https://www.project.semesin.com"));

atau jika menggunakan alamat

const PROGMEM char text[] = "https://www.project.semesin.com";
void setup() {
  Serial.begin(9600);
  Serial.println((const __FlashStringHelper *)text);
}

Definisi nilai output

Nilai parameter fungsi digitalWrite relay (NC/NO) dan transistor/mosfet sering kali terbalik, untuk mempermudah pekerjaan nilainya LOW-nya lebih baik didefenisikan.

#define pinRelay   8
#define relayLOW   HIGH //relay dengan nilai kebalikan

void setup() {
  pinMode(pinRelay, OUTPUT);
}

void loop() {
  digitalWrite(pinRelay, relayLOW);//mati
  delay(1000);
  digitalWrite(pinRelay, !relayLOW);//hidup
  delay(1000);
}

Cast Float to Byte Array

Dalam komunikasi data berbentuk float, lebih baik mengirim data berupa byte array dari pada nilai string dari float

Serial.begin(9600);
float nilaiFloat = 0.15625;
byte *arrayByte;

arrayByte = (byte*)&nilaiFloat;
Serial.print(nilaiFloat);
Serial.print(" = ");
Serial.print(arrayByte[3],HEX);
Serial.print(' ');
Serial.print(arrayByte[2],HEX);
Serial.print(' ');
Serial.print(arrayByte[1],HEX);
Serial.print(' ');
Serial.println(arrayByte[0],HEX);

Aksi tombol repeat (tekan lama dan berulang)

Penggunaan tombol untuk merubah nilai settingan naik dan turun dengan fitur ‘tekan sekali-berubah sekali’ dan ‘tekan lama-berubah berulang’  (repeating button) bisa menggunakan metode berikut:

#define pinTombolUp         2
pinMode(pinTombolUp, INPUT_PULLUP);
if (!digitalRead(pinTombolUp))
{
  delay(50);//debounce
  bool tombolStart = true;
  while (!digitalRead(pinTombolUp))
  {


    // Aksi tombol disini


    if (tombolStart)
    {
      //tekan pertama delay 10*50 = 500ms
      for (byte d = 0; d < 10 && !digitalRead(pinTombolUp); d++)
      {
        delay(50);
      }
      tombolStart = false;
    }
    else
    {
      delay(100);//perulangan aksi dengan delay 100ms
    }
  }
}

Konstanta memory

Konstanta memory mikrokontroller pada arduino

  Serial.println(FLASHEND);//ukuran flash
  Serial.println(RAMEND);//ukuran ram
  Serial.println(XRAMEND);//ukuran ram tambahan
  Serial.println(E2END);//ukuran eeprom

Offset anggota struct

Ketika menggunakan memori EEPROM untuk menyimpan struktur data (struct), maka untuk merujuk data tertentu dapat menggunakan fungsi ‘offsetof’ (jika alamat struct di EEPROM = 0)

struct Setting
{
  byte tokenSetting;
  byte jumlahDatabase;
  byte password[4];
};

Serial.println(offsetof(Setting, tokenSetting));
Serial.println(offsetof(Setting, jumlahDatabase));

Karakter Simbol untuk LCD Matrix

LCD karakter menyimpan font di dalam memory internalnya dengan penomoran 0 hingga 255. Sebagi contoh simbol derajat ‘°’ bernilai 223 (hex 0xDF) maka untuk mencetak karakter diluar char (0x80 – 0xFF) nilai tersebut di cast dulu menjadi char.

lcd.print((char)223);

Set baudrate esp8266

Penggunaan SoftwareSerial menjadi alternatif untuk komunikasi serial, namun menjadi tidak stabil pada baudrate yang tinggi. Seperti pada esp8266 yang lalu lintas komunikasi serial-nya besar akan sangat berpengaruh akibat ketidakstabilan ini. Untuk itu baudrate esp8288 mau tidak mau harus diturunkan menjadi 9600, caranya adalah dengan memberikan perintah AT command kepada esp8266 seperti berikut :

AT+UART_DEF=9600,8,1,0,0

Input keyboard untuk LCD 16×2 menggunakan Arduino

Keyboard dan mouse merupakan perangkat PC (personal computer) yang berfungsi sebagai interface atau perantara antara pengguna dan pc. Perangkat ini umumnya terhubung menggunakan interface usb, akan tetapi umumnya keyboard dan mouse juga memiliki interface PS2 dan AT Bus.

PS2

PS2 atau Personal System/2 adalah salah satu protokol komunikasi antara komputer dan perangkat lain yang dikembangkan pada tahun 1987. Untuk keyboard dan mouse standar soket PS2-nya berbentuk seperti ini :

AT Bus

AT atau advanced technology bus dikembangkan pada tahun 1984. Standar interface AT Bus untuk keyboad berbentuk seperti ini:

USB Keyboard / mouse

Saat sekarang keyboad dan mouse dengan interface PS2 dan AT bus sudah tidak ditemukan yang digantikan dengan soket dengan interface USB. Namun kebanyakan keyboard dan mouse masih mendukung interface PS2 dengan kombinasi sebagai berikut :

Skema penggunaan keyboard dan mouse sebagai input

contoh sketch/program arduino menggunakan keyboard

#include <PS2_Semesin.h>
#include <LiquidCrystal_I2C.h>
#include <PS2Code.h>

#define keyboardDATAPIN   4
#define keyboardClockPIN  3

#define MAX_COL 16
#define MAX_ROW  2
int8_t cols = 0;
int8_t rows = 0;

LiquidCrystal_I2C lcd(0x3F, MAX_COL, MAX_ROW);
PS2 keyboard;

void setup()
{
  Serial.begin(9600);
  Serial.println("Keyboard arduino dengan tampilan I2C LCD");
  Serial.println("https://www.project.semesin.com/");
  Serial.println();

  Wire.begin();
  Wire.beginTransmission(0x3F);
  if (Wire.endTransmission())
  {
    lcd = LiquidCrystal_I2C(0x27, 16, 2);
  }
  lcd.begin();
  lcd.backlight();

  keyboard.begin( keyboardDATAPIN, keyboardClockPIN );
  keyboard.setNoBreak(1);
  keyboard.setNoRepeat( 1 );
}


void loop()
{
  if ( keyboard.available() )
  {
    char keyboardData = keyboard.read();
    if(keyboardData >= ' ' && keyboardData <= '~')
    {
      lcd.print(keyboardData);
      cols++;
      if ( cols >= MAX_COL )
      {
        cols = 0;
        rows++;
        if ( rows >= MAX_ROW )
        {
          rows = 0;
        }
      }
      lcd.setCursor( cols, rows );
      Serial.print(keyboardData);
    }
    else
    {
      Serial.println();
      Serial.print("Special char = ");
      Serial.println(keyboardData,HEX);
      switch ( keyboardData )
      {
        case PS2_KEY_ENTER:
        case PS2_KEY_KP_ENTER:
          cols = 0;
          rows++;
          if ( rows >= MAX_ROW )
            rows = 0;
          break;
        case PS2_KEY_PGDN:
          rows = MAX_ROW - 1;
          break;
        case PS2_KEY_PGUP:
          rows = 0;
          break;
        case PS2_KEY_L_ARROW:
          cols--;
          if ( cols < 0 )
          {
            cols = MAX_COL - 1;
            rows--;
            if ( rows < 0 )
              rows = MAX_ROW - 1;
          }
          break;
        case PS2_KEY_R_ARROW:
          cols++;
          if ( cols >= MAX_COL )
          {
            cols = 0;
            rows++;
            if ( rows >= MAX_ROW )
              rows = 0;
          }
          break;
        case PS2_KEY_UP_ARROW:
          rows--;
          if ( rows < 0 )
            rows = 0;
          break;
        case PS2_KEY_DN_ARROW:
          rows++;
          if ( rows >= MAX_ROW )
            rows = MAX_ROW - 1;
          break;
        case PS2_KEY_BS:
          cols--;
          if ( cols < 0 )
          {
            cols = MAX_COL - 1;
            rows--;
            if ( rows < 0 )
              rows = MAX_ROW - 1;
          }
          lcd.setCursor( cols, rows );
          lcd.write( ' ' );
          break;
        case PS2_KEY_HOME:
          cols = 0;
          rows = 0;
          break;
        case PS2_KEY_END:
          cols = MAX_COL - 1;
          rows = MAX_ROW - 1;
          break;
      }
      lcd.setCursor( cols, rows );
    }
  }
}

library LiquidCrystal-I2C.zip, PS2_Semesin.zip

Pengontrolan tegangan menggunakan PWM pada arduino

Pengontrolan tegangan berfungsi untuk menjaga kestabilan tegangan keluaran ke beban sehingga beban bisa bekerja semestinya. Contohnya jika beban lampu yang intensitas cahayanya bergantung kepada tegangan, maka dengan tegangan yang stabil akan mengeluarkan cahaya yang stabil pula.

Hal yang menyebabkan ketidakstabilan tegangan diantaranya :

  1. Perubahan beban (penambahan dan pengurangan)
  2. Perubahan nilai masukan (input)
  3. Faktor luar seperti interferensi.

Faktor yang mempengaruhi keandalan pengontrolan tegangan :

  1. Kecepatan respon dari pengontrol tegangan terhadap perubahan yang terjadi, semakin cepat semakin baik.
  2. Sistem koreksi yang digunakan, seperti PID, fuzzy
  3. Karakteristik sensor dan beban yang digunakan

Pengontrolan tegangan dengan arduino

Pengendalian tegangan harus memiliki masukan sensor tegangan dan aktuator kontrol tegangan. Pada aplikasi arduino pembacaan tegangan menggunakan ADC dan aktuasi kontrol tegangan menggunakan PWM.

Dalam contoh ini, sistim koreksi tegangan menggunakan metode proporsional, yaitu semakin besar selisih tegangan dan input maka akan semakin besar pula penambahan nilai PWM.

skema sistem kontrol tegangan mengguakan arduino:

Sketch / koding Penngendalian Tegangan dengan PWM:

#define pinSensorTegangan     0
#define pinOutputPWM          9
#define setTegangan           2.5//volt
#define faktorProporsional    0.1

float keluaran;

void setup() {
  pinMode(pinOutputPWM, OUTPUT);
  
  Serial.begin(9600);
  Serial.println("Sumber tegangan stabil (automatic voltage regulator) menggunakan kontrol proporsional");
  Serial.println("https://www.project.semesin.com/");
  Serial.println();

  keluaran = setTegangan;
}

void loop() {
  uint16_t adc = analogRead(pinSensorTegangan);
  float tegangan = map(adc, 0, 1024, 0, 500)/100.0;
  float selisih = setTegangan - tegangan;
  float proporsional = faktorProporsional * selisih;
  
  keluaran += proporsional;
  keluaran = constrain(keluaran, 0, 5);
  byte keluaranPWM = map(keluaran*100, 0, 5*100, 0, 255);
  analogWrite(pinOutputPWM, keluaranPWM);

  //Plot serial, hapus untuk menambah kecepatan
  Serial.print(tegangan);
  Serial.print(", ");
  Serial.print(keluaranPWM);
  Serial.println();
  delay(10);
}

Hasil Plot sinyal PWM (merah) dan tegangan keluaran (biru) terhadap perubahan beban.

Tombol power otomatis arduino

tombol pengunci (self-latching)

Rangkaian kunci tombol otomatis bisa diterapkan dengan bermacam komponen utama, yang umum digunakan adalah dengan menggunakan relay, transistor, mosfet dan SCR.

Penggrendel tombol dengan relay

Dalam sistem listrik rangkaian ini dikenal sebagai DOL (direct on line). Jika tombol ‘nyala’ ditekan maka tegangan akan masuk ke relay melalui tombol ‘padam’, kemudian relay akan aktif dan NO dalam keadaan kontak dan mengunci tegangan tetap masuk ke coil relai dan mengunci posisi relay aktif.

Jika tombol ‘padam’ ditekan dalam keadaan rangkaian aktif, maka suplai  kunci akan terputus dan relay kembali nonaktif.

Pengancing tombol dengan SCR

Masih menggunakan interface yang sama tetapi menggunakan SCR/thyristor 2P4M sebagai penguncinya. Jika tombol ‘nyala ditekan, akan mengalirkan arus ‘gate trigger’ melalui resistor 330 ohmm dan menjadikan SCR dalam keadaan on-state yang selalu aktif walaupun tombol ‘nyala dilepaskan’.

Jika tombol ‘padam’ ditekan, maka suplay arus ke SCR melalui beban outpun akan hilang dan otomatis akan mematikan operasi SCR.

Tombol Vcc otomatis pada arduino menggunakan MOSFET

Tombol power dan arduino bisa dikombinasikan (saling melengkapi) antara pemberi sumber, dan pengunci sumber.

Cara kerjanya adalah pada saat tombol power ditekan, gate pada mosfet akan diberi tegangan 5V, dan menjadikan mosfet menghantarkan GND arduino ke ground sumber.

Sesaat setelah menerima power dari sumber/baterai melalui mosfet, arduino akan menjalankan perintah untuk mengunci kondisi mosfet tetap aktif.

Untuk menonaktifkan mosfetm, cukup dengan menjadikan pin Gate bernilai LOW.

contoh sketch Sumber baterai dengan tombol otomais:

#define pinPower A0
#define pinLED   13

void setup() {
  Serial.begin(9600);
  Serial.println("Power arduino otomatis menggunakan MOSFET");
  Serial.println("https://www.project.semesin.com/");
  Serial.println();
  Serial.println("Jangan beri power supply / catu daya USB");
  Serial.println("Akan dimatikan dalam 10 detik");
  Serial.println();
  
  pinMode(pinPower, OUTPUT);
  pinMode(pinLED, OUTPUT);
  digitalWrite(pinPower, HIGH);
  digitalWrite(pinLED, LOW);

  delay(7000);
  digitalWrite(pinLED, HIGH);//Tanda arduino akan dimatikan 3 detik lagi
  delay(3000);

  //Fungsi mematikan power Vcc
  digitalWrite(pinPower, LOW);
  
}

void loop() {

}

Tombol power otomatis pada arduino menggunakan SCR (hemat energi)

Prinsip kerja power otomatis pada arduino ini adalah ketika tombol ‘power’ ditekan, maka gate dari scr akan diberi trigger untuk mengaktifkan SCR, setelah tombol dilepaskan SCR akan tetap aktif tanpa sumber arus lain. Tidak seperti menggunakan transistor atau mosfet dimana arus dan tegangan harus disuplai dari arduino.

Untuk mematikan power ini, cukup dengan meng-ground-kan / logika LOW pada pin gate.

contoh sketch kunci tombol power otomatis:

#define pinPower A0
#define pinLED   13

void setup() {
  Serial.begin(9600);
  Serial.println("Power arduino otomatis ");
  Serial.println("https://www.project.semesin.com/");
  Serial.println();
  Serial.println("Jangan beri power supply / catu daya USB");
  Serial.println("Akan dimatikan dalam 10 detik");
  Serial.println();
  
  pinMode(pinLED, OUTPUT);
  digitalWrite(pinLED, LOW);

  delay(7000);
  digitalWrite(pinLED, HIGH);//Tanda arduino akan dimatikan 3 detik lagi
  delay(3000);

  //Fungsi mematikan power Vcc
  digitalWrite(pinPower, LOW);
  pinMode(pinPower, OUTPUT);
  
}

void loop() {

}

Kalkulator online persamaan garis lurus antara dua titik

Menghitung persamaan garis linear dengan rumus :

masukkan nilai (x1, y1), (x2, y2) dan nilai x jika ingin mencari nilai y pada titik x tersebut pada kotak berikut :

[WPcalc id=1]

 

Persamaan garis lurus adalah garis dalam bentuk matematis berpangkat satu. Persamaan umumnya adalah :

y = mx + c

dimana m = gradien dan c = konstanta.

Gradien adalah kemiringan dari suatu garis dan konstanta adalah ketinggian garis pada x = 0.

Komposisi larutan HCl dan H2O2 (praktek pelarut PCB dengan vixal dan vanish)

Asam Klorida (HCL)

Hidrogen klorida adalah asam kuat yang terdisosiasi dengan air dan sangat korosif, Jika terioniasasi akan melepaskan H+ dan ion Cl-. Massa relatif (Mr) HCL adalah 36,46094 gram/mol.

Tabel konsentrasi HCL beserta molaritasnya

Nomor Konsentrasi (kg HCl/kg) Massa jenis (gram/liter) Molaritas (mol/liter)
1 10% 1048 2.87
2 20% 1098 6.02
3 30% 1149 9.45
4 32% 1159 10.17
5 34% 1169 10.90
6 36% 1179 11.64
7 38% 1189 12.39

Hidrogen Peroksida (H2O2)

Adalah cairan oksidator kuat yang berbau khas keasaman dan mudah larut dalam air. Massa relatif H2O2 =  34,01468 gram/mol dan massa jenis H2O2 = 1.450 gram/liter

Tembaga (Cu)

Massa relatif tembaga (Cu) = 63,546 gram/mol, dan massa jenis tembaga 8.900 gram/liter.

Pada PCB elektronik ketebalan lapisan tembaga berkisar 0,034 mm.

Menghitung campuran HCL + H2O2 untuk Etching PCB

Persamaan kesetimbangan kimia campuran etching H2O2 + HCL:

Cu + 2 HCl + H2O2 = CuCl2 + 2 H2O

Hasil dari reaksi ini adalah CuCl2 (Copper(II) chloride) yang merupakan asam lemah.

Massa relatif dari masing-masing senyawa adalah :

  • Cu = 63,546
  • HCl = 36.46094
  • H2O2 = 34.01468

Vixal + Vanish sebagai pelarut PCB

Konsentrasi HCl pada vixal adalah 17 %v/v

Konsentrasi H2Opada vanish adalah 5 %v/v

Untuk mendapatkan volume setiap molnya dari tabel molaritas senyawa digunakan rumus :

Jika konsentrasi tidak ada dalam tabel, volume untuk setiap mol digunakan rumus:

dengan formula volume per mol dari persen tersebut diperoleh:

Volume per mol HCl 17% vixal = 36,46094 / (0.17 x 1083) = 0,198 liter.

Volume per mol H2O2 Vanish = 34,01468 / (0.05 x 1450) = 0,469 liter.

Volume per mol tembaga murni = 63,546 / (1 x 8900) = 0,00714 liter atau 63,546 gram.

cat: massa jenis digunakan pendekatan umum.

Jadi dengan campuran :

(2 x 0.198 liter Vixal 17%) + 0.469 liter Vanish 5% akan melarutkan 0,00714 liter tembaga.

Jika ketebalan tembaga pada PCB 0.034 mm maka luas yang akan digerus oleh larutan tersebut adalah :

Luas tembaga = 0,00714 liter / 0.00034 dm = 21 dm² (2100 cm²). cukup untuk 10 lembar PCB kosong.

Namun dalam praktek sering ditemui kendala berikut:

  1. Konsentrasi bahan aktif yang tidak sesuai.
  2. Jenis pelarut bukan air.
  3. Tembaga PCB bukan tembaga murni.
  4. Kontaminan pada pelarut atau di permukaan PCB.

Komunikasi data Bluetooth dengan App Inventor

Bluetooth adalah Sistem jaringan lokal tanpa kabel. Bluetooth telah digunakan secara luas pada perangkat android dan telepon pintar lainnya.

Menghubungkan ke bluetooth server

Untuk mempermudah jalan program app inventor, proses menghubungkan bluetooth ke server dimasukkan kedalam sebuah prosedur.

Prosedur ‘Hubungkan_Bluetooth’ ini bisa dipanggil dengan variabel ‘alamatBluetooth’ yang berisi alamat unik bluetooth. Sebelum dihubungkan prosedur akan mengecek status ‘bluetooth.available’, jika aktif, dilanjutkan dengan pengecekan status ‘bluetooth.IsConnected’, dan mematikan bluetooth jika sedang terkoneksi.

Selanjutnya bluetooth dihubungkan dengan fungsi ‘bluetoothClient.Connect’ dengan variabel alamat yang diberikan.

Pemanggilan bluetooth saat Inisialisasi

Apabila sistem menghubungkan bluetooth dibuat otomatis, maka bluetooth akan dihubungkan keserver dengan alamat yang tersimpan sebelumnya pada database ‘TinyDB’. Pemanfaatan database ini adalah untuk otomatisasi data saat aplikasi app dibuka tanpa perlu disetting ulang, cukup menggunakan settingan terakhir.

Koneksi bluetooth dengan alamat langsung

Jika alamat bluetooth server sudah diketahui, app bisa langsung mengkondisikan tombol sebagai pemanggil prosedur ‘hubungkan_bluetooth’ dengan alamat yang ada pada ‘textbox’.

Menghubungkan bluetooth dengan alamat pada daftar bluetooth

Daftar perangkat bluetooth yang terhubung ke android bisa diambil sebagai referensi alamat. Sebelum digunakan, elemen ‘listPicker’ digunakan sebagai penampung daftar bluetooth yang tersedia.

Apabila item yang ada dalam daftar sudah dpilih, maka akan dipanggil prosedur ‘hubungkan_Bluetooth’ dengan variabel alamat terpilih.

Putuskan hubungan bluetooth

Jika ingin memutuskan bluetooth dari server, cukup menggunakan fungsi ‘BluetoothClient.Disconect’

Cek koneksi bluetooth

Status konksitas bluetooth bisa diketahui dengan fungsi ‘BluetoothClient.IsConnected’

Mengirim data string melalui bluetooth

Untuk mengirim data melalui bluetooth, terlebih dahulu dipastikan koneksi bluetooth dengan fungsi’bluetoothClient.IsConnected’. Dan apabila terhubung, proses pengiriman string dilakukan dengan memanggil fungsi ‘bluetoothClient.SendText’ dengan variabel ‘text’ yang diisi contohnya text dalam ‘textBox’.

Menerima data dari bluetooth

Jika ada data yang dikirim melalui bluetooth, data tersebut bisa dideteksi dengan fungsi ‘bluetoothClient.SendText’ seperti pada block berikut :

Dengan bantuan ‘clock.Timer’, setiap interval waktu dilakukan pengecekan apabila ada data yang diterima lewat bluetooth, kemudian mengambilnya dengan fungsi ‘bluetooth.ReceiveText’.

Namun fungsi diatas memiliki beberapa kelemahan yaitu:

  1. Data yang diterima tidak diparsing, sehingga data apapun yang diterima adalah merupakan deretan String.
  2. Jika dalam satu interval waktu diterima beberapa data, maka data tersebut dianggap suatu kesatuan.
  3. Jika proses pengiriman data berlangsung saat interupsi timer terjadi, maka data tersebut akan terpotong (data tidak utuh).

Untuk menutui itu digunakan algoritma app inventor yang lebih rumit namun mampu berjalan pada jumlah data besar.

Proses pengambilan data dari buffer android dilakukan secara parsial sehingga terhindar dari pemotongan, dengan metode ini dimungkinkan menerima beberapa nilai variabel sekaligus (dalam lalu lintas kecepatan tinggi).

Dalam contoh ini digunakan format ‘namaVariabel’=’nilai’, dalam contoh sketch arduino :

void setup() {
  Serial.begin(9600);
  Serial.println("Koneksi Beluetooth pada Android dengan App Inventor");
  Serial.println("https://www.project.semesin.com/");
  Serial.println();
}

byte variabel1 = 0;
uint16_t variabel2 = 1000L;

void loop() {
  while (Serial.available())
  {
    Serial.write(Serial.read());
  }
  delay(1000);
  Serial.println("var1=" + String(variabel1));
  Serial.println("var2=" + String(variabel2));
  variabel1 += 1;
  variabel2 += 10;
}

Mendeteksi kegagalan bluetooth

Fungsi ‘bluetoothClient.BluetoothError’ akan mendeteksi jika terjadi kegagalan bluetooth seperti kesalahan dalam proses menghubungkan, mengirim atau menerima data. Blok diatas akan mengirimkan pesan kepada pengguna melalui label ‘pesan’.

Design cara komunikasi bluetooth app inventor:

Block lengkap sistem komunikasi bluetooth android:

apk menghubungkan bluetooh dengan app inventor:

Multi screen dengan App Inventor

Aplikasi App Inventor dibuat diatas screen. Screen adalah halaman tempat meletakkan interface, layout dan komponen lainnya.

Tutorial pindah antar screen ini mencakup metode pindah screen yang biasa digunakan.

Layout scroll

Apabila ruang (space) satu screen tidak mencukupi, maka bisa memanfaatkan layout dalam mode scroll baik vertikal maupun horizontal.

Penggunaan layout scroll sangat efektif karena ruang yang disediakan lebih besar.

Banyak Screen App Inventor

Pada aplikasi App Inventor, kita bisa membangun sebuah app dengan lebih dari satu screen. Dengan banyak screen kita bisa mengelompokkan komponen-komponen agar lebih terorganisir.

Screen bertindak sebagai alas tempat komponen. Apabila sebuah screen saat ini (screen induk) membuka screen lainnya (screen anak), maka screen anak akan menimpa screen induk.

Perpindahan antar Screen dengan nilai awal dan nilai balik

Sebuah screen bisa mengirim nilai awal (start value) ke screen anaknya. juga sebaliknya screen anak bisa mengirim hasil (result) ke screen induknya.

Dari contoh diatas :

  1. Button1 adalah tombol untuk membuka ‘screen2’, dengan kontrol ‘open another screen’ dan variabel ‘screenName’ diisi dengan ‘Screen2’, maka ketika button1 ditekan ‘screen2’ akan dibuka diatas ‘screen1’.
  2. Button2 adalah tombol untuk membuka screen3, pemanfaatan fungsi ‘open another screen with start value’ dengan variabel ‘screenName’ dan ‘startValue’ diisi dengan input interface ‘textBox1.text’, maka screen induk akan mengirimkan nilai awal dalam ‘textbox1.text’ ke ‘screen3’.
  3. Fungsi ‘other screen closed’ akan dijalankan saat terdeteksi sebuah screen anak ditutup dan dan kembali ke screen ini (induk). variabel ‘result’ akan memiliki nilai yang dikirim oleh screen anak.

Perpindahan antar screen anak

Pindah atar screen akan bisa dalam dua metode:

  1. Pindah ke screen anak lainnya sebagai screen cucu (tanpa menutup screen sekarang)
  2. Pindah ke screen anak lainnya sebagai ganti anak (dengan menutup screen sekarang).

Pada blok diatas adalah metode pindah antar screen anak sebagai ganti screen sekarang dengan tetap menginduk ke ‘screen1’.

Penggunaan fungsi ‘if’ dengan variabel ‘true’ hanya sebuah trik untuk menempatkan prosedur/fungsi lain dibawah fungsi ‘open another screen’.

Nilai awal dari screen induk

Saat ‘screen3’ dibuka, app akan menjalankan terlebih dahulu fungsi ‘Initialize’. Fungsi ini ditugaskan mengambil nilai awal yang dikirimkan oleh screen induk dalam variabel ‘get start value’ dan menyimpannya kedalam interface ‘textBox1/text’

Mengirim nilai balik ke screen induk

Ada beberapa cara untuk kembali ke screen induk, diantaranya adalah fungsi ‘BackPressed’ atau penggunaan tombol.

Fungsi ‘close screen with value’ berfungsi menutup screen dengan mengirimkan nilai balik dalam variabel ‘result’.

design app inventor banyak screen:

Screenshoot tukar screen app inventor:

apk multi screen app inventor: MultiScreen.apk

Fungsi Waktu App Inventor

Data waktu pada App Inventor berupa tanggal dan waktu yang dinyatakan dalam orde milidetik dari 1 Januari 1970. Data waktu ini bisa digunakan untuk beberapa keperluan diantaranya hitung mundur dan durasi.

Data waktu Sekarang (now)

Interface / modul clock memiliki fungsi ‘now’ yang menyediakan data waktu serta fungsi format datetime guna mendapatkan text waktu yang diinginkan.

Block diatas akan mengganti text pada label1 dengan text waktu sekarang dalam format MM/dd/yyyy hh:mm:ss a. Penggantian dilakukan setiap interval dari clock1 (default 1 detik).

Ambil waktu sistem android

Untuk mengambil data waktu ketika tombol di tekan dengan format tertentu bisa menggunakan block berikut:

Ketika button1 ditekan, text pada label2 akan diganti dengan waktu sekarang dalam format hh:mm.

Edit data waktu dengan TimePicker

TimePicker berfungsi sebagai dialog pengedit waktu dengan nilai default waktu sekarang (now)

Ketika tombol [selesai] pada dialog timePicker ditekan, text pada tombol [TimePicker] akan diganti dengan hasil pengeditan jam dan menit.

Angka dua digit (time pattern) app inventor

Agar tampilan waktu jam:menit terlihat rapi dalam format waktu hh:mm, App inventor 2 belum mengakomodir text pattern (kecuali sensor clock), untuk itu bisa dikombinasikan dengan perhitungan matematis yakni apabila angka kecil dari 10 maka akan ditambahkan ‘0’ diawalnya.

Hitung mundur (Countdown)

Hitung mundur adalah metode penghitungan waktu tunda. Hitung mundur bisa dimanfaatkan untuk berbagai keperluan seperti aktifasi sebuah interface/screen dan sebagainya.

Hitung mundur dengan app inventor diaplikasikan dalam orde mili detik, agar lebih mudah waktu hitung mundur dikonversi menjadi detik dengan fungsi DurationToSeconds.

Untuk perancangan hitung mundur dengan app inventor dibutuhkan variabel waktu mulai dan durasi. Dengan bantuan ‘Clock’ penghitungan dideteksi setiap detik.

Hitung munder dengan metode time epoch ini bisa disetting dalam orde tahunan atau sesuai kebutuhan.

Menghitung durasi dengan app inventor

Durasi / selisih waktu adalah waktu yang dibutuhkan antara dua evenr/trigger, hasilnya diperoleh dengan formula waktu event2 dikurangi waktu event1.

Penghitungan durasi diukur dalam orde mili detik.

Block lengkap fungsi waktu menggunakan app inventor:

Design fungsi waktu dengan app inventor:

Tampilan app fungsi waktu memakai app inventor:

File apk fungsi waktu pada app inventor: FungsiWaktu.apk

Hitung mundur Arduino (volatile countdown)

Hitung mundur volatile (tidak menguap) adalah metode hitung mundur digital yang terus berlangsung walaupun catu daya (power supply) dimatikan. Jadi hitung mundur berlanjut sesuai waktu normal ketika catu daya aktif kembali.

Countdown arduino ini berfungsi sebagai penghitung mundur hingga jangka tahunan karena menggunakan metode epoch time. Waktu acuan yang digunakan adalah waktu RTC (DS1307/DS3231).

Waktu epoch adalah jumlah detik hingga saat ini dari tangga 1 Januari 1970.

Hitung mundur menggunakan Arduino dan RTC memanfaatkan EEPROM untuk menyimpan data-data berikut :

  1. Aktif
  2. Waktu mulai hitung mundur (epoch waktu)
  3. Waktu hitung mundur (dalam detik)

Karena perangkat ini memiliki fitur volatile maka dibutuhkan mekanisme pengujian/pengecekan data waktu yang disimpan yaitu :

  1. Apakah ada hitung mundur yang aktif
  2. Jika aktif apakah epoch waktu sekarang lebih besar dari epoch waktu hitung mundur mulai
  3. jika aktif apakan epoch waktu sekarang kecil dari jumlah epoch waktu mulai ditambah jumlah detik hitung mundur.

jika syarat ini di penuhi maka hitung mundur dengan arduino dilanjutkan.

berikut skema Arduino countdown (berlaku untuk DS1307/DS3231):

koding / sketch arduino hitung mundur:

#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#include <EEPROM.h>

#define alamatEEPROMCountDownAktif  0
#define alamatEEPROMCountDownMulai  1
#define alamatEEPROMCountDownDetik  5

byte countDownAktif;
uint32_t countDownMulai;
uint32_t countDownDetik;
uint32_t RTCEpoch;
byte detikSebelumnya = 60;

void setup() {
  Serial.begin(9600);
  Serial.println("Hitung mundur Arduino (volatile countdown)");
  Serial.println("https://www.project.semesin.com/");
  Serial.println("Entri waktu hitung mundur (dalam detik) :");
  Serial.println();

  countDownAktif = EEPROM.read(alamatEEPROMCountDownAktif);
  if(countDownAktif)
  {
    EEPROM.get(alamatEEPROMCountDownMulai, countDownMulai);
    EEPROM.get(alamatEEPROMCountDownDetik, countDownDetik);
    Serial.println("Hitung mundur aktif");
  }
  
}

void loop() {
  tmElements_t tm;

  if(Serial.available())
  {
    if (RTC.read(tm))
    {
      delay(200);
      countDownDetik = Serial.parseInt();
      countDownMulai = makeTime(tm);
      countDownAktif = true;
      EEPROM.write(alamatEEPROMCountDownAktif, countDownAktif);
      EEPROM.put(alamatEEPROMCountDownDetik, countDownDetik);
      EEPROM.put(alamatEEPROMCountDownMulai, countDownMulai);
      Serial.println("Waktu hitung mundur = " + String(countDownDetik) + " detik");
    }
    else
    {
      Serial.println("Gagal membaca RTC");
    }
  }
  
  if (RTC.read(tm)) 
  {
    if(detikSebelumnya != tm.Second)
    {
      if(countDownAktif)
      {
        RTCEpoch = makeTime(tm);
        uint32_t waktuCountDown = countDownDetik - (RTCEpoch - countDownMulai);
        Serial.println("Hitung mundur : " + String(waktuCountDown) + " detik");
  
        if(RTCEpoch < countDownMulai)
        {
          countDownAktif = false;
          EEPROM.write(alamatEEPROMCountDownAktif, countDownAktif);
          Serial.println("Hitung mundur di matikan karena Waktu RTC salah");
          Serial.println("Entri waktu hitung mundur (dalam detik) :");
        }
        else if(RTCEpoch > (countDownMulai + countDownDetik))
        {
          countDownAktif = false;
          EEPROM.write(alamatEEPROMCountDownAktif, countDownAktif);
          Serial.println("Hitung mundur kadaluarsa");
          Serial.println("Entri waktu hitung mundur (dalam detik) :");
        }
        else if(waktuCountDown == 0)
        {
          countDownAktif = false;
          EEPROM.write(alamatEEPROMCountDownAktif, countDownAktif);
          Serial.println("Hitung mundur berakhir");
          Serial.println("Entri waktu hitung mundur (dalam detik) :");
        }
      }
      detikSebelumnya = tm.Second;
    }
  }
  else
  {
    Serial.println("Gagal membaca RTC");
  }
  delay(100);
}

Keluaran serial monitor hitung mundur berbasis arduino:

Menu LCD Arduino dengan keypad

Aplikasi menu arduino memang menarik namun tidak mudah untuk dibuat. Arduino menggunakan menu merupakan aplikasi yang menampilkan sejumlah pilihan sehingga pengguna bisa memilih/merubah pilihannya.

Menu interaktif lebih cocok digunakan apabila sejumlah pilihan tidak bisa ditampilkan dalam satu halaman. Misalnya menampilkan menu pada LCD karakter 16×2 yang hanya bisa menampung 16 karakter setiap barisnya.

Salah satu menu yang sering digunakan adalah menu-menu makanan dan minuman pada penerapan restoran yang menggunakan sistem digital terkoneksi.

Kelebihan Menu I2C LCD Arduino ini adalah:

  1. Tampilan interaktif bergilir setiap 1 detik dan tampil 5 detik ketika hendak dipilih.
  2. Menggunakan keypad 4×4 sehingga lebih lega.

Video menu interaktif arduino:

Dalam perancangan berbasis arduino ini digunakan komponen berikut :

  1. Arduino Uno
  2. LCD 1602 + I2C
  3. Keypad 4×4

skema menu arduino LCD dan keypad:

koding/sketch menu keypad arduino:

#include <LiquidCrystal_I2C.h>
#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 4; //three columns
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {4, 5, 6, 7};
byte colPins[COLS] = {8, 9, 10, 11};

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

LiquidCrystal_I2C lcd(0x3F, 16, 2);

struct daftarMenu {
  char strMenu[17];
  uint32_t harga;
  bool pilihan;
};

char menuUtama[][17] = {
  "0..9 - Pilih    ",
  "* - Pilih       ",
  "# - Batal       ",
  "A - Makanan     ",
  "B - Minuman     ",
  "C - Total/Pesan ",
  "D - Batal       ",
};
daftarMenu menuMakanan[] = {
  {"1 Nasi Goreng   ", 13000L, false},
  {"2 Mie Goreng    ", 8000L, false},
  {"3 Bihun Goreng  ", 8000L, false},
  {"4 Mie Rebus     ", 6000L, false},
  {"5 Gado-gado     ", 13000L, false},
  {"6 Soto Padang   ", 15000L, false},
  {"7 Sate Padang   ", 18000L, false},
};
daftarMenu menuMinuman[] = {
  {"1 Es Campur     ", 6000L, false},
  {"2 Es Tebak      ", 7500L, false},
  {"3 Es Kosong     ", 2000L, false},
  {"4 Jus Jeruk     ", 6000L, false},
  {"5 Jus Pokat     ", 6500L, false},
  {"6 Kopi          ", 4000L, false},
  {"7 Teh Panas     ", 4000L, false},
  {"8 Teh Telur     ", 8000L, false},
};

int8_t indexMenu = -1;
byte menuLevel = 0;
byte menuLevelSebelumnya = -1;

enum ModeMenu {
  modeMenuMakanan,
  modeMenuMinuman,
};
ModeMenu modeMenu;
#define standarWaktuTampil  1000L
#define LihatWaktuTampil    5000L
#define jumlahMakanan       sizeof(menuMakanan)/sizeof(menuMakanan[0])
#define jumlahMinuman       sizeof(menuMinuman)/sizeof(menuMinuman[0])

uint16_t waktuTampil;
unsigned long millisMulai;

void setup()
{
  Serial.begin(9600);
  Serial.println("Menu LCD Arduino dengan keypad");
  Serial.println("https://www.project.semesin.com/");

  Wire.begin();
  Wire.beginTransmission(0x3F);
  if (Wire.endTransmission())
  {
    lcd = LiquidCrystal_I2C(0x27, 16, 2);
  }
  lcd.begin ();
  lcd.backlight();
  //  tampilanDepan();
  millisMulai = millis();
  resetPilihan();
}

void loop()
{
  char key = keypad.getKey();

  if (key) {
    Serial.println(key);
    switch (key)
    {
      case 'A':
        menuLevel = 1;
        indexMenu = -1;
        modeMenu = modeMenuMakanan;
        break;
      case 'B':
        menuLevel = 1;
        indexMenu = -1;
        modeMenu = modeMenuMinuman;
        break;
      case 'C':
        menuLevel = 2;
        updateMenu();
        break;
      case 'D':
        resetPilihan();
        menuLevel = 0;
        indexMenu = -1;
        updateMenu();
        break;
      case '*':
        if (menuLevel == 2)
        {
          pesananMasuk();
        }
        else
        {
          if (waktuTampil == LihatWaktuTampil)
          {
            if (modeMenu == modeMenuMakanan)
            {
              menuMakanan[indexMenu].pilihan = true;
            }
            else if (modeMenu == modeMenuMinuman)
            {
              menuMinuman[indexMenu].pilihan = true;
            }
            updateMenu();
          }
          else
          {
            waktuTampil = LihatWaktuTampil;
            millisMulai = millis();
          }
        }
        break;
      case '#':
        if (menuLevel == 2)
        {
          menuLevel = 1;
        }
        else
        {
          if (waktuTampil == LihatWaktuTampil)
          {
            if (modeMenu == modeMenuMakanan)
            {
              menuMakanan[indexMenu].pilihan = false;
            }
            else if (modeMenu == modeMenuMinuman)
            {
              menuMinuman[indexMenu].pilihan = false;
            }
            updateMenu();
          }
          else
          {
            waktuTampil = LihatWaktuTampil;
            millisMulai = millis();
          }
        }
        break;
      default:
        indexMenu = key - '1';
        updateMenu();
        millisMulai = millis();
        waktuTampil = LihatWaktuTampil;
        break;
    }
  }

  if (millis() - millisMulai > waktuTampil)
  {
    millisMulai = millis();
    waktuTampil = standarWaktuTampil;
    indexMenu++;
    updateMenu();
    menuLevelSebelumnya = menuLevel;
  }
}
void pesananMasuk()
{
  //Aksi pesanan masuk
  lcd.setCursor(0, 0);
  lcd.println("  Terima Kasih  ");
  lcd.setCursor(0, 1);
  lcd.println("Silahkan tunggu ");
  delay(3000);
  Serial.println("Pesanan masuk!!!");
  menuLevel = 0;
  indexMenu = -1;
}
void updateMenu()
{
  if (menuLevel == 0)
  {
    if (indexMenu == sizeof(menuUtama) / sizeof(menuUtama[0]))
    {
      indexMenu = 0;
    }
    if (menuLevelSebelumnya != menuLevel)
    {
      lcd.clear();
      lcd.print("Selamat Datang");
    }
    tampilMenuUtama(indexMenu);
  }
  else if (menuLevel == 2)
  {
    lcd.setCursor(0, 0);
    lcd.print("Rp. ");
    formatStrHarga(totalPilihan());
    lcd.setCursor(0, 1);
    lcd.print("* Ya   # kembali");
  }
  else if (modeMenu == modeMenuMakanan)
  {
    if (indexMenu >= jumlahMakanan)
    {
      indexMenu = 0;
    }
    tampilMenuMakanan(indexMenu);
  }
  else if (modeMenu == modeMenuMinuman)
  {
    if (indexMenu >= jumlahMinuman)
    {
      indexMenu = 0;
    }
    tampilMenuMinuman(indexMenu);
  }
}
void resetPilihan()
{
  for (byte i = 0; i < jumlahMakanan; i++)
  {
    menuMakanan[i].pilihan = false;
  }
  for (byte i = 0; i < jumlahMinuman; i++)
  {
    menuMinuman[i].pilihan = false;
  }
}
uint32_t totalPilihan()
{
  uint32_t total = 0;
  for (byte i = 0; i < jumlahMakanan; i++)
  {
    if (menuMakanan[i].pilihan)
    {
      total += menuMakanan[i].harga;
    }
  }
  for (byte i = 0; i < jumlahMinuman; i++)
  {
    if (menuMinuman[i].pilihan)
    {
      total += menuMinuman[i].harga;
    }
  }
  return total;
}
void tampilMenuUtama(byte index)
{
  lcd.setCursor(0, 1);
  lcd.print(menuUtama[index]);
}
void tampilMenuMakanan(byte index)
{
  lcd.setCursor(0, 0);
  lcd.print(menuMakanan[index].strMenu);
  lcd.setCursor(0, 1);
  lcd.print("Rp. ");
  formatStrHarga(menuMakanan[index].harga);
  if (menuMakanan[index].pilihan)
  {
    lcd.setCursor(15, 1);
    lcd.print("*");
  }
}
void tampilMenuMinuman(byte index)
{
  lcd.setCursor(0, 0);
  lcd.print(menuMinuman[index].strMenu);
  lcd.setCursor(0, 1);
  lcd.print("Rp. ");
  formatStrHarga(menuMinuman[index].harga);
  if (menuMinuman[index].pilihan)
  {
    lcd.setCursor(15, 1);
    lcd.print("*");
  }
}
void formatStrHarga(uint32_t harga)
{
  String strHarga = String(harga);
  uint8_t panjangStr = strHarga.length();
  uint8_t offset = 3 - (panjangStr % 3);
  for (byte i = 0; i < strHarga.length(); i++)
  {
    lcd.print(strHarga[i]);
    if (!((strHarga.length() + i - offset + 1) % 3))
    {
      if (i != strHarga.length() - 1)
      {
        lcd.print('.');
      }
    }
  }
  for (byte i = 0; i < 16 - 5 - strHarga.length(); i++)
  {
    lcd.print(' ');
  }
}

Library:

Suara Arduino

Arduino merupakan platform elektronik yang dirancang untuk memudahkan pengontrolan berbagai perangkat.

Hardware arduino mikrokontroller yang memiliki port masukan (input)  dan port keluaran (output). Perangkat keras arduino juga dilengkapi dengan fitur-fitur bawaan chip mikrokontroler atau diprogram secara software, seperti komunikasi serial, SPI, I2C, ADC, TIMER.

Sebagai perangkat digital, arduino juga mampu berperilaku sebagai perangkat multimedia terbatas, yakni hanya sebagai pengontrol melalui protokol yang disediakan. Salah satu perangkat multimedia yang mampu ditangani oleh arduino adalah suara.

Arduino audio / suara bisa dihasilkan dalam 2 metode, yaitu arduino sebagai pembangkit suara dan arduino sebagai pengontrol modul suara.

Suara PWM arduino

Port output arduino mampu melewatkan arus 40mA setiap pinnya, artinya arduno masih mampu membangkitkan suara untuk keperluan headset. Selain keuntungan itu, arduino juga memiliki memory walaupun kapasitasnya kecil, seperti pada arduino uno mampu menampung kurang dari 8detik data PCM 8bit 8KHz yang membutuhkan 8.000 byte flash memory setiap detiknya. Untuk efesiensi memory bisa dengan menurunkan sampling rate atau menggunakan sistem kompresi ADPCM.

prinsip kerja Suara PCM Arduino dengan PWM:

  1. Data suara disimpan dalam memory flash.
  2. Dibutuhkan dua timer, timer1 berfungsi mengatur waktu sampling (misalnya 8KHz, 16KHz dst), dan timer 2 yang berfungsi mengatur PWM Sesuai ukuran bit sampling (misalnya 8bit dan 16 bit).
  3. Data PWM berubah dan dikirim saat timer1 selesai 1 periode sampling.

Keterbatasan PCM arduino adalah : waktu * ukuran sampling tidak bisa melebihi besar clock arduino 16MHz.

Contoh rangkaian suara arduino langsung :

 

Modul Suara WTV020

 

Modul ini menggunakan format suara .ad4 yang disimpan dalam kartu memory microSD (kapasitas terbatas). Pengontrolan menggunakan pin-pin kontrol dengan mode operasi yang tersedia:

  1. MP3 mode
  2. Key mode(3 group of voice)
  3. Key mode(5 group of voice)
  4. Loop play mode
  5. Two line serial mode

Modul suara ISD

Modul ISD menggunakan chip yang memiliki fitur rekam dan membangkitkan suara. terdiri dari beberapa seri dan kapasitas.

Modul mini MP3 TF Player

 

Mini Mp3 TF Player banyak disukai karena murah dan mudah dalam operasionalnya, mampu menggerakkan speaker 0.5Watt, dan menggunakan protokol Serial frame. Data suara disimpan dalam format MP3 / WAV dalam microSD.

Modul Suara VS1053

VS1053 merupakan chip audio player yang powerfull dengan kualitas suara yang bagus. Data suara dalam format MP3 yang disimpan dalam microSD.

Perbandingan modul suara arduino

Masih terdapat beberapa modul suara yang bisa disandingkan dengan arduino, yang disebutkan diatas bisa mewakili yang paling umum digunakan dalam perancangan perangkat multimedia berbasis arduino.

Kualitas suara yang dihasilkan menjadi alasan utama dari pemilihan modul audio untuk arduino, dan menurut penulis kualitas modul VS1053 paling baik dan layak digunakan sebagai media informasi. sedangkan modul lainnya efektif digunakan dalam pengembangan perangkat karena harganya relatif lebih murah.

Kualitas suara bisa ditingkatkan dengan memperhatikan hal berikut:

  1. Filter keluaran suara
  2. Power supply / catu daya yang cukup dan tidak saling menmpengaruhi dengan modu lainnya.
  3. Penggunaan speaker yang sesuai, kalau perlu gunakan box.
  4. Konversi suara mono ke stereo atau sebaliknya menggunakan cara koneksi yang benar.
  5. Konversi suara single supply ke balance double supply atau sebaliknya menggunakan cara konversi yang benar.

Simpan dan ambil data setting menggunakan metode list pada App Inventor

Database tinyDB

Cara menyimpan data input app inventor agar dapat ditampilkan kembali saat app dibuka kembali adalah dengan memanfaatkan media storage seperti TinyDB untuk menyimpan data tersebut. Data-data yang disimpan dalam database TinyDB diberi index/tag tertentu. Untuk mengambil data dari TinyDB dibutuhkan input/tag yang sesuai.

Kegunaan menyimpan data ke dalam database TinyDB adalah supaya data yang telah di entri tidak hilang setelah app ditutup.

List interface / object

Penggunaan interface dalam jumlah besar pada App Iventor sebaiknya menggunakan list, karena dengan list proses pengambilan dan penyimpanan hasil lebih mudah dan memperpendek block.

berikut keterangan block perancangan apk App Inventor:

Tahap awal penggunaan list adalah dengan menginisialisasi-nya sebagai list kosong (empty list) masing-masing kelompok interface (seperti checkbox dan textbox)

Kemudian list-list tersebut ditambahkan item yang bersesuaian, dalam contoh ini checkbox1, checkbox2, checkbox3 ditambahkan dalam list Array_checkbox. dan textbox1, textbox2, textbox3 ditambahkan dalam list Array_textbox.

Selanjutnya prosedur ambil_dari_database dipanggil untuk mengambil data-data yang tersimpan sebelumnya ke dalam masing-masing item list.

Prosedur ini menggunakan metode list dengan memecah item-item dalam variabel Array_checkbox dan Array_textbox.

Prosedur ambil data dari database akan melaksanakan fungsi mengambil data-data dari tinyDB dengan tag checkbox1, checkbox2, checkbox3 dan textbox1, textbox2, textbox3.

Prosedur Simpan_ke_database berfungsi menyimpan data-data yang telah di entry  kedalam database dengan tag/index tertentu.

Contoh penggunaan prosedur simpan_ke_database adalah saat pengguna / user menekan tombol back. Sebelum keluar dari app, terlebih dahulu dipanggil prosedur Simpan_ke_database untuk menyimpan data-data entri interface untuk digunakan kembali saat app dibuka selanjutnya.

blok app Inventor cara menyimpan data dan cara menggunakan list yang digunakan :

Design screen app inventor dengan banyak interface serupa yang di kelompokkan kedalam list yang berguna mempersingkat perulangan block.

dan apk cara menggunakan database app Inventor yang dipakai dalam contoh ini:

SimpanDanAmbilDataSetting.apk

Tampilan seven segment dengan metode scanning menggunakan arduino

Seven segment banyak digunakan sebagai tampilan dari sebuah perangkat, terutama tampilan angka. Seven segment telah tersedia dalam berbagai macam teknologi serta kelebihan masing-masing. Seven segment jenis LED memiliki kelebihan mampu menghasilkan cahaya sendiri sehingga terlihat lebih terang. Sedangkan seven segmen LCD hanya membutuhkan daya kecil untuk operasi-nya.

Setiap segment dari 7segment memiliki paling kurang 8 pin yaitu 7 pin untuk digit, dan 1 pin untuk CA/CC. Untuk membentuk deret angka seven segment digabungkan menjadi beberapa digit. Untuk mengontrol banyak digit seven segment biasanya digunakan metode scanning. Melalui metode scanning ini penggunaan pin menjadi lebih efisien.

Cara menghubungkan seven segment dengan arduino bisa menggunakan transistor array dan BCD to seven segment. Apabila menggunakan seven segment ukuran kecil misalnya ukuran 0.5″, bisa saja seven segment-nya dihubungkan langsung ke arduino seperti contoh berikut:

skema rangkaian seven segment menggunakan arduino:

tata letak seven segment:

sketch atau program scanning seven segment menggunakan arduino:

#include <TimerOne.h>

#define CCorCA            1//0 = CC, 1 = CA
#define jumlah7Segment    4

#define pin1    A0
#define pin2    A1
#define pin3    A2
#define pin4    A3

#define pinA    4
#define pinB    5
#define pinC    6
#define pinD    7
#define pinE    8
#define pinF    9
#define pinG    10
#define pinDot  11

byte pin7Segment[] = {pinA, pinB, pinC, pinD, pinE, pinF, pinG};
byte pin7SegmentCommon[] = {pin1, pin2, pin3, pin4};

const char angka[] = {
  0b00111111,
  0b00000110,
  0b01011011,
  0b01001111,
  0b01100110,
  0b01101101,
  0b01111100,
  0b00000111,
  0b01111111,
  0b01100111,
  0b00000000,//blank
  0b01000000,//strip
  0b01101101,//S
  0b00000100,//i
  0b01011111,//a
  0b01110011,//p

};
#define Seg_blank 10
#define Seg_stip 11
#define Seg_S 12
#define Seg_i 13
#define Seg_a 14
#define Seg_p 15

#define dotBlank 6
#define dotAll   5

volatile byte nilai7Segment[4];
volatile byte lastScanIndex = 0;
volatile byte index7Segment = 0;
volatile byte posisiTitik;

void setup() {
  Serial.begin(9600);
  Serial.println("Tampilan 7 Segment metode scanning menggunakan arduino");
  Serial.println("https://www.project.semesin.com/");
  Serial.println();

  pinMode(pin1, OUTPUT);
  pinMode(pin2, OUTPUT);
  pinMode(pin3, OUTPUT);
  pinMode(pin4, OUTPUT);

  pinMode(pinA, OUTPUT);
  pinMode(pinB, OUTPUT);
  pinMode(pinC, OUTPUT);
  pinMode(pinD, OUTPUT);
  pinMode(pinE, OUTPUT);
  pinMode(pinF, OUTPUT);
  pinMode(pinG, OUTPUT);
  pinMode(pinDot, OUTPUT);

  Timer1.initialize(2000);
  Timer1.attachInterrupt( timerIsr );

  nilai7Segment [3] = Seg_S;
  nilai7Segment [2] = Seg_i;
  nilai7Segment [1] = Seg_a;
  nilai7Segment [0] = Seg_p;
  posisiTitik = dotBlank;
  delay(1000);
}

void loop() {
  for (uint16_t i = 0; i < 10000; i++)//0 .. 99.99
  {
    uint32_t BCD = Convert_IntToBCD32(i);
    nilai7Segment [0] = (BCD >> 0) & 0x0F;
    nilai7Segment [1] = (BCD >> 4) & 0x0F;
    nilai7Segment [2] = (BCD >> 8) & 0x0F;
    nilai7Segment [3] = (BCD >> 12) & 0x0F;
    posisiTitik = 2;
    delay(100);
  }
}

void timerIsr()
{
  digitalWrite(pin7SegmentCommon[lastScanIndex], CCorCA ? LOW : HIGH);
  drive7Segment(nilai7Segment[index7Segment]);
  if (posisiTitik == dotAll)
  {
    digitalWrite(pinDot, CCorCA ? LOW : HIGH);
  }
  else if (posisiTitik == dotBlank)
  {
    digitalWrite(pinDot, CCorCA ? HIGH : LOW);
  }
  else
  {
    digitalWrite(pinDot, (index7Segment == posisiTitik) ? CCorCA ? LOW : HIGH : CCorCA ? HIGH : LOW);
  }
  digitalWrite(pin7SegmentCommon[index7Segment], CCorCA ? HIGH : LOW);
  lastScanIndex = index7Segment++;
  if (index7Segment >= jumlah7Segment)index7Segment = 0;
}
void drive7Segment(byte nilai)
{
  byte nilai7Segment = CCorCA ? ~angka[nilai] : angka[nilai];
  for (byte i = 0; i < sizeof(pin7Segment); i++)
  {
    digitalWrite(pin7Segment[i], nilai7Segment & 0x01);
    nilai7Segment >>= 1;
  }
}
uint32_t Convert_IntToBCD32(uint32_t DecimalValue)
{
  uint32_t returnValue = 0;
  //uint32_t LSB_L = DecimalValue;

  while (DecimalValue >= 10000000L)
  {
    DecimalValue -= 10000000L;
    returnValue += 0x10000000;
  }
  while (DecimalValue >= 1000000L)
  {
    DecimalValue -= 1000000L;
    returnValue += 0x01000000;
  }
  while (DecimalValue >= 100000L)
  {
    DecimalValue -= 100000L;
    returnValue += 0x00100000;
  }
  while (DecimalValue >= 10000)
  {
    DecimalValue -= 10000;
    returnValue += 0x00010000;
  }
  while (DecimalValue >= 1000L)
  {
    DecimalValue -= 1000L;
    returnValue += 0x00001000;
  }
  while (DecimalValue >= 100)
  {
    DecimalValue -= 100;
    returnValue += 0x00000100;
  }
  while (DecimalValue >= 10)
  {
    DecimalValue -= 10;
    returnValue += 0x00000010;
  }
  return returnValue + DecimalValue;
}

Library : TimerOne.zip

Kalibrasi sensor loadcell interaktif dengan Serial USB TTL Arduino

Sebelum digunakan, timbangan wajib ditera atau di kalibrasi. Proses kalibrasi timbangan dilakukan dengan membandingkan hasil terukurnya dengan berat diketahui sebuah beban pengkalibrasinya.

Loadcell digunakan sebagai sensor berat digital. Proses kalibrasi loadcell dengan arduino menggunakan metode dua beban menggunakan formula :

cara mengkalibrasi loadcell dengan metode dua beban ini menghasilkan dua variabel yaitu skala dan ofset.

Timbangan digital dengan sensor loadcell memiliki karakteristik akurasi berikut :

  1. Skala
  2. Ofset
  3. Linearitas
  4. Diferensial linearitas
  5. Kuantisasi
  6. Transisi akurasi

Setiap loadcell memiliki karakteristik yang berbeda-beda. cara memperoleh nilai skala dan offset yang baik adalah dengan menetapkan nilai beban pengkalibrasi antara 25% – 75% dari kapasitas loadcell.

skema / rangkaian kalibrasi loadcell arduino:

Setting kalibrasi ini akan disimpan di EEPROM, dan akan diambil kembali saat perangkat mulai dihidupkan.

koding /program kalibrasi loadcell menggunakan arduino:

#include "HX711.h"
#include <EEPROM.h>

#define alamatKalibrasiM 0
#define alamatKalibrasiC 4

//pin
HX711 scale(A0, A1); // (DT, SCK)

byte modeKalibrasi = 0;
uint16_t beratKalibrasi1Tera;
uint16_t beratKalibrasi2Tera;
long beratKalibrasi1;
long beratKalibrasi2;

long lastMillis;

void setup() {
  Serial.begin(9600);
  Serial.println("Kalibrasi Loadcell");
  Serial.println("https://www.project.semesin.com/");
  Serial.println();
  
  float m,c;
  EEPROM.get(alamatKalibrasiM, m);
  EEPROM.get(alamatKalibrasiC, c);
  scale.power_up();
  scale.set_scale(m);
  scale.set_offset(c);
  scale.power_down();

  lastMillis = millis();
}

void loop() {
  if(Serial.available())
  {
    if(modeKalibrasi == 0)
    {
      if(toupper(Serial.read()) == 'K')
      {
        Serial.println("Masukkan beban kalibrasi pertama (gram) :");
        modeKalibrasi = 1;
      }
    }
    else if (modeKalibrasi == 1)
    {
      scale.power_up();
      delay(100);
      beratKalibrasi1Tera = Serial.parseInt();
      beratKalibrasi1 = scale.read_average(10);
      Serial.println("Beban = " + String(beratKalibrasi1Tera) + " gram, terukur = " + String(beratKalibrasi1) + " unit");
      Serial.println("Masukkan beban kalibrasi kedua yang lebih besar (gram) :");
      modeKalibrasi = 2;
      scale.power_down();
    }
    else if (modeKalibrasi == 2)
    {
      scale.power_up();
      delay(100);
      beratKalibrasi2Tera = Serial.parseInt();
      beratKalibrasi2 = scale.read_average(10);
      Serial.println("Beban = " + String(beratKalibrasi2Tera) + " gram, terukur = " + String(beratKalibrasi2) + " unit");
      
      float m = 1.0 * (beratKalibrasi2 - beratKalibrasi1) / ( beratKalibrasi2Tera - beratKalibrasi1Tera);
      float c = beratKalibrasi2 - (1.0 * m * beratKalibrasi2Tera);

      scale.set_scale(m);
      scale.set_offset(c);
      EEPROM.put(alamatKalibrasiM, m);
      EEPROM.put(alamatKalibrasiC, c);

      Serial.print("Skala = ");
      Serial.println(m);
      Serial.print("Ofset = ");
      Serial.println(c);

      scale.power_down();
      Serial.println("Kalibrasi berhasil.");
      
      modeKalibrasi = 0;
    }
  }

  if(!modeKalibrasi)
  {
    if(millis() - lastMillis > 100)
    {
      scale.power_up();
      delay(10);
      float berat = scale.get_units(10);
      scale.power_down();
      Serial.print("Berat : ");
      Serial.println(berat);
      lastMillis = millis();
    }
  }
}

Cara kalibrasi loadcell melalui serial monitor:

Prosedur kalibrasi loadcell dengan arduino melalui serial monitor:

  1. Input ‘K’ atau ‘k’ untuk masuk mode kalibrasi.
  2. Naikkan beban pengkalibrasi pertama
  3. Inputkan berat tera beban pengkalibrasi pertama – Enter
  4. Naikkan beban pengkalibrasi kedua
  5. Inputkan berat tera beban pengkalibrasi kedua – Enter
  6. Selesai

Library : HX711.zip