#include <Arduino.h>
#include <Adafruit_TinyUSB.h> 

// USB HID report descriptor
// Specifies the structure of the gamepad data (for radio receiver 
// (16bit data x 8ch) + (1bit data x 8ch))
#define TUD_HID_REPORT_DESC_GAMEPAD_9(...)                                    \
  HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP),                                     \
      HID_USAGE(HID_USAGE_DESKTOP_GAMEPAD),                                   \
      HID_COLLECTION(HID_COLLECTION_APPLICATION), /* Report ID if any */      \
      __VA_ARGS__ HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP),                     \
      HID_USAGE(HID_USAGE_DESKTOP_X), HID_USAGE(HID_USAGE_DESKTOP_Y),         \
      HID_USAGE(HID_USAGE_DESKTOP_Z), HID_USAGE(HID_USAGE_DESKTOP_RX),        \
      HID_USAGE(HID_USAGE_DESKTOP_RY), HID_USAGE(HID_USAGE_DESKTOP_RZ),       \
      HID_USAGE(HID_USAGE_DESKTOP_SLIDER), HID_USAGE(HID_USAGE_DESKTOP_DIAL), \
      HID_LOGICAL_MIN(0), /* HID_LOGICAL_MAX ( 0x7ff ) ,*/                    \
      HID_LOGICAL_MAX_N(0xffff, 3), HID_REPORT_COUNT(8), HID_REPORT_SIZE(16), \
      HID_INPUT(HID_DATA | HID_VARIABLE |                                     \
                HID_ABSOLUTE), /* 8 bit Button Map */                         \
      HID_USAGE_PAGE(HID_USAGE_PAGE_BUTTON), HID_USAGE_MIN(1),                \
      HID_USAGE_MAX(8), HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1),               \
      HID_REPORT_COUNT(8), HID_REPORT_SIZE(1),                                \
      HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE),                      \
      HID_COLLECTION_END  

// CrossFire settings
#define CRSF_BAUDRATE 420000
#define CRSF_MAX_PACKET_LEN 64
#define CRSF_NUM_CHANNELS 16
// 添加自定义设备名称
#define MANUFACTURER_NAME "LicardoFPV"
#define PRODUCT_NAME     "LicardoFPV ELRSJoystick"

typedef enum {
  CRSF_ADDRESS_BROADCAST = 0x00,
  CRSF_ADDRESS_USB = 0x10,
  CRSF_ADDRESS_TBS_CORE_PNP_PRO = 0x80,
  CRSF_ADDRESS_RESERVED1 = 0x8A,
  CRSF_ADDRESS_CURRENT_SENSOR = 0xC0,
  CRSF_ADDRESS_GPS = 0xC2,
  CRSF_ADDRESS_TBS_BLACKBOX = 0xC4,
  CRSF_ADDRESS_FLIGHT_CONTROLLER = 0xC8,  // Incoming data comes in this
  CRSF_ADDRESS_RESERVED2 = 0xCA,
  CRSF_ADDRESS_RACE_TAG = 0xCC,
  CRSF_ADDRESS_RADIO_TRANSMITTER = 0xEA,
  CRSF_ADDRESS_CRSF_RECEIVER = 0xEC,
  CRSF_ADDRESS_CRSF_TRANSMITTER = 0xEE,
} crsf_addr_e;

