数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 69|回复: 2

[电脑] 【开源、免驱】基于 CH552T 的低成本海盗船 Commander Pro 兼容风扇控制器

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
本帖最后由 digitking 于 2026-8-3 10:09 编辑

【开源、免驱】基于 CH552T 的低成本海盗船 Commander Pro 兼容风扇控制器
——DIY一个2+1盘位小体积的NAS(机箱)的续作
项目地址:https://github.com/stevenpaul007-creator/CommanderPro_CH55xDuino-
固件下载:https://github.com/stevenpaul007 ... Duino.ino.ch552.hex
PCB:https://github.com/stevenpaul007 ... uino-/tree/main/pcb

> 背景和目标
我那个小nas的主板上没有提供PWM风扇接口,在linux下也没办法调速。我的12V风扇插在了5V的电源上,“全速”运行的时候晚上有点吵,所以我制作了一个USB接口的风扇PWM调速硬件。期望不高,
1.linux下免驱;
2.价格便宜
所以就采用了CH552T来制作这个小玩意儿。CH552T提供了2路PWM、2个中断IO、4个ADC(跟2路PWM复用,建议用2个)。
这样就可以轻松的实现2路PWM+2路测速+2路NTC测温。
如果采用更小的CH552E封装,就只能提供1路PWM+1路测速+1路NTC测温


> 制作过程
>> PCB原理图
这里只引出了2组PWM调速和测速。对于CH552T来说轻松胜任


>> PCB效果图
主打一个小巧,18mm*18mm。主板上地方不大,采用了USB插针方式与主板连接。
复刻的时候,根据自己主板的情况,排插可以采用弯头的


>> 开始焊接
祖传手艺:铁板烧

验明正身,CH552T

硬改一路NTC,采用10K,B值是3950

通过USB烧录固件,然后简单测试一下

Windows下的fancontrol装好插件,可以识别和控制速度

Linux内核自带驱动,直接识别和控制

上主板,接好连线。NTC放在内存上了,是风道的最末端;5V上加个电解电容,启动更有劲儿。


好了,这个夏天清静了
哦对了,还有个CH552E的USB接口版本PCB


> 以下是代码
/*
  CH552T Corsair Commander Pro compatible lower-board firmware for CH55xDuino.

  SPDX-License-Identifier: MIT
  Copyright (c) 2026 seven

  First target: make Windows enumerate as Corsair Commander Pro compatible HID:
    VID:PID = 1B1C:0C10
    HID OUT report: 64 bytes, host writes report-id 0 + command frame
    HID IN  report: 16 bytes, data fields at response offsets 1..n

  Arduino IDE / CH55xDuino notes:
    - Select your CH552/CH552T board.
    - USB setting must be a USER USB RAM option, not the stock built-in USB mode.
      CH55xDuino examples call this e.g. "USER USB setting" / user148/user266.
    - This file is HID-only.

  Pin plan used here, matching CH55xDuino decimal pin naming convention:
    P3.4 = 34 -> Fan1 PWM / hardware PWM2
    P1.5 = 15 -> Fan2 PWM / hardware PWM1
    P3.2 = 32 -> Fan1 tach / INT0-style input
    P3.3 = 33 -> Fan2 tach / INT1-style input

  Hardware protection remains required:
    PWM:  CH552 pin -> 270R -> fan PWM, 1N4148 clamp to +5V, optional 10k pulldown
    Tach: fan tach -> 10k -> CH552 input, 1N4148 clamp to +5V, 100k pulldown,
          optional 10k pull-up to +5V if fan tach is open collector without pull-up
*/

#ifndef USER_USB_RAM
#error "In Arduino IDE select a CH55xDuino USER USB RAM setting, not the stock USB mode."
#endif

#include <Arduino.h>
#include <stdint.h>
#include <stdbool.h>
#include "include/ch5xx.h"
#include "include/ch5xx_usb.h"

