蓝牙调光台灯(降压型)· CH582F 固件

基于官方 openwch/ch583 SDK 的 EVT/EXAM/BLE/Peripheral 例程改,用 MounRiver Studio 编译,核心板 USB 口 + WCHISPTool 下载。功能:蓝牙开关/调光 + 旋转编码器调光 + 按键(开关/延时关机) + PWM 调光输出。对应你们手绘原理图:调光 PWM 直接驱动 TLV62569 的 EN 脚(占空比=亮度,0%=关灯),电流由 LMV321 采样放大器恒定。

两点提醒(和固件无关,但影响成绩,最后定一下):
电流值:Rcs=68mΩ × 6倍增益 → 实际恒流到 1.47A,超了 1000mA 额定。改 Rcs=0.1Ω(保持6倍)即为 1.0A。只是换个电阻,不动程序。
PWM 打 EN 的软启动:TLV62569 EN 有 800µs 软启动,所以调光 PWM 频率要低(本固件设 200Hz);低亮度段会有点非线性/轻微频闪。要更顺滑可把这路 PWM 改去驱动一颗串在 LED 回路的 N-MOSFET(频率就能上到几 kHz)——程序完全不用改,只改接线。

1. 环境与下载

  1. MounRiver Studiomounriver.com/download,RISC-V 版)。
  2. 下载官方 SDK:github.com/openwch/ch583。用 MounRiver 打开 EVT/EXAM/BLE/Peripheral 工程(.wvproj)。先原样编译烧一遍确认工具链通,手机用 nRF Connect 能搜到 "Simple Peripheral" 再往下改。
  3. 芯片选 CH582F
  4. 下载:核心板 USB 连电脑按住 BOOT/下载键上电进 USB 引导,用 WCHISPTool(或开源 wchisp)下载 .hex/.bin。之后普通复位运行。

2. 引脚定义(对应原理图,按核心板丝印核对)

功能CH582F 脚接到
调光 PWM(→EN)PB22 (TMR3)TLV62569 EN(占空比=亮度)
编码器 APB10EC11 A
编码器 BPB11EC11 B
编码器按下 K0PB12EC11 按键
按键 K1 开/关PB14轻触开关→GND
按键 K2 延时关机PB15轻触开关→GND
按键 K3 备用PA4轻触开关→GND
调试串口PB7=TXD0printf 调试(可选)

若你们原理图上的 PWM/按键接的是别的脚,改下面 lamp.h 的宏即可,逻辑不变。全部输入用内部上拉,按键/编码器另一端接 GND。所有 GPIO 都是 3.3V,别接 5V。

3. lamp.h(引脚 + 接口)

// lamp.h —— 台灯硬件控制层
#ifndef __LAMP_H
#define __LAMP_H
#include "CH58x_common.h"

// ---- 引脚(按原理图/核心板丝印改)----
#define PWM_PORT_B          // 调光PWM在PB22(TMR3)
#define ENC_A_PIN     GPIO_Pin_10   // PB10 编码器A
#define ENC_B_PIN     GPIO_Pin_11   // PB11 编码器B
#define ENC_K_PIN     GPIO_Pin_12   // PB12 编码器按下
#define KEY1_PIN      GPIO_Pin_14   // PB14 开/关
#define KEY2_PIN      GPIO_Pin_15   // PB15 延时关机
// KEY3 在 PA4,单独处理

#define BRIGHT_MAX    255           // 亮度 0~255
#define BRIGHT_STEP   13            // 编码器每格±13(0~255满量程约20格)

void  Lamp_Init(void);              // 初始化PWM+GPIO+中断
void  Lamp_SetLevel(uint8_t level); // 设亮度0~255(0=关)
uint8_t Lamp_GetLevel(void);
void  Lamp_On(void);                // 恢复上次亮度
void  Lamp_Off(void);              // 关(PWM=0)
void  Lamp_Toggle(void);
void  Lamp_StartDelayOff(uint16_t sec); // 延时关机
void  Lamp_Poll(void);              // 主循环轮询(消抖/延时计时),放tmos周期任务
#endif

4. lamp.c(PWM 调光 + 编码器 + 按键 + 延时关机)

// lamp.c
#include "lamp.h"

static uint8_t  s_level     = 0;      // 当前亮度
static uint8_t  s_lastLevel = 128;    // 关灯前的亮度(用于开灯恢复)
static volatile uint8_t s_encA_last = 1;
static uint32_t s_delayTicks = 0;     // 延时关机剩余(10ms为单位)