typedef enum {
  CRSF_FRAMETYPE_GPS = 0x02,
  CRSF_FRAMETYPE_BATTERY_SENSOR = 0x08,
  CRSF_FRAMETYPE_LINK_STATISTICS = 0x14,
  CRSF_FRAMETYPE_OPENTX_SYNC = 0x10,
  CRSF_FRAMETYPE_RADIO_ID = 0x3A,
  CRSF_FRAMETYPE_RC_CHANNELS_PACKED = 0x16,  // packed channel frame
  CRSF_FRAMETYPE_ATTITUDE = 0x1E,
  CRSF_FRAMETYPE_FLIGHT_MODE = 0x21,
  // Extended Header Frames, range: 0x28 to 0x96
  CRSF_FRAMETYPE_DEVICE_PING = 0x28,
  CRSF_FRAMETYPE_DEVICE_INFO = 0x29,
  CRSF_FRAMETYPE_PARAMETER_SETTINGS_ENTRY = 0x2B,
  CRSF_FRAMETYPE_PARAMETER_READ = 0x2C,
  CRSF_FRAMETYPE_PARAMETER_WRITE = 0x2D,
  CRSF_FRAMETYPE_COMMAND = 0x32,
  // MSP commands
  CRSF_FRAMETYPE_MSP_REQ =  0x7A,   // response request using msp sequence as command
  CRSF_FRAMETYPE_MSP_RESP = 0x7B,   // reply with 58 byte chunked binary
  CRSF_FRAMETYPE_MSP_WRITE = 0x7C,  // write with 8 byte chunked binary (OpenTX outbound telemetry buffer limit)
} crsf_frame_type_e;

typedef struct crsf_header_s {
  uint8_t device_addr;  // from crsf_addr_e
  uint8_t frame_size;   // counts size after this byte, so it must be the payload size + 2 (type and crc)
  uint8_t type;         // from crsf_frame_type_e
  uint8_t data[0];
} crsf_header_t;

Adafruit_USBD_HID usb_hid;  // USB HID object
uint8_t const desc_hid_report[] = {
    TUD_HID_REPORT_DESC_GAMEPAD_9()  // USB GamePad data structure
};

typedef struct __attribute__((packed)) gamepad_data {
  uint16_t ch[8];  // 16 bit 8ch
  uint8_t sw;      //  1 bit 8ch
} gp_t;

uint8_t rxbuf[CRSF_MAX_PACKET_LEN + 3];  // Raw data received from the CRSF Rx
uint8_t rxPos = 0;
static gamepad_data gp;  // Data sorted by channel
uint8_t frameSize = 0;
int datardyf = 0;        // Data read to send to USB
uint32_t gaptime;        // For bus delimiter measurement

#if defined(DEBUG)
uint32_t time_m;         // Interval time (for debug)
void debug_out();
#endif

void crsf();
void crsfdecode();
void uart();

void setup()
{
  // 设置自定义USB设备名称
  USBDevice.setManufacturerDescriptor(MANUFACTURER_NAME);
  USBDevice.setProductDescriptor(PRODUCT_NAME);
  USBDevice.setID(0x2e8a, 0x0408);

  // USB HID Device Settings
  usb_hid.setPollInterval(2);
  usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report));
  usb_hid.begin();
  while (!USBDevice.mounted()) delay(1);  // wait until device mounted

  datardyf = 0;
  gaptime = 0;
  rxPos = 0;

  // For PC serial communication (to match receiver speed)
  Serial.begin(CRSF_BAUDRATE);
  // For CRSF communication (420kbps, 8bit data, nonParity, 1 stopbit)
  Serial1.begin(CRSF_BAUDRATE, SERIAL_8N1);
#if defined(DEBUG)
  time_m = micros();  // For interval measurement
#endif
}

void loop()
{
  // WAke up host if in suspend mode and REMOTE_WAKEUP feature by host
  if (USBDevice.suspended()) {
    USBDevice.remoteWakeup();
  }
  crsf();           // Process CRSF
  uart();           // UART communication processing (for firmware rewriting)
  if (datardyf) {   // USB transmission when data is ready
    if (usb_hid.ready()) {
      usb_hid.sendReport(0, &gp, sizeof(gp));
#ifdef DEBUG
      debug_out();  // For debugging (check values on serial monitor)
#endif
    }
    datardyf = 0;  // Clear the flag after transmission
  }
}

