Choppy Hue change on RGB LED spectrum at low brightness

So I'm working on a project that involves changing the hue of the RBG LED in a spectral pattern, and its brightness is adjusted by a potentiometer sending a signal to the arduino and it uses it to scale down the brightness of the led accordingly, so if the potentiometer was at 50% and the LED was at a point in its cycle where it was at Red: 255, Blue: 155, Green: 0 then it would change to Red: 177, Blue: 77, Green: 0, but what ends up happening is as the brightness is turned down, the resulting hue transitions is much less smooth, so that when the potentiometer is at a high resistance (say 90-95%) the serial output will show that the output is something like

RED:0 BLUE:0 GREEN:0
RED:1 BLUE: 0 GREEN:0
RED:1 BLUE:0 GREEN:0
RED:1 BLUE:0 GREEN:0
RED:2 BLUE:0 GREEN:0
RED:2 BLUE:0 GREEN:0
RED:2 BLUE:0 GREEN:0
RED:3 BLUE:0 GREEN:0
RED:3 BLUE:0 GREEN:0
RED:3 BLUE:0 GREEN:0
RED:3 BLUE:1 GREEN:0
RED:3 BLUE:1 GREEN:0
RED:3 BLUE:1 GREEN:0
RED:3 BLUE:2 GREEN:0

as opposed to the usual style which is more like

RED:0 BLUE:0 GREEN:0
RED:5 BLUE: 0 GREEN:0
RED:10 BLUE:0 GREEN:0
RED:15 BLUE:0 GREEN:0
RED:20 BLUE:0 GREEN:0
RED:25 BLUE:0 GREEN:0
RED:30 BLUE:0 GREEN:0
RED:35 BLUE:0 GREEN:0
RED:40 BLUE:0 GREEN:0
RED:45 BLUE:0 GREEN:0
RED:50 BLUE:0 GREEN:0
RED:55 BLUE:0 GREEN:0
RED:60 BLUE:0 GREEN:0
RED:65 BLUE:0 GREEN:0

it acts this way because the relative maximum moves down from 255 to 3 so the percentage decrease only shows in 3 intervals vs the normal 255, I looked into digital potentiometers to solve the problem but they don't seem like they have as many resistance levels as an analog one and very expensive, any ideas? I dont know if theres a fix in the coding or a hardware fix? heres the code for reference! thank you in advance!

