Multi-trigger til RC

Jeg har lavet en multi-trigger styring til en kanal på en RC bil.
Ved at aktivere kanal 3 en, to eller tre gange på senderen, kan audio modulet afspille lydfil 1, 2 eller 3.
Min sender havde kun en on/off funktion på kanal 3. Så jeg skilte den ad og indsatte en trykknap over omskifteren. Så kunne jeg hurtigt sende pulser.

Jeg har brugt en ESP32-C3 Supermini til at aflæse PWM fra RC receiveren. Den kommunikere med DFPlayer Mini modulet, som afspiller den rigtige lydfil.

Link til projektet på Cirkitdesigner.com
https://app.cirkitdesigner.com/project/d3ae5b3d-81b9-4ae4-90a4-b36e1a0adefb

Lydfilerne på SD kortet

Navngiv lydfilerne korrekt.
Dine lydfiler skal navngives i et specifikt format i roden på SD kortet:
0001.mp3
0002.mp3
0003.mp3
Hvis du har problemer med at programmet vælger de forkerte lydfiler, kan du evt. slette alle filer på kortet. Læg derefter en fil ind på kortet i den rigtige rækkefølge og med ovenstående filnavne.

SD-kortets format

  • Filformat (FAT16 eller FAT32): DFPlayer Mini understøtter kun SD-kort formateret som FAT16 eller FAT32. De fleste SD-kort er sandsynligvis allerede i et af disse formater, men det er godt at dobbelttjekke. Hvis du bruger et helt nyt kort, vil det ofte være FAT32. Hvis du oplever problemer, kan det hjælpe at formatere kortet på ny på din computer (brug Windows Stifinder eller et lignende værktøj, og vælg FAT32).

Forbindelser

  • RC_CHANNEL_PIN (Pin 2 på ESP32-C3): Dette er, hvor signalledningen fra din RC-modtager skal tilsluttes. Det er vigtigt, at dette er et digitalt input, da ESP32-C3 vil måle pulsbredden (PWM) på dette signal.
  • DFPLAYER_TX (Pin 0 på ESP32-C3): Dette er TX (transmit) pinnen for ESP32-C3. Den skal forbindes til RX (receive) pinnen på din DFPlayer Mini.
  • DFPLAYER_RX (Pin 1 på ESP32-C3): Dette er RX (receive) pinnen for ESP32-C3. Den skal forbindes til TX (transmit) pinnen på din DFPlayer Mini.
  • LED_PIN (Pin 8 på ESP32-C3): Dette er, hvor du har tilsluttet LED’en. Husk, at du sandsynligvis skal bruge en seriemodstand (typisk 220-470 ohm) mellem LED’en og pin 8 for at beskytte både LED’en og ESP32-C3.
  • DFPlayer strømforsyning: Sørg for at din DFPlayer Mini har den korrekte strømforsyning (typisk 3.3V til 5V, tjek databladet for din specifikke DFPlayer). Du kan muligvis bruge en af ESP32-C3’s strømforsyningspins (f.eks. 3.3V eller 5V), men vær forsigtig med strømbegrænsninger. Hvis din DFPlayer trækker meget strøm, kan det være nødvendigt med en separat strømforsyning.
  • Fælles jordforbindelse: Det er afgørende, at ESP32-C3 og DFPlayer Mini har en fælles jordforbindelse. Tilslut en jordledning fra en af ESP32-C3’s GND-pins til en af DFPlayer Mini’s GND-pins. Uden en fælles jordforbindelse vil seriel kommunikation sandsynligvis ikke fungere.
#include <HardwareSerial.h>

// Pin definitions for ESP32-C3 Super Mini
#define RC_CHANNEL_PIN 2
#define DFPLAYER_TX 0
#define DFPLAYER_RX 1
#define LED_PIN 8   // Onboard LED

// RC PWM constants
#define RC_MIN_PULSE 1000    // μs - minimum PWM pulse
#define RC_MAX_PULSE 2000    // μs - maximum PWM pulse
#define RC_THRESHOLD 1750    // μs - Threshold for RC channel activation

