Untitled

 avatar
unknown
c_cpp
9 months ago
10 kB
16
Indexable
/*
(Dalilul Falihin)
22/506495/TK/55478

TUGAS UTS
SISTEM TERTANAM DAN IOT

"Mini PLC on ESP32"
*/

#include <Arduino.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"

// ================= CONFIG =================
#define NUM_INPUTS 4
#define NUM_OUTPUTS 4

const int inputPins[NUM_INPUTS]  = {18, 19, 22, 23}; // tombol
const int outputPins[NUM_OUTPUTS] = {5, 12, 14, 15};   // lampu/relay
const int ANALOG_PIN = 34; // Potensiometer ADC input

#define MAX_IL_LINES 256
#define SCAN_CYCLE_MS 100 / portTICK_PERIOD_MS
#define DEBOUNCE_MS 40 / portTICK_PERIOD_MS
#define IO_POLL_MS 20 / portTICK_PERIOD_MS

// ================= Data Structures =================
typedef struct { int pin; bool value; } IOEvent;
typedef struct { String opcode; String operand; } ParsedLine;

// ================= Global State =================
String incomingLines[MAX_IL_LINES];
int incomingCount = 0;
ParsedLine program[MAX_IL_LINES];
int programLineCount = 0;
volatile bool receivingFile = false;
volatile bool fileReceived = false;
volatile bool programLoaded = false;
volatile bool programRunning = false;

int analogValue = 0; // global analog
bool lastStable[NUM_INPUTS] = {0};
TickType_t lastChangeTime[NUM_INPUTS] = {0};

// ================= Kernel Objects =================
SemaphoreHandle_t xConfigMutex;
SemaphoreHandle_t xFileReady;
QueueHandle_t ioQueue;
TimerHandle_t systemTimer;

// ================= Utility Functions =================
int operandToInputIndex(const String &op) {
  String s = op; s.trim(); s.toUpperCase();
  if (s == "IN1") return 0;
  if (s == "IN2") return 1;
  if (s == "IN3") return 2;
  if (s == "IN4") return 3;
  return -1;
}

int operandToOutputPin(const String &op) {
  String s = op; s.trim(); s.toUpperCase();
  if (s == "Q1") return outputPins[0];
  if (s == "Q2") return outputPins[1];
  if (s == "Q3") return outputPins[2];
  if (s == "Q4") return outputPins[3];
  return -1;
}

// ================= Analog Task =================
void analogTask(void *pvParameters) {
  while (1) {
    analogValue = analogRead(ANALOG_PIN);
    vTaskDelay(50 / portTICK_PERIOD_MS);
  }
}

// ================= Interrupt Handler Example =================
volatile bool interruptFlag = false;
void IRAM_ATTR inputISR() {
  interruptFlag = true;
}

// ================= Serial Task =================
void serialTask(void *pvParameters) {
  Serial.println("SerialTask Ready. Send PROGRAM_START ... PROGRAM_END");
  String curLine = "";

  while (1) {
    if (Serial.available()) {
      char c = Serial.read();
      if (c == '\r') continue;
      if (c == '\n') {
        String L = curLine; L.trim();
        if (xSemaphoreTake(xConfigMutex, (TickType_t)10) == pdTRUE) {
          if (L == "PROGRAM_START") {
            receivingFile = true;
            incomingCount = 0;
            fileReceived = false;
            programLoaded = false;
            programRunning = false;
            Serial.println("[OK] START_RECEIVING");
          } else if (L == "PROGRAM_END") {
            receivingFile = false;
            fileReceived = true;
            Serial.println("[OK] END_RECEIVING");
          } else if (L == "RUN") {
            programRunning = true;
            Serial.println("[OK] PROGRAM RUN");
          } else if (L == "STOP") {
            programRunning = false;
            Serial.println("[OK] PROGRAM STOP");
          } else if (receivingFile && L.length() > 0) {
            if (incomingCount < MAX_IL_LINES) {
              incomingLines[incomingCount++] = L;
              Serial.printf("[LINE %d] %s\n", incomingCount, L.c_str());
            }
          }
          xSemaphoreGive(xConfigMutex);
        }
        curLine = "";
      } else curLine += c;
    }
    vTaskDelay(10 / portTICK_PERIOD_MS);
  }
}

// ================= Parser Task =================
void parserTask(void *pvParameters) {
  while (1) {
    if (fileReceived) {
      if (xSemaphoreTake(xConfigMutex, portMAX_DELAY) == pdTRUE) {
        programLineCount = 0;
        for (int i = 0; i < incomingCount; i++) {
          String ln = incomingLines[i]; ln.trim();
          if (ln.length() == 0 || ln.startsWith(";")) continue;
          String op = ln, operand = "";
          int idx = ln.indexOf(' ');
          if (idx > 0) { op = ln.substring(0, idx); operand = ln.substring(idx + 1); operand.trim(); }
          op.toUpperCase();
          program[programLineCount].opcode = op;
          program[programLineCount].operand = operand;
          programLineCount++;
        }
        fileReceived = false;
        programLoaded = true;
        xSemaphoreGive(xConfigMutex);
        Serial.printf("PARSE COMPLETE | %d lines ready\n", programLineCount);
        xSemaphoreGive(xFileReady);
      }
    }
    vTaskDelay(50 / portTICK_PERIOD_MS);
  }
}

// ================= IO Handler Task =================
void ioHandlerTask(void *pvParameters) {
  for (int i = 0; i < NUM_INPUTS; i++) {
    pinMode(inputPins[i], INPUT_PULLDOWN);
    attachInterrupt(digitalPinToInterrupt(inputPins[i]), inputISR, CHANGE);
    lastStable[i] = digitalRead(inputPins[i]);
    lastChangeTime[i] = xTaskGetTickCount();
  }
  for (int j = 0; j < NUM_OUTPUTS; j++) {
    pinMode(outputPins[j], OUTPUT);
    digitalWrite(outputPins[j], LOW);
  }

  IOEvent ev;
  while (1) {
    if (interruptFlag) {
      interruptFlag = false;
      Serial.println("[INT] Input changed!");
    }

    // Debounce check
    for (int i = 0; i < NUM_INPUTS; i++) {
      bool sample = digitalRead(inputPins[i]);
      if (sample != lastStable[i]) {
        TickType_t now = xTaskGetTickCount();
        if ((now - lastChangeTime[i]) >= DEBOUNCE_MS) {
          lastStable[i] = sample;
          lastChangeTime[i] = now;
          Serial.printf("IN%d -> %d\n", i + 1, sample);
        }
      }
    }

    while (xQueueReceive(ioQueue, &ev, 0) == pdTRUE) {
      digitalWrite(ev.pin, ev.value ? HIGH : LOW);
      Serial.printf("OUT PIN %d => %d\n", ev.pin, ev.value);
    }
    vTaskDelay(IO_POLL_MS);
  }
}

// ================= Executor Task =================
void executorTask(void *pvParameters) {
  bool acc = false;
  bool inputState[NUM_INPUTS] = {0};
  bool lastOutputState[NUM_OUTPUTS] = {0};

  while (1) {
    if (xSemaphoreTake(xFileReady, 0) == pdTRUE)
      Serial.println("[EXEC] Program ready (use RUN).");

    if (programLoaded && programRunning) {
      for (int i = 0; i < NUM_INPUTS; i++) inputState[i] = lastStable[i];
      acc = false;

      if (xSemaphoreTake(xConfigMutex, portMAX_DELAY) == pdTRUE) {
        for (int i = 0; i < programLineCount; i++) {
          String op = program[i].opcode;
          String operand = program[i].operand;
          op.toUpperCase();

          if (op == "LD") {
            if (operand == "ANA1") acc = (analogValue > 0);
            else {
              int idx = operandToInputIndex(operand);
              acc = (idx != -1) ? inputState[idx] : operand.toInt() != 0;
            }
          } else if (op == "GT") {
            acc = (analogValue > operand.toInt());
          } else if (op == "AND") {
            int idx = operandToInputIndex(operand);
            bool val = (idx != -1) ? inputState[idx] : operand.toInt() != 0;
            acc = acc && val;
          } else if (op == "OR") {
            int idx = operandToInputIndex(operand);
            bool val = (idx != -1) ? inputState[idx] : operand.toInt() != 0;
            acc = acc || val;
          } else if (op == "NOT") {
            acc = !acc;
          } else if (op == "ST" || op == "OUT") {
            int pin = operandToOutputPin(operand);
            if (pin != -1) {
              int outIndex = -1;
              for (int j = 0; j < NUM_OUTPUTS; j++)
                if (outputPins[j] == pin) outIndex = j;
              bool outVal = acc;
              if (outIndex != -1 && lastOutputState[outIndex] != outVal) {
                IOEvent ev = { pin, outVal };
                xQueueSend(ioQueue, &ev, 0);
                lastOutputState[outIndex] = outVal;
              }
            }
          }
        }
        xSemaphoreGive(xConfigMutex);
      }
      vTaskDelay(SCAN_CYCLE_MS);
    } else {
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
  }
}

// ================= Timer Callback =================
void systemTimerCallback(TimerHandle_t xTimer) {
  Serial.printf("[TIMER] Heap: %u | ANA1: %d | Running: %s\n",
                esp_get_free_heap_size(), analogValue,
                programRunning ? "YES" : "NO");
}

// ================= Setup =================
void setup() {
  Serial.begin(115200);
  while (!Serial) vTaskDelay(10 / portTICK_PERIOD_MS);

  xConfigMutex = xSemaphoreCreateMutex();
  xFileReady = xSemaphoreCreateBinary();
  ioQueue = xQueueCreate(16, sizeof(IOEvent));
  systemTimer = xTimerCreate("SysTimer", pdMS_TO_TICKS(5000), pdTRUE, 0, systemTimerCallback);
  xTimerStart(systemTimer, 0);

  pinMode(ANALOG_PIN, INPUT);

  xTaskCreatePinnedToCore(serialTask, "Serial", 4096, NULL, 3, NULL, 1);
  xTaskCreatePinnedToCore(parserTask, "Parser", 4096, NULL, 2, NULL, 1);
  xTaskCreatePinnedToCore(ioHandlerTask, "IO", 4096, NULL, 2, NULL, 0);
  xTaskCreatePinnedToCore(executorTask, "Exec", 4096, NULL, 2, NULL, 0);
  xTaskCreatePinnedToCore(analogTask, "Analog", 2048, NULL, 1, NULL, 0);

  Serial.println("=== PLC Mini FULL BONUS EDITION Ready ===");
  Serial.println("Use IL:");
  Serial.println("PROGRAM_START");
  Serial.println("LD ANA1");
  Serial.println("GT 2000");
  Serial.println("ST Q1");
  Serial.println("PROGRAM_END");
  Serial.println("RUN");
}

void loop() {
  vTaskDelete(NULL);
}
Editor is loading...
Leave a Comment