ambient light sensor/Using it/09. A night light
Using it · 09 of 10

A night light

A TK01 XL LED that comes on when the room gets dark. The sketch calibrates itself at start-up: it reads the room lit, then the block covered, and puts two lines between them, on below 40 % of the way up and off above 60 %. The gap between them is what stops it flickering at dusk.

Calibrate, do not guess

A threshold copied from someone else's sketch is wrong in your room. Their lamp is a different kind, their sensor is up to 30 % more or less sensitive than yours, and their board may read in different units. The one reliable way to choose a line is to read the two conditions you care about, with the block you have, where it will live.

So the sketch starts by measuring. It reads the room lit, asks you to cover the sensor with a finger, and reads that too. The finger stands in for night. Both readings use the 50 ms average from the flicker under room lights, so a rippling lamp cannot skew them.

Two lines, not one

A night light
Switch at
Reading
235
TK01
off
Switches
0
The sketch has read the room lit (240) and covered (1). Press run and the evening comes.

Leave it on One line, halfway and let it get dark. As the evening fades the reading does not fall smoothly: a shadow crosses it, a car passes, a little ripple is left over. Near the line every wobble crosses it one way and then the other, and the light flickers on and off before it settles.

Pick Two lines. The LED now comes on below 40 % of the way from dark to lit and goes off only above 60 %. A wobble smaller than that gap cannot undo the switch, and the light changes once. That gap is called hysteresis.

Wiring the LED

The TK01 XL LED needs two wires, as in its own book: GND to GND, and SIGNAL to D9 on an Uno, GPIO 4 on an ESP32, GPIO 5 on an ESP32-S3, GP15 on a Pico. They are the pins the NTC thermistor's alarm uses. On the ESP32-S3, GPIO 4 is the light sensor, which is why the LED is on GPIO 5.

Point the LED away from the sensor. This is the one mistake the gap cannot fix: the LED comes on, its light reaches the sensor, the room reads as bright, and it turns itself off. If its light adds more than the gap, it flashes on and off for ever.

Running it

Open the serial monitor at 115200 before the board starts, or press reset with it open. Leave the lights on for the first three seconds; cover the clear part with a fingertip when it asks; uncover it when it says done. It prints the two lines it chose, then a reading every quarter of a second or so. Cover the sensor again and the LED comes on; uncover it and it goes off.

The code

The 50 ms average from the flicker article, a calibration in setup, and a TK01 XL LED that turns on below the lower line and off only above the upper one. Follow the prompts in the serial monitor while it starts.

light_night_light.ino
/*
  Ambient Light Sensor - a night light                   TK20 / /p/tk20

  Wiring, the TK20. Count from the square pad, parts up, header at
  the bottom:

    GND    -> GND
    VCC    -> 5V on an Uno; 3V3 on an ESP32, ESP32-S3 or Pico
    NC     -> nothing   (unconnected on the board)
    SIGNAL -> A0 on an Uno, GPIO 34 on an ESP32, GPIO 4 on an
              ESP32-S3, GP26 on a Raspberry Pi Pico

  The TK01 XL LED, counted the same way. Point it away from the TK20:

    GND    -> GND
    NC     -> nothing   (both of its NC pins)
    SIGNAL -> D9 on an Uno, GPIO 4 on an ESP32, GPIO 5 on an
              ESP32-S3, GP15 on a Raspberry Pi Pico

  Arduino IDE
    Tools > Board                 your board, e.g. Arduino Uno
    Tools > Port                  the one that appears when you plug in
    Tools > USB CDC On Boot       Enabled   (ESP32-S3 only)
    No library needed.
*/

// The analog pin SIGNAL is wired to.
// Uno: A0. ESP32: 34. ESP32-S3: 4. Pico: 26.
const int LIGHT_PIN = A0;
// The TK01's SIGNAL. Uno: 9. ESP32: 4. ESP32-S3: 5. Pico: 15.
const int LED_PIN = 9;

// Uno and Pico only: the ADC's full scale, in mV.
// Uno: 5000. Pico: 3300.
const float FULL_SCALE_MV = 5000.0;
// 5 cycles of a 100 Hz ripple, 6 of a 120 Hz one.
const unsigned long WINDOW_MS = 50;

const float ON_FRACTION = 0.4;    // on below 40 % of dark-to-lit
const float OFF_FRACTION = 0.6;   // off only above 60 %

float onBelowMv;
float offAboveMv;
bool lampOn = false;

float readMilliVolts() {
#if defined(ARDUINO_ARCH_ESP32)
  return analogReadMilliVolts(LIGHT_PIN);   // calibrated in the chip
#else
  return analogRead(LIGHT_PIN) * FULL_SCALE_MV / 1023.0;
#endif
}

float readSteadyMilliVolts() {
  float sum = 0;
  long n = 0;
  unsigned long start = millis();
  while (millis() - start < WINDOW_MS) {
    sum += readMilliVolts();
    n++;
  }
  return sum / n;
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  delay(1000);

  Serial.println("Calibrating. Leave the room lit...");
  delay(3000);
  float lit = readSteadyMilliVolts();
  Serial.println("Now cover the sensor with a finger...");
  delay(4000);
  float dark = readSteadyMilliVolts();
  Serial.println("Done. Uncover it.");

  if (lit - dark < 20) {
    Serial.println("Lit and dark are too close: press reset.");
  }
  onBelowMv = dark + ON_FRACTION * (lit - dark);
  offAboveMv = dark + OFF_FRACTION * (lit - dark);

  Serial.print("On below ");
  Serial.print(onBelowMv, 0);
  Serial.print(" mV, off above ");
  Serial.print(offAboveMv, 0);
  Serial.println(" mV");
}

void loop() {
  float mv = readSteadyMilliVolts();

  if (!lampOn && mv < onBelowMv) lampOn = true;
  if (lampOn && mv > offAboveMv) lampOn = false;
  digitalWrite(LED_PIN, lampOn ? HIGH : LOW);

  Serial.print(mv, 0);
  Serial.println(lampOn ? " mV  on" : " mV");
  delay(200);
}

Calibration runs every time the board starts, so the lines fit the room and the sensor you have. ON_FRACTION and OFF_FRACTION set the gap; make them equal to see the flicker the figure shows. Keep the TK01's light off the sensor, or it will switch itself off.

When it does not work

It turns on, then off, then on again, over and over.

The TK01's own light is reaching the sensor. It comes on, lights the sensor, reads as bright, and turns itself off. Point the LED away from the sensor, or put something opaque between them. Hysteresis cannot fix this if the LED adds more light than the gap between the two lines.

It says lit and dark are too close together.

The two calibration readings were nearly the same: the room was already dark, or the hand did not cover the sensor. Press reset and try again with the lights on for the first reading and a finger pressed over the clear part for the second.

It comes on too early, or too late.

Move the lines. A smaller ON_FRACTION, such as 0.2, waits until it is darker; a larger one, such as 0.5, comes on earlier. Keep OFF_FRACTION about 0.2 above it so the gap stays. On an ESP32, a room under about 43 lux reads the same as covered, so the light cannot tell dusk from night below that.

How do I stop it waking me with a remote or a car's headlights?

Short bright bursts turn it off only while they last, because it only needs to climb above the off line once. Add a rule that the reading must stay above the off line for a few seconds before the LED goes off; millis() can time it.

Where this goes next

Six symptoms, and where to look first for each.

When the reading is wrong

Edit this page — content/books/ambient-light-sensor/a-night-light.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

Ambient Light Sensor

Loading discussions…

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum