Close

Ṭ̴̯̿̂h̶̫̏̀ę̵̙̒ ̷̩̉C̴̖̞̀͝ọ̵̬̎̔ḓ̵̓e̸̥̞̓̓ part 4 - Picking Colors and Putting Them On Your Retinas

A project log for Jumperless

a jumperless (solderless) breadboard

kevin-santo-cappuccioKevin Santo Cappuccio 09/04/2023 at 18:001 Comment

Table of Contents  (bolded ones are in this project log)

If you're wondering why you feel a strange sexual attraction to this board, I can give you a hint...

I didn't want to do the LEDs at first because it seemed a bit out-of-scope. But after using the prototype board without them, I found that it involved a lot of counting rows to find where you're connecting things. And that gets pretty tedious. 

Around the same time, I had ended my month-long search for anyone who sells just the metal spring clips from a breadboard and resigned to having custom spring clips made by Shenzhen Fulimei Technology Co. LTD. 

Hot tip for makers that want a bunch of some thing custom made using a non-so-common process: put your drawings and requirements up on made-in-china.com, you'll get a ton of quotes within days and just pick your favorite. 

I'm glad I did, because using this thing is sooo much smoother now, you really only have to look at the board for the first connection and then everything else can just be counted as an offset from there. Also, the new versions of the breadboard shell have every 5th embossed number raised (which is actually super weird on breadboards because they count from 1, so there's 3 holes between 1 and 5, and 4 holes between the rest, enjoy being bothered by that forever) which makes it even easier to locate where you're connecting stuff.

How the colors are chosen

It would be really cool if the colors would correspond to the colors in the Wokwi diagram, but there's a couple of problems with that.

First:

All the wires default to green, and having to select a unique color for each wire is kind of a pain in the current version of Wokwi. I know they'll eventually work on something to change this, but their voting system of which features they should work on next shows it's a fairly low priority.

The other issue is this:

