Project tutorial

Dodecagon Portal Project © MIT

A huge Nanoleaf like device made from soldering 12 PCBs each contains 7 WS2812B LEDs and anESP12F

  • 1 respect

Components and supplies

Necessary tools and machines

3D Printer (generic)

Apps and online services

About this project

Hey everyone what's up,

So this is my "DODECAGON" which is a huge Nanoleaf like device which is made from combining 12 PCBs together at a certain angle to make a dodecagon shape.

The goal for making this project was simple, I wanted to make a huge Nanoleaf like Setup with PCBs, Previously I made this similar version which was small so I wanted to enlarge that project so I created this Dodecagon Portal Project which is actually version 2.

Material Required

  • PCBs which were provided by PCBWay
  • WS2812B LEDs x 84
  • 100nf Capacitor 0805 x 36
  • ESP12F Module x 1
  • AMS1117 3.3V x 1
  • 10uF Cap x 1
  • 100uF Cap x 1
  • 0 ohms Resistance x 2
  • 10K x 4
  • Female header pins
  • THT Micro USB Port x 1
  • 10uF 16V Aluminium Capacitor x 1
  • Solder Paste
  • 3D Printed Soldering JIG

WorkingProcess/PCB Prep

This Project utilizes two PCBs one is the mainboard and another one contains 7 WS2812B LEDs.

Mainboard has ESP12F Module hooked up with WS2812B LEDs. The mainboard generates the signal for the first WS2812B LED, through NZR communication mode, the first pixel obtains the data from ESP8266 via DIN Port and then sends the 24bit data to the second pixel via its DOUT Port.

Main Board Contains 7 PCBs so when the data reaches to 7th pixel, we add another PCB onto the main board which adds 7 more pixels to this setup, the data then transfer from the first PCB to the second PCB, and so on.

The Second PCB doesn't have an ESP12F module soldered on it, it's just a breakout for 7 RGB LEDs.

My idea here is to solder 12 PCBs together in this arrangement, each PCB will get soldered to each other at an angle of 150, just like a classic dodecagon.

So overall, this project requires 12 PCBs, 1 mainboard, and 11 LED Breakout boards.

BUT Buying two different PCBs will cost more so I prepared this Setup in such a way that if I need to use the ESP12F which is on the bottom side, I have to add a 0Ohms resistor that connects the DIN port of the first LED with GPIO12.

Because of this, we only need to add ESP12F to one board, and the rest of the PCBs can be directly soldered with WS2812B LEDs without changing anything.

here's the schematic of the main PCB.

After Preparing the PCB for this project, I send this to PCBWay for samples.

Why PCBWay?

Duh, because their service is top-notch and great.

I placed the order and received these cool-looking PCBs in just a week!

I have to say, the PCBs that I received were nice as expected, PCBWay, you guys rocks!

PCBWay offers a large variety of services which also includes PCB Assembly, Flexible PCBs, even 3D Printing, not just any 3D printing, Metal 3D Printing!

Check out PCBWay if you need great PCB service for less cost.

after receiving the PCB, it's time to start the assembly process.

Assembly of Main Board

To Prepare the first PCB which would be this Main Board, I first applied solder paste to each component's PADs and then pick and place each component in their assigned place in the right order.

After this, I turned on my DIY SMD HOTPLATE which is a ghetto Hotplate for reflowing PCBs. I place the PCB on it and let the solder paste melt.

This whole process took hardly 5 minutes and the end result was this PCB.

Now we need to add ESP12F on the bottom side with a Soldering Iron because we cannot use our hotplate for this task.

We First add ESP12F in its place and then add the AMS1117 Voltage Regulator along with 10uf Cap, 100uF Cap, and 0ohms Resistance.

then we add THT Components which are the Main Power Switch, Programming header pins, and this THT Micro USB Port.

In the PCB design, I made a mistake.

The Micro USB Pads are right now on the TOP Layer of this PCB, it needed to be on the bottom side. now because of this mistake, when I place the USB port on the bottom side, its polarity inverts, and positive became negative, and negative became positive.

To correct this, I just cut down both of its tracks and soldered two wires separately to make positive terminal negative and negative terminal positive.

We only have to do this change in a single PCB as this is only for supplying power to the Main Board.

Preparing 11 more boards

After Preparing the Mainboard, we need to make other 11 Boards which will only have 7 WS2812B LEDs and 3 10nf Capacitors each.

First, we gather all the components that we need which are 77 WS2812B LEDs and 33 10nF Capacitors.

we start by placing solder paste to each component pad one by one.

For such a task, a stencil is mandatory which I didn't get because I'm such an idiot.

Anyways, this project required a total of 12 PCBs, and 11 of them are breakout boards for ws2812b so we need to reflow all 11 PCBs one by one by first applying solder paste to components pads and then placing the LEDs in their assigned place and then reflowing the PCBs all together one by one.

this will take an eternity to finish but after few hours, you will get these cool-looking PCBs.

Testing each Board

Now, before the final assembly, we first need to check each PCB as they might not work because I probably have not applied solder paste properly to each pad.


