Dinamik menu dan submenu dengan keypad dan lcd 16×2 menggunakan Arduino

Dalam perancangan perangkat berbasis arduino yang mudah beradaptasi dengan kondisi baru haruslah memiliki sistem setingan variabel yang dinamis. Untuk keperluan setting variabel dinamis ini perangkat bisa dilengkapi panel potensio atau sistem menu yang bisa melakukan setting variabel interaktif. Membuat menu arduino bisa diterapkan secara dinamik dengan menggunakan database / tabel menu. Database menu dibuat fleksibel sehingga pengaturan variabel bisa dikelompokkan dalam sub menu.

Kelebihan database menu dinamis dengan arduino:

  1. Template menu arduino yang fleksibel, bisa ditambah/kurang/edit dengan mudah
  2. Sub-menu yang tak terbatas
  3. Akses alamat variabel yang dinamis sehingga mampu menjangkau ratusan variabel
  4. Tampilan skroll texk bagi item-menu
  5. Respon cepat
  6. Nilai variabel langsung bisa di potong dalam batas min dan max
  7. Menu interaktif arduino

Video submenu Arduino dinamis:

Membuat Menu arduino serta submenu-nya membutuhkan komponen :

  1. Arduino Mega
  2. Membrane keypad 3×4
  3. LCD 16×2/1602

skema arduino menu:

Sebelum digunakan sistem menu fleksibel ini dengan mudah dapat dikonfigurasi sesuai kebutuhan, terutama menu utama dan submenu-submenunya. Kolom text, tipe, alamat variabel, nilai minimal variabel, minai maksimum variabel, alamat submenu dan ukuran submenu harus dikonfigurasi dengan benar.

Struktur menu dibuat fleksibel sehingga bisa menukar urutan/pindah posisi dengan mudah, selain itu juga mudah untuk diedit, dihapus, disisip.

Sistem menu di buat dinamik sehingga cocok digunakan untuk semua perangkat. untuk membangun sebuah menu bisa mengisi item-item berikut:


//text                          tipe            variabel          nilaiMin nilaiMax submenu      jumlahBaris
const Menu menuUtama[] = 
{
  {"1.Aktif         "         , textDropDown  , &aktif            , false , true  , &aktifText   , 2 },   //"1.Aktif         ",
  {"2.Suhu /\xDF\x43      "   , UInt8         , &suhu             , 28    , 36    , 0            , 0 },   //"2.Suhu          ",celcius
  {"3.Kelembapan /% "         , UInt8         , &kelembapan       , 50    , 100   , 0            , 0 },   //"3.Kelembapan    ",%
  {"4.Int Cahaya /lm"         , UInt16        , &intensitasCahaya , 5     , 300   , 0            , 0 },   //"4.Inten. Cahaya ",lm
  {"5.Aliran udara  "         , UInt8         , &aliranUdara      , 0     , 5     , 0            , 0 },   //"5.Aliran udara  ",m/s
  {"6.Level suara/db"         , UInt16        , &levelSuara       , 30    , 120   , 0            , 0 },   //"6.Level suara   ",db tangisan
  {"7.Warna lampu   "         , textDropDown  , &warnaLampu       , 1     , 3     , &WarnaLampu  , 4 },   //"7.Warna lampu   ",
  {"8.Alarm        >"         , subMenu       , 0                 , 1     , 3     , &menuWaktu   , 3 },   //"7.Setting waktu  ",
};

penjelasan item menu:

  1. Text, yaitu teks yang akan ditampilkan oleh lcd
  2. Tipe, adalah jenis data/submenu antara lain : UInt8, UInt16, textDropDown, subMenu
  3. variabel, menyimpan alamat variabel yang akan disetting
  4. nilaiMin, merupakan nilai minimum variabel
  5. nilaiMax, merupakan nilai maksimum variabel
  6. Submenu, diisi jika item ini bertipe sub-menu yang diisi dengan alamat menu yang akan dijadikan submenu
  7. jumlahBaris merupakan jumlah baris dari submenu yang berkaitan

berikut koding/sketch lengkapnya:

#include <Keypad.h>
#include <LiquidCrystal.h>

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = {
  {'1','2','3'},
  {'4','5','6'},
  {'7','8','9'},
  {'*','0','#'}
};

//========================================================
//pin 
byte rowPins[ROWS] = {23, 25, 27, 29};
byte colPins[COLS] = {31, 33, 35};

LiquidCrystal lcd(53, 51, 49, 47, 45, 43);

//Variabel lcd dan menu
#define lebarTextLCD 16 + 1//lebar LCD + 1 null terminated
#define menuLevel 2
//========================================================

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

enum mode 
{
  UInt8,
  UInt16,
  textDropDown,
  subMenu
};

struct Menu
{
  char text[lebarTextLCD];
  byte tipe;
  void *variabel;
  uint16_t nilaiMin;
  uint16_t nilaiMax;
  void *subMenu;
  byte jumlahBaris;
};

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

//========================================================
//variabel
bool aktif;
byte suhu;
byte kelembapan;
uint16_t intensitasCahaya;
uint16_t aliranUdara;
uint16_t levelSuara;
uint16_t warnaLampu;

byte alarmJam;
byte alarmMenit;
byte alarmDetik;

//Dropdown menu
const char aktifText[][lebarTextLCD]  = 
{
  "0. Tidak        ",
  "1. Ya           ",
};
const char WarnaLampu[][lebarTextLCD]  = 
{
  "1. Mati         ",
  "2. Dingin       ",
  "3. Putih        ",
  "4. Hangat       ",
};

//Sub menu
//text                          tipe            variabel          nilaiMin nilaiMax submenu      jumlahBaris
const Menu menuWaktu[] = 
{
  {"1. Jam          "         , UInt8         , &alarmJam         , 1     , 24    , 0            , 0 },
  {"2. Menit        "         , UInt8         , &alarmMenit       , 0     , 59    , 0            , 0 },
  {"3. Detik        "         , UInt8         , &alarmDetik       , 0     , 59    , 0            , 0 },
};

//Menu utama
const Menu menuUtama[] = 
{
  {"1.Aktif         "         , textDropDown  , &aktif            , false , true  , &aktifText   , 2 },   //"1.Aktif         ",
  {"2.Suhu /\xDF\x43      "   , UInt8         , &suhu             , 28    , 36    , 0            , 0 },   //"2.Suhu          ",celcius
  {"3.Kelembapan /% "         , UInt8         , &kelembapan       , 50    , 100   , 0            , 0 },   //"3.Kelembapan    ",%
  {"4.Int Cahaya /lm"         , UInt16        , &intensitasCahaya , 5     , 300   , 0            , 0 },   //"4.Inten. Cahaya ",lm
  {"5.Aliran udara  "         , UInt8         , &aliranUdara      , 0     , 5     , 0            , 0 },   //"5.Aliran udara  ",m/s
  {"6.Level suara/db"         , UInt16        , &levelSuara       , 30    , 120   , 0            , 0 },   //"6.Level suara   ",db tangisan
  {"7.Warna lampu   "         , textDropDown  , &warnaLampu       , 1     , 3     , &WarnaLampu  , 4 },   //"7.Warna lampu   ",
  {"8.Alarm        >"         , subMenu       , 0                 , 1     , 3     , &menuWaktu   , 3 },   //"7.Setting waktu  ",
};
//========================================================

MenuIndex menuIndex[menuLevel];

long millismenuText;
String menuEntriNilai;
int8_t levelMenu = -1;
bool entriNilai;
byte menuTextIndex;
char *judulMenu;
byte judulMenuTampil;
byte lcdEntriPos;


void setup() 
{
  Serial.begin(9600);
  Serial.println("Dinamik menu tak terbatas sub-menu dengan keypad 3x4 dan arduino");
  Serial.println("(Aplikasi Inkubator)");
  Serial.println("entri/kirim 'h' untuk melihat nilai variabel");
  Serial.println("https://www.project.semesin.com/");

  lcd.begin(16, 2);
  menuIdle();

  millismenuText = millis();
}

void menuIdle()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Tekan * untuk");
  lcd.setCursor(0, 1);
  lcd.print("masuk ke menu");
}

void loop() 
{
  cekMenu();
  
  if(Serial.available())
  {
    if(toupper(Serial.read()) == 'H')
    {
      Serial.println("--------------------------");
      Serial.print("aktif = ");
      Serial.println(aktif);
      Serial.print("suhu = ");
      Serial.println(suhu);
      Serial.print("kelembapan = ");
      Serial.println(kelembapan);
      Serial.print("intensitasCahaya = ");
      Serial.println(intensitasCahaya);
      Serial.print("aliranUdara = ");
      Serial.println(aliranUdara);
      Serial.print("levelSuara = ");
      Serial.println(levelSuara);
      Serial.print("warnaLampu = ");
      Serial.println(warnaLampu);

      Serial.print("alarmJam = ");
      Serial.println(alarmJam);
      Serial.print("alarmMenit = ");
      Serial.println(alarmMenit);
      Serial.print("alarmDetik = ");
      Serial.println(alarmDetik);
      Serial.println("--------------------------");
      Serial.println();
    }
  }
}

