Ramazan Eren Arslan
Published © GPL3+

Bus Display

Aren't you bored of being late and missing your bus every time? With this display you can view the upcoming busses and not have to hurry.

IntermediateFull instructions provided8 hours41
Bus Display

Things used in this project

Hardware components

64*64 P3 Led Matrix
×1
Wemos D1 Mini
Espressif Wemos D1 Mini
×1
Usb 2A Power Supply
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Code

Arduino Code

Arduino
#include <WiFi.h>
#include <time.h>
#include <vector>
#include <algorithm>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>

/* ================= WIFI ================= */
const char* ssid     = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

/* ================= TIME ================= */
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 3600;     // adjust for your timezone
const int   daylightOffset_sec = 3600;

int lastBrightness = 120;  // initial brightness
unsigned long lastBusUpdate = 0;        // tracks last bus schedule update
const unsigned long BUS_UPDATE_INTERVAL = 10000;
/* ================= MATRIX ================= */
#define PANEL_WIDTH 64
#define PANEL_HEIGHT 64  	// Panel height of 64 will required PIN_E to be defined.
#define PANELS_NUMBER 2 
#define PIN_E 21
#define PIN_D 2
#define POT_PIN 32  // POTENTIOMETER PIN

#define PANE_WIDTH PANEL_WIDTH * PANELS_NUMBER
#define PANE_HEIGHT PANEL_HEIGHT

MatrixPanel_I2S_DMA* dma_display;

/* ================= ROUTE DEFINITIONS ================= */
struct BusRoute {
  const char* route;
  const char* destination;
};

/* PUT IN YOUR BUS ROUTES*/
BusRoute routes[] = {
  {"14",  "Hubland"},
  {"14",  "Bahnhof"},
  {"114", "UniSport"},
  {"114", "Bahnhof"},
  {"214", "Bibliotk"},
  {"214", "Bahnhof"},
  {"29",  "TGZ"},
  {"29",  "Bahnhof"}
};

const int ROUTE_COUNT = sizeof(routes) / sizeof(routes[0]);

/* ================= BUS SCHEDULE TABLES ================= */
/* ---- REPLACE TIMES WITH REAL DATA ---- */

/* 14 → Am Hubland */
const char* bus14_hub_weekday[]  = {
"04:43",  
"05:12","05:40","05:53"

};
const char* bus14_hub_saturday[] = {
"00:23",
"01:23"

};
const char* bus14_hub_sunday[]   = {
"00:23",
"01:23"

};

/* 14 → Hauptbahnhof */
const char* bus14_hbf_weekday[]  = {
"05:03","05:33",
"06:07","06:19","06:45"

};
const char* bus14_hbf_saturday[] = {
"05:59",
"06:29","06:59"
 
};
const char* bus14_hbf_sunday[]   = {
"07:29",
"08:29"

};

/* 114 → Am Hubland */
const char* bus114_hub_weekday[] = {
"07:37","07:47","07:57",
"08:07","08:27","08:47",
"09:07","09:27","09:47"
};
const char* bus114_hub_saturday[] = {};
const char* bus114_hub_sunday[]   = {};

/* 114 → Hauptbahnhof */
const char* bus114_hbf_weekday[]  = {
  "07:33",
  "08:16","08:36","08:56",
  "09:16","09:36","09:56",
  "10:16","10:36","10:56"
};
const char* bus114_hbf_saturday[] = {};
const char* bus114_hbf_sunday[]   = {};

/* 214 → Am Hubland */
const char* bus214_hub_weekday[] = {
"07:20","07:37"
};
const char* bus214_hub_saturday[] = {};
const char* bus214_hub_sunday[]   = {};

/* 214 → Hauptbahnhof */
const char* bus214_hbf_weekday[]  = {
  "07:17","07:33",
  "08:04","08:22"
};
const char* bus214_hbf_saturday[] = {};
const char* bus214_hbf_sunday[]   = {};

/* 29 → Am Hubland */
const char* bus29_hub_weekday[] = {
"06:17","06:47",
"07:17","07:32"
};
const char* bus29_hub_saturday[] = {
"06:31",
"07:31",
"08:31",
"09:01"
};
const char* bus29_hub_sunday[]   = {
"06:31",
"07:31",
"08:31"
};

/* 29 → Hauptbahnhof */
const char* bus29_hbf_weekday[]  = {
  "06:12","06:42",
  "07:07","07:22","07:37","07:52",
  "08:07","08:22","08:52",
  "09:22","09:52"
};
const char* bus29_hbf_saturday[] = {};
const char* bus29_hbf_sunday[]   = {};

/* ================= SCHEDULE REGISTRY ================= */
struct BusSchedule {
  const char** weekday;
  int weekdayCount;
  const char** saturday;
  int saturdayCount;
  const char** sunday;
  int sundayCount;
};

BusSchedule schedules[] = {
  {bus14_hub_weekday,  sizeof(bus14_hub_weekday)/sizeof(char*),
   bus14_hub_saturday, sizeof(bus14_hub_saturday)/sizeof(char*),
   bus14_hub_sunday,   sizeof(bus14_hub_sunday)/sizeof(char*)},

  {bus14_hbf_weekday,  sizeof(bus14_hbf_weekday)/sizeof(char*),
   bus14_hbf_saturday, sizeof(bus14_hbf_saturday)/sizeof(char*),
   bus14_hbf_sunday,   sizeof(bus14_hbf_sunday)/sizeof(char*)},

  {bus114_hub_weekday,  sizeof(bus114_hub_weekday)/sizeof(char*),
   bus114_hub_saturday, sizeof(bus114_hub_saturday)/sizeof(char*),
   bus114_hub_sunday,   sizeof(bus114_hub_sunday)/sizeof(char*)},

  {bus114_hbf_weekday,  sizeof(bus114_hbf_weekday)/sizeof(char*),
   bus114_hbf_saturday, sizeof(bus114_hbf_saturday)/sizeof(char*),
   bus114_hbf_sunday,   sizeof(bus114_hbf_sunday)/sizeof(char*)},

  {bus214_hub_weekday,  sizeof(bus214_hub_weekday)/sizeof(char*),
   bus214_hub_saturday, sizeof(bus214_hub_saturday)/sizeof(char*),
   bus214_hub_sunday,   sizeof(bus214_hub_sunday)/sizeof(char*)},

  {bus214_hbf_weekday,  sizeof(bus214_hbf_weekday)/sizeof(char*),
   bus214_hbf_saturday, sizeof(bus214_hbf_saturday)/sizeof(char*),
   bus214_hbf_sunday,   sizeof(bus214_hbf_sunday)/sizeof(char*)},

  {bus29_hub_weekday,  sizeof(bus29_hub_weekday)/sizeof(char*),
   bus29_hub_saturday, sizeof(bus29_hub_saturday)/sizeof(char*),
   bus29_hub_sunday,   sizeof(bus29_hub_sunday)/sizeof(char*)},

  {bus29_hbf_weekday,  sizeof(bus29_hbf_weekday)/sizeof(char*),
   bus29_hbf_saturday, sizeof(bus29_hbf_saturday)/sizeof(char*),
   bus29_hbf_sunday,   sizeof(bus29_hbf_sunday)/sizeof(char*)}
};

const int SCHEDULE_COUNT = sizeof(schedules) / sizeof(schedules[0]);

/* ================= UPCOMING STRUCT ================= */
struct UpcomingBus {
  int minutes;
  int index;
};

/* ================= UTILS ================= */
int timeToMinutes(const char* t) {
  return (t[0]-'0')*600 + (t[1]-'0')*60 +
         (t[3]-'0')*10  + (t[4]-'0');
}

/* ================= DISPLAY ================= */
void drawBuses(const std::vector<UpcomingBus>& list) {
  dma_display->clearScreen();
  dma_display->setTextSize(1);

  const int startX = 6;
  const int startY = 5;
  const int rowHeight = 12; // 8px font + 4px gap

  for (int i = 0; i < list.size() && i < 5; i++) {
    const BusRoute& r = routes[list[i].index];
    int y = startY + i * rowHeight;

    dma_display->setCursor(startX, y);

    /* BUS NUMBER — GREEN */
    dma_display->setTextColor(dma_display->color565(0, 255, 0));
    dma_display->printf("%-3s ", r.route);

    /* DESTINATION — WHITE */
    dma_display->setTextColor(dma_display->color565(255, 255, 255));
    dma_display->printf("%-8s ", r.destination);

    /* TIME — RED */
    dma_display->setTextColor(dma_display->color565(0, 255, 0));
    dma_display->printf("%2d min", list[i].minutes);
    
  }
}


/* ================= SETUP ================= */
void setup() {
  Serial.begin(115200);

  HUB75_I2S_CFG mxconfig;
  mxconfig.mx_height = PANEL_HEIGHT;      // we have 64 pix heigh panels
  mxconfig.chain_length = PANELS_NUMBER;  // we have 2 panels chained
  mxconfig.gpio.e = PIN_E;  
  mxconfig.gpio.d = PIN_D;  
  dma_display = new MatrixPanel_I2S_DMA(mxconfig);
  dma_display->begin();
  dma_display->setBrightness8(120);

  analogReadResolution(12);             // 0–4095
  analogSetPinAttenuation(POT_PIN, ADC_11db);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);

  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}

/* ================= LOOP ================= */
void loop() {
  int raw = analogRead(POT_PIN);               // read ADC
  int brightness = map(raw, 0, 4095, 120, 0); // map to 120–0
  brightness = constrain(brightness, 0, 140);

// only update if the difference is bigger than 200
  if (abs(brightness - lastBrightness) > 15) {
    dma_display->setBrightness8(brightness);
    lastBrightness = brightness;
  }
  // -----------------------------
  //  Non-blocking bus update
  // -----------------------------
  unsigned long now = millis();
  if (now - lastBusUpdate >= BUS_UPDATE_INTERVAL) {
    lastBusUpdate = now;

    // get time
    struct tm timeinfo;
    if (!getLocalTime(&timeinfo)) return;

    int nowMin = timeinfo.tm_hour * 60 + timeinfo.tm_min;
    int wday = timeinfo.tm_wday; // 0=Sunday

    std::vector<UpcomingBus> upcoming;

    // iterate schedules
    for (int i = 0; i < SCHEDULE_COUNT; i++) {
      const char** times;
      int count;

      if (wday == 0) {
        times = schedules[i].sunday;
        count = schedules[i].sundayCount;
      } else if (wday == 6) {
        times = schedules[i].saturday;
        count = schedules[i].saturdayCount;
      } else {
        times = schedules[i].weekday;
        count = schedules[i].weekdayCount;
      }

      for (int t = 0; t < count; t++) {
        int busMin = timeToMinutes(times[t]);
        if (busMin >= nowMin) {
          upcoming.push_back({busMin - nowMin, i});
        }
      }
    }

    // sort and draw
    std::sort(upcoming.begin(), upcoming.end(),
      [](const UpcomingBus& a, const UpcomingBus& b) {
        return a.minutes < b.minutes;
      });

    drawBuses(upcoming);
  }

  // -----------------------------
  // ️Small delay to prevent CPU hogging
  // -----------------------------
  delay(10);  // 10 ms is enough; brightness updates continuously
}

Credits

Ramazan Eren Arslan
4 projects • 1 follower
Electrical Engineering Student @ THWS

Comments