Antar muka (interface) kalibrasi sensor PH melalui serial monitor Arduino

Sensor PH adalah instrumen untuk mengukur konsentrasi hidrogen dalam sebuah larutan. Baik sensor pH untuk air maupun untuk tanah perlu dikalibrasi berkala agar ke-akuratannya terjamin.

Untuk menjamin keakuratan sensor ph, diperlukan bahan buffer solution dengan pH diketahui dan akurat. buffer solution yang digunakan umumnya adalah dengan pH 4.0 dan pH 7.0.

Beberapa produsen sensor pH juga menyertakan instrumen untuk melakukan kalibrasi secara manual. Namun jika dihubungkan dengan arduino maka, arduino (instrumen baca) juga perlu dikalibrasi. Dalam atikel ini disertakan program interface kalibrasi sensor pH melalui serial monitor, yang merupakan pengembangan dari library sensor pH yang ada. Hasil kalibrasi akan tersimpan dalam EEPROM untuk digunakan dalam pengukuran normal.

Skema mengkalibrasi sensor pH dengan arduino:

Sktech program pengkalibrasian sensor ph pada arduino :

#define pinPH               A1
#define suhuPengukuran      25

#include "DFRobot_PH.h"

DFRobot_PH ph;

float voltagePH;
float phValue;

long millisBacaSensor;

void setup()
{
  Serial.begin(9600);
  Serial.println("Kalibrasi sensor PH melalui serial monitor");
  Serial.println("Buka serial monitor dan kirim (send) sebarang karakter");
  Serial.println("https://www.semesin.com/project");
  Serial.println();

  ph.begin();
}

void loop()
{
  if (millisBacaSensor < millis())
  {
    millisBacaSensor = millis() + 3000;

    voltagePH = analogRead(pinPH) / 1024.0 * 5000;          // read the ph voltage
    phValue    = ph.readPH(voltagePH, suhuPengukuran);      // convert voltage to pH with temperature compensation

    byte PH = round(phValue);

    Serial.println();
    Serial.print("voltagePH = ");
    Serial.println(voltagePH);
    Serial.print("phValue = ");
    Serial.println(phValue);
    Serial.print("PH = ");
    Serial.println(PH);
  }

  if (Serial.available())
  {
    Serial.readString();

    Serial.println();
    Serial.println("Proses kalibrasi, ikuti petunjuk di serial monitor");
    
    ph.calibration(voltagePH, suhuPengukuran, "ENTERPH");

    while (!Serial.available());
    Serial.readString();
    
    voltagePH = analogRead(pinPH) / 1024.0 * 5000;      // read the ph voltage
    ph.calibration(voltagePH, suhuPengukuran, "CALPH");

    ph.calibration(voltagePH, suhuPengukuran, "EXITPH");
  }
}

library : DFRobot_PH.zip

 

Penjadwalan dan durasi aktif relay dengan setting melalui tombol berbasis arduino

Pengontrolan waktu hidup dan mati peralatan sebagai otomasi yang mengurangi ketergantungan dari kontrol operator, bisa dilakukan dengan penjadwalan beban/peralatan. Contohnya dalam pengaturan beban dalam rumah tangga, misalkan terdapat beban yang akan dikontrol seperti berikut :

  1. lampu teras
  2. lampu taman
  3. pompa pengisi air tandon
  4. kran/solenoid Penyiram tanaman
  5. electric water heater

adalah beban-beban listrik yang biasanya hidup dan mati-nya terjadwal.

Untuk mengatur jadwalnya, pada contoh ini menggunakan 3 tombol yaitu set, up dan down. adapun komponen yang digunakan adalah :

  1. Arduino uno
  2. rtc ds3231
  3. lcd 16×02 i2c
  4. relay 8 channel

Skema penjadwalan relay menggunakan arduino :

koding arduino penjadwalan relay :


#define pinTombolSet        A0
#define pinTombolUp         A1
#define pinTombolDown       A2

#define pinRelay1           2
#define pinRelay2           3
#define pinRelay3           4
#define pinRelay4           5
#define pinRelay5           6
#define pinRelay6           7
#define pinRelay7           8
#define pinRelay8           9

#define relayOn             LOW
#define jumlahRelay         8

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "Sodaq_DS3231.h"
#include "EEPROM.h"

LiquidCrystal_I2C lcd(0x3F, 16, 2);//coba juga 0x27

byte setting[jumlahRelay * 4];
byte menu = 0;
char buf[32];
DateTime now;
byte detikSebelumnya = 60;
byte pinRelay[] = {pinRelay1, pinRelay2, pinRelay3, pinRelay4, pinRelay5, pinRelay6, pinRelay7, pinRelay8};

void setup()
{
  pinMode(pinTombolSet, INPUT_PULLUP);
  pinMode(pinTombolUp, INPUT_PULLUP);
  pinMode(pinTombolDown, INPUT_PULLUP);

  for (byte i = 0; i < jumlahRelay; i++)
  {
    digitalWrite(pinRelay[i], !relayOn);
    pinMode(pinRelay[i], OUTPUT);
  }

  Serial.begin(9600);
  Serial.println(F("Penjadwalan dan durasi aktif relay dengan setting melalui tombol berbasis arduino"));
  Serial.println(F("https://www.semesin.com/project"));
  Serial.println();
  
  Wire.begin();
  rtc.begin();
  lcd.begin();

  lcd.backlight();
  lcd.print("Jadwal relay");
  lcd.setCursor(0, 1);
  lcd.print("www.Semesin.com");
  delay(3000);

  lcd.clear();

  if (EEPROM.read(0) != 12)
  {
    EEPROM.update(0, 12);
    for (byte i = 0; i < jumlahRelay * 4; i++)
    {
      setting[i] = 0;
    }
    EEPROM.put(1, setting);
  }
  EEPROM.get(1, setting);

  Serial.println("Sistem mulai");

}