To test each Board, I prepared this setup which consists of an Arduino nano connected with a push button. I've uploaded the Buttoncycler sketch which changes the color of WS2812B LEDs on Button Press.

We have to manually solder VCC, GND, and Din Port to 5V, GND, and D6 of Arduino Nano board.

then we provide supply via micro USB cable and press the button, if PCB is shorted the setup won't work and if it does work then this means our setup is perfectly soldered and now we need to remove the wires from this PCB and add the next one for checking.

this will also take time.

After checking all the Boards, all that's left to do is the final assembly.

FINAL Assembly

The shape Dodecagon has 12 sides, the internal angle between the two sides is 150°. To Solder this setup at a proper 150° angle, I modeled this JIG in Fusion360 which holds two PCBs at an angle of 150°.

By putting the JIG at both sides of two PCB, we can securely connect two PCBs together and solder them together by adding solder wire between their two solder pads.

This whole Assembly process involves me putting two PCBs together and connecting them together by soldering their pads together.

No glue is required here, PCBs are being held in their place by solder joints.

to give this Structure more adhesion, I soldered.8mm wire on the outward sides of two joints to give the structure strength from the outside.

Making this kind of stuff with PCBs is possible but it's not really durable.

this structure is so fragile that if it drops from this table, solder joints might tear off and this huge dodecagon will be destroyed!

Anyways, let's flash some sweet sweet code in this setup.


Now, I will First Upload this code to this setup which is actually a hello world code of WS2812B LEDs.

For Uploading the code, I will be using my Nodemcu programmer which is actually a NODEMCU whose ESP12F module is powered down and we are using its CP2102.

For Detail version, check out my post about this process. -

The Nodemcu programmer setup has these breakout points that get connected with ESP12F Module.

  • 3v to 3v,
  • GND to GND
  • RST to RST
  • GPIO0 to GPIO0 //D3 is GPIO0 on Nodemcu
  • TX to TX
  • RX to RX

I Jumper wire between ENA and GND Pin on Nodemcu. this will turn off the ESP12F of the Nodemcu and our external ESP12F will get connected with the Nodemcu's CP2102 chip


  • Connect the Nodemcu programmer with the ESP12F Board
  • open Arduino ide, plug the USB on Nodemcu
  • then go to the Tools menu and select the Nodemcu board that you are using
  • select the right port and hit upload

after uploading goto the serial monitor and hit the reset button on Nodemcu. as you can see, our setup has been connected with the smartphone

now copy this IP address and open this in your web browser and BANG.


So here's our DODECAGON setup, alive and running.

It's being powered by a 5V 2A Charger and yes, it requires a 2A Charger as it's drawing a lot of current than a regular Neopixel ring.

If this video was fun then do not forget to hit that subscribe button!

Thanks PCBWAY for providing PCBs for this project,

Check out PCBWay for getting great PCB service for less price.

and I'll see you guys with the next video!


Main CodeC/C++
#include <Adafruit_NeoPixel.h>

// ESP8266
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

// Webserver Config
const char *ssid = "OneplusNord"; //put your WIFI ssid in between " "
const char *password = "12345678q"; // and password too
ESP8266WebServer server ( 80 );

// Neopixel Config
#define NeoPIN 12 //GPIOI12
#define NUM_LEDS 84 //define number of leds in your strip, mine is 18
int brightness = 250;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, NeoPIN, NEO_RGB + NEO_KHZ800);

const int led = 13;

void setup ( void ) {

  Serial.begin ( 115200 );

  // ##############
  // NeoPixel start
  Serial.println("NeoPixel started");
  // #########
  // Webserver
  pinMode ( led, OUTPUT );
  digitalWrite ( led, 0 );
  WiFi.begin ( ssid, password );
  Serial.println ( "" );

  // Wait for connection
  while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 );
    Serial.print ( "." );

  Serial.println ( "" );
  Serial.print ( "Connected to " );
  Serial.println ( ssid );
  Serial.print ( "IP address: " );
  Serial.println ( WiFi.localIP() );

  if ( MDNS.begin ( "esp8266" ) ) {
    Serial.println ( "MDNS responder started" );

  // what to do with requests
  server.on ( "/", handleRoot );
  server.onNotFound ( handleNotFound );

  Serial.println ( "HTTP server started" );

void loop ( void ) {
  // waiting fo a client

void handleRoot() {
  Serial.println("Client connected");
  digitalWrite ( led, 1 );
  // data from the colorpicker (e.g. #FF00FF)
  String color = server.arg("c");
  Serial.println("Color: " + color);
  // setting the color to the strip 

  // building a website
  char temp[5000];
  int sec = millis() / 1000;
  int min = sec / 60;
  int hr = min / 60;
  char clr [7];
  color.toCharArray(clr, 7);
  snprintf ( temp, 5000,

"<!DOCTYPE html>\n<html>\n\
    <title>Dodecagon Controller</title>\n\
      body { background-color: #cccccc; font-family: Arial; Color: #008; }\
    <meta name=\"viewport\" content=\"width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0\" />\n\
    <p>Uptime: %02d:%02d:%02d</p>\n\
    <form action=\"\" name=\"pick\" method=\"post\">\n\
    <input type=\"color\" name=\"c\" value=\"%02d\" onchange=\"document.forms['pick'].submit();\" />\n\
    &nbsp;<span onclick=\"document.forms['pick'].submit();\" style=\"font-size:16pt;\"> CHANGE </span>\n\

    hr, min % 60, sec % 60, clr
  server.send ( 200, "text/html", temp );
  digitalWrite ( led, 0 );

void handleNotFound() {
  digitalWrite ( led, 1 );
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";

  for ( uint8_t i = 0; i < server.args(); i++ ) {
    message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";

  server.send ( 404, "text/plain", message );
  digitalWrite ( led, 0 );

void setNeoColor(String value){
  Serial.print("Setting Neopixel...");
  // converting Hex to Int
  int number = (int) strtol( &value[1], NULL, 16);
  // splitting into three parts
  int r = number >> 16;
  int g = number >> 8 & 0xFF;
  int b = number & 0xFF;
  // DEBUG
  Serial.print("RGB: ");
  Serial.print(r, DEC);
  Serial.print(" ");
  Serial.print(g, DEC);
  Serial.print(" ");
  Serial.print(b, DEC);
  Serial.println(" ");
  // setting whole strip to the given color
  for(int i=0; i < NUM_LEDS; i++) {
    strip.setPixelColor(i, strip.Color( g, r, b ) );
  // init;
TEST codeC/C++
// This is a demonstration on how to use an input device to trigger changes on your neo pixels.
// You should wire a momentary push button to connect from ground to a digital IO pin.  When you
// press the button it will change to a new pixel animation.  Note that you need to press the
// button once to start the first animation!

#include <Adafruit_NeoPixel.h>

#define BUTTON_PIN   2    // Digital IO pin connected to the button.  This will be
                          // driven with a pull-up resistor so the switch should
                          // pull the pin to ground momentarily.  On a high -> low
                          // transition the button press logic will execute.

#define PIXEL_PIN    6    // Digital IO pin connected to the NeoPixels.

#define PIXEL_COUNT 16

// Parameter 1 = number of pixels in strip,  neopixel stick has 8
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_RGB     Pixels are wired for RGB bitstream
//   NEO_GRB     Pixels are wired for GRB bitstream, correct for neopixel stick
//   NEO_KHZ400  400 KHz bitstream (e.g. FLORA pixels)
//   NEO_KHZ800  800 KHz bitstream (e.g. High Density LED strip), correct for neopixel stick
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

bool oldState = HIGH;
int showType = 0;

void setup() {
  strip.begin();; // Initialize all pixels to 'off'

void loop() {
  // Get current button state.
  bool newState = digitalRead(BUTTON_PIN);

  // Check if state changed from high to low (button press).
  if (newState == LOW && oldState == HIGH) {
    // Short delay to debounce button.
    // Check if button is still low after debounce.
    newState = digitalRead(BUTTON_PIN);
    if (newState == LOW) {
      if (showType > 9)

  // Set the last button state to the old state.
  oldState = newState;

void startShow(int i) {
    case 0: colorWipe(strip.Color(0, 0, 0), 50);    // Black/off
    case 1: colorWipe(strip.Color(255, 0, 0), 50);  // Red
    case 2: colorWipe(strip.Color(0, 255, 0), 50);  // Green
    case 3: colorWipe(strip.Color(0, 0, 255), 50);  // Blue
    case 4: theaterChase(strip.Color(127, 127, 127), 50); // White
    case 5: theaterChase(strip.Color(127,   0,   0), 50); // Red
    case 6: theaterChase(strip.Color(  0,   0, 127), 50); // Blue
    case 7: rainbow(20);
    case 8: rainbowCycle(20);
    case 9: theaterChaseRainbow(50);

// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, c);;

void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));

// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));

//Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait) {
  for (int j=0; j<10; j++) {  //do 10 cycles of chasing
    for (int q=0; q < 3; q++) {
      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, c);    //turn every third pixel on


      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off

//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
  for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
    for (int q=0; q < 3; q++) {
      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on


      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);

Custom parts and enclosures


Main Schematic
Gerber data for PCB
schematic for LED STRIP Testing
Capture5 y3jybu4fy3


Similar projects you might like

Arduino Rotating Platform Based on NEMA17

Project tutorial by Arnov Sharma

  • 6 respects

DIY HexaLeaf with Attiny85

Project tutorial by Arnov Sharma

  • 11 respects

IoT Using ESP8266-01 and Arduino

Project tutorial by Ahmed Ibrahim Ahmed

  • 60 respects

Portal 2 Turret - Master Turret Control of Portal 2 Turrets

Project tutorial by Novachris

  • 19 respects

Atmospheric Air Analyser

Project in progress by Tejas Shah and Kavyashree P

  • 34 respects

How To Use To Improve IoT Projects

Project tutorial by Kutluhan Aktar

  • 9 respects
Add projectSign up / Login