void cekMenu()
{
  char key = keypad.getKey();
  if (key)
  {
    if(key == '*')
    {
      if(entriNilai)
      {
        uint16_t nilaiBaru = menuEntriNilai.toInt();

        if((nilaiBaru >= menuIndex[levelMenu].menu[menuIndex[levelMenu].index].nilaiMin) && (nilaiBaru <= menuIndex[levelMenu].menu[menuIndex[levelMenu].index].nilaiMax))
        {
          switch(menuIndex[levelMenu].menu[menuIndex[levelMenu].index].tipe)
          {
            case UInt8:
              *(uint8_t*)menuIndex[levelMenu].menu[menuIndex[levelMenu].index].variabel = nilaiBaru;
              break;
            case UInt16:
              *(uint16_t*)menuIndex[levelMenu].menu[menuIndex[levelMenu].index].variabel = nilaiBaru;
              break;
            case textDropDown:
              menuIndex[levelMenu].dropDownLength = 0;
              *(byte*)menuIndex[levelMenu].menu[menuIndex[levelMenu].index].variabel = nilaiBaru;
              menuTextIndex = 0;
              break;
          }
          lcd.clear();
          lcd.print(judulMenu);
          lcd.setCursor(0, 1);
          lcd.print(nilaiBaru);
          lcd.print(" disimpan");
          delay(1000);
        }
        entriNilai = false;
      }
      else if(levelMenu == -1)
      {
        levelMenu = 0;
      }
      else
      {
        levelMenu--;
      }
      displayMenu();
    }
    else if(key == '#')
    {
      if(entriNilai)
      {
        entriNilai = false;
        displayMenu();
      }
      else if(levelMenu >= 0)
      {
        levelMenu--;
        displayMenu();
      }
    }
    else if((key >= '0') || (key <= '9') )
    {
      if(entriNilai)
      {
          menuEntriNilai += key;
          lcd.setCursor(lcdEntriPos++, 1);
          lcd.print(key);
      }
      else if((key != '0') && (key - '0' <= menuIndex[levelMenu].menuLength))//pilihan menu
      {
        menuIndex[levelMenu].index = key - '1';
        judulMenu = menuIndex[levelMenu].menu[menuIndex[levelMenu].index].text;
        lcd.clear();
        lcd.print(judulMenu);
        lcd.setCursor(0, 1);
        lcdEntriPos = 8;
        judulMenuTampil = 0;

        uint16_t nilaiUInt8;
        uint16_t nilaiUInt16;
        uint16_t nilaiUInt32;
        int16_t nilaiFloat;

        menuEntriNilai = "";
        entriNilai = true;

        switch(menuIndex[levelMenu].menu[menuIndex[levelMenu].index].tipe)
        {
          case UInt8:
            lcd.print(*(byte*)menuIndex[levelMenu].menu[menuIndex[levelMenu].index].variabel);
            lcd.print(" ==> ");
            break;
          case UInt16:
            lcd.print(*(uint16_t*)menuIndex[levelMenu].menu[menuIndex[levelMenu].index].variabel);
            lcd.print(" ==> ");
            break;
          case textDropDown:
            menuIndex[levelMenu].dropDownLength = menuIndex[levelMenu].menu[menuIndex[levelMenu].index].jumlahBaris;
            menuIndex[levelMenu].dropDown = menuIndex[levelMenu].menu[menuIndex[levelMenu].index].subMenu;
            menuTextIndex = 0;
            lcd.print(*(uint8_t*)menuIndex[levelMenu].menu[menuIndex[levelMenu].index].variabel);
            lcd.print(" ==> ");
            delay(1000);
            break;
          case subMenu:
            levelMenu++;
            menuIndex[levelMenu].index = 0;
            menuIndex[levelMenu].menu = menuIndex[levelMenu-1].menu[menuIndex[levelMenu-1].index].subMenu;
            menuIndex[levelMenu].menuLength = menuIndex[levelMenu-1].menu[menuIndex[levelMenu-1].index].jumlahBaris;
            menuIndex[levelMenu].dropDownLength = 0;
            menuTextIndex = 0;
            entriNilai = false;
            break;
        }
      }
    }
  }
  if(millis() - millismenuText > 1000)
  {
    millismenuText = millis();
    
    if(menuIndex[levelMenu].dropDownLength != 0)
    {
      if(menuTextIndex >= menuIndex[levelMenu].dropDownLength)
      {
        menuTextIndex = 0;
      }
      lcd.setCursor(0, 1);
      lcd.print(menuIndex[levelMenu].dropDown + (menuTextIndex++ * (lebarTextLCD)));
    }
    if(entriNilai)
    {
      lcd.setCursor(0, 0);
      if((judulMenuTampil % 3) == 0)
      {
        lcd.print(judulMenu);
      }
      else if((judulMenuTampil % 3) == 1)
      {
        lcd.print("* untuk simpan  ");
      }
      else
      {
        lcd.print("# untuk batal   ");
      }
      judulMenuTampil++;
    }
    else if(levelMenu != -1)  
    {
      if(menuTextIndex >= menuIndex[levelMenu].menuLength)
      {
        menuTextIndex = 0;
      }
      lcd.setCursor(0, 1);
      lcd.print(menuIndex[levelMenu].menu[menuTextIndex++].text);
    }
  }
}