void loop()
{
  now = rtc.now();
  if (detikSebelumnya != now.second())
  {
    detikSebelumnya = now.second();
    if (now.second() == 0)
    {
      for (byte i = 0; i < jumlahRelay; i++)
      {
        uint16_t unixNow = (now.hour() * 60) +  now.minute();
        uint16_t unixWaktu = ((setting[(i * 4) + 0] * 60) +  setting[(i * 4) + 1]);
        uint16_t unixDurasi = ((setting[(i * 4) + 2] * 60) +  setting[(i * 4) + 3]);
        if (unixNow == unixWaktu)
        {
          digitalWrite(pinRelay[i], LOW);
          Serial.print("Relay ");
          Serial.print(i + 1);
          Serial.println(" aktif");
        }
        if (unixNow == unixWaktu + unixDurasi)
        {
          digitalWrite(pinRelay[i], HIGH);
          Serial.print("Relay ");
          Serial.print(i + 1);
          Serial.println(" tidak aktif");
        }
      }
    }
    if (!menu)
    {
      sprintf(buf, "%02d/%02d/%04d", now.date(), now.month(), now.year());
      lcd.setCursor(3, 0);
      lcd.print(buf);
      sprintf(buf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
      lcd.setCursor(4, 1);
      lcd.print(buf);
    }
  }

  if (menu)
  {
    if (!digitalRead(pinTombolUp))
    {
      delay(50);
      while (!digitalRead(pinTombolUp))
      {
        setting[menu - 1]++;
        if ((menu - 1) % 2)
        {
          if (setting[menu - 1] >= 60)
          {
            setting[menu - 1] = 0;
          }
        }
        else
        {
          if (setting[menu - 1] >= 24)
          {
            setting[menu - 1] = 0;
          }
        }

        tampilanMenu();
        delay(100);
      }
    }
    if (!digitalRead(pinTombolDown))
    {
      delay(50);
      while (!digitalRead(pinTombolDown))
      {

        if ((menu - 1) % 2)
        {
          if (setting[menu - 1] == 0)
          {
            setting[menu - 1] = 59;
          }
          else
          {
            setting[menu - 1]--;
          }
        }
        else
        {
          if (setting[menu - 1] == 0)
          {
            setting[menu - 1] = 23;
          }
          else
          {
            setting[menu - 1]--;
          }
        }

        tampilanMenu();
        delay(100);
      }
    }
  }

  if (!digitalRead(pinTombolSet))
  {
    delay(50);
    if (!digitalRead(pinTombolSet))
    {
      menu++;

      if (menu == jumlahRelay * 4 + 1)
      {
        menu = 0;
        lcd.clear();
        lcd.noCursor();
        EEPROM.put(1, setting);
      }
      else
      {
        lcd.clear();
        lcd.setCursor(2, 0);
        lcd.print("Set Relay ");
        lcd.print(((menu - 1) / 4) + 1);
        tampilanMenu();
      }
      
      long miliisTekanPanjang = millis() + 3000;
      while (!digitalRead(pinTombolSet))
      {
        if (miliisTekanPanjang < millis())
        {
          menu = 0;
          lcd.clear();
          lcd.noCursor();
          EEPROM.put(1, setting);

        }
      }
    }
  }
}
void tampilanMenu()
{
  sprintf(buf, "%s : %02d:%02d", ((menu - 1) % 4) / 2 ? "Durasi" : "Waktu ", setting[(menu - 1) & 0xFE], setting[((menu - 1) & 0xFE) + 1]);
  lcd.setCursor(0, 1);
  lcd.print(buf);

  lcd.setCursor(((menu - 1) % 2) ? 12 : 9, 1);
  lcd.cursor();
}
  1. LiquidCrystal-I2C.zip
  2. Sodaq_DS3231.zip

Remot kontrol android untuk mobil arduino melalui bluetooth

Remot kontrol adalah instrumen jarak jauh (remot) yang mengendalikan perangkat untuk melaksanakan fungsi-fungsi sesuai perintah. Komunikasi antara remot kontrol dan perangkat (rtu) bisa menggunakan kabel atau tanpa kabel (bluetooth, wifi, radio).

Android bisa dimanfatkan sebagai remot kontrol dengan memanfatkan fasilitas sensor-sensor yang dimilikinya. Dalam contoh ini (pengontrolan mobil arduino) memanfatkan fitur berikut :

  1. Bluetooth
  2. Sensor orientasi/kemiringan
  3. Touch screen

Pengendalian remot kontrol bisa dilakukan dalam 2 mode yaitu mode steer dan mode sensor orientasi.

Prototipe mobil arduino dibuat dari komponen berikut :

  1. Arduino uno
  2. Driver motor L298
  3. Motor DC
  4. Bluetooth HC-05 dan resistor pembagi tegangan
  5. Rangka miniatur mobil

Fokus dalam perancangan mobil arduino ini adalah pengendalian kecepatan motor kiri dan kanan sehingga diperoleh gerakan yang mulus/smooth. jadi tidak seperti kendali steer mobil pada umumnya yang menggunakan metode putaran poros roda untuk melakukan belokan. Untuk itu digunakan formula gerakan menggunakan pwm (pulse width modulation) dan pengaturan kecepatan roda kiri dan kanan sehingga dihasilkan gerakan yang halus.

berikut skema pengendalian gerakan mobil-mobilan arduino melalui android :

sketch arduino untuk mobil remot dengan gerakan yang halus:

#include <SoftwareSerial.h>

#define pinMotorKiriPlus            5
#define pinMotorKiriMinus           6
#define pinMotorKananPlus           10
#define pinMotorKananMinus          11

SoftwareSerial bluetooth(2, 3);

int x = 128;
int y = 128;
byte kiri;
byte kanan;
char buffer[32];
bool statusBerhenti;

void setup() {
  pinMode(pinMotorKiriPlus, OUTPUT);
  pinMode(pinMotorKiriMinus, OUTPUT);
  pinMode(pinMotorKananPlus, OUTPUT);
  pinMode(pinMotorKananMinus, OUTPUT);

  Serial.begin(9600);
  Serial.println(F("Remot kontrol android untuk mobil arduino melalui bluetooth"));
  Serial.println(F("https://www.semesin.com/project"));
  Serial.println();
  
  bluetooth.begin(9600);

  Serial.println("Sistem mulai");

}

void loop() {

  while (bluetooth.available())
  {
    char c = bluetooth.read();
    switch (c)
    {
      case 'x':
        x = bluetooth.parseInt();
        break;
      case 'y':
        y = bluetooth.parseInt();
        break;
    }
  }

  if ((y > 96) && (y < 160))
  {
    if (!statusBerhenti)
    {
      digitalWrite(pinMotorKiriPlus, LOW);
      digitalWrite(pinMotorKiriMinus, LOW);
      digitalWrite(pinMotorKananPlus, LOW);
      digitalWrite(pinMotorKananMinus, LOW);
      Serial.println("berhenti");
      statusBerhenti = true;
    }

  }
  else
  {
    float rasioKiri = 1.0 * x / 255;
    float rasioKanan = 1.0 * (255 - x) / 255;
    byte kecepatan = abs(y - 128) * 2;

    if (rasioKiri > rasioKanan)
    {
      rasioKanan += 1 - rasioKiri;
      rasioKiri = 1;
    }
    else
    {
      rasioKiri += 1 - rasioKanan;
      rasioKanan = 1;
    }

    kiri = constrain(rasioKiri * kecepatan, 0 , 255);
    kanan = constrain(rasioKanan * kecepatan, 0 , 255);

    sprintf(buffer, "%s : kiri: %d, kanan: %d", y < 128 ? "Maju" : "mundur", kiri, kanan);
    Serial.println(buffer);

    if (y < 128)
    {
      analogWrite(pinMotorKiriPlus, kiri);
      analogWrite(pinMotorKananPlus, kanan);
      digitalWrite(pinMotorKiriMinus, LOW);
      digitalWrite(pinMotorKananMinus, LOW);
    }
    else
    {
      analogWrite(pinMotorKiriMinus, kiri);
      analogWrite(pinMotorKananMinus, kanan);
      digitalWrite(pinMotorKiriPlus, LOW);
      digitalWrite(pinMotorKananPlus, LOW);
    }
    statusBerhenti = false;
  }
}

block app inventor remot kontrol android:

screenshoot Remot kontrol arduino pengendali mobil-mobilan:

File app inventor remot kontrol:

1. Setir_Android.apk
2. Setir_Android.aia

Sistem kontrol PID close loop – stabilizer tegangan – menggunakan arduino melalui simulink matlab

Sistem kontrol PID (Proportional–Integral–Derivative controller) bertujuan memperoleh hasil optimum yang menggunakan mekanisme umpan balik (sistem tertutup / close loop). sistem kontrol PID juga bisa diterapkan pada arduino. Untuk menambah kemampuan numerik pada arduino, bisa dikombinasikan dengan program matlab.

Program Matlab (Matrix Laboratory) yang memiliki fitur simulink dapat dapat digunakan untuk memprogram arduino, dan dengan kelebihan matlab bisa dengan mudah mengembangkan sistem kontrol (dan keperluan numerikal lain) kedepannya.

Untuk pemrograman arduino menggunakan matlab, diperlukan add-ons matlab berikut ini:

  1.  Simulink Support Package for Arduino Hardware
  2. MATLAB Support for MinGW-w64 C/C++ Compiler

Dalam contoh ini menggunakan plant pengatur tegangan keluaran pwm dengan beban kapasitor.

Skema / plant / rangkaian percobaan sistem kontrol pid loop tertutup berbasis arduino:

Model simulink matlab sistem kontrol tegangan

respon keluaran pengontrolan tegangan dengan arduino dan simulink

file simulink pengontrolan pid tegangan pwm:

pidArduino.slx

 

 

Input string melalui keypad menggunakan arduino

Keypad (dalam konteks arduino) sama halnya dengan keypad pada handphone lawas (tuts) yang terdiri dari setidaknya 12 tombol berupa angka 0 hingga 9 serta karakter * dan karakter #. Untuk menuliskan karakter huruf, maka tuts keypad harus ditekan beberapa kali hingga karakter yang diinginkan muncul.

String / text terdiri atas karakter-karakter penyusunnya, penggunaan keypad 3×4 (numerik) membutuhkan trik khusus dan terdapat bermacam-macam metode, dalam contoh ini menggunakan metode entry deret.

 

aturan pemakaian umum:

  1. Masing-masing tuts memiliki beberapa karakter, untuk memilih karakter maka tuts yang sama ditekan berulang-ulang hingga karakter yang diinginkan tampil.
  2. Jika tuts/tombol tidak ditekan selama 3 detik, maka karakter sebelumnya dimasukkan dalam memory dan sistem lanjut ke karakter berikutnya.
  3. Jika tuts yang ditekan berbeda dengan tuts sebelumnya, maka karakter terakhir akan dimasukkan dalam memory.
  4. tuts * berfungsi untuk menghapus karakter terakhir
  5. jika tuts * pada saat belum ada karakter yang dimasukkan, maka sistem akan kembali ke sistem normal (dalam contoh ini kembali ke keadaan awal).
  6. tuts # berfungsi seperti ‘enter’ untuk menyimpan string dan kembali ke sistem normal (dalam contoh ini kembali ke keadaan awal).

skema pemanfaatan keypad sebagai entry teks:

koding/sketch masukan keypad sebagai string / teks :

#define periodaKeypad         3000

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

LiquidCrystal_I2C lcd(0x3F, 16, 2);

const byte ROWS = 4;
const byte COLS = 3;
char keys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};
byte rowPins[ROWS] = {11, 10, 9, 8};
byte colPins[COLS] = {7, 6, 5};
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );


char pad[11][11] = {
  "0 ",
  "1?!*#:=/+-",
  "2AaBbCc",
  "3DdEeFf",
  "4GgHhIi",
  "5JjKkLl",
  "6MmNnOo",
  "7PpQqRrSs",
  "8TtUuVvWw",
  "9XxYyZz",
};

byte padCounter;
char padChar;
bool padDitekan;
byte charCounter;
byte keySebelumnya;
char bufferKeypad[17];
char *bufferKeypadPtr;

long millisKeypad;

void setup() {
  Serial.begin(9600);
  Serial.println(F("Input string malalui keypad menggunakan arduino"));
  Serial.println(F("https://www.semesin.com/project"));
  Serial.println();

  lcd.begin();

  resetInput();
}

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

  if (key) {
    switch (key)
    {
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':

        millisKeypad = millis() + periodaKeypad;
        if ((key == keySebelumnya) || (keySebelumnya == 0))
        {
          padChar = pad[key - '0'][charCounter];
          keySebelumnya = key;
        }
        else if ((padDitekan) && (padCounter < sizeof(bufferKeypad) - 1))
        {
          *bufferKeypadPtr++ = padChar;
          keySebelumnya = key;
          charCounter = 0;
          padCounter++;
          padChar = pad[key - '0'][charCounter];
        }

        padDitekan = true;

        lcd.setCursor(padCounter, 1);
        lcd.print(padChar);
        lcd.setCursor(padCounter, 1);

        charCounter++;
        if (!pad[key - '0'][charCounter])
        {
          charCounter = 0;
        }
        break;

      case '*':
        if (padCounter)
        {
          if (keySebelumnya)
          {
            keySebelumnya = 0;
          }
          lcd.setCursor(padCounter, 1);
          lcd.print(' ');
          charCounter = 0;
          padCounter--;
          bufferKeypadPtr--;
          padChar = *bufferKeypadPtr;

          lcd.setCursor(padCounter, 1);
        }
        else
        {
          resetInput();
        }
        break;
      case '#':
        if ((padDitekan) && (padCounter < sizeof(bufferKeypad) - 1))
        {
          *bufferKeypadPtr++ = padChar;
        }
        *bufferKeypadPtr = 0;

        Serial.print("String input = ");
        Serial.println(bufferKeypad);

        lcd.clear();
        lcd.noBlink();
        lcd.setCursor(0, 0);
        lcd.print("String input = ");
        lcd.setCursor(0, 1);
        lcd.print(bufferKeypad);
        delay(3000);

        resetInput();
        break;
    }
  }

  if ((padDitekan) && (padCounter < sizeof(bufferKeypad) - 1))
  {
    if (millisKeypad < millis())
    {
      *bufferKeypadPtr++ = padChar;
      keySebelumnya = key;
      charCounter = 0;
      padCounter++;
      padDitekan = false;

      lcd.setCursor(padCounter, 1);
      lcd.print(' ');
      lcd.setCursor(padCounter, 1);
    }
  }
}

void resetInput()
{
  bufferKeypadPtr = bufferKeypad;
  charCounter = 0;
  padCounter = 0;
  keySebelumnya = 0;
  padDitekan = false;

  lcd.clear();
  lcd.print("Masukkan string");
  lcd.setCursor(padCounter, 1);
  lcd.blink();
}

library entry teks melalui keypad berbasis arduino :
LiquidCrystal-I2C.zip
Keypad.zip

Komunikasi Modbus dengan arduino sebagai master (read input register tanpa library)

Modbus adalah sebuah protokol komunikasi antar perangkat. Modbus tergolong sebagai komunikasi serial dengan kelebihan mampu berkomunikasi dalam bus data (lalu lintas data),  jadi dalam sebuah jaringan modbus bisa menghubungkan lebih dari 2 alat (hingga 254 alat/perangkat). Contoh penggunaan protokol modbus adalah programmable logic controllers (PLCs) serta supervisory control and data acquisition (SCADA).

Dalam protokol modbus terdapat fungsi-fungsi diantaranya :

  • Coils (1 bit) baca/tulis
  • Discrete Inputs: (1 bit) hanya baca
  • Input Registers: (2 byte) hanya baca
  • Holding Registers: (2 byte) baca/tulis

Komunikasi antara perangkat dalam jaringan modbus digambarkan dalam diagram berikut :