// Timing constants
#define DEBOUNCE_DELAY 50    // ms - Debounce delay for button presses
#define CLICK_INTERVAL 1000   // ms - Max time between two clicks in a multi-click sequence

// DFPlayer commands (for readability)
#define DFPLAYER_CMD_INITIALIZE 0x3F
#define DFPLAYER_CMD_SET_VOLUME 0x06
#define DFPLAYER_CMD_PLAY_TRACK 0x03

// Variables for click detection
int clickCount = 0;
unsigned long lastClickTime = 0;
unsigned long lastDebounceTime = 0;
bool lastChannelState = LOW; // Last raw reading of RC channel
bool channelState = LOW;     // Debounced state of RC channel (HIGH = activated/pressed)

// Variables for non-blocking LED blinking
bool isBlinking = false;
int targetBlinks = 0;       // Total number of blinks requested
int currentBlinkCycle = 0;  // Current blink cycle (1 for ON, 2 for OFF)
unsigned long lastBlinkStateChangeTime = 0; // Time of last LED state change
const unsigned int BLINK_DURATION = 100; // Duration of each LED ON/OFF state in ms

// HardwareSerial for DFPlayer (using Serial1)
HardwareSerial dfSerial(1);

// Function prototypes
void sendDFCommand(byte command, byte param1, byte param2);
void handleMultiClick();
void playTrack(int trackNumber);
void startLedBlinkSequence(int numBlinks);
void updateLedBlink();

void setup() {
  Serial.begin(115200); // Standard for ESP32-C3 is usually 115200
  dfSerial.begin(9600, SERIAL_8N1, DFPLAYER_RX, DFPLAYER_TX);
  
  pinMode(RC_CHANNEL_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  
  // LED test - blink 3 times on startup (blocking here is acceptable for startup)
  for(int i = 0; i < 3; i++) {
    digitalWrite(LED_PIN, HIGH);
    delay(200);
    digitalWrite(LED_PIN, LOW);
    delay(200);
  }
  
  delay(1000); // Wait for DFPlayer to be ready
  
  // Initialize DFPlayer
  sendDFCommand(DFPLAYER_CMD_INITIALIZE, 0x00, 0x00);
  delay(200);
  sendDFCommand(DFPLAYER_CMD_SET_VOLUME, 0x00, 0x1E); // Set volume to 30 (max)
  delay(200);
  
  Serial.println("ESP32-C3 Super Mini DFPlayer Controller ready");
  Serial.println("1 click = File 1, 2 clicks = File 2, 3 clicks = File 3");
}

void loop() {
  // Update LED blinking state non-blockingly
  updateLedBlink();

  // Read RC PWM signal
  unsigned long pulseWidth = pulseIn(RC_CHANNEL_PIN, HIGH, 25000); // 25ms timeout
  
  // Debug output every 2 seconds
  static unsigned long lastDebugTime = 0;
  if (millis() - lastDebugTime > 2000) {
    Serial.print("PWM reading: ");
    Serial.print(pulseWidth);
    Serial.println("μs");
    lastDebugTime = millis();
  }
  
  // Convert PWM to digital state
  // PWM: 1519μs, Reading: HIGH  (button pressed)
  // PWM: 2022μs, Reading: LOW   (button released)
  bool currentReading = (pulseWidth < RC_THRESHOLD && pulseWidth > 0);
  
  // Only set LED if not currently blinking to avoid conflicts
  if (!isBlinking) {
    digitalWrite(LED_PIN, currentReading ? HIGH : LOW);
  }
  
  // --- Button Debounce and Click Detection ---
  // If raw state has changed
  if (currentReading != lastChannelState) {
    lastDebounceTime = millis();
    Serial.print("Raw State change - PWM: ");
    Serial.print(pulseWidth);
    Serial.print("μs, Raw Reading: ");
    Serial.println(currentReading ? "HIGH" : "LOW");
  }

  // If state is stable for more than DEBOUNCE_DELAY
  if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
    // Only if the debounced state has changed
    if (currentReading != channelState) {
      channelState = currentReading; // Update the debounced state
      
      // Detect rising edge (button pressed)
      if (channelState == HIGH) {
        // If this click is the first OR it's past the CLICK_INTERVAL
        if (clickCount == 0 || (millis() - lastClickTime) > CLICK_INTERVAL) {
          clickCount = 1; // Start new sequence
        } else { 
          clickCount++; // Accumulate clicks
        }
        lastClickTime = millis(); // Update time for this click
        
        Serial.print("RC trigger #");
        Serial.print(clickCount);
        Serial.print(" (PWM: ");
        Serial.print(pulseWidth);
        Serial.println("μs)");

        // Start the non-blocking LED blink sequence
        startLedBlinkSequence(clickCount);
      }
    }
  }

  // --- Process Multi-click Sequence ---
  // If clicks have been registered and it's past the CLICK_INTERVAL since the last click
  // AND clickCount is greater than 0 (i.e., there is a sequence to process)
  if (clickCount > 0 && (millis() - lastClickTime) > CLICK_INTERVAL) {
    handleMultiClick(); // Call the function with the accumulated clickCount
    clickCount = 0;     // Reset clickCount after sequence is processed
  }
  
  lastChannelState = currentReading; // Update lastChannelState with the raw reading
}