void displayMenu()
{
  if(levelMenu == -1)
  {
    menuIdle();
    menuIndex[levelMenu].dropDownLength = 0;
  }
  else if(levelMenu == 0)
  {
    menuIndex[levelMenu].index = 0;
    menuIndex[levelMenu].menu = menuUtama;
    menuIndex[levelMenu].menuLength = sizeof(menuUtama)/sizeof(menuUtama[0]);
    menuIndex[levelMenu].dropDownLength = 0;
    menuTextIndex = 0;

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Pilih [1..");
    lcd.print(sizeof(menuUtama)/sizeof(menuUtama[0]));
    lcd.print("]");
  }
  else
  {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(menuIndex[levelMenu-1].menu[menuIndex[levelMenu-1].index].text);
    menuIndex[levelMenu].dropDownLength = 0;
  }
}

Library yang digunakan dalam sketch Arduino menu:

  1. Keypad.zip

Pengujian menu dinamik Arduino:

  1. tekan ‘*’ untuk masuk ke menu
  2. tekan ‘*’ pada saat menu aktif berfungsi untuk simpan dan kembali
  3. tekan ‘#’ untuk kembali ke level menu diatasnya
  4. Jika entri nilai valid akan muncul pesan ‘disimpan’

Timbangan beras otomatis dengan arduino

Timbangan beras digital ini merupakan timbangan beras pintar yang akan memberitahukan kapasitas beras tersisa dalam silo/tampungan beras ke apk Android melalui bluetooth sehingga pengguna dapat mengatur ketersiaan stok beras dengan mudah. Disamping itu timbangan beras berbasis arduino ini juga akan mengingatkan pengguna melalui suara apabila beras hampir habis.

Memiliki dua timbangan yaitu timbangan beras di silo dan timbangan beras yang akan dikeluarkan melalui slot beras. Beras yang ingin diambil dapat dipilih melalui panel keypad 1.000gr hingga 10.000 gram. Jika slot beras telah terisi sesuai berat yang diminta perangkat akan mengeluarkan peringatan melalui suara.

album : Galeri timbangan beras arduino android

Pengunci nilai batas (Bandgap interlock) pada arduino

Sistem interlock pada pemograman digunakan untuk mengunci suatu blok program agar dijalankan hanya satu kali. Penguncian dapat dilakukan dengan flag/bendera, jadi sebelum masuk suatu blok program nilai flag penguncinya diuji dan hanya bisa dilewati jika nilainya cocok. pada arduino algoritma flag arduino (pengunci nilai arduino / interlock arduino) biasanya dideklarasikan sebagai boolean/bool yang bernilai true dan false.

Contoh sketch berikut ini menggunakan sensor analog (bisa dikembangkan untuk sensor dengan basis komunikasi) sebagai input. Sedangkan alarm/buzzer berlaku sebagai output. Tombol berfungsi sebagai konfirmasi bahwa alarm telah disetujui.

Komponen yang digunakan :

  1. Arduino uno
  2. Sensor analog (potensio, LM35, water level, flex, dll)
  3. Buzzer
  4. Tombol push on

Skema pengujian sistem kunci arduino:

Sketch / koding contoh penggunaan flag pada arduino:
Fungsi utama:

  1. Alarm hanya hidup jika nilai diluar batas dan belum dikonfirmasi
  2. Alarm mati jika tombol konfirmasi sudah ditekan
  3. Sistem alarm dapat kembali berfungsi jika nilai sudah dalam batas
//Pin
#define pinSensor           A0
#define pinTombolKonfirmasi 2
#define pinAlarm            13

#define batasAtas 600
#define batasBawah 400

bool sistemNormal = true;

void setup() {
  pinMode(pinTombolKonfirmasi, INPUT_PULLUP);
  pinMode(pinAlarm, OUTPUT);

  Serial.begin(9600);
  Serial.println("Alarm pengunci nilai batas (Bandgap interlock) pada arduino");
  Serial.println("https://www.project.semesin.com/");
}

void loop() {
  int nilaiSensor = analogRead(pinSensor);
  Serial.print("Nilai pembacaan sensor : ");
  Serial.println(nilaiSensor);

  if((nilaiSensor <= batasBawah) || (nilaiSensor >= batasAtas))//Pembacaan diluar batas
  {
    if(sistemNormal)//alarm belum dikonfirmasi
    {
      Serial.println("Pembacaan sensor diluar batas.");
      digitalWrite(pinAlarm, HIGH);
      sistemNormal = false;
    }
  }
  else //Pembacaan dalam batas
  {
    sistemNormal = true;
  }

  if(!digitalRead(pinTombolKonfirmasi))
  {
    Serial.println("Alarm dikonfirmasi.");
    digitalWrite(pinAlarm, LOW);
  }

  delay(1000);
}

dalam bentuk lain:

//Pin
#define pinSensor           A0
#define pinTombolKonfirmasi 2
#define pinAlarm            13

#define batasAtas 600
#define batasBawah 400

bool Alarm;
bool telahKonfirmasi;
bool Konfirmasi;

void setup() {
  pinMode(pinTombolKonfirmasi, INPUT_PULLUP);
  pinMode(pinAlarm, OUTPUT);

  Serial.begin(9600);
  Serial.println("Alarm pengunci nilai batas (Bandgap interlock) pada arduino");
  Serial.println("https://www.project.semesin.com/");
}

void loop() {

  int nilaiSensor = analogRead(pinSensor);
  Serial.print("Nilai pembacaan sensor : ");
  Serial.println(nilaiSensor);

  bool sistemNormal = ((nilaiSensor >= batasBawah) && (nilaiSensor <= batasAtas));
  if(!telahKonfirmasi)
  {
    Konfirmasi = !digitalRead(pinTombolKonfirmasi);
  }

  if(sistemNormal && (!Alarm || (Alarm && Konfirmasi)))
  {
    Alarm = false;
    telahKonfirmasi = false;
    Serial.println("Status : Sistem normal");
  }
  else if(sistemNormal && Alarm)
  {
    Serial.println("Status : Sistem normal, belum dikonfirmasi");
  }
  else if(!sistemNormal && Konfirmasi)
  {
    Alarm = false;
    telahKonfirmasi = true;
    Serial.println("Status : Sistem tidak normal, telah dikonfirmasi");
  }
  else if(!sistemNormal && !telahKonfirmasi)
  {
    Alarm = true;
    Serial.println("Status : Sistem tidak normal");
  }
  digitalWrite(pinAlarm, Alarm);

  delay(1000);
}

Pengukuran panjang objek/benda bergerak dengan arduino

Prinsip kerja mengukur panjang benda bergerak  adalah dengan menghitung kecepatan benda tersebut kemudian dikalikan dengan waktu benda tersebut melewati sebuah titik.

Persamaan mencari panjang benda bergerak:
Panjang benda (m) = Kecepatan (m/s) x waktu lewat (dtk)

Mencari kecepatan benda menggunakan rumus:

Kecepatan (m/s) = jarak (m) / waktu tempuh (dtk)

Pengukuran waktu tempuh dimulai saat objek menyentuh sensor 1 dan berakhir saat objek menyentuh sensor 2. Dengan jarak sensor 1 dan 2 yang tetap, maka pembagian jarak sensor dengan waktu tempuh akan menghasilkan kecepatan.

Mengukur kecepatan benda dengan arduino membutuhkan respon/penanganan yang cepat. Untuk itu pengukuran kecepatan benda dengan arduino digunakan timer1 16-bit dengan prescale 1 (presisi maksimum 1/16juta detik).

Sedangkan guna mengukur panjang benda dengan arduino dibutuhkan data waktu benda melintasi satu titik. Waktu tempuh benda dihitung saat benda menyentuh sensor dan selesai saat benda meninggalkan sensor. Perkalian antara waktu ini dengan kecepatan benda akan diperoleh panjang benda tersebut.

Komponen yang digunakan untuk menentukan kecepatan objek dengan arduino:

  1. Arduino Uno
  2. Photo dioda 2 buah jarak 5 cm.
  3. IR LED 2 buah didepan photo dioda
  4. Resistor 100 ohm 2 buah

skema mengukur panjang benda dengan arduino:

sketch/program cara mencari panjang benda bergerak dengan arduino :

#define sensor1 2
#define sensor2 3
#define jarakSensor 0.05 //meter
#define waktuTick 1/8000000 //detik
#define mpsKekmph 3.6
 
volatile uint16_t waktuA;
volatile uint16_t waktuB;
volatile uint16_t overFlowA;
volatile uint16_t overFlowB;
volatile uint16_t panjangA;
volatile uint16_t panjangB;
volatile uint16_t overflowTimer;
volatile bool dariA;
volatile bool dariB;
volatile bool sampaiA;
volatile bool sampaiB;
volatile bool lewatA;
volatile bool lewatB;

uint8_t maskSensor1; 
uint8_t maskSensor2; 
uint8_t *pinSensor1; 
uint8_t *pinSensor2; 
 
void setup() {
  Serial.begin(9600);
  Serial.println("Pengukuran panjang objek bergerak dengan Arduino");
  Serial.println("dan photo dioda menggunakan metode interupsi serta timer");
  Serial.println("https://www.project.semesin.com");
 
  pinMode(sensor1, INPUT_PULLUP);
  pinMode(sensor2, INPUT_PULLUP);

  maskSensor1  = digitalPinToBitMask(sensor1);
  pinSensor1 = portInputRegister(digitalPinToPort(sensor1));
  maskSensor2  = digitalPinToBitMask(sensor2);
  pinSensor2 = portInputRegister(digitalPinToPort(sensor2));

  EIFR |= _BV(INTF1) |_BV(INTF0);
  TIMSK1 |= _BV(TOIE1);
  TCCR1A = 0;

  attachInterrupt(digitalPinToInterrupt(sensor1), objekMelewatiA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(sensor2), objekMelewatiB, CHANGE);
}
 
void loop() {
  double waktuSampai;
  double waktuLewat;
  double Kecepatan;
  double Panjang;
  if(dariA && sampaiB && lewatB)
  {
    waktuSampai = 1.0 * (double)waktuTick * ((uint32_t)waktuB + (overFlowB * 65535));
    waktuLewat = 1.0 * (double)waktuTick * ((uint32_t)panjangB + (overflowTimer * 65535));
    Kecepatan = 1.0 * jarakSensor / waktuSampai;
    Panjang = 1.0 * (waktuLewat - waktuSampai) * Kecepatan;
    
    Serial.print("Panjang = ");
    printDouble(Panjang, 6);
    Serial.println(" meter");
    Serial.print("Kecepatan = ");
    printDouble(Kecepatan, 6);
    Serial.print(" meter/detik (");
    printDouble(Kecepatan * mpsKekmph, 6);
    Serial.println(" km/jam) dari B ke A");
    dariA = false;  
    sampaiB = false;  
    lewatB = false;
  }
  else if(dariB && sampaiA && lewatA)
  {
    waktuSampai = 1.0 * (double)waktuTick * ((uint32_t)waktuA + (overFlowA * 65535));
    waktuLewat = 1.0 * (double)waktuTick * ((uint32_t)panjangA + (overflowTimer * 65535));
    Kecepatan = 1.0 * jarakSensor / waktuSampai;
    Panjang = 1.0 * (waktuLewat - waktuSampai) * Kecepatan;
    
    Serial.print("Panjang = ");
    printDouble(Panjang, 6);
    Serial.println(" meter");
    Serial.print("Kecepatan = ");
    printDouble(Kecepatan, 6);
    Serial.print(" meter/detik (");
    printDouble(Kecepatan * mpsKekmph, 6);
    Serial.println(" km/jam) dari A ke B");
    dariB = false;  
    sampaiA = false;  
    lewatA = false;
  }
  else if(dariA || dariB)
  {
    if(overflowTimer > 5000)
    {
      dariA = false;
      dariB = false;
      TCCR1B = 0;
      Serial.println("Terlalu lambat");
    }
  }
}
 
void objekMelewatiA()
{
  if(*pinSensor1 & maskSensor1)
  {
    if(dariB)
    {
      waktuA = TCNT1;
      overFlowA = overflowTimer;
      sampaiA = true;
    }
    else
    {
      TCNT1 = 0;
      TCCR1B = _BV(CS10);
      TIFR1 |= _BV(TOV1);
      overflowTimer = 0;
      dariA = true;
    }
  }
  else if(dariB && sampaiA)
  {
    panjangA = TCNT1;
    TCCR1B = 0;
    lewatA = true;
  }
  EIFR |= _BV(INTF0);
}
void objekMelewatiB()
{
  if(*pinSensor2 & maskSensor2)
  {
    if(dariA)
    {
      waktuB = TCNT1;
      overFlowB = overflowTimer;
      sampaiB = true;
    }
    else
    {
      TCNT1 = 0;
      TCCR1B = _BV(CS10);
      TIFR1 |= _BV(TOV1);
      overflowTimer = 0;
      dariB = true;
    }
  }
  else if(dariA && sampaiB)
  {
    panjangB = TCNT1;
    TCCR1B = 0;
    lewatB = true;
  }
  EIFR |= _BV(INTF1);
}
 
ISR(TIMER1_OVF_vect)
{
  overflowTimer++;
}
 
void printDouble(double nilai, byte belakangKoma)
{
  if(nilai < 0.0)
  {
    Serial.print('-');
    nilai = -nilai;
  }
   
  Serial.print ((long)nilai);
  if( belakangKoma > 0) 
  {
    Serial.print(".");
    unsigned long frac;
    unsigned long mult = 1;
    byte padding = belakangKoma - 1;
    while(belakangKoma--)
    {
      mult *=10;
    }
    if(nilai >= 0)
    {
      frac = (nilai - int(nilai)) * mult;
    }
    else
    {
      frac = (int(nilai)- nilai ) * mult;
    }
    unsigned long frac1 = frac;
    while( frac1 /= 10 )
    {
      padding--;
    }
    while(  padding--)
    {
      Serial.print("0");
    }
    Serial.print(frac,DEC) ;
 }
}

SMS gateway pengontrol Arduino dengan SIM800L

[CEK IMEI di kemenperin sebelum mencoba]

 

Library SIM800L ini berbasis AT command dengan improvisasi koding sehingga akses lebih cepat dan stabil.
Download library untuk arduino SIM800L anti lelet update GSM library termasuk sim800l

SMS gateway adalah perangkat penerima dan pengirim SMS. dengan bantuan Arduino dan modul SIM 800, SMS gateway dapat difungsikan sebagai pengontrol bagi perangkat lain.

Kelebihan :

  • Template/format database yang mudah digunakan
  • Mampu mengontrol seluruh pin arduino karena menggunakan kompigurasi metode tabel/database
  • Cepat tanpa delay berlebihan
  • Responsif terhadap SMS baru yang masuk
  • Menyimpan database dalam flash sehingga menghemat memory
  • Pada arduino mega, sistem ini mampu mengontrol hingga 57 kanal/perangkat
  • Case insensitive, Perintah SMS tidak mempedulikan huruf besar/kecil

Fungsi-fungsi SMS server yang diterapkan antara lain :

  • Menghidupkan perangkat
  • Mematikan perangkat
  • Membaca masukan digital
  • Membaca masukan analog

Fungsi ini bisa dikembangkan sebagai pembaca dan pengontrol perangkat dengan komunikasi digital seperti Serial, I2C/TWI, One wire, SPI dan lainnya.

pastikan modul SIM800L mendapatkan Tegangan 3.6-4.2 Volt yang stabil dengan Arus 2A yang memadai. Juga gunakan power supply external jika sumber tegangan dari USB(PC/Laptop) saat memprogram dirasa kurang

Komponen sistem kendali SMS melalui arduino:

  • Arduino Mega 2560
  • Modul SIM800L
  • LM2596
  • Modul/perangkat lain yang ingin dikontrol

skema SMS Gateway pengontrol Arduino menggunakan modul SIM 800L:

Perangkat yang akan dikontrol bisa bermacam-macam seperti LED, relay, sensor asap, solenoid dan lain-lain.

Pada perancangan SMS gateway menggunakan SIM800L ini menerapkan struktur database sehingga pengaturan isi SMS perintah dan pin yang dikontrol lebih mudah dan yang lebih penting program dapat berjalan efektif.
Item yang perlu dikonfigurasi adalah :

  • isiPerintah, yaitu isi pesan/perintah yang akan dikirimkan melalui SMS
  • pinKeluaran, nomor pin arduino yang dikontrol/dibaca apabila SMS IsiPerintah diterima
  • mode, terdiri dari mode_TulisNilai, mode_TulisAnalog, mode_Pemicu, mode_InputDigital dan mode_InputAnalog
  • nilaiPin, adalah nilai HIGH/LOW yang akan diterapkan ke pinKeluaran apabila SMS IsiPerintah diterima

Untuk menghidupkan/aktif dan mematikan/nonaktif sebuah pin digital, masukkan pinKeluaran yang sama dengan nilaiPin yang berbeda sesuai karakteristik modul/perangkat yang akan dikontrol.

untuk modul/perangkat sensor bisa diambil datanya dengan mengkonfigurasinya sebagai mode_InputDigital dan mode_InputAnalog, apabila ada permintaan melalui SMS sesuai text isiPerintah, maka sistem arduino akan mengirimkan datanya melalui SMS.

sketch / program SMS gateway sim800 (Gunakan library dari semesin.com –link diatas–):

#include <sim800l.h>
#include <SoftwareSerial.h>
#include <avr/pgmspace.h>

#define pinSIM800RX 2
#define pinSIM800TX 3
#define pinSIM800Reset 4

struct Perintah
{
  char isiPerintah[33];
  byte pinKeluaran;
  byte mode;
  byte nilaiPin;
};

enum mode
{
  mode_TulisNilai,
  mode_TulisAnalog,
  mode_Pemicu,
  mode_InputDigital,
  mode_InputAnalog
};

const PROGMEM Perintah perintah[] = {
//Isi SMS perintah (32 karakter)     pin  nilai pin
{"Hidupkan lampu 1\0               ", 22, mode_TulisNilai,   HIGH },
{"Matikan lampu 1\0                ", 22, mode_TulisNilai,   LOW  },//pin yang sama
{"Hidupkan lampu 2\0               ", 23, mode_TulisNilai,   HIGH },
{"Matikan lampu 2\0                ", 23, mode_TulisNilai,   LOW  },
{"Hidupkan kipas\0                 ", 24, mode_TulisNilai,   HIGH },
{"Matikan kipas\0                  ", 24, mode_TulisNilai,   LOW  },
{"Hidupkan dispenser\0             ", 25, mode_TulisNilai,   HIGH },
{"Matikan dispenser\0              ", 25, mode_TulisNilai,   LOW  },
{"Buka pintu\0                     ", 26, mode_TulisNilai,   HIGH },
{"Kunci pintu\0                    ", 26, mode_TulisNilai,   LOW  },
{"Hidupkan relay 1\0               ", 27, mode_TulisNilai,   HIGH },
{"Matikan relay 1\0                ", 27, mode_TulisNilai,   LOW  },
{"Hidupkan relay 2\0               ", 28, mode_TulisNilai,   HIGH },
{"Matikan relay 2\0                ", 28, mode_TulisNilai,   LOW  },
{"Hidupkan relay 3\0               ", 29, mode_TulisNilai,   HIGH },
{"Matikan relay 3\0                ", 29, mode_TulisNilai,   LOW  },
{"Hidupkan relay 4\0               ", 30, mode_TulisNilai,   HIGH },
{"Matikan relay 4\0                ", 30, mode_TulisNilai,   LOW  },
{"Hidupkan motor\0                 ", 31, mode_TulisAnalog,  175  },
{"Matikan motor\0                  ", 31, mode_TulisAnalog,  0    },
{"\0                               ", 32, mode_TulisNilai,   LOW  },
{"Beri makan ikan\0                ", 33, mode_Pemicu,       HIGH },//pin bersifat trigger
{"Baca temperatur\0                ", 34, mode_InputDigital, LOW  },//digitalRead()
{"\0                               ", 35, mode_InputDigital, LOW  },
{"\0                               ", 36, mode_InputDigital, LOW  },
{"\0                               ", 37, mode_InputDigital, LOW  },
{"\0                               ", 38, mode_InputDigital, LOW  },
{"\0                               ", 39, mode_InputDigital, LOW  },
{"\0                               ", 40, mode_InputDigital, LOW  },
{"\0                               ", 41, mode_InputDigital, LOW  },
{"\0                               ", 42, mode_InputDigital, LOW  },
{"\0                               ", 43, mode_InputDigital, LOW  },
{"\0                               ", 44, mode_InputDigital, LOW  },
{"\0                               ", 45, mode_InputDigital, LOW  },
{"\0                               ", 46, mode_InputDigital, LOW  },
{"\0                               ", 47, mode_InputDigital, LOW  },
{"\0                               ", 48, mode_InputDigital, LOW  },
{"\0                               ", 49, mode_InputDigital, LOW  },
{"\0                               ", 50, mode_InputDigital, LOW  },
{"\0                               ", 51, mode_InputDigital, LOW  },
{"\0                               ", 52, mode_InputDigital, LOW  },
{"\0                               ", 53, mode_InputDigital, LOW  },
{"\0                               ", A0, mode_TulisNilai,   LOW  },
{"\0                               ", A1, mode_TulisNilai,   LOW  },
{"\0                               ", A2, mode_TulisNilai,   LOW },
{"\0                               ", A3, mode_TulisNilai,   LOW  },
{"Baca konsentrasi Asap\0          ", A4, mode_InputAnalog,  LOW  },//analogRead()
{"\0                               ", A5, mode_InputAnalog,  LOW  },
{"\0                               ", A6, mode_InputAnalog,  LOW  },
{"\0                               ", A7, mode_InputAnalog,  LOW  },
{"\0                               ", A8, mode_InputAnalog,  LOW  },
{"\0                               ", A9, mode_InputAnalog,  LOW  },
{"\0                               ",A10, mode_InputAnalog,  LOW  },
{"\0                               ",A11, mode_InputAnalog,  LOW  },
{"\0                               ",A12, mode_InputAnalog,  LOW  },
{"\0                               ",A13, mode_InputAnalog,  LOW  },
{"\0                               ",A14, mode_InputAnalog,  LOW  },
{"\0                               ",A15, mode_InputAnalog,  LOW  },
};

//Software serial
//#define pinSIM800RX 2
//#define pinSIM800TX 3
//#define pinSIM800Reset 4
//SoftwareSerial mySerial(pinSIM800RX, pinSIM800TX); // RX, TX jika menggunakan software serial
//SIM800 sim(&mySerial, pinSIM800Reset, 9600);

//Hardware serial
#define pinSIM800Serial Serial1
#define pinSIM800Reset 17
SIM800 sim(&pinSIM800Serial, pinSIM800Reset, 9600);//Menggunakan hardware serial

void setup() {
  Serial.begin(115200);
  Serial.println("SMS gateway menggunakan Arduino");
  Serial.println("https://www.project.semesin.com/");

  Serial.println();
  Serial.println("Inisialisasi...");

  for(byte i=0;i<sizeof(perintah)/sizeof(perintah[0]);i++)
  {
    switch(perintah[i].mode)
    {
      case mode_TulisNilai:
      case mode_Pemicu:
        pinMode(pgm_read_byte(&perintah[i].pinKeluaran), OUTPUT);
        digitalWrite(pgm_read_byte(&perintah[i].pinKeluaran), LOW);
        break;
      case mode_InputDigital:
      case mode_InputAnalog:
        pinMode(pgm_read_byte(&perintah[i].pinKeluaran), INPUT);
        break;
    }
  }

  while(sim.init(9600) != 0) {
    Serial.println("Mengulangi inisialisasi");
  }  
  Serial.println("Menghubungkan ke jaringan selular");
  while(!sim.waitSIMReady());
  Serial.println("SMS gateway telah aktif");
}

void loop() 
{
  byte IndexSMSBaru;
  
  if(IndexSMSBaru = sim.checkEvent())
  {
    if(IndexSMSBaru != newEventCall)
    {
      String bufferIsiSMS = "";
      String bufferNomorPengirim = "";
      char bufferPerintah[sizeof(perintah[0])];
      
      if(sim.readSMS(IndexSMSBaru, &bufferIsiSMS, &bufferNomorPengirim) == 0)
      {
        Serial.println();
        Serial.print("Perintah : ");
        Serial.println(bufferIsiSMS);
        Serial.print("Nomor pengirim : ");
        Serial.println(bufferNomorPengirim);
  
        bool perintahDilaksanakan = false;
        char NomorPengirim[16];
        char nilaiInput[5];
        char aktif[] = "Aktif";
        char tidakAktif[] = "Tidak aktif";
        
        for(byte i=0;i<sizeof(perintah)/sizeof(perintah[0]);i++)
        {
          copyFlashString(bufferPerintah, (char*)(&perintah[i].isiPerintah));
          if(bufferIsiSMS.equalsIgnoreCase((String)bufferPerintah))
          {
            byte nomorPin = pgm_read_byte(&perintah[i].pinKeluaran);
            byte nilaiPin = pgm_read_byte(&perintah[i].nilaiPin);
            
            bufferNomorPengirim.toCharArray(NomorPengirim, 16);
            
            switch(pgm_read_byte(&perintah[i].mode) == mode_TulisNilai)
            {
              case mode_TulisNilai:
                digitalWrite(nomorPin, nilaiPin);
                break;
              case mode_TulisAnalog:
                analogWrite(nomorPin, nilaiPin);
                break;
              case mode_Pemicu:
                digitalWrite(nomorPin, nilaiPin);
                delay(1000);
                digitalWrite(nomorPin, !nilaiPin);
                break;
              case mode_InputDigital:
                sim.sendSMS(NomorPengirim, digitalRead(nomorPin)?aktif:tidakAktif);
                break;
              case mode_InputAnalog:
                sprintf (nilaiInput, "%04i", analogRead(nomorPin));
                sim.sendSMS(NomorPengirim, nilaiInput);
                break;
            }
            Serial.print("Perintah ");
            Serial.print(bufferIsiSMS);
            Serial.println(" Telah dilaksanakan");
            perintahDilaksanakan = true;
            break;
          }
        }
        if(!perintahDilaksanakan)
        {
          Serial.println("Perintah tidak dikenali!");
        }
        sim.deleteSMS(IndexSMSBaru);
      }
    }
  }
}
void copyFlashString(char* buf, const char* alamat)
{
  char c;
  while(c = pgm_read_byte(alamat++))
  {
    *buf++ = c;
  }
  *buf = 0;
}

Rangkaian Dimmer lampu yang dikendalikan oleh sinyal PWM digital

Lampu merupakan komponen yang mengubah energi listrik menjadi energi cahaya. Untuk mengontrol intensitas cahaya lampu kita biasa menggunakan dimmer/peredup. Dimmer umumnya dikendalikan oleh potensio, yang mengontrol waktu penyalaan SCR.

Rangkaian dimmer ac:

Untuk mengontrol intensitas cahaya lampu menggunakan sinyal PWM digital memiliki beberapa kendala yaitu :

  1. Waktu penyalaan, sinyal bolak-balik (AC) senantiasa bergerak naik dan turun, maka sinyal pengontrol PWM haruslah dimulai saat sinyal AC meninggalkan nilai nol
  2. Frekuensi AC, Sinyal pengontrol PWM juga harus memiliki frekuensi tepat dengan frekuensi sinyal AC (listrik)

Akibat dari frekuensi yang tidak pas tepat adalah intensitas cahaya lampu yang tidak rata.

Permasalahan waktu penyalaan bisa dideteksi dengan rangkaian zero crossing detector (ZCD), dan permasalahan frekuensi AC bisa dideteksi dengan melakukan pengukuran rentang frekuensinya.

Namun perangkat digital juga memiliki tick point yang tidak singkron dengan sinyal AC misalnya faktor pergeseran frekuensi akibat pembagian bilangan yang tidak sempurna. Sebagai contoh perangkat digital akan sulit mencapai frekuensi 50,00019 Hz.

Untuk mengatasi pemasalahan tersebut rangkaian peredup lampu (Dimmer) didesain dengan penggunaan frekuensi yang lebih tinggi dari frekuensi sinyal AC tanpa memperhatikan waktu nol dan nilai frekuensinya.

Sinyal PWM bisa diperoleh dari rangkaian pembangkit PWM (PWM generator) atau dari mikrokontroller seperti arduino. Aplikasi rangkaian dimmer lampu arduino (rangkaian dimmer arduino) bisa menggunakan perintah analogWrite().

Berikut komponen elektronika yang digunakan dalam perancangan dimmer PWM lampu 220v:

  1. Dioda 1N5408 4 buah
  2. Dioda 1N4007
  3. Dioda zener 10V
  4. Resistor 220 ohm 2 buah
  5. Resistor 330 ohm
  6. Resistor 68 Kohm
  7. Kapasitor 2.2 uH
  8. Optocoupler 4N35
  9. Mosfet IRF 830
  10. Lampu dan Fitting

Berikut skema rangkaian dimmer lampu dc/ac yang dikontrol PWM:

 

update: https://www.project.semesin.com/2018/05/01/dimmer-pwm-arduino/

Mengirim file di SDCard ke PC melalui ESP8266 dengan Arduino

Mengunduh file (download) dari web server melaui web browser biasa kita lakukan. protokol yang umum digunakan adalah FTP atau HTTP. Perancangan kali ini menggunakan Arduino sebagai file server. File-filenya disimpan dalam SDCard, dan untuk berkomunikasi dengan jaringan menggunakan modul wifi ESP8266.

Perancangan sistem file server arduino ini mampu menampilkan File SDCard ke web browser, daftar file file ini merupakan isi dari root direktori kartu memori.

Untuk mendownload file dari arduino, buka browser di halaman sesuai alamat IP Wifi ESP8266 kemudian klik file yang akan didownload, atau jika ingin mendownload secara langsung bisa melalui link http://(alamat ip ESP8266)/namafile.ext.

Komponen yang digunakan :

  1. Arduino Mega
  2. Modul MicroSD
  3. Modul ESP8266 ESP12E

skema download file dari kartu memori melalui ESP8266 :

Sebelum digunakan edit dulu bagian ini :

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

char ssid[] = "Twim";
char pass[] = "12345678";
int status = WL_IDLE_STATUS;

WiFiEspServer server(80);
File FileSDCard;

void setup() {
  Serial.begin(115200);
  Serial.println("Mengirim file di SDCard ke PC melalui ESP8266 dengan Arduino");
  Serial.println("https://www.project.semesin.com");

  Serial1.begin(115200);
  WiFi.init(&Serial1);

  if (!SD.begin(53)) 
  {
    Serial.println("Gagal memulai kartu memori!");
    while (true);
  }
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("Modul ESP8266 tidak ditemukan");
    while (true);
  }

  while ( status != WL_CONNECTED) {
    Serial.print("Attempting to connect to WPA SSID: ");
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);
  }

  Serial.println("You're connected to the network");
  printWifiStatus();
  server.begin();
}