RS485

rs485 adalah perangkat elektronika pengirim dan penerima data (serial) umumnya menggunakan dua kabel dengan karakteristik sinyal yang seimbang. rs485 juga mampu menghubungkan lebih dari 2 perangkat komunikasi (multipoint).

rs485 sering dimanfaatkan sebagai perangkat tambahan dalam komunikasi modbus.

Pembacaan modul PZEM modbus dengan arduino sebagai master

modul pzem dikenal sebagai modul pembaca tegangan, arus, daya, energi, frekuensi dari listrik. beberapa diantaranya mendukung protokol modbus. dalam contoh ini digunakan modul pzem 003 yaitu modul pengukuran dc.

skema komunikasi modbus berbasis arduino:

komponen yang digunakan :

  1. Arduino uno
  2. modul serial rs485

sketch/program arduino untuk pembacaan sensor pzem :

#define pinModBusTX         4

#include <SoftwareSerial.h>

SoftwareSerial pzem(2, 3); // RX, TX

byte perintah[] = {0x01, 0x04, 0x00, 0x00, 0x00, 0x08};//readInputRegisters
byte bufferDataModbus[100];
byte *ptr;

void setup() {
  pinMode(pinModBusTX, OUTPUT);

  Serial.begin(9600);
  Serial.println(F("Komunikasi Modbus dengan arduino sebagai master (read input register tanpa library)"));
  Serial.println(F("https://www.semesin.com/project"));
  Serial.println();

  pzem.begin(9600);
  ptr = bufferDataModbus;
}

void loop()
{
  uint16_t crc = calcCRC(perintah, sizeof(perintah));

  digitalWrite(pinModBusTX, HIGH);
  delay(1);
  pzem.write(perintah, sizeof(perintah));
  pzem.write(lowByte(crc));
  pzem.write(highByte(crc));
  delay(10);
  digitalWrite(pinModBusTX, LOW);

  long millisResponModbus = millis() + 1000;
  while (!pzem.available())
  {
    if (millisResponModbus < millis())
    {
      break;//timeout
    }
  }

  while (pzem.available())
  {
    byte b = pzem.read();
    *ptr++ = b;
    delay(2);
  }

  if (memcmp(bufferDataModbus, perintah, 2) == 0)
  {
    ptr = bufferDataModbus;

    float tegangan      = ((ptr[0 + 3] << 8) + ptr[1 + 3]) * 0.01;
    float arus          = ((ptr[2 + 3] << 8) + ptr[3 + 3]) * 0.01;
    float daya          = ((ptr[4 + 3] << 24) + (ptr[5 + 3] << 16) + (ptr[6 + 3] << 8) + ptr[7 + 3]) * 0.1;
    float energi        = ((ptr[8 + 3] << 24) + (ptr[9 + 3] << 16) + (ptr[10 + 3] << 8) + ptr[11 + 3]) * 0.1;
    int16_t alarmHigh  = ((ptr[12 + 3] * 256) + ptr[13 + 3]);
    int16_t alarmLow   = ((ptr[14 + 3] * 256) + ptr[15 + 3]);

    memset(bufferDataModbus, 0x00, sizeof(bufferDataModbus));

    Serial.println("==========");
    Serial.print("tegangan      = ");
    Serial.println(tegangan);
    Serial.print("arus          = ");
    Serial.println(arus);
    Serial.print("daya          = ");
    Serial.println(daya);
    Serial.print("energi        = ");
    Serial.println(energi);
    Serial.print("alarmHigh     = ");
    Serial.println(alarmHigh);
    Serial.print("alarmLow      = ");
    Serial.println(alarmLow);
  }

  Serial.println();
  delay(1000);
}

uint16_t calcCRC(byte *data, byte panjang)
{
  int i;
  uint16_t crc = 0xFFFF;
  for (byte p = 0; p < panjang; p++)
  {
    crc ^= data[p];
    for (i = 0; i < 8; ++i)
    {
      if (crc & 1)
        crc = (crc >> 1) ^ 0xA001;
      else
        crc = (crc >> 1);
    }
  }
  return crc;
}

 

Tombol cerdas cermat +sesi diskualifikasi menggunakan arduino

Cerdas cermat adalah pertandingan yang mengandalkan kecerdasan serta kecepatan. Untuk memberikan keadilan bagi seluruh group peserta, maka perangkat pendukung harus memiliki kriteria berikut :

  1. waktu scanning
    metode yang sering digunakan adalah interupsi dan scanning tombol, dalam hal penggunaan arduino uno (yang memiliki 2 external interrupt dan 23 pin change interrupt) masing-masing memiliki kelemahan dan kelebihan :

    • metode interruptpenggunaan external interrupt adalah metode yang paling baik, namun arduino uno hanya memiliki 2 external interrupt (hanya untuk 2 group) sehingga kurang efektif. Jika menggunakan 23 jalur external interrupt lebih banyak akan tetapi jenis interupsi ini di arduino uno terpisah dalam 3 kelompok, sehingga, seandainya 2 group cerdas cermat dalam satu kelompok interupsi menekan tombol (dalam waktu  scanning-nya) naka harus diambil salah satu group cerdas cermat saja, dan masalahnya pengambilan keputusan ini akan menjadi masalah keadilan bagi peserta.
    • metode scanning tombol
      metode ini bisa diistilahkan ‘tombol yang tepat di waktu yang tepat’ karena jika tombol cerdas cermat ditekan bersamaan, maka yang akan terpilih adalah tombol yang sedang di scanning. Namun sebetulnya waktu scanning ini sangatlah cepat, dan letak keadilannya ditentukan oleh ‘waktu’
  2.  interlock tombol
    • Ketika group yang paling cepat di sahkan, maka tombol group lain tidak berfungsi.
    • seluruh tombol group cerdas cermat hanya bisa di tekan pada saat yang ditentukan (misalkan setelah pertanyaan selesai dibacakan) jika mendahului waktu tersebut, maka tombolnya tidak berfungsi (diskualifikasi sesi pertanyaan) dengan cara menahan [tombol reset] dan melepas-nya ketika pertanyaan selesai di bacakan

blok diagram cerdas cermat menggunakan arduino:

 

skema mesin cerdas cermat berbasis arduino:

 

koding bel cerdas cermat berbasis arduino:

#define pinGroup1               2
#define pinGroup2               3
#define pinGroup3               4
#define pinGroup4               5

#define pinGroup1Indikator      A0
#define pinGroup2Indikator      A1
#define pinGroup3Indikator      A2
#define pinGroup4Indikator      A3

#define pinReset                8
#define pinBel                  9

#define relayAktif              LOW
#define jumlahGroup             4
#define waktuBel                3000

byte pinGroup[jumlahGroup] = {pinGroup1, pinGroup2, pinGroup3, pinGroup4};
byte pinGroupIndikator[jumlahGroup] = {pinGroup1Indikator, pinGroup2Indikator, pinGroup3Indikator, pinGroup4Indikator};

byte groupAktif;
byte groupScan;
byte tombolAktif;
long millisBel;
bool statusTombol;
bool statusSesi;
bool sesi[jumlahGroup];

// the setup routine runs once when you press reset:
void setup() {
  Serial.begin(9600);
  Serial.println(F("Tombol cerdas cermat berbasis arduino"));
  Serial.println(F("https://www.semesin.com/project"));
  Serial.println();

  for (int i = 0; i < sizeof(pinGroup); i++)
  {
    pinMode(pinGroup[i], INPUT_PULLUP);
    pinMode(pinGroupIndikator[i], OUTPUT);
  }

  pinMode(pinReset, INPUT_PULLUP);

  digitalWrite(pinBel, !relayAktif);
  pinMode(pinBel, OUTPUT);

  memset(sesi, 1, 4);
}

// the loop routine runs over and over again forever:
void loop() {

  groupScan = (groupScan + 1) % jumlahGroup;
  if (!digitalRead(pinGroup[groupScan]))
  {
    if (tombolAktif)
    {
      if (sesi[groupScan])
      {
        groupAktif = groupScan + 1;
        statusTombol = true;
        statusSesi = true;
      }
    }
    else if (!statusSesi)
    {
      if (sesi[groupScan])
      {
        sesi[groupScan] = false;
        Serial.print("Diskualifikasi : ");
        Serial.println(groupScan + 1);
      }
    }
  }

  if (!digitalRead(pinReset))
  {
    delay(50);

    if (!digitalRead(pinReset))
    {
      tombolAktif = false;
      if (groupAktif)
      {
        digitalWrite(pinGroupIndikator[groupAktif - 1], LOW);
        digitalWrite(pinBel, !relayAktif);
        groupAktif = 0;
        Serial.println("Reset");
        statusSesi = false;
        memset(sesi, 1, 4);
      }
    }
  }
  else if (groupAktif)
  {
    if (statusTombol)
    {
      Serial.print("Group : ");
      Serial.println(groupAktif);

      millisBel = millis() + waktuBel;
      digitalWrite(pinBel, relayAktif);

      digitalWrite(pinGroupIndikator[groupScan], HIGH);
      statusTombol = false;
      tombolAktif = false;
    }
  }
  else if (!tombolAktif)
  {
    Serial.println("Sesi mulai");

    tombolAktif = true;
  }

  if (millisBel < millis())
  {
    digitalWrite(pinBel, !relayAktif);
  }
}

 

Pewaktu otomatis dengan arduino (aplikasi pemberi pakan ikan)

Pewaktu otomatis adalah perangkat bekerja menggunakan real time clock (RTC) sebagai basis waktu dan menghasilkan output aksi tepat pada waktu yang ditentukan.

setting waktu alarm

program ini akan setiap detik menmbandingkan/mencek apakah waktu alarm sama sama dengan waktu saat ini, untuk menentukan waktu alarm dalam contoh ini menggunakan 2 waktu alarm dengan cara mengatur variabel pada bagian ini :


#define waktuMakan1 DateTime(0, 1, 1, 8, 0, 0, 0)//jam 8 pagi
#define waktuMakan2 DateTime(0, 1, 1, 17, 0, 0, 0)//jam 5 sore

kelas date time diisi nilai tahun, bulan, tanggal, jam, menit, detik, hari.
namun untuk pengplikasian di sketch pemberi makanan ikan ini hanya diset bagian jam, menit dan detik dengan tujuan nilai waktu ini berulang setiap hari.

Waktu makan ikan

Pada contoh ini perangkat akan menggerakkan servo pemberi makan ikan pada jam 8 pagi dan jam 5 sore.

skema pemberi makan ikan menggunakan arduino :

 

koding/program feeder ikan berbasis arduino:

#define pinServoMakanan               A0

#define waktuBukaServo                1000//milidetik
#define servoBuka                     20//derajat
#define servoTutup                    60//derajat

#define waktuMakan1                   DateTime(0, 1, 1,  8, 0, 0, 0)//jam 8 pagi
#define waktuMakan2                   DateTime(0, 1, 1, 17, 0, 0, 0)//jam 5 sore

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "Sodaq_DS3231.h"
#include <Servo.h>

LiquidCrystal_I2C lcd(0x3F, 16, 2);//coba juga 0x27
Servo servoMakanIkan;

byte detikSebelumnya;

void setup() {
  Serial.begin(9600);
  Serial.println("Pemberi pakan ikan otomatis");
  Serial.println("https://www.semesin.com/project");
  
  servoMakanIkan.attach(pinServoMakanan);
  servoMakanIkan.write(servoTutup);

  Wire.begin();
  rtc.begin();

  lcd.begin();
  lcd.backlight();

  lcd.print("Pemberi ");
  lcd.setCursor(0, 1);
  lcd.print("pakan ikan");
  delay(3000);
  lcd.clear();

  Serial.println("Sistem mulai");
}

void loop() {

  DateTime now = rtc.now();
  if (detikSebelumnya != now.second())
  {
    char buf[17];
    sprintf(buf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
    lcd.setCursor(4, 0);
    lcd.print(buf);

    detikSebelumnya = now.second();

    uint32_t epoch = now.get() % 86400;//hanya jam menit detik

    if ((epoch == waktuMakan1.get()) ||
        (epoch == waktuMakan2.get()))
    {
      char buf[17];
      sprintf(buf, "Pakan = %02d:%02d", now.hour(), now.minute());
      lcd.setCursor(0, 1);
      lcd.print(buf);

      servoMakanIkan.write(servoBuka);
      delay(waktuBukaServo);
      servoMakanIkan.write(servoTutup);

    }
  }
}

 

Library pemberi pakan ikan otomatis :

Input angka menggunakan keypad pada Arduino

Keypad untuk arduino terdiri atas numerik 0-9 serta ‘*’ dan ‘#’ berfungsi sebagai input bagi arduino. Arduino membaca keypad (type membrane) dengan metode scanning 7 kabel (untuk keypad 3×4), atau adc satu kabel. Dengan menggunakan library keypad, nilai input yang diterima arduino sudah berupa karakter ‘0’ – ‘9’, ‘*’ dan ‘#’, nilai ini bisa baca langsung sebagai perintah seperti contoh berikut ”

 
void loop(){
  char key = keypad.getKey();
  
  if (key){
    Serial.println(key);
    switch(key)
    {
      case '0':
        digitalWrite(2, HIGH);
        break;
      case '1':
        digitalWrite(3, HIGH);
        break;
      case '*'://reset
        digitalWrite(2, LOW);
        digitalWrite(3, LOW);
        break;
    }
  }
}

Input deret angka

Supaya keypad berfungsi sebagai input nilai angka (misal 0-1000) seperti untuk keperluan input variabel ‘setting batas sensor analog’, maka keypad dibaca beberapa kali dengan ketentuan:

  • karakter ‘0’ – ‘9’ sebagai input numerik
  • karakter ‘*’ berfungsi sebagai reset (kembali ke 0)
  • karakter ‘#’ berfungsi sebagai enter layaknya keybboard laptop dan menyimpan pembacaan keypad sebagai nilai variabel

berikut skema yang digunakan untuk pembacaan keypad dengan arduino:

koding input nilai variabel dari keypad :

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

const byte ROWS = 4;
const byte COLS = 3;
char keys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};
byte rowPins[ROWS] = {8, 7, 6, 5};
byte colPins[COLS] = {4, 3, 2};

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
LiquidCrystal_I2C lcd(0x3F, 16, 2);//coba juga 0x27

