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);
  }
}

 

Kontrol motor servo dengan delphi melalui port parallel lpt

Motor servo berputar ke posisi sudut tertentu berdasarkan lebar pulsa yang diberikan padanya. Pada umumnya lebar pulsa yang dibutuhkan adalah 1ms – 2ms, dengan perida 20ms seperti diagram berikut :

Membangkitkan sinyal pulsa ini menjadi masalah tersendiri jika menggunakan pc melalui port paralel, karena disisi komputer cara ini (akses langsung ke perangkat) dianggap tidak aman.

Beberapa metode/trik bisa dilakukan untuk menggerakkan motor servo melalui lpt port seperti kode berikut :


for i:=1 to 10 do
begin
Out32($378, $01);
delay(1);
Out32($378, $00);
delay(19);
end;

cara ini menjadi tidak efektif karena fungsi ‘delay()’ tidak bisa menjamin timingnya. contohnya jika delay(1) tidak ada garansi akan tepat 1ms. Maka yang terjadi adalah servo agak bergerak tidak beraturan.

jadi kunci memitar motor servo tepat pada posisinya adalah timing yang pas dan tepat. untuk itu digunakan fungsi ‘QueryPerformanceCounter(lastTick);’ yang lebih presisi pewaktuannya.

skema simulasi gerak servo untuk membuka pintu menggunakan port parallel:

 

program delphi penggerak motor servo, simulasi buka tutup:

 

unit servo;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    ButtonBuka: TButton;
    ButtonTutup: TButton;
    Label1: TLabel;
    Label2: TLabel;
    procedure ButtonBukaClick(Sender: TObject);
    procedure ButtonTutupClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  nilaiPort : byte;

const
  //cek alamat resource lpt port melalui device manager
  baseRegister = $DC00;
  dataRegister = baseRegister + 0;
  statusRegister = baseRegister + 1;
  controlRegister = baseRegister + 2;

  sudutBuka = 180;
  sudutTutup = 0;
  maskPinServo = $01;


implementation

{$R *.dfm}

function Inp32(PortAdr: word): byte; stdcall; external 'inpout32.dll';
function Out32(PortAdr: word; Data: byte): byte; stdcall; external 'inpout32.dll';

procedure gerakServo(maskPort : byte; sudut : byte);
var
	lastTick, tick : Int64;
	lastTick20 : Int64;
	mikroDetik : integer;
	Frequency : Int64;
	i : integer;
begin
	QueryPerformanceFrequency(Frequency);
	mikroDetik := 500 + (Round(sudut/180) * 2000);

	for i := 1 to 20 do
	begin
		nilaiPort := nilaiPort or maskPort;
		Out32(dataRegister, nilaiPort);

		QueryPerformanceCounter(lastTick);
		lastTick20 := lastTick + round((20000 / 1000000) * Frequency);
		lastTick := lastTick + round((mikroDetik / 1000000) * Frequency);

		while true do
		begin
			QueryPerformanceCounter(tick);
			if tick > lastTick then
				break;
		end;

		nilaiPort := nilaiPort and (not maskPort);
		Out32(dataRegister, nilaiPort);

		while true do
		begin
			QueryPerformanceCounter(tick);
			if tick > lastTick20 then
				break;
		end;
	end;
end;

procedure TForm1.ButtonBukaClick(Sender: TObject);
begin
	gerakServo(maskPinServo, sudutBuka);
end;

procedure TForm1.ButtonTutupClick(Sender: TObject);
begin
	gerakServo(maskPinServo, sudutTutup);
end;

end.

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);

    }
  }
}

 

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

Pin mapping board ESP8266

Board ESP8266 yang ada dipasaran membawa standar penamaan sendiri-sendiri, berikut tabel persamaannya:

Generik Alias D1 D1 mini Espectro Inven tone Node MCU Oak Wifi slot Wifi duino Wifinfo Wifio
0 D8 D3 D2 D3 P2 PIN_A1 D3 D3 E0
1 TX D1 TX TX/TX0 TX D10 P4 D1/TX D10 E1
2 TX1 D9 D4 TX1 D4 D4 P0 D2 D4 E2
3 RX D0 RX RX/RX0 RX D9 P3 PIN_A0 D0/RX D9 E3
4 SDA D4/D14 D2 D6 D2 P5 PIN_A6 D4 D2 E4
5 SCL D3/D15 D1 D5 D1 P1 PIN_A4 D5 D1 E5
12 MISO D6/D12 D6 D8 D6 P8 PIN_A7 D8/D12 D6 E12
13 MOSI D7/D11 D7 D7 D7 P7 PIN_A3 D9/D11 D7 E13
14 SCK D5/D13 D5 D3 D5 P9 PIN_A5 D7/D13 D5 E14
15 SS D10 D8 D1 D8 P6 D10 D8 E15
16 D2 D0 D9 D0 P10 PIN_A2 D6 D0 E16