void loop() {
  WiFiEspClient client = server.available();
  if (client) {
    boolean currentLineIsBlank = true;
    String request = "";

    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        request += c;
        if (c == '\n' && currentLineIsBlank) {
          break;
        }
        if (c == '\n') {
          currentLineIsBlank = true;
        }
        else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }
    client.println("HTTP/1.1 200 OK");
    client.println("Connection: close");

    Serial.println(request);
    Serial.println();
    Serial.println("Mengirim respon");

    int startFilename = request.indexOf('/') + 1;
    int endFilename = request.indexOf("HTTP/1.1") - 1;

    String fileRquest = request.substring(startFilename, endFilename);
    Serial.print("Permintaan file : ");
    Serial.println(fileRquest);

    if(fileRquest == "")
    {
      File root = SD.open("/");
      client.println("Content-Type: text/html");
      client.println();
      client.print("<!DOCTYPE HTML>");
      client.print("<html>");
      client.print("Daftar file:<br>");

      while (true) 
      {
        File entry =  root.openNextFile();
        if (! entry) {
          break;
        }
        client.print("<a href=\"");
        client.print(entry.name());
        client.print("\">");
        client.print(entry.name());
        client.print("</a><br>");
        entry.close();
      }
      client.print("</html>\r\n");
    }
    else if (!SD.exists(fileRquest)) 
    {
      client.println("Content-Type: text/html");
      client.println();
      client.print("<!DOCTYPE HTML>");
      client.print("<html>");
      client.print("Maaf file tidak ditemukan");
      client.print("</html>");
    } 
    else 
    {
      Serial.print("Mengirim file : ");
      Serial.println(fileRquest);
      FileSDCard = SD.open(fileRquest, FILE_READ);

      client.print("Content-Length:");
      client.println((String)FileSDCard.size());
      client.println("Content-Type: text/plain");
      client.print("Content-Disposition: attachment; filename=\"");
      client.print(fileRquest);
      client.println("\"");
      client.println("Content-Transfer-Encoding: binary");
      client.println();

      for(uint16_t i=0;i<FileSDCard.size();i++)
      {
        client.write(FileSDCard.read());
      }
      FileSDCard.close();
    }
    delay(10);
    client.stop();
    Serial.println("Client disconnected");
  }
}
void printWifiStatus()
{
  IPAddress ip = WiFi.localIP();
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());
  Serial.print("IP Address: ");
  Serial.println(ip);
  Serial.println();
  Serial.print("To see this page in action, open a browser to http://");
  Serial.println(ip);
  Serial.println();
}