const int  buttonLightPin = A3; //sets buttonLight to input 12
const int  buttonShowPin = A5; //sets buttonShow to input 13
const int brightnessPotPin = A0; //sets the pot output to A0
const int colorPotPin = A1; // sets the color during solid light
int brightnessPotState; //variable for storing the value of the brightness pot
float brightnessSet =1; // variable for storing the brightness level
int buttonState = 2; // variable for storing the mode that the devvice is in
// 0 is for off, 1 is for light show and 2 is for solid color
int PIN_ONE = 11; // input for red; red jumper, white cord
int PIN_TWO = 10; // input for green; green jumper
int PIN_THREE = 9; //input for blue; red jumper, yellow cord
int PIN_FOUR = 6;
int PIN_FIVE = 5;
int PIN_SIX = 3;
int counter;
int numColors = 900;
int animationDelay = 0; //adjustable speed of animation
float brightness = 0; // default brightness level
int lag = 0;
int showCounter = 0;
int lightCounter = 0;
int offCounter = 0;
void setup() 
{
  Serial.begin(9600);
  pinMode(buttonLightPin, INPUT_PULLUP);
  pinMode(buttonShowPin, INPUT_PULLUP);
  pinMode(brightnessPotPin, INPUT_PULLUP);
  pinMode(colorPotPin, INPUT_PULLUP);
  pinMode(PIN_ONE, OUTPUT);
  pinMode(PIN_TWO, OUTPUT);
  pinMode(PIN_THREE, OUTPUT);
  pinMode(PIN_FOUR, OUTPUT);
  pinMode(PIN_FIVE, OUTPUT);
  pinMode(PIN_SIX, OUTPUT);
}
void loop()
{
  Serial.print("Begin");
  float hue;
  float colorNumber = counter > numColors ? counter - numColors: counter;
  float brightness = setBrightness();
  buttonState = setButtonState(); //function below for determining button state
  Serial.print("  MODE: ");
  if(buttonState == 1) // if buttonstate is 1, light show will commence
  {
      Serial.print("SHOW");
      float hue = (colorNumber / float(numColors)) * 360; 
      colorSet(hue, brightness);
      counter = (counter + 1) % (numColors * 2); // sets counter to increment the color changes
      delay(animationDelay);
  }
  else if(buttonState == 2) // if buttonstate is 2, LEDs will emit solid colors determined by the colorPotPin
  {
      Serial.print("SOLID COLOR");
      hue = solidColor();
      colorSet(hue, brightness);
  }
  else if(buttonState == 0) // if buttonstate is 0, no light will emit
  {
      Serial.print("OFF");
      off();
  }
  Serial.print("\t");
  Serial.println("END");
}
float setBrightness() // finds brightness from pot
{
  float brightnessPotState; //variable for storing the value of the brightness pot
  brightnessPotState = analogRead(brightnessPotPin); //reads brightness
  Serial.print("  Brightness Pot: ");
  Serial.print(brightnessPotState);
  if(brightnessPotState <= 1000) // converts pot input(0-1023) into a percentage
    //i ignored the final 23 values and lumped it into a solid 100%
  {
    brightnessSet = brightnessPotState/1000;
  }
  else
  {
    brightnessSet = 1;
  }
  return brightnessSet; // returns the percentage brightness
}
int setButtonState() //toggles between Show, Light, and off
{
  int buttonShow; // variable storing the value of the buttonShowPin
  int buttonLight; //variable storing the value of the buttonLightPin
  int x;
  buttonShow = digitalRead(buttonShowPin);
  buttonLight = digitalRead(buttonLightPin);
  Serial.print("\t");
  Serial.print ("  Button Show: ");
  Serial.print (buttonShow);
  Serial.print ("\t");
  Serial.print ("  Button Light: ");
  Serial.print (buttonLight);
  Serial.print ("\t");
  if (buttonShow == LOW)
  {
    return(1);
  }
  else if( buttonLight == LOW)
  {      
    return(2);
  }
  else if(buttonLight == HIGH && buttonShow == HIGH)
  {
    return(0);
  }
}
void off() // turns off all LEDs
{
  analogWrite(PIN_ONE, LOW); 
  analogWrite(PIN_TWO, LOW);
  analogWrite(PIN_THREE, LOW);
  analogWrite(PIN_FOUR, LOW);
  analogWrite(PIN_FIVE, LOW);
  analogWrite(PIN_SIX, LOW);
}
float solidColor() //sets the color for light mode
{
  float hueSet;
  float colorPotState;
  colorPotState = analogRead(colorPotPin);
  Serial.print ("\t");
  Serial.print ("Color Pot: ");
  Serial.print (colorPotState);
  hueSet = (colorPotState*360)/1023;
  return hueSet;
}
long colorSet(float _hue, float _brightness) //interprets and changes the color
{
  float one = 0; // initialize local variables
  float two = 0;
  float three = 0;
  float four = 0;
  float five = 0;
  float six = 0;
    if (_hue == 360.0)  // when hue reaches 360, it resets to 0
    {
      _hue = 0;
    }
    int slice = _hue / 20.0; // initializes varible to break the process into 18 "slices"
    float hue_frac = (_hue / 20.0) - slice; // each slice is one 18th of the process
    float low = 0;
    float decrease = _brightness * (1 - hue_frac);
    float increase = _brightness * hue_frac;
    float high = _brightness;
    switch(slice) //depending on which slice the program is in, it will pick one of these options to adjust the color
    {
    case 0:
      one = high;
      two = increase;
      three = low;
      
      four = high;
      five = low;
      six = increase;
      break;
    case 1:
      one = decrease;
      two = high;
      three = low;
      
      four = decrease;
      five = low;
      six = high;
      break;
    case 2:
      one = low;
      two = high;
      three = increase;
      
      four = low;
      five = increase;
      six = high;
      break;
    case 3:
      one = low;
      two = decrease;
      three = high;
      
      four = low;
      five = high;
      six = decrease;
      break;
    case 4:
      one = low;
      two = increase;
      three = high;
      
      four = low;
      five = high;
      six = low;
      break;
    case 5:
      one = low;
      two = high;
      three = decrease;
      
      four = low;
      five = high;
      six = low;
      break;
    case 6:
      one = low;
      two = high;
      three = increase;
      
      four = increase;
      five = high;
      six = low;
      break;
    case 7:
      one = low;
      two = decrease;
      three = high;
      
      four = high;
      five = decrease;
      six = low;
      break;
    case 8:
      one = increase;
      two = low;
      three = high;
      
      four = high;
      five = low;
      six = increase;
      break;
    case 9:
      one = decrease;
      two = low;
      three = high;
      
      four = high;
      five = low;
      six = decrease;
      break;
    case 10:
      one = low;
      two = low;
      three = high;
      
      four = high;
      five = low;
      six = increase;
      break;
    case 11:
      one = low;
      two = low;
      three = high;
      
      four = decrease;
      five = low;
      six = high;
      break;
    case 12:
      one = increase;
      two = low;
      three = high;
      
      four = low;
      five = increase;
      six = high;
      break;
    case 13:
      one = high;
      two = low;
      three = decrease;
      
      four = low;
      five = high;
      six = decrease;
      break;
    case 14:
      one = high;
      two = increase;
      three = low;
      
      four = increase;
      five = high;
      six = low;
      break;
    case 15:
      one = decrease;
      two = high;
      three = low;
      
      four = high;
      five = decrease;
      six = low;
      break;
    case 16:
      one = increase;
      two = high;
      three = low;
      
      four = high;
      five = low;
      six = low;
      break;
    case 17:
      one = high;
      two = decrease;
      three = low;
      
      four = high;
      five = low;
      six = low;
      break;
    default:
      one = 0.0;
      two = 0.0;
      three = 0.0;
      break;
    }

  long ione = one * 255.0; // set one
  long itwo = two * 255.0; // set two
  long ithree = three * 255.0; // set three
  long ifour = four * 255.0;
  long ifive = five * 255.0;
  long isix = six * 255.0;
  analogWrite(PIN_ONE, ione); // set red leve
  analogWrite(PIN_TWO, itwo); // set red leve
  analogWrite(PIN_THREE, ithree); // set red leve
  analogWrite(PIN_FOUR, ifour); // set red leve
  analogWrite(PIN_FIVE, ifive); // set red leve
  analogWrite(PIN_SIX, isix); // set red leve
  return (0);
}

You're mapping 24 bits of 3-source color onto a linear 10 bits. Smart move would be to use 3 pots at which point you could ditch needing the MCU altogether.

GoForSmoke:
You're mapping 24 bits of 3-source color onto a linear 10 bits. Smart move would be to use 3 pots at which point you could ditch needing the MCU altogether.

I'm sorry, I'm relatively new at electronics, can you elaborate that a little bit, how could i use 3 potentiometers to automatically change the color without a mcu and i didn't really understand ANY of the first part.. Sorry, I'm doing my best to learn this stuff, but theres a lot to learn

RGB led is either common cathode or common anode but regardless there is 1 pin for each color.
You =could= connect 1 pot to each color to vary the power going to each one.

The wiring is different depending on the RGB led type but the principle is the same.

And for one thing, you won't be stuck with steps as the pots are analog.

Wouldn't it be possible to put (1-brightness)*255 as pwm on the common cathode, assuming theat is what you've got. In that setup zero output to common cathode would mean 100% brightness. The pwm frequency to the cathode needs to be different than for the rgb pins.

JohnLincoln:
What you describe is bound to happen.
At the low intensities, each step changes by a large percentage, e.g 4 to 3 is a 25% change, 3 to 2 is a 33% change, 2 to 1 is a 50% change, but at the other end of the scale 255 to 244, 244 to 243, 243 to 242, etc each step is less than 0.5%.

You could get smaller changes by dithering between 2 values e.g switching backwards and forwards between 2 and 3 with equal times at each value is in effect 2.5, at 2 for 25% of the time then 3 for 75% is in effect 2.25 etc.

You would have to do this at a rate that is indistinguishable to the human eye.