// Function to manage non-blocking LED blinking
void updateLedBlink() {
  if (isBlinking) {
    unsigned long currentMillis = millis();
    if (currentMillis - lastBlinkStateChangeTime >= BLINK_DURATION) {
      lastBlinkStateChangeTime = currentMillis;
      
      // Toggle LED state
      digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Read current state and toggle
      
      // If LED just turned ON, increment current blink cycle (e.g., 1st blink, 2nd blink)
      if (digitalRead(LED_PIN) == HIGH) {
        currentBlinkCycle++;
        if (currentBlinkCycle > targetBlinks) { // If all blinks are done
          isBlinking = false; // Stop blinking
          digitalWrite(LED_PIN, LOW); // Ensure LED is off
        }
      }
    }
  }
}

// Function to initiate a LED blink sequence
void startLedBlinkSequence(int numBlinks) {
  isBlinking = true;
  targetBlinks = numBlinks;
  currentBlinkCycle = 0; // Reset for a new sequence
  lastBlinkStateChangeTime = millis();
  digitalWrite(LED_PIN, HIGH); // Start by turning LED ON
}

// Handles the action based on multi-click count
void handleMultiClick() {
  Serial.print("Playing file ");
  Serial.println(clickCount);
  
  switch (clickCount) {
    case 1:
      playTrack(1);
      break;
    case 2:
      playTrack(2);
      break;
    case 3:
      playTrack(3);
      break;
    default:
      Serial.println("Unknown click count, or clicks out of range.");
      break;
  }
}

// Plays a specific track on the DFPlayer
void playTrack(int trackNumber) {
  sendDFCommand(DFPLAYER_CMD_PLAY_TRACK, 0x00, trackNumber);
  Serial.print("Playing track: ");
  Serial.println(trackNumber);
}

// Sends a command to the DFPlayer Mini
void sendDFCommand(byte command, byte param1, byte param2) {
  // DFPlayer protocol: 7E FF 06 [CMD] [FEEDBACK] [PARAM1] [PARAM2] [CHECKSUM_HIGH] [CHECKSUM_LOW] EF
  byte buffer[10];
  buffer[0] = 0x7E;    // Start byte
  buffer[1] = 0xFF;    // Version
  buffer[2] = 0x06;    // Length
  buffer[3] = command; // Command
  buffer[4] = 0x00;    // Feedback (0 = no feedback)
  buffer[5] = param1;  // Parameter 1
  buffer[6] = param2;  // Parameter 2
  
  // Calculate checksum
  int checksum = -(buffer[1] + buffer[2] + buffer[3] + buffer[4] + buffer[5] + buffer[6]);
  buffer[7] = (checksum >> 8) & 0xFF; // Checksum high
  buffer[8] = checksum & 0xFF;        // Checksum low
  buffer[9] = 0xEF;                   // End byte
  
  // Send to DFPlayer
  for (int i = 0; i < 10; i++) {
    dfSerial.write(buffer[i]);
  }
}