Фоторезистор

Датчик света: чем ярче, тем меньше его сопротивление. Подключим к Arduino Nano, спрячем в коробку и будем закрывать её окошко ширмой.

Датчик в коробке, ширма на окошке

Фоторезистор — это резистор из полупроводника, который «проводит» тем лучше, чем больше на него падает света. У самого ходового GL5528 в темноте не меньше 1 МОм, в сумерках (10 лк) — 10–20 кОм, при комнатном свете (100–300 лк) — 2–5 кОм, на ярком солнце — сотни ом.

Фоторезистор стоит в коробке, свет попадает только через окошко. Тяните ширму мышкой (или пальцем) или двигайте ползунок — опущенная ширма закрывает окошко целиком, и внутри полная темнота.

солнце коробка окошко ширма фоторезистор
ширма поднята окошко закрыто

Закрытое окошко — это настоящая темнота при любом свете снаружи: остаётся только темновое сопротивление, 1 МОм, analogRead ≈ 10. Ползунок ниже — сколько света снаружи: от ночи до яркого солнца.

ночь солнце
Закройте окошко ширмой и включите — Arduino запомнит темноту в коробке (см. точку темноты).
освещённость в коробке
сопротивление фоторезистора
напряжение на A1
analogRead(A1)
level после калибровки, 0…255—

Как подключить к Arduino Nano

Сам по себе фоторезистор не выдаёт напряжение — он только меняет сопротивление. Arduino же умеет измерять напряжение. Поэтому к фоторезистору добавляют обычный постоянный резистор, и получается делитель напряжения — как потенциометр, только «ползунок» двигает свет.

+5 В фоторезистор резистор 10 кОм коричневый-чёрный-оранжевый GND Arduino Nano A1 тремоло в скетче (A1)

Почему 10 кОм: делитель чувствительнее всего, когда постоянный резистор примерно равен сопротивлению фоторезистора при «рабочем» освещении. Для комнаты подходит 10 кОм, для тёмного помещения — 47–100 кОм, для улицы — 1 кОм.

int light = analogRead(A1);    // 0 — темно, 1023 — очень светло
// в скетче sine_pm_am_crush_lfo2.ino:
amDepth = map(light, LIGHT_MIN, LIGHT_MAX, 0, 255);   // свет → глубина тремоло

Формулы на странице: U = 5 В × 10 кОм / (R + 10 кОм), сопротивление — по даташиту GL5528: R = 15 кОм × (лк / 10)−0.6, в полной темноте 1 МОм. Настоящий датчик может отличаться в 2–3 раза, поэтому LIGHT_MIN в скетче подбирают по факту — или Arduino находит его сам при включении, см. точку темноты.

Точка темноты: Arduino калибруется сам

// Фоторезистор с «точкой темноты»: при включении Arduino запоминает
// текущий свет как «полностью темно» и дальше считает свет от неё до солнца.
// Схема: +5V — фоторезистор — A1 — 10 кОм — GND. Звук на D9.
#include <Mozzi.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>

#define LDR_PIN      A1
#define LIGHT_SUN    1000   // analogRead на солнце (GL5528 + 10 кОм: около 975–1020)
#define DARK_MARGIN  8      // запас над точкой темноты, чтобы шум АЦП не «шуршал»
#define CAL_SAMPLES  64     // сколько замеров усреднить при калибровке (64 × 5 мс ≈ 0,3 с)

Oscil<SIN2048_NUM_CELLS, MOZZI_AUDIO_RATE> aSin(SIN2048_DATA);

int darkPoint;              // analogRead, который считаем «полностью темно»
int lightSm;                // сглаженный analogRead × 16
uint8_t level = 0;          // 0 (темно) … 255 (солнце)

// Усредняем несколько замеров — это и есть точка темноты.
// Вызывать ДО startMozzi(): обычный analogRead работает только до него.
int calibrateDark(uint8_t pin) {
  analogRead(pin);                       // первый замер после смены пина выбрасываем
  long sum = 0;
  for (int i = 0; i < CAL_SAMPLES; i++) {
    sum += analogRead(pin);
    delay(5);
  }
  int dark = sum / CAL_SAMPLES + DARK_MARGIN;
  return min(dark, LIGHT_SUN - 100);     // включили на солнце — оставим хоть какой-то диапазон
}

void setup() {
  darkPoint = calibrateDark(LDR_PIN);
  lightSm = darkPoint << 4;
  aSin.setFreq(220);
  startMozzi();
}

void updateControl() {
  int raw = mozziAnalogRead(LDR_PIN);
  lightSm += ((raw << 4) - lightSm) >> 3;        // сглаживание, как в скетче курса
  int light = lightSm >> 4;

  // стало темнее, чем при включении? сдвигаем точку темноты вниз
  if (light + DARK_MARGIN < darkPoint) darkPoint = light + DARK_MARGIN;

  light = constrain(light, darkPoint, LIGHT_SUN);
  level = map(light, darkPoint, LIGHT_SUN, 0, 255);
}

AudioOutput updateAudio() {
  return MonoOutput::from16Bit(aSin.next() * level);   // громкость = свет
}

void loop() {
  audioHook();
}