keluaran serial monitor:

library yang digunakan :
WifiEsp.zip

Fakta menarik arduino

Fakta arduino yang menarik dicermati (arduino fun fact):

  1. Pin 13 pada Arduino terhubung ke onboard led ‘L’.
  2. Maksimum nilai delayMicroseconds(x) adalah x = 16383.
  3. Nilai millis() akan kembai ke angka 0 dalam 49.7 hari atau 49 hari 17 jam 2 menit dan 47 detik
  4. Pin digital mampu mengalirkan arus 40mA (200mA dalam satu port) dan mampu menghidupkan LED (menggunakan resistor sebagai pembatas arus).
  5. analogWrite(pin, value) hanya berlaku pada pin: UNO 3, 5, 6, 9, 10, dan 11; MEGA 2 – 13 dan 44 – 46.
  6. Memiliki fitur Capacitive touch sensing.
  7. Maximum frekuensi PWM 8.000.000 Hz (8MHz).
  8. Maksimal baud rate Serial = 2.000.000 (2Mbps). Arduino 101 57600bps
  9. Flash memory Mega 8KBytes; UNO 2KBytes
  10. RAM Mega 256KBytes; UNO 32KBytes
  11. EEPROM Mega 4KBytes; UNO 1KBytes
  12. Area kode program uno 32.256 byte, nano 30.720 byte, micro 28.672 byte, walaupun sama-sama menggunakan ATMega328
  13. EEPROM Mega 4KBytes; UNO 1KBytes
  14. Fitur virtual komunikasi serial bisa menggunakan Fungsi SoftewareSerial dengan ketentuan :
    • Jika menggunakan lebih dari satu SoftewareSerial maka hanya satu SoftewareSerial yang bisa menerima data (receive) dalam satu waktu.
    • Pada arduino mega hanya pin-pin berikut yang bisa digunakan sebagai pin RX: 10, 11, 12, 13, 14, 15, 50, 51, 52, 53, A8, A9, A10, A11, A12, A13, A14 , A15. Pada Leonardo hanya : 8, 9, 10, 11, 14, 15, 16. dan Arduino 101 selain pin 13.
  15. Nama lain/alias pin Arduino Uno
    • 0 = RX
    • 1 = TX
    • 10 = SS
    • 11 = MOSI
    • 12 = MISO
    • 13 = SCK
    • A0 = 14
    • A1 = 15
    • A2 = 16
    • A3 = 17
    • A4 = 18
    • A5 = 19
  16. Nama lain/alias pin Arduino mega
    • 0 = RX
    • 1 = TX
    • 50 = MISO
    • 51 = MOSI
    • 52 = SCK
    • 53 = SS
    • A0 = 54
    • A1 = 55
    • A2 = 56
    • A3 = 57
    • A4 = 58
    • A5 = 59
    • A6 = 60
    • A7 = 61
    • A8 = 62
    • A9 = 63
    • A10 = 64
    • A11 = 65
    • A12 =66
    • A13 = 67
    • A14 = 68
    • A15 = 69

