{"id":855975,"date":"2026-05-02T09:32:38","date_gmt":"2026-05-02T09:32:38","guid":{"rendered":"https:\/\/elektrobox.ch\/?page_id=855975"},"modified":"2026-06-02T17:13:34","modified_gmt":"2026-06-02T17:13:34","slug":"rgb-led-controller","status":"publish","type":"page","link":"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/","title":{"rendered":"RGB LED Controller"},"content":{"rendered":"\n<style data-wp-block-html=\"css\">\n\/* =========================================================\n   ElektroBox Project Page CSS\n   Shared layout for all project pages\n   Matches the ElektroClock restructured HTML form\n   Paste once into Appearance -> Customize -> Additional CSS\n   ========================================================= *\/\n\n.ebox-project-page,\n.ebox-project-page *,\n.ebox-project-page *::before,\n.ebox-project-page *::after {\n  box-sizing: border-box;\n}\n\n.ebox-project-page {\n  width: 100%;\n  max-width: 1080px;\n  margin: 0 auto;\n  padding: 0 20px;\n  font-family: inherit;\n  line-height: 1.7;\n  color: #1f2933;\n  overflow-x: hidden;\n}\n\n.ebox-project-page h1,\n.ebox-project-page h2,\n.ebox-project-page h3 {\n  color: #111827;\n  line-height: 1.25;\n  overflow-wrap: break-word;\n}\n\n.ebox-project-page h1 {\n  margin: 0 0 16px;\n  font-size: clamp(2.15rem, 4.5vw, 3.5rem);\n  letter-spacing: -0.035em;\n}\n\n.ebox-project-page h2 {\n  margin: 52px 0 16px;\n  padding-bottom: 8px;\n  border-bottom: 2px solid #e5e7eb;\n  font-size: clamp(1.45rem, 2.6vw, 2.05rem);\n}\n\n.ebox-project-page h3 {\n  margin: 28px 0 10px;\n  font-size: 1.2rem;\n}\n\n.ebox-project-page p {\n  margin: 0 0 18px;\n}\n\n.ebox-project-page ul,\n.ebox-project-page ol {\n  margin: 0 0 20px 22px;\n  padding: 0;\n}\n\n.ebox-project-page li {\n  margin: 6px 0;\n}\n\n.ebox-project-page code {\n  padding: 0.08em 0.28em;\n  border-radius: 6px;\n  background: #eef2f7;\n  color: #111827;\n  font-family: Consolas, Monaco, \"Courier New\", monospace;\n  font-size: 0.92em;\n}\n\n\/* Hero *\/\n\n.ebox-hero {\n  margin: 28px 0 34px;\n  padding: clamp(24px, 4vw, 44px);\n  border: 1px solid #e5e7eb;\n  border-radius: 22px;\n  background: linear-gradient(135deg, #f8fafc 0%, #ffffff 55%, #eef6ff 100%);\n  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);\n}\n\n.ebox-hero-with-image {\n  display: grid;\n  grid-template-columns: minmax(0, 1.35fr) minmax(260px, 0.9fr);\n  align-items: center;\n  gap: 32px;\n}\n\n.ebox-hero-text {\n  min-width: 0;\n}\n\n.ebox-hero-label {\n  display: inline-block;\n  margin-bottom: 14px;\n  padding: 6px 14px;\n  border-radius: 999px;\n  background: #e0f2fe;\n  color: #075985;\n  font-size: 0.92rem;\n  font-weight: 800;\n}\n\n.ebox-hero-image {\n  min-width: 0;\n  width: 100%;\n}\n\n.ebox-hero-image img {\n  display: block;\n  width: 100%;\n  height: auto;\n  border-radius: 18px;\n  object-fit: cover;\n  box-shadow: 0 10px 28px rgba(15, 23, 42, 0.14);\n}\n\n\/* Video *\/\n\n.ebox-video-section {\n  margin: 42px 0 46px;\n}\n\n.ebox-video-card,\n.ebox-video-box {\n  margin: 20px 0 12px;\n  padding: clamp(12px, 2vw, 18px);\n  border: 1px solid #dce3ea;\n  border-radius: 22px;\n  background: #f8fafc;\n  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);\n}\n\n.ebox-video-frame,\n.ebox-video-wrapper {\n  position: relative;\n  width: 100%;\n  aspect-ratio: 16 \/ 9;\n  overflow: hidden;\n  border-radius: 18px;\n  background: #0f172a;\n}\n\n.ebox-video-frame iframe,\n.ebox-video-wrapper iframe {\n  position: absolute;\n  inset: 0;\n  display: block;\n  width: 100%;\n  height: 100%;\n  border: 0;\n}\n\n\/* Cards and grids *\/\n\n.ebox-card-grid,\n.ebox-summary-grid,\n.ebox-check-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));\n  gap: 16px;\n  margin: 20px 0 28px;\n}\n\n.ebox-info-card,\n.ebox-check-card {\n  min-width: 0;\n  padding: 18px;\n  border: 1px solid #e5e7eb;\n  border-radius: 16px;\n  background: #ffffff;\n  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);\n}\n\n.ebox-info-card strong,\n.ebox-check-card strong {\n  display: block;\n  margin-bottom: 6px;\n  color: #111827;\n}\n\n\/* Sections and images *\/\n\n.ebox-step {\n  margin: 24px 0;\n  padding: clamp(18px, 3vw, 26px);\n  border: 1px solid #e5e7eb;\n  border-radius: 18px;\n  background: #ffffff;\n  box-shadow: 0 8px 22px rgba(15, 23, 42, 0.05);\n}\n\n.ebox-step h2 {\n  margin-top: 0;\n}\n\n.ebox-section-grid {\n  display: grid;\n  grid-template-columns: minmax(0, 1.1fr) minmax(260px, 0.9fr);\n  gap: 28px;\n  align-items: center;\n  margin: 20px 0 30px;\n}\n\n.ebox-image-card,\n.ebox-svg-preview {\n  min-width: 0;\n  margin: 28px 0 34px;\n  padding: clamp(12px, 2vw, 20px);\n  border: 1px solid #dce3ea;\n  border-radius: 18px;\n  background: #f8fafc;\n  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);\n  overflow: hidden;\n  text-align: center;\n}\n\n.ebox-image-card img,\n.ebox-svg-preview img {\n  display: block;\n  width: 100%;\n  max-width: 100%;\n  height: auto;\n  max-height: 72vh;\n  margin: 0 auto;\n  border-radius: 14px;\n  object-fit: contain;\n}\n\n.ebox-image-card .ebox-img-tall {\n  width: auto;\n  max-height: 520px;\n}\n\n.ebox-caption,\n.ebox-svg-caption {\n  margin: 12px 0 0;\n  color: #475569;\n  font-size: 0.95rem;\n  text-align: center;\n}\n\n\/* Notes *\/\n\n.ebox-note,\n.ebox-warning {\n  margin: 22px 0;\n  padding: 18px 20px;\n  border-radius: 12px;\n}\n\n.ebox-note {\n  border-left: 5px solid #0ea5e9;\n  background: #f0f9ff;\n}\n\n.ebox-warning {\n  border-left: 5px solid #f97316;\n  background: #fff7ed;\n}\n\n\/* Flow *\/\n\n.ebox-flow {\n  display: grid;\n  gap: 10px;\n  margin: 20px 0 26px;\n}\n\n.ebox-flow-step {\n  padding: 14px 16px;\n  border-radius: 14px;\n  background: #111827;\n  color: #ffffff;\n  font-weight: 700;\n}\n\n\/* Collapsible details *\/\n\n.ebox-details {\n  margin: 24px 0 34px;\n  border: 1px solid #e5e7eb;\n  border-radius: 18px;\n  background: #ffffff;\n  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);\n  overflow: hidden;\n}\n\n.ebox-details summary {\n  cursor: pointer;\n  padding: 16px 18px;\n  color: #111827;\n  font-weight: 800;\n  background: #f8fafc;\n  border-bottom: 1px solid #e5e7eb;\n}\n\n.ebox-details-content {\n  padding: 18px;\n}\n\n\/* Code *\/\n\n.ebox-code-block {\n  margin: 20px 0 26px;\n  border: 1px solid #d1d5db;\n  border-radius: 16px;\n  overflow: hidden;\n  background: #0b1020;\n  box-shadow: 0 10px 28px rgba(15, 23, 42, 0.16);\n}\n\n.ebox-code-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  padding: 12px 14px;\n  background: #111827;\n  color: #ffffff;\n}\n\n.ebox-code-title {\n  font-size: 0.95rem;\n  font-weight: 800;\n  word-break: break-word;\n}\n\n.ebox-copy-button {\n  appearance: none;\n  border: 0;\n  border-radius: 999px;\n  padding: 8px 13px;\n  background: #dbeafe;\n  color: #0f172a;\n  cursor: pointer;\n  font-weight: 800;\n  font-size: 0.85rem;\n  white-space: nowrap;\n}\n\n.ebox-copy-button:hover {\n  background: #bfdbfe;\n}\n\n.ebox-code-textarea {\n  display: block;\n  width: 100%;\n  min-height: 360px;\n  max-height: 620px;\n  padding: 18px;\n  border: 0;\n  outline: none;\n  resize: vertical;\n  background: #0b1020;\n  color: #e5e7eb;\n  font-family: Consolas, Monaco, \"Courier New\", monospace;\n  font-size: 13px;\n  line-height: 1.55;\n  white-space: pre;\n  overflow: auto;\n}\n\n.ebox-code-textarea.small {\n  min-height: 190px;\n}\n\n.ebox-code-textarea.medium {\n  min-height: 300px;\n}\n\n.ebox-code-textarea.large {\n  min-height: 520px;\n}\n\n.ebox-small-code-box {\n  margin: 18px 0 28px;\n}\n\n.ebox-small-code {\n  width: 100%;\n  min-height: 90px;\n  max-height: 500px;\n  height: 220px;\n  padding: 16px;\n  resize: vertical;\n  overflow: auto;\n  border: 1px solid #334155;\n  border-radius: 12px;\n  background-color: #111827;\n  color: #f1f5f9;\n  font-family: Consolas, Monaco, \"Courier New\", monospace;\n  font-size: 13px;\n  line-height: 1.45;\n  white-space: pre;\n  box-sizing: border-box;\n}\n\n\/* Tables, galleries, FAQ *\/\n\n.ebox-table {\n  width: 100%;\n  border-collapse: collapse;\n  margin: 18px 0 28px;\n}\n\n.ebox-table th,\n.ebox-table td {\n  border: 1px solid #d1d5db;\n  padding: 10px;\n  text-align: left;\n  vertical-align: top;\n}\n\n.ebox-table th {\n  background: #f2f2f2;\n}\n\n.ebox-gallery {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));\n  gap: 18px;\n  margin: 22px 0 32px;\n}\n\n.ebox-faq-list {\n  display: grid;\n  gap: 14px;\n  margin: 20px 0 34px;\n}\n\n.ebox-faq-list details {\n  border: 1px solid #e5e7eb;\n  border-radius: 16px;\n  background: #ffffff;\n  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.05);\n  overflow: hidden;\n}\n\n.ebox-faq-list summary {\n  cursor: pointer;\n  padding: 16px 18px;\n  color: #111827;\n  font-weight: 800;\n}\n\n.ebox-faq-content {\n  padding: 0 18px 18px;\n  color: #4b5563;\n}\n\n\/* Links and final box *\/\n\n.ebox-download-link,\n.ebox-schematic-link {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  margin: 10px 0 18px;\n  padding: 10px 16px;\n  border-radius: 999px;\n  background: #dbe8ee;\n  color: #1f3a46;\n  font-size: 0.92rem;\n  font-weight: 800;\n  text-decoration: none;\n}\n\n.ebox-download-link:hover,\n.ebox-schematic-link:hover {\n  background: #c8dce5;\n  color: #132a34;\n}\n\n.ebox-final-box {\n  margin: 42px 0 24px;\n  padding: clamp(20px, 3vw, 28px);\n  border: 1px solid #e5e7eb;\n  border-radius: 20px;\n  background: #f8fafc;\n}\n\n\/* Optional project-specific accent hooks *\/\n\n.ebox-project-page--elektroclock .ebox-hero-label,\n.ebox-project-page--matrix .ebox-hero-label {\n  background: #e0f2fe;\n  color: #075985;\n}\n\n.ebox-project-page--lightbox .ebox-hero-label {\n  background: #fef3c7;\n  color: #92400e;\n}\n\n.ebox-project-page--rgb .ebox-hero-label {\n  background: #fae8ff;\n  color: #86198f;\n}\n\n.ebox-project-page--pcmon .ebox-hero-label {\n  background: #dcfce7;\n  color: #166534;\n}\n\n.ebox-project-page--ttm .ebox-hero-label {\n  background: #ffedd5;\n  color: #9a3412;\n}\n\n\/* Responsive *\/\n\n@media (max-width: 820px) {\n  .ebox-hero-with-image,\n  .ebox-section-grid {\n    grid-template-columns: 1fr;\n  }\n\n  .ebox-hero-image {\n    margin-top: 8px;\n  }\n}\n\n@media (max-width: 640px) {\n  .ebox-project-page {\n    padding: 0 16px;\n  }\n\n  .ebox-hero {\n    padding: 22px 18px;\n  }\n\n  .ebox-code-header {\n    flex-direction: column;\n    align-items: flex-start;\n  }\n\n  .ebox-copy-button {\n    width: 100%;\n  }\n\n  .ebox-code-textarea {\n    min-height: 320px;\n    font-size: 12.5px;\n  }\n\n  .ebox-image-card .ebox-img-tall {\n    width: 100%;\n    max-height: none;\n  }\n}\n<\/style>\n\n\n\n\n<div class=\"ebox-project-page ebox-project-page--rgb\">\n\n  <section class=\"ebox-hero ebox-hero-with-image\">\n    <div class=\"ebox-hero-text\">\n      <span class=\"ebox-hero-label\">ESP32 Project<\/span>\n      <h1>Five-Channel RGB LED Controller<\/h1>\n      <p>\n        In this project, I build a five-channel RGB LED controller using two ESP32 boards, PWM output\n        stages, an ILI9341 touchscreen display, and I2C communication between the two controllers.\n      <\/p>\n      <p>\n        The controller can drive five separate RGB LED channels. Each RGB channel needs three\n        individual outputs for red, green, and blue, so the final controller uses 15 PWM outputs in total.\n      <\/p>\n    <\/div>\n\n    <div class=\"ebox-hero-image\">\n      <img decoding=\"async\" src=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/04\/IMG_2536-scaled.jpg\" alt=\"DIY ElektroBox five-channel RGB LED controller with touchscreen display\">\n    <\/div>\n  <\/section>\n\n\n  <div class=\"ebox-video-card\">\n    <div class=\"ebox-video-frame\">\n      <iframe\n        src=\"https:\/\/www.youtube.com\/embed\/y0zbJFD-WiA?start=9\"\n        title=\"RGB LED Controller project video\"\n        allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n        allowfullscreen>\n      <\/iframe>\n    <\/div>\n  <\/div>\n\n  <div class=\"ebox-card-grid\">\n    <div class=\"ebox-info-card\"><strong>Master ESP32<\/strong>Handles the ILI9341 touchscreen interface and sends selected RGB values over I2C.<\/div>\n    <div class=\"ebox-info-card\"><strong>Slave ESP32<\/strong>Receives channel and color data, then controls the RGB PWM output stages.<\/div>\n    <div class=\"ebox-info-card\"><strong>Output channels<\/strong>Five separate RGB LED channels with 15 PWM outputs in total.<\/div>\n  <\/div>\n\n  <h2>1. Required Parts<\/h2>\n\n  <div class=\"ebox-card-grid\">\n    <div class=\"ebox-info-card\"><strong>Controllers<\/strong>2 ESP32 development boards.<\/div>\n    <div class=\"ebox-info-card\"><strong>Display<\/strong>ILI9341 touchscreen display module.<\/div>\n    <div class=\"ebox-info-card\"><strong>Prototype<\/strong>Breadboard, Dupont wires, resistors, and test LEDs.<\/div>\n    <div class=\"ebox-info-card\"><strong>Output stage<\/strong>Transistors or suitable logic-level MOSFETs.<\/div>\n    <div class=\"ebox-info-card\"><strong>Final lighting<\/strong>RGB LED strips and external power supply.<\/div>\n    <div class=\"ebox-info-card\"><strong>Optional<\/strong>Custom PCB, display housing, electronics housing, and screws.<\/div>\n  <\/div>\n\n  <h2>2. Required Tools<\/h2>\n\n  <ul>\n    <li>Soldering iron<\/li>\n    <li>Solder wire<\/li>\n    <li>Wire cutter<\/li>\n    <li>Wire stripper<\/li>\n    <li>Multimeter<\/li>\n    <li>Computer with Arduino IDE<\/li>\n    <li>USB cables for the ESP32 boards<\/li>\n    <li>Optional PCB design software and 3D printer<\/li>\n  <\/ul>\n\n  <h2>3. Project Structure<\/h2>\n\n  <p>\n    For more complex projects, I like to start with simple subsystem prototypes. This makes the project\n    easier to debug because every function can be tested separately before everything is combined.\n  <\/p>\n\n  <div class=\"ebox-flow\">\n    <div class=\"ebox-flow-step\">PWM color and brightness control<\/div>\n    <div class=\"ebox-flow-step\">Transistor or MOSFET output stage<\/div>\n    <div class=\"ebox-flow-step\">Touchscreen display test<\/div>\n    <div class=\"ebox-flow-step\">I2C communication between two ESP32 boards<\/div>\n    <div class=\"ebox-flow-step\">Final five-channel RGB controller<\/div>\n    <div class=\"ebox-flow-step\">PCB and housing design<\/div>\n  <\/div>\n\n  <h2>4. Test the PWM Color Control<\/h2>\n\n  <p>\n    The brightness of each LED color is controlled with PWM. PWM stands for Pulse Width Modulation.\n    The ESP32 switches the output on and off very quickly. By changing the duty cycle, the average\n    power delivered to the LED changes and the LED appears brighter or darker.\n  <\/p>\n\n  <p>\n    For the first prototype, connect one red, one green, and one blue LED to three transistor output\n    stages. In this example, the ESP32 uses GPIO16 for red, GPIO17 for green, and GPIO5 for blue.\n  <\/p>\n\n  <details class=\"ebox-details\">\n    <summary>PWM test code<\/summary>\n    <div class=\"ebox-details-content\">\n      <div class=\"ebox-code-block\">\n        <div class=\"ebox-code-header\">\n          <span class=\"ebox-code-title\">ESP32 RGB PWM test<\/span>\n          <button class=\"ebox-copy-button\" onclick=\"eboxCopyCode('rgb-pwm-test', this)\">Copy code<\/button>\n        <\/div>\n        <textarea id=\"rgb-pwm-test\" class=\"ebox-code-textarea small\" readonly>const int redPin = 16;\nconst int greenPin = 17;\nconst int bluePin = 5;\n\nvoid setup() {\n  ledcAttach(redPin, 5000, 8);\n  ledcAttach(greenPin, 5000, 8);\n  ledcAttach(bluePin, 5000, 8);\n}\n\nvoid loop() {\n  ledcWrite(redPin, 255);\n  ledcWrite(greenPin, 0);\n  ledcWrite(bluePin, 0);\n  delay(1000);\n\n  ledcWrite(redPin, 0);\n  ledcWrite(greenPin, 255);\n  ledcWrite(bluePin, 0);\n  delay(1000);\n\n  ledcWrite(redPin, 0);\n  ledcWrite(greenPin, 0);\n  ledcWrite(bluePin, 255);\n  delay(1000);\n}<\/textarea>\n      <\/div>\n    <\/div>\n  <\/details>\n\n  <h2>5. Output Stage<\/h2>\n\n  <div class=\"ebox-section-grid\">\n    <div>\n      <p>\n        LED strips need more current than an ESP32 pin can provide. The ESP32 PWM pins should only\n        control the gate or base of the output stage. The LED strip current flows through the external\n        transistor or MOSFET.\n      <\/p>\n      <p>\n        For reliable operation, use suitable logic-level MOSFETs and make sure the LED power supply is\n        dimensioned for all channels at full brightness.\n      <\/p>\n    <\/div>\n\n    <div class=\"ebox-image-card\">\n      <img decoding=\"async\" src=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/04\/IMG_2536-scaled.jpg\" alt=\"RGB LED controller prototype with touchscreen\">\n      <p class=\"ebox-caption\">Prototype setup with touchscreen control and RGB output testing.<\/p>\n    <\/div>\n  <\/div>\n\n  <div class=\"ebox-warning\">\n    <strong>Important:<\/strong>\n    The ESP32 and LED strip power supply must share a common ground. Without a shared ground, the PWM\n    signal does not have a stable reference.\n  <\/div>\n\n  <h2>6. Touchscreen Interface<\/h2>\n\n  <p>\n    The master ESP32 handles the touchscreen display. It shows the user interface and allows selecting\n    channels, colors, and brightness values.\n  <\/p>\n\n  <h2>7. I2C Communication<\/h2>\n\n  <p>\n    Because the touchscreen already uses many pins, I split the system into two controllers. The master\n    ESP32 sends the selected channel and RGB values to the slave ESP32 over I2C. The slave ESP32 then\n    updates the PWM outputs.\n  <\/p>\n\n  <details class=\"ebox-details\">\n    <summary>I2C data concept<\/summary>\n    <div class=\"ebox-details-content\">\n      <p>\n        A simple message can contain the selected channel number and three color values. For example:\n        channel 3, red 120, green 40, blue 255.\n      <\/p>\n\n      <div class=\"ebox-code-block\">\n        <div class=\"ebox-code-header\">\n          <span class=\"ebox-code-title\">Example I2C payload<\/span>\n          <button class=\"ebox-copy-button\" onclick=\"eboxCopyCode('rgb-i2c-payload', this)\">Copy code<\/button>\n        <\/div>\n        <textarea id=\"rgb-i2c-payload\" class=\"ebox-code-textarea small\" readonly>Channel: 3\nRed:     120\nGreen:   40\nBlue:    255<\/textarea>\n      <\/div>\n    <\/div>\n  <\/details>\n\n\n  <h2>8. Final Software Structure<\/h2>\n\n  <p>\n    The final controller uses two separate Arduino sketches. The <strong>Master ESP32<\/strong> runs the\n    touchscreen menu, stores the RGB values for five independent channels, converts the selected color\n    into RGB565, and sends the active channel data to the slave controller over I2C.\n  <\/p>\n\n  <p>\n    The <strong>Slave ESP32<\/strong> listens as an I2C receiver at address <code>0x42<\/code>. It receives\n    three bytes from the master, converts the RGB565 color back into 8-bit red, green, and blue values,\n    and updates the PWM outputs for the selected RGB LED channel.\n  <\/p>\n\n  <div class=\"ebox-card-grid\">\n    <div class=\"ebox-info-card\">\n      <strong>Master sketch<\/strong>\n      Touchscreen user interface, tab menu, RGB sliders, RGB565 conversion, and I2C transmission.\n    <\/div>\n    <div class=\"ebox-info-card\">\n      <strong>Slave sketch<\/strong>\n      I2C reception, RGB565 decoding, five-channel color storage, and 15 PWM outputs.\n    <\/div>\n    <div class=\"ebox-info-card\">\n      <strong>I2C message<\/strong>\n      The master sends three bytes: channel index, RGB565 high byte, and RGB565 low byte.\n    <\/div>\n  <\/div>\n\n  <div class=\"ebox-flow\">\n    <div class=\"ebox-flow-step\">Touch input \u2192 Master ESP32 updates the selected channel and slider values<\/div>\n    <div class=\"ebox-flow-step\">Master ESP32 \u2192 converts the selected RGB color into RGB565<\/div>\n    <div class=\"ebox-flow-step\">I2C message \u2192 channel index + RGB565 high byte + RGB565 low byte<\/div>\n    <div class=\"ebox-flow-step\">Slave ESP32 \u2192 decodes RGB565 back to red, green, and blue<\/div>\n    <div class=\"ebox-flow-step\">PWM outputs \u2192 drive the selected RGB LED channel through the output stage<\/div>\n  <\/div>\n\n  <details class=\"ebox-details\" open>\n    <summary>LED_Controller_Master.ino<\/summary>\n    <div class=\"ebox-details-content\">\n      <p>\n        Upload this sketch to the ESP32 connected to the ILI9341 touchscreen. This board is the user\n        interface controller and sends the selected channel color to the slave ESP32.\n      <\/p>\n\n      <div class=\"ebox-code-block\">\n        <div class=\"ebox-code-header\">\n          <span class=\"ebox-code-title\">LED_Controller_Master.ino<\/span>\n          <button class=\"ebox-copy-button\" onclick=\"eboxCopyCode('rgb-master-final-code', this)\">Copy code<\/button>\n        <\/div>\n        <textarea id=\"rgb-master-final-code\" class=\"ebox-code-textarea large\" readonly>\/*\n  &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;\n  ESP32 TFT_eSPI + XPT2046_Touchscreen\n  5x RGB Channels with Tab Menu (SNAP + I2C TX)\n  &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;\n  \u2022 3 vertical sliders for R, G, B, snapping every 10% (0,25,51,&#8230;,255)\n  \u2022 5 independent RGB channels, selected via tab menu\n  \u2022 Touch updates values; display via TFT_eSPI (VSPI), touch via XPT2046 (HSPI)\n  \u2022 Sends current channel + color as:\n        [channel_index (0..4), RGB565_MSB, RGB565_LSB]\n    over I2C to 0x42\n  \u2022 Screen rotated 90\u00b0 clockwise vs previous version (TFT rotation = 2)\n  &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;\n*\/\n\n#include &lt;SPI.h&gt;\n#include &lt;Wire.h&gt;\n#include &lt;TFT_eSPI.h&gt;\n#include &lt;XPT2046_Touchscreen.h&gt;\n\nTFT_eSPI tft = TFT_eSPI();\n\n\/\/ &#8212;- Touch configuration (HSPI) &#8212;-\n#define TOUCH_CS   26\n#define TOUCH_IRQ  25\nSPIClass touchSPI(HSPI);\nXPT2046_Touchscreen ts(TOUCH_CS, TOUCH_IRQ);\n\n\/\/ &#8212;- Touch calibration &#8212;-\n#define TS_MINX 200\n#define TS_MAXX 3800\n#define TS_MINY 200\n#define TS_MAXY 3800\n\n\/\/ &#8212;- I2C (Master) &#8212;-\n#define I2C_SDA        21\n#define I2C_SCL        22\n#define I2C_FREQ       100000\n#define I2C_SLAVE_ADDR 0x42  \/\/ receiver address\n\n\/\/ &#8212;- UI constants &#8212;-\n#define HEADER_H    28\n#define TAB_H       24\n#define PREVIEW_H   60\n#define SLIDER_GAP  20\n#define SLIDER_W    60\n#define DEBOUNCE_MS 80\n#define SNAP_STEP   25      \/\/ ~10% steps across 0\u2013255\n#define BOX_SIZE    20      \/\/ retained for compatibility with earlier spec\n\n\/\/ &#8212;- Channels &#8212;-\n#define NUM_CHANNELS 5\n\n\/\/ &#8212;- GPIO output &#8212;-\n#define GPIO_OUT_PIN 17   \/\/ &lt;&#8212; your pin\n\n\/\/ &#8212;- Globals &#8212;-\nint sliderX[3];\nint sliderY, sliderH;\n\nint tabX[NUM_CHANNELS];\nint tabW;\n\nuint8_t currentChannel = 0;  \/\/ 0..4\n\nuint8_t rgb[NUM_CHANNELS][3];      \/\/ rgb[channel][component 0=R,1=G,2=B]\nuint8_t lastRgb[NUM_CHANNELS][3];\n\nunsigned long lastTouchTime = 0;\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\n\/\/ Init color arrays\nvoid initColors() {\n  for (int ch = 0; ch &lt; NUM_CHANNELS; ch++) {\n    for (int c = 0; c &lt; 3; c++) {\n      rgb[ch][c]     = 128; \/\/ mid level\n      lastRgb[ch][c] = 255; \/\/ force first draw\/send\n    }\n  }\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid drawHeader() {\n  tft.fillRect(0, 0, tft.width(), HEADER_H, TFT_BLACK);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  tft.setTextDatum(TC_DATUM);\n  tft.drawString(&#8220;5x RGB Sliders (Snap 10%)&#8221;, tft.width() \/ 2, HEADER_H \/ 2);\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid drawTabs() {\n  tabW = tft.width() \/ NUM_CHANNELS;\n  int y0 = HEADER_H;\n  int y1 = HEADER_H + TAB_H;\n\n  \/\/ Clear tab area\n  tft.fillRect(0, y0, tft.width(), TAB_H, TFT_BLACK);\n\n  tft.setTextDatum(MC_DATUM);\n  for (int ch = 0; ch &lt; NUM_CHANNELS; ch++) {\n    tabX[ch] = ch * tabW;\n    bool active = (ch == currentChannel);\n\n    uint16_t borderColor = TFT_WHITE;\n    uint16_t fillColor   = active ? TFT_DARKGREY : TFT_BLACK;\n\n    tft.fillRect(tabX[ch], y0, tabW, TAB_H, fillColor);\n    tft.drawRect(tabX[ch], y0, tabW, TAB_H, borderColor);\n\n    \/\/ Label: &#8220;CH1&#8221;, &#8220;CH2&#8221;, &#8230;\n    char label[8];\n    sprintf(label, &#8220;CH%d&#8221;, ch + 1);\n    tft.setTextColor(TFT_WHITE, fillColor);\n    tft.drawString(label, tabX[ch] + tabW \/ 2, y0 + TAB_H \/ 2);\n  }\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid drawSliderValue(uint8_t idx) {\n  \/\/ idx: 0=R,1=G,2=B for the CURRENT channel\n  uint8_t val = rgb[currentChannel][idx];\n\n  int y = sliderY + sliderH &#8211; map(val, 0, 255, 0, sliderH);\n  uint16_t color = (idx == 0) ? TFT_RED : (idx == 1) ? TFT_GREEN : TFT_BLUE;\n\n  \/\/ Clear slider area\n  tft.fillRect(sliderX[idx] + 1, sliderY + 1, SLIDER_W &#8211; 2, sliderH &#8211; 2, TFT_BLACK);\n\n  \/\/ Draw filled region\n  tft.fillRect(sliderX[idx] + 2, y, SLIDER_W &#8211; 4, sliderH &#8211; (y &#8211; sliderY) &#8211; 2, color);\n\n  \/\/ Grid lines every 10%\n  for (int i = 1; i &lt; 10; i++) {\n    int gy = sliderY + (sliderH * i) \/ 10;\n    tft.drawFastHLine(sliderX[idx] + 2, gy, SLIDER_W &#8211; 4, TFT_DARKGREY);\n  }\n\n  \/\/ Value label\n  tft.setTextDatum(MC_DATUM);\n  tft.setTextColor(TFT_WHITE, TFT_BLACK);\n  char buf[8];\n  sprintf(buf, &#8220;%3d&#8221;, val);\n  tft.drawString(buf, sliderX[idx] + SLIDER_W \/ 2, sliderY + sliderH + 10);\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid drawSliders() {\n  \/\/ Compute slider area taking header + tabs + preview into account\n  sliderY = HEADER_H + TAB_H + 10;\n  sliderH = tft.height() &#8211; sliderY &#8211; PREVIEW_H &#8211; 20;\n\n  int totalW = (3 * SLIDER_W) + (2 * SLIDER_GAP);\n  int startX = (tft.width() &#8211; totalW) \/ 2;\n\n  \/\/ Clear slider area\n  tft.fillRect(0, sliderY &#8211; 2, tft.width(), sliderH + 40, TFT_BLACK);\n\n  for (int i = 0; i &lt; 3; i++) {\n    sliderX[i] = startX + i * (SLIDER_W + SLIDER_GAP);\n    uint16_t color = (i == 0) ? TFT_RED : (i == 1) ? TFT_GREEN : TFT_BLUE;\n    tft.drawRect(sliderX[i], sliderY, SLIDER_W, sliderH, color);\n    drawSliderValue(i);\n  }\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid drawPreview() {\n  int y0 = tft.height() &#8211; PREVIEW_H;\n  uint8_t r = rgb[currentChannel][0];\n  uint8_t g = rgb[currentChannel][1];\n  uint8_t b = rgb[currentChannel][2];\n\n  uint16_t color = tft.color565(r, g, b);\n  tft.fillRect(0, y0, tft.width(), PREVIEW_H, color);\n\n  float luma = 0.299f * r + 0.587f * g + 0.114f * b;\n  uint16_t txtColor = (luma &gt; 128) ? TFT_BLACK : TFT_WHITE;\n  tft.setTextColor(txtColor, color);\n  tft.setTextDatum(MC_DATUM);\n\n  char label[32];\n  sprintf(label, &#8220;CH%d Result&#8221;, currentChannel + 1);\n  tft.drawString(label, tft.width() \/ 2, y0 + PREVIEW_H \/ 2);\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nbool readTouchPixel(int &amp;x, int &amp;y) {\n  if (!ts.touched()) return false;\n  TS_Point p = ts.getPoint();\n\n  \/\/ Map raw touch to screen coordinates\n  x = map(p.x, TS_MINX, TS_MAXX, 0, tft.width());\n  y = map(p.y, TS_MINY, TS_MAXY, 0, tft.height());\n  x = constrain(x, 0, tft.width() &#8211; 1);\n  y = constrain(y, 0, tft.height() &#8211; 1);\n  return true;\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\n\/\/ Now sends channel index + RGB565\nvoid sendRGB565_I2C(uint8_t channel, uint16_t rgb565) {\n  Wire.beginTransmission(I2C_SLAVE_ADDR);\n  Wire.write(channel);                   \/\/ channel index 0..4\n  Wire.write((uint8_t)(rgb565 &gt;&gt; 8));    \/\/ MSB\n  Wire.write((uint8_t)(rgb565 &amp; 0xFF));  \/\/ LSB\n  Wire.endTransmission();\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid printColorAndSend() {\n  uint8_t r = rgb[currentChannel][0];\n  uint8_t g = rgb[currentChannel][1];\n  uint8_t b = rgb[currentChannel][2];\n\n  uint32_t hex = ((uint32_t)r &lt;&lt; 16) | ((uint32_t)g &lt;&lt; 8) | b;\n  uint16_t rgb565 = tft.color565(r, g, b);\n\n  Serial.printf(&#8220;CH%d: RGB(%3u,%3u,%3u) HEX=0x%06X RGB565=0x%04X\\n&#8221;,\n                currentChannel + 1, r, g, b, hex, rgb565);\n\n  sendRGB565_I2C(currentChannel, rgb565);\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid setup() {\n  Serial.begin(115200);\n  Serial.println(&#8220;5x RGB Slider (Snap + I2C TX) demo starting&#8230;&#8221;);\n  delay(500);\n\n  pinMode(GPIO_OUT_PIN, OUTPUT);\n  digitalWrite(GPIO_OUT_PIN, HIGH);   \/\/ drive pin 17 high\n  delay(500);\n\n  initColors();\n\n  \/\/ Display\n  tft.init();\n  \/\/ Rotate screen 90\u00b0 clockwise compared to previous (1 -&gt; 2)\n  tft.setRotation(2);\n  tft.fillScreen(TFT_BLACK);\n\n  drawHeader();\n  drawTabs();\n  drawSliders();\n  drawPreview();\n\n  \/\/ Touch on HSPI\n  touchSPI.begin(14, 12, 13, TOUCH_CS);  \/\/ SCK, MISO, MOSI, CS\n  ts.begin(touchSPI);\n  \/\/ Keep relative orientation with TFT (3 -&gt; 0 is +1 step \/ 90\u00b0 cw)\n  ts.setRotation(0);\n\n  \/\/ I2C master\n  Wire.begin(I2C_SDA, I2C_SCL, I2C_FREQ);\n\n  printColorAndSend();\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid loop() {\n  int x, y;\n  if (readTouchPixel(x, y)) {\n    unsigned long now = millis();\n    if (now &#8211; lastTouchTime &lt; DEBOUNCE_MS) return;\n    lastTouchTime = now;\n\n    \/\/ &#8212;- Tab handling &#8212;-\n    int tabsY0 = HEADER_H;\n    int tabsY1 = HEADER_H + TAB_H;\n    if (y &gt;= tabsY0 &amp;&amp; y &lt; tabsY1) {\n      if (tabW &gt; 0) {\n        int ch = x \/ tabW;\n        if (ch &gt;= 0 &amp;&amp; ch &lt; NUM_CHANNELS &amp;&amp; ch != currentChannel) {\n          currentChannel = ch;\n          drawTabs();\n          drawSliders();\n          drawPreview();\n          printColorAndSend();\n        }\n      }\n      delay(80);\n      return;\n    }\n\n    \/\/ &#8212;- Slider handling &#8212;-\n    for (int i = 0; i &lt; 3; i++) {\n      if (x &gt;= sliderX[i] &amp;&amp; x &lt; sliderX[i] + SLIDER_W &amp;&amp;\n          y &gt;= sliderY &amp;&amp; y &lt; sliderY + sliderH) {\n\n        int val = map(sliderY + sliderH &#8211; y, 0, sliderH, 0, 255);\n        val = constrain(val, 0, 255);\n\n        \/\/ Snap to nearest 10%\n        val = ((val + (SNAP_STEP \/ 2)) \/ SNAP_STEP) * SNAP_STEP;\n        val = constrain(val, 0, 255);\n\n        rgb[currentChannel][i] = val;\n\n        if (rgb[currentChannel][i] != lastRgb[currentChannel][i]) {\n          drawSliderValue(i);\n          drawPreview();\n          printColorAndSend();\n          lastRgb[currentChannel][i] = rgb[currentChannel][i];\n        }\n        delay(80);\n      }\n    }\n  }\n}\n<\/textarea>\n      <\/div>\n    <\/div>\n  <\/details>\n\n  <details class=\"ebox-details\">\n    <summary>LED_Controller_Slave.ino<\/summary>\n    <div class=\"ebox-details-content\">\n      <p>\n        Upload this sketch to the ESP32 connected to the RGB output stages. This board receives the\n        selected color and updates the PWM pins for the five RGB LED channels.\n      <\/p>\n\n      <div class=\"ebox-code-block\">\n        <div class=\"ebox-code-header\">\n          <span class=\"ebox-code-title\">LED_Controller_Slave.ino<\/span>\n          <button class=\"ebox-copy-button\" onclick=\"eboxCopyCode('rgb-slave-final-code', this)\">Copy code<\/button>\n        <\/div>\n        <textarea id=\"rgb-slave-final-code\" class=\"ebox-code-textarea large\" readonly>\/*\n  &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;\n  ESP32 I2C RGB Receiver for 5 Channels (analogWrite version)\n  &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;\n  \u2022 I2C slave at address 0x42\n  \u2022 Expects 3 bytes per message:\n        [channel_index (0..4), RGB565_MSB, RGB565_LSB]\n  \u2022 Converts RGB565 -&gt; 8-bit R,G,B\n  \u2022 Outputs PWM via analogWrite() on these pins:\n\n        CH1: R1 = GPIO2,  G1 = GPIO0,  B1 = GPIO15\n        CH2: R2 = GPIO16, G2 = GPIO17, B2 = GPIO4\n        CH3: R3 = GPIO18, G3 = GPIO19, B3 = GPIO5\n        CH4: R4 = GPIO12, G4 = GPIO14, B4 = GPIO13\n        CH5: R5 = GPIO26, G5 = GPIO25, B5 = GPIO27\n\n  &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;\n*\/\n\n#include &lt;Arduino.h&gt;\n#include &lt;Wire.h&gt;\n\n\/\/ I2C address (must match your sender)\n#define I2C_SLAVE_ADDR 0x42\n\n\/\/ Channels &amp; colors\nconst uint8_t NUM_CHANNELS = 5;\nconst uint8_t NUM_COLORS   = 3; \/\/ R, G, B\n\n\/\/ Indexes for readability\nenum { COL_R = 0, COL_G = 1, COL_B = 2 };\n\n\/\/ Pin mapping for each channel (R,G,B)\nconst int rgbPins[NUM_CHANNELS][NUM_COLORS] = {\n  \/\/ CH1\n  {  2,  0, 15 },  \/\/ R1, G1, B1\n  \/\/ CH2\n  { 16, 17,  4 },  \/\/ R2, G2, B2\n  \/\/ CH3\n  { 18, 19,  5 },  \/\/ R3, G3, B3\n  \/\/ CH4\n  { 12, 14, 13 },  \/\/ R4, G4, B4\n  \/\/ CH5\n  { 26, 25, 27 }   \/\/ R5, G5, B5\n};\n\n\/\/ Store current 8-bit colors (for debug \/ future use)\nuint8_t colors[NUM_CHANNELS][NUM_COLORS];\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\n\/\/ Convert 16-bit RGB565 -&gt; 8-bit R,G,B\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid rgb565To888(uint16_t rgb565, uint8_t &amp;r, uint8_t &amp;g, uint8_t &amp;b) {\n  uint8_t r5 = (rgb565 &gt;&gt; 11) &amp; 0x1F; \/\/ 5 bits\n  uint8_t g6 = (rgb565 &gt;&gt; 5)  &amp; 0x3F; \/\/ 6 bits\n  uint8_t b5 =  rgb565        &amp; 0x1F; \/\/ 5 bits\n\n  \/\/ Expand to 8 bits\n  r = (r5 &lt;&lt; 3) | (r5 &gt;&gt; 2);  \/\/ 5 -&gt; 8 bits\n  g = (g6 &lt;&lt; 2) | (g6 &gt;&gt; 4);  \/\/ 6 -&gt; 8 bits\n  b = (b5 &lt;&lt; 3) | (b5 &gt;&gt; 2);  \/\/ 5 -&gt; 8 bits\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\n\/\/ Apply R,G,B to one channel (0..4) using analogWrite\n\/\/ Only this channel is touched; others keep their last PWM\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid setChannelColor(uint8_t ch, uint8_t r, uint8_t g, uint8_t b) {\n  if (ch &gt;= NUM_CHANNELS) return;\n\n  \/\/ Save current color (for debugging \/ optional reuse)\n  colors[ch][COL_R] = r;\n  colors[ch][COL_G] = g;\n  colors[ch][COL_B] = b;\n\n  \/\/ Update ONLY this channel&#8217;s pins\n  analogWrite(rgbPins[ch][COL_R], r);\n  analogWrite(rgbPins[ch][COL_G], g);\n  analogWrite(rgbPins[ch][COL_B], b);\n\n  Serial.printf(&#8220;Set CH%d -&gt; R=%3u G=%3u B=%3u\\n&#8221;,\n                ch + 1, r, g, b);\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\n\/\/ I2C receive interrupt callback\n\/\/ called when master sends data\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid onReceive(int numBytes) {\n  \/\/ Process data in chunks of 3 bytes: [ch, msb, lsb]\n  while (Wire.available() &gt;= 3) {\n    uint8_t ch  = Wire.read();          \/\/ channel 0..4\n    uint8_t msb = Wire.read();\n    uint8_t lsb = Wire.read();\n\n    uint16_t rgb565 = ((uint16_t)msb &lt;&lt; 8) | lsb;\n\n    uint8_t r, g, b;\n    rgb565To888(rgb565, r, g, b);\n\n    Serial.printf(&#8220;I2C recv: ch=%u, rgb565=0x%04X -&gt; R=%3u G=%3u B=%3u\\n&#8221;,\n                  ch, rgb565, r, g, b);\n\n    \/\/ Only this channel is updated; others remain untouched\n    setChannelColor(ch, r, g, b);\n  }\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid setupPins() {\n  for (int ch = 0; ch &lt; NUM_CHANNELS; ch++) {\n    for (int c = 0; c &lt; NUM_COLORS; c++) {\n      pinMode(rgbPins[ch][c], OUTPUT);\n      analogWrite(rgbPins[ch][c], 0);  \/\/ start all off at boot\n    }\n  }\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid setup() {\n  Serial.begin(115200);\n  Serial.println(&#8220;\\nI2C RGB Receiver (analogWrite) starting&#8230;&#8221;);\n\n  setupPins();\n\n  \/\/ Clear color array\n  for (int ch = 0; ch &lt; NUM_CHANNELS; ch++) {\n    for (int c = 0; c &lt; NUM_COLORS; c++) {\n      colors[ch][c] = 0;\n    }\n  }\n\n  \/\/ Optional: explicitly set resolution\/frequency on newer ESP32 cores\n  \/\/ analogWriteResolution(8);   \/\/ 0\u2013255\n  \/\/ analogWriteFrequency(5000); \/\/ 5 kHz\n\n  \/\/ I2C slave init\n  Wire.begin(I2C_SLAVE_ADDR);   \/\/ ESP32 Arduino: slave mode with address\n  Wire.onReceive(onReceive);\n\n  Serial.printf(&#8220;I2C slave listening on 0x%02X\\n&#8221;, I2C_SLAVE_ADDR);\n}\n\n\/\/ &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\nvoid loop() {\n  \/\/ Nothing to do here; all work handled in onReceive()\n}\n<\/textarea>\n      <\/div>\n    <\/div>\n  <\/details>\n\n  <h2>9. Common Problems<\/h2>\n\n  <div class=\"ebox-faq-list\">\n    <details>\n      <summary>The LED strip does not light up<\/summary>\n      <div class=\"ebox-faq-content\">\n        <p>Check the power supply, shared ground, MOSFET wiring, and LED strip polarity.<\/p>\n      <\/div>\n    <\/details>\n\n    <details>\n      <summary>The colors are swapped<\/summary>\n      <div class=\"ebox-faq-content\">\n        <p>Check the channel order. Some LED strips or wiring layouts use a different RGB order.<\/p>\n      <\/div>\n    <\/details>\n\n    <details>\n      <summary>The touchscreen works but the LEDs do not react<\/summary>\n      <div class=\"ebox-faq-content\">\n        <p>Check the I2C wiring, addresses, pull-up resistors, and whether both ESP32 boards are powered.<\/p>\n      <\/div>\n    <\/details>\n  <\/div>\n\n  <div class=\"ebox-final-box\">\n    <h2>10. Final Result<\/h2>\n    <p>\n      The final controller uses a touchscreen as the user interface and a second ESP32 for the RGB\n      output channels. This keeps the project modular and makes the wiring easier to understand.\n    <\/p>\n    <p>\n      The design can be expanded with saved presets, animations, Wi-Fi control, or a custom PCB for a\n      cleaner final build.\n    <\/p>\n  <\/div>\n\n<\/div>\n\n<script>\n  function eboxCopyCode(id, button) {\n    const textarea = document.getElementById(id);\n    if (!textarea) return;\n\n    textarea.focus();\n    textarea.select();\n    textarea.setSelectionRange(0, 999999);\n\n    const oldText = button.textContent;\n\n    function showCopied() {\n      button.textContent = 'Copied!';\n      setTimeout(function () {\n        button.textContent = oldText;\n      }, 1400);\n    }\n\n    if (navigator.clipboard && window.isSecureContext) {\n      navigator.clipboard.writeText(textarea.value).then(showCopied).catch(function () {\n        document.execCommand('copy');\n        showCopied();\n      });\n    } else {\n      document.execCommand('copy');\n      showCopied();\n    }\n  }\n<\/script>\n\n","protected":false},"excerpt":{"rendered":"<p>ESP32 Project Five-Channel RGB LED Controller In this project, I build a five-channel RGB LED controller using two ESP32 boards, PWM output stages, an ILI9341 touchscreen display, and I2C communication between the two controllers. The controller can drive five separate RGB LED channels. Each RGB channel needs three individual outputs for red, green, and blue, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-855975","page","type-page","status-publish","hentry"],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":7}},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>RGB LED Controller - ElektroBox<\/title>\n<meta name=\"description\" content=\"Build a DIY RGB LED controller with two ESP32 boards, PWM outputs, an ILI9341 touchscreen, I2C communication, and five RGB LED channels\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"RGB LED Controller - ElektroBox\" \/>\n<meta property=\"og:description\" content=\"Build a DIY RGB LED controller with two ESP32 boards, PWM outputs, an ILI9341 touchscreen, I2C communication, and five RGB LED channels\" \/>\n<meta property=\"og:url\" content=\"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/\" \/>\n<meta property=\"og:site_name\" content=\"ElektroBox\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-02T17:13:34+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/04\/IMG_2536-scaled.jpg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/rgb-led-controller\\\/\",\"url\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/rgb-led-controller\\\/\",\"name\":\"RGB LED Controller - ElektroBox\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/rgb-led-controller\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/rgb-led-controller\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/04\\\/IMG_2536-scaled.jpg\",\"datePublished\":\"2026-05-02T09:32:38+00:00\",\"dateModified\":\"2026-06-02T17:13:34+00:00\",\"description\":\"Build a DIY RGB LED controller with two ESP32 boards, PWM outputs, an ILI9341 touchscreen, I2C communication, and five RGB LED channels\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/rgb-led-controller\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/rgb-led-controller\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/rgb-led-controller\\\/#primaryimage\",\"url\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/04\\\/IMG_2536-scaled.jpg\",\"contentUrl\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/04\\\/IMG_2536-scaled.jpg\",\"width\":1920,\"height\":2560},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/rgb-led-controller\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/elektrobox.ch\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"RGB LED Controller\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/#website\",\"url\":\"https:\\\/\\\/elektrobox.ch\\\/\",\"name\":\"ElektroBox\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/#\\\/schema\\\/person\\\/878bd55e17d9d0b1a53523fa5e90f6b2\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/elektrobox.ch\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/#\\\/schema\\\/person\\\/878bd55e17d9d0b1a53523fa5e90f6b2\",\"name\":\"Jacob Jarosz\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Logo_Color-Kopie-scaled.png\",\"url\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Logo_Color-Kopie-scaled.png\",\"contentUrl\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Logo_Color-Kopie-scaled.png\",\"width\":2560,\"height\":2560,\"caption\":\"Jacob Jarosz\"},\"logo\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Logo_Color-Kopie-scaled.png\"},\"sameAs\":[\"http:\\\/\\\/elektrobox.ch\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"RGB LED Controller - ElektroBox","description":"Build a DIY RGB LED controller with two ESP32 boards, PWM outputs, an ILI9341 touchscreen, I2C communication, and five RGB LED channels","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/","og_locale":"en_US","og_type":"article","og_title":"RGB LED Controller - ElektroBox","og_description":"Build a DIY RGB LED controller with two ESP32 boards, PWM outputs, an ILI9341 touchscreen, I2C communication, and five RGB LED channels","og_url":"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/","og_site_name":"ElektroBox","article_modified_time":"2026-06-02T17:13:34+00:00","og_image":[{"url":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/04\/IMG_2536-scaled.jpg","type":"","width":"","height":""}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/","url":"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/","name":"RGB LED Controller - ElektroBox","isPartOf":{"@id":"https:\/\/elektrobox.ch\/#website"},"primaryImageOfPage":{"@id":"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/#primaryimage"},"image":{"@id":"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/#primaryimage"},"thumbnailUrl":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/04\/IMG_2536-scaled.jpg","datePublished":"2026-05-02T09:32:38+00:00","dateModified":"2026-06-02T17:13:34+00:00","description":"Build a DIY RGB LED controller with two ESP32 boards, PWM outputs, an ILI9341 touchscreen, I2C communication, and five RGB LED channels","breadcrumb":{"@id":"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/#primaryimage","url":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/04\/IMG_2536-scaled.jpg","contentUrl":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/04\/IMG_2536-scaled.jpg","width":1920,"height":2560},{"@type":"BreadcrumbList","@id":"https:\/\/elektrobox.ch\/index.php\/rgb-led-controller\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/elektrobox.ch\/"},{"@type":"ListItem","position":2,"name":"RGB LED Controller"}]},{"@type":"WebSite","@id":"https:\/\/elektrobox.ch\/#website","url":"https:\/\/elektrobox.ch\/","name":"ElektroBox","description":"","publisher":{"@id":"https:\/\/elektrobox.ch\/#\/schema\/person\/878bd55e17d9d0b1a53523fa5e90f6b2"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/elektrobox.ch\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/elektrobox.ch\/#\/schema\/person\/878bd55e17d9d0b1a53523fa5e90f6b2","name":"Jacob Jarosz","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/Logo_Color-Kopie-scaled.png","url":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/Logo_Color-Kopie-scaled.png","contentUrl":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/Logo_Color-Kopie-scaled.png","width":2560,"height":2560,"caption":"Jacob Jarosz"},"logo":{"@id":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/Logo_Color-Kopie-scaled.png"},"sameAs":["http:\/\/elektrobox.ch"]}]}},"_links":{"self":[{"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/pages\/855975","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/comments?post=855975"}],"version-history":[{"count":18,"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/pages\/855975\/revisions"}],"predecessor-version":[{"id":856325,"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/pages\/855975\/revisions\/856325"}],"wp:attachment":[{"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/media?parent=855975"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}