char stringAngka[17];
int indexKeypad = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("Input angka menggunakan keypad");
  Serial.println("https://www.semesin.com/project/");
  Serial.println();

  lcd.begin();
  lcd.backlight();
  lcd.print("Input angka");
}

void loop() {


  char key = keypad.getKey();

  if (key) {
    Serial.println(key);
    switch (key)
    {
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        if (!indexKeypad)
        {
          lcd.clear();
        }
        stringAngka[indexKeypad++] = key;
        lcd.print(key);
        break;
      case '*'://reset
        lcd.clear();
        indexKeypad = 0;
        break;
      case '#':
        stringAngka[indexKeypad] = 0;
        lcd.setCursor(0, 1);

        int nilaiAngka = atoi(stringAngka);
        lcd.print(nilaiAngka);

        indexKeypad = 0;
        break;
    }
  }
}

contoh penggunaan keypad sebagai masukan variabel integer:

Jpeg

Putar musik .wav dari kartu memori SDCard dengan arduino

jenis file suara menurut sistem kompres data-nya terdiri atas file suara terkompresi dan file suara tidak dikompresi (compressed/uncompresses), yaitu metode penyimpanan data suara digital yang bertujuan memperkecil ukuran file suara dan dengan penurunan kualitas suara sekecil-kecilnya.

File suara tidak dikompres memiliki keunggulan kualitas yang asli selain itu tidak memerlukan proses dekompresi yang rumit untuk mengambil/memutar-nya menjadi suara.

WAV (waveform audio file format) adalah contoh file suara yang tidak dikompres. karena masih menyimpan data aslinya jenis file ini memiliki ukuran yang besar. tidak seperti file suara terkompresi seperti .mp3, .aac, .ogg, .wma yang mmembutuhkan algoritma/codec untuk membuka datanya, .wav bisa langsung digunakan. sehingga .wav sangat cocok untuk perangkat mikrokontroller seperti arduino yang memiliki kecepatan dan memory yang kecil.

play .wav dengan arduino

file wav disimpan dan di ambil data-nya dengan metode PCM (pulse code modulation). file wav memiliki struktur header 44 byte yang berisi informasi jum;ah channel (mono/stereo), sample rate, bit per sampel dan informasi lainnya.

Khusus penggunaan arduino untuk memutar  file .wav dengan kecepatan 16MHz hanya efektif di sample rate 32.000, 16.000, 8.000 dengan kanal mono dan 8 bit per sampel.

Skema memutar file suara .wav menggunakan arduino dari microSD

Rangkaian speaker bukan stereo (tapi unbalanced audio connection)

(seandainya menggunakan ampli) jangan hubungkan ground arduino dan ground ampli jika keluaran suara ke speaker menggunakan 2 kabel pin 9 dan 10 (gunakan salah satu saja jika ground terhubung)

koding memainkan suara dari kartu memori berbasis arduino:


#define pinSpeakerA     9
#define pinSpeakerB     10

#define pinCS           8
#define faktorKali      2

#include <SD.h>
#include <SPI.h>


bool suaraDimainkan;
uint32_t sampleCounter;
byte ulangPerSampel;
byte ulang;

struct  HeaderWAV
{
  char                RIFF[4];
  unsigned long       ChunkSize;
  char                WAVE[4];
  char                fmt[4];
  unsigned long       Subchunk1Size;
  unsigned short      AudioFormat;
  unsigned short      NumOfChan;
  unsigned long       SamplesPerSec;
  unsigned long       bytesPerSec;
  unsigned short      blockAlign;
  unsigned short      bitsPerSample;
  char                Subchunk2ID[4];
  unsigned long       Subchunk2Size;

};

HeaderWAV headerWAV;
File fileSuara;


void setup(void)
{
  pinMode(pinSpeakerA, OUTPUT);
  pinMode(pinSpeakerB, OUTPUT);

  Serial.begin(9600);
  Serial.println("Memutar file suara .wav pada kartu memory SDCard dengan arduino");
  Serial.println("https://www.semesin.com/project/");

  if (!SD.begin(pinCS))
  {
    Serial.println("SD fail");
    return;
  }
}

void loop(void)
{
  if (!suaraDimainkan)
  {
    mainkanSuara("pulang.wav");
  }
  else
  {
    lanjutkanSuara();
  }
}

void mainkanSuara(char *namaFile)
{
  fileSuara = SD.open(namaFile);
  if ( !fileSuara )
  {
    Serial.println("File suara tidak ditemukan");
    return 0;
  }

  byte *alamat = (byte*)&headerWAV;
  for (byte i = 0; i < sizeof(headerWAV); i++)
  {
    byte data = fileSuara.read();
    *alamat++ = data;
  }
  Serial.print("namaFile=");
  Serial.println(namaFile);
  Serial.print("headerWAV.SamplesPerSec=");
  Serial.println(headerWAV.SamplesPerSec);
  Serial.print("headerWAV.NumOfChan=");
  Serial.println(headerWAV.NumOfChan);
  Serial.print("headerWAV.bitsPerSample=");
  Serial.println(headerWAV.bitsPerSample);
  Serial.print("headerWAV.Subchunk2Size=");
  Serial.println(headerWAV.Subchunk2Size);

  ulangPerSampel = 32000L / headerWAV.SamplesPerSec;
  ICR1 = 256;
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);

  sampleCounter = 0;
  ulang = 0;
  suaraDimainkan = true;
}

void lanjutkanSuara()
{
  if (TIFR1 & _BV(TOV1))
  {
    TIFR1 |= _BV(TOV1);
    if (!(ulang++ % ulangPerSampel))
    {
      if (sampleCounter++ >= headerWAV.Subchunk2Size)
      {
        Serial.println("Selesai");
        stopPlayback();
      }
      else
      {
        byte data = fileSuara.read();

        uint16_t sample = data;
        OCR1B = 256 - sample;
        OCR1A = sample;
      }
    }
  }
}


void stopPlayback()
{
  TIMSK1 &= ~_BV(OCIE1A);
  TCCR1B &= ~_BV(CS10);
  OCR1A = 128;
  OCR1B = 128;

  fileSuara.close();
  digitalWrite(pinSpeakerA, LOW);
  digitalWrite(pinSpeakerB, LOW);

  suaraDimainkan = false;
}



contoh file .wav mono 16kHz 8 bit:
pulang.wav

Enkripsi sederhana berbasis arduino (Improve XOR)

Sistem keamanan komunikasi arduino

Arduino memiliki port komunikasi bawaan seperti Serial, SPI, I2C dan di beberapa jenis arduino juga memiliki JTAG, USB, CAN, selain itu arduino juga dapat ditambahkan modul komunikasi seperti Bluetooth, BLE, WiFi, Ethernet, GPRS dan lainnya.

Masing-masing memiliki protokol, namun sebagian besar transfer data-nya dengan mudah diretas, misalnya komunikasi Serial: dengan menyadap jalur rx/tx dengan baud rate yang sesuai maka data-datanya sudah bisa di baca.

Enkripsi adalah metode menyembunyikan informasi melalui proses enkripsi dan hanya dapat dibaca setelah melalui proses decode dengan kunci yang tepat.

terdapat bermacam-macam metode enkripsi mulai dari metode lama seperti substitusi, transposisi hingga metode enkripsi modern seperti MD2, MD4, MD5, SHA, RC4, Base64. Metode-metode ini memiliki kelebihan dan kekurangan masing-masing.