Currently, they only support 15 HTML colors. So having only 15 different possible nets with unique colors (fewer, because black, grey, and brown wouldn't really show up on RGBs)

So we're just gonna pick our own colors.

The special nets have hard-coded colors, they are as follows:

  rgbColor specialNetColors[8] = {      
  {00, 00, 00},
   {0x00, 0xFF, 0x30},    //GND     - Green 
   {0xFF, 0x41, 0x14},    //5V     - Red leaning towards orange {0xFF, 0x10, 0x40},    //3.3v    - Red leaning towards purple 
   {0xeF, 0x78, 0x7a},    //DAC 0-5V- Pinkish White  (changes brightness and hue based on output magnitude) 
   {0xeF, 0x40, 0x7f},    //DAC +-8V- Purplish White (changes brightness based on output magnitude, hue for positive or negative) 
   {0xFF, 0xff, 0xff},    //Current Sense+ - Yellow 
   {0xff, 0xFF, 0xff}};   //Current Sense- - Blue 
   
   rgbColor railColors[4] = { 
    {0xFF, 0x32, 0x30},    //GND - Green 
    {0x00, 0xFF, 0x30},    //5V - Red 
    {0xFF, 0x32, 0x30},    //GND - Green 
    {0x00, 0xFF, 0x30}};   //5V - Red

Here's what those look like:

Note the bottom rail is 3.3V, top rail is 5V. pot1-sig is DAC 0, pot2-sig is DAC1.

The colors are saved at full brightness and scaled down to the default brightness or the brightness you set in the menu when they're displayed.

For the other nets, here's the general algorithm:

Here's how that plays out:

Correcting for FR4

For the colors to be useful as an indicator, you should be able to recognize a color is the same whether you're directly looking a the LED on the breadboard or it's passing through the yellowish PCB around the Nano Header.

This turned out to be surprisingly complicated, I basically spent 2 days just tweaking the values until it looked right. Why it was so complex is that the yellowish filtering only affects some colors and basically doesn't do anything to others (except making them slightly dimmer). 

The shifts are defined as constants in LEDs.h

#define PCBEXTINCTION 30 //extra brightness for to offset the extinction through pcb 


#define PCBREDSHIFTPINK -18 //extra hue shift to offset the hue shift through pcb 

#define PCBGREENSHIFTPINK -25 

#define PCBBLUESHIFTPINK 35 


#define PCBREDSHIFTBLUE -25 //extra hue shift to offset the hue shift through pcb 

#define PCBGREENSHIFTBLUE -25 

#define PCBBLUESHIFTBLUE 42

 Here's that whole function if you want to try figuring it out

The shifting is different depending on which side of the spectrum you're on, hence the shifts for the blue and pink separately. I was going to do like a continuous blending between the two, but it turns out it looks better with just a cutoff at a certain hue.

struct rgbColor pcbColorCorrect(rgbColor colorToShift)
{

    uint8_t redShift = 0;
    uint8_t greenShift = 0;
    uint8_t blueShift = 0;

    int testNeg = 0;

    struct rgbColor colorToShiftRgb = colorToShift;

    struct hsvColor colorToShiftHsv = RgbToHsv(colorToShiftRgb);

    // colorToShiftHsv.v += PCBEXTINCTION;

    if (colorToShiftHsv.h > 100 && colorToShiftHsv.h < 150)
    {

        if (PCBREDSHIFTBLUE < 0)
        {
            testNeg = colorToShiftRgb.r;
            testNeg -= abs(PCBREDSHIFTBLUE);

            if (testNeg < 0)
            {
                colorToShiftRgb.r = 0;
            }
            else
            {

                colorToShiftRgb.r = colorToShiftRgb.r - abs(PCBREDSHIFTBLUE);
            }
        }
        else
        {

            colorToShiftRgb.r = colorToShiftRgb.r + abs(PCBREDSHIFTBLUE);

            if (colorToShiftRgb.r > 254)
            {
                colorToShiftRgb.r = 254;
            }
        }

        if (PCBGREENSHIFTBLUE < 0)
        {

            testNeg = colorToShiftRgb.g;
            testNeg -= abs(PCBGREENSHIFTBLUE);

            if (testNeg < 0)
            {
                colorToShiftRgb.g = 0;
            }
            else
            {
                colorToShiftRgb.g = colorToShiftRgb.g - abs(PCBGREENSHIFTBLUE);
            }
        }
        else
        {
            colorToShiftRgb.g = colorToShiftRgb.g + abs(PCBGREENSHIFTBLUE);
            if (colorToShiftRgb.g > 254)
            {
                colorToShiftRgb.g = 254;
            }
        }

        if (PCBBLUESHIFTBLUE < 0)
        {

            testNeg = colorToShiftRgb.b;

            testNeg -= abs(PCBBLUESHIFTBLUE);

            if (testNeg < 0)
            {
                colorToShiftRgb.b = 0;
            }
            else
            {
                colorToShiftRgb.b = colorToShiftRgb.b - abs(PCBBLUESHIFTBLUE);
            }
        }
        else
        {
            colorToShiftRgb.b = colorToShiftRgb.b + abs(PCBBLUESHIFTBLUE);
            if (colorToShiftRgb.b > 254)
            {
                colorToShiftRgb.b = 254;
            }
        }

    }
    else if (colorToShiftHsv.h >= 150 && colorToShiftHsv.h < 255)
    {

        if (PCBREDSHIFTPINK < 0)
        {
            testNeg = colorToShiftRgb.r;
            testNeg -= abs(PCBREDSHIFTPINK);

            if (testNeg < 0)
            {
                colorToShiftRgb.r = 0;
            }
            else
            {

                colorToShiftRgb.r = colorToShiftRgb.r - abs(PCBREDSHIFTPINK);
            }
        }
        else
        {

            colorToShiftRgb.r = colorToShiftRgb.r + abs(PCBREDSHIFTPINK);

            if (colorToShiftRgb.r > 254)
            {
                colorToShiftRgb.r = 254;
            }
        }

        if (PCBGREENSHIFTPINK < 0)
        {

            testNeg = colorToShiftRgb.g;
            testNeg -= abs(PCBGREENSHIFTPINK);

            if (testNeg < 0)
            {
                colorToShiftRgb.g = 0;
            }
            else
            {
                colorToShiftRgb.g = colorToShiftRgb.g - abs(PCBGREENSHIFTPINK);
            }
        }
        else
        {
            colorToShiftRgb.g = colorToShiftRgb.g + abs(PCBGREENSHIFTPINK);
            if (colorToShiftRgb.g > 254)
            {
                colorToShiftRgb.g = 254;
            }
        }

        if (PCBBLUESHIFTPINK < 0)
        {

            testNeg = colorToShiftRgb.b;

            testNeg -= abs(PCBBLUESHIFTPINK);

            if (testNeg < 0)
            {
                colorToShiftRgb.b = 0;
            }
            else
            {
                colorToShiftRgb.b = colorToShiftRgb.b - abs(PCBBLUESHIFTPINK);
            }
        }
        else
        {
            colorToShiftRgb.b = colorToShiftRgb.b + abs(PCBBLUESHIFTPINK);
            if (colorToShiftRgb.b > 254)
            {
                colorToShiftRgb.b = 254;
            }
        }
    }
    return colorToShiftRgb;
}

I do a lot of swapping between RGB and HSV in the code, if I was working with a slower microcontroller, I might have been more strict about it, but on the RP2040 it handles it just fine.  

How it sends the data to the LEDs

Jumperless just uses the standard Adafruit_NeoPixel library, one thing to note about it is that it's blocking. So when you're sending data to LEDs, it stops your other code.

I didn't even notice this until I was working on code to have the DACs change hues depending on the measured output. Like this:

Note that DAC 1 (right) is an actual measurement from ADC 3, If you look closely you can see a bit of "wobbliness," that's from taking real measurements of the output. For now DAC 0 (left) is just showing what it should be set to, but it's super easy to change that in the code.

So, to get around the blocking nonsense, I run the LEDs from core 2, just like the CH446Qs. So if anything on core 1 wants to update the LEDs, it just sets showLEDsCore2 = 1 and the second core will see that and do its thing.

void loop1() // core 2 handles the LEDs and the CH446Q8
{


  if (showLEDsCore2 >= 1)
  {
    int rails = showLEDsCore2;
   
    //showNets();
    if (rails == 1)
    {
     lightUpRail();
    } 
   delayMicroseconds(5200);
    

    leds.show();
    delayMicroseconds(9200);
    showLEDsCore2 = 0;
  }

  if (sendAllPathsCore2 == 1)
  {
    delayMicroseconds(9200);
    sendAllPaths();
    delayMicroseconds(2200);
    showNets();
    delayMicroseconds(9200);
    sendAllPathsCore2 = 0;
  }


  if (logoFlash == 2)
  {
    logoFlashTimer = millis();
    logoFlash = 1;
    
  } 

  if (logoFlash == 1 && logoFlashTimer != 0 && millis() - logoFlashTimer > 600)
  {
    logoFlash = 0;
    logoFlashTimer = 0;
    //lightUpRail();
    leds.setPixelColor(110, 0x550008);
    leds.show();
  }


}

This allows the LEDs to update quickly and not cause weird glitches in the DAC outputs.

That's all I got about the LEDs, if you want me to be clearer about something, let me know and I'll add to this log.

Next part - The Jumperless Wokwi Bridge App or: How I Learned Python and Went From Wishing I Knew Python to Wishing I Didn't

Discussions

Mike wrote 09/05/2023 at 16:44 point

So rad! The custom clips are just next level. I see myself ordering some in the near future. 

  Are you sure? yes | no