A colour wheel puts red, green and blue around a circle with all the mixed colours in between. In this project the joystick becomes a pointer on that wheel: push it in any direction and the RGB LED shows the colour in that direction. The further you push, the stronger the colour. Click the stick to switch between bright and dim.
What you'll learn
- How to turn joystick X and Y into an angle and a distance
- How the colour wheel (hue) maps to red, green and blue
- How to mix colours with PWM
- How to use the joystick's built-in button
What you need
- UNO R3 board and USB cable (UNO R3 Starter Kit)
- Joystick module (45-in-1 Sensor Kit)
- RGB LED module (45-in-1 Sensor Kit)
- 3 resistors, 220 Ω (UNO R3 Starter Kit)
- Breadboard and jumper wires (UNO R3 Starter Kit)
Wiring

| From | Through | To |
|---|---|---|
| Joystick GND | GND | |
| Joystick +5V | 5V | |
| Joystick VRx | UNO pin A0 | |
| Joystick VRy | UNO pin A1 | |
| Joystick SW | UNO pin 2 | |
| UNO pin 9 | 220 Ω resistor | RGB module R |
| UNO pin 10 | 220 Ω resistor | RGB module G |
| UNO pin 11 | 220 Ω resistor | RGB module B |
| RGB module − | GND |
Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.
The sketch
const int X_PIN = A0;
const int Y_PIN = A1;
const int SW_PIN = 2;
const int RED_PIN = 9;
const int GREEN_PIN = 10;
const int BLUE_PIN = 11;
bool dim = false;
int lastButton = HIGH;
// Hue 0-359 on the colour wheel -> red, green and blue 0-255
void hueToRgb(int hue, int &r, int &g, int &b) {
int part = hue / 60; // which sixth of the wheel
int f = (hue % 60) * 255 / 60; // how far into that sixth
switch (part) {
case 0: r = 255; g = f; b = 0; break; // red -> yellow
case 1: r = 255 - f; g = 255; b = 0; break; // yellow -> green
case 2: r = 0; g = 255; b = f; break; // green -> cyan
case 3: r = 0; g = 255 - f; b = 255; break; // cyan -> blue
case 4: r = f; g = 0; b = 255; break; // blue -> magenta
default: r = 255; g = 0; b = 255 - f; break; // magenta -> red
}
}
void setup() {
pinMode(SW_PIN, INPUT_PULLUP);
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
}
void loop() {
int x = analogRead(X_PIN) - 512; // -512 ... +511, 0 in the middle
int y = analogRead(Y_PIN) - 512;
int button = digitalRead(SW_PIN);
if (button == LOW && lastButton == HIGH) dim = !dim; // click: bright <-> dim
lastButton = button;
float distance = sqrt((float)x * x + (float)y * y); // how far you push
float strength = constrain(distance / 500.0, 0.0, 1.0);
int hue = (int)(atan2(y, x) * 180.0 / PI + 360) % 360; // which direction
int r, g, b;
hueToRgb(hue, r, g, b);
float scale = strength * (dim ? 0.15 : 1.0);
if (distance < 60) scale = 0; // in the middle: off
analogWrite(RED_PIN, r * scale);
analogWrite(GREEN_PIN, g * scale);
analogWrite(BLUE_PIN, b * scale);
delay(20);
}
How it works
The joystick gives two numbers, X and Y, each about 512 when the stick is in the middle. Subtracting 512 puts the middle at 0, so the pair (x, y) is an arrow pointing where you push. atan2(y, x) turns that arrow into an angle, and the Pythagorean theorem gives its length, which is how far you push.
The angle becomes the hue, the position on the colour wheel. The wheel is split into six parts; in each part one colour channel rises or falls while the other two stay fixed, which is exactly how screens produce all the colours in between. The length sets the brightness, and the click toggles a dim mode for using the lamp in the dark.
Try this next
- Hold the colour when you let go of the stick, so you can pick a colour and keep it.
- Show the hue as a number from 0 to 359 in the Serial Monitor.
- Fade slowly through the whole colour wheel when nobody touches the joystick.
Troubleshooting
-
The colours go round the wrong way: swap the X and Y wires, or write
atan2(-y, x). - One colour never appears: check that channel's resistor and its wire to pin 9, 10 or 11.
-
The LED flickers in the middle position: raise the 60 in
distance < 60a little. - The click doesn't work: check SW on pin 2.