so your suggestion would be to set it to alternate between the lower brightness levels to simulate a change, does the atmega process at a rate fast enough to perform those changes faster than is visible? im planning on making a bare bones arduino circuit which would consist of an atmega 328 with a 16mhz oscillator.

GoForSmoke:
RGB led is either common cathode or common anode but regardless there is 1 pin for each color.
You =could= connect 1 pot to each color to vary the power going to each one.

The wiring is different depending on the RGB led type but the principle is the same.

And for one thing, you won't be stuck with steps as the pots are analog.

well yes that would work, but the who point of this project is that its supposed to all happen automatically, its like a show (kind of) and unless i hooked the analog pots to servos (which would just be silly) then i don't see how it can work/: someone needs to invent a digital potentiometer that behaves like an analog one!

Then you're back to mapping 3 axes onto 1 line.

If your could rewire a 3-axis joystick then that 1 controller could drive all your colors.

Or you could use a 3D compass or 3 linear Hall sensors (1 for each axis, they read 1 axis only) and move a magnet around them....

Hey,

I just tested this code on my arduino with a single led connected to Pin 3 (+) and Pin 9 (-), Pin 9 also connected to ground via a 220 ohm resistor.

/*
  
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 9, 2);


const int buttonPin = 7;
const int potPin = A2;
const int ledPin = 3;
const int bledPin = 10;

int buttonState = 0;
int lastVal = 0;

int lastButtonState = 0;
long lastDebounceTime = 0;
long debounceDelay = 50;
int toggleState = 0;
int potDelay = 300;
long lastPotPrint = 0;
void setup() {
  // set up the number of columns and rows on the LCD 
  lcd.begin(16, 2);

  // set up the switch pin as an input
  pinMode(buttonPin,INPUT);

  // Print a message to the LCD.
  lcd.print("Hello");
  // set the cursor to column 0, line 1
  // line 1 is the second row, since counting begins with 0
  lcd.setCursor(0, 1);
  // print to the second line
  lcd.print("World!");
 setPwmFrequency(3,1); //[b]the PWM frequency on cathode is changed to be much faster than for anode [/b]. The function (below) is from arduino playground. 
}

void loop() {


  int reading = digitalRead(buttonPin);
  int val = analogRead(potPin);
  int ledVal = 1; //[b]this is the positive side of the LED, notice it has 1/255 duty cycle![/b]
  analogWrite(ledPin,ledVal);
  analogWrite(bledPin,map(val,0,1023,255,0));//[b]this is the cathode side of the led [/b]
  if (toggleState&&millis()>(lastPotPrint+potDelay)&&((val > lastVal+3) || lastVal>val+3 )){
    lcd.setCursor(0, 1);  
    lcd.print("      ");
    lcd.setCursor(0, 1);  
    lcd.print(val);
    lastVal=val;
    lastPotPrint=millis();
  }
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  } 
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
      if (buttonState)toggleState = !toggleState;
      lcd.setCursor(0, 0);
      lcd.print("     ");
      lcd.setCursor(0, 0);
      if (toggleState == HIGH) {
        lcd.print("On");
      }
      else
      {
        lcd.print("Off");
      }
    }
  }


  lastButtonState = reading;
}
void setPwmFrequency(int pin, int divisor) {
   byte mode;
   if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
     switch(divisor) {
       case 1: mode = 0x01; break;
       case 8: mode = 0x02; break;
       case 64: mode = 0x03; break;
       case 256: mode = 0x04; break;
       case 1024: mode = 0x05; break;
       default: return;
     }
     if(pin == 5 || pin == 6) {
       TCCR0B = TCCR0B & 0b11111000 | mode;
     } else {
       TCCR1B = TCCR1B & 0b11111000 | mode;
     }
   } else if(pin == 3 || pin == 11) {
     switch(divisor) {
       case 1: mode = 0x01; break;
       case 8: mode = 0x02; break;
       case 32: mode = 0x03; break;
       case 64: mode = 0x04; break;
       case 128: mode = 0x05; break;
       case 256: mode = 0x06; break;
       case 1024: mode = 0x7; break;
       default: return;
     }
     TCCR2B = TCCR2B & 0b11111000 | mode;
   }
}

It works for the full range of the pot, i.e. It gets extremely dim when pot is low and dim when pot is high. Transition is smooth.