Sedangkan aksesoris seperti LED dan button terpasa seperti tabel berikut:

Board LED Button
Adafruit 0
Arduino SPI 2
Arduino UART 14
D1 2
D1 mini 2
ESP8285 1
Espectro 15 0/2
Espino 2/4/5 0
Espinotee 16
Espresso lite v1 16
Espresso lite v2 2
Inventone 2
Modwifi 1
NodeMCU 16
Oak 5
phoenix v1 16
phoenix v2 2
Thing 5
Wifi slot 2
Wifiduino 2
Wifinfo 12
Wifio 2
Wiolink 2
Xinabox 5/12/13

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:

Menampilkan data audio dari sound card dengan GUI Matlab

Matlab menyedian fasilitas pengambilan data audio langsung dari hardware soundcard (internal/external) menggunakan system audio toolbox atau data acquisition toolbox. Namun toolbox tersebut hanya mendukung beberapa jenis soundcard saja.

Jika hardware tidak didukung dan pengambilan data audio realtime bisa dikesampingkan, maka pengambilan data audio melalui mic/line in masih bisa dilakukan dengan fungsi standar yang disediakan matlab.

Dalam proyek ini digunakan fungsi-fungsi utama berikut:

  1. audiodevinfo, bertugas mengambil informasi perangkat/hardware audio input seperti mic dan line in. Daftar perangkat masukan suara ini ditampilkan dalam pop-up menu sehingga pengguna bisa memilih perangkat yang akan digunakan sebagai masukan audio.
  2. audiorecorder, merupakan fungsi perekam audio standar matlab yang akan mulai merekam saat diberi perintah start() dan akan berhenti saat diberi perintah stop(). Data suara yang terekam bisa diambil dengan perintah getaudiodata();
  3. Timer, berfungsi mengatur jeda pengambilan data suara.

Metode ini akan memiliki jeda tergantung pengaturan waktu di timer, agar terlihat lebih realtime, perioda timer dibuat lebih kecil dan dalam mode tetap/fixedSpacing. Selain itu waktu proses lanjutan seperti analisa ampltudo/phase, FFT, Filter dan lain-lain dibuat seefektif mungkin sehingga jeda (kehilangan data audio) bisa diperkecil.

berikut koding fungsi merekam data suara matlab yang digunakan:

function varargout = audioSoundcard(varargin)
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
    'gui_Singleton',  gui_Singleton, ...
    'gui_OpeningFcn', @audioSoundcard_OpeningFcn, ...
    'gui_OutputFcn',  @audioSoundcard_OutputFcn, ...
    'gui_LayoutFcn',  [] , ...
    'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end

% --- Executes just before audioSoundcard is made visible.
function audioSoundcard_OpeningFcn(hObject, eventdata, handles, varargin)
% Choose default command line output for AudioSpectrumAnalyzer
handles.output = hObject;

% Update handles structure
guidata(hObject, handles);

% uiwait(handles.figure1);
global guiHandle;
global recorder;
global audioData;

global panjangDataRekaman;
global frequencySampling;
global bitsPerSample;
global audioChannel;

panjangDataRekaman = 8191;
frequencySampling = 22050;
bitsPerSample = 16;
audioChannel = 1;

ylim(handles.axes1, [-0.5, 0.5]);
xlim(handles.axes1, [0, panjangDataRekaman]);
title(handles.axes1, 'Real time');
xlabel(handles.axes1, 'sampling (bit)')
ylabel(handles.axes1, 'Amplitude')
hold(handles.axes1,'on');
guiHandle = guidata(hObject);
set(handles.checkboxAktif,'value', 0);

info = audiodevinfo;
nDevices = audiodevinfo(1);
str = {};
set(handles.popupmenuDevice,'string',str);

for i = 1:nDevices
    str = [str, char(info.input(i).Name)];
end
set(handles.popupmenuDevice,'string',str);
set(handles.checkboxAktif,'value',0);

deviceID = get(handles.popupmenuDevice,'value') - 1;
recorder = audiorecorder(frequencySampling, bitsPerSample, audioChannel, deviceID);
audioData = double.empty();

% --- Outputs from this function are returned to the command line.
function varargout = audioSoundcard_OutputFcn(hObject, eventdata, handles)
varargout{1} = handles.output;


