User avatar
By RichardS
#43214 User
Barnabybear

Description
What does it do and why?

I wanted to be able to control multiple strips of 12V LED strips, some single colour, some RGB. From an E1.31 (Streaming-ACN) source (DMX over a network). Ideally for ease of setup the controller needed to be small and wireless so it could be incorporated into the various display elements and for ease of setup only power needed to be connected. I estimated needing between ten and twenty of these so cost was a factor.

Why would you want to do this? Many reasons, but in my case amongst other things, I wanted to add some talking Christmas trees to my current Christmas display. Below is a link to how the finished items need to look and function (I feel I need to state the video is not mine it is a published example of talking Christmas trees and what can be achieved with a sCAN controller – which is why I needed one).

So what does it look like?

The front with all the main components.

The back with the PCT fuses (polymeric Positive Temperature Coefficient device) to much current = gets hot and shuts down – cools and resets when powered off. No need to replace like a normal fuse, ideal for building into elements that are not easy to access.

This is my first ever SMD board. The front took 2 x 30 minutes to populate and just over 5 minutes each time to reflow with a hot air gun with about 30 minutes testing between. The board was assembled in three stages, the lower half (FETs and 10K resistors). These were tested to ensure there were no shorts that could have damaged the PCA9685 and that the FETs were functional with a 3.3V coin cell. Then the top half was populated and reflowed whist the bottom was covered with aluminium baking foil. Finally the PTC’s and socket were added by hand.

What are the main components and what do they do?

ESP8266-01

This receives via Wi-Fi a packet of data that contains a value between 0 & 255 which relates to the brightness of a channel (0 is off and 255 is fully on). The packet can contain up to 512 channels of data. This packet is parsed.

The parsed values then have Gamma correction applied to them and are converted from 8 to 12 bits. LEDs and our eyes are not very well matched; our eyes notice the smallest of changes at low light levels and are less sensitive the brighter something gets, whereas LEDs have their greatest change in light output at low levels and reduce as they get brighter, basically the opposite. The end result is that we see big changes in the brightness between 0 and 127, and from 128 to 255 we hardly notice any difference in brightness. This is where Gamma correction comes in, it adjusts the values so that to our eyes each step increases the brightness of the LEDs by the same amount. In order to do this we need to convert to 12 bit resolution, this is then sent via I2C to the PWM IC.

Note: Vcc, Rst & CH_PD need to be linked with solder on the ESP header pins.

PCA9685.

This is a 16 channel PWM IC. Once given a value this will generate a PWM output until the value is changed. This is useful as it means 16 channels can be generated with minimal input from the ESP. Interestingly because the PWM frequency is settable this enables this board to control Servos with a pullup resistor and be used for POV effects.

Si2303DS MOSFETs.

These are logic level N-Channel MOSFETs rated at about 20V and just over 2A dependant on application. They are used to form open drain outputs controlled by the PCA9685, to enable the switching of 12V LED strips.

PTC Fuse.

The board has 16 open drain outputs, each has a corresponding supply (board supply voltage). These are grouped into 3s, and the group of 3 is covered by a 2.6A PTC fuse. In total there are 6 PTCs, output 1 & the regulated 3.3V supply share one, the others are grouped in 3s (2, 3, & 4 etc.).

PCB.

A simple double sided board that was kept to 50x50mm to take advantage of 10 for $14 from Dirty PCBs. With two 4mm mounting holes that also form the incoming power supply connection.

Note: when using both bolt holes to mount the board, the object it is fixed to must be non-conductive.

Features of interest.

The board has voltage regulation from the supply which can range from 5 to 12V as required by the LEDs to be supplied, to supply 3.3V for the ESP8266-01 and PCA9685.

The boards can be configured as ‘master’ or ‘slave’ dependant on the components fitted. A ‘master’ has all components fitted including ESP8266 and the voltage regulator, the ‘slave’ board picks up the I2C and regulated voltage from the ‘master’ so those components are not required (keeps the cost down if you need more than 16 channels). With this board design up to 3 slaves can be used giving 64 channels (this limit is because I only broke out 2 of the 6 available I2C address lines on the PCA9685. In theory this could be expanded to 63 slaves 1024 channels).

Note: Components with an ‘x’ suffix are not required on slave boards.

As the output (inputs – I never really know what to call them) are open drain you can use mixed voltages if required but only one voltage can be supplied from the board.

Schematic.

Below is the schematic. The symbol for the Si2303 N-channel FET is incorrect (long story – the pad layout is correct for the board and I didn’t have time to mess about or I wouldn’t have got the boards to the manufacturer before the Chinese new year and I’m not making any changes now until all the testing is complete).

Components.

Not much to be said the schematic sort of says it all. Before the regulator minimum 12V rated after minimum 3.3v. PCTs I used 13V 2.6A. Change address resistors are – not needed for the first board and 0 ohm as required. With careful shopping a master could cost as little as £6.25 ($9.00) and the slaves £4.02 ($5.78).

Code.

This code uses three libraries which are all on Github (two I’d think you have anyway if you own an ESP).

A change is need to Wire.h. As standard the buffer length is only 32 bits, in order to send a full set of instructions to the PCA9685 via I2C we need to send 68 bits.

Code: Select all
#define BUFFER_LENGTH 32 // needs changing to

#define BUFFER_LENGTH 68 // if not only the first 7 outputs will work


Please take note of the setup comments.

Code: Select all
#include <ESP8266WiFi.h>
#include <E131.h>
#include <Wire.h>
 
//==================== Setup infomation section start ====================//
                        //==== WiFi & Universe ====//
const char ssid[] = "????????";         /* Replace ???????? with your SSID */
const char passphrase[] = "????????";   /* Replace ???????? with your WPA2 passphrase */
const int universe = 1;   /* Replace with your universe number */
const int channel = 0;   /* Replace with your channel number */
 
                        //==== Control boards ====//
//set boards used, 1 = used, 0 = not, order = Board 0x43, Board 0x42, Board 0x41, Board 0x40.
const int board_used = (B0001); // boards used.
//channel numbers for output 1 -> 16 in that order, 0 = unused output.
int board_40[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; // if fitted.
int board_41[16] = {0, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}; // if fitted.
int board_42[16] = {0, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45}; // if fitted.
int board_43[16] = {0, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60}; // if fitted.
//if using RGB strip use outputs 2 to 16 to match PTC fuses.
                        //==== Debug - may cause lag ====//
const int debug = 1; //set value 1= debug 0 = normal
//==================== Setup infomation section end ====================//
 
int gamma_val[256] = {0, 1, 1, 1, 1, 2, 2, 3, 4, 5, 6, 8, 9, 11, 12, 14, 16, 18, 20,
23, 25, 28, 30, 33, 36, 39, 43, 46, 49, 53, 57, 61, 64, 69, 73, 77, 82, 86, 91, 96,
101, 106, 111, 116, 122, 128, 133, 139, 145, 151, 157, 164, 170, 177, 184, 191, 197,
205, 212, 219, 227, 234, 242, 250, 258, 266, 274, 283, 291, 300, 309, 317, 326, 336,
345, 354, 364, 373, 383, 393, 403, 413, 423, 434, 444, 455, 466, 477, 488, 499, 510,
522, 533, 545, 556, 568, 580, 593, 605, 617, 630, 642, 655, 668, 681, 694, 708, 721,
735, 748, 762, 776, 790, 804, 818, 833, 847, 862, 877, 892, 907, 922, 937, 953, 968,
984, 1000, 1016, 1032, 1048, 1064, 1081, 1097, 1114, 1131, 1148, 1165, 1182, 1199, 1217,
1234, 1252, 1270, 1288, 1306, 1324, 1342, 1361, 1379, 1398, 1417, 1436, 1455, 1474, 1494,
1513, 1533, 1552, 1572, 1592, 1612, 1632, 1653, 1673, 1694, 1715, 1735, 1756, 1777, 1799,
1820, 1841, 1863, 1885, 1907, 1929, 1951, 1973, 1995, 2018, 2040, 2063, 2086, 2109, 2132,
2155, 2179, 2202, 2226, 2250, 2273, 2297, 2322, 2346, 2370, 2395, 2419, 2444, 2469, 2494,
2519, 2544, 2570, 2595, 2621, 2647, 2672, 2698, 2725, 2751, 2777, 2804, 2830, 2857, 2884,
2911, 2938, 2965, 2993, 3020, 3048, 3076, 3104, 3132, 3160, 3188, 3217, 3245, 3274, 3303,
3331, 3360, 3390, 3419, 3448, 3478, 3507, 3537, 3567, 3597, 3627, 3658, 3688, 3719, 3749,
3780, 3811, 3842, 3873, 3905, 3936, 3968, 3999, 4031, 4063, 4095}; //gamma corrected channel value.
int OUTPUT_NUMBER;
int CHANNEL_NUMBER;
int CHANNEL_VALUE;
int OUTPUT_VALUE;
int OE = 3; //OE = GPIO 3.
E131 e131;
 
void setup() {
Wire.begin(2,0); //start Wire and set SDA - SCL
Serial.begin(115200); //start serial for debug.
 
                    //========== PCA setup ==========//
 
    if(bitRead(board_used,0)){ //check to see if this board is used - if not skip section.
      Wire.beginTransmission(0x40); //start comms with board 0x40.
      Wire.write(00); //move pointer to MODE 1 register.
      Wire.write(0xa1); //enables ALL CALL ADDRESS, Register Auto-Increment & Restart.
      Wire.endTransmission(); //end comms with board.
    }                                                                                        if(debug) {Serial.println("board 0x40 first setup done");}
    if(bitRead(board_used,1)){ //check to see if this board is used - if not skip section.
      Wire.beginTransmission(0x41); //start comms with board 0x41.
      Wire.write(00); //move pointer to MODE 1 register.
      Wire.write(0xa1); //enables ALL CALL ADDRESS, Register Auto-Increment & Restart.
      Wire.endTransmission(); //end comms with board.
    }                                                                                        if(debug) {Serial.println("board 0x41 first setup done");}
    if(bitRead(board_used,2)){ //check to see if this board is used - if not skip section.
      Wire.beginTransmission(0x42); //start comms with board 0x42.
      Wire.write(00); //move pointer to MODE 1 register.
      Wire.write(0xa1); //enables ALL CALL ADDRESS, Register Auto-Increment & Restart.
      Wire.endTransmission(); //end comms with board.
    }                                                                                        if(debug) {Serial.println("board 0x42 first setup done");}
    if(bitRead(board_used,3)){ //check to see if this board is used - if not skip section.
      Wire.beginTransmission(0x43); //start comms with board 0x43.
      Wire.write(00); //move pointer to MODE 1 register.
      Wire.write(0xa1); //enables ALL CALL ADDRESS, Register Auto-Increment & Restart.
      Wire.endTransmission(); //end comms with board.
    }                                                                                        if(debug) {Serial.println("board 0x43 first setup done");}
 
  Wire.beginTransmission(0xE0); //start comms with all boards (ALLCALLADR).
  Wire.write(01); //move pointer to MODE 2 register.
  Wire.write(0x02); //(0x04) invert outputs for use with FETs - (0x02) to set as source.
  Wire.endTransmission(); //end comms with board.
                                                                                             if(debug) {Serial.println(); Serial.println("all boards second setup done");}
  Wire.beginTransmission(0xE0); //start comms with all boards (ALLCALLADR).
  Wire.write(0x06); //move pointer to LED 0 register.
  for (OUTPUT_NUMBER = 0; OUTPUT_NUMBER < 64; OUTPUT_NUMBER++) { //loop 64 times.
  Wire.write(0x0F); //write on time 0, off time 0 to all LEDs (all off).
  }                                                                                           if(debug) {Serial.println("all boards third setup done");}
  Wire.endTransmission();  //end comms with board.
 
                        //========== ESP8266-01 setup ==========//
  pinMode(3, FUNCTION_3); //change pin 3 from Rx to GPIO 3 (OE output enable).
  pinMode(3, OUTPUT);
  digitalWrite(3, LOW); //set OE low to enable outputs.
                                                                                              if(debug) {Serial.println("all boards setup complete ");}
                        //========== e131 setup ==========//
  /* Choose one to begin listening for E1.31 data */
  e131.begin(ssid, passphrase);               /* via Unicast on the default port */
  //e131.beginMulticast(ssid, passphrase, universe); /* via Multicast for Universe 1 */
                                                                                              if(debug) {Serial.println("if paused - waiting for e1.31 data ");}
}
 
void loop() {
    /* Parse a packet */
    uint16_t num_channels = e131.parsePacket();
   
    /* Process channel data if we have it */
    if (num_channels) {
 
                        //========== board 0x40 main loop ==========//
    if(bitRead(board_used,0)){ //check to see if this board is used - if not skip section.
      Wire.beginTransmission(0x40); //start comms with board 0x40.
                                                                                            if(debug) {Serial.print("board "); Serial.println(0x40);}
      Wire.write(0x06); //move pointer to LED 0 register.
      for (OUTPUT_NUMBER = 0; OUTPUT_NUMBER < 16; OUTPUT_NUMBER++) { //cycle through outputs.       
                                                                                            if(debug) {Serial.print(" | OUTPUT_NUMBER "); Serial.print(OUTPUT_NUMBER);}   
        CHANNEL_NUMBER = board_40[OUTPUT_NUMBER]; //lookup channel number for output.                           
                                                                                            if(debug) { Serial.print(" | CHANNEL_NUMBER "); Serial.print(CHANNEL_NUMBER);}
        WRITE_FUNCTION(); //call function to finish lookups and write data.
      }
      Wire.endTransmission();
    } //0x40 end
                        //========== board 0x41 main loop ==========//
    if(bitRead(board_used,1)){ //check to see if this board is used - if not skip section.
      Wire.beginTransmission(0x41); //start comms with board 0x41.
                                                                                            if(debug) {Serial.print("board "); Serial.println(0x41);}
      Wire.write(0x06); //move pointer to LED 0 register.
      for (OUTPUT_NUMBER = 0; OUTPUT_NUMBER < 16; OUTPUT_NUMBER++) { //cycle through outputs.
                                                                                            if(debug) {Serial.print(" | OUTPUT_NUMBER "); Serial.print(OUTPUT_NUMBER);}   
        CHANNEL_NUMBER = board_41[OUTPUT_NUMBER]; //lookup channel number for output.
                                                                                            if(debug) { Serial.print(" | CHANNEL_NUMBER "); Serial.print(CHANNEL_NUMBER);}     
        WRITE_FUNCTION(); //call function to finish lookups and write data.
      }
      Wire.endTransmission();
    } //0x41 end
                        //========== board 0x42 main loop ==========//
    if(bitRead(board_used,2)){ //check to see if this board is used - if not skip section.
      Wire.beginTransmission(0x42); //start comms with board 0x42.
                                                                                            if(debug) {Serial.print("board "); Serial.println(0x42);}
      Wire.write(0x06); //move pointer to LED 0 register.
      for (OUTPUT_NUMBER = 0; OUTPUT_NUMBER < 16; OUTPUT_NUMBER++) { //cycle through outputs.
                                                                                            if(debug) {Serial.print(" | OUTPUT_NUMBER "); Serial.print(OUTPUT_NUMBER);}       
        CHANNEL_NUMBER = board_42[OUTPUT_NUMBER]; //lookup channel number for output.
                                                                                            if(debug) { Serial.print(" | CHANNEL_NUMBER "); Serial.print(CHANNEL_NUMBER);}       
        WRITE_FUNCTION(); //call function to finish lookups and write data.
      }
      Wire.endTransmission();
    } //0x42 end
                        //========== board 0x43 main loop ==========//
    if(bitRead(board_used,3)){ //check to see if this board is used - if not skip section.
      Wire.beginTransmission(0x43); //start comms with board 0x43.
                                                                                            if(debug) {Serial.print("board "); Serial.println(0x43);}
      Wire.write(0x06); //move pointer to LED 0 register.
      for (OUTPUT_NUMBER = 0; OUTPUT_NUMBER < 16; OUTPUT_NUMBER++) { //cycle through outputs.
                                                                                            if(debug) {Serial.print(" | OUTPUT_NUMBER "); Serial.print(OUTPUT_NUMBER);}       
        CHANNEL_NUMBER = board_43[OUTPUT_NUMBER]; //lookup channel number for output.
                                                                                            if(debug) { Serial.print(" | CHANNEL_NUMBER "); Serial.print(CHANNEL_NUMBER);}     
        WRITE_FUNCTION(); //call function to finish lookups and write data.
      }
      Wire.endTransmission();
    } //0x43 end
 
                                                                                            if(debug) { Serial.println("End of loop");}
  }} //if & void loop end
 
                        //========== write output function ==========//
void WRITE_FUNCTION() { //function to finish lookups and write data to PCA9685. Registers set to auto increment.
  switch (CHANNEL_NUMBER) {
    case 0: //check if channel unused (0).
      OUTPUT_VALUE = 0; //set value to 0.
                                                                                            if(debug) { Serial.print(" | UN-USED_OUTPUT ");}
      break;
    default: //if here channel must bee used so get value.
      CHANNEL_VALUE = e131.data[CHANNEL_NUMBER - 1]; //lookup value of channel.
if(debug) { Serial.print(" | CHANNEL_VALUE "); Serial.print(CHANNEL_VALUE);}
      OUTPUT_VALUE = gamma_val[CHANNEL_VALUE]; //lookup gamma corrected value.
  }
  Wire.write (0x00); //switch on time LSB - switches on at zero.
                                                                                            if(debug) { Serial.print(" | LSB ON "); Serial.print(0x00);}
  Wire.write (0x00); //switch on time MSB.
                                                                                            if(debug) { Serial.print(" | MSB ON "); Serial.print(0x00);}
  Wire.write (OUTPUT_VALUE); //switch off time LSB - switches off after OUTPUT_VALUE cycles.
                                                                                            if(debug) { Serial.print(" | LSB OFF "); Serial.print((OUTPUT_VALUE));}
  OUTPUT_VALUE = OUTPUT_VALUE >> 8; //bit shift to remove LSB.
  Wire.write (OUTPUT_VALUE); //switch off time MSB.
                                                                                            if(debug) { Serial.print(" | MSB OFF "); Serial.println(OUTPUT_VALUE);}
} //write output end
 



OK – time to finish up.

The boards arrived on Tuesday (now Sunday) only the master has been assembled and run for 48 hours with 1A loads, it worked fine. If you thinking of having ago at this it should work fine.

Usual sort of disclaimer – posted in good faith, use any information at your own risk. It was written in word -> emailed -> put on a website – so much could have gone wrong and so much I have missed out.

Feel free to PM me for updates or if you need any help with the PCA9685 – very cool IC a bit of a steep learning curve.

Parts

Links
[link_open={URL}]{TEXT}[/link_open]

Video


Images
Attachments
image007.jpg
image008.jpg
image009.jpg
User avatar
By Barnabybear
#43286 Update: A quick video of a test here

For those not wanting to go down the SMD (serface mounted device) route the are some good breakout boards for the PCA9685 to convert to a normal 0.1" pinned device. I used one like this for development https://www.adafruit.com/product/815 (shop around).

Whilst I used sCAN (DMX) as an input to control the outputs / LED strips this could be modified to accept anything you wanted as an input. The code to drive the PCA9685 is there - feed it values however you want.

I suppose it more of a extended breakout board in ways, you can use it to drive any DC items - relays, stepper motors, servos, robot motors or solenoids.

I've added the eagle files to this post and a link to my dropbox as the schematic is unreadable.
Attachments
(43.35 KiB) Downloaded 82 times