/*
Program     : Percobaan Observasi Sinyal Respon Kontrol menggunakan Arduino Mega
DiBuat Oleh : EruP-RMK
Keluaran    : v1.00 / v3.00
Tanggal     : 12 September 2018 - 8 Agustus 2020 / 21 Juni 2023 /6 Agustus 2023
Untuk       : Praktikum Kontrol Cerdas 2 POLITEKNIK ELEKTRONIKA NEGERI SURABAYA (PENS)

Perangkat   : Arduino Mega, Plant Emulasi, Koneksi ADC Emulasi dan Pin Port I/O
              (sebagai PWM) Emulasi
*/

float SettingPoint=0;       // Setting Point
float Kecepatan=0;          // Kecepatan Plant (Motor) saat ini
float PWM=0;                // nilai PWM yang dikeluarkan 8 bit (0-255)
float adc=0;                // nilai ADC hasil pembacaan sensor 10 bit (0-1023)

const int MOTOR=3;          // Nomor PIN untuk output PWM (emulasi PWM oleh Arduino)
const int SENSOR=A0;        // Nomot PIN ADC

float TmMotor=1;            // Motor naik dari 0% sampai 63% selama berapa detik
                            // Ganti nilai Tm dengan NomorUrut/10

float out_plant=0;          // Keluaran dari plant, hasil dari Tachometer (0v - 5v)
float oMotor=0;             // Keluaran Motor saat ini (0 rpm sampan Max rpm)

float BebanMotor=0;         // Pembebanan motor, 0% sampai 100%
float TeganganMotorMax=5;   // Tegangan dari Motor, dianggap Motor 5V
float KecepatanMax=3000;    // Kecepatan tertinggi dari sistem
float RpmMax=2000;          // Kecepatan tertinggi Diaplay
float RpmMin=0;             // Kecepatan terrendah Diaplay

#define PeriodeSampling 20  // waktu sampling 20ms

unsigned long t1, t2, S;    // menghitung interval antar sampling
                            // untuk perhitungan plant
unsigned long t_lalu;       // waktu sampling sebelumnya

int   Periode=0;            // panjang periode (Setting Point on/off)
                            // dalam satuan sampling, Periode=0 -> tidak periodik
int   Random=0;             // nilai Setting Point acak
int   Index=0;              // penghitung sampling (Loop)
float SetP=0;               // BackUp Setting Point
float Error, MSE=RpmMax;    // Error saat ini dan Error Kuadrad rata-rata 

float Lineritas(float x)    // Menghitung ketidak-linearan Km dari plant berdasarkan
{                           // input, hasil regresi Polinomial pengujian Km plant real
  // y=-0.4541*I2^6-15.024*I2^5+49.092*I2^4-54.251*I2^3+21.936*I2^2-0.6329*I2+0.0057
  if(x < 0) x = 0;
  if( x > 1) x = 1;
  return -0.4541*pow(x,6)-15.024*pow(x,5)+49.092*pow(x,4)-54.251*pow(x,3)+21.936*x*x
         -0.6329*x+0.0057;
}

float plant(float Tegangan)               // Emulasi plant secara software
{                                         // Menggunakan pendekatan pengisian Kapasitor
                                          // Dengan formula Km Tm (lihat teori)
  t2=millis();                            // berapa waktu sekarang ? Dalam satuan ms
  S=t2-t1;                                // Hitung interval antar sampling (t1 & t2)
  float tSampling=float(S)/1000.0;        // Interval untuk menghitung waktu Sampling
  t1=t2;                                  // untuk perhitungan berikutnya
  
  float KmMotor=(1-BebanMotor/100)                  // Km Plant akibat pembebanan
                *KecepatanMax/TeganganMotorMax;     // dan perbandingan rpm/volt plant
  float V=Tegangan/TeganganMotorMax;                // Perbandingan Vin dari Vin Max
  float KmLineritas=Lineritas(V);                   // Hitung Ketidak-linearan Km plant
  float iMotor=Tegangan*KmMotor*KmLineritas;        // Hitung rpm yang masuk plant
  oMotor=oMotor+(iMotor-oMotor)*tSampling/TmMotor;  // Hitung rpm keluaran plant
  if( oMotor < 0) oMotor = 0;                       // plant tidak bisa mundur      
  return oMotor;
}

int _analogRead(int ch)               // ADC Emulasi, input dari Tachometer, 0v - 5v
{                                     // sesuai dengan 0rpm sampai KecepatanMax Plant
  float Vi=out_plant*5/KecepatanMax;  // Sensor Tachometer, dari rmp ke Vi (0v-5v)
  return Vi*1023/5;                   // ADC 10 bit, Referensi 5v resolusi 10 bit
}                                     // Input 0v-5v diubah menjadi 0-1023

void _analogWrite(int pin, int PWM)   // DAC Emulasi, output ke Driver Plant
{                                     // Tegangan 0v-5v
  float Vo=PWM*5.0/255.0;             // dari PWM (0-255) diubah ke Vo (0v-5v)
  out_plant=plant(Vo);                // dari Vo (0v-5v) masuk ke Plant menjadi rpm
}

void Kontrol()                  // Proses utama dari sistem Kontrol,
{                               // hanya input output untuk observasi Sistem
  adc=_analogRead(SENSOR);          // Baca tegangan sensor menggunakan ADC 
                                    // sebanding dengan kecepatan/keluaran Plant
                                    // dari Tachometer 0-MaxRpm menjadi Vin ADC 0v-5v
                                    // keluaran ADC 0-1023 (ADC 10 bit VRef 5v)
  Kecepatan=adc*KecepatanMax/1023;  // Konversi dari biner ADC ke Kecepatan rpm
  
  Error=SettingPoint-Kecepatan;     // Hitung Error
  MSE=Error*Error*0.001+MSE*0.999;  // Hitung MSE menggunakan teknik Smoothing
  if(MSE>RpmMax) MSE=RpmMax;        // Batasi nilai MSE, biar tidak terlalu besar
  
  PWM=SettingPoint*255/KecepatanMax;  // Tentukan nilai PWM (data biner) berdasarkan
                                      // Setting Point yang telah ditentukan
  if(PWM>255) PWM=255;                // Nilai PWM Maksimal 255
  if(PWM<0) PWM=0;                    // Nilai PWM Minimal 0
  _analogWrite(MOTOR,PWM);            // Kirim ke PLANT melalui PORT I/O PWM emulasi
                                      // dalam bentuk program dalam Arduino)
  analogWrite(LED_BUILTIN,PWM);       // tampilkan juga dalam bentuk nyala LED,
                                      // untuk mengetahui besarnya sinyal PWM
}

float cut(float Data)           // Membatasi tampilan pada Plotter
{                               // harus diantara RpmMin dan RpmMax
  if(Data>RpmMax) Data=RpmMax;  // Jika melebihi RpmMax, batasi
  if(Data<RpmMin) Data=RpmMin;  // atau kurang dari RpmMin, batasi
  return Data;  
}

void Tampilkan()                  // Untuk mengirimkan data-data sistem ke PC
{                                 // melalui komunikasi Serial
  Serial.print("SetPoint=");      // Tampilkan Label "SetPoint=1000" (misalkan) di atas             
  Serial.print(SettingPoint,0);   // gambar grafik. Angka "0" artinya tanpa pecahan
  Serial.print(" Kecepatan=");    // Tampilkan Label Kecepatan
  Serial.print(Kecepatan,0);      // Tiap label harus dipisah dengan spasi " "
  Serial.print(" PWM=");
  Serial.print(PWM,0);
  Serial.print(" Error=");
  Serial.print(Error,0);
  Serial.print(" MSE=");
  Serial.print(MSE,0);
  Serial.print(" Min Max");
  Serial.print(" tSampling=");  // Tampilkan juga Waktu Sampling berdasarkan
  Serial.print(S);              // pengukuran waktu antar sampling (cek bagian Plant)
  Serial.println("ms");         // Untuk melihat, apakah waktu sampling sesuai
                                // dengan yang diinginkan
                                // Jika waktu sampling tertulis melebihi yang
                                // diinginkan, artinya waktu sampling yang diinginkan
                                // terlalu cepat, yang tidak dapat diikuti oleh
                                // kecepatan sistem

  Serial.print(SettingPoint,0);   // Kirim data grafik ke program monitor
                                  // melalui komunikasi serial (Serial Plotter)
  Serial.write(' ');              // tiap data harus dipisah dengan spasi " "
  Serial.print(cut(Kecepatan),0); // Kirim data Kecepatan (bilangan bulat)
  Serial.write(' ');              // tiap data harus dipisah dengan spasi " "
  Serial.print(PWM,0);            // tanda 0 artinya tidak ada nilai pecahan
  Serial.write(' ');                            
  Serial.print(cut(Error),0);     // Tampilkan Error
  Serial.write(' ');              // Dan MSE, jika melebihi
  Serial.print(cut(MSE),0);       // max, potong
  Serial.write(' ');
  Serial.print(RpmMin);           // Berikan nilai Min dan Max
  Serial.write(' ');              // agar skala grafik tetap tidak berubah
  Serial.println(RpmMax);         // dan akhiri dengan karakter <LF>
}

void BacaSerial()                     // Menerima data perintah dari PC
{                                     // melalui komunikasi serial
  if(Serial.available())              // Apakah ada penerimaan data serial dari PC ?
  {
    switch(Serial.read())             // akhiri setiap perintah dengan ";" atau "<CR>"
    {                                 // contoh "s1000;" atau "s1000<CR>"
      case 's':                       // Jika Ya, apakah dimulai huruf depan 's' ?
        SetP=Serial.parseInt();       // Jika Ya, baca data integer berikutnya
        if(SetP<0) SetP=0;            // dan simpan sebagai Setting Point
        else                          // Misal data yang diterima, "s1000<CR>"
        if(SetP>RpmMax)SetP=RpmMax;   // Set Carriage Return pada terminal,
        Index=0;                      // agar saat enter, lansung dikirimkan CR
        break;
      case 't':                           // 't0.001' - 't10' Tm 0.01 sampai Tm 10.00
        TmMotor=Serial.parseFloat();      // Tm dihitung dalam detik, untuk setiap
        if(TmMotor<0.001) TmMotor=0.001;  // output plant naik dari 0% sampai 63%
        else if(TmMotor>10) TmMotor=10;   // dari kecepatan tertinggi dari output plant
        break;                            // (kondisi steady state dari plant)
      case 'l':                           // 'l0' - 'l100' tanpa load (0%)
        BebanMotor=Serial.parseInt();     // sampai load penuh (plant berhenti) 100%
        if(BebanMotor<0) BebanMotor=0;    // Mensimulasikan efek pembebanan pada plant
        else
        if(BebanMotor>100) BebanMotor=100;
        break;
      case 'p':                           // 'p0' periodik mati, 'p100' periodik
        Periode=Serial.parseInt();        // dihitung persatuan sampling
        Periode=(Periode/2)*2;            // harus genap
        if(Periode<0) Periode=0;          // Min 0 (mati)
        else
        if(Periode>1000) Periode=1000;    // Max Periode 1000 sampling
        Index=0;                          // setiap pergantian periode, mulai hitungan
        break;                            // dari nol
      case 'r':                           // 'r0' random mati, 'r1' random aktif
        Random=Serial.parseInt();         // Setting Point diberikan secara ramdom
        if(Random<0) Random=0;            // digunakan saat melakukan pelatihan sistem
        else if(Random>1) Random=1;       // adaptif atau NN
        randomSeed(analogRead(0));
        break;
      case 'm':                             // 'm0' - 'm10000'
        RpmMax=Serial.parseInt();           // Menentukan batas tertinggi dari sistem
        if(RpmMax<0) RpmMax=0;              // hanya untuk memudahkan proses menampilkan
        else if(RpmMax>10000) RpmMax=10000; // sinyal sistem kontrol
        break;
    }
  }   
}

void SetPoint()                 // Untuk pengaturan Setting Point
{                               // Statis (tetap), Periodik atau Random
  if(Index==0)                                      // setiap awal hitungan
    SettingPoint=(Random==0?SetP:random(0,RpmMax)); // tentukan Setting Point
  if(Periode>0&&Index*2==Periode)                   // dalam mode Periodik
    SettingPoint=(Random==0?0:random(0,RpmMax));    // maka tiap 1/2 periode diubah
                                                    // 0 atau sesuai bilangan random
                                                    
  if(Periode>0)                 // Jika setting point dibuat Periodik
  {
    Index++;                    // naikkan hitungan
    if(Index>=Periode)          // Maka setiap hitungan mencapai periode
      Index=0;                  // kembalikan hitungan ke 0
  }
}

void setup()                                // Bagian yang pertama dikerjakan
{                                           // hanya satu kali
  Serial.begin(115200);                     // sesuaikan dengan serial pada Komputer
  Serial.println(" ");                      // Cetak Label (Legenda) kosong
  t_lalu=t1=millis();                       // catat waktu mulai,
                                            // untuk perhitungan Periode Sampling
  randomSeed(analogRead(0)*analogRead(1));  // inisialisasi bilangan random
}

void loop()                     // Bagian yang dikerjakan secara
{                               // terus-menerus oleh Arduino
  BacaSerial();                 // Baca perintah serial dari PC             
  unsigned long t=millis();     // Baca waktu saat ini, dalam ms
  if(t-t_lalu>=PeriodeSampling) // Apakah dengan waktu sebelumnya
  {                             // sudah terpaut 1 periode sampling ?
    SetPoint();                 // Jika ya, atur nilai Setting Point
    Kontrol();                  // Jika ya, lakukan operasi sampling
                                // untuk menjalankan kontrol
    Tampilkan();                // Tampilkan data-data sistem ke PC
                                // melalui komunikasi serial
    t_lalu=t;                   // dan catat waktu sekarang sebagai waktu lalu,
                                // untuk perhitungan 1 sampling berikutnya
  }
}
mega:SCL
mega:SDA
mega:AREF
mega:GND.1
mega:13
mega:12
mega:11
mega:10
mega:9
mega:8
mega:7
mega:6
mega:5
mega:4
mega:3
mega:2
mega:1
mega:0
mega:14
mega:15
mega:16
mega:17
mega:18
mega:19
mega:20
mega:21
mega:5V.1
mega:5V.2
mega:22
mega:23
mega:24
mega:25
mega:26
mega:27
mega:28
mega:29
mega:30
mega:31
mega:32
mega:33
mega:34
mega:35
mega:36
mega:37
mega:38
mega:39
mega:40
mega:41
mega:42
mega:43
mega:44
mega:45
mega:46
mega:47
mega:48
mega:49
mega:50
mega:51
mega:52
mega:53
mega:GND.4
mega:GND.5
mega:IOREF
mega:RESET
mega:3.3V
mega:5V
mega:GND.2
mega:GND.3
mega:VIN
mega:A0
mega:A1
mega:A2
mega:A3
mega:A4
mega:A5
mega:A6
mega:A7
mega:A8
mega:A9
mega:A10
mega:A11
mega:A12
mega:A13
mega:A14
mega:A15