Membaca file txt dari SDCard/kartu memori kedalam struct menggunakan Arduino (Aplikasi Bel Sekolah)

SDCard/microSD/kartu memori merupakan media penyimpanan yang banyak digunakan pada perangkat-perangkat elektronik untuk menyimpan foto, video, data dll. Dalam aplikasi arduino SDCard bisa dibaca/ditulis dalam protokol SPI, format yang didukung adalah FAT.

Dalam perancangan ini saya menggunakan file .txt yang tersusun sebagai database (contoh database jadwal bel sekolah), dan dibaca dengan arduino untuk kemudian dimasukkan kedalam tabel “jadwalBelajar”.

format yang digunakan adalah

Aktif | Waktu | Hari aktif | Minggu aktif | Kegiatan

Aktif
Sebagai status item/baris jadwal diaktifkan atau tidak(diabaikan)
isiannya Aktif, Tidak Aktif

Waktu
Format waktu jam:menit

Hari aktif
hari dimana item jadwal diaktifkan
isiannya: Senin, Selasa, Rabu, Kamis, Jum’at, Sabtu, Minggu atau kombinasinya dalam bentuk array didalam tanda ‘{‘ dan ‘}’

minggu aktif
isiannya kombinasi minggu bentuk array didalam tanda ‘{‘ dan ‘}’

Kegiatan
text Kegiatan

Skema:

sketch/program:

#include <avr/pgmspace.h>
#include <SPI.h>
#include <SD.h>

struct Waktu
{ 
  byte jam; 
  byte menit; 
};

struct TabelMataPelajaran
{
  byte aktif;
  Waktu waktu;
  byte hariAktif;
  byte mingguAktif;
  byte kegiatan;
};

const PROGMEM char kegiatan[][21] = 
{
  "Jam Pelajaran 1\0    ",
  "Jam Pelajaran 2\0    ",
  "Jam Pelajaran 3\0    ",
  "Jam Pelajaran 4\0    ",
  "Jam Pelajaran 5\0    ",
  "Jam Pelajaran 6\0    ",
  "Jam Pelajaran 7\0    ",
  "Jam Pelajaran 8\0    ",
  "Jam Pelajaran 9\0    ",
  "Jam Pelajaran 10\0   ",
  "Jam Pelajaran 11\0   ",
  "Jam Pelajaran 12\0   ",
  "Jam Pelajaran 13\0   ",
  "Jam Pelajaran 14\0   ",
  "Jam Pelajaran 15\0   ",
  "Masuk\0              ",
  "Upacara\0            ",
  "Istirahat\0          ",
  "Selesai Istirahat\0  ",
  "Kepramukaan\0        ",
  "Khusus\0             ",
  "Pulang\0             ",
  "Pulang Jumat\0       ",
  "Pulang Sabtu\0       "
};

const char namaHari[][7] PROGMEM = 
{
  "Minggu",
  "Senin\0",
  "Selasa",
  "Rabu\0 ",
  "Kamis\0",
  "Jum'at",
  "Sabtu\0"
};

const char Aktifasi[][12] PROGMEM = 
{
  "Aktif\0     ",
  "Tidak aktif"
};

#define hariAktifSenin 1<<6
#define hariAktifSelasa 1<<5
#define hariAktifRabu 1<<4
#define hariAktifKamis 1<<3
#define hariAktifJumat 1<<2
#define hariAktifSabtu 1<<1
#define hariAktifMinggu 1<<7


#define _hariAktif(Sen,Sel,Rab,Kam,Jum,Sab,Mgu) (Mgu<<7)|(Sen<<6)|(Sel<<5)|(Rab<<4)|(Kam<<3)|(Jum<<2)|(Sab<<1)
#define _mingguAktif(Mgu1,Mgu2,Mgu3,Mgu4,Mgu5) (Mgu1<<7)|(Mgu2<<6)|(Mgu3<<5)|(Mgu4<<4)|(Mgu5<<3)
#define _waktu(Jam, Menit) {Jam, Menit}
#define Aktif 1
#define TidakAktif 0

TabelMataPelajaran jadwalBelajar[150];
Waktu waktu;
File fileJadwal;
char buffer[21];

void setup() {
  Serial.begin(9600);
  Serial.println("Membaca file txt dari SDCard/kartu memori kedalam struct menggunakan Arduino");
  Serial.println("(Aplikasi Bel Sekolah)");
  Serial.println("https://www.project.semesin.com");
}