// Fallbacks for CH55xDuino versions where these request constants are only
// pulled in by the bundled USB examples.
#ifndef DEFAULT_ENDP0_SIZE
#define DEFAULT_ENDP0_SIZE 8
#endif
#ifndef USB_REQ_TYP_MASK
#define USB_REQ_TYP_MASK 0x60
#endif
#ifndef USB_REQ_TYP_STANDARD
#define USB_REQ_TYP_STANDARD 0x00
#endif
#ifndef USB_REQ_TYP_CLASS
#define USB_REQ_TYP_CLASS 0x20
#endif
#ifndef USB_GET_STATUS
#define USB_GET_STATUS 0x00
#endif
#ifndef USB_GET_DESCRIPTOR
#define USB_GET_DESCRIPTOR 0x06
#endif
#ifndef USB_SET_ADDRESS
#define USB_SET_ADDRESS 0x05
#endif
#ifndef USB_GET_CONFIGURATION
#define USB_GET_CONFIGURATION 0x08
#endif
#ifndef USB_SET_CONFIGURATION
#define USB_SET_CONFIGURATION 0x09
#endif
#ifndef USB_GET_INTERFACE
#define USB_GET_INTERFACE 0x0A
#endif
#ifndef HID_GET_REPORT
#define HID_GET_REPORT 0x01
#endif
#ifndef HID_SET_IDLE
#define HID_SET_IDLE 0x0A
#endif
#ifndef bUDA_GP_BIT
#define bUDA_GP_BIT 0x80
#endif

#define USB_VID 0x1B1C
#define USB_PID 0x0C10

// Endpoint buffer placement inside CH55xDuino USER USB RAM.
// Keep these ranges non-overlapping: EP0 uses 8 bytes, EP1 IN uses 16 bytes,
// and EP2 OUT uses 64 bytes.
#define EP0_ADDR 0
#define EP1_IN_ADDR 16
#define EP2_OUT_ADDR 96

#define FAN_COUNT_PHYSICAL 2
#define FAN_COUNT_LOGICAL 6
#define TACH_PULSES_PER_REV 2UL

#define FAN1_PWM_PIN 15  // P1.5, CH55xDuino hardware PWM1
#define FAN2_PWM_PIN 34  // P3.4, CH55xDuino hardware PWM2
#define FAN1_TACH_PIN 32 // P3.2
#define FAN2_TACH_PIN 33 // P3.3

#define CPRO_CMD_GET_FIRMWARE    0x02
#define CPRO_CMD_GET_BOOTLOADER  0x06
#define CPRO_CMD_GET_TEMP_CONFIG 0x10
#define CPRO_CMD_GET_TEMP        0x11
#define CPRO_CMD_GET_VOLTS       0x12
#define CPRO_CMD_GET_FAN_MODES   0x20
#define CPRO_CMD_GET_FAN_RPM     0x21
#define CPRO_CMD_GET_FAN_PWM     0x22
#define CPRO_CMD_SET_FAN_DUTY    0x23
#define CPRO_CMD_SET_FAN_TARGET  0x24
#define CPRO_CMD_SET_FAN_PROFILE 0x25
#define CPRO_CMD_SET_FAN_MODE    0x28
#define CPRO_CMD_LED_COMMIT      0x33
#define CPRO_CMD_BEGIN_LED_EFFECT 0x34
#define CPRO_CMD_LED_EFFECT      0x35
#define CPRO_CMD_RESET_LED_CHANNEL 0x37
#define CPRO_CMD_SET_LED_CHANNEL_STATE 0x38

// Plausible Commander Pro identity values for host software that displays or
// keys devices by firmware version and serial number.  The firmware response is
// formatted by host tools as "major.minor.patch".
#define CPRO_FW_MAJOR 1
#define CPRO_FW_MINOR 0
#define CPRO_FW_PATCH 0
#define CPRO_BL_MAJOR 0
#define CPRO_BL_MINOR 1

#define CPRO_FAN_MODE_DISCONNECTED 0x00
#define CPRO_FAN_MODE_DC           0x01
#define CPRO_FAN_MODE_PWM          0x02

