Fade. Плавное разгорание и затухание светодиода. (analogWrite, PWM/ШИМ)

Пример плавного разгорания и затухания двух светодиодных лент. Можно использовать например для подсветки рабочей зоны на кухне. Запуск повышения яркости происходит по датчику движения, сначала загорается одна лента, потом другая. Через несколько секунд без движения ленты плавно гаснут в обратном порядке.
Понадобится:

  • Arduino Nano/UNO
  • датчик движения HC-SR501
  • резистор 10 килоом
  • БП на 12 вольт (выбирается исходя из потребляемой мощности светодиодной ленты + запас 50%)
  • преобразователь 12 V → 5 V LM2596
  • два драйвера светодиодной ленты на полевом транзисторе (выбирается исходя из длины/потребления светодиодной ленты)
  • две светодиодные ленты на 12 вольт.
Спойлер
/*
 Name:		KitchHomeL.ino
 Created:	25.11.2022 11:23:44
 Author:	Andrey
*/

static constexpr unsigned char pin_RIP_in = 4; // Nano D5 // пин 4 подключения датчика движения HC-SR501
static constexpr unsigned char pin_PWM_1out = 9; // Nano PB1 OC1A // пин 9 подключения драйвера светодиодной ленты 1
static constexpr unsigned char pin_PWM_2out = 11; // Nano PB3 OC2A // пин 11 подключения драйвера светодиодной ленты 2

static constexpr unsigned long stepChange = 10; // период изменения яркости, миллисекунд, фактически скорость повышения/понижения яркости, чем меньше число - тем быстрее
static constexpr unsigned long periodWait = 10000; // время ожидания движения при максимальной яркости лент, миллисекунд

enum tStatusWork { lStart = 0 /*начало*/, lToUP /*повышение яркости*/, 
	lToDOWN /*понижение яркости*/, lWaitMove /*ожидание движения при макс.яркости*/ }; // режимы работы

void setPWMout(unsigned char out1 /*яркость ленты 1*/, unsigned char out2 /*яркость ленты 2*/) { // функция установки яркости лент
	analogWrite(pin_PWM_1out, out1); // установца ШИМ ленты 1
	analogWrite(pin_PWM_2out, out2); // установца ШИМ ленты 2
}

void initPins(void) { // начальная инициализации пина датчика движения
	pinMode(pin_RIP_in, INPUT); // режим пина - вход (по умолчанию, при включении МК, все пины и так в режиме входа)
	digitalWrite(pin_RIP_in, LOW); // отключаем подтягивающий резистор (по умолчанию он и так отключен)
}

unsigned char readRIP(void) { // чтение пина датчика движения
	static unsigned char lastLevel = 0; // хранение предыдущего значения пина - нет движения
	unsigned char currLevel = digitalRead(pin_RIP_in); // читаем текущее состояние пина
	if (currLevel != lastLevel) { // если прочитанное состояние пина не равно предыдущему
		Serial.println(currLevel); // выведем в монитор порта (для отладки) текущее значение пина
		digitalWrite(LED_BUILTIN, currLevel); // установим встроенный светодиод МК в состояние пина датчика движенния (удобно при отладке на реальном устройстве - видно работает датчик движения или нет)
		lastLevel = currLevel; // присваиваем предыдущему значению пина текущее
	}
	return currLevel; // функция возвращает текущее значение пина датчика движения HC-SR501
}

// the setup function runs once when you press reset or power the board
void setup() { // стартовая функция при включении или сбросе МК
	Serial.begin(115200); // инициализируем HW UART МК
	pinMode(LED_BUILTIN, OUTPUT); // установка режима пина встроенного МК - выход
	initPins(); // инициализируем пин датчика движения
	setPWMout(0, 0); // гасим обе ленты
}

void workKitchenL(void) { // основная рабочая функция
	static tStatusWork currentMode = lStart; // храним текущее значение режима работы
	static unsigned char pwm1 = 0, pwm2 = 0; // текущая яркость лент
	static unsigned long timerLastChange = 0; // таймер изменения яркости
	static unsigned long timerWaitMove = 0; // таймер ожидания движения при максимальной яркости
	switch (currentMode) { // работаем с логикой в зависимости от текущего режима
		case lToUP: { // режим повышение ярккости
			if ((millis() - timerLastChange) >= stepChange) { // если прошел шаг изменения яркости
				timerLastChange = millis(); // обновим таймер изменения яркости
				if (pwm1 < 255) { // если яркость первой ленты меньше максимальной
					++pwm1; // увеличиваем яркость первой ленты
				} else { // если яркость первой ленты максимальная - начинаем увеличивать яркость второй ленты
					if (pwm2 < 255) { // если яркость второй ленты меньше максимальной
						++pwm2; // увеличиваем яркость второй ленты
					} else { // если яркость второй ленты максимальная
						timerWaitMove = millis(); // обновляем таймер ожидания движения при максимальной яркости
						currentMode = lWaitMove; // переходим в режим ожидания движения при максимальной яркости
					}
				}
				setPWMout(pwm1, pwm2); // устанавливаем яркость лент
			}
			break;
		}
		case lToDOWN: { // режим понижения аркости
			if (readRIP()) { // если есть движенние
				currentMode = lToUP; // переходим в режим повышения аркости
			} else { // если нет движения
				if ((millis() - timerLastChange) >= stepChange) { // если прошел шаг изменения яркости
					timerLastChange = millis(); // обновим таймер изменения яркости
					if (pwm2) { // если яркость второй ленты больше нуля
						--pwm2; // уменьшаем яркость второй ленты
					} else { // если вторя лента не светится
						if (pwm1) { // если яркость первой ленты больше нуля
							--pwm1; // уменьшаем яркость первой ленты
						} else { // если первая лента не светится
							currentMode = lStart; // переходим в начальный режим
						}
					}
					setPWMout(pwm1, pwm2); // устанавливаем яркость лент
				}
			}
			break;
		}
		case lWaitMove: { // режим ожидания движения при максимальной яркости лент
			if (readRIP()) { // если есть движение
				timerWaitMove = millis(); // обновляем таймер ожидания
			} else { // если нет движения
				if ((millis() - timerWaitMove) >= periodWait) currentMode = lToDOWN; // если вышло время ожидания движения - переходим в режим уменьшения яркости лент
			}
			break;
		}
		default: { // lStart // начальный режим
			if (readRIP()) { // если произошло движение
				currentMode = lToUP; // переходим в режим повышения яркости лент
			}
		}
	}
}

// the loop function runs over and over again until power down or reset
void loop() { // бесконечный цикл
	workKitchenL(); // работаем
}