// ---- PWM: TMR3 输出到 PB22,频率≈200Hz(EN软启动要求低频) ----
// Fsys=60MHz。周期计数 = 60e6/200 = 300000
#define PWM_CYCLE   300000UL
void PWM_Init(void)
{
    GPIOB_ModeCfg(GPIO_Pin_22, GPIO_ModeOut_PP_5mA);
    GPIOB_ResetBits(GPIO_Pin_22);
    // TMR3 PWM 模式,高电平有效
    TMR3_PWMInit(High_Level, PWM_Times_1);
    TMR3_PWMCycleCfg(PWM_CYCLE);           // 设周期→200Hz
    TMR3_PWMActDataWidth(0);               // 初始占空0=关
    TMR3_Disable();                        // 灯默认关
    // 注意:TMR3 PWM 输出脚映射到 PB22,若SDK需要remap请查 CH58x_timer/GPIOPinRemap
}

void Lamp_SetLevel(uint8_t level)
{
    if(level > BRIGHT_MAX) level = BRIGHT_MAX;
    s_level = level;
    if(level == 0){
        TMR3_PWMActDataWidth(0);
        TMR3_Disable();                    // 0%→关灯,EN一直低
    }else{
        uint32_t duty = (uint32_t)PWM_CYCLE * level / BRIGHT_MAX;
        TMR3_PWMActDataWidth(duty);
        TMR3_Enable();
        s_lastLevel = level;
    }
    // 亮度变了→通过BLE notify上报(见peripheral.c里的hook)
}
uint8_t Lamp_GetLevel(void){ return s_level; }
void Lamp_On(void){ Lamp_SetLevel(s_lastLevel ? s_lastLevel : 128); }
void Lamp_Off(void){ Lamp_SetLevel(0); }
void Lamp_Toggle(void){ if(s_level) Lamp_Off(); else Lamp_On(); }
void Lamp_StartDelayOff(uint16_t sec){ s_delayTicks = (uint32_t)sec * 100; } // 10ms单位

// ---- GPIO 初始化:编码器+按键,全部上拉输入+中断 ----
void Lamp_Init(void)
{
    PWM_Init();
    // 编码器 A/B/按下 (PB10/11/12)
    GPIOB_ModeCfg(ENC_A_PIN|ENC_B_PIN|ENC_K_PIN, GPIO_ModeIN_PU);
    GPIOB_ITModeCfg(ENC_A_PIN, GPIO_ITMode_FallEdge); // A相下降沿触发解码
    GPIOB_ITModeCfg(ENC_K_PIN, GPIO_ITMode_FallEdge);
    // 按键 K1/K2 (PB14/15) + K3(PA4)
    GPIOB_ModeCfg(KEY1_PIN|KEY2_PIN, GPIO_ModeIN_PU);
    GPIOA_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU);
    PFIC_EnableIRQ(GPIO_B_IRQn);
    s_encA_last = (GPIOB_ReadPortPin(ENC_A_PIN)?1:0);
}

// ---- 编码器中断:A相边沿时读B相判方向 ----
__INTERRUPT __HIGH_CODE
void GPIOB_IRQHandler(void)
{
    uint16_t flag = GPIOB_ReadITFlagPort();
    if(flag & ENC_A_PIN){
        uint8_t b = GPIOB_ReadPortPin(ENC_B_PIN)?1:0;
        // A下降沿:B=1顺时针(增),B=0逆时针(减)
        int16_t lv = s_level + (b ? BRIGHT_STEP : -BRIGHT_STEP);
        if(lv < 0) lv = 0; if(lv > BRIGHT_MAX) lv = BRIGHT_MAX;
        Lamp_SetLevel((uint8_t)lv);
    }
    if(flag & ENC_K_PIN){        // 编码器按下=开关
        Lamp_Toggle();
    }
    GPIOB_ClearITFlagBit(flag);
}

// ---- 主循环轮询:按键消抖 + 延时关机计时。放tmos周期任务里,每10ms调一次 ----
void Lamp_Poll(void)
{
    static uint8_t k1=0,k2=0;
    // K1 开/关 (低有效,简单消抖)
    if(!GPIOB_ReadPortPin(KEY1_PIN)){ if(++k1==2) Lamp_Toggle(); }
    else k1=0;
    // K2 延时关机:按一下设30分钟(可改)
    if(!GPIOB_ReadPortPin(KEY2_PIN)){ if(++k2==2) Lamp_StartDelayOff(30*60); }
    else k2=0;
    // 延时关机倒计时
    if(s_delayTicks){ if(--s_delayTicks==0) Lamp_Off(); }
}