//CRSF receive process
void crsf()
{
  uint8_t data;
  // byte received from CRSF
  if (Serial1.available()) {  // If there is incoming data on Serial1
    data = Serial1.read();    // 8-bit data read
    gaptime = micros();
    if (rxPos == 1) {
      frameSize = data;       // Second byte is the frame size
    }
    rxbuf[rxPos++] = data;    // Store received data in buffer
    if (rxPos > 1 && rxPos >= frameSize + 2) {
      crsfdecode();           // Decode after receiving one frame
      rxPos = 0;
    }
  } else {
    // If no data comes in for more than 800us, judge as a break
    if (rxPos > 0 && micros() - gaptime > 800) {
      rxPos = 0;
    }
  }
}

// Decode 11-bit serial data received from CRSF to 16-bit data (further bottom up. 5-bit)
// sift)
void crsfdecode()
{
  if (rxbuf[0] == CRSF_ADDRESS_FLIGHT_CONTROLLER) {       // header check
    if (rxbuf[2] == CRSF_FRAMETYPE_RC_CHANNELS_PACKED) {  // Decode if CH data
      gp.sw = 0;
      gp.ch[0] = ((rxbuf[3] | rxbuf[4] << 8) & 0x07ff) << 5;
      gp.ch[1] = ((rxbuf[4] >> 3 | rxbuf[5] << 5) & 0x07ff) << 5;
      gp.ch[2] = ((rxbuf[5] >> 6 | rxbuf[6] << 2 | rxbuf[7] << 10) & 0x07ff)
                 << 5;
      gp.ch[3] = ((rxbuf[7] >> 1 | rxbuf[8] << 7) & 0x07ff) << 5;
      if (((rxbuf[8] >> 4 | rxbuf[9] << 4) & 0x07ff) > 0x3ff)
        gp.sw |= 0x01;  //  AUX1 is binary data
      gp.ch[4] = ((rxbuf[9] >> 7 | rxbuf[10] << 1 | rxbuf[11] << 9) & 0x07ff)
                 << 5;
      gp.ch[5] = ((rxbuf[11] >> 2 | rxbuf[12] << 6) & 0x07ff) << 5;
      gp.ch[6] = ((rxbuf[12] >> 5 | rxbuf[13] << 3) & 0x07ff) << 5;
      gp.ch[7] = ((rxbuf[14] | rxbuf[15] << 8) & 0x7ff) << 5;
      if (((rxbuf[15] >> 3 | rxbuf[16] << 5) & 0x07ff) > 0x3ff) gp.sw |= 0x02;
      if (((rxbuf[16] >> 6 | rxbuf[17] << 2 | rxbuf[18] << 10) & 0x07ff) >
          0x3ff)
        gp.sw |= 0x04;
      if (((rxbuf[18] >> 1 | rxbuf[19] << 7) & 0x07ff) > 0x3ff) gp.sw |= 0x08;
      if (((rxbuf[19] >> 4 | rxbuf[20] << 4) & 0x07ff) > 0x3ff) gp.sw |= 0x10;
      if (((rxbuf[20] >> 7 | rxbuf[21] << 1 | rxbuf[22] << 9) & 0x07ff) > 0x3ff)
        gp.sw |= 0x20;
      if (((rxbuf[22] >> 2 | rxbuf[23] << 6) & 0x07ff) > 0x3ff) gp.sw |= 0x40;
      if (((rxbuf[23] >> 5 | rxbuf[24] << 3) & 0x07ff) > 0x3ff) gp.sw |= 0x80;

      datardyf = 1;  // set data ready flag
    }
  }
}

// UART communication process (for firmware rewriting)
// Flashing Method in ExpressLRS Configurator should be [BetaflightPassthough], not [UART].
void uart()
{
  uint32_t t;

  // When data comes from the PC, it is determined to be in firmware update mode
  if (Serial.available()) {
    t = millis();
    do {
      while (Serial.available()) {     // When data comes in from the PC
        Serial1.write(Serial.read());  // Send data from PC to receiver
        t = millis();
      }
      while (Serial1.available()) {    // When data comes in from the receiver
        Serial.write(Serial1.read());  // Send receiver data to PC
        t = millis();
      }
    } while (millis() - t < 2000);     // When data stops coming in, it's over
  }
}

#if defined(DEBUG)
// Display received data on serial monitor (for debugging)
void debug_out()
{
  int i;
  Serial.print(rxbuf[0], HEX);  // device addr
  Serial.print(" ");
  Serial.print(rxbuf[1]);       // data size +1
  Serial.print(" ");
  Serial.print(rxbuf[2], HEX);  // type
  Serial.print(" ");
  for (i = 0; i < 8; i++) {
    Serial.print(gp.ch[i]);
    Serial.print(" ");
  }
  Serial.print(gp.sw, BIN);
  Serial.print(" ");
  Serial.print(micros() - time_m);  // Display interval time (us)
  Serial.println("us");
  time_m = micros();
}
#endif



上面是一个platformio项目，帮我根据serial1 是否已连接，添加一个指示灯，指示灯的io是25，请你参考下面的代码的亮灯

注意，你要参考下面的代码，来改写上面的！

#include <Arduino.h>
#include <CrsfSerial.h>  // https://github.com/CapnBry/CRServoF/  
#include <PicoGamepad.h> // https://gitlab.com/realrobots/PicoGamepad/-/tree/master  

#include "calibration.h"

// BOARD_ID SELECTION HAPPENS IN PLATFORMIO ENVIRONMENT SELECTION

// Blink routine variables and state tracking
#define BLINK_ENABLED                    // comment this line out to disable led blink
#define BLINK_TIME 60000                 // blink routine window (in ms)
#define BLINK_DELAY 500                  // delay in between led state change (in ms)

static bool serialEcho;
static char serialInBuff[64];
static uint8_t serialInBuffLen;

bool led_state = false;                  // track led on/off state
unsigned long ms_curr = 0;               // current time
unsigned long ms_last_link_changed = 0;  // last time crsf link changed
unsigned long ms_last_led_changed = 0;   // last time led changed state in blink routine

#include "board_defs.h"

UART Serial2(CRSF_TX, CRSF_RX, NC, NC);

CrsfSerial crsf(Serial2, CRSF_BAUDRATE); // pass any HardwareSerial port
int channel_data = 0;
int map_data = 0;

PicoGamepad gamepad;

btn_config *c;

/***
 * This callback is called whenever new channel values are available.
 * Use crsf.getChannel(x) to get us channel values (1-16).
 ***/
void packetChannels()
{
    // Manually expanding instead of looping so I can change params as needed

    // X - Channel 1 - A
    channel_data = crsf.getChannel(1);
    map_data = map(channel_data, \
      CHANNEL_1_LOW_EP,          \
      CHANNEL_1_HIGH_EP,         \
      JOYSTICK_LOW,              \
      JOYSTICK_HIGH);
    gamepad.SetX(map_data);

    // Y - Channel 2 - E
    channel_data = crsf.getChannel(2);
    map_data = map(channel_data, \
      CHANNEL_2_LOW_EP,          \
      CHANNEL_2_HIGH_EP,         \
      JOYSTICK_LOW,              \
      JOYSTICK_HIGH);
    gamepad.SetY(map_data);

    // Rx - Channel 3 - T
    channel_data = crsf.getChannel(3);
    map_data = map(channel_data, \
      CHANNEL_3_LOW_EP,          \
      CHANNEL_3_HIGH_EP,         \
      JOYSTICK_LOW,              \
      JOYSTICK_HIGH);
    gamepad.SetRx(map_data);

    // Ry - Channel 4 - R
    channel_data = crsf.getChannel(4);
    map_data = map(channel_data, \
      CHANNEL_4_LOW_EP,          \
      CHANNEL_4_HIGH_EP,         \
      JOYSTICK_LOW,              \
      JOYSTICK_HIGH);
    gamepad.SetRy(map_data);

    // Z - Channel 5
    channel_data = crsf.getChannel(5);
    map_data = map(channel_data, \
      CHANNEL_5_LOW_EP,          \
      CHANNEL_5_HIGH_EP,         \
      JOYSTICK_LOW,              \
      JOYSTICK_HIGH);
    gamepad.SetZ(map_data);

    // Rz - Channel 6
    channel_data = crsf.getChannel(6);
    map_data = map(channel_data, \
      CHANNEL_6_LOW_EP,          \
      CHANNEL_6_HIGH_EP,         \
      JOYSTICK_LOW,              \
      JOYSTICK_HIGH);
    gamepad.SetRz(map_data);

    // Rx - Channel 7
    channel_data = crsf.getChannel(7);
    map_data = map(channel_data, \
      CHANNEL_7_LOW_EP,          \
      CHANNEL_7_HIGH_EP,         \
      JOYSTICK_LOW,              \
      JOYSTICK_HIGH);
    gamepad.SetThrottle(map_data);

    // Rx - Channel 8
    channel_data = crsf.getChannel(8);
    map_data = map(channel_data, \
      CHANNEL_8_LOW_EP,          \
      CHANNEL_8_HIGH_EP,         \
      JOYSTICK_LOW,              \
      JOYSTICK_HIGH);
    gamepad.SetS0(map_data);

    // Ry - unused
    // gamepad.SetRy(map_data);
    // Rz - unused
    // gamepad.SetRz(map_data);
    // S0 - unused
    // gamepad.SetS0(map_data);

    // Multi-position switches can be set up in calibrations.h
    // The button will report HIGH when the channel is withing
    // a lower / upper bound (inclusive) constraint.
    // Default is HIGH (1510, 2011) else LOW

    for(uint8_t i = 0; i < NUM_BUTTONS; i++){
      c = &btn_map[i];
      channel_data = crsf.getChannel(c->channel);
      // bounds check inclusive
      if(channel_data >= c->lower_bound && channel_data <= c->upper_bound) {
        map_data = c->invert ? LOW : HIGH;
      }
      else {
        map_data = c->invert ? HIGH : LOW;
      }
      gamepad.SetButton(c->id, map_data);

      #ifdef BTN_PRINT
      Serial.print("b: "); Serial.print(c->id));
      Serial.print(" c: "); Serial.print(channel_data);
      Serial.print(" m: "); Serial.println(map_data);
      #endif
    }
    // TODO what to do with Channel 13,14,15,16 (NA,NA,LQ,RSSI)

    // Set hat direction, 4 hats available. direction is clockwise 0=N 1=NE 2=E 3=SE 4=S 5=SW 6=W 7=NW 8=CENTER
    // gamepad.SetHat(0, 8);

    gamepad.send_update();
}

void crsfLinkUp() {
  ms_last_link_changed = millis();
  ms_last_led_changed = ms_last_link_changed;
  led_state = true;
  led_on();
}

void crsfLinkDown() {
  ms_last_link_changed = millis();
  ms_last_led_changed = ms_last_link_changed;
  led_state = false;
  led_off();
}

static void passthroughBegin(uint32_t baud)
{
    if (baud != crsf.getBaud())
    {
        // Force a reboot command since we want to send the reboot
        // at 420000 then switch to what the user wanted
        const uint8_t rebootpayload[] = { 'b', 'l' };
        crsf.queuePacket(CRSF_ADDRESS_CRSF_RECEIVER, CRSF_FRAMETYPE_COMMAND, &rebootpayload, sizeof(rebootpayload));
    }
    crsf.setPassthroughMode(true, baud);

    serialEcho = false;
}

static void crsfOobData(uint8_t b)
{
    Serial.write(b);
}

/***
 * @brief Processes a text command like we're some sort of CLI or something
 * @return true if CrsfSerial was placed into passthrough mode, false otherwise
*/
static bool handleSerialCommand(char *cmd)
{
    // Fake a CRSF RX on UART6
    bool prompt = true;
    if (strcmp(cmd, "#") == 0)
    {
        Serial.println("Fake CLI Mode, type 'exit' or 'help' to do nothing\r\n");
        serialEcho = true;
    }

    else if (strcmp(cmd, "serial") == 0)
        Serial.println("serial 5 64 0 0 0 0\r\n");

    else if (strcmp(cmd, "get serialrx_provider") == 0)
        Serial.println("serialrx_provider = CRSF\r\n");

    else if (strcmp(cmd, "get serialrx_inverted") == 0)
        Serial.println("serialrx_inverted = OFF\r\n");

    else if (strcmp(cmd, "get serialrx_halfduplex") == 0)
        Serial.println("serialrx_halfduplex = OFF\r\n");

    else if (strncmp(cmd, "serialpassthrough 5 ", 20) == 0)
    {
        Serial.println(cmd);

        unsigned int baud = atoi(&cmd[20]);
        passthroughBegin(baud);

        return true;
    }

    else
        prompt = false;

    if (prompt)
        Serial.print("# ");

    return false;
}

static void checkSerialInNormal()
{
    while (Serial.available())
    {
        char c = Serial.read();
        if (serialEcho && c != '\n')
            Serial.write(c);

        if (c == '\r' || c == '\n')
        {
            if (serialInBuffLen != 0)
            {
                Serial.write('\n');
                serialInBuff[serialInBuffLen] = '\0';
                serialInBuffLen = 0;

                bool goToPassthrough = handleSerialCommand(serialInBuff);
                // If need to go to passthrough, get outta here before we dequeue any bytes
                if (goToPassthrough)
                    return;
            }
        }
        // Betaflight CLI is entered just by hitting #, no return needed
        else if (c == '#')
        {
            serialInBuffLen = 0;
            handleSerialCommand("#");
        }
        else
        {
            serialInBuff[serialInBuffLen++] = c;
            // if the buffer fills without getting a newline, just reset
            if (serialInBuffLen >= sizeof(serialInBuff))
                serialInBuffLen = 0;
        }
    }  /* while Serial */
}

static void checkSerialInPassthrough()
{
    static uint32_t lastData = 0;
    static bool LED = false;
    bool gotData = false;
    unsigned int avail;
    while ((avail = Serial.available()) != 0)
    {
        uint8_t buf[16];
        avail = Serial.readBytes((char *)buf, min(sizeof(buf), avail));
        crsf.write(buf, avail);
        LED ? led_on() : led_off();
        LED = !LED;
        gotData = true;
    }
    // If longer than X seconds since last data, switch out of passthrough
    if (gotData || !lastData)
        lastData = millis();

    // Turn off LED 1s after last data
    else if (LED && (millis() - lastData > 1000))
    {
        LED = false;
        led_off();
    }

    // Short blink LED after timeout
    else if (millis() - lastData > 5000)
    {
        lastData = 0;
        led_on();
        delay(200);
        led_off();
        crsf.setPassthroughMode(false);
    }
}

static void checkSerialIn()
{
    if (crsf.getPassthroughMode())
        checkSerialInPassthrough();
    else
        checkSerialInNormal();
}

#ifdef BLINK_ENABLED
void led_loop() {
  ms_curr = millis();
  // link is down
  if(!crsf.isLinkUp()) {
    // within the blink routine window (BLINK_TIME)
    if(ms_curr < (ms_last_link_changed + BLINK_TIME)) {
      // handle led toggle delay
      if(ms_curr > (ms_last_led_changed + BLINK_DELAY)) {
        ms_last_led_changed = ms_curr;
        led_state ? led_on() : led_off();
        led_state = !led_state;  // toggle led state
      }
    }
    else
    {
      // ensure the led is off if the blink routine expired and link is down
      led_off();
    }
  }
}
#endif

void setup()
{
    Serial.begin(115200);
    boardSetup();
    crsfLinkDown();

    gamepad.send_update();

    // Attach the channels callback
    crsf.onPacketChannels = &packetChannels;
    crsf.onLinkUp = &crsfLinkUp;
    crsf.onLinkDown = &crsfLinkDown;
    crsf.onOobData = &crsfOobData;
    crsf.begin();
    serialEcho = true;
}

void loop()
{
    // Must call CrsfSerial.loop() in loop() to process data
    crsf.loop();
    checkSerialIn();
    #ifdef BLINK_ENABLED
    led_loop();
    #endif
}