Today, 03:23 AM
Code:
/*
Made by KinCony IoT: https://www.kincony.com
KC868-A128 - 128-channel light show for YouTube Shorts.
Effects:
1. Meteor chase with an 8-channel tail.
2. Two lights collide and move apart.
3. Four synchronized chase groups.
4. Fill and empty all 128 channels.
5. Odd/even alternation and a flashing finale.
Required library:
PCF8575 by Renzo Mischianti (xreef).
I2C SDA: GPIO5
I2C SCL: GPIO16
Outputs are active LOW.
Use the onboard LEDs or suitable demonstration lights.
The sequence includes stages with all outputs ON.
*/
#include <Arduino.h>
#include <Wire.h>
#include <PCF8575.h>
constexpr int I2C_SDA = 5;
constexpr int I2C_SCL = 16;
constexpr uint8_t CHIP_COUNT = 8;
constexpr uint8_t PINS_PER_CHIP = 16;
constexpr int CHANNEL_COUNT = 128;
// Increase this value to slow down every effect.
// Example: 150 means 1.5 times the original duration.
constexpr uint16_t SPEED_PERCENT = 100;
PCF8575 pcf1(0x24, I2C_SDA, I2C_SCL);
PCF8575 pcf2(0x25, I2C_SDA, I2C_SCL);
PCF8575 pcf3(0x21, I2C_SDA, I2C_SCL);
PCF8575 pcf4(0x22, I2C_SDA, I2C_SCL);
PCF8575 pcf5(0x26, I2C_SDA, I2C_SCL);
PCF8575 pcf6(0x27, I2C_SDA, I2C_SCL);
PCF8575 pcf7(0x20, I2C_SDA, I2C_SCL);
PCF8575 pcf8(0x23, I2C_SDA, I2C_SCL);
PCF8575* const outputs[] = {
&pcf1, &pcf2, &pcf3, &pcf4,
&pcf5, &pcf6, &pcf7, &pcf8
};
// Store current and requested states to avoid redundant writes.
bool currentState[CHANNEL_COUNT] = {};
bool frame[CHANNEL_COUNT] = {};
void pauseMs(uint32_t duration) {
delay(duration * SPEED_PERCENT / 100);
}
// Channel indexes are 0-127.
void setChannel(int channel, bool on) {
if (channel < 0 || channel >= CHANNEL_COUNT) {
return;
}
if (currentState[channel] == on) {
return;
}
outputs[channel / PINS_PER_CHIP]->digitalWrite(
channel % PINS_PER_CHIP,
on ? LOW : HIGH
);
currentState[channel] = on;
}
void setAll(bool on) {
for (int channel = 0; channel < CHANNEL_COUNT; channel++) {
setChannel(channel, on);
}
}
void clearFrame() {
for (int channel = 0; channel < CHANNEL_COUNT; channel++) {
frame[channel] = false;
}
}
void addLight(int channel) {
if (channel >= 0 && channel < CHANNEL_COUNT) {
frame[channel] = true;
}
}
void showFrame(uint32_t duration) {
for (int channel = 0; channel < CHANNEL_COUNT; channel++) {
setChannel(channel, frame[channel]);
}
pauseMs(duration);
}
// Move an 8-channel tail in both directions.
void meteorShow() {
constexpr int TAIL_LENGTH = 8;
for (int head = 0;
head < CHANNEL_COUNT + TAIL_LENGTH;
head++) {
clearFrame();
for (int tail = 0; tail < TAIL_LENGTH; tail++) {
addLight(head - tail);
}
showFrame(18);
}
for (int head = CHANNEL_COUNT - 1;
head >= -TAIL_LENGTH;
head--) {
clearFrame();
for (int tail = 0; tail < TAIL_LENGTH; tail++) {
addLight(head + tail);
}
showFrame(14);
}
}
// Move two 4-channel beams toward the center and back.
void collisionShow() {
for (int repeat = 0; repeat < 2; repeat++) {
for (int position = 0; position < 64; position++) {
clearFrame();
for (int tail = 0; tail < 4; tail++) {
addLight(position - tail);
addLight(CHANNEL_COUNT - 1 - position + tail);
}
showFrame(18);
}
for (int position = 63; position >= 0; position--) {
clearFrame();
for (int tail = 0; tail < 4; tail++) {
addLight(position - tail);
addLight(CHANNEL_COUNT - 1 - position + tail);
}
showFrame(14);
}
}
setAll(false);
}
// Run four independent 32-channel chase groups.
void fourGroupShow() {
constexpr int GROUP_SIZE = 32;
constexpr int TAIL_LENGTH = 4;
for (int repeat = 0; repeat < 3; repeat++) {
for (int step = 0; step < GROUP_SIZE; step++) {
clearFrame();
for (int group = 0; group < 4; group++) {
for (int tail = 0; tail < TAIL_LENGTH; tail++) {
int local = (step - tail + GROUP_SIZE) % GROUP_SIZE;
// Adjacent groups move in opposite directions.
if (group % 2 == 1) {
local = GROUP_SIZE - 1 - local;
}
addLight(group * GROUP_SIZE + local);
}
}
showFrame(35);
}
}
setAll(false);
}
// Gradually fill the board, then empty it in the same order.
void fillShow() {
setAll(false);
for (int channel = 0; channel < CHANNEL_COUNT; channel++) {
setChannel(channel, true);
pauseMs(12);
}
pauseMs(350);
for (int channel = 0; channel < CHANNEL_COUNT; channel++) {
setChannel(channel, false);
pauseMs(9);
}
pauseMs(150);
}
// Alternate physical channels 1/3/5... and 2/4/6...
void finaleShow() {
for (int step = 0; step < 8; step++) {
clearFrame();
for (int channel = 0; channel < CHANNEL_COUNT; channel++) {
if ((channel % 2) == (step % 2)) {
addLight(channel);
}
}
showFrame(120);
}
setAll(false);
pauseMs(150);
for (int repeat = 0; repeat < 2; repeat++) {
setAll(true);
pauseMs(180);
setAll(false);
pauseMs(180);
}
}
void setup() {
Serial.begin(115200);
// Configure every output HIGH before initializing the chips.
for (uint8_t chip = 0; chip < CHIP_COUNT; chip++) {
for (uint8_t pin = 0; pin < PINS_PER_CHIP; pin++) {
outputs[chip]->pinMode(pin, OUTPUT, HIGH);
}
}
for (uint8_t chip = 0; chip < CHIP_COUNT; chip++) {
if (!outputs[chip]->begin()) {
Serial.printf("PCF8575 group %u initialization failed.\n",
chip + 1);
while (true) {
delay(1000);
}
}
}
Serial.println("KC868-A128 light show ready.");
pauseMs(500);
}
void loop() {
meteorShow();
collisionShow();
fourGroupShow();
fillShow();
finaleShow();
setAll(false);
pauseMs(500);
}
LED.zip (Size: 1.95 KB / Downloads: 6)
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download:
LED.ino.merged.zip (Size: 186.24 KB / Downloads: 5)
YouTube: https://www.youtube.com/c/KinCony
Online Store: https://shop.kincony.com
Alibaba Store: https://kincony.en.alibaba.com/
Online Store: https://shop.kincony.com
Alibaba Store: https://kincony.en.alibaba.com/