void loop() {
  if (!SD.begin(53)) 
  {
    Serial.println("Gagal memulai kartu memori!");
  }
  else
  {  
    if (!SD.exists("Jadwal.txt")) 
    {
      Serial.println("file Jadwal.txt tidak ditemukan");
    } 
    else 
    {
      char c;
      byte index;
      String str;
      fileJadwal = SD.open("Jadwal.txt", FILE_READ);
      while(1)
      {
        c = fileJadwal.peek();
        if(c == -1)
        {
          break;
        }
        else if(c == '/')
        {
          while(fileJadwal.read() != '\n');
          continue;
        }
        else if((c == '\r') || (c == '\n') || (c == '\t') || (c == ' '))
        {
          fileJadwal.read();
          continue;
        }
        jadwalBelajar[index].aktif = ambilCSVAktif(fileJadwal);
        ambilCSVWaktu(&jadwalBelajar[index].waktu.jam,&jadwalBelajar[index].waktu.menit, fileJadwal);
        jadwalBelajar[index].hariAktif = ambilCSVHari(fileJadwal);
        jadwalBelajar[index].mingguAktif = ambilCSVMinggu(fileJadwal);
        jadwalBelajar[index].kegiatan = ambilCSVKegiatan(fileJadwal);

        copyFlashString(buffer, &Aktifasi[jadwalBelajar[index].aktif][0]);
        Serial.print(buffer);
        Serial.print('\t');
        if(jadwalBelajar[index].waktu.jam < 10) Serial.print('0');
        Serial.print(jadwalBelajar[index].waktu.jam);
        Serial.print(':');
        if(jadwalBelajar[index].waktu.menit < 10) Serial.print('0');
        Serial.print(jadwalBelajar[index].waktu.menit);
        Serial.print('\t');
        Serial.print(jadwalBelajar[index].hariAktif,HEX);
        Serial.print('\t');
        Serial.print(jadwalBelajar[index].mingguAktif,HEX);
        Serial.print('\t');
        copyFlashString(buffer, &kegiatan[jadwalBelajar[index].kegiatan][0]);
        Serial.println(buffer);
      }
      fileJadwal.close();
    }
  }

  while(1);//***
}
byte ambilCSVAktif(File file)
{
  String str = ambilCSVDariFile(file);

  for(byte i=0;i<sizeof(Aktifasi)/sizeof(Aktifasi[0]);i++)
  {
    copyFlashString(buffer, &Aktifasi[i][0]);
    if(str.equalsIgnoreCase((String)buffer))
    {
      return i;
    }
  }
}
void ambilCSVWaktu(byte* jam, byte* menit, File file)
{
  String str = ambilCSVDariFile(file);
  byte pos1 = str.indexOf(':');
  *jam = str.substring(0,pos1).toInt();
  *menit = str.substring(pos1+1).toInt();
}
byte ambilCSVHari(File file)
{
  String str = ambilCSVDariFile(file);
  for(byte i=0;i<sizeof(namaHari)/sizeof(namaHari[0]);i++)
  {
    copyFlashString(buffer, &namaHari[i][0]);
    if(str.equalsIgnoreCase((String)buffer))
    {
      return 1<<(7-i);
    }
  }
  byte dataHari = 0;
  byte shift = 0x80;
  byte hari[7];

  ambilCSVDariString(hari, str);
  for(byte i=0;i<sizeof(hari);i++)
  {
    if(hari[i])
    {
      dataHari += shift;
    }
    shift >>= 1;
    
  }
  return dataHari;
}

byte ambilCSVMinggu(File file)
{
  byte dataMinggu = 0;
  byte shift = 0x80;
  byte minggu[5];
  String str = ambilCSVDariFile(file);

  ambilCSVDariString(minggu, str);
  for(byte i=0;i<sizeof(minggu);i++)
  {
    if(minggu[i])
    {
      dataMinggu += shift;
    }
    shift >>= 1;
  }
  return dataMinggu;
}

byte ambilCSVKegiatan(File file)
{
  String str = ambilCSVDariFile(file);
  for(byte i=0;i<sizeof(kegiatan)/sizeof(kegiatan[0]);i++)
  {
    copyFlashString(buffer, &kegiatan[i][0]);
    if(str.equalsIgnoreCase((String)buffer))
    {
      return i;
    }
  }
}
String ambilCSVDariFile(File file)
{
  String str = "";
  char terminator = ',';

  while(1)
  {
    char c = file.peek();
    if(c == '{')
    {
      terminator = '}';
      file.read();
    }
    else if(((c != terminator) && (c != '\r')))
    {
      str += (char)file.read();
    }
    else
    {
      file.read();
      if(terminator == '}')
      {
        terminator = ',';
      }
      else if((terminator == ',') || (c == '\r'))
      {
        break;
      }
    }
  }
  str.trim();
  return str;
}
void copyFlashString(char* buf, const char* alamat)
{
  char c;
  while(c = pgm_read_byte(alamat++))
  {
    *buf++ = c;
  }
  *buf = 0;
}
void ambilCSVDariString(byte *minggu, String str)
{
  while(1)
  {
    int pos = str.indexOf(',');
    *minggu++ = str.substring(0,pos).toInt();
    if(pos == -1)
    {
      break;
    }
    str = str.substring(pos+1);
  }
}

contok keluaran serial monitor:

Contoh file jadwal.txt:
Jadwal.txt

Bel sekolah bluetooth dengan kontrol android : disini

Bel Sekolah menggunakan tabel database fleksibel berbasis Arduino

Bel sekolah digital dimanfaatkan untuk menentukan waktu pertukaran kegiatan di sekolah-sekolah. Jadwal untuk sebuah sekolah berbeda dengan sekolah lainnya, baik perbedaan tingkat sekolah maupun zona waktunya. Selain itu terdapat juga perbedaan jadwal untuk masing masing hari, seperti senin pertama setiap bulan untuk upacara dan hari lainnya yang mempunyai kekhususan waktu. Bel sekolah digital berbasis arduino atau mikrokontroller lain memiliki kemampuan penjadwalan jam pelajaran tersebut. selain itu bel sekolah digital arduino juga mudah dalam pengembangan nantinya.

Dalam perancangan bel sekolah arduino ini dibatasi hanya sampai pengaplikasian tabel database arduino fleksibel yang bisa disesuaikan dengan kebutuhan. Bel sekolah berbasis arduino ini hanya berupa penerapan dasar saja yang bisa dikombinasikan dengan aplikasi Bel Sekolah fungsional lainnya.

Untuk pengembangannya bisa saja perangkat bel sekolah otomatis berbasis arduino ini ditambahkan sistem entri data dari bluetooth, wifi, PC dll. juga bisa ditambahkan sistem output multimedia seperti alarm, suara, running text, DMD dan lain-lain.

Kelebihan perancangan ini adalah:

  1. Tabel database jadwal fleksibel
  2. Pembacaan RTC dengan interupsi sehingga menghemat resource
  3. Pemanfaatan sleep mode

Komponen yang digunakan:

  1. Arduino Uno
  2. RTC DS3231

berikut skema / rangkaian bel sekolah dengan arduino:

dan program/sketch Arduino:

#include <DS3232RTC.h>
#include <avr/sleep.h>

#define SQWPin 12

struct Waktu
{ 
  byte jam; 
  byte menit; 
};

struct TabelMataPelajaran
{
  byte aktif;
  Waktu waktu;
  byte hariAktif;
  byte mingguAktif;
  byte kegiatan;
};

enum _kegiatan
{
  JamPelajaran1,
  JamPelajaran2,
  JamPelajaran3,
  JamPelajaran4,
  JamPelajaran5,
  JamPelajaran6,
  JamPelajaran7,
  JamPelajaran8,
  JamPelajaran9,
  JamPelajaran10,
  JamPelajaran11,
  JamPelajaran12,
  JamPelajaran13,
  JamPelajaran14,
  JamPelajaran15,
  Masuk,
  Upacara,
  Istirahat,
  SelesaiIstirahat,
  Kepramukaan,
  Khusus,
  Pulang,
  PulangJumat,
  PulangSabtu
};

volatile bool interupsiDetik;
byte indexMataPelajaran;

#define hariAktifSenin 1<<6
#define hariAktifSelasa 1<<5
#define hariAktifRabu 1<<4
#define hariAktifKamis 1<<3
#define hariAktifJumat 1<<2
#define hariAktifSabtu 1<<1
#define hariAktifMinggu 1<<7

String namaHari[] = {"Minggu","Senin","Selasa","Rabu","Kamis","Jum;at","Sabtu"};
#define _hariAktif(Sen,Sel,Rab,Kam,Jum,Sab,Mgu) (Mgu<<7)|(Sen<<6)|(Sel<<5)|(Rab<<4)|(Kam<<3)|(Jum<<2)|(Sab<<1)
#define _mingguAktif(Mgu1,Mgu2,Mgu3,Mgu4,Mgu5) (Mgu1<<7)|(Mgu2<<6)|(Mgu3<<5)|(Mgu4<<4)|(Mgu5<<3)
#define _waktu(Jam, Menit) {Jam, Menit}
#define Aktif 1
#define TidakAktif 0



TabelMataPelajaran jadwalBelajar[35];
Waktu waktu;
tmElements_t tm;

void setup() {
  byte i = 0;

  //Senin
  jadwalBelajar[i++] = {Aktif, _waktu(  6, 45 ), hariAktifSenin,            _mingguAktif(1,0,0,0,0), Upacara};
  jadwalBelajar[i++] = {Aktif, _waktu(  6, 45 ), hariAktifSenin,            _mingguAktif(0,1,1,1,1), JamPelajaran1};
  jadwalBelajar[i++] = {Aktif, _waktu(  6, 45 ), _hariAktif(0,1,1,1,1,0,0), _mingguAktif(1,1,1,1,1), JamPelajaran1};
  
  jadwalBelajar[i++] = {Aktif, _waktu(  7, 30 ), _hariAktif(1,1,1,1,0,0,0), _mingguAktif(1,1,1,1,1), JamPelajaran2};
  jadwalBelajar[i++] = {Aktif, _waktu(  8, 15 ), _hariAktif(1,1,1,1,0,0,0), _mingguAktif(1,1,1,1,1), JamPelajaran3};
  jadwalBelajar[i++] = {Aktif, _waktu(  9, 0  ), _hariAktif(1,1,1,1,0,0,0), _mingguAktif(1,1,1,1,1), JamPelajaran4};
  jadwalBelajar[i++] = {Aktif, _waktu(  9, 45 ), _hariAktif(1,1,1,1,0,0,0), _mingguAktif(1,1,1,1,1), Istirahat};
  jadwalBelajar[i++] = {Aktif, _waktu( 10, 15 ), _hariAktif(1,1,1,1,0,0,0), _mingguAktif(1,1,1,1,1), JamPelajaran5};
  jadwalBelajar[i++] = {Aktif, _waktu( 11, 0  ), _hariAktif(1,1,1,1,0,0,0), _mingguAktif(1,1,1,1,1), JamPelajaran6};
  jadwalBelajar[i++] = {Aktif, _waktu( 11, 45 ), _hariAktif(1,1,1,1,0,0,0), _mingguAktif(1,1,1,1,1), Istirahat};
  jadwalBelajar[i++] = {Aktif, _waktu( 12, 30 ), _hariAktif(1,1,1,1,0,0,0), _mingguAktif(1,1,1,1,1), JamPelajaran7};
  jadwalBelajar[i++] = {Aktif, _waktu( 13, 15 ), _hariAktif(1,1,1,1,0,0,0), _mingguAktif(1,1,1,1,1), JamPelajaran8};

  jadwalBelajar[i++] = {Aktif, _waktu( 14, 0  ), _hariAktif(1,1,1,0,0,0,0), _mingguAktif(1,1,1,1,1), JamPelajaran9};
  jadwalBelajar[i++] = {Aktif, _waktu( 14, 45 ), _hariAktif(1,1,1,0,0,0,0), _mingguAktif(1,1,1,1,1), JamPelajaran10};

  jadwalBelajar[i++] = {Aktif, _waktu( 14, 0  ), hariAktifKamis,            _mingguAktif(1,1,1,1,1), Kepramukaan};
  jadwalBelajar[i++] = {Aktif, _waktu( 14, 45 ), hariAktifKamis,            _mingguAktif(1,1,1,1,1), Khusus};

  jadwalBelajar[i++] = {Aktif, _waktu( 15, 30 ), _hariAktif(1,1,1,1,0,0,0), _mingguAktif(1,1,1,1,1), Pulang};

  jadwalBelajar[i++] = {Aktif, _waktu(  7, 25 ), hariAktifJumat,            _mingguAktif(1,1,1,1,1), JamPelajaran2};
  jadwalBelajar[i++] = {Aktif, _waktu(  8, 5  ), hariAktifJumat,            _mingguAktif(1,1,1,1,1), JamPelajaran3};
  jadwalBelajar[i++] = {Aktif, _waktu(  8, 45 ), hariAktifJumat,            _mingguAktif(1,1,1,1,1), JamPelajaran4};
  jadwalBelajar[i++] = {Aktif, _waktu(  9, 25 ), hariAktifJumat,            _mingguAktif(1,1,1,1,1), Istirahat};
  jadwalBelajar[i++] = {Aktif, _waktu(  9, 55 ), hariAktifJumat,            _mingguAktif(1,1,1,1,1), JamPelajaran5};
  jadwalBelajar[i++] = {Aktif, _waktu( 10, 35 ), hariAktifJumat,            _mingguAktif(1,1,1,1,1), JamPelajaran6};
  jadwalBelajar[i++] = {Aktif, _waktu( 11, 15 ), hariAktifJumat,            _mingguAktif(1,1,1,1,1), PulangJumat};

  indexMataPelajaran = i;

  Serial.begin(9600);
  Serial.println("Bel Sekolah menggunakan tabel database flexibel berbaasis Arduino");
  Serial.println("Bisa ditambahkan input bluetooth, wifi, PC, komputer dll");
  Serial.println("Bisa ditambahkan output multimedia seperti suara, alarm, running text, dmd dll");
  Serial.println("https://www.project.semesin.com");
  
  byte ControlRegister;
  RTC.readRTC(0x0E,&ControlRegister,1);
  ControlRegister &= ~(0x07<<2);
  RTC.writeRTC(0x0E, &ControlRegister,1);

  *digitalPinToPCMSK(SQWPin) |= bit (digitalPinToPCMSKbit(SQWPin));
  PCIFR  |= bit (digitalPinToPCICRbit(SQWPin));
  PCICR  |= bit (digitalPinToPCICRbit(SQWPin));
 
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();

////Set waktu sekali saja
//  tm.Day = 1;
//  tm.Month = 4;
//  tm.Year = CalendarYrToTm(2018);
//  tm.Hour = 15;
//  tm.Minute = 24;
//  tm.Second = 00;
//
//  time_t t = makeTime(tm);
//  tm.Wday = dayOfWeek(t);
//  RTC.write(tm);
//  TampilkanWaktu();
}

ISR (PCINT0_vect)
{
  if(digitalRead(SQWPin))
  {
    interupsiDetik = true;
  }
}  

void loop() {
  
  
  if(interupsiDetik)
  {
    interupsiDetik = false;
    RTC.read(tm);
    
    if(tm.Second == 0)
    {
      TampilkanWaktu();//***
      
      byte hariKeDiTanggal1 = (((tm.Wday + 8)  - (tm.Day % 7)) % 7);
      byte SeninKe = ((tm.Day + 7 - hariKeDiTanggal1) / 7);
      byte mingguKe = ((tm.Day + 8 - hariKeDiTanggal1) / 7) + 1;
      
      for(byte i=0; i<indexMataPelajaran ;i++)
      {
        if(jadwalBelajar[i].aktif)
        {
          if((jadwalBelajar[i].waktu.jam == tm.Hour) && 
          (jadwalBelajar[i].waktu.menit == tm.Minute) && 
          (jadwalBelajar[i].hariAktif & (1<<(8-tm.Wday))) &&
          (jadwalBelajar[i].mingguAktif & (1<<(8-SeninKe))))
          {
            TampilkanWaktu();
            //Bisa ditambahkan sistem output
            switch(jadwalBelajar[i].kegiatan)
            {
              case JamPelajaran1:
                Serial.println("Jam Pelajaran 1");
                break;
              case JamPelajaran2:
                Serial.println("Jam Pelajaran 2");
                break;
              case JamPelajaran3:
                Serial.println("Jam Pelajaran 3");
                break;
              case JamPelajaran4:
                Serial.println("Jam Pelajaran 4");
                break;
              case JamPelajaran5:
                Serial.println("Jam Pelajaran 5");
                break;
              case JamPelajaran6:
                Serial.println("Jam Pelajaran 6");
                break;
              case JamPelajaran7:
                Serial.println("Jam Pelajaran 7");
                break;
              case JamPelajaran8:
                Serial.println("Jam Pelajaran 8");
                break;
              case JamPelajaran9:
                Serial.println("Jam Pelajaran 9");
                break;
              case JamPelajaran10:
                Serial.println("Jam Pelajaran 10");
                break;
              case JamPelajaran11:
                Serial.println("Jam Pelajaran 11");
                break;
              case JamPelajaran12:
                Serial.println("Jam Pelajaran 12");
                break;
              case JamPelajaran13:
                Serial.println("Jam Pelajaran 13");
                break;
              case JamPelajaran14:
                Serial.println("Jam Pelajaran 14");
                break;
              case JamPelajaran15:
                Serial.println("Jam Pelajaran 15");
                break;
              case Masuk:
                Serial.println("Masuk");
                break;
              case Upacara:
                Serial.println("Upacara");
                break;
              case Istirahat:
                Serial.println("Istirahat");
                break;
              case SelesaiIstirahat:
                Serial.println("Selesai istirahat");
                break;
              case Kepramukaan:
                Serial.println("Kepramukaan");
                break;
              case Khusus:
                Serial.println("Khusus");
                break;
              case Pulang:
                Serial.println("Jam pelajaran telah selesai, sampai jumpa esok hari");
                break;
              case PulangJumat:
                Serial.println("Jam pelajaran telah selesai, sampai jumpa minggu depan");
                break;
              case PulangSabtu:
                Serial.println("Jam pelajaran telah selesai, sampai jumpa minggu depan");
                break;
              default:
                Serial.println("Lainnya");
                break;
            }
          }
        }
      }
    }
  }
  delay(100);//Selesaikan Serial nge print, hapus saja jika tidak diperlukan
  sleep_mode();
}
void TampilkanWaktu()
{
  Serial.print("Waktu = ");
  print2digits(tm.Hour);
  Serial.write(':');
  print2digits(tm.Minute);
  Serial.write(':');
  print2digits(tm.Second);
  Serial.print(", Tanggal = ");
  Serial.print(namaHari[tm.Wday-1]);
  Serial.write(' ');
  Serial.print(tm.Day);
  Serial.write('/');
  Serial.print(tm.Month);
  Serial.write('/');
  Serial.print(tmYearToCalendar(tm.Year));
  Serial.println();
}

void print2digits(int number) {
  if (number >= 0 && number < 10) {
    Serial.write('0');
  }
  Serial.print(number);
}

contoh Bel Sekolah Arduino Uno dengan output modul mini MP3 DFPlayer (library DFPlayer_Mini_Mp3.h) dan software serial : Bel_Sekolah_tanpa_interupsi.ino

Bel sekolah bluetooth dengan kontrol android : disini