Penerapan metode enkripsi pada aplikasi arduino dibatasi oleh kecepatan dan kapasitas memori arduino itu sendiri terutama jika transmisi data dalam jumlah besar.

Metode XOR

XOR adalah operasi logika yang memberikan hasil yang sama jika diterapkan dua kali sehingga cocok digunakan sebagai operasi enkripsi.

void setup() {
  Serial.begin(9600);
  Serial.println(F("Enkripsi sederhana berbasis arduino"));
  Serial.println(F("https://www.semesin.com/project"));
  Serial.println();

  char textBiasa[]      = "semesin.com";
  char kunci[]          = "kunci";
  byte chiperTeks[32];
  char teksDecode[32];
  
  //encode
  int i;
  for (i = 0; i < strlen(textBiasa); i++)
  {
    chiperTeks[i] = textBiasa[i] xor kunci[i % sizeof(kunci)];
  }
  chiperTeks[i] = 0;

  Serial.print("textBiasa = ");
  Serial.println(textBiasa);
  Serial.print("kunci = ");
  Serial.println(kunci);
  Serial.print("chiperTeks = ");
  Serial.println((char*)chiperTeks);

  Serial.print("chiperTeks (hex) = ");

  //decode
  for (i = 0; i < strlen(textBiasa); i++)
  {
    Serial.print(chiperTeks[i], HEX);
    Serial.print(' ');
  }
  Serial.println();

  for (i = 0; i < strlen(chiperTeks); i++)
  {
    teksDecode[i] = chiperTeks[i] xor kunci[i % sizeof(kunci)];
  }
  teksDecode[i] = 0;

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

  //hack
  char textHacker[] = {0, 0, 0, 0, 0, 0};

  for (i = 0; i < strlen(kunci); i++)
  {
    chiperTeks[i] = textHacker[i] xor kunci[i % sizeof(kunci)];
  }
  chiperTeks[i] = 0;

  Serial.print("Bobol kunci = ");
  Serial.println((char*)chiperTeks);

}

void loop() {


}

kelemahan metode sederhana ini adalah mudah dibobol dengan memasukkan plaintext/teks biasa dengan 0x00.

 

Metode XORS

metode ini tetap menggunakan logika xor sebagai operator utama dengan tambahan operator penambahan/pengurangan, pertimbangan utama metode ini adalah kecepatan dan penggunaan memory yang minimal.

Metode ini di gambarkan dalam skema berikut:

koding encode dan decode berbasis arduino:

char key[] = "www.Semesin.com/";

byte encode(byte dataEncode, byte index)
{
  return ((dataEncode xor key[index % 8]) + key[(index % 4) + 8]) xor key[(index % 4) + 12];
}
byte decode(byte dataDecode, byte index)
{
  return ((dataDecode xor key[(index % 4) + 12]) - key[(index % 4) + 8]) xor key[index % 8];
}

Bel sekolah dengan pengaturan melalui tombol

Bel sekolah merupakan pengingat waktu terjadwal yang menandai pergantian antar waktu pelajaran di sekolah. Ber sekolah juga memiliki penjawalan mingguan dan bulanan.

Perangkat bel sekolah otomatis bisa diaplikasikan menggunakan arduino sebagai unit prosesornya, tidak seperti bel sekolah digital berbasis komputer yang menyimpan jadwal dalam harddisk, bel sekolah digital arduino menyimpan data jadwal pelajaran didalam EEPROM. Data Jadwal disimpan dalam format/struktur berikut :

  • Aktif
  • Jam
  • Menit

Guna mengatur data jadwal tersebut, perangkat bel sekolah ini dilengkapi dengan tombol-tombol dengan fungsi [menu], [tambah], [kurang] dan [ok].

serta penggunaan LCD sebagai tampilan waktu sekarang dan tampilan jadwal apabila waktunya telah tepat.

skema bel sekolah dengan pengaturan melalui tombol/button:

komponen yang digunakan :

  1. Arduino uno
  2. RTC DS3231
  3. LCD i2C
  4. push button 4x

sketch/koding bel sekolah arduino :

#define pinTombolMenu       5
#define pinTombolTambah     4
#define pinTombolKurang     3
#define pinTombolOk         2

#include <EEPROM.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>
#include "RTC_Semesin.h"

