Program ESP32 untuk Multiplexer 64 Channel dengan FIFO dan Web Interface + LCD:
```cpp
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SD.h>
#include <SPI.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
// Konfigurasi Hardware
#define MUX_S0 13
#define MUX_S1 12
#define MUX_S2 14
#define MUX_S3 27
#define MUX_SIG 36
#define SD_CS 5
// Konfigurasi LCD I2C
#define LCD_ADDRESS 0x27
#define LCD_COLS 16
#define LCD_ROWS 2
// Konfigurasi Threshold
#define ACTIVE_THRESHOLD 2000
#define DEBOUNCE_TIME 100
// Konfigurasi FIFO
#define MAX_FIFO_ENTRIES 10
// Konfigurasi WiFi
const char* ssid = "YourSSID";
const char* password = "YourPassword";
// Struktur Data Channel
struct Channel {
int id;
String name;
int value;
bool active;
unsigned long timestamp;
};
// FIFO
Channel fifo[MAX_FIFO_ENTRIES];
int fifoCount = 0;
// Variabel Global
Channel channels[64];
WebServer server(80);
LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_COLS, LCD_ROWS);
void setup() {
Serial.begin(115200);
// Inisialisasi Pin
pinMode(MUX_S0, OUTPUT);
pinMode(MUX_S1, OUTPUT);
pinMode(MUX_S2, OUTPUT);
pinMode(MUX_S3, OUTPUT);
pinMode(MUX_SIG, INPUT);
// Inisialisasi LCD
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Initializing...");
// Inisialisasi SD Card
if (!SD.begin(SD_CS)) {
lcd.clear();
lcd.print("SD Card Failed");
while (1);
}
// Baca file channels.csv
if (!loadChannelNames()) {
lcd.clear();
lcd.print("Channel CSV Error");
while (1);
}
// Hubungkan ke WiFi
WiFi.begin(ssid, password);
lcd.clear();
lcd.print("Connecting WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
lcd.print(".");
}
lcd.clear();
lcd.print("WiFi Connected");
lcd.setCursor(0, 1);
lcd.print(WiFi.localIP());
delay(2000);
// Setup Web Server
setupWebServer();
// Tampilkan konfigurasi sistem
printSystemConfig();
}
void loop() {
server.handleClient();
// Baca semua channel
readAllChannels();
// Update FIFO
updateFifo();
// Update LCD
updateLcd();
// Kirim data FIFO ke Serial jika perlu
if (Serial.available()) {
if (Serial.read() == 'f') {
sendFifoToSerial();
}
}
delay(50);
}
// Fungsi untuk membaca nilai dari multiplexer
int readMux(int channel) {
digitalWrite(MUX_S0, bitRead(channel, 0));
digitalWrite(MUX_S1, bitRead(channel, 1));
digitalWrite(MUX_S2, bitRead(channel, 2));
digitalWrite(MUX_S3, bitRead(channel, 3));
return analogRead(MUX_SIG);
}
// Baca semua 64 channel
void readAllChannels() {
for (int i = 0; i < 64; i++) {
channels[i].value = readMux(i);
channels[i].active = (channels[i].value > ACTIVE_THRESHOLD);
channels[i].timestamp = millis();
}
}
// Update FIFO dengan data channel aktif
void updateFifo() {
// Hapus entri yang tidak aktif lagi
for (int i = 0; i < fifoCount; i++) {
int channelId = fifo[i].id;
if (!channels[channelId].active) {
// Geser semua entri setelahnya ke depan
for (int j = i; j < fifoCount - 1; j++) {
fifo[j] = fifo[j + 1];
}
fifoCount--;
i--; // Periksa lagi posisi ini karena ada pergeseran
}
}
// Tambahkan channel aktif baru ke FIFO
for (int i = 0; i < 64; i++) {
if (channels[i].active) {
bool alreadyInFifo = false;
// Cek apakah channel sudah ada di FIFO
for (int j = 0; j < fifoCount; j++) {
if (fifo[j].id == i) {
alreadyInFifo = true;
// Update nilai dan timestamp
fifo[j].value = channels[i].value;
fifo[j].timestamp = channels[i].timestamp;
break;
}
}
// Jika belum ada di FIFO dan masih ada slot
if (!alreadyInFifo && fifoCount < MAX_FIFO_ENTRIES) {
fifo[fifoCount].id = i;
fifo[fifoCount].name = channels[i].name;
fifo[fifoCount].value = channels[i].value;
fifo[fifoCount].active = true;
fifo[fifoCount].timestamp = channels[i].timestamp;
fifoCount++;
}
}
}
// Urutkan FIFO berdasarkan timestamp (terlama pertama)
sortFifoByTimestamp();
}
// Urutkan FIFO berdasarkan timestamp
void sortFifoByTimestamp() {
for (int i = 0; i < fifoCount - 1; i++) {
for (int j = i + 1; j < fifoCount; j++) {
if (fifo[i].timestamp > fifo[j].timestamp) {
Channel temp = fifo[i];
fifo[i] = fifo[j];
fifo[j] = temp;
}
}
}
}
// Load nama channel dari SD card
bool loadChannelNames() {
File file = SD.open("/channels.csv");
if (!file) {
return false;
}
while (file.available()) {
String line = file.readStringUntil('\n');
line.trim();
int commaPos = line.indexOf(',');
if (commaPos == -1) continue;
String idStr = line.substring(0, commaPos);
String name = line.substring(commaPos + 1);
int id = idStr.toInt();
if (id >= 0 && id < 64) {
channels[id].id = id;
channels[id].name = name;
}
}
file.close();
return true;
}
// Update tampilan LCD
void updateLcd() {
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate < 1000) return;
lastUpdate = millis();
// Hitung channel aktif
int activeChannels = 0;
for (int i = 0; i < 64; i++) {
if (channels[i].active) activeChannels++;
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Active: ");
lcd.print(activeChannels);
lcd.setCursor(0, 1);
lcd.print("FIFO: ");
lcd.print(fifoCount);
}
// Setup Web Server
void setupWebServer() {
server.on("/", HTTP_GET, []() {
String html = "<html><head><title>ESP32 Multiplexer Monitor</title>";
html += "<style>table {border-collapse: collapse;} th, td {border: 1px solid #ddd; padding: 8px;}</style>";
html += "</head><body>";
html += "<h1>Active Channels</h1>";
html += "<table><tr><th>ID</th><th>Name</th><th>Value</th><th>Active</th><th>Timestamp</th></tr>";
for (int i = 0; i < fifoCount; i++) {
html += "<tr>";
html += "<td>" + String(fifo[i].id) + "</td>";
html += "<td>" + fifo[i].name + "</td>";
html += "<td>" + String(fifo[i].value) + "</td>";
html += "<td>" + String(fifo[i].active ? "Yes" : "No") + "</td>";
html += "<td>" + String(fifo[i].timestamp) + "</td>";
html += "</tr>";
}
html += "</table>";
html += "<h2>Upload New channels.csv</h2>";
html += "<form method='POST' action='/upload' enctype='multipart/form-data'>";
html += "<input type='file' name='csvfile' accept='.csv'>";
html += "<input type='submit' value='Upload'>";
html += "</form>";
html += "<h2>Download Current channels.csv</h2>";
html += "<a href='/download'>Download channels.csv</a>";
html += "</body></html>";
server.send(200, "text/html", html);
});
server.on("/download", HTTP_GET, []() {
File file = SD.open("/channels.csv");
if (file) {
server.sendHeader("Content-Type", "text/csv");
server.sendHeader("Content-Disposition", "attachment; filename=channels.csv");
server.streamFile(file, "text/csv");
file.close();
} else {
server.send(404, "text/plain", "File not found");
}
});
server.on("/upload", HTTP_POST, []() {
server.send(200, "text/plain", "Upload complete. Please restart the device.");
}, []() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
SD.remove("/channels.csv");
File file = SD.open("/channels.csv", FILE_WRITE);
file.close();
} else if (upload.status == UPLOAD_FILE_WRITE) {
File file = SD.open("/channels.csv", FILE_APPEND);
if (file) {
file.write(upload.buf, upload.currentSize);
file.close();
}
} else if (upload.status == UPLOAD_FILE_END) {
// Validasi format file
if (!validateCsvFormat()) {
SD.remove("/channels.csv");
server.send(400, "text/plain", "Invalid CSV format");
}
}
});
server.begin();
}
// Validasi format CSV
bool validateCsvFormat() {
File file = SD.open("/channels.csv");
if (!file) return false;
bool valid = true;
int lineCount = 0;
while (file.available() && valid) {
String line = file.readStringUntil('\n');
line.trim();
if (line.length() > 0) {
int commaPos = line.indexOf(',');
if (commaPos == -1) {
valid = false;
break;
}
String idStr = line.substring(0, commaPos);
int id = idStr.toInt();
if (id < 0 || id >= 64) {
valid = false;
break;
}
lineCount++;
}
}
file.close();
return valid && (lineCount == 64);
}
// Debug: Print konfigurasi sistem
void printSystemConfig() {
Serial.println("System Configuration:");
Serial.println("---------------------");
Serial.println("Multiplexer Pins:");
Serial.print("S0-S3: "); Serial.print(MUX_S0); Serial.print(", "); Serial.print(MUX_S1);
Serial.print(", "); Serial.print(MUX_S2); Serial.print(", "); Serial.println(MUX_S3);
Serial.print("SIG: "); Serial.println(MUX_SIG);
Serial.print("SD CS: "); Serial.println(SD_CS);
Serial.print("LCD Address: 0x"); Serial.println(LCD_ADDRESS, HEX);
Serial.print("Active Threshold: "); Serial.println(ACTIVE_THRESHOLD);
Serial.print("Max FIFO Entries: "); Serial.println(MAX_FIFO_ENTRIES);
Serial.println("---------------------");
}
// Kirim data FIFO ke Serial Monitor
void sendFifoToSerial() {
Serial.println("Current FIFO Contents:");
Serial.println("ID\tName\t\tValue\tActive\tTimestamp");
Serial.println("-----------------------------------------------");
for (int i = 0; i < fifoCount; i++) {
Serial.print(fifo[i].id); Serial.print("\t");
Serial.print(fifo[i].name);
if (fifo[i].name.length() < 8) Serial.print("\t");
Serial.print("\t");
Serial.print(fifo[i].value); Serial.print("\t");
Serial.print(fifo[i].active ? "Yes" : "No"); Serial.print("\t");
Serial.println(fifo[i].timestamp);
}
Serial.println("-----------------------------------------------");
}
```
## Penjelasan Program:
1. **Konfigurasi Hardware**:
- Menggunakan 4 pin kontrol (S0-S3) untuk multiplexer CD74HC4067
- Pin GPIO36 (MUX_SIG) untuk membaca sinyal analog
- SD card dihubungkan via SPI dengan CS pin GPIO5
- LCD I2C di alamat 0x27 dengan ukuran 16x2
2. **FIFO (Antrian Data Channel Aktif)**:
- Menyimpan hingga 10 entri (MAX_FIFO_ENTRIES)
- Setiap entri berisi: ID, nama, nilai, status aktif, dan timestamp
- Hanya menyimpan channel dengan nilai > ACTIVE_THRESHOLD (2000)
- Menghapus entri saat channel tidak aktif lagi
- Mengurutkan berdasarkan timestamp (fungsi sortFifoByTimestamp)
3. **Web Interface**:
- Menampilkan tabel channel aktif di root path ("/")
- Memungkinkan upload file CSV baru via form POST
- Menyediakan link untuk download file channels.csv saat ini
- Validasi format CSV saat upload (fungsi validateCsvFormat)
4. **Pembacaan MUX & Channel**:
- Membaca semua 64 channel secara berurutan (fungsi readAllChannels)
- Menggunakan threshold 2000 (ACTIVE_THRESHOLD) untuk deteksi aktif
- Nama channel dimuat dari file channels.csv di SD card (fungsi loadChannelNames)
5. **LCD Display**:
- Baris pertama menampilkan jumlah channel aktif
- Baris kedua menampilkan jumlah data di FIFO
- Diupdate setiap 1 detik (fungsi updateLcd)
6. **Fungsi Tambahan**:
- `printSystemConfig()` untuk debugging konfigurasi sistem
- `sendFifoToSerial()` untuk mengirim data FIFO ke serial monitor (dipicu dengan mengirim 'f' ke serial)
- Validasi format CSV saat upload
## Instruksi Penggunaan:
1. Buat file channels.csv di SD card dengan format:
```
0,Channel 1
1,Channel 2
...
63,Channel 64
```
2. Upload program ke ESP32
3. Hubungkan hardware sesuai konfigurasi pin
4. Akses antarmuka web melalui IP yang ditampilkan di LCD
5. Untuk debugging, buka Serial Monitor dan ketik 'f' untuk melihat isi FIFO
Program ini akan secara otomatis mendeteksi channel aktif, menyimpannya dalam FIFO, dan menampilkannya di LCD serta antarmuka web.