% --- Executes on button press in checkboxAktif.
function checkboxAktif_Callback(hObject, eventdata, handles)
global  timerRekam
T = timerfind;
if isempty(T)
    disp('timer empty')
    timerRekam = timerRekaman();
end

if get(handles.checkboxAktif,'value')
    start(timerRekam)
else
    stop(timerRekam)
end

% --- Executes on selection change in popupmenuDevice.
function popupmenuDevice_Callback(hObject, eventdata, handles)
global recorder
global frequencySampling
global bitsPerSample;
global audioChannel;

deviceID = get(handles.popupmenuDevice,'value') - 1;
recorder = audiorecorder(frequencySampling, bitsPerSample, audioChannel, deviceID);


% --- Executes during object creation, after setting all properties.
function popupmenuDevice_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes during object deletion, before destroying properties.
function figure1_DeleteFcn(hObject, eventdata, handles)
T = timerfind;
if ~isempty(T)
    stop(T)
    delete(T)
end

function t = timerRekaman()
t = timer;
t.StartDelay = 0;
t.TimerFcn = @rekamSuara;
t.StopFcn  = @selesaiRekamSuara;
t.Period = 0.5;
t.ExecutionMode = 'fixedSpacing';

function rekamSuara(mTimer,~)
global recorder
global audioData;
global plotData;
global panjangDataRekaman;

if recorder.isrecording
    stop(recorder);
    delete(plotData);
    audioData = [audioData; getaudiodata(recorder)];
    
    if length(audioData) > panjangDataRekaman
        audioData = audioData(length(audioData)-panjangDataRekaman:length(audioData));
    end
    
    tampilGrafik;
end
disp('AmbilSuara...')
recorder.record;

function selesaiRekamSuara(mTimer,~)
disp('Selesai.')

function tampilGrafik()
global guiHandle;
global audioData;
global plotData;
global panjangDataRekaman;

if ~isempty(audioData)
    plotData = plot(audioData, 'b', 'Parent', guiHandle.axes1);
end

Contah capture audio matlab menggunakan fungsi standar pembacaan soundcard:

file pendukung pengambilan data suara dengan matlab:

audioSoundcard.fig

Papan skor arduino 3 panel P10 dengan kontrol android

Papan skor (score board) adalah papan tempat informasi dan hasil pertandingan yang dapat dilihat oleh semua orang yang berada di arena pertandingan dan penonton.

Papan skor dengan dot matrix (dmd) dengan kontrol android melalui bluetooth ini memiliki fitur :

  1. Tambah-kurang point, skor, waktu, dan ronde pertandingan serta reset point.
  2. Informasi running text yang bisa tampil di tengah pertandingan.
  3. Master reset untuk menginisialisasi semua informasi pertandingan.

Skema papan skor arduino

lepas ‘pin 0’ arduino – bluetooth saat upload sketch:

komponen papan skor bluetooth

  1. Arduino Uno
  2. 3 buah DMD Panel P10
  3. Bluetooth HC-05
  4. Android

Sketch/koding scoring board arduino dengan android melalui bluetooth

#include <SoftwareSerial.h>
#include <DMD_Semesin.h>
#include <fonts/Arial_Black_16.h>
#include <fonts/SystemFont5x7.h>
#include <fonts/SystemFont5x7Gemuk.h>
#include <Wire.h>
#include "RTC_Semesin.h"

//defenisi pin
#define pinOE         9
#define pinSCK        8
#define pinA          6
#define pinB          7

#define DISPLAYS_WIDE 3
#define DISPLAYS_HIGH 1
#define fontSkor Arial_Black_16

SPIDMD dmd(DISPLAYS_WIDE, DISPLAYS_HIGH, pinOE, pinA, pinB, pinSCK);
//SoftwareSerial bluetooth(2, 3);
HardwareSerial *bluetooth = &Serial;
RTC_DS1307 rtc;

byte pointA, pointB, skorA, skorB, ronde, menit, detik = 0;
byte Arial14TengahY;
char strInformasi[200] = "Selamat Datang";
char bufferBluetooth[200];
bool pertandinganBerjalan;
long millisDetik;
long millisEfek;
byte detikSebelumnya = 60;
bool modeInformasi;

EfekMarque efekMarque;
EfekMarqueClear efekMarqueClear;

enum perintah {
  initPerangkat,
  pointAplus,
  pointAminus,
  pointBplus,
  pointBminus,
  skorAplus,
  skorAminus,
  skorBplus,
  skorBminus,
  rondePlus,
  rondeMinus,
  menitPlus,
  menitMinus,
  text,
  resetPoint,
  resetSemua,
  mulai,
};

DMD_TextBox boxSkorA(dmd, 0,0, 28,16);
DMD_TextBox boxSkorB(dmd, 68,0, 28,16);

void setup() {
  Serial.begin(9600);
  Serial.println(F("Papan skor arduino 3 panel P10 dengan kontrol android"));
  Serial.println(F("https://www.semesin.com/project"));

  bluetooth->begin(9600);

  dmd.setBrightness(128);
  dmd.selectFont(fontSkor);
  dmd.clearScreen();
  dmd.begin();
  dmd.drawString(0, 0, F("Papan Skor bluetooth"));
  delay(1000);
  dmd.clearScreen();

  efekMarque.mode = nonAktif;
  efekMarque.init = false;
  efekMarque.sumber = sumberRAM;
  efekMarque.alamat = strInformasi;
  efekMarque.kiri = 0;
  efekMarque.atas = 0;
  efekMarque.tinggi = 16;
  efekMarque.lebar = 96;
  efekMarque.step = 1;
  efekMarque.skip = 0;

  efekMarqueClear.mode = nonAktif;
  efekMarqueClear.kiri = 0;
  efekMarqueClear.atasInit = 0;
  efekMarqueClear.atas = 0;
  efekMarqueClear.lebarInit = dmd.width;
  efekMarqueClear.lebar = dmd.width;
  efekMarqueClear.tinggi = dmd.height;
  efekMarqueClear.step = 1;

  tampilanUtama();

  Serial.println("Sistem dimulai");

  millisDetik = millis();
}

void loop() {
  if(millisDetik != millis() / 1000L)
  {
    millisDetik = millis() / 1000L;
    if(pertandinganBerjalan)
    {
      detik++;
      if(detik == 60)
      {
        detik = 0;
        menit++;
      }
    }
  }
  
  if(millisEfek < millis() - 100)
  {
    millisEfek = millis();
    if (efekMarque.mode == XMinus)
    {
      dmd.marqueeXMinus(&efekMarque);
    }
    else if (efekMarqueClear.mode == XMinus)
    {
      dmd.marqueeClearXMinus(&efekMarqueClear);
    }
    else if(modeInformasi)
    {
      modeInformasi = false;
      tampilanUtama();
    }
  }

  if (bluetooth->available())
  {
    byte tokenMulai = bluetooth->read();
    if(tokenMulai == 0xFE)
    {
      delay(2);
      byte perintah = bluetooth->read();
      delay(2);
      byte panjang = bluetooth->read();

      for(uint16_t i=0;i<panjang;i++)
      {
        delay(2);
        char c = bluetooth->read();
        bufferBluetooth[i] = c;
      }
      delay(2);
      byte tokenSelesai = bluetooth->read();
      if(tokenSelesai == 0xFF)
      {
        uint16_t i;
        switch(perintah)
        {
          case initPerangkat:
            bluetooth->write(237);
            break;
          case pointAplus:
            pointA++;
            break;
          case pointAminus:
            pointA--;
            break;
          case pointBplus:
            pointB++;
            break;
          case pointBminus:
            pointB--;
            break;
          case skorAplus:
            skorA++;
            break;
          case skorAminus:
            skorA--;
            break;
          case skorBplus:
            skorB++;
            break;
          case skorBminus:
            skorB--;
            break;
          case rondePlus:
            ronde++;
            break;
          case rondeMinus:
            ronde--;
            break;
          case menitPlus:
            menit++;
            break;
          case menitMinus:
            menit--;
            break;
          case resetPoint:
            pointA = 0;
            pointB = 0;
            break;
          case resetSemua:
            pointA = 0;
            pointB = 0;
            skorA = 0;
            skorB = 0;
            ronde = 0;
            menit = 0;
            detik = 0;
            break;
          case mulai:
            pertandinganBerjalan = true;
            break;
          case text:
            for(i=0;i<panjang;i++)
            {
              strInformasi[i] = bufferBluetooth[i];
            }
            strInformasi[i] = 0;
            dmd.clearScreen();
            dmd.selectFont(fontSkor);
            efekMarqueClear.mode = XMinus;
            efekMarque.init = true;
            efekMarque.alamat = strInformasi;
            efekMarque.mode = XMinus;
            modeInformasi = true;
        
            millisEfek = millis();
            break;
        }
        if(pointA == 255)
          pointA = 0;
        if(pointB == 255)
          pointB = 0;
        if(skorA == 255)
          skorA = 0;
        if(skorB == 255)
          skorB = 0;
        if(ronde == 255)
          ronde = 0;
        if(menit == 255)
          menit = 0;
        
        if (!modeInformasi)
        {
          tampilanUtama();
        }
      }
    }
  }
}
byte bin2bcd(byte val)
{
  return val + 6 * (val / 10);
}

void tampilanUtama()
{
  byte lebarText;
  dmd.selectFont(fontSkor);
  Arial14TengahY = (dmd.height - dmd.fontHeader.height) / 2;

  boxSkorA.clear();
  boxSkorB.clear();
  dmd.drawString((28-lebarText)/2 + 0, Arial14TengahY, String(pointA));
  lebarText = dmd.stringWidth(String(pointB));
  dmd.drawString((28 - lebarText)/2 + 68, Arial14TengahY, String(pointB));
  

  dmd.selectFont(SystemFont5x7);
  char waktu[] = "00:00";
  byte menitBCD = bin2bcd(menit);
  byte detikBCD = bin2bcd(detik);
  waktu[0] = (menitBCD >> 4) + 0x30;
  waktu[1] = (menitBCD & 0x0F) + 0x30;
  waktu[3] = (detikBCD >> 4) + 0x30;
  waktu[4] = (detikBCD & 0x0F) + 0x30;
  lebarText = dmd.stringWidth(waktu);
  dmd.drawString((dmd.width - lebarText)/2, 0, String(waktu));

  dmd.selectFont(SystemFont5x7Gemuk);

  dmd.drawString(28, 8, String(skorA));
  lebarText = dmd.stringWidth(String(skorB));
  dmd.drawString((68 - lebarText), 8, String(skorB));

  lebarText = dmd.stringWidth(String(ronde));
  dmd.drawString((dmd.width - lebarText)/2, 8, String(ronde));
}

screenshot apk papan skor

File papan skor android

 

Properti tabel dinamis app inventor dengan WebViewer

MIT App Inventor 2 menawarkan kemudahan dalam membuat aplikasi apk android. Namun dibalik kesederhanaannya App Inventor memiliki kelemahan pada aplikasi dengan jumlah komponen yang banyak. Penyebabnya adalah area design yang terbatas dan bersifat semi statis.

Perancangan tabel dinamis AI2 ini dapat memudahkan dalam merancang apk aplikasi android dengan fitur:

  1. Komponen/Palette bisa disesuaikan, sementara yang tersedia adalah tabel, judul, label, input checkbox, input select 1-4, input byte, input int, input slider, input float, input text, button, button horizontal, horizontal line.
  2. Palette tabel dengan tipe yang tersedia : label increment, label integer, label text, input checkbox, input select 1-4, input byte, input int, input slider, input float, input text, button.
  3. Penggunaan WebViewer memungkinkan tampilan sizeable atau dapat diperbesar/diperkecil.

Designer Dinamis App Inventor dengan WebViewer

Untuk menggunakan platform ini, buatlah di designer cukup satu komponen saja yaitu : WebViewer

upload file “semesin.html”, file ini berisi script yang berfungsi untuk berkomunikasi antara webViewer dengan apk app Inventor.

<body onload="document.getElementById('content').innerHTML = window.AppInventor.getWebViewString();">
<div id="content"></div>
</body>
<script type="text/javascript">
  function checkboxChange(id, value)
  {
    if (value == true)
    {
      window.AppInventor.setWebViewString(id + ',1');
    }
    else
    {
      window.AppInventor.setWebViewString(id + ',0');
    }
  }
  function inputChange(id, value)
  {
    window.AppInventor.setWebViewString(id + ',' + value);
  }
</script>

fungsi window.AppInventor.getWebViewString() adalah mengambil nilai dari app inventor (dalam format html)

sedangkan window.AppInventor.setWebViewString() berfungsi mengirimkan data ke app inventor.

 

Struktur properti dan tabel dinamis menggunakan App Inventor

Untuk membangun skema properti App Inventor, buatlah list dalam format berikut :

  • List teks
  • List tipe
  • List nilai

Apabila  list tipe bernilai tabel maka list nilai diisi dengan format :

  • List tabel teks
  • List tabel tipe
  • List tabel nilai

berikut ini contoh struktur properti designer dinamis webViewer:

disamping itu perlu juga dibangun mekanisme handler/penanganan khususnya tombol-tombol mirip properti button.onclick().

contoh tampilan membuat tabel dengan mit app inventor :

File membuat tabel dinamis dengan app inventor:

  1. DinamikPropertiWebViewer.aia
  2. DinamikPropertiWebViewer.apk
  3. semesin.html