ESP32-S3 Lab · Day 16 of 30

Mix your own colour
with three knobs

Today the colour mixing moves into your hands. Three knobs each feed a reading to the board, and one line of arithmetic — map() — rescales each reading onto one colour's brightness, red, green, and blue on their own channels. Learn how that one line works and you hold the move that turns any sensor into any output for the rest of the course.

About 25 minutesArduino firstMicroPython optionalDay 13's reading meets Day 9's dome
Agent assist code TSK-DAY16-SOFTCOLOUR

Hand this to an agent so it can pull the lesson packet and coach you step by step.

01 First, know the pieces

What you need

Seven things, and every one of them has been aboard before. Tap Define on any part you haven't met — the answer opens as a field note you can read and dismiss without losing your place.

Official manual photo of the ESP32-S3 development board.
Manual photo

ESP32-S3 board

The brain that runs your uploaded sketch.

Official manual image of the ESP32-S3 GPIO extension board.
Manual photo

GPIO extension board

Spreads the pins into rows you can reach and label.

Official manual photo of a rotary potentiometer with its three legs visible.
Manual photo

3 × rotary potentiometers

Three knobs — one for each colour channel.

Official manual photo of an RGB LED showing its long common pin and three colour legs.
Manual photo

RGB LED

Three lights — red, green, blue — in one dome with a shared pin.

Official manual photo of a resistor with coloured value bands.
Manual photo

3 × 220 Ω resistors

One in series with each colour leg to keep the current gentle.

Official manual image of a jumper wire.
Manual photo

Jumper wires

Temporary, solder-free connections — thirteen of them today.

Official manual screenshot of the Arduino IDE interface.
Manual screenshot

Arduino IDE

Uploads the sketch to the board.

02 Make the physical circuit

Chart the circuit

The official Freenove diagram is your chart — schematic on top, the same circuit built on a breadboard below. Click it to enlarge. The pairing to keep in your head: the knob on GPIO 12 drives the red leg on GPIO 38, 13 drives green on 39, and 14 drives blue on 40.

Official Freenove circuit — C Tutorial, Chapter 10 (Potentiometer & LED), page 113.
Red knob middle pin GPIO 12 The board reads this knob to set how much red shows.
Green knob middle pin GPIO 13 The board reads this knob to set how much green shows.
Blue knob middle pin GPIO 14 The board reads this knob to set how much blue shows.
Each knob's outer legs 3.3V and GND The two ends give the middle pin a full sweep from zero to full voltage.
Red leg (via 220 Ω) GPIO 38 PWM on this pin sets the red brightness.
Green leg (via 220 Ω) GPIO 39 PWM on this pin sets the green brightness.
Blue leg (via 220 Ω) GPIO 40 PWM on this pin sets the blue brightness.
Common (long) pin 3.3V All three colours share this positive pin so a colour lights when its pin goes LOW.

The common pin goes to 3.3V. This is the same common-anode LED as Day 9 — the long common pin goes to 3.3V, the opposite of a single LED's ground leg. Each colour leg reaches its own GPIO through a 220 Ω resistor. Unplug USB before you move any wire.

03 One action at a time

Build it

This is the main path — you can finish the day without opening a single field note. Tap each step as you go to keep your place.

0 / 9 done
  1. Seat the ESP32-S3 on the GPIO extension board and keep USB unplugged while you wire.

  2. Place the three potentiometers on the breadboard with room to turn each knob.

  3. Wire each knob's outer legs to 3.3V and GND so its middle pin can sweep the full range.

  4. Run the middle pins to GPIO 12, 13, and 14 — red, green, and blue in that order.

  5. Wire the RGB LED's red leg through a 220 Ω resistor to GPIO 38, green to GPIO 39, blue to GPIO 40.

  6. Run the LED's long common pin to 3.3V.

  7. Compare every wire to the chart before you plug in USB.

  8. Open Sketch_10.2_SoftColorfulLight.ino in Arduino IDE and upload it.

  9. Turn each knob in turn and watch its colour rise and fall in the dome.

Your own mixing desk.

Three knobs now hold the colour between them. Head to Test & debug to confirm each channel answers its own knob.

04 Read just enough code

Read the code

Day 15 ran one chain — read a knob, write a brightness — and its two scales happened to match, so it needed no map() call. A colour channel runs 0 to 255 while the knob runs 0 to 4095, so today every pass leans on map() to rescale. Three small arrays and one loop run all three channels in a few lines. Switch to MicroPython if you'd rather see the same idea in Python — the wiring never changes.

Sketch_10.2_SoftColorfulLight.ino
const byte adcChns[] = {12, 13, 14};    // define the adc channels
const byte ledPins[] = {38, 39, 40};    // define led pins
const byte pwmChns[] = { 0,  1,  2};    // define the pwm channels
int colors[] = {0, 0, 0};               // red, green ,blue values of color.
void setup() {
  for (int i = 0; i < 3; i++) {         //setup the pwm channels
    ledcAttachChannel(ledPins[i], 1000, 8, pwmChns[i]);//1KHz, 8bit(0-255).
  }
}

