#include #include #include #include #include // RFID引脚定义 #define RFID_RST_PIN 14 // RC522 复位引脚 #define RFID_SS_PIN 10 // RC522 CS/SDA引脚(SPI和I2C共用) #define RFID_MISO_PIN 13 // MISO 引脚 #define RFID_MOSI_PIN 12 // MOSI 引脚 #define RFID_SCK_PIN 11 // SCK 引脚 // LED定义 #define LED_PIN_1 4 // 1颗WS2812灯珠引脚 #define LED_PIN_2 5 // 160颗WS2812灯带引脚(控制灯珠颜色) #define LED_PIN_3 48 // 1颗WS2812灯珠引脚(新增) #define LED_COUNT_1 1 // 1颗灯珠 #define LED_COUNT_2 186 // 160颗灯带 #define LED_COUNT_3 1 // 1颗灯珠(新增) // PWM定义 #define PWM_PIN 6 // PWM输出(控制输出PWM功率) #define PWM_CHANNEL 0 // PWM通道 #define PWM_FREQ 1000 // PWM频率(Hz) #define PWM_RESOLUTION 10 // PWM分辨率(位) #define DEFAULT_DUTY 819 // 默认占空比(80%) // 按钮和输入引脚定义 #define BTN0_PIN 15 // 按钮0引脚 #define WAKEUP1_PIN 16 // 唤醒引脚1 #define BTN1_PIN 17 // 按钮1引脚 #define BTN2_PIN 18 // 按钮2引脚 // 任务句柄 TaskHandle_t TaskRFID, TaskLED1, TaskLED2, TaskLED3, TaskPWM, TaskBTN0, TaskWAKEUP1, TaskBTN1, TaskBTN2; // 双串口架构: // - Serial = USB-Serial-JTAG (USB2 口),连 Windows 做调试日志 // - SerialLinux = UART0 (CH343/USB1 口),连 Android 开发板收发业务数据 // UART0 默认引脚:TX=GPIO43、RX=GPIO44(对应 CH343P 的 RXD/TXD) // // ⚠️ 重要:必须用 #define 别名引用 Arduino core 自带的 Serial0 对象 // 不能自己创建 HardwareSerial(0),否则 Arduino core 的 UART0 RX 中断会把 // 数据放进 Serial0 的 ring buffer,而自建对象的 available() 读不到 → 命令无响应 #define SerialLinux Serial0 // Serial 输出互斥锁:防止多任务并发写串口导致数据交错/截断 SemaphoreHandle_t serialMutex = NULL; // 业务数据输出:只发到 UART0 (给 Android 开发板) // 调用方:RFID 任务发 SORC_xxx,按键任务发 SO_xxx // 为什么不发 Serial (USB CDC): // 1. 生产场景只连 Android,Windows 不连,Serial 写入+flush 在无 host 时可能阻塞 // 2. 阻塞会拖慢 RFID 响应(之前刷卡不灵敏的潜在原因之一) // 3. UART0 是广播式通讯,无 host 也能正常写入 FIFO,不阻塞 // 互斥锁保留:虽然只发一个串口,但防止多任务并发调用时缓冲混乱 void serialPrintlnSafe(const String& msg) { if (serialMutex && xSemaphoreTake(serialMutex, pdMS_TO_TICKS(100)) == pdTRUE) { SerialLinux.println(msg); SerialLinux.flush(); // UART0 硬件 FIFO 128 字节,115200 波特率约 1ms 完成 xSemaphoreGive(serialMutex); } else { // 降级路径:拿不到锁(极少发生) SerialLinux.println(msg); } } // 全局变量 MFRC522 rfid(RFID_SS_PIN, RFID_RST_PIN); // 创建RFID实例 CRGB leds1[LED_COUNT_1]; // 1颗灯珠数组 CRGB leds2[LED_COUNT_2]; // 160颗灯带数组 CRGB leds3[LED_COUNT_3]; // 1颗灯珠数组(新增) CRGB frozenLeds2[LED_COUNT_2]; // 保存冻结时的颜色数据(模式5专用) uint8_t frozenBrightness = 255; // 保存冻结时的亮度值,用于计算相对亮度比例 String lastCardData = ""; // 上次读取的RFID卡数据 int ledMode = 1; // 灯带模式,默认为1(白色) int pwmDuty = DEFAULT_DUTY; // PWM占空比 bool btn0State = HIGH; // 按钮0状态 bool btn0LongPress = false; // 按钮0长按标志 bool wakeup1State = LOW; // 唤醒引脚1状态 bool btn1State = LOW; // 按钮1状态 bool btn2State = LOW; // 按钮2状态 int singleLedMode = 7; // 单颗LED模式,默认为7(白色) // 灯带动画全局变量 static uint8_t rainbowHue = 0; static int trainPos = 0; static unsigned long lastUpdate = 0; static const int TRAIN_LENGTH = 16; // 火车灯长度 static int trainPhase = 0; // 火车阶段:0-正向出站,1-正向前进,2-正向进站,3-反向出站,4-反向前进,5-反向进站 static const int VIRTUAL_LED_COUNT = LED_COUNT_2 + TRAIN_LENGTH; // 虚拟灯带长度 // LED亮度线性映射表 (0~100 → 26~255) - 最小阈值10% // 用于将用户输入的0-100%亮度值映射到实际的PWM值 // 避免过低亮度导致LED完全不可见的问题 const uint8_t brightnessMapLinear[101] = { 0, 28, 31, 33, 36, 38, 41, 43, 46, 48, // 0-9 51, 54, 56, 59, 61, 64, 66, 69, 71, 74, // 10-19 77, 79, 82, 84, 87, 89, 92, 94, 97, 99, // 20-29 102, 105, 107, 110, 112, 115, 117, 120, 122, 125, // 30-39 128, 130, 133, 135, 138, 140, 143, 145, 148, 150, // 40-49 153, 156, 158, 161, 163, 166, 168, 171, 173, 176, // 50-59 179, 181, 184, 186, 189, 191, 194, 196, 199, 201, // 60-69 204, 207, 209, 212, 214, 217, 219, 222, 224, 227, // 70-79 230, 232, 235, 237, 240, 242, 245, 247, 250, 252, // 80-89 253, 254, 254, 254, 255, 255, 255, 255, 255, 255, // 90-99 255 // 100% }; // 全局选择映射表 const uint8_t* brightnessMap = brightnessMapLinear; // LED2亮度控制(0-255) // 用于控制LED灯带的整体亮度,影响模式1、2、4和5 // 模式3使用独立的呼吸算法,基于此值计算动态亮度范围 uint8_t led2Brightness = 102; // 默认40%左右(102/255≈40%) // 单颗LED颜色数组 CRGB singleLedColors[8] = { CRGB::Black, // 0: 熄灭 CRGB::Blue, // 1: 蓝色 CRGB::Green, // 2: 绿色 CRGB::Orange, // 3: 橙色 CRGB::Red, // 4: 红色 CRGB::Purple, // 5: 紫色 CRGB::Yellow, // 6: 黄色 CRGB::White // 7: 白色 }; // RC522 硬件复位(运行时调用,不依赖 MFRC522 库的 RST 自动判断) // 用于 setup 初始化和运行时检测到 RC522 异常时恢复 void rc522HardResetRuntime() { pinMode(RFID_RST_PIN, OUTPUT); digitalWrite(RFID_RST_PIN, LOW); delay(10); digitalWrite(RFID_RST_PIN, HIGH); delay(50); } // RFID读取任务 void TaskRFIDcode(void* pvParameters) { // 运行时健康检查:每 5 秒读一次 VersionReg,检测到异常自动恢复 // 防止运行过程中 RC522 因电源波动、WS2812 大电流干扰等原因进入异常状态 static uint32_t lastHealthCheck = 0; const uint32_t HEALTH_CHECK_INTERVAL_MS = 5000; for (;;) { // 运行时健康检查(和下面的轮询并行) uint32_t now = millis(); if (now - lastHealthCheck > HEALTH_CHECK_INTERVAL_MS) { lastHealthCheck = now; byte version = rfid.PCD_ReadRegister(MFRC522::VersionReg); if (version != 0x91 && version != 0x92) { // 通讯异常,自动恢复 Serial.print("RC522 health check failed (VersionReg=0x"); Serial.print(version, HEX); Serial.println("), reinitializing..."); rc522HardResetRuntime(); rfid.PCD_Init(); } } // 寻找新卡片 if (!rfid.PICC_IsNewCardPresent()) { delay(10); continue; } // 验证NUID是否可读 if (!rfid.PICC_ReadCardSerial()) { delay(10); continue; } // 读取卡片数据(用户数据区) String cardData = ""; MFRC522::MIFARE_Key key; // 准备认证密钥 for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF; // 选择卡片 MFRC522::StatusCode status; status = rfid.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 4, &key, &(rfid.uid)); if (status != MFRC522::STATUS_OK) { // 调试日志用普通 Serial.println:失败频率较高时,避免 flush 阻塞拖慢 RFID 响应 // 偶尔截断可接受(Linux 端用正则 ^SORC_HA\d+$ 过滤业务数据即可) Serial.println(String("Authentication failed: ") + rfid.GetStatusCodeName(status)); rfid.PICC_HaltA(); rfid.PCD_StopCrypto1(); delay(30); // 从 100ms 降到 30ms,提升刷卡响应速度 continue; } // 读取数据块 byte buffer[18]; byte size = sizeof(buffer); status = rfid.MIFARE_Read(4, buffer, &size); if (status != MFRC522::STATUS_OK) { Serial.println(String("Reading failed: ") + rfid.GetStatusCodeName(status)); rfid.PICC_HaltA(); rfid.PCD_StopCrypto1(); delay(30); // 从 100ms 降到 30ms continue; } // 转换为ASCII字符串 for (byte i = 0; i < 16; i++) { if (buffer[i] >= 32 && buffer[i] <= 126) { // 可打印ASCII字符 cardData += (char)buffer[i]; } } // 移除空白字符 cardData.trim(); // 卡片数据格式校验:规则 "HA" + 阿拉伯数字 // 过滤掉卡片读取异常或数据损坏的情况,避免发送无效数据给 Linux auto isValidCardData = [](const String& d) -> bool { if (d.length() < 3) return false; // 至少 "HA" + 1 位数字 if (!d.startsWith("HA")) return false; // 必须以 HA 开头 for (size_t i = 2; i < d.length(); i++) { if (!isdigit(d[i])) return false; // HA 后面必须全是数字 } return true; }; // 3 秒去重窗口:同一张卡 3 秒内只发送一次,超过后允许重发 // 切换到不同卡立即发送 static String lastSentCard = ""; static unsigned long lastSentTime = 0; const unsigned long DUPLICATE_WINDOW_MS = 3000; if (!cardData.isEmpty() && isValidCardData(cardData)) { unsigned long now = millis(); bool isDuplicate = (cardData == lastSentCard) && (now - lastSentTime < DUPLICATE_WINDOW_MS); if (!isDuplicate) { serialPrintlnSafe("SORC_" + cardData); lastSentCard = cardData; lastSentTime = now; } } // 使放置在读卡区的IC卡进入休眠状态,不再重复读卡 rfid.PICC_HaltA(); // 停止加密PCD rfid.PCD_StopCrypto1(); delay(100); } } // 注意:以下两个函数已被TaskLEDUnifiedCode替代,保留仅供参考 // 实际运行中不会被调用,因为setup()中没有创建对应的任务 // LED1控制任务(已废弃,由TaskLEDUnifiedCode统一处理) void TaskLED1code(void* pvParameters) { // 此函数已被废弃,不再使用 // LED1的控制已集成到TaskLEDUnifiedCode中 vTaskDelete(NULL); // 如果意外创建了此任务,立即删除 } // LED3控制任务(已废弃,由TaskLEDUnifiedCode统一处理) void TaskLED3code(void* pvParameters) { // 此函数已被废弃,不再使用 // LED3的控制已集成到TaskLEDUnifiedCode中 vTaskDelete(NULL); // 如果意外创建了此任务,立即删除 } // PWM控制任务 void TaskPWMcode(void* pvParameters) { for (;;) { // 设置PWM占空比 ledc_set_duty(LEDC_LOW_SPEED_MODE, (ledc_channel_t)PWM_CHANNEL, pwmDuty); ledc_update_duty(LEDC_LOW_SPEED_MODE, (ledc_channel_t)PWM_CHANNEL); delay(100); } } // 按钮0检测任务 void TaskBTN0code(void* pvParameters) { static unsigned long pressStartTime = 0; // 首次进入任务时读取实际电平作为初始值,避免上电时 GPIO 浮空触发虚假边沿事件 bool lastState = digitalRead(BTN0_PIN); btn0State = lastState; for (;;) { bool currentState = digitalRead(BTN0_PIN); // 检测下降沿(按下) if (lastState == HIGH && currentState == LOW) { pressStartTime = millis(); btn0State = LOW; serialPrintlnSafe("SO_BT0_HIGH"); btn0LongPress = false; } // 检测上升沿(释放) else if (lastState == LOW && currentState == HIGH) { btn0State = HIGH; serialPrintlnSafe("SO_BT0_LOW"); btn0LongPress = false; } // 检测长按 else if (currentState == LOW && millis() - pressStartTime >= 2000 && !btn0LongPress) { btn0LongPress = true; serialPrintlnSafe("SO_BT0_HIGHL"); } lastState = currentState; delay(10); } } // WAKEUP1检测任务 void TaskWAKEUP1code(void* pvParameters) { // 首次进入任务时读取实际电平作为初始值,避免上电时 GPIO 浮空触发虚假边沿事件 bool lastState = digitalRead(WAKEUP1_PIN); wakeup1State = lastState; for (;;) { bool currentState = digitalRead(WAKEUP1_PIN); // 检测上升沿 if (lastState == LOW && currentState == HIGH) { wakeup1State = HIGH; serialPrintlnSafe("SO_WAKEUP1"); } // 检测下降沿 else if (lastState == HIGH && currentState == LOW) { wakeup1State = LOW; serialPrintlnSafe("SO_WAKEUP0"); } lastState = currentState; delay(10); } } // 按钮1检测任务 void TaskBTN1code(void* pvParameters) { // 首次进入任务时读取实际电平作为初始值,避免上电时 GPIO 浮空触发虚假边沿事件 bool lastState = digitalRead(BTN1_PIN); btn1State = lastState; for (;;) { bool currentState = digitalRead(BTN1_PIN); // 检测上升沿 if (lastState == LOW && currentState == HIGH) { btn1State = HIGH; serialPrintlnSafe("SO_BT1_HIGH"); } // 检测下降沿 else if (lastState == HIGH && currentState == LOW) { btn1State = LOW; serialPrintlnSafe("SO_BT1_LOW"); } lastState = currentState; delay(10); } } // 按钮2检测任务 void TaskBTN2code(void* pvParameters) { // 首次进入任务时读取实际电平作为初始值,避免上电时 GPIO 浮空触发虚假边沿事件 bool lastState = digitalRead(BTN2_PIN); btn2State = lastState; for (;;) { bool currentState = digitalRead(BTN2_PIN); // 检测上升沿 if (lastState == LOW && currentState == HIGH) { btn2State = HIGH; serialPrintlnSafe("SO_BT2_HIGH"); } // 检测下降沿 else if (lastState == HIGH && currentState == LOW) { btn2State = LOW; serialPrintlnSafe("SO_BT2_LOW"); } lastState = currentState; delay(10); } } // 处理单条完整命令 // 响应统一发到 Serial (USB CDC / Windows 调试口),不回发给 SerialLinux (Android) // 原因:Android 高频发灯光命令(音乐律动)时,若每条命令都回响应到 UART0 TX, // 会堵塞业务数据 SORC_xxx / SO_BTx 的发送,导致刷卡后 Android 收到延迟甚至丢失 // 参数 src 仅用于保留调用兼容性,响应不再写入 src void processCommand(const String& command, Stream& /*src*/) { Stream& resp = Serial; // 所有命令响应只发 Windows 调试口 if (command.startsWith("MO_LED_")) { String modeStr = command.substring(7); int newMode = modeStr.toInt(); if (newMode >= 0 && newMode <= 7) { singleLedMode = newMode; resp.print("Single LED set to mode: "); resp.println(newMode); } else { resp.println("Invalid single LED mode command"); } } else if (command.startsWith("MO_LEDN_")) { String modeStr = command.substring(8); int newMode = modeStr.toInt(); if (newMode >= 0 && newMode <= 5) { if (led2Brightness == 0) { resp.println("当前亮度为0,请先将亮度调整至0以上再切换显示模式!"); } else { ledMode = newMode; if (newMode == 4) { trainPos = -TRAIN_LENGTH; trainPhase = 0; rainbowHue = random8(); } if (newMode == 5) { memcpy(frozenLeds2, leds2, sizeof(leds2)); frozenBrightness = led2Brightness; } resp.print("LED strip set to mode: "); resp.println(newMode); } } else { resp.println("Invalid LED strip mode command"); } } else if (command.startsWith("MO_PWM_")) { String dutyStr = command.substring(7); int newDuty = dutyStr.toInt(); if (newDuty == 1) { pwmDuty = 1023; } else if (newDuty == 0 || newDuty == 20 || newDuty == 40 || newDuty == 60 || newDuty == 80) { pwmDuty = (newDuty * 1023) / 100; } else { resp.println("Invalid PWM command"); } resp.print("PWM set to: "); resp.print((pwmDuty * 100) / 1023); resp.println("%"); } else if (command.startsWith("MO_BRI_")) { String levelStr = command.substring(7); levelStr.trim(); if (levelStr.length() == 0) { resp.println("错误: 缺少亮度值"); return; } bool isNumeric = true; for (char c : levelStr) { if (!isdigit(c)) { isNumeric = false; break; } } if (!isNumeric) { resp.println("错误: 亮度值必须为整数"); return; } int level = levelStr.toInt(); if (level >= 0 && level <= 100) { led2Brightness = brightnessMap[level]; resp.print("LED亮度: "); resp.print(level); resp.println("%"); } else { resp.println("错误: 亮度值需在0-100之间"); } } else if (command == "RESET") { // 软复位:响应同时发到两个串口(Windows 和 Android 都能看到重启日志) Serial.println("System resetting..."); SerialLinux.println("System resetting..."); Serial.flush(); SerialLinux.flush(); delay(100); ESP.restart(); } } // 从指定流读取命令字节,累积到完整一行后交给 processCommand 处理 // 每个流需要独立的命令缓冲区(通过引用传入 static 变量保持状态) void handleCommandFromStream(Stream& src, String& cmdBuf) { while (src.available()) { // 命令长度保护:防止恶意或异常数据撑爆内存 if (cmdBuf.length() > 64) { src.println("错误: 命令过长(最大64字符)"); cmdBuf = ""; while (src.available()) src.read(); return; } char c = src.read(); if (c == '\n') { cmdBuf.trim(); // 去掉末尾 \r 兼容不同行尾符 if (cmdBuf.length() > 0) { processCommand(cmdBuf, src); } cmdBuf = ""; } else { cmdBuf += c; } } } // 统一LED控制任务(防闪烁优化版本) // 集中管理LED1(单颗)、LED2(灯带)、LED3(强制关闭)的显示逻辑 // 优化特性: // 1. 30FPS稳定更新频率,防止闪烁 // 2. 修复模式5的双重亮度衰减问题 // 3. 统一亮度处理机制 // 4. 内存优化,减少不必要的数据拷贝 // 5. 防闪烁机制,确保LED显示稳定 void TaskLEDUnifiedCode(void* pvParameters) { static unsigned long lastLEDUpdate = 0; // 20FPS 更新频率:兼顾视觉流畅度和 RFID 稳定性 // 每秒 20 次 WS2812 传输(每次 ~5.6ms 关中断),相比 30FPS 减少 33% 干扰窗口 const unsigned long LED_UPDATE_INTERVAL = 50; for (;;) { unsigned long currentTime = millis(); // 控制更新频率,避免过度占用CPU和闪烁问题 if (currentTime - lastLEDUpdate < LED_UPDATE_INTERVAL) { delay(5); // 增加延时,确保任务调度稳定 continue; } lastLEDUpdate = currentTime; // ---- LED1 控制(单颗 LED)---- if (singleLedMode >= 0 && singleLedMode <= 7) { leds1[0] = singleLedColors[singleLedMode]; } else { leds1[0] = CRGB::Blue; } // ---- LED3 控制(熄灭)---- leds3[0] = CRGB::Black; // ---- LED2 控制(灯带)---- switch (ledMode) { case 0: // 模式0:全部熄灭,关闭所有LED灯珠 fill_solid(leds2, LED_COUNT_2, CRGB::Black); break; case 1: // 模式1:纯白色静态光,亮度可通过led2Brightness调节 fill_solid(leds2, LED_COUNT_2, CHSV(0, 0, led2Brightness)); break; case 2: // 模式2:彩虹流水灯,颜色沿灯带流动,速度和亮度可调 for (int i = 0; i < LED_COUNT_2; i++) { leds2[i] = CHSV(rainbowHue + i * 256 / LED_COUNT_2, 255, led2Brightness); } rainbowHue++; break; case 3: // 模式3:彩虹呼吸灯(优化版本),缓慢变色配合呼吸效果 { static unsigned long lastHueUpdate = 0; static unsigned long lastBreathUpdate = 0; static uint8_t breathingHue = 0; static uint8_t breathPhase = 0; unsigned long currentTime = millis(); // 每300ms更新一次色相,实现非常缓慢的颜色变化 if (currentTime - lastHueUpdate > 300) { breathingHue += 1; lastHueUpdate = currentTime; } // 每30ms更新一次呼吸相位,控制亮度变化节奏 if (currentTime - lastBreathUpdate > 30) { breathPhase += 2; lastBreathUpdate = currentTime; } // 计算呼吸亮度:基于led2Brightness的60%-100%范围,避免过暗 uint8_t minBrightness = led2Brightness * 60 / 100; uint8_t maxBrightness = led2Brightness; uint8_t breathValue = map(sin8(breathPhase), 0, 255, minBrightness, maxBrightness); for(int i = 0; i < LED_COUNT_2; i++) { leds2[i] = CHSV(breathingHue, 200, breathValue); } } break; case 4: // 模式4:彩虹火车灯,模拟火车往返运行的动态效果 if (millis() - lastUpdate > 30) { // 30ms更新间隔,控制火车移动速度 lastUpdate = millis(); fill_solid(leds2, LED_COUNT_2, CRGB::Black); switch (trainPhase) { case 0: // 阶段0:正向出站,火车从起点逐渐显现 for (int i = 0; i < TRAIN_LENGTH; i++) { int pos = trainPos + i; if (pos >= 0 && pos < LED_COUNT_2) { uint8_t hue = rainbowHue + (i * 256 / TRAIN_LENGTH); leds2[pos] = CHSV(hue, 255, led2Brightness); } } trainPos++; if (trainPos >= 0) { trainPhase = 1; // 切换到正向前进阶段 trainPos = 0; } break; case 1: // 阶段1:正向前进,火车完整显示并向终点移动 for (int i = 0; i < TRAIN_LENGTH; i++) { int pos = trainPos + i; if (pos >= 0 && pos < LED_COUNT_2) { uint8_t hue = rainbowHue + (i * 256 / TRAIN_LENGTH); leds2[pos] = CHSV(hue, 255, led2Brightness); } } trainPos++; if (trainPos >= LED_COUNT_2 - TRAIN_LENGTH) { trainPhase = 2; // 切换到正向进站阶段 trainPos = LED_COUNT_2 - TRAIN_LENGTH; } break; case 2: // 阶段2:正向进站,火车从尾部开始消失 for (int i = 0; i < TRAIN_LENGTH; i++) { int displayPos = LED_COUNT_2 - 1 - i; if (displayPos >= trainPos) { uint8_t hue = rainbowHue + (i * 256 / TRAIN_LENGTH); leds2[displayPos] = CHSV(hue, 255, led2Brightness); } } trainPos++; if (trainPos >= LED_COUNT_2) { trainPhase = 3; // 切换到反向出站阶段 trainPos = 0; rainbowHue += 64; // 改变彩虹颜色,增加视觉变化 } break; case 3: // 阶段3:反向出站,火车从终点逐渐显现 for (int i = 0; i < trainPos + 1; i++) { int pos = LED_COUNT_2 - 1 - i; if (pos >= 0) { uint8_t hue = rainbowHue + ((TRAIN_LENGTH - 1 - i) * 256 / TRAIN_LENGTH); leds2[pos] = CHSV(hue, 255, led2Brightness); } } trainPos++; if (trainPos >= TRAIN_LENGTH) { trainPhase = 4; // 切换到反向前进阶段 trainPos = TRAIN_LENGTH; } break; case 4: // 阶段4:反向前进,火车完整显示并向起点移动 for (int i = 0; i < TRAIN_LENGTH; i++) { int pos = LED_COUNT_2 - trainPos + i; if (pos >= 0 && pos < LED_COUNT_2) { uint8_t hue = rainbowHue + ((TRAIN_LENGTH - 1 - i) * 256 / TRAIN_LENGTH); leds2[pos] = CHSV(hue, 255, led2Brightness); } } trainPos++; if (trainPos >= LED_COUNT_2) { trainPhase = 5; // 切换到反向进站阶段 trainPos = 0; } break; case 5: // 阶段5:反向进站,火车从头部开始消失 for (int i = 0; i < TRAIN_LENGTH - trainPos; i++) { int pos = i; if (pos < LED_COUNT_2) { uint8_t hue = rainbowHue + ((TRAIN_LENGTH - 1 - i) * 256 / TRAIN_LENGTH); leds2[pos] = CHSV(hue, 255, led2Brightness); } } trainPos++; if (trainPos >= TRAIN_LENGTH) { trainPhase = 0; // 重新开始正向出站,形成循环 trainPos = -TRAIN_LENGTH; rainbowHue += 64; // 再次改变彩虹颜色 } break; } } break; case 5: // 模式5:冻结当前灯效,保持切换时的图像但允许调节亮度(内存优化版本) if (led2Brightness == 0) { fill_solid(leds2, LED_COUNT_2, CRGB::Black); // 亮度为0时完全熄灭 } else { // 计算相对亮度比例,避免双重衰减问题 uint16_t brightnessRatio = (uint16_t)led2Brightness * 255 / frozenBrightness; if (brightnessRatio > 255) brightnessRatio = 255; // 直接计算并设置像素颜色,内存优化,避免使用memcpy for (int i = 0; i < LED_COUNT_2; i++) { leds2[i].r = (frozenLeds2[i].r * brightnessRatio) >> 8; leds2[i].g = (frozenLeds2[i].g * brightnessRatio) >> 8; leds2[i].b = (frozenLeds2[i].b * brightnessRatio) >> 8; } } break; } // ---- 最终统一刷新LED ---- // 添加FastLED刷新保护,确保数据稳定后再显示 FastLED.show(); // ---- 稳定的延时机制 ---- // 使用固定延时确保LED显示稳定,避免闪烁 delay(10); // 10ms延时,确保LED数据传输完成 } } void setup() { // 初始化 USB CDC 串口(Windows 调试) // 增大 TX 缓冲区到 4KB:防止多任务并发写串口时 USB CDC 默认缓冲区溢出导致数据截断 Serial.setTxBufferSize(4096); Serial.begin(115200); // 初始化 UART0(CH343/USB1 → Linux 业务通讯) // 默认引脚:TX=GPIO43、RX=GPIO44,波特率与 Linux 端保持一致 SerialLinux.begin(115200); // 创建 Serial 输出互斥锁 serialMutex = xSemaphoreCreateMutex(); Serial.println("System starting..."); SerialLinux.println("System starting..."); // 初始化SPI总线 SPI.begin(RFID_SCK_PIN, RFID_MISO_PIN, RFID_MOSI_PIN, RFID_SS_PIN); // 初始化RFID:显式硬件复位 + 版本校验 + 失败重试 // 背景:MFRC522 库的 PCD_Init 自动判断 RST 电平,冷启动时 GPIO14 浮空可能读到 HIGH, // 库只做软件复位但 RC522 尚未完成上电 → 芯片卡在异常状态 → 刷卡永远失败。 // 显式拉低再拉高 RST 可避免这个坑。GitHub miguelbalboa/rfid #229 #269 记录此问题。 // 复用顶部定义的 rc522HardResetRuntime() 函数 // 最多重试 3 次初始化,直到版本寄存器返回合法值 bool rfidReady = false; for (uint8_t attempt = 1; attempt <= 3; attempt++) { rc522HardResetRuntime(); // 显式拉低再拉高 RST rfid.PCD_Init(); byte version = rfid.PCD_ReadRegister(MFRC522::VersionReg); Serial.print("RC522 init attempt "); Serial.print(attempt); Serial.print(", VersionReg=0x"); Serial.println(version, HEX); // 0x91=v1.0, 0x92=v2.0 为合法;0x00/0xFF 表示通讯异常 if (version == 0x91 || version == 0x92) { rfidReady = true; break; } delay(100); // 重试前等待 } if (rfidReady) { Serial.println("RFID initialized."); } else { Serial.println("RFID initialization FAILED after 3 attempts! Check wiring/power."); } // 初始化LED FastLED.addLeds(leds1, LED_COUNT_1); FastLED.addLeds(leds2, LED_COUNT_2); FastLED.addLeds(leds3, LED_COUNT_3); // 新增LED3 // 启动时先全黑,避免 186 颗 LED 同时点亮产生瞬时 4.5A 大电流 // 冲击 3.3V/5V 电源导致刚初始化好的 RC522 进入异常状态 // TaskLEDUnified 启动后会根据 ledMode/led2Brightness 自动恢复默认显示 fill_solid(leds1, LED_COUNT_1, CRGB::Black); fill_solid(leds2, LED_COUNT_2, CRGB::Black); fill_solid(leds3, LED_COUNT_3, CRGB::Black); FastLED.show(); Serial.println("LED initialized (dark startup, task will restore default)."); // 初始化PWM // 创建LED控制器配置 ledc_timer_config_t ledc_timer = { .speed_mode = LEDC_LOW_SPEED_MODE, .duty_resolution = (ledc_timer_bit_t)PWM_RESOLUTION, .timer_num = (ledc_timer_t)PWM_CHANNEL, .freq_hz = PWM_FREQ, .clk_cfg = LEDC_AUTO_CLK }; ledc_timer_config(&ledc_timer); // 创建LED通道配置 ledc_channel_config_t ledc_channel = { .gpio_num = PWM_PIN, .speed_mode = LEDC_LOW_SPEED_MODE, .channel = (ledc_channel_t)PWM_CHANNEL, .intr_type = LEDC_INTR_DISABLE, .timer_sel = (ledc_timer_t)PWM_CHANNEL, .duty = 0, .hpoint = 0 }; ledc_channel_config(&ledc_channel); // 设置初始占空比 ledc_set_duty(LEDC_LOW_SPEED_MODE, (ledc_channel_t)PWM_CHANNEL, pwmDuty); ledc_update_duty(LEDC_LOW_SPEED_MODE, (ledc_channel_t)PWM_CHANNEL); Serial.println("PWM initialized."); // 初始化输入引脚 pinMode(BTN0_PIN, INPUT_PULLUP); pinMode(WAKEUP1_PIN, INPUT); pinMode(BTN1_PIN, INPUT); pinMode(BTN2_PIN, INPUT); Serial.println("Inputs initialized."); // 创建任务 // TaskRFID 放 Core 0:避开 Core 1 上 WS2812 bit-banging 关中断窗口,SPI 通讯更稳定 xTaskCreatePinnedToCore( TaskRFIDcode, /* 任务函数 */ "TaskRFID", /* 任务名称 */ 4096, /* 任务栈大小 */ NULL, /* 传递给任务的参数 */ 2, /* 任务优先级(提高到 2,避免被按键任务频繁抢占)*/ &TaskRFID, /* 任务句柄 */ 0); /* 运行在核心0上(与 LED 任务物理隔离)*/ xTaskCreatePinnedToCore( TaskLEDUnifiedCode, "TaskLEDUnified", 8192, // 建议栈大一点 NULL, 3, // 提高优先级,确保LED更新不被其他任务干扰 NULL, 1); xTaskCreatePinnedToCore( TaskPWMcode, "TaskPWM", 1024, NULL, 1, &TaskPWM, 1); xTaskCreatePinnedToCore( TaskBTN0code, "TaskBTN0", 2048, NULL, 1, &TaskBTN0, 0); xTaskCreatePinnedToCore( TaskWAKEUP1code, "TaskWAKEUP1", 2048, NULL, 1, &TaskWAKEUP1, 0); xTaskCreatePinnedToCore( TaskBTN1code, "TaskBTN1", 2048, NULL, 1, &TaskBTN1, 0); xTaskCreatePinnedToCore( TaskBTN2code, "TaskBTN2", 2048, NULL, 1, &TaskBTN2, 0); Serial.println("Tasks created. System ready."); } void loop() { // 同时处理两个串口的命令输入(双端都能下发控制命令) // - Serial (USB-Serial-JTAG):Windows 调试发命令 // - SerialLinux (UART0 / CH343):Android 开发板发命令 // 两个缓冲区独立保存,避免一方半发命令被另一方打断 static String cmdFromSerial = ""; static String cmdFromLinux = ""; handleCommandFromStream(Serial, cmdFromSerial); handleCommandFromStream(SerialLinux, cmdFromLinux); // 让出CPU时间 delay(1); }