#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Inisialisasi variabel untuk pembacaan kondisi tombol input
const byte pin_tombol_RS = 9; // Deklarasi alamat pin tombol RS untuk mereset Arduino
const byte pin_tombol_PD = 8; // Deklarasi alamat pin tombol PD untuk pengambilan data
const byte pin_tombol_KL = 7; // Deklarasi alamat pin tombol KL untuk klasifikasi data
bool tombol_RS = LOW; // kondisi tombol RS (reset)
bool tombol_PD = LOW; // kondisi tombol PD (pengambilan data)
bool tombol_PD_sebelumnya = LOW; // Kondisi tombol PD sebelumnya
bool tombol_KL = LOW; // kondisi tombol KL (klasifikasi)
bool tombol_KL_sebelumnya = HIGH; // kondisi tombol KL sebelumnya
// Inisialisasi struktur data dan variabel sensor AD8232
struct DataECG {
unsigned int amplitudo;
unsigned long waktu;
void reset(){
amplitudo = 0;
waktu = 0;
}
};
DataECG data_sensor;
// Inisialisasi struktur dan variabel untuk Klasifikasi KNN
const unsigned int jumlah_data_latih = 20;
struct KNN {
byte nilai_k[3] = {3, 5, 7};
bool hasil_klasifikasi[3];
double waktu_komputasi;
};
KNN knn;
struct JarakEuclidean {
unsigned int nomor;
float jarak;
bool kelas; // kelas fitur 'false' = normal, 'true' = AF
};
JarakEuclidean jarakEuclidean[jumlah_data_latih];
const bool af = true;
const bool normal = false;
struct DataFitur {
unsigned int nomor;
float meanRR; // Mean RR (x1)
float medianRR; // Median RR (x2)
float meanQTc; // Mean QTc (x3)
bool kelas; // kelas fitur 'false' = normal, 'true' = af
};
// var
const byte periodeBufferQ = 10;
const byte periodeRR = 4;
const byte periodeQTc = 4;
unsigned long bufferMedian[periodeRR];
unsigned int intervalRR = 0;
unsigned int intervalQT = 0;
float intervalQTc = 0;
// Variabel untuk tampilan LCD
byte mode_tampilan = 0;
byte mode_tampilan_sebelumnya = 0;
unsigned long waktuPengambilanDataTerakhir = 0;
const unsigned int durasiModeTampilan = 3000;
class CircularBuffer {
public:
bool jamak, penuh;
byte indeksRing, periode;
unsigned long waktu[periodeBufferQ], interval[periodeRR];
unsigned int amplitudo[periodeBufferQ], tipe;
float intervalFloat[periodeQTc];
CircularBuffer(byte panjangBuffer, unsigned int tipeBuffer){ // Constructor
tipe = tipeBuffer; // 0:buffer ECG, 1:buffer RR 2:buffer QTc (float)
periode = panjangBuffer;
reset();
}
void insert(unsigned long _waktu, unsigned int _amplitudo){ // tipe 0
waktu[indeksRing] = _waktu;
amplitudo[indeksRing] = _amplitudo;
next();
}
void insert(unsigned long _interval){ // tipe 1
interval[indeksRing] = _interval;
next();
}
void insertFloat(float _interval){ // tipe 2
intervalFloat[indeksRing] = _interval;
next();
}
void next(){
indeksRing++;
penuh = penuh || indeksRing >= periode;
jamak = jamak || true;
indeksRing %= periode;
}
void reset(){
indeksRing = 0;
penuh = false;
jamak = false;
for (byte i=0; i<periode; i++){
if(tipe == 0){
waktu[i] = 0; amplitudo[i] = 0;
} else if(tipe == 1){
interval[i] = 0;
} else if(tipe == 2){
intervalFloat[i] = 0;
}
}
}
DataECG titikMinimum(){ // tipe 0
unsigned int n = (penuh) ? periode: indeksRing;
DataECG titikMinimum = {1024, 0}; // a, w
for (unsigned int i=0; i<n; i++){
if(amplitudo[i]<titikMinimum.amplitudo){
titikMinimum.amplitudo = amplitudo[i];
titikMinimum.waktu = waktu[i];
}
}
return titikMinimum;
}
};
CircularBuffer bufferQ(periodeBufferQ, 0);
CircularBuffer RR(periodeRR, 1);
CircularBuffer QTc(periodeQTc, 2);
// KNN
DataFitur dataLatih[jumlah_data_latih] = {
{1, 716.75, 715, 9.039262, normal},
{2, 968.75, 968.5, 10.619153, normal},
{3, 726.5, 730.5, 10.437728, normal},
{4, 773.5, 781.5, 10.825891, normal},
{5, 853.5, 824.5, 9.315563, normal},
{6, 836, 836, 9.461796, normal},
{7, 664, 668, 11.437618, normal},
{8, 603.5, 597.5, 10.332104, normal},
{9, 634.75, 636.5, 9.684297, normal},
{10, 898.5, 902, 8.417310, normal},
{11, 579, 550, 12.133929, af},
{12, 590, 552, 13.267593, af},
{13, 576, 604, 12.813022, af},
{14, 527, 546, 12.832421, af},
{15, 548, 566, 13.102284, af},
{16, 560, 582, 13.150027, af},
{17, 584, 562, 12.722649, af},
{18, 421, 418, 14.116503, af},
{19, 567, 528, 11.325887, af},
{20, 595, 588, 10.722944, af}
};
DataFitur dataUji;
void ekstraksiFitur(){
// Fitur 1 dan 3 (Mean RR & Mean QTc)
dataUji.meanRR = 0;
dataUji.meanQTc = 0;
for (byte i=0; i<periodeRR; i++){
dataUji.meanRR += RR.interval[i];
dataUji.meanQTc += QTc.intervalFloat[i];
}
dataUji.meanRR /= periodeRR;
dataUji.meanQTc /= periodeQTc;
// Fitur 2 (Median RR)
urutkanDataBuffer(RR.interval, periodeRR); // sorting in place
dataUji.medianRR = bufferMedian[(int)(periodeRR/2)];
if (!(periodeRR % 2)){ // genap
dataUji.medianRR += bufferMedian[(int)(periodeRR/2) - 1];
dataUji.medianRR /= 2;
}
}
void urutkanDataBuffer(unsigned long *buffer, byte panjangBuffer){ // sorting with duplicate
for (byte i=0; i<panjangBuffer; i++){ bufferMedian[i] = buffer[i]; }
for (byte i=0; i<panjangBuffer; i++){
for (byte j=1; j<panjangBuffer; j++){
if (bufferMedian[j-1]>bufferMedian[j]){
float temp = bufferMedian[j-1];
bufferMedian[j-1] = bufferMedian[j];
bufferMedian[j] = temp;
}
}
}
}
void urutkanDataJarakEuclidean(JarakEuclidean *dataArray, byte panjangArray){
byte i=0;
for (byte i=0; i<panjangArray; i++){
for (byte j=1; j<panjangArray; j++){
if (dataArray[j-1].jarak>dataArray[j].jarak){
JarakEuclidean temp = dataArray[j-1];
dataArray[j-1] = dataArray[j];
dataArray[j] = temp;
}
}
}
}
void klasifikasi_KNN(){
knn.waktu_komputasi = micros();
for (unsigned int j=0; j<jumlah_data_latih; j++){
jarakEuclidean[j].nomor = dataLatih[j].nomor; // Menyalin nomor data latih
jarakEuclidean[j].jarak = pow((dataLatih[j].meanRR - dataUji.meanRR),2); // Melakukan penghitungan jarak euclidean
jarakEuclidean[j].jarak += pow((dataLatih[j].medianRR - dataUji.medianRR),2); // Melakukan penghitungan jarak euclidean
jarakEuclidean[j].jarak += pow((dataLatih[j].meanQTc - dataUji.meanQTc),2); // Melakukan penghitungan jarak euclidean
jarakEuclidean[j].jarak = sqrt(jarakEuclidean[j].jarak); // Melakukan penghitungan jarak euclidean
jarakEuclidean[j].kelas = dataLatih[j].kelas; // Menyalin kelas
}
urutkanDataJarakEuclidean(jarakEuclidean, jumlah_data_latih-1); // Pengurutan jarak euclidean secara ascending (menaik)
for(byte k=0; k<3; k++){
byte frekAF = 0; byte frekNormal = 0; // Deklarasi variabel frekuensi kelas untuk pencarian kelas modus
for(byte j=0; j<knn.nilai_k[k]; j++){
((jarakEuclidean[j].kelas) ? frekAF++ : frekNormal++);
}
knn.hasil_klasifikasi[k] = (frekAF>frekNormal ? true: false);
}
knn.waktu_komputasi = (micros() - knn.waktu_komputasi)/1000.f;
}
// Output - LCD
void tampilkanHasilDeteksiRRQTcLCD(){
lcd.setCursor(1,0); lcd.print(F("QT:")); lcd.print(intervalQT); lcd.print(F(" "));
lcd.setCursor(8,0); lcd.print(F(" RR:")); lcd.print(intervalRR); lcd.print(F(" "));
lcd.setCursor(3,1); lcd.print(F("QTc:")); lcd.print(intervalQTc, 3); lcd.print(F(" "));
}
void tampilkanIntervalRRLCD(){
lcd.setCursor(0,0); lcd.print(F(" RR:"));
for(byte i=0; i<2; i++){
lcd.setCursor((i+1)*5,0); lcd.print(RR.interval[i]); lcd.print(F(" "));
lcd.setCursor((i+1)*5,1); lcd.print(RR.interval[i+2]); lcd.print(F(" "));
}
}
void tampilkanIntervalQTcLCD(){
lcd.setCursor(0,0); lcd.print(F("QTc:"));
for(byte i=0; i<2; i++){
lcd.setCursor(((i+1)*5)+i,0); lcd.print(QTc.intervalFloat[i], 2); lcd.print(F(" "));
lcd.setCursor(((i+1)*5)+i,1); lcd.print(QTc.intervalFloat[i+2], 2); lcd.print(F(" "));
}
}
void tampilkanFiturRRLCD(){
lcd.setCursor(1,0); lcd.print(F("Mean Median RR"));
lcd.setCursor(0,1); lcd.print(dataUji.meanRR, 2);
lcd.setCursor(8,1); lcd.print(dataUji.medianRR, 2);
}
void tampilkanFiturQTcLCD(){
lcd.setCursor(4,0); lcd.print(F("Mean QTc"));
lcd.setCursor(5,1); lcd.print(dataUji.meanQTc, 3);
}
void tampilkanDataFiturLCD(){
const byte jumlahModeTampilan = 4;
mode_tampilan = ((millis()-waktuPengambilanDataTerakhir)%(durasiModeTampilan*jumlahModeTampilan))/durasiModeTampilan;
if(mode_tampilan_sebelumnya != mode_tampilan){ lcd.clear(); }
if(mode_tampilan == 0){
tampilkanIntervalRRLCD();
} else if(mode_tampilan == 1){
tampilkanFiturRRLCD();
} else if(mode_tampilan == 2){
tampilkanIntervalQTcLCD();
} else if(mode_tampilan == 3){
tampilkanFiturQTcLCD();
}
mode_tampilan_sebelumnya = mode_tampilan;
}
void tampilkanHasilKlasifikasi(){
lcd.setCursor(0, 0); lcd.print(F("Hsl: ")); // Menampilkan teks ke LCD
for(byte i=0; i<3; i++){ lcd.print(((knn.hasil_klasifikasi[i]) ? "AF " : "Nor ")); }
lcd.setCursor(0, 1); lcd.print(F("Waktu: ")); lcd.print(knn.waktu_komputasi); lcd.print(F(" ms"));
}
void bacaKondisiTombol(){
tombol_RS = !digitalRead(pin_tombol_RS); // Membaca tombol RS untuk reset
tombol_PD = !digitalRead(pin_tombol_PD); // Membaca tombol PD untuk pengambilan fitur Mean_RR, Median_RR, StDev_RR, Min_RR, Maks_RR
tombol_KL = !digitalRead(pin_tombol_KL); // Membaca tombol KL untuk klasifikasi fitur
}
void setup() {
Serial.begin(115200);
pinMode(pin_tombol_RS, INPUT); // Melakukan konfigurasi pin tombol RS sebagai INPUT
pinMode(pin_tombol_PD, INPUT); // Melakukan konfigurasi pin tombol PD sebagai INPUT
pinMode(pin_tombol_KL, INPUT); // Melakukan konfigurasi pin tombol KL sebagai INPUT
lcd.init();
lcd.backlight();
}
// Data Uji Injection
//const int jedaPengujian = 1000;
const int jumlahDataUji = 10;
//byte indeksDataUji = 0;
const unsigned int tabelRR[10][4] = {
{742, 734, 711, 711},
{679, 672, 672, 672},
{640, 633, 641, 640},
{617, 618, 617, 617},
{828, 852, 914, 859},
{532, 876, 528, 532},
{652, 1032, 780, 952},
{764, 392, 568, 464},
{624, 496, 360, 744},
{824, 384, 620, 408}
};
const float tabelQTc[10][4] = {
{9.471478, 9.227668, 9.375733, 9.675756},
{9.287108, 9.335353, 9.335353, 9.026746},
{9.249662, 9.300665, 9.874406, 9.565890},
{11.956770, 11.947092, 11.916511, 11.956770},
{8.688101, 8.325043, 8.533882, 8.529890},
{12.659806, 11.352388, 11.140970, 13.006650},
{13.942036, 9.089563, 10.168831, 8.945211},
{12.156053, 15.152288, 12.084212, 13.184369},
{11.689355, 13.290792, 14.335659, 6.452473},
{10.869035, 16.125807, 13.654729, 13.466007}
};
void loop() {
bacaKondisiTombol();
if(tombol_PD != tombol_PD_sebelumnya || tombol_KL != tombol_KL_sebelumnya){ lcd.clear(); } // Apabila ada tombol yang berubah kondisinya layar LCD dibersihkan
if(tombol_KL == LOW){
if(tombol_KL_sebelumnya == HIGH){
/* Load Data Uji, Inject Buffer RR & QTc */
static byte indeksDataUji = 0;
RR.interval[0] = tabelRR[indeksDataUji][0];
RR.interval[1] = tabelRR[indeksDataUji][1];
RR.interval[2] = tabelRR[indeksDataUji][2];
RR.interval[3] = tabelRR[indeksDataUji][3];
QTc.intervalFloat[0] = tabelQTc[indeksDataUji][0];
QTc.intervalFloat[1] = tabelQTc[indeksDataUji][1];
QTc.intervalFloat[2] = tabelQTc[indeksDataUji][2];
QTc.intervalFloat[3] = tabelQTc[indeksDataUji][3];
ekstraksiFitur();
// Finally
Serial.print(F("Data Uji:"));
Serial.println(indeksDataUji+1);
indeksDataUji++;
indeksDataUji%=jumlahDataUji;
}
// Tampilkan ekstraksi fitur
tampilkanDataFiturLCD(); // Menampilkan data hasil ekstraksi fitur pada LCD
} else if(tombol_KL == HIGH){
if(tombol_KL_sebelumnya == LOW){
//ekstraksiFitur(); // Ekstraksi Fitur
klasifikasi_KNN(); // Melakukan klasifikasi
waktuPengambilanDataTerakhir = millis();
}
tampilkanHasilKlasifikasi(); // Menampilkan hasil klasifikasi saja
}
// Finally
tombol_PD_sebelumnya = tombol_PD; tombol_KL_sebelumnya = tombol_KL; // Menyimpan state tombol sekarang sebagai state tombol sebelumnya
delay(4);
}