void loop() {
  for (int i = 0; i < 3; i++) {
    colors[i] = map(analogRead(adcChns[i]), 0, 4096, 0, 255); //calculate color value.
    ledcWrite(ledPins[i], 256 - colors[i]);                   //set color
  }
  delay(10);
}
const byte adcChns[] = {12, 13, 14}Three matched arrays pair each knob with a colour leg — position 0 links the knob on 12 to the red leg on 38 and so on down the line.
colors[i] = map(analogRead(adcChns[i]), 0, 4096, 0, 255)Reads one knob and rescales it proportionally — where the reading sits between 0 and 4096 is where the colour value lands between 0 and 255.
ledcWrite(ledPins[i], 256 - colors[i])Day 9's common-anode inversion — this dome lights when its pin goes LOW so the sketch subtracts the value before writing it.
ledcAttachChannel(ledPins[i], 1000, 8, pwmChns[i])Gives each colour leg its own hardware PWM channel at 1 kHz with 8-bit brightness.

05 Understand, don't memorise

How map() turns a reading into a colour

A knob hands you a number from 0 to 4095. A colour channel wants a number from 0 to 255. map() is the one line that bridges the two, and it is the same move behind turning any sensor reading into any output. Day 13 gave you the reading, Day 9 gave you the channel, and map() is the adapter between them.

In

The reading

analogRead hands back a number from 0 to 4095 — where the knob sits between ground and 3.3V.

Fraction

How far along

map asks where that number sits in its input span as a fraction — halfway up the knob is halfway along 0 to 4096.

Out

Same spot, other scale

It places that same fraction on the output span, so halfway lands at about 128 out of 255.

Colour

Brightness

That 0-to-255 number is the PWM level for one colour leg, and the loop repeats it for red, green, and blue every pass.

The arithmetic out = outLow + (value − inLow) × (outHigh − outLow) ÷ (inHigh − inLow)

Match the input ends to the real reading

The input ends should be the smallest and largest numbers the sensor truly reaches. Claim a top of 4096 when the knob only reaches 3600 and the colour never quite hits full. Claim a top of 2048 when the knob reaches 4095 and everything past halfway pins at full and stops changing. The output uses its whole range only when the input ends are honest about the reading.

map() rescales, it does not clamp

map() is straight proportional arithmetic, so a reading outside the stated input range lands outside the output range too. When a value might stray past its ends, fence it with constrain() first — the pairing you met in Day 15's glossary.

Where 4096 and 255 come from

4096 is the top of the ADC's 12-bit scale and 255 is the top of an 8-bit PWM channel, so this map squeezes a 4096-step reading down onto a 256-step brightness. Change the chip or the settings and those numbers move, and map() still only needs the four ends.

06 Know it worked

Test & debug

Nothing prints to the screen today — the proof is the colour under your fingers.

What you should see
RGB LED
  • Turn one knob and one colour sweeps smoothly from dark to full while the other two hold steady.
  • Set the knobs in combinations and the dome mixes the shades — red and green up makes yellow.
  • All three knobs up glows white; all three down leaves the dome dark.

Every knob turned down means darkness by design — white takes all three channels at full.

If it doesn't
  • One colour never changes? Check that knob's middle wire on GPIO 12, 13, or 14 — the board is reading air.
  • Turning the red knob changes the wrong colour? The RGB legs are in the wrong order — red goes to GPIO 38, green to 39, blue to 40.
  • The dome stays dark with knobs up? Check the common pin — it goes to 3.3V.
  • Upload fails? Swap in a data-capable USB cable.

07 Make the idea yours

Try this: bend the mapping

Same working circuit, two one-line edits to the red channel — each one puts a different pair of map()'s numbers under your hands. Both fit inside today's 25 minutes.

Reverse one channel

On the map line, swap red's output ends — write map(analogRead(adcChns[0]), 0, 4096, 255, 0). Turning the red knob up now dims red instead of raising it. The output ends are simply the two numbers you choose, and swapping them runs the colour the other way.

Narrow the input

Put the ends back, then drop red's input top from 4096 to 2048 — map(analogRead(adcChns[0]), 0, 2048, 0, 255). Red now reaches full by the knob's halfway point and holds there across the whole top half. That dead travel is what a mismatched input range costs — set the ends to the numbers the knob really reaches.

08 Learn it with a hand on the tiller

Coach me through it

Every lesson ships with a code and a machine-readable packet, so an agent can guide you with full context.

Lesson code

TSK-DAY16-SOFTCOLOUR

How the agent should behave: guide one physical connection at a time, wait for confirmation, and teach map() as proportional arithmetic — where a reading sits in its input range is where it lands in the output range. Always check wiring, board, port, and USB before changing code.

Keep your place

Finished Day 16?

Mark it complete — it shows on your course map, and your place is saved on this device.

Field note

Shortcut

Prompt copied