#include SPI.h #include MFRC522.h #include FastLED.h #include Arduino.h #include driverledc.h RFID引脚定义 #define RFID_RST_PIN 14 RC522 复位引脚 #define RFID_SS_PIN 10 RC522 片选引脚 #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输出引脚 #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; 全局变量 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%左右(102255≈40%) 单颗LED颜色数组 CRGB singleLedColors[8] = { CRGBBlack, 0 熄灭 CRGBBlue, 1 蓝色 CRGBGreen, 2 绿色 CRGBOrange, 3 橙色 CRGBRed, 4 红色 CRGBPurple, 5 紫色 CRGBYellow, 6 黄色 CRGBWhite 7 白色 }; RFID读取任务 void TaskRFIDcode(void pvParameters) { for (;;) { 寻找新卡片 if (!rfid.PICC_IsNewCardPresent()) { delay(10); continue; } 验证NUID是否可读 if (!rfid.PICC_ReadCardSerial()) { delay(10); continue; } 读取卡片数据(用户数据区) String cardData = ; MFRC522MIFARE_Key key; 准备认证密钥 for (byte i = 0; i 6; i++) key.keyByte[i] = 0xFF; 选择卡片 MFRC522StatusCode status; status = rfid.PCD_Authenticate(MFRC522PICC_CMD_MF_AUTH_KEY_A, 4, &key, &(rfid.uid)); if (status != MFRC522STATUS_OK) { Serial.print(F(Authentication failed )); Serial.println(rfid.GetStatusCodeName(status)); rfid.PICC_HaltA(); rfid.PCD_StopCrypto1(); delay(100); continue; } 读取数据块 byte buffer[18]; byte size = sizeof(buffer); status = rfid.MIFARE_Read(4, buffer, &size); if (status != MFRC522STATUS_OK) { Serial.print(F(Reading failed )); Serial.println(rfid.GetStatusCodeName(status)); } else { 转换为ASCII字符串 for (byte i = 0; i 16; i++) { if (buffer[i] = 32 && buffer[i] = 126) { 可打印ASCII字符 cardData += (char)buffer[i]; } } 移除空白字符 cardData.trim(); 卡片数据处理 if (cardData != lastCardData && !cardData.isEmpty()) { lastCardData = cardData; Serial.println(SORC_ + cardData); } } 使放置在读卡区的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; static bool lastState = HIGH; for (;;) { bool currentState = digitalRead(BTN0_PIN); 检测下降沿(按下) if (lastState == HIGH && currentState == LOW) { pressStartTime = millis(); btn0State = LOW; Serial.println(SO_BT0_HIGH); btn0LongPress = false; } 检测上升沿(释放) else if (lastState == LOW && currentState == HIGH) { btn0State = HIGH; Serial.println(SO_BT0_LOW); btn0LongPress = false; } 检测长按 else if (currentState == LOW && millis() - pressStartTime = 2000 && !btn0LongPress) { btn0LongPress = true; Serial.println(SO_BT0_HIGHL); } lastState = currentState; delay(10); } } WAKEUP1检测任务 void TaskWAKEUP1code(void pvParameters) { static bool lastState = LOW; for (;;) { bool currentState = digitalRead(WAKEUP1_PIN); 检测上升沿 if (lastState == LOW && currentState == HIGH) { wakeup1State = HIGH; Serial.println(SO_WAKEUP1); } 检测下降沿 else if (lastState == HIGH && currentState == LOW) { wakeup1State = LOW; Serial.println(SO_WAKEUP0); } lastState = currentState; delay(10); } } 按钮1检测任务 void TaskBTN1code(void pvParameters) { static bool lastState = LOW; for (;;) { bool currentState = digitalRead(BTN1_PIN); 检测上升沿 if (lastState == LOW && currentState == HIGH) { btn1State = HIGH; Serial.println(SO_BT1_HIGH); } 检测下降沿 else if (lastState == HIGH && currentState == LOW) { btn1State = LOW; Serial.println(SO_BT1_LOW); } lastState = currentState; delay(10); } } 按钮2检测任务 void TaskBTN2code(void pvParameters) { static bool lastState = LOW; for (;;) { bool currentState = digitalRead(BTN2_PIN); 检测上升沿 if (lastState == LOW && currentState == HIGH) { btn2State = HIGH; Serial.println(SO_BT2_HIGH); } 检测下降沿 else if (lastState == HIGH && currentState == LOW) { btn2State = LOW; Serial.println(SO_BT2_LOW); } lastState = currentState; delay(10); } } 串口命令处理 void handleSerialCommand() { static String command = ; while (Serial.available()) { 先检查命令长度(新增的防护代码) if (command.length() 64) { Serial.println(错误 命令过长(最大64字符)); command = ; 清空当前命令 while (Serial.available()) Serial.read(); 清空串口缓冲区 continue; } char c = Serial.read(); if (c == 'n') { 处理命令 if (command.startsWith(MO_LED_)) { String modeStr = command.substring(7); int newMode = modeStr.toInt(); 控制单颗LED if (newMode = 0 && newMode = 7) { singleLedMode = newMode; Serial.print(Single LED set to mode ); Serial.println(newMode); } else { Serial.println(Invalid single LED mode command); } } else if (command.startsWith(MO_LEDN_)) { String modeStr = command.substring(8); int newMode = modeStr.toInt(); 控制灯带(只有亮度不为0时才允许) if (newMode = 0 && newMode = 5) { if (led2Brightness == 0) { Serial.println(当前亮度为0,请先将亮度调整至0以上再切换显示模式!); } else { ledMode = newMode; 重置火车灯状态 if (newMode == 4) { trainPos = -TRAIN_LENGTH; trainPhase = 0; rainbowHue = random8(); } 新增:切换到模式5时,复制当前LED2状态和亮度 if (newMode == 5) { memcpy(frozenLeds2, leds2, sizeof(leds2)); frozenBrightness = led2Brightness; 保存冻结时的亮度 } Serial.print(LED strip set to mode ); Serial.println(newMode); } } else { Serial.println(Invalid LED strip mode command); } } else if (command.startsWith(MO_PWM_)) { String dutyStr = command.substring(7); int newDuty = dutyStr.toInt(); 检查PWM百分比 if (newDuty == 1) { pwmDuty = 1023; 100% } else if (newDuty == 0 newDuty == 20 newDuty == 40 newDuty == 60 newDuty == 80) { pwmDuty = (newDuty 1023) 100; 转换为实际占空比 } else { Serial.println(Invalid PWM command); } Serial.print(PWM set to ); Serial.print((pwmDuty 100) 1023); Serial.println(%); } else if (command.startsWith(MO_BRI_)) { 提取亮度参数(跳过MO_BRI_前缀) String levelStr = command.substring(7); levelStr.trim(); command = ; 清空命令缓冲区 空参数检查 if (levelStr.length() == 0) { Serial.println(错误 缺少亮度值); return; 终止处理 } 严格数字验证(拒绝非数字字符) bool isNumeric = true; for (char c levelStr) { if (!isdigit(c)) { isNumeric = false; break; 发现非数字立即退出 } } 非数字错误处理 if (!isNumeric) { Serial.println(错误 亮度值必须为整数); return; } 转换为整数并验证范围 int level = levelStr.toInt(); if (level = 0 && level = 100) { 更新亮度值(映射到PWM范围) led2Brightness = brightnessMap[level]; 使用预定义映射表 Serial.print(LED亮度 ); Serial.print(level); Serial.println(%); 亮度为0时输出警告 if (level == 0) { Serial.println(亮度已设置为0,所有灯光将熄灭!); } } else { Serial.println(错误 亮度值需在0-100之间); } command = ; 清空命令 } command = ; 清空命令 } else { 累积非换行符字符 command += c; } } } 统一LED控制任务(防闪烁优化版本) 集中管理LED1(单颗)、LED2(灯带)、LED3(强制关闭)的显示逻辑 优化特性: 1. 30FPS稳定更新频率,防止闪烁 2. 修复模式5的双重亮度衰减问题 3. 统一亮度处理机制 4. 内存优化,减少不必要的数据拷贝 5. 防闪烁机制,确保LED显示稳定 void TaskLEDUnifiedCode(void pvParameters) { static unsigned long lastLEDUpdate = 0; const unsigned long LED_UPDATE_INTERVAL = 33; ~30FPS,降低更新频率减少闪烁 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] = CRGBBlue; } ---- LED3 控制(熄灭)---- leds3[0] = CRGBBlack; ---- LED2 控制(灯带)---- switch (ledMode) { case 0 模式0:全部熄灭,关闭所有LED灯珠 fill_solid(leds2, LED_COUNT_2, CRGBBlack); 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, CRGBBlack); 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, CRGBBlack); 亮度为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() { 初始化串口 Serial.begin(115200); Serial.println(System starting...); 初始化SPI总线 SPI.begin(RFID_SCK_PIN, RFID_MISO_PIN, RFID_MOSI_PIN, RFID_SS_PIN); 初始化RFID rfid.PCD_Init(); Serial.println(RFID initialized.); 初始化LED FastLED.addLedsWS2812, LED_PIN_1, GRB(leds1, LED_COUNT_1); FastLED.addLedsWS2812, LED_PIN_2, GRB(leds2, LED_COUNT_2); FastLED.addLedsWS2812, LED_PIN_3, GRB(leds3, LED_COUNT_3); 新增LED3 初始化LED状态 fill_solid(leds1, LED_COUNT_1, singleLedColors[singleLedMode]); fill_solid(leds2, LED_COUNT_2, CHSV(0, 0, led2Brightness)); 初始化白色 fill_solid(leds3, LED_COUNT_3, CRGBBlack); 强制GPIO48的灯珠熄灭 FastLED.show(); Serial.println(LED initialized.); 初始化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.); 创建任务 xTaskCreatePinnedToCore( TaskRFIDcode, 任务函数 TaskRFID, 任务名称 4096, 任务栈大小 NULL, 传递给任务的参数 1, 任务优先级 &TaskRFID, 任务句柄 1); 运行在核心1上 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() { 处理串口命令 handleSerialCommand(); 让出CPU时间 delay(1); }