// CH55xDuino USB buffers. Endpoint DMA addresses must be in USER USB RAM.
__xdata __at(EP0_ADDR)     uint8_t Ep0Buffer[8];
__xdata __at(EP1_IN_ADDR)  uint8_t Ep1InBuffer[16];
__xdata __at(EP2_OUT_ADDR) uint8_t Ep2OutBuffer[64];

__data uint16_t SetupLen;
__data uint8_t SetupReq;
__code uint8_t *__data pDescr;
volatile __xdata uint8_t UsbConfig = 0;
volatile __bit Ep1InBusy = 0;

// Tach pulse counters are updated by external interrupt callbacks and consumed
// once per second in loop().  Most PC fans produce two tach pulses per turn.
volatile uint16_t tachPulses[2] = {0, 0};
uint16_t fanRpm[2] = {0, 0};
uint8_t fanDuty[2] = {50, 50};
uint8_t fanMode[2] = {CPRO_FAN_MODE_PWM, CPRO_FAN_MODE_PWM};
unsigned long lastRpmMs = 0;

// Device descriptor: Corsair Commander Pro identity.
__code uint8_t DeviceDescriptor[] = {
  18, 0x01,
  0x10, 0x01,
  0x00, 0x00, 0x00,
  8,
  (uint8_t)(USB_VID & 0xff), (uint8_t)(USB_VID >> 8),
  (uint8_t)(USB_PID & 0xff), (uint8_t)(USB_PID >> 8),
  0x00, 0x01,
  1, 2, 3,
  1
};

// Vendor HID report descriptor: 64-byte OUT, 16-byte IN, no report ID.
__code uint8_t HidReportDescriptor[] = {
  0x06, 0x00, 0xFF,       // Usage Page (Vendor Defined)
  0x09, 0x01,             // Usage
  0xA1, 0x01,             // Collection Application
  0x15, 0x00,             // Logical Min 0
  0x26, 0xFF, 0x00,       // Logical Max 255
  0x75, 0x08,             // Report Size 8
  0x95, 0x40,             // Report Count 64
  0x09, 0x01,
  0x91, 0x02,             // Output Data Var Abs
  0x95, 0x10,             // Report Count 16
  0x09, 0x01,
  0x81, 0x02,             // Input Data Var Abs
  0xC0
};

// One-interface HID config: EP1 IN 16 bytes, EP2 OUT 64 bytes.
// Total length is 9(config)+9(interface)+9(HID)+7(IN ep)+7(OUT ep) = 41.
// A previous 34-byte total length made Windows read only the IN endpoint while
// bNumEndpoints still said 2, causing HIDClass Code 10 / failed start.
__code uint8_t ConfigurationDescriptor[] = {
  9, 0x02, 41, 0x00, 1, 1, 0, 0x80, 50,
  9, 0x04, 0, 0, 2, 0x03, 0x00, 0x00, 0,
  9, 0x21, 0x11, 0x01, 0x00, 1, 0x22, sizeof(HidReportDescriptor), 0,
  7, 0x05, 0x81, 0x03, 16, 0, 1,
  7, 0x05, 0x02, 0x03, 64, 0, 1
};

__code uint8_t LanguageDescriptor[] = {4, 0x03, 0x09, 0x04};
__code uint16_t ManufacturerDescriptor[] = {
  ((7 + 1) * 2) | (0x03 << 8), 'C','o','r','s','a','i','r'
};
__code uint16_t ProductDescriptor[] = {
  ((13 + 1) * 2) | (0x03 << 8), 'C','o','m','m','a','n','d','e','r',' ','P','r','o'
};
__code uint16_t SerialDescriptor[] = {
  ((12 + 1) * 2) | (0x03 << 8), 'C','P','R','O','0','0','0','8','9','7','5','7'
};

static void put_be16(uint8_t *p, uint16_t value) {
  // Commander Pro protocol returns multi-byte numeric fields big-endian.
  p[0] = (uint8_t)(value >> 8);
  p[1] = (uint8_t)(value & 0xff);
}

static void setFanDuty(uint8_t fan, uint8_t duty) {
  // Host commands use percent 0..100; CH55xDuino analogWrite uses 0..255.
  if (fan >= FAN_COUNT_PHYSICAL) return;
  if (duty > 100) duty = 100;
  fanDuty[fan] = duty;
  uint8_t pwm = (uint8_t)(((uint16_t)duty * 255U) / 100U);
  analogWrite(fan == 0 ? FAN1_PWM_PIN : FAN2_PWM_PIN, pwm);
}

static uint8_t getFanMode(uint8_t fan) {
  if (fan >= FAN_COUNT_PHYSICAL) return CPRO_FAN_MODE_DISCONNECTED;
  return fanMode[fan];
}

static uint16_t getFanRpm(uint8_t fan) {
  if (fan >= FAN_COUNT_PHYSICAL) return 0;
  return fanRpm[fan];
}

static void handleCommanderFrame(__xdata uint8_t *outFrame) {
  // EP2 OUT receives a 64-byte command frame.  EP1 IN returns a 16-byte
  // response frame.  Response byte 0 must be a status code, not a command echo,
  // otherwise Linux corsair-cpro treats command 0x10 as -EINVAL during probe.
  uint8_t cmd = outFrame[0];
  uint8_t *resp = Ep1InBuffer;
  memset(resp, 0, 16);
  // Real Commander Pro responses use byte 0 as status/error code:
  //   0x00 success, 0x01 invalid command, 0x10 invalid argument,
  //   0x11 disconnected temp, 0x12 PWM not fixed-duty controlled.
  // Linux corsair-cpro checks this byte during probe; echoing the command
  // makes command 0x10 look like -EINVAL and causes probe error -22.
  resp[0] = 0x00;

  switch (cmd) {
    case CPRO_CMD_GET_FIRMWARE:
      resp[1] = CPRO_FW_MAJOR; resp[2] = CPRO_FW_MINOR; resp[3] = CPRO_FW_PATCH;
      break;
    case CPRO_CMD_GET_BOOTLOADER:
      resp[1] = CPRO_BL_MAJOR; resp[2] = CPRO_BL_MINOR;
      break;
    case CPRO_CMD_GET_TEMP_CONFIG:
      // Dummy temperature support: report all four Commander Pro thermistor
      // channels as present so host software can exercise GET_TEMP, but return
      // a fixed value below instead of reading real sensors.
      resp[1] = resp[2] = resp[3] = resp[4] = 1;
      break;
    case CPRO_CMD_GET_TEMP:
      if (outFrame[1] < 4) put_be16(&resp[1], 2500); // 25.00 C dummy
      else resp[0] = 0x10;
      break;
    case CPRO_CMD_GET_VOLTS:
      if (outFrame[1] == 0) put_be16(&resp[1], 12000);
      else if (outFrame[1] == 1) put_be16(&resp[1], 5000);
      else put_be16(&resp[1], 3300);
      break;
    case CPRO_CMD_GET_FAN_MODES:
      for (uint8_t i = 0; i < FAN_COUNT_LOGICAL; i++) resp[1 + i] = getFanMode(i);
      break;
    case CPRO_CMD_GET_FAN_RPM:
      put_be16(&resp[1], getFanRpm(outFrame[1]));
      break;
    case CPRO_CMD_GET_FAN_PWM:
      if (outFrame[1] < FAN_COUNT_PHYSICAL) resp[1] = fanDuty[outFrame[1]];
      else resp[0] = 0x12;
      break;
    case CPRO_CMD_SET_FAN_DUTY:
      setFanDuty(outFrame[1], outFrame[2]);
      break;
    case CPRO_CMD_SET_FAN_TARGET:
      // Linux hwmon may expose fan*_target. Accept target-RPM writes for
      // compatibility, but keep the simple fixed-duty control loop for now.
      break;
    case CPRO_CMD_SET_FAN_PROFILE:
      // Accept for compatibility; no temperature/RPM closed-loop yet.
      break;
    case CPRO_CMD_SET_FAN_MODE:
      if (outFrame[2] < FAN_COUNT_PHYSICAL) {
        fanMode[outFrame[2]] = outFrame[3];
        if (fanMode[outFrame[2]] == CPRO_FAN_MODE_DISCONNECTED) setFanDuty(outFrame[2], 0);
      }
      break;
    case CPRO_CMD_LED_COMMIT:
    case CPRO_CMD_BEGIN_LED_EFFECT:
    case CPRO_CMD_LED_EFFECT:
    case CPRO_CMD_RESET_LED_CHANNEL:
    case CPRO_CMD_SET_LED_CHANNEL_STATE:
      // Lighting is intentionally not implemented on this 2-fan board.  Return
      // success so iCUE/FanControl-style initialization paths that touch LED
      // channels do not reject the device for an unsupported command.
      break;
    default:
      resp[0] = 0x01;
      break;
  }

  UEP1_T_LEN = 16;
  Ep1InBusy = 1;
  UEP1_CTRL = (UEP1_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_ACK;
}

#pragma save
#pragma nooverlay
void tach1ISR() { tachPulses[0]++; }
#pragma restore

#pragma save
#pragma nooverlay
void tach2ISR() { tachPulses[1]++; }
#pragma restore

static void updateRpm() {
  // Convert the number of falling tach edges seen during the last second into
  // RPM.  This intentionally avoids floating point to keep SDCC output small.
  unsigned long now = millis();
  if (now - lastRpmMs < 1000) return;
  lastRpmMs = now;
  noInterrupts();
  uint16_t p0 = tachPulses[0]; tachPulses[0] = 0;
  uint16_t p1 = tachPulses[1]; tachPulses[1] = 0;
  interrupts();
  fanRpm[0] = (uint16_t)((uint32_t)p0 * 60UL / TACH_PULSES_PER_REV);
  fanRpm[1] = (uint16_t)((uint32_t)p1 * 60UL / TACH_PULSES_PER_REV);
}

static void USB_EP0_SETUP() {
  // EP0 SETUP handles standard USB enumeration requests plus the minimal HID
  // class requests Windows/Linux send before starting the interrupt endpoints.
  uint8_t len = USB_RX_LEN;
  if (len != 8) {
    UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
    return;
  }

  SetupLen = ((uint16_t)Ep0Buffer[7] << 8) | Ep0Buffer[6];
  SetupReq = Ep0Buffer[1];
  len = 0;

  if ((Ep0Buffer[0] & USB_REQ_TYP_MASK) == USB_REQ_TYP_STANDARD) {
    switch (SetupReq) {
      case USB_GET_DESCRIPTOR:
        switch (Ep0Buffer[3]) {
          case 1: pDescr = DeviceDescriptor; len = sizeof(DeviceDescriptor); break;
          case 2: pDescr = ConfigurationDescriptor; len = sizeof(ConfigurationDescriptor); break;
          case 3:
            if (Ep0Buffer[2] == 0) { pDescr = LanguageDescriptor; len = sizeof(LanguageDescriptor); }
            else if (Ep0Buffer[2] == 1) { pDescr = (__code uint8_t *)ManufacturerDescriptor; len = ((uint8_t *)ManufacturerDescriptor)[0]; }
            else if (Ep0Buffer[2] == 2) { pDescr = (__code uint8_t *)ProductDescriptor; len = ((uint8_t *)ProductDescriptor)[0]; }
            else if (Ep0Buffer[2] == 3) { pDescr = (__code uint8_t *)SerialDescriptor; len = ((uint8_t *)SerialDescriptor)[0]; }
            else len = 0xff;
            break;
          case 0x22: pDescr = HidReportDescriptor; len = sizeof(HidReportDescriptor); break;
          default: len = 0xff; break;
        }
        if (len != 0xff) {
          if (SetupLen > len) SetupLen = len;
          len = (SetupLen >= DEFAULT_ENDP0_SIZE) ? DEFAULT_ENDP0_SIZE : SetupLen;
          for (uint8_t i = 0; i < len; i++) Ep0Buffer = pDescr;
          SetupLen -= len;
          pDescr += len;
        }
        break;
      case USB_SET_ADDRESS:
        SetupLen = Ep0Buffer[2];
        break;
      case USB_SET_CONFIGURATION:
        UsbConfig = Ep0Buffer[2];
        break;
      case USB_GET_CONFIGURATION:
        Ep0Buffer[0] = UsbConfig;
        len = 1;
        break;
      case USB_GET_STATUS:
        Ep0Buffer[0] = 0; Ep0Buffer[1] = 0; len = 2;
        break;
      case USB_GET_INTERFACE:
        Ep0Buffer[0] = 0; len = 1;
        break;
      default:
        len = 0xff;
        break;
    }
  } else if ((Ep0Buffer[0] & USB_REQ_TYP_MASK) == USB_REQ_TYP_CLASS) {
    // HID class: SET_IDLE and GET_REPORT are enough for this vendor HID bring-up.
    if (SetupReq == HID_SET_IDLE) {
      len = 0;
    } else if (SetupReq == HID_GET_REPORT) {
      memset(Ep0Buffer, 0, DEFAULT_ENDP0_SIZE);
      len = DEFAULT_ENDP0_SIZE;
    } else {
      len = 0xff;
    }
  } else {
    len = 0xff;
  }

  if (len == 0xff) {
    SetupReq = 0xff;
    UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
  } else {
    UEP0_T_LEN = len;
    UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
  }
}

static void USB_EP0_IN() {
  if (SetupReq == USB_GET_DESCRIPTOR) {
    uint8_t len = (SetupLen >= DEFAULT_ENDP0_SIZE) ? DEFAULT_ENDP0_SIZE : SetupLen;
    for (uint8_t i = 0; i < len; i++) Ep0Buffer = pDescr;
    SetupLen -= len;
    pDescr += len;
    UEP0_T_LEN = len;
    UEP0_CTRL ^= bUEP_T_TOG;
  } else if (SetupReq == USB_SET_ADDRESS) {
    USB_DEV_AD = (USB_DEV_AD & bUDA_GP_BIT) | SetupLen;
    UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
  } else {
    UEP0_T_LEN = 0;
    UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
  }
}

static void USB_EP0_OUT() {
  UEP0_T_LEN = 0;
  UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
}

static void USB_EP1_IN() {
  // Host has consumed the last 16-byte response.  NAK further IN tokens until
  // the next command prepares a fresh response.
  UEP1_T_LEN = 0;
  Ep1InBusy = 0;
  UEP1_CTRL = (UEP1_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_NAK;
}

static void USB_EP2_OUT() {
  // Host sent a Commander Pro command frame on the interrupt OUT endpoint.
  if (U_TOG_OK) {
    handleCommanderFrame(Ep2OutBuffer);
  }
  UEP2_CTRL = (UEP2_CTRL & ~MASK_UEP_R_RES) | UEP_R_RES_ACK;
}

#pragma save
#pragma nooverlay
void USBInterrupt(void) {
  // CH55xDuino installs this USB ISR when USER USB RAM mode is selected.  The
  // ISR dispatches token events to EP0/EP1/EP2 handlers and resets endpoint
  // state after a USB bus reset.
  if (UIF_TRANSFER) {
    uint8_t ep = USB_INT_ST & MASK_UIS_ENDP;
    uint8_t tok = USB_INT_ST & MASK_UIS_TOKEN;
    if (ep == 0 && tok == UIS_TOKEN_SETUP) USB_EP0_SETUP();
    else if (ep == 0 && tok == UIS_TOKEN_IN) USB_EP0_IN();
    else if (ep == 0 && tok == UIS_TOKEN_OUT) USB_EP0_OUT();
    else if (ep == 1 && tok == UIS_TOKEN_IN) USB_EP1_IN();
    else if (ep == 2 && tok == UIS_TOKEN_OUT) USB_EP2_OUT();
    UIF_TRANSFER = 0;
  }

  if (UIF_BUS_RST) {
    UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
    UEP1_CTRL = bUEP_AUTO_TOG | UEP_T_RES_NAK;
    UEP2_CTRL = bUEP_AUTO_TOG | UEP_R_RES_ACK;
    USB_DEV_AD = 0;
    UIF_SUSPEND = 0;
    UIF_TRANSFER = 0;
    UIF_BUS_RST = 0;
    UsbConfig = 0;
  }

  if (UIF_SUSPEND) {
    UIF_SUSPEND = 0;
  }
}
#pragma restore

static void USBDeviceCfgLocal() {
  // Device mode, full speed, internal D+ pull-up enabled.
  USB_CTRL = 0x00;
  USB_CTRL &= ~bUC_HOST_MODE;
  USB_CTRL |= bUC_DEV_PU_EN | bUC_INT_BUSY | bUC_DMA_EN;
  USB_DEV_AD = 0x00;
  USB_CTRL &= ~bUC_LOW_SPEED;
  UDEV_CTRL &= ~bUD_LOW_SPEED;
#if defined(CH551) || defined(CH552) || defined(CH549)
  UDEV_CTRL = bUD_PD_DIS;
#endif
  UDEV_CTRL |= bUD_PORT_EN;
}

static void USBDeviceEndPointCfgLocal() {
  // Bind SIE DMA pointers to our USB RAM buffers and enable EP1 IN + EP2 OUT.
#if defined(CH559)
  UEP0_DMA_H = ((uint16_t)Ep0Buffer >> 8);
  UEP0_DMA_L = ((uint16_t)Ep0Buffer >> 0);
  UEP1_DMA_H = ((uint16_t)Ep1InBuffer >> 8);
  UEP1_DMA_L = ((uint16_t)Ep1InBuffer >> 0);
  UEP2_DMA_H = ((uint16_t)Ep2OutBuffer >> 8);
  UEP2_DMA_L = ((uint16_t)Ep2OutBuffer >> 0);
#else
  UEP0_DMA = (uint16_t)Ep0Buffer;
  UEP1_DMA = (uint16_t)Ep1InBuffer;
  UEP2_DMA = (uint16_t)Ep2OutBuffer;
#endif
  UEP4_1_MOD = bUEP1_TX_EN;
  UEP2_3_MOD = bUEP2_RX_EN;
  UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
  UEP1_CTRL = bUEP_AUTO_TOG | UEP_T_RES_NAK;
  UEP2_CTRL = bUEP_AUTO_TOG | UEP_R_RES_ACK;
}

static void USBDeviceIntCfgLocal() {
  // Enable USB transfer/reset/suspend interrupts and global interrupts.
  USB_INT_EN |= bUIE_SUSPEND;
  USB_INT_EN |= bUIE_TRANSFER;
  USB_INT_EN |= bUIE_BUS_RST;
  USB_INT_FG |= 0x1F;
  IE_USB = 1;
  EA = 1;
}

void USBInitLocal() {
  USBDeviceCfgLocal();
  USBDeviceEndPointCfgLocal();
  USBDeviceIntCfgLocal();
}

void setup() {
  // Start fans at a safe 50% duty, configure tach inputs, then attach USB.
  pinMode(FAN1_PWM_PIN, OUTPUT);
  pinMode(FAN2_PWM_PIN, OUTPUT);
  pinMode(FAN1_TACH_PIN, INPUT_PULLUP);
  pinMode(FAN2_TACH_PIN, INPUT_PULLUP);
  setFanDuty(0, 50);
  setFanDuty(1, 50);
  attachInterrupt(0, tach1ISR, FALLING); // INT0 / P3.2
  attachInterrupt(1, tach2ISR, FALLING); // INT1 / P3.3
  USBInitLocal();
}

void loop() {
  // USB work is interrupt-driven; the main loop only maintains RPM counters.
  updateRpm();
}



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
 楼主| 发表于 3 小时前 | 显示全部楼层
游客请登录后查看回复内容
回复 支持 反对

使用道具 举报

发表于 22 分钟前 | 显示全部楼层
游客请登录后查看回复内容
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|数码之家-技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2026-8-3 13:27 , Processed in 0.218400 second(s), 7 queries , Gzip On, Redis On.

Powered by Discuz!

© MyDigit.Net Since 2006

快速回复 返回顶部 返回列表