LiquidCrystal_I2C lcd(0x3F, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display
RTC_DS3231 rtc;
char namaHari[][7] = {"Minggu", "Senin", "Selasa", " Rabu", "Kamis", "Jum'at", "Sabtu"};
char textMenuUtama[][17] = {
  "Bel masuk +pel 1",
  "Ganti pel 2     ",
  "Bel istirahat   ",
  "Bel masuk +pel 3",
  "Ganti pel 4     ",
  "Bel pulang      ",
  "Set Jam         ",
};
char textAktif[][6] = {"Mati ", "Aktif"};

char bufWaktu[40];

struct Jadwal
{
  bool aktif;
  byte jam;
  byte menit;
};

Jadwal jadwal[6] = {{0, 7, 0}, {0, 7, 0}, {0, 7, 0}, {0, 7, 0}, {0, 7, 0}, {0, 7, 0},} ;
Jadwal jadwalSet;

void setup()
{
  pinMode(pinTombolMenu, INPUT_PULLUP);
  pinMode(pinTombolTambah, INPUT_PULLUP);
  pinMode(pinTombolKurang, INPUT_PULLUP);
  pinMode(pinTombolOk, INPUT_PULLUP);

  Serial.begin (9600);
  Serial.println(F("Bel sekolah dengan pengaturan melalui tombol"));
  Serial.println(F("https://www.semesin.com/project"));

  if (! rtc.begin()) {
    Serial.println(F("Modul RTC tidak ditemukan"));
    while (1);
  }

  if (rtc.lostPower())
  {
    Serial.println(F("Waktu RTC di set ulang"));
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  lcd.begin();
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Nama");
  lcd.setCursor(0, 1);
  lcd.print("Judul");

  //  delay(3000);

  if (EEPROM.read(0) != 0x48)
  {
    simpanJadwal();
    EEPROM.write(0, 0x48);
  }
  else
  {
    ambilJadwal();
  }
  Serial.println("Bel sekolah dimulai");
}

byte detikSebelumnya = 60;
byte menitSebelumnya = 60;
byte menuLevel = 0;
byte menuIndex[2];
DateTime setWaktu;

void loop()
{
  DateTime now = rtc.now();
  if (detikSebelumnya != now.detik)
  {
    detikSebelumnya = now.detik;
    if (!menuLevel)
    {
      sprintf(bufWaktu, "Pukul : %02d:%02d:%02d", now.jam, now.menit, now.detik);
      lcd.setCursor(0, 0);
      lcd.print(bufWaktu);
      sprintf(bufWaktu, "%s, %02d/%02d/%02d", namaHari[now.hari - 1], now.tanggal, now.bulan, now.tahun - 2000);
      lcd.setCursor(0, 1);
      lcd.print(bufWaktu);

    }
    if (menitSebelumnya != now.menit)
    {
      menitSebelumnya = now.menit;
      for (byte i = 0; i < 6; i++)
      {
        if (jadwal[i].aktif)
        {
          if ((jadwal[i].jam == now.jam) && (jadwal[i].menit == now.menit))
          {
            lcd.setCursor(0, 0);
            lcd.print("  Bel Sekolah   ");
            lcd.setCursor(0, 1);
            lcd.print(textMenuUtama[i]);
            delay(10000);
          }
        }
      }
    }
  }

  if (!digitalRead(pinTombolMenu))
  {
    delay(100);
    if (!digitalRead(pinTombolMenu))
    {
      if (menuLevel == 0)
      {
        menuLevel = 1;
        menuIndex[0] = 0;
      }
      else
      {
        menuLevel = 0;
        lcd.noBlink();
      }
      tampilanMenu();
      while (!digitalRead(pinTombolMenu));
    }
  }
  if (!digitalRead(pinTombolTambah))
  {
    delay(100);
    if (!digitalRead(pinTombolTambah))
    {
      if (menuLevel == 1)
      {
        menuIndex[0]++;
        if (menuIndex[0] >= sizeof(textMenuUtama) / sizeof(textMenuUtama[0]))
        {
          menuIndex[0] = 0;
        }
        tampilanMenu();
      }
      if (menuLevel == 2)
      {
        if (menuIndex[0] == 6)
        {
          switch (menuIndex[1])
          {
            case 0:
              setWaktu.jam++;
              if (setWaktu.jam >= 24)
              {
                setWaktu.jam = 0;
              }
              break;
            case 1:
              setWaktu.menit++;
              if (setWaktu.menit >= 24)
              {
                setWaktu.menit = 0;
              }
              break;
            case 2:
              setWaktu.detik++;
              if (setWaktu.detik >= 60)
              {
                setWaktu.detik = 0;
              }
              break;
          }
        }
        else
        {
          switch (menuIndex[1])
          {
            case 0:
              jadwalSet.aktif = !jadwalSet.aktif;
              break;
            case 1:
              jadwalSet.jam++;
              if (jadwalSet.jam >= 24)
              {
                jadwalSet.jam = 0;
              }
              break;
            case 2:
              jadwalSet.menit++;
              if (jadwalSet.menit >= 60)
              {
                jadwalSet.menit = 0;
              }
              break;
          }
        }
        tampilanMenu();
      }
      delay(100);
    }
  }
  if (!digitalRead(pinTombolKurang))
  {
    delay(100);
    if (!digitalRead(pinTombolKurang))
    {
      if (menuLevel == 1)
      {
        if (menuIndex[0] == 0)
        {
          menuIndex[0] = sizeof(textMenuUtama) / sizeof(textMenuUtama[0]) - 1;
        }
        else
        {
          menuIndex[0]--;
        }
        tampilanMenu();
      }
      if (menuLevel == 2)
      {
        if (menuIndex[0] == 6)
        {
          switch (menuIndex[1])
          {
            case 0:
              if (setWaktu.jam == 0)
              {
                setWaktu.jam = 23;
              }
              else
              {
                setWaktu.jam--;
              }
              break;
            case 1:
              if (setWaktu.menit == 0)
              {
                setWaktu.menit = 59;
              }
              else
              {
                setWaktu.menit--;
              }
              break;
            case 2:
              if (setWaktu.detik == 0)
              {
                setWaktu.detik = 59;
              }
              else
              {
                setWaktu.detik--;
              }
              break;
          }
        }
        else
        {
          switch (menuIndex[1])
          {
            case 0:
              jadwalSet.aktif = !jadwalSet.aktif;
              break;
            case 1:
              if (jadwalSet.jam == 0)
              {
                jadwalSet.jam = 23;
              }
              else
              {
                jadwalSet.jam--;
              }
              break;
            case 2:
              if (jadwalSet.menit == 0)
              {
                jadwalSet.menit = 59;
              }
              else
              {
                jadwalSet.menit--;
              }
              break;
          }
        }
        tampilanMenu();
      }
      delay(100);
    }
  }
  if (!digitalRead(pinTombolOk))
  {
    delay(100);
    if (!digitalRead(pinTombolOk))
    {
      if (menuLevel == 1)
      {
        menuLevel = 2;
        menuIndex[1] = 0;
        if (menuIndex[0] == 6)
        {
          setWaktu = rtc.now();
          tampilanMenu();
          lcd.setCursor(4, 1);
          lcd.blink();
        }
        else
        {
          jadwalSet.aktif = jadwal[menuIndex[0]].aktif;
          jadwalSet.jam = jadwal[menuIndex[0]].jam;
          jadwalSet.menit = jadwal[menuIndex[0]].menit;
          tampilanMenu();
          lcd.setCursor(2, 1);
          lcd.blink();
        }
      }
      else if (menuLevel == 2)
      {
        menuIndex[1]++;
        if (menuIndex[0] == 6)
        {
          switch (menuIndex[1])
          {
            case 1:
              tampilanMenu();
              lcd.setCursor(7, 1);
              lcd.blink();
              break;
            case 2:
              tampilanMenu();
              lcd.setCursor(10, 1);
              lcd.blink();
              break;
            case 3:
              menuLevel = 1;
              rtc.adjust(setWaktu);
              tampilanMenu();
              lcd.noBlink();
              break;
          }
        }
        else
        {
          switch (menuIndex[1])
          {
            case 1:
              tampilanMenu();
              lcd.setCursor(8, 1);
              lcd.blink();
              break;
            case 2:
              tampilanMenu();
              lcd.setCursor(11, 1);
              lcd.blink();
              break;
            case 3:
              menuLevel = 1;
              jadwal[menuIndex[0]].aktif = jadwalSet.aktif;
              jadwal[menuIndex[0]].jam = jadwalSet.jam;
              jadwal[menuIndex[0]].menit = jadwalSet.menit;
              simpanJadwal();
              lcd.noBlink();
              tampilanMenu();
              break;
          }
        }
        while (!digitalRead(pinTombolOk));
        delay(500);
      }
    }
  }
}
void tampilanMenu()
{
  if (menuLevel == 1)
  {
    lcd.setCursor(0, 0);
    lcd.print("   Menu Utama   ");
    lcd.setCursor(0, 1);
    lcd.print(textMenuUtama[menuIndex[0]]);
  }
  else if (menuLevel == 2)
  {
    if (menuIndex[0] == 6)
    {
      lcd.setCursor(0, 0);
      lcd.print(textMenuUtama[menuIndex[0]]);
      sprintf(bufWaktu, "    %02d:%02d:%02d    ", setWaktu.jam, setWaktu.menit, setWaktu.detik);
      lcd.setCursor(0, 1);
      lcd.print(bufWaktu);
    }
    else
    {
      lcd.setCursor(0, 0);
      lcd.print(textMenuUtama[menuIndex[0]]);
      sprintf(bufWaktu, "  %s %02d:%02d   ", textAktif[jadwalSet.aktif], jadwalSet.jam, jadwalSet.menit);
      lcd.setCursor(0, 1);
      lcd.print(bufWaktu);
    }
  }
}

void ambilJadwal()
{
  byte *alamat = (byte*)jadwal;
  for (byte i = 1; i < sizeof(jadwal) + 1; i++)
  {
    *alamat++ = EEPROM.read(i);
  }
}
void simpanJadwal()
{
  byte *alamat = (byte*)jadwal;
  for (byte i = 1; i < sizeof(jadwal) + 1; i++)
  {
    EEPROM.update(i, *alamat++);
  }
}

library yang digunakan: