/*
Program : Percobaan Kontroler NN 2 menggunakan Arduino Mega
DiBuat Oleh : EruP-RMK
Keluaran : v1.00
Tanggal : 01 Juli 2005 - NN Versi MCS52
05 Desember 2008 - NN Versi AVR ATmega8
30 Oktober 2019 - NN Versi Arduino
12 September 2018 - 8 Agustus 2020 - Observasi Plant
17 Nopember 2020 - 19 Nopember 2020 - NN + Observasi Plant
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
*/
const int MOTOR=3; // Nomor PIN untuk output PWM (emulasi PWM oleh Arduino)
const int SENSOR=A0; // Nomot PIN ADC
//float Kecepatan=0, SettingPoint=0, PWM=0, adc;
//float out_plant=0;
//float alfa=0.2; // semakin kecil Alfa, TM semakin besar
//float Load=0; // Pembebanan, 0% = tanpa beban, 100% = beban maksimal
//int Periode=200; // panjang periode (Setting Point on/off) dalam satuan sampling, Periode=0 -> tidak periodik
//int Random=1; // nilai Setting Point acak
//int Index=0; // penghitung sampling (Loop)
//float SetP=0; // BackUp Setting Point
//float VinMin=0.25; // Tegangan plant minimal, antara 0 volt sampai 5 volt
//int RpmMax=2000; // Display Max
float Error, dE, eL, MSE=2e5;
/*
float plant(float vin) // Plant Emulasi
{
static float v1=0, v2=0;
vin-=VinMin; if(vin<0) vin=0; // dilengkapi dengan titik nyala minimal
v1=vin*.8+v1*.2;
v2=v1*.5+v2*.5;
return v2*alfa+out_plant*(1-alfa);
}
int _analogRead(int ch) // ADC Emulasi
{
return out_plant*1023/5*(1.0-Load); // VRef 5 Volt resolusi 10 bit
}
void _analogWrite(int pin, int PWM) // DAC Emulasi
{
out_plant = plant(PWM*5.0/255); // VRef 5 Volt resolusi 8 bit
}
*/
/*
Adaptive Polinomial
y=ax5+bx4+cx3+dx2+ex+f
*/
#define ORDE 6
float K[ORDE+1]={0,0,0,0,0,0,0};
float Poly(float x)
(
float Z=1;
float P=0;
for(i=0;i<=ORDE;i++)
{
P+=K[i]*Z;
Z*=Z;
}
)
void UpdatePoly(float x, float e)
{
#define LR 0.01
float Z=1;
float P=0;
for(i=0;i<=ORDE;i++)
{
K[i]+=LR*e*Z;
Z*=Z;
}
}
/*
Demo NN, SImple NN dengan
- 1 Input Node
- 1 LApis Hidden Layer, 5-beberapa Node
- 1 Output Neuron
- Domain Data [0,1]
*/
#define nInput 2
#define MaxHidden 50 // Max Jumlah Node/Neuron pada Hidden Layer
float LearningRate = 0.06;// jangan 5, terlalu besar // dapat diubah dengan perintah "R0.5;"
unsigned char nHidden = 25; // dapat diubah dengan perintah "H10;"
float InNN[nInput]; // Data Masukan NN, 1 input
float OutNN; // Data Keluaran NN, Output Neouron, setelah fungsi Aktifasi, 1 output
float NH[MaxHidden]; // Keluaran Hidden Neuron, setelah Aktifasi
float eH[MaxHidden]; // Error Propagasi Hidden Neuron, setelah Aktifasi
float BH[MaxHidden]; // Bias dari Hidden Neuron
float WH[MaxHidden][nInput]; // Bobot Masukan Hidden Neuron
float WO[MaxHidden]; // Bobot Masukan dari Output Neuron
float BO; // Bias Output Neuron
float ErrNN; // Error untuk sistem update NN
float SkalaInput = 1.0/200.0;
float SkalaOutput = 100;
void Init_NN(void)
{
unsigned char i,h;
for(h=0;h<nHidden;h++)
{
for(i=0;i<nInput;i++)
{
WH[h][i]=(float)random(-10000,10001)/10000.0; // Bangkitkan random untuk tiap bobot, [-1,1]
//Serial.print(" W="); Serial.print((float)random(-1000,1001)/1000);
//Serial.print(" WH[h][i]="); Serial.print(WH[h][i]);
}
BH[h]=0; // Nilai Awal Bias=0
WO[h]=(float)random(-10000,10001)/10000.0; // Bangkitkan random untuk tiap bobot, [-1,1]
//Serial.print(" WO[h]="); Serial.print(WO[h]);
}
BO=0;
}
float sigmoid(float x)
{
if(x>5) return 1; // Untuk menghindarkan OverFlow,
else if(x<-5) return 0; // Jika x>5 set y=1, Jika x<5, y=0
else return (float)1/(1+exp(-x)); // Diantara itu, hitung normal
}
void HitungMaju(void)
{
float zo,zh;
unsigned char i,h;
zo=0;
for(h=0;h<nHidden;h++)
{
zh=0;
//Serial.print(" InNN[0]="); Serial.print(InNN[0]);
//Serial.print(" InNN[1]="); Serial.print(InNN[1]);
for(i=0;i<nInput;i++)
{
zh+=InNN[i]*WH[h][i];
//Serial.print(" InNN[0]="); Serial.print(InNN[i]);
//Serial.print(" WH[h][0]="); Serial.print(WH[h][i]);
//Serial.print(" zh="); Serial.print(zh);
}
zh+=BH[h];
NH[h]=sigmoid(zh); // Hitung keluaran masing-masing Hidden Neuron
zo+=NH[h]*WO[h]; // Sekaligus hitung masukan Sigma dari Output Neuron
}
//Serial.print(" zo"); Serial.print(zo);
OutNN=sigmoid(zo+BO); // Hitung keluaran dari Output Neuron
//Serial.print(" OutNN"); Serial.print(OutNN);
}
void UpdateNN(void)
{
unsigned char i,h;
float eo, eh;
eo=ErrNN*OutNN*(1-OutNN); // temporary, biar cepat
for(h=0;h<nHidden;h++)
{
eH[h]=WO[h]*eo; // selesaikan sebelum update WO
WO[h]+=LearningRate*eo*NH[h]; // update bobot output
eh=eH[h]*NH[h]*(1-NH[h]); // temporary, biar cepat
for(i=0;i<nInput;i++)
WH[h][i]+=LearningRate*eh*InNN[i];
BH[h]+=LearningRate*eh;
}
BO+=LearningRate*eo;
}
void setup() // Bagian yang pertama dikerjakan oleh Arduino, hanya satu kali
{
Serial.begin(115200); // sesuaikan dengan serial pada program Monitor
Serial.println(" ");
randomSeed(analogRead(0)); // atur nilai awal bilangan random, dari ADC
//Init_NN();
}
#define NTabel 25
float Tabel[25][4]={
//L C Perkiraan Banjir Bahaya
{175,175,24,16}, //1
{175,150,23,12}, //1
{175,100,22,8}, //1
{175,50,21,4}, //1
{175,25,20,0}, //1
{150,175,19,12}, //1
{150,150,18,9}, //1
{150,100,17,6}, //1
{150,50,16,3}, //1
{150,25,15,0}, //1
{100,175,14,8}, //5
{100,150,13,6}, //5
{100,100,12,4}, //5
{100,50,11,2}, //5
{100,25,10,0}, //5
{50,175,9,4}, //9
{50,150,8,3}, //9
{50,100,7,2}, //9
{50,50,6,1}, //9
{50,25,5,0}, //9
{25,175,4,0}, //9
{25,150,3,0}, //9
{25,100,2,0}, //9
{25,50,1,0}, //9
{25,25,0,0}}; //16
void Learning()
{
#define TOL 0.1
float MSE=1e30;
float tMSE=1;
int iterasi=0;
//Init_NN();
float LR=0.05;
LearningRate=LR;
while(tMSE>TOL&&iterasi<10000)
{
iterasi++;
Serial.print("#");
Serial.print(iterasi);
MSE=0;
for(int i=0;i<25;i++)
{
float L=Tabel[i][0];
float C=Tabel[i][1];
float K=Tabel[i][2];
float B=Tabel[i][3];
InNN[0]=L*SkalaInput;
InNN[1]=C*SkalaInput;
//Serial.print("L:"); Serial.print(L); Serial.print(" ");
//Serial.print("C:"); Serial.print(C); Serial.print(" ");
//Serial.print("K:"); Serial.print(K); Serial.print(" ");
//Serial.print("B:"); Serial.print(B); Serial.print(" ");
HitungMaju();
//Serial.print("NN:"); Serial.print(OutNN*SkalaOutput); Serial.print(" ");
ErrNN=B-OutNN*SkalaOutput;
//Serial.print("e:"); Serial.print(ErrNN); Serial.println();
MSE+=(ErrNN*ErrNN);
//ErrNN=ErrNN*1/tMSE;
UpdateNN();
}
//Serial.print(" MSEz=");
//Serial.print(MSE,6);
MSE=sqrt(MSE)/25;
tMSE=0.9*tMSE+0.1*MSE;
LearningRate=0.01/tMSE;
Serial.print(" MSE=");
Serial.println(MSE,6);
//Serial.print(" tMSE=");
//Serial.println(tMSE,6);
//Serial.println();
}
}
void Running()
{
float MSE=0;
MSE=0;
for(int i=0;i<25;i++)
{
float L=Tabel[i][0];
float C=Tabel[i][1];
float K=Tabel[i][2];
float B=Tabel[i][3];
InNN[0]=L*SkalaInput;
InNN[1]=C*SkalaInput;
Serial.print("#:"); Serial.print(i+1); Serial.print(" ");
Serial.print("L:"); Serial.print(L); Serial.print(" ");
Serial.print("C:"); Serial.print(C); Serial.print(" ");
Serial.print("K:"); Serial.print(K); Serial.print(" ");
Serial.print("B:"); Serial.print(B); Serial.print(" ");
HitungMaju();
Serial.print("NN:"); Serial.print(OutNN*SkalaOutput); Serial.print(" ");
ErrNN=B-OutNN*SkalaOutput;
Serial.print("e:"); Serial.print(ErrNN); Serial.println();
MSE+=(ErrNN*ErrNN);
}
Serial.print("MSEz=");
Serial.print(MSE,6);
MSE=sqrt(MSE)/25;
Serial.print(" MSE=");
Serial.println(MSE,6);
Serial.println();
}
void CetakTabel()
{
for(int i=0;i<25;i++)
{
Serial.print(i+1);
Serial.print(" ");
Serial.print(Tabel[i][0]);
Serial.print(" ");
Serial.print(Tabel[i][1]);
Serial.print(" ");
Serial.print(Tabel[i][2]);
Serial.print(" ");
Serial.print(Tabel[i][3]);
Serial.println();
}
}
/*
Perintah Serial
T - Lihat Tabel
Tn L C K B - Mengisi Tabel Prediksi, contoh T1 20 50 10 20
Ln - Kalibrasi Level Ketinggian Air Sungai
P - Perintah memulai Pelatihan NN
R - Uji Running
R L C - Uji Running NN, contoh R 20 30
I - Inisialisasi
H - Help
*/
void loop() // Bagian yang dikerjakan secara terus-menerus oleh Arduino
{
if(Serial.available()) // Apakah ada penerimaan data serial dari PC ?
{
float L, C, K, B;
int n;
switch(Serial.read()) // akhiri setiap perintah dengan ';' contoh "s1000;"
{
case 't': // Tabel Presiksi
case 'T': // Jika Ya, apakah data dimulai dengan huruf depan 's' ?
n=Serial.parseInt(); // nomor baris Tabel
if(n==0)
{
CetakTabel();
break;
}
L=Serial.parseFloat(); // Level Ketinggian Sungai
C=Serial.parseFloat(); // Curah Hujan
K=Serial.parseFloat(); // Kemungkinan Banjir
B=Serial.parseFloat(); // Bahaya
Tabel[n][0]=L;
Tabel[n][1]=C;
Tabel[n][2]=K;
Tabel[n][3]=B;
CetakTabel();
break;
case 'L':
case 'l':
L=Serial.parseFloat();
// S = Baca Sensor Ketinggian
// Tinggi = Konstanta - S
// Hitung, Konstanta = L + S
break;
case 'p':
case 'P': // Ubah Timing plant, "a0.0;" - "a1.0;" alfa 0.00 sampai alfa 1.00
Learning();
break;
case 'r':
case 'R': // Ubah beban Plant, "l0;" - "l100;" load mati (0%) sampai load 100%
L=Serial.parseFloat();
if(L==0)
Running();
else
{
C=Serial.parseFloat();
InNN[0]=L*SkalaInput;
InNN[1]=C*SkalaInput;
HitungMaju();
Serial.println(OutNN);
}
break;
case 'I':
case 'i':
Init_NN();
break;
case 'h':
case 'H': // Ubah Timing plant, "a0.0;" - "a1.0;" alfa 0.00 sampai alfa 1.00
Serial.println("Perintah Serial");
Serial.println("Tn L C K B - Mengisi Tabel Prediksi, contoh T1 20 50 10 20");
Serial.println("Ln - Kalibrasi Level Ketinggian Air Sungai");
Serial.println("P - Perintah memulai Pelatian NN");
Serial.println("R L C - Uji Running NN, contoh R 20 30");
Serial.println("I - Inisialisasi/Hapus Pelatian");
Serial.println("H - Help");
break;
}
} // Misal data yang diterima, "s1000"<CR>
// Set Carriage Return pada terminal, agar saat enter, lansung dikirimkan CR
//if(Index==0)
// SettingPoint=(Random==0?SetP:random(0,RpmMax));
//if(Periode>0&&Index*2==Periode)
// SettingPoint=(Random==0?0:random(0,RpmMax));
//adc=_analogRead(SENSOR); // Baca tegangan sensor dari PORT ADC, tegangan sensor sebanding dengan kecepatan/keluaran PLANT
//Kecepatan=adc*RpmMax/1023; // Konversi dari ADC ke Kecepatan (dari data biner ADC ke RPM)
//Error=SettingPoint-Kecepatan;
//dE=Error-eL;
//eL=Error;
//MSE=Error*Error*0.0002+MSE*0.9998;
// ubah jumlah input (nInput) sesuai dengan yang diinginkan
// contoh, hanya input error dan feedback kecepatan
//satu input
//InNN[0]=SettingPoint/RpmMax;
//InNN[0]=Error/(2*RpmMax)+0.5; // InNN=[0,1] normalisasi
//InNN[0]=dE/(2*RpmMax)+0.5; // InNN=[0,1] normalisasi
//InNN[0]=Kecepatan/RpmMax;
//dua input
//InNN[0]=SettingPoint/RpmMax;
//InNN[0]=Error/(2*RpmMax)+0.5;
//InNN[1]=dE/(2*RpmMax)+0.5; // InNN=[0,1] normalisasi
//InNN[1]=Kecepatan/RpmMax;
//tiga input
//InNN[2]=SettingPoint/RpmMax;
//InNN[0]=Error/(2*RpmMax)+0.5; // InNN=[0,1] normalisasi
//InNN[1]=dE/(2*RpmMax)+0.5; // InNN=[0,1] normalisasi
//InNN[1]=Kecepatan/RpmMax;
// empat input
//InNN[2]=SettingPoint/RpmMax;
//InNN[0]=Error/(2*RpmMax)+0.5; // InNN=[0,1] normalisasi
//InNN[1]=dE/(2*RpmMax)+0.5; // InNN=[0,1] normalisasi
//InNN[3]=Kecepatan/RpmMax;
//HitungMaju(); // Hitung dari InNN[0,1] ke OutNN[0,1]
//ErrNN=Error/RpmMax; // Error untuk Update NN, nilai [-1,1], dari Error[-2000,2000]
//UpdateNN(); // Dari ErrNN, lakukan Update secara OnLine
//PWM=OutNN*255; // Tentukan nilai PWM (data biner) berdasarkan Setting Point yang telah ditentukan
//if(PWM<0) PWM=0; if(PWM>255) PWM=255;
//_analogWrite(MOTOR,PWM); // Kirim ke PLANT melalui PORT I/O (sinyal PWM yang di-emulasi dalam bentuk program dalam Arduino)
/*
Serial.print("SetPoint="); // Tampilkan SetPoint ke Keterangan, SetPoint=1000
Serial.print(SettingPoint,0);
Serial.print(" Kecepatan="); // Tampilkan Kecepatan
Serial.print(Kecepatan,0);
Serial.print(" PWM=");
Serial.print(PWM,0);
Serial.print(" Error=");
Serial.print(Error,0);
Serial.print(" MSE=");
Serial.print(MSE,0);
Serial.print(" Max=");
Serial.print(RpmMax);
Serial.print(" Hidden=");
Serial.print(nHidden);
Serial.print(" LR=");
Serial.println(LearningRate);
Serial.print(SettingPoint,0); // Kirim data ke program monitor melalui komunikasi serial
Serial.write(' ');
Serial.print(Kecepatan,0); // Format Protokol: "1000 850 87 2000"<CR>
Serial.write(' ');
Serial.print(PWM,0); // tanda 0 artinya tidak ada nilai pecahan
Serial.write(' ');
Serial.print(Error>RpmMax?RpmMax:Error<0?0:Error,0);
Serial.write(' ');
Serial.print(MSE/100>RpmMax?RpmMax:MSE/100,0);
Serial.write(' ');
Serial.println(RpmMax); // akhir data serial, dengan karakter <LF-CR>
*/
//if(Periode>0) // Jika setting point dibuat periodik
//{
// Index++;
// if(Index>=Periode) Index=0;
//}
delay(10); // Atur waktu sampling
}