5. BLE 自定义服务(改 SimpleProfile)

官方 Peripheral 例程自带 SimpleProfile(服务 UUID 0xFFF0)。直接复用它的特征值:

特征UUID属性本项目用途
CHAR10xFFF1读+写亮度 0~255(App 写=调光,读=当前值)
CHAR30xFFF3开关:写 1=开, 0=关
CHAR40xFFF4通知状态上报(亮度/开关变化时 notify 给 App)

peripheral.c 的写回调 simpleProfileChangeCB() 里,把收到的值转成台灯动作:

// peripheral.c —— 找到 simpleProfileChangeCB,改成:
static void simpleProfileChangeCB(uint8_t paramID, uint8_t *pValue, uint16_t len)
{
    switch(paramID){
    case SIMPLEPROFILE_CHAR1:        // App写亮度 0~255
        Lamp_SetLevel(pValue[0]);
        break;
    case SIMPLEPROFILE_CHAR3:        // App写开关
        if(pValue[0]) Lamp_On(); else Lamp_Off();
        break;
    default: break;
    }
    // 变化后把最新亮度回写CHAR1并notify(CHAR4),让App同步
    uint8_t lv = Lamp_GetLevel();
    SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR1, 1, &lv);
}
提示:例程里 SimpleProfile_AddService()simpleProfile_ReadAttrCB/WriteAttrCB 都是现成的,你只改 simpleProfileChangeCB 的分支即可。要 notify 状态就用例程里 CHAR4 的 simpleProfile_Notify 那套(Peripheral 例程有演示定时 notify,照抄改成"亮度变化时 notify")。GATT 服务名/UUID 想自定义可改 gattprofile.c 里的 UUID 表。

6. 整合到 Peripheral 例程

// main.c 或 peripheral.c 的 Peripheral_Init() 末尾加:
Lamp_Init();

// 用 tmos 周期任务跑 Lamp_Poll() —— 在 Peripheral_ProcessEvent 里加一个事件:
#define LAMP_POLL_EVT   (1 << 4)   // 选个没用的事件位

// Peripheral_Init() 里启动周期任务:
tmos_start_task(Peripheral_TaskID, LAMP_POLL_EVT, MS1_TO_SYSTEM_TICK(10)); // 10ms

// Peripheral_ProcessEvent() 里处理:
if(events & LAMP_POLL_EVT){
    Lamp_Poll();
    tmos_start_task(Peripheral_TaskID, LAMP_POLL_EVT, MS1_TO_SYSTEM_TICK(10));
    return (events ^ LAMP_POLL_EVT);
}

别忘了 #include "lamp.h",并把 lamp.c 加进工程源文件。TMR3/GPIO 相关头(CH58x_timer.hCH58x_gpio.h)SDK 已 include 在 CH58x_common.h

7. 验证顺序(建议)

  1. 先只跑 Lamp_Init+Lamp_SetLevel,串口/按键把亮度从 0→100,示波器看 PB22 PWM 占空比、万用表看 LED 电流(量 Rcs 压降 ÷ Rcs)。
  2. 加编码器:转动看亮度增减、按下开关。
  3. 加 BLE:nRF Connect 连上,写 CHAR1(0~255) 看调光、写 CHAR3 看开关。
  4. 再接自己做的 App(安卓网页蓝牙,另发)。
  5. 延时关机、掉电亮度记忆(存 DataFlash) 最后加。
API 名字提示:不同 SDK 版本个别函数名可能略有出入(如 TMR3_PWMInit / TMR3_PWMCycleCfg / TMR3_PWMActDataWidth、GPIO 中断 GPIOB_ITModeCfg/ReadITFlagPort/ClearITFlagBit)。以你们下载的 SDK 里 StdPeriphDriver/inc/CH58x_timer.hCH58x_gpio.h 的实际声明为准,对不上就按头文件里的名字调。TMR3 PWM 输出到 PB22 若需要引脚重映射,查 GPIOPinRemap()

基于 openwch/ch583 · Peripheral 例程 · MounRiver Studio 编译 · WCHISPTool USB 下载 · 手机 App 另发