Modular LED Bar Light with a custom PCB

Published on August 26, 2026

View all of the files for this project here on Github.

After testing the LinkLite for a few months, I came to realize that it has a few problems.

  1. The battery died too quickly, often within 5 hours, killing the possibility of long-term usage without being wired.
  2. It wasn't bright enough, it didn't properly light up the room beyond what a reading light would.
  3. It was a prototype made on a perfboard and could be made much more compactly.

Problem 1 forced me to redesign the project's foundation and problem number 2 was very important to me, as I made this project to replace my dorm room lights, not to just use for reading.

Thats why I've reimagined the project completely.

LinkLite Version 2.0

This summer, I spent the better part of a month redesigning everything from the ground up. The idea is the same though: A modular room light that syncs its state across the light network locally and not through the cloud.

See what I came up with below:

A gif of the linklite v2 with its controller.

While working, I kept in mind the three problems above and sought to address each one of them. Here's what's new in version 2.0:

  1. The LinkLite is exclusively powered via USB-C PD(15V) or daisy-chain rather than a battery.
  2. Linklites can daisy-chain their power through any standard speaker wire capable of carrying up to 3A.
  3. The light souce is now made of 28 high power 3000K White LEDs that are designed for lighting.
  4. The warmth control is practically useless. In all my testing I never once wanted the light to be cooler, so I removed it. Brightness is now controlled via a battery-powered controller.
  5. The whole project is a two-sided 4-layer PCB fitted around a 3d-printed case.

Below is a demonstration of 4 LinkLites (3 in view, one to the left). It lights up the room considerably. I also have since angled the LinkLites upward 30 degrees to be less direct to the eye.

A photo of my bedroom without the LinkLites on. A photo of my bedroom with the LinkLites on.

Using the Linklite v2

To setup a chain of LinkLites, you need to daisy chain the +12V/GND to one another using wire. I find 18awg speaker wire does just fine. Once the Linklites are daisy-chained, simply connect one of them to a USB-C power supply capable of delivery 15V at 3A (which is pretty common for a laptop charger). Then, all of the LinkLites should share a power bus.

A diagram of the linklite v2 power daisy-chaining.

Although, a network does not need to be wired together to sync. It is simply convenient to use one power source.

To control the network, click the controller dial down once, and the charging indicator should shine solid blue. Turn the knob clockwise to increase brightness, and turn it counter-clockwise to decrease brightness. More than one controller can be used for a network.

The controller is comprised of an ESP32-C3 Mini Dev board, an EC11 encoder, and a USB-C battery management IC. The device is powered via a Lithium Ion cell of 3400mAh from Li-Ion Wholesale.

A Look at the Case

The case of the project is 2 pieces. There is a bottom half made of black PLA and gray PLA for indicators and text, and there is a top half made from transparent Polycarbonate to diffuse the light. Everything was designed in OnShape and printed on a Bambu P1S printer with the AMS 2.

A screenshot of the onshape cad of the full bar.

The back of the case has 2 magnets and a inward slot for mounting onto a bracket. This is designed in such a way so that the bracket can be changed or adjusted to the application.

A screenshot of the onshape cad of the bottomn of the bar.

Under the Hood - the PCB

The PCB of this project was designed in KiCad 10.0 and is centered around an ESP32-C6 Mini module. The PCB is 4 layers, and was assembled on both sides by JLCPCB.

A screenshot from KiCad of the power electronics section of the PCB schematic.

The board has 3 ports on it. Two are WAGOs for daisy-chaining the power and one is a USB-C port for injecting power into the chain. The bottom side is for power electronics and the microcontroller, and the top side is purely for LEDs. The PCB is white to better reflect the light from the LEDs outward.

Power either comes in from the USB-C port or through the WAGOs for daisy-chaining. In the case of USB-C, the CH221K negotiates a 15V input voltage from the USB-C power supply that's fed into VBUS through a PTC fuse that caps out at 3A. The USB-C is designed to be used with a USB-C Power Delivery capable supply for 15V at 3A for a full chain, but for single bar use, you can plug it into almost any USB-C port. My phone can power it, laptop, and portable phone charger. In the case of the WAGOs, they are simply connected to VBUS.

VBUS is then fed into the P-FET reverse polarity protection circuit by another PTC fuse for full per-bar protection that wont interrupt the daisy-chain. You can find an explaination of that circuit here. With all of that, a clean 15V should be coming out of the reverse protection circuit, which is fed into 2 places. Its fed out to the chain through the PWR OUT wago and its fed into the TPS563201 3.3V buck converter.

The USB data lines are fed through a USBLC6-2 ESD protection circuit for the microcontroller.

A screenshot from KiCad of the microcontroller section of the PCB schematic.

The microcontroller for this PCB is an ESP32-C6 Mini and is setup in a pretty standard way. I fed the USB data lines in from the ESD protection circuit and then just copied everything from its datasheet's example schematic. I have 2 push buttons connected to GPIO pins 21/22, and one LED connected to GPIO 23 as well. Then on pin 23 is the main LED control PWM signal.

A screenshot from KiCad of the low-side mosfet section of the PCB schematic. A screenshot from KiCad of the led section of the PCB schematic.

That PWM signal is fed into the gate of a low-side N-Channel MOSFET for dimming. All of the board's main LEDs are always connected to 3.3V, but they are only connected to ground when the MOSFET is conducting. So, by switching the FET on and off with a PWM signal, you get dimming capabilities.

The PCB Layout

The PCB is 150mm x 87mm and white. I used large copper pours for the ground, VBUS, and 3.3v nets since the LinkLites are somewhat high power.

You can see the layer stack-up screenshots below and find the KiCad project files on my Github Repository for the project.

A screenshot from KiCad of the front side of the PCB layout. A screenshot from KiCad of the back side of the PCB layout.

And see the PCB render below.

A screenshot from KiCad of the front side of the PCB render. A screenshot from KiCad of the back side of the PCB render.

The Programming

The code is written by Claude Code for Arduino IDE. You can find everything on my Github Repository for the project. Claude was given the arduino-cli tool in order to compile the code to check for accuracy and errors. It based the code off of the original LinkLite code from version 1.

It continued the Last Write Wins method of syncing (see my LinkLite v1 post), but added a read buffer and modularized the code much more. Also, it changed the sync model to have the controller send out deltas rather than brightness percentages since the controller cannot always be listening on the network.

Side Note on the ESP-32 C3 Mini Dev Board Antenna

I wasn't aware of this until after I purchased 12 of these mini boards for the controller and testing, but the antennas are notable terrible.

A photo of an ESP32-C3 mini dev board.

Apparently, this is due to cheap manufacturers having a poor layout design, but I'm not sure. Anyway, I was completely unable to send a signal from the controller to the network unless directly next to it.

I did find a fix though! For reasons I cannot explain until I take a higher physics course, lowering the output power of the radio to 8db fixes the problem for me completely for transmitting. I have not testing receiving. But if anyone is having similar issues, try that!

PCB Post Processing

There were three PCB modifications I had to make after the boards arrived, only one of them necessary for functionality.

  1. The button footprints laid out in a way that they will always short and so the microcontroller is held reset indefinetely. I just needed to add different buttons.
  2. The resistors I had calculated for the main LEDs were correct, but when I was setting up the assembly, I accidentily chose way too high of a resistance and so the brightness was effectively capped at 20%.
  3. My original PCB used the TPS563201 buck converter from TI. However, this buck converter switches into a power efficiency mode when the current output is low. That would make the PCB buzz when the brightness was low and only silence when brightness was very high (>80%). To fix this, I simply swapped out the IC for a pin-compatible TPS563208 which did not have the extra efficiency mode.

After these modifications the board were totally functional.

Testing

After a while of being on at a high brightness, these boards get very hot. Additionally, after around 80% brightness, the bars would begin to flicker one by one after being left on for long enough. I wanted to know exactly how hot and find out why this was happening.

I started by collecting data on a simple bench test with brightness as the independent variable.

Looking at the current pulled by the LEDs against brightness, its clear that around 80% theres a huge jump in current pulled. This is consistent for the LED datasheet, and means that there is a huge jump in the output current the bucks have to push.

A screenshot from google sheets of the data I collected on the LinkLite bench test.

This led me to wonder just how hot the PCB was getting at those current consumption levels, so I created a small temperature logger on a breadboard using a thermistor, and SD card, and an esp32-c3 mini.

A photo of the temperature logging setup.

Every one second, the sensor measures the temperature of the thermistor and logs it in the txt file. After a few trials I had some nice data on the 80% brightness mark. I analyzed it in MATLAB, and you can see the results below.

A graph generated by MATLAB displaying the temperature data.

This is a busy chart. Let me explain the legend. Single refers to a LinkLite chain of 1. Double refers to 2, and triple 3. The percentage is the brightness it was set to, and the parenthesis are the trial numbers.

On the x-axis we have time, in seconds, elapsed since beginning. Most trials lasted 30 minutes. And the y-axis displays the temperature measured in degrees celsius over time.

First, let's look at two lines. Single 80% and Double 80%. As you may expect from intuition, the double chart reaches a higher equilibrium temperature as there is more current flowing through the node. What I didn't expect, however, was the double and triple lines to be virtually identical. So that was an interesting find.

We can also see the 60% line is considerably cooler than the 80% lines, but neither are at a temperature of which there should be concern for the PCB.

Now we come to the flickering. As I mentioned before, any brightness value above 80% would set the chain to flicker oddly. So, I wondered if maybe the components were triggering a cooldown since they got too hot. Looking at the 90% line, this is obviously not the case. The reason the line stops prematurely is that I stopped recording the instant I saw a flicker begin. So, the temperature cannot be causing this issue.

Therefore, it must be the buck converter! The daisy-chain is not interrupted by the flicker, so the 15V rail is okay, but its really the 3.3v rail that only goes down. So that isolated the issue to the buck converter.

A set of graphs generated by MATLAB displaying the linklite data.

I calculated the buck output based on the current drawn by the bench power supply and how much the forward current of the LEDs were, and graphed some of the data above. This is interesting because it really shows how the buck kicks in after 80%, which would explain the odd behavior.

Looking at the datasheet for it, I seem to have either miscalculated the inductor values, or I should have chosen a different buck for this current output. In the end, I capped the brightness to 80% and will address the issue in a future revision.

Concluding Thoughts

Overall, this project was a massive success and I finished just in time for moving into my dorm. In the future, I may do another revision, but it would probably still resemble this version. There just seems to be a little to iron out on the buck converter.

Thank you for reading!