diff --git a/.gitignore b/.gitignore index 5e00332..350e6ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.cache/ +.spec-workflow/ +.vscode/ *.a *.so *.bin diff --git a/LPT262_PWM_user_guide.md b/LPT262_PWM_user_guide.md new file mode 100644 index 0000000..85dbb42 --- /dev/null +++ b/LPT262_PWM_user_guide.md @@ -0,0 +1,1056 @@ +# WS63 PWM 技术使用文档 + +## 文档概述 + +本文档基于WS63平台的PWM驱动实现,为不同经验的开发人员提供PWM(脉宽调制)功能的配置与控制指导。 + +**适用人群**: +- 🌱 **新手开发者**:只熟悉0-255亮度控制,希望快速上手PWM +- 🛠️ **进阶开发者**:需要深入了解PWM参数配置和高级功能 +- 🔍 **系统调试者**:需要问题诊断和性能优化 + +**平台基础参数**: +- MCU平台:WS63 (HiSilicon) +- 系统时钟:80 MHz +- 计数器位宽:32-bit(u32) +- PWM版本:CONFIG_PWM_USING_V151 +- 支持特性:多通道、PWM组、相位控制、运行时更新 + +## 文档导航 + +### 🌱 新手必读(建议阅读顺序) +1. **章节1 - 快速入门**: 5分钟了解PWM和快速上手 +2. **章节5 - 参数详解**: 从0-255到PWM参数的转换和理解 +3. **章节11 - 错误处理**: 常见问题解决 + +### 🛠️ 进阶内容(可选阅读) +- **章节2-4**: PWM系统架构、API和初始化流程 +- **章节6-10**: 高级功能(频率配置、相位控制、PWM组等) +- **章节12**: 完整流程图和技术参考 + +--- + +## 1. 快速入门(新手必读) + +### 1.1 PWM是什么? + +PWM就像一个非常快的开关,通过控制"开"和"关"的时间比例来调节LED的亮度、电机的速度等。 + +**简单理解**: +- 如果一直开着 = 100%亮度 +- 如果一直关着 = 0%亮度 +- 如果一半时间开、一半时间关 = 50%亮度 + +### 1.2 你之前的使用方式 vs 现在的PWM方式 + +**你之前的方式**: +```c +// 简单直观,但是功能有限 +set_led_brightness(128); // 0-255范围 +``` + +**现在的PWM方式**: +```c +// 功能强大,但是参数复杂 +pwm_config_t cfg = { + .high_time = 40000, // 高电平时间(计数) + .low_time = 40000, // 低电平时间(计数) + .offset_time = 0, // 相位偏移 + .repeat = true, // 是否重复 + .cycles = 0 // 循环次数 +}; +``` + +**不用担心!**本文档提供了简化函数,让你像以前一样使用0-255的数值。 + +### 1.3 5分钟快速上手 + +```c +// 第1步:初始化PWM系统 +uapi_pwm_init(); + +// 第2步:使用简化函数设置亮度(就像以前一样) +set_pwm_brightness_simple(0, 128); // 通道0,50%亮度 + +// 第3步:启动PWM输出 +uapi_pwm_start(0); + +// 完成!LED就会以50%亮度点亮 +``` + +**简化函数**(复制到你的代码里): +```c +// 这个函数让PWM像以前一样简单 +errcode_t set_pwm_brightness_simple(uint8_t channel, uint8_t brightness_0_255) +{ + uint32_t frequency = 1000; // 固定1000Hz,适合LED调光 + float duty_percent = (brightness_0_255 / 255.0f) * 100.0f; + uint32_t period_cnt = 80000000 / frequency; // 80000 + uint32_t high_time = (uint32_t)(period_cnt * duty_percent / 100.0f); + uint32_t low_time = period_cnt - high_time; + + pwm_config_t cfg = { + .high_time = high_time, + .low_time = low_time, + .offset_time = 0, + .repeat = true, + .cycles = 0 + }; + + return uapi_pwm_open(channel, &cfg); +} +``` + +--- + +## 2. PWM系统架构(进阶内容) + +### 2.1 驱动层次结构 + +```mermaid +graph TB + A[应用层] --> B[PWM驱动接口 pwm.h] + B --> C[HAL层 hal_pwm_v151.h] + C --> D[寄存器操作层 hal_pwm_v151_regs_op.h] + D --> E[硬件寄存器层] + + A --> F[GPIO配置] + F --> G[PWM输出引脚] + + A --> I[PWM组控制] + I --> J[多通道同步控制] + + style A fill:#e1f5fe + style B fill:#f3e5f5 + style C fill:#e8f5e8 + style D fill:#fff3e0 +``` + +### 2.2 PWM工作原理 + +```mermaid +graph LR + A[频率配置] --> B[周期计算] + B --> C[占空比设置] + C --> D[high_time/low_time计算] + D --> E[PWM波形输出] + + F[相位控制] --> E + G[PWM组同步] --> E + + style B fill:#e3f2fd + style D fill:#f1f8e9 + style E fill:#fff3e0 +``` + +--- + +## 3. PWM核心数据结构与API(进阶内容) + +### 3.1 PWM配置结构体 + +```c +// 源码位置:include/driver/pwm.h +typedef struct pwm_config { + uint32_t low_time; // 低电平持续的计数数 + uint32_t high_time; // 高电平持续的计数数 + uint32_t offset_time; // PWM相位偏移时间 + uint16_t cycles; // PWM重复周期,范围:0~32767 + bool repeat; // 连续输出标志 +} pwm_config_t; +``` + +### 3.2 关键API函数 + +```c +// PWM系统初始化和控制 +errcode_t uapi_pwm_init(void); // 初始化PWM系统 +errcode_t uapi_pwm_open(uint8_t channel, const pwm_config_t *cfg); // 打开PWM通道 +errcode_t uapi_pwm_start(uint8_t channel); // 启动单个PWM通道 +errcode_t uapi_pwm_close(uint8_t channel); // 关闭PWM通道 +uint32_t uapi_pwm_get_frequency(uint8_t channel); // 获取PWM工作频率 + +// PWM组控制 +errcode_t uapi_pwm_set_group(uint8_t group, const uint8_t *channel_set, uint32_t channel_set_len); +errcode_t uapi_pwm_start_group(uint8_t group); // 同步启动PWM组 +errcode_t uapi_pwm_stop_group(uint8_t group); // 同步停止PWM组 +errcode_t uapi_pwm_clear_group(uint8_t group); // 清理PWM组 + +// 运行时占空比更新 +errcode_t uapi_pwm_update_duty_ratio(uint8_t channel, uint32_t low_time, uint32_t high_time); + +// 中断处理 +errcode_t uapi_pwm_register_interrupt(uint8_t channel, pwm_callback_t callback); +errcode_t uapi_pwm_unregister_interrupt(uint8_t channel); +``` + +--- + +## 4. PWM初始化流程(进阶内容) + +### 4.1 初始化时序图 + +```mermaid +sequenceDiagram + participant App as 应用层 + participant PWM as PWM驱动 + participant HAL as HAL层 + participant GPIO as GPIO系统 + + App->>PWM: uapi_pwm_init() + PWM->>HAL: hal_pwm_v151初始化 + HAL-->>PWM: 返回success + + App->>PWM: uapi_pwm_get_frequency(channel) + PWM-->>App: 返回基础频率(80MHz) + + App->>App: 计算周期计数值 + + App->>GPIO: 配置GPIO引脚为PWM功能 + GPIO-->>App: GPIO配置完成 + + App->>PWM: uapi_pwm_open(channel, config) + PWM-->>App: PWM通道配置完成 + + App->>PWM: uapi_pwm_start(channel) + PWM-->>App: PWM开始输出 + + Note over App: PWM波形输出 +``` + +### 4.2 初始化代码实现 + +```c +// PWM初始化示例 +void pwm_init_example(uint8_t gpio_pin, uint32_t target_frequency) +{ + pwm_config_t cfg = {0}; + + // 1. 初始化PWM驱动 + errcode_t ret = uapi_pwm_init(); + if (ret != ERRCODE_SUCC) { + printf("PWM初始化失败: %d\n", ret); + return; + } + + // 2. 获取PWM基础频率 (通常为80MHz) + uint8_t channel = gpio_pin % 8; // GPIO转PWM通道 + uint32_t base_frequency = uapi_pwm_get_frequency(channel); + + // 3. 计算PWM周期计数值 + uint32_t period_cnt = base_frequency / target_frequency; + + // 4. 配置PWM参数(初始50%占空比) + cfg.high_time = period_cnt / 2; + cfg.low_time = period_cnt - cfg.high_time; + cfg.offset_time = 0; // 无相位偏移 + cfg.repeat = true; // 连续输出 + cfg.cycles = 0; // 无限循环 + + // 5. 打开PWM通道 + ret = uapi_pwm_open(channel, &cfg); + if (ret != ERRCODE_SUCC) { + printf("PWM通道打开失败: %d\n", ret); + return; + } + + // 6. 启动PWM输出 + ret = uapi_pwm_start(channel); + if (ret != ERRCODE_SUCC) { + printf("PWM启动失败: %d\n", ret); + return; + } + + printf("PWM初始化成功: 频率=%dHz, 周期=%d计数\n", + target_frequency, period_cnt); +} +``` + +--- + +## 5. PWM参数详解(小白入门篇) + +### 5.1 什么是PWM? + +PWM就像开关灯一样,快速地开关来控制亮度。想象你手里有个开关,如果一直开着,灯就是100%亮;如果一直关着,灯就是0%亮。但如果你快速地开开关关,开的时间长一点,灯就亮一点;关的时间长一点,灯就暗一点。 + +**关键概念**: +- **频率**:一秒钟开关多少次(就像你手开关灯的频率) +- **占空比**:一个开关周期中,"开"的时间占多少比例 +- **周期**:完成一次"开-关"动作需要的时间 + +### 5.2 从0-255到实际参数的转换 + +你熟悉的0-255其实就是占空比的另一种表示方法: + +```c +// 你熟悉的方式:0-255的数值 +uint8_t brightness = 128; // 相当于50%亮度 + +// 转换成PWM需要的参数 +float duty_percent = (brightness / 255.0f) * 100.0f; // 转换成百分比 + +// 例子: +// brightness = 0 → duty_percent = 0% (完全不亮) +// brightness = 64 → duty_percent = 25% (1/4亮度) +// brightness = 128 → duty_percent = 50% (一半亮度) +// brightness = 192 → duty_percent = 75% (3/4亮度) +// brightness = 255 → duty_percent = 100% (最亮) +``` + +### 5.3 参数计算原理(用通俗的话解释) + +```c +// 第1步:确定系统时钟(这是硬件固定的,不用改) +uint32_t system_clock = 80000000; // 80MHz = 每秒8000万次计数 + +// 第2步:设定PWM频率(就是每秒开关多少次) +uint32_t pwm_frequency = 1000; // 1000Hz = 每秒开关1000次 + +// 第3步:计算一个开关周期需要多少个时钟计数 +// 打个比方:如果系统每秒数8000万下,你要让它每秒开关1000次 +// 那么每次开关就需要:8000万 ÷ 1000 = 80000个计数 +uint32_t period_count = system_clock / pwm_frequency; + +// 第4步:根据亮度(占空比)计算开和关的时间 +// 如果要60%亮度,那么80000个计数中,48000个用来"开",32000个用来"关" +float brightness_percent = 60.0; // 60%亮度 +uint32_t on_time = (uint32_t)(period_count * brightness_percent / 100.0); +uint32_t off_time = period_count - on_time; +``` + +### 5.4 简单的参数设置函数(推荐使用) + +```c +// 简化版本:像以前一样用0-255设置亮度 +errcode_t set_pwm_brightness_simple(uint8_t channel, uint8_t brightness_0_255) +{ + // 固定使用1000Hz频率(适合大多数LED调光应用) + uint32_t frequency = 1000; + + // 将0-255转换为百分比 + float duty_percent = (brightness_0_255 / 255.0f) * 100.0f; + + // 计算周期计数(80MHz ÷ 1000Hz = 80000) + uint32_t period_cnt = 80000000 / frequency; + + // 计算高低电平时间 + uint32_t high_time = (uint32_t)(period_cnt * duty_percent / 100.0f); + uint32_t low_time = period_cnt - high_time; + + // 配置PWM + pwm_config_t cfg = { + .high_time = high_time, // "开"的时间(计数值) + .low_time = low_time, // "关"的时间(计数值) + .offset_time = 0, // 不需要相位偏移 + .repeat = true, // 持续输出 + .cycles = 0 // 无限循环 + }; + + return uapi_pwm_open(channel, &cfg); +} + +// 使用示例: +// set_pwm_brightness_simple(0, 0); // 通道0,0%亮度(关闭) +// set_pwm_brightness_simple(0, 128); // 通道0,50%亮度 +// set_pwm_brightness_simple(0, 255); // 通道0,100%亮度(最亮) +``` + +### 5.5 高级参数详解(可选阅读) + +```c +如果你想深入了解PWM的各个参数,这里详细解释每个参数的含义: + +**pwm_config_t结构体中的参数:** + +1. **high_time(高电平时间)**: + - 就是"开关"中"开"的时间,用时钟计数表示 + - 例如:如果high_time = 40000,系统时钟80MHz,那么"开"的时间 = 40000 ÷ 80000000 = 0.5ms + +2. **low_time(低电平时间)**: + - 就是"开关"中"关"的时间,用时钟计数表示 + - 例如:如果low_time = 40000,那么"关"的时间也是0.5ms + - high_time + low_time = 一个完整周期的时间 + +3. **offset_time(相位偏移)**: + - 用来控制多个PWM通道之间的时间差 + - 大多数情况下设为0即可 + - 高级应用:比如多色LED,可以让红绿蓝三色错开时间启动 + +4. **cycles(重复次数)**: + - 设为0表示无限重复(常用) + - 设为具体数字表示输出多少个周期后停止 + +5. **repeat(连续输出标志)**: + - true:持续输出PWM波形(常用) + - false:只输出指定次数后停止 + +```c +// 标准的占空比设置函数 +errcode_t set_pwm_duty_cycle(uint8_t channel, float duty_cycle_percent) +{ + // 参数检查 + if (duty_cycle_percent < 0.0f) duty_cycle_percent = 0.0f; + if (duty_cycle_percent > 100.0f) duty_cycle_percent = 100.0f; + + // 使用固定的1000Hz频率 + uint32_t period_cnt = 80000; // 80MHz ÷ 1000Hz = 80000 + + // 计算高低电平时间 + uint32_t high_time = (uint32_t)(period_cnt * duty_cycle_percent / 100.0f); + uint32_t low_time = period_cnt - high_time; + + // 配置PWM + pwm_config_t cfg = { + .high_time = high_time, + .low_time = low_time, + .offset_time = 0, // 无相位偏移 + .repeat = true, // 持续输出 + .cycles = 0 // 无限循环 + }; + + return uapi_pwm_open(channel, &cfg); +} + +// 运行时更新占空比(V151版本) +errcode_t update_pwm_duty_runtime(uint8_t channel, float duty_cycle_percent) +{ + // V151需要重新配置整个通道来更新占空比 + return set_pwm_duty_cycle(channel, duty_cycle_percent); +} +``` + +``` + +### 5.6 完整的PWM使用示例(推荐新手使用) + +```c +// 完整的PWM初始化和使用流程 +void pwm_simple_example(void) +{ + // 第1步:初始化PWM系统 + if (uapi_pwm_init() != ERRCODE_SUCC) { + printf("PWM初始化失败!\n"); + return; + } + + // 第2步:配置GPIO引脚为PWM功能(这里假设已经配置好) + + // 第3步:设置PWM通道0的亮度 + uint8_t channel = 0; // 使用PWM通道0 + + // 设置不同亮度测试 + printf("开始PWM亮度测试...\n"); + + // 0% 亮度(关闭) + set_pwm_brightness_simple(channel, 0); + uapi_pwm_start(channel); + printf("当前亮度: 0%% (关闭)\n"); + // delay_ms(1000); // 延时1秒 + + // 25% 亮度 + set_pwm_brightness_simple(channel, 64); + printf("当前亮度: 25%%\n"); + // delay_ms(1000); + + // 50% 亮度 + set_pwm_brightness_simple(channel, 128); + printf("当前亮度: 50%%\n"); + // delay_ms(1000); + + // 75% 亮度 + set_pwm_brightness_simple(channel, 192); + printf("当前亮度: 75%%\n"); + // delay_ms(1000); + + // 100% 亮度(最亮) + set_pwm_brightness_simple(channel, 255); + printf("当前亮度: 100%% (最亮)\n"); + + printf("PWM测试完成!\n"); +} + +// 渐变效果示例 +void pwm_fade_example(void) +{ + uint8_t channel = 0; + + // 从暗到亮的渐变 + printf("渐亮效果...\n"); + for (int brightness = 0; brightness <= 255; brightness += 5) { + set_pwm_brightness_simple(channel, brightness); + // delay_ms(50); // 每步延时50ms,实现平滑渐变 + } + + // 从亮到暗的渐变 + printf("渐暗效果...\n"); + for (int brightness = 255; brightness >= 0; brightness -= 5) { + set_pwm_brightness_simple(channel, brightness); + // delay_ms(50); + } + + printf("渐变效果完成!\n"); +} +``` + +--- + +## 6. PWM频率与周期配置(进阶内容) + +### 6.1 常用频率配置表 + +**基于80MHz系统时钟的配置参数**: + +| 目标频率 | 周期计数值 | 周期时间 | 最小占空比步进 | 应用场景 | 说明 | +|----------|------------|----------|----------------|----------|------| +| 100 Hz | 800,000 | 10.0 ms | 0.000125% | 低频控制 | 人眼可见闪烁 | +| 1 kHz | 80,000 | 1.0 ms | 0.00125% | LED调光 | **推荐频率** - 无闪烁,适合照明 | +| 10 kHz | 8,000 | 100 μs | 0.0125% | 音频PWM | 超出听觉范围 | +| 20 kHz | 4,000 | 50 μs | 0.025% | 电机控制 | 无噪音,适合电机驱动 | +| 100 kHz | 800 | 10 μs | 0.125% | 高频开关 | 开关电源应用 | +| 1 MHz | 80 | 1 μs | 1.25% | 快速响应 | 高速信号生成 | + +### 6.2 频率配置示例 + +```c +// 不同应用场景的频率配置 +typedef struct { + uint32_t frequency; + uint32_t period_cnt; + const char* application; +} pwm_freq_preset_t; + +const pwm_freq_preset_t pwm_presets[] = { + {100, 800000, "低频控制信号(有闪烁)"}, + {1000, 80000, "LED调光(推荐使用)"}, + {20000, 4000, "电机控制(无噪音)"}, + {100000, 800, "高频开关电源"}, + {1000000, 80, "高速信号生成"} +}; + +// 快速配置函数 +errcode_t pwm_quick_setup(uint8_t channel, uint8_t preset_index, float duty_percent) +{ + if (preset_index >= sizeof(pwm_presets)/sizeof(pwm_presets[0])) { + return ERRCODE_INVALID_PARAM; + } + + const pwm_freq_preset_t* preset = &pwm_presets[preset_index]; + + uint32_t high_time = (uint32_t)(preset->period_cnt * duty_percent / 100.0f); + uint32_t low_time = preset->period_cnt - high_time; + + pwm_config_t cfg = { + .high_time = high_time, + .low_time = low_time, + .repeat = true + }; + + printf("配置PWM: %s\n", preset->application); + printf(" - 频率: %dHz\n", preset->frequency); + printf(" - 占空比: %.1f%%\n", duty_percent); + printf(" - 周期时间: %.2fms\n", 1000.0f / preset->frequency); + + return uapi_pwm_open(channel, &cfg); +} +``` + +--- + +## 7. PWM波形与相位控制(进阶内容) + +### 7.1 PWM波形分析 + +``` +基本PWM波形 (以1kHz频率,60%占空比为例): + +周期 = 1ms (80000计数) +高电平时间 = 0.6ms (48000计数) +低电平时间 = 0.4ms (32000计数) + + ████████████████████████████████ ████████████████████████████████ +____ ________ ____ + |←─────── 48000 cnt ────────→| |←─────── 48000 cnt ────────→| + |←─────────────── 80000 cnt (1ms) ──────────→|←──────────── 80000 cnt ──────→| +``` + +### 7.2 相位偏移控制 + +```c +// 相位偏移示例:两个PWM通道错相输出 +void setup_phase_shifted_pwm(uint8_t ch1, uint8_t ch2, uint32_t frequency, float duty_percent) +{ + uint32_t period_cnt = 80000000 / frequency; + uint32_t high_time = (uint32_t)(period_cnt * duty_percent / 100.0f); + uint32_t low_time = period_cnt - high_time; + + // 通道1:正常输出 + pwm_config_t cfg1 = { + .high_time = high_time, + .low_time = low_time, + .offset_time = 0, // 无偏移 + .repeat = true + }; + + // 通道2:相位偏移180度 + pwm_config_t cfg2 = { + .high_time = high_time, + .low_time = low_time, + .offset_time = period_cnt / 2, // 半周期偏移 + .repeat = true + }; + + uapi_pwm_open(ch1, &cfg1); + uapi_pwm_open(ch2, &cfg2); + uapi_pwm_start(ch1); + uapi_pwm_start(ch2); +} +``` + +### 7.3 互补输出波形 + +``` +互补PWM输出 (避免直通,添加死区时间): + +Channel A: ████████ ████████ ████████ +Channel B: ████████ ████████ ████████ + |dt| |dt| |dt| |dt| |dt| |dt| + 死区 死区 死区 死区 死区 死区 + +死区时间计算: +dead_time_ns = 500; // 500ns死区 +dead_time_cnt = (500 * 80000000) / 1000000000 = 40计数 +``` + +--- + +## 8. PWM组同步控制(进阶内容) + +### 8.1 PWM组管理 + +```c +// PWM组配置和同步控制 +#define PWM_GROUP_0 0 +#define MAX_CHANNELS_PER_GROUP 8 + +errcode_t setup_pwm_group(uint8_t group_id, uint8_t* channels, uint8_t channel_count) +{ + errcode_t ret; + + // 1. 清除现有组配置 + ret = uapi_pwm_clear_group(group_id); + if (ret != ERRCODE_SUCC) return ret; + + // 2. 设置新的PWM组 + ret = uapi_pwm_set_group(group_id, channels, channel_count); + if (ret != ERRCODE_SUCC) return ret; + + printf("PWM组%d配置完成,包含%d个通道\n", group_id, channel_count); + + return ERRCODE_SUCC; +} + +// 同步启动PWM组 +errcode_t start_pwm_group_sync(uint8_t group_id) +{ + return uapi_pwm_start_group(group_id); +} + +// 同步停止PWM组 +errcode_t stop_pwm_group_sync(uint8_t group_id) +{ + return uapi_pwm_stop_group(group_id); +} +``` + +### 8.2 组同步流程 + +```mermaid +sequenceDiagram + participant App as 应用层 + participant Group as PWM组控制 + participant CH0 as 通道0 + participant CH1 as 通道1 + participant CH2 as 通道2 + + App->>Group: uapi_pwm_set_group(0, [0,1,2]) + Group->>CH0: 加入组0 + Group->>CH1: 加入组0 + Group->>CH2: 加入组0 + + App->>CH0: uapi_pwm_open(0, config0) + App->>CH1: uapi_pwm_open(1, config1) + App->>CH2: uapi_pwm_open(2, config2) + + App->>Group: uapi_pwm_start_group(0) + Group->>CH0: 同步启动 + Group->>CH1: 同步启动 + Group->>CH2: 同步启动 + + Note over CH0,CH2: 所有通道同时开始PWM输出 +``` + +--- + +## 9. 动态PWM调整(进阶内容) + +### 9.1 运行时参数更新 + +```c +// 动态调整PWM参数 +errcode_t dynamic_pwm_update(uint8_t channel, uint32_t new_frequency, float new_duty_percent) +{ + // 计算新参数 + uint32_t period_cnt = 80000000 / new_frequency; + uint32_t high_time = (uint32_t)(period_cnt * new_duty_percent / 100.0f); + uint32_t low_time = period_cnt - high_time; + + // 重新配置PWM + pwm_config_t cfg = { + .high_time = high_time, + .low_time = low_time, + .repeat = true + }; + + errcode_t ret = uapi_pwm_open(channel, &cfg); + if (ret != ERRCODE_SUCC) { + return ret; + } + + return uapi_pwm_start(channel); +} + +// 渐变调节函数 +typedef struct { + uint8_t channel; + float current_duty; + float target_duty; + float step_size; + uint32_t interval_ms; +} pwm_fade_t; + +void pwm_fade_step(pwm_fade_t* fade) +{ + if (abs(fade->current_duty - fade->target_duty) < fade->step_size) { + fade->current_duty = fade->target_duty; + return; // 渐变完成 + } + + if (fade->current_duty < fade->target_duty) { + fade->current_duty += fade->step_size; + } else { + fade->current_duty -= fade->step_size; + } + + set_pwm_duty_cycle(fade->channel, fade->current_duty); +} +``` + +### 9.2 PWM调制模式 + +```c +// PWM调制模式示例 +typedef enum { + PWM_MODE_CONSTANT, // 恒定输出 + PWM_MODE_BREATHING, // 呼吸模式 + PWM_MODE_BLINKING, // 闪烁模式 + PWM_MODE_SAWTOOTH, // 锯齿波模式 +} pwm_modulation_mode_t; + +typedef struct { + uint8_t channel; + pwm_modulation_mode_t mode; + uint32_t period_ms; // 调制周期 + float min_duty; // 最小占空比 + float max_duty; // 最大占空比 + uint32_t step_count; // 步数 +} pwm_modulation_t; + +void pwm_modulation_update(pwm_modulation_t* mod, uint32_t current_step) +{ + float duty = mod->min_duty; + + switch (mod->mode) { + case PWM_MODE_BREATHING: + // 正弦波呼吸 + duty = mod->min_duty + (mod->max_duty - mod->min_duty) * + (1.0f + sinf(2 * 3.14159f * current_step / mod->step_count)) / 2.0f; + break; + + case PWM_MODE_SAWTOOTH: + // 锯齿波 + duty = mod->min_duty + (mod->max_duty - mod->min_duty) * + current_step / mod->step_count; + break; + + case PWM_MODE_BLINKING: + // 方波闪烁 + duty = (current_step < mod->step_count/2) ? mod->max_duty : mod->min_duty; + break; + + default: + duty = mod->max_duty; + break; + } + + set_pwm_duty_cycle(mod->channel, duty); +} +``` + +--- + +## 10. PWM应用示例(进阶内容) + +### 10.1 电机PWM控制 + +```c +// 电机PWM控制示例 +void motor_pwm_control(uint8_t motor_channel, float speed_percent, bool direction) +{ + // 电机PWM频率通常选择20kHz以避免听到噪音 + uint32_t motor_frequency = 20000; + + // 限制速度范围 + if (speed_percent < 0) speed_percent = 0; + if (speed_percent > 100) speed_percent = 100; + + // 计算周期和占空比 + uint32_t period_cnt = 80000000 / motor_frequency; + uint32_t high_time = (uint32_t)(period_cnt * speed_percent / 100.0f); + uint32_t low_time = period_cnt - high_time; + + pwm_config_t cfg = { + .high_time = high_time, + .low_time = low_time, + .repeat = true + }; + + uapi_pwm_open(motor_channel, &cfg); + uapi_pwm_start(motor_channel); + + // 方向控制需要额外的GPIO控制 + // gpio_set_direction_pin(direction); + + printf("电机PWM: 速度=%.1f%%, 方向=%s\n", + speed_percent, direction ? "正转" : "反转"); +} +``` + +### 10.2 伺服电机控制 + +```c +// 伺服电机PWM控制 (50Hz, 1-2ms脉宽) +void servo_pwm_control(uint8_t servo_channel, float angle_degrees) +{ + // 伺服PWM频率固定为50Hz (20ms周期) + uint32_t servo_frequency = 50; + uint32_t period_cnt = 80000000 / servo_frequency; // 1,600,000计数 (20ms) + + // 角度范围限制 (-90° 到 +90°) + if (angle_degrees < -90) angle_degrees = -90; + if (angle_degrees > 90) angle_degrees = 90; + + // 脉宽计算:1ms(-90°) 到 2ms(+90°) + float pulse_width_ms = 1.5f + (angle_degrees / 90.0f) * 0.5f; + uint32_t high_time = (uint32_t)(80000 * pulse_width_ms); // 80000计数/ms + uint32_t low_time = period_cnt - high_time; + + pwm_config_t cfg = { + .high_time = high_time, + .low_time = low_time, + .repeat = true + }; + + uapi_pwm_open(servo_channel, &cfg); + uapi_pwm_start(servo_channel); + + printf("伺服控制: 角度=%.1f°, 脉宽=%.2fms\n", angle_degrees, pulse_width_ms); +} +``` + +### 10.3 音频PWM输出 + +```c +// 简单音频PWM输出 +void audio_pwm_tone(uint8_t audio_channel, uint32_t tone_frequency, uint32_t duration_ms) +{ + // 音频PWM载波频率选择高于20kHz + uint32_t carrier_frequency = 44100; // 44.1kHz载波 + + uint32_t period_cnt = 80000000 / carrier_frequency; + + // 音频调制(简单50%占空比) + uint32_t high_time = period_cnt / 2; + uint32_t low_time = period_cnt - high_time; + + pwm_config_t cfg = { + .high_time = high_time, + .low_time = low_time, + .repeat = true + }; + + uapi_pwm_open(audio_channel, &cfg); + uapi_pwm_start(audio_channel); + + // 持续时间控制 + // delay_ms(duration_ms); + // uapi_pwm_stop(audio_channel); + + printf("音频输出: 载波=%dHz, 持续=%dms\n", carrier_frequency, duration_ms); +} +``` + +--- + +## 11. 错误处理与调试 + +### 11.1 常见问题诊断 + +| 问题现象 | 可能原因 | 解决方案 | 验证方法 | +|----------|----------|----------|----------| +| PWM无输出 | 通道未启动 | 调用uapi_pwm_start() | 示波器检查引脚 | +| 频率不正确 | 计算错误 | 检查period_cnt计算 | 频率计测量 | +| 占空比异常 | high_time设置错误 | 检查占空比计算公式 | 占空比测量 | +| 多通道不同步 | 未使用PWM组 | 使用uapi_pwm_start_group() | 示波器多通道观察 | +| PWM抖动 | 中断影响 | 关闭不必要中断 | 波形稳定性分析 | + +### 11.2 调试代码示例 + +```c +// PWM状态监控和调试 +void pwm_debug_info(uint8_t channel) +{ + uint32_t frequency = uapi_pwm_get_frequency(channel); + printf("=== PWM通道%d调试信息 ===\n", channel); + printf("基础频率: %d Hz\n", frequency); + printf("系统时钟: 80MHz\n"); + + // 假设当前配置 + uint32_t target_freq = 1000; + uint32_t period_cnt = 80000000 / target_freq; + float duty_cycle = 60.0f; + uint32_t high_time = (uint32_t)(period_cnt * duty_cycle / 100.0f); + uint32_t low_time = period_cnt - high_time; + + printf("目标频率: %d Hz\n", target_freq); + printf("周期计数: %d\n", period_cnt); + printf("高电平计数: %d (%.2fms)\n", high_time, (float)high_time/80000); + printf("低电平计数: %d (%.2fms)\n", low_time, (float)low_time/80000); + printf("占空比: %.1f%%\n", duty_cycle); + printf("实际周期: %.2fms\n", (float)period_cnt/80000); +} + +// PWM波形验证 +bool verify_pwm_output(uint8_t channel, uint32_t expected_freq, float expected_duty) +{ + // 这里需要硬件支持或外部测量设备 + // 返回验证结果 + printf("验证PWM通道%d: 期望频率=%dHz, 期望占空比=%.1f%%\n", + channel, expected_freq, expected_duty); + return true; // 简化示例 +} +``` + +### 11.3 性能优化建议 + +```c +// PWM性能优化建议 +void pwm_performance_tips(void) +{ + // 1. 使用PWM组进行批量操作 + uint8_t channels[] = {0, 1, 2, 3}; + uapi_pwm_set_group(0, channels, 4); + uapi_pwm_start_group(0); // 比逐个启动效率高 + + // 2. 避免频繁的参数更改 + // 缓存计算结果,批量更新 + + // 3. 合理选择PWM频率 + // 过高频率会增加功耗,过低可能产生可见闪烁 + + // 4. 使用合适的占空比精度 + // 不需要过高精度时使用整数运算 + + // 5. 中断优化 + // 只在必要时注册PWM中断回调 +} +``` + +--- + +## 12. 完整PWM控制流程 + +### 12.1 PWM完整使用流程 + +```mermaid +flowchart TD + A[系统启动] --> B[uapi_pwm_init 初始化] + B --> C[GPIO配置为PWM功能] + C --> D[计算PWM参数] + + D --> E{单通道?} + E -->|是| F[uapi_pwm_open 配置通道] + E -->|否| G[uapi_pwm_set_group 配置组] + + F --> H[uapi_pwm_start 启动单通道] + G --> I[批量配置各通道] + I --> J[uapi_pwm_start_group 同步启动] + + H --> K[PWM波形输出] + J --> K + + K --> L{需要调整?} + L -->|是| M[更新参数] + L -->|否| N[持续运行] + + M --> O[uapi_pwm_open 重新配置] + O --> K + + N --> P{停止?} + P -->|否| L + P -->|是| Q[uapi_pwm_close 关闭] + Q --> R[结束] + + style B fill:#e3f2fd + style D fill:#f1f8e9 + style K fill:#fff3e0 + style O fill:#e8f5e8 +``` + +--- + +## 附录:技术参考 + +### A.1 WS63 PWM技术规格 + +| 规格项 | 参数值 | 说明 | +|--------|--------|------| +| 系统时钟 | 80 MHz | PWM计数基准 | +| 计数器位宽 | 32-bit | 支持超长周期 | +| PWM通道数 | 8 | 每组最多8通道 | +| PWM组数 | 多组 | 支持同步控制 | +| 最高频率 | 40 MHz | 理论最大值 | +| 最低频率 | 1.2 Hz | 32位计数器极限 | +| 占空比精度 | 0.0000012% | 32位分辨率 | +| 相位控制 | 支持 | offset_time参数 | + +### A.2 应用场景频率推荐 + +| 应用场景 | 推荐频率 | 原因 | +|----------|----------|------| +| LED调光 | 1-20 kHz | 避免频闪,超出人眼感知 | +| 电机控制 | 10-50 kHz | 超出听觉范围,降低噪音 | +| 伺服控制 | 50 Hz | 伺服电机标准频率 | +| 开关电源 | 100-500 kHz | 减小磁性元件尺寸 | +| 音频输出 | 20-100 kHz | 超出人类听觉范围 | +| 数字通信 | 1-10 MHz | 高速数据传输 | + +--- + +**文档版本**:V3.0 +**更新日期**:2025年 +**适用平台**:WS63 (HiSilicon) +**PWM版本**:CONFIG_PWM_USING_V151 +**系统时钟**:80MHz + +本文档提供通用PWM技术指导,适用于各种基于WS63平台的PWM应用开发。 \ No newline at end of file diff --git a/PWM参数计算详解.md b/PWM参数计算详解.md new file mode 100644 index 0000000..ab354e3 --- /dev/null +++ b/PWM参数计算详解.md @@ -0,0 +1,320 @@ +# PWM参数计算详解(通俗易懂版) + +## 文档说明 + +本文档专门解释PWM的各个参数计算原理,用通俗的语言和图表帮助理解复杂的PWM概念。适合只熟悉0-255亮度控制的开发者快速理解PWM参数。 + +--- + +## 1. 什么是PWM? + +PWM就像开关灯一样,快速地开关来控制亮度。想象你手里有个开关,如果一直开着,灯就是100%亮;如果一直关着,灯就是0%亮。但如果你快速地开开关关,开的时间长一点,灯就亮一点;关的时间长一点,灯就暗一点。 + +### PWM波形图解 + +``` +100%亮度 (一直开着): +████████████████████████████████████████████████████ + +50%亮度 (一半时间开,一半时间关): +████████ ████████ ████████ ████ + +25%亮度 (1/4时间开,3/4时间关): +████ ████ ████ ████ ████ ████ ████ + +0%亮度 (一直关着): +________________________________________________ +``` + +### 关键概念图解 + +```mermaid +graph LR + A[频率
每秒开关多少次] --> B[周期
一次开关的总时间] + B --> C[占空比
开的时间占比例] + C --> D[亮度效果
人眼感知的亮度] + + style A fill:#e3f2fd + style B fill:#f1f8e9 + style C fill:#fff3e0 + style D fill:#fce4ec +``` + +--- + +## 2. 从0-255到实际参数的转换 + +### 2.1 对应关系表 + +| 你熟悉的数值 | 百分比 | PWM术语 | 实际效果 | +|-------------|--------|---------|----------| +| 0 | 0% | 0%占空比 | 完全不亮 | +| 64 | 25% | 25%占空比 | 1/4亮度 | +| 128 | 50% | 50%占空比 | 一半亮度 | +| 192 | 75% | 75%占空比 | 3/4亮度 | +| 255 | 100% | 100%占空比 | 最亮 | + +### 2.2 转换公式 + +```c +// 从你熟悉的0-255转换到百分比 +uint8_t brightness = 128; // 你设置的亮度值 +float duty_percent = (brightness / 255.0f) * 100.0f; // 转换成百分比 + +// 例子: +// brightness = 0 → duty_percent = 0% (完全不亮) +// brightness = 64 → duty_percent = 25% (1/4亮度) +// brightness = 128 → duty_percent = 50% (一半亮度) +// brightness = 192 → duty_percent = 75% (3/4亮度) +// brightness = 255 → duty_percent = 100% (最亮) +``` + +--- + +## 3. PWM参数计算原理 + +### 3.1 基础概念图解 + +``` +系统就像一个超快的时钟(80MHZ),每秒"滴答(cycle)"8000万次,每个滴答节奏所对应的时间是固定的。 +也就是这个PWM的最高精度就是(1/80*1000*1000)s = + +如果我们要让PWM 1000HZ,也就是每秒开关1000次: +那么每次开关需要:80M ÷ 1000 = 80000 cycle所对应的时间 +所以我们控制PWM本质上是控制这个PWM,一个周期的cycle总个数以及比例分配。 + +比如我要 PWM 是1KHZ +首先计算,1KHZ 一个PWM周期就是1/1000s 也就是1ms +然后计算1ms 对应多少个cycle: 0.001 / (1/80*1000*1000) = 80,000 +然后再根据占空比去分配这 80,000 个数字就行了 +只要确保high_time low_time加起来是8000就行 + +``` + +### 3.2 时间轴图解 + +``` +一个PWM周期 (1000Hz = 1ms): + +0 20000 40000 60000 80000 +|---------|---------|---------|---------| + 25% 50% 75% 100% + +如果要50%亮度: +████████████████████████████████████████ +|←----- 开40000-----→|←---- 关40000 ----| + +如果要75%亮度: +████████████████████████████████████████████████████████ +|←-------- 开 (60000滴答) ---------→|←- 关 (20000滴答) -| +``` + +### 3.3 计算步骤详解 + +```c +// 第1步:确定系统时钟(这是硬件固定的,不用改) +uint32_t system_clock = 80000000; // 80MHz = 每秒8000万次计数 + +// 第2步:设定PWM频率(就是每秒开关多少次) +uint32_t pwm_frequency = 1000; // 1000Hz = 每秒开关1000次 + +// 第3步:计算一个开关周期需要多少个时钟计数 +// 打个比方:如果系统每秒有8000万下,你要让它每秒开关1000次 +// 那么每次开关就需要:8000万 ÷ 1000 = 80000个计数 +uint32_t period_count = system_clock / pwm_frequency; + +// 第4步:根据亮度(占空比)计算开和关的时间 +// 如果要60%亮度,那么80000个计数中,48000个用来"开",32000个用来"关" +float brightness_percent = 60.0; // 60%亮度 +uint32_t on_time = (uint32_t)(period_count * brightness_percent / 100.0); +uint32_t off_time = period_count - on_time; + +// 结果: +// on_time = 48000 (开的时间) +// off_time = 32000 (关的时间) +``` + +--- + +## 4. PWM参数详解图表 + +### 4.1 pwm_config_t结构体参数图解 + +```c +typedef struct pwm_config { + uint32_t high_time; // 高电平时间("开"的时间,用计数表示) + uint32_t low_time; // 低电平时间("关"的时间,用计数表示) + uint32_t offset_time; // 相位偏移时间(延迟启动时间) + uint16_t cycles; // 重复次数(0=无限重复) + bool repeat; // 连续输出标志(true=一直输出) +} pwm_config_t; +``` + +### 4.2 参数关系图 + +```mermaid +graph TB + A[一个PWM周期] --> B[high_time
开的时间] + A --> C[low_time
关的时间] + + B --> D[占空比 = high_time / (high_time + low_time)] + C --> D + + D --> E[最终亮度效果] + + F[offset_time
延迟启动] --> G[多通道同步控制] + H[cycles
重复次数] --> I[输出控制] + J[repeat
连续标志] --> I + + style A fill:#e3f2fd + style D fill:#f1f8e9 + style E fill:#fff3e0 +``` + +### 4.3 各参数的通俗解释 + +#### **high_time(高电平时间)** +- **通俗理解**:就是"开关"中"开"的时间,用时钟计数表示 +- **计算方法**:`high_time = 周期计数 × 亮度百分比` +- **例子**:如果high_time = 40000,系统时钟80MHz,那么"开"的时间 = 40000 ÷ 80000000 = 0.5ms + +#### **low_time(低电平时间)** +- **通俗理解**:就是"开关"中"关"的时间,用时钟计数表示 +- **计算方法**:`low_time = 周期计数 - high_time` +- **例子**:如果low_time = 40000,那么"关"的时间也是0.5ms + +#### **offset_time(相位偏移)** +- **通俗理解**:延迟多长时间再开始PWM输出 +- **使用场景**:多个LED需要错开时间启动,避免同时启动造成电流冲击 +- **大多数情况**:设为0即可 + +#### **cycles(重复次数)** +- **通俗理解**:PWM波形重复多少次后停止 +- **常用设置**: + - `0`:无限重复(最常用) + - `具体数字`:输出指定次数后停止 + +#### **repeat(连续输出标志)** +- **通俗理解**:是否持续输出PWM波形 +- **常用设置**: + - `true`:持续输出(最常用) + - `false`:只输出指定次数后停止 + +--- + +## 5. 实际计算示例 + +### 5.1 示例1:设置50%亮度 + +```c +// 目标:PWM频率1000Hz,50%亮度 + +// 第1步:计算周期计数 +uint32_t period_cnt = 80000000 / 1000; // = 80000 + +// 第2步:计算高低电平时间 +uint32_t high_time = 80000 * 50 / 100; // = 40000 +uint32_t low_time = 80000 - 40000; // = 40000 + +// 第3步:配置PWM +pwm_config_t cfg = { + .high_time = 40000, // "开" 0.5ms + .low_time = 40000, // "关" 0.5ms + .offset_time = 0, // 不延迟 + .repeat = true, // 持续输出 + .cycles = 0 // 无限重复 +}; +``` + +### 5.2 示例2:设置75%亮度 + +```c +// 目标:PWM频率1000Hz,75%亮度 + +uint32_t period_cnt = 80000; +uint32_t high_time = 80000 * 75 / 100; // = 60000 +uint32_t low_time = 80000 - 60000; // = 20000 + +pwm_config_t cfg = { + .high_time = 60000, // "开" 0.75ms (更长) + .low_time = 20000, // "关" 0.25ms (更短) + .offset_time = 0, + .repeat = true, + .cycles = 0 +}; +``` + +### 5.3 波形对比图 + +``` +50%占空比 (high_time=40000, low_time=40000): +████████████████████████████████████████ ████████████████████████████████████████ +|←-------- 0.5ms ------→|←-- 0.5ms --→| |←-------- 0.5ms ------→|←-- 0.5ms --→| + +75%占空比 (high_time=60000, low_time=20000): +████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████ +|←-------------- 0.75ms ------------→|0.25ms| |←-------------- 0.75ms ------------→|0.25ms| + +25%占空比 (high_time=20000, low_time=60000): +████████████████████ ████████████████████ +|←-- 0.25ms --→|←---- 0.75ms ----→| |←-- 0.25ms --→|←---- 0.75ms ----→| +``` +--- + +## 7. 简化使用函数 + +### 7.1 推荐的简化函数 + +```c +// 这个函数让PWM像以前一样简单 +errcode_t set_pwm_brightness_simple(uint8_t channel, uint8_t brightness_0_255) +{ + // 固定使用1000Hz频率,适合LED调光 + uint32_t frequency = 1000; + + // 将0-255转换为百分比 + float duty_percent = (brightness_0_255 / 255.0f) * 100.0f; + + // 计算周期计数(80MHz ÷ 1000Hz = 80000) + uint32_t period_cnt = 80000000 / frequency; + + // 计算高低电平时间 + uint32_t high_time = (uint32_t)(period_cnt * duty_percent / 100.0f); + uint32_t low_time = period_cnt - high_time; + + // 配置PWM + pwm_config_t cfg = { + .high_time = high_time, // "开"的时间(计数值) + .low_time = low_time, // "关"的时间(计数值) + .offset_time = 0, // 不需要相位偏移 + .repeat = true, // 持续输出 + .cycles = 0 // 无限循环 + }; + + return uapi_pwm_open(channel, &cfg); +} +``` + +--- + +## 8. 常见问题解答 + +### Q1: 为什么不能直接用0-255,要用这些复杂参数? +**A**: PWM硬件需要知道具体的时钟计数,不是百分比。就像你告诉司机"开快点",司机需要知道具体开多少码一样。 + +### Q2: high_time和low_time的单位是什么? +**A**: 单位是时钟计数次数,不是时间。1个计数 = 1/80000000秒 = 12.5纳秒。 + +### Q3: 为什么推荐1000Hz频率? +**A**: +- 高于100Hz:人眼看不到闪烁 +- 低于20kHz:不会产生高频噪音 +- 1000Hz刚好在这个范围内,且计算简单 + +### Q4: offset_time什么时候用? +**A**: 主要用于多通道同步,比如RGB灯的三个颜色错开启动,避免电流冲击。 + +### Q5: 如何实现渐变效果? +**A**: 在循环中逐步改变brightness_0_255的值,每次改变后调用set_pwm_brightness_simple()。 + +--- diff --git a/application/samples/wifi/ohos_connect/hilink_adapt/adapter/oh_sle_srv_ssap_server.c b/application/samples/wifi/ohos_connect/hilink_adapt/adapter/oh_sle_srv_ssap_server.c index 041ecea..ba672b0 100755 --- a/application/samples/wifi/ohos_connect/hilink_adapt/adapter/oh_sle_srv_ssap_server.c +++ b/application/samples/wifi/ohos_connect/hilink_adapt/adapter/oh_sle_srv_ssap_server.c @@ -54,11 +54,15 @@ errcode_t SsapsAddDescriptorSync(uint8_t serverId, uint16_t serviceHandle, uint1 return ssaps_add_descriptor_sync(serverId, serviceHandle, propertyHandle, (ssaps_desc_info_t *)descriptor); } +#define DEFAULT_SLE_SPEED_MTU_SIZE 1500 errcode_t SsapsStartService(uint8_t serverId, uint16_t serviceHandle) { + SsapcExchangeInfo info = {0}; + info.mtuSize = DEFAULT_SLE_SPEED_MTU_SIZE; + info.version = 1; + SsapsSetInfo(serverId, &info); return ssaps_start_service(serverId, serviceHandle); } - errcode_t SsapsDeleteAllServices(uint8_t serverId) { return ssaps_delete_all_services(serverId); diff --git a/application/samples/wifi/ohos_connect/hilink_adapt/entry/hilink_ble_main.c b/application/samples/wifi/ohos_connect/hilink_adapt/entry/hilink_ble_main.c index ce4d209..dbc4c4e 100755 --- a/application/samples/wifi/ohos_connect/hilink_adapt/entry/hilink_ble_main.c +++ b/application/samples/wifi/ohos_connect/hilink_adapt/entry/hilink_ble_main.c @@ -294,7 +294,7 @@ static void HILINK_BT_StateChangeHandler(HILINK_BT_SdkStatus event, const void * HILINK_SAL_ERROR("set addr err\n"); } /* 设置蓝牙广播格式,包括靠近发现、碰一碰等,下一次发送广播生效 */ - BLE_SetAdvType(BLE_ADV_NEARBY_V0); + BLE_SetAdvType(BLE_ADV_LOCAL_NAME); /* BLE配网广播控制:参数代表广播时间,0:停止;0xFFFFFFFF:一直广播,其他:广播指定时间后停止,单位秒 */ if(ble_adv_time) { @@ -617,7 +617,7 @@ int hilink_ble_main(void) } /* 设置广播方式为靠近发现 */ - BLE_SetAdvType(BLE_ADV_NEARBY_V0); + BLE_SetAdvType(BLE_ADV_LOCAL_NAME); /* 初始化ble sdk */ ret = BLE_CfgNetInit(&g_bleInitParam, &g_bleCfgNetCb); diff --git a/application/samples/wifi/ohos_connect/hilink_adapt/entry/hilink_indie_upgrade_main.c b/application/samples/wifi/ohos_connect/hilink_adapt/entry/hilink_indie_upgrade_main.c index fb0a342..7aea1c1 100755 --- a/application/samples/wifi/ohos_connect/hilink_adapt/entry/hilink_indie_upgrade_main.c +++ b/application/samples/wifi/ohos_connect/hilink_adapt/entry/hilink_indie_upgrade_main.c @@ -18,7 +18,7 @@ #define APP_BIN_ADDR (APP_PROGRAM_ORIGIN - FLASH_START - 0x300) #define CODE_AREA_HASH_OFFSET 0x128 -bool hexify(const unsigned char *inBuf, unsigned int inBufLen, char *outBuf, unsigned int outBufLen) +bool hexify(const unsigned char *inBuf, unsigned int inBufLen, char *outBuf, unsigned int outBufLen) { if (outBufLen < HEXIFY_LEN(inBufLen)) { return false; @@ -91,4 +91,4 @@ int hilink_indie_upgrade_main(void) return 0; } -#endif \ No newline at end of file +#endif diff --git a/application/samples/wifi/ohos_connect/hilink_adapt/product/device_profile.h b/application/samples/wifi/ohos_connect/hilink_adapt/product/device_profile.h index 9ac9852..40b63df 100755 --- a/application/samples/wifi/ohos_connect/hilink_adapt/product/device_profile.h +++ b/application/samples/wifi/ohos_connect/hilink_adapt/product/device_profile.h @@ -30,7 +30,7 @@ extern "C" { #define DEVICE_HIVERSION "1.0.0" /* 设备固件版本号 */ -#define FIRMWARE_VER "1.0.13" +#define FIRMWARE_VER "1.0.6" /* 设备硬件版本号 */ #define HARDWARE_VER "1.0.0" /* 设备软件版本号 */ diff --git a/application/samples/wifi/ohos_connect/hilink_adapt/product/hilink_device.c b/application/samples/wifi/ohos_connect/hilink_adapt/product/hilink_device.c index 8441a01..75e2cae 100755 --- a/application/samples/wifi/ohos_connect/hilink_adapt/product/hilink_device.c +++ b/application/samples/wifi/ohos_connect/hilink_adapt/product/hilink_device.c @@ -112,7 +112,7 @@ int HILINK_GetDevInfo(HILINK_DevInfo *devinfo) return -1; } HILINK_SAL_DEBUG("HILINK_GetDevInfo manuName: [%s]\r\n", devinfo->manuName); -#ifdef CONFIG_SUPPORT_HILINK_INDIE_UPGRADE +#if 0 //def CONFIG_SUPPORT_HILINK_INDIE_UPGRADE err = sprintf_s(devinfo->fwv, sizeof(devinfo->fwv), "%s_%s_%s", "103","1.312", FIRMWARE_VER); if (err <= 0) { HILINK_SAL_ERROR("sprintf_s err:%d\r\n", err); @@ -632,6 +632,7 @@ void HILINK_NotifyDevStatus(int status) case HILINK_LINK_CONNECTED_WIFI: /* 设备已经连上路由器,请在此处添加实现 */ hf_set_wifi_state(1); + handle_device_online(); break; case HILINK_M2M_CONNECTTING_CLOUD: /* 设备正在连接云端,请在此处添加实现 */ diff --git a/application/ws63/user_main/spotlight/device_module.c b/application/ws63/user_main/spotlight/device_module.c index 7ce7a9d..b99f196 100755 --- a/application/ws63/user_main/spotlight/device_module.c +++ b/application/ws63/user_main/spotlight/device_module.c @@ -57,6 +57,10 @@ int myhandle_put_brightness(const char* svc_id, const char* payload, unsigned in // 处理色温控制 int myhandle_put_cct(const char* svc_id, const char* payload, unsigned int len) { + // 🔧 添加调试信息:显示调用前的状态 + e_printf("[put_cct] 调用开始: g_device_control.cct_local=%d, duty_cw=%d, duty_ww=%d\n", + g_device_control.cct_local, g_device_control.duty_cw, g_device_control.duty_ww); + int ret = -1; cJSON *json = cJSON_Parse(payload); if (json == NULL) { diff --git a/application/ws63/user_main/spotlight/spotlight.h b/application/ws63/user_main/spotlight/spotlight.h index d7e8099..16a8635 100755 --- a/application/ws63/user_main/spotlight/spotlight.h +++ b/application/ws63/user_main/spotlight/spotlight.h @@ -100,8 +100,14 @@ typedef struct __attribute__((packed, aligned(1))) { #define BRIGHTNESS_MAX 100 // 最大亮度 #define BRIGHTNESS_LOCAL_MIN 0 // 最小亮度 #define BRIGHTNESS_LOCAL_MAX 10000 // 最大亮度 -#define BRIGHTNESS_REMOTE2LOCAL(x) (x * 100) //变化范围0 -10000 -#define BRIGHTNESS_LOCAL2REMOTE(x) (x / 100) + +// 亮度映射函数:仅对1-10%范围进行映射到2-10%,11-100%保持不变 +// 映射公式: +// - 1-10%: 实际亮度 = 2 + (APP亮度 - 1) * 8 / 9 +// - 11-100%: 实际亮度 = APP亮度 +// 当APP亮度为1%时,实际亮度为2%;当APP亮度为10%时,实际亮度为10%;11%及以上保持不变 +#define BRIGHTNESS_REMOTE2LOCAL(x) (((x) <= 0) ? 0 : ((x) <= 10 ? (200 + (((x) - 1) * 800) / 9) : ((x) * 100))) +#define BRIGHTNESS_LOCAL2REMOTE(x) (((x) <= 0) ? 0 : ((x) <= 1000 ? (1 + (((x) - 200) * 9) / 800) : ((x) / 100))) #define BRIGHTNESS_LITME_RANGE(x) do { \ if (x > BRIGHTNESS_LOCAL_MAX) x = BRIGHTNESS_LOCAL_MAX; \ @@ -113,9 +119,9 @@ typedef struct __attribute__((packed, aligned(1))) { #define INIT_STA__BRIGHTNESS 50 #define INIT_STA__CCT 4000 -#define INIT_NET_CFG_PWOER_ON_KEEP_TIME (5 * 1000) // 统计进入配网每次打开状态的保持时间 +#define INIT_NET_CFG_PWOER_ON_KEEP_TIME (5 * 1000) // 上电保持超过该时间则清零计数(防误触) -#define NET_CFG_ENTRY_CNT 8 // 配网进入的上电次数 +#define NET_CFG_ENTRY_CNT 8 // 快速上电计数阈值:达到即强制解绑并恢复出厂 #define NET_CFG_BREATH_DURATION 1*60*1000 // 配网呼吸灯持续时间(ms) #define NET_CFG_TOTAL_TIMEOUT 10*60*1000 // 配网总超时时间(ms) @@ -195,6 +201,10 @@ typedef enum { #define CCT_PIN GPIO_00 // 暖白LED (WW) #define SWITCH_PIN GPIO_13 +// PWM 更新标志输出引脚(用于逻辑分析仪观察控制时序) +// 选取 GPIO10:每次调用 update_pwm_output() 时翻转一次电平 +#define PWM_UPDATE_FLAG_PIN GPIO_10 + #define CONFIG_PWM_GROUP_ID 2 #define OPEN_LIGHT GPIO_LEVEL_HIGH @@ -213,6 +223,7 @@ typedef enum { .is_net_configured = false, \ .duty_cw = 0, \ .duty_ww = 0, \ + .power_on_cnt = 0, \ }; int spotlight_main(void); @@ -228,4 +239,3 @@ void stop_net_config(void); extern int fast_report(const char* svc_id); #endif - diff --git a/application/ws63/user_main/spotlight/spotlight_main.c b/application/ws63/user_main/spotlight/spotlight_main.c index 2cca2bd..79520bf 100755 --- a/application/ws63/user_main/spotlight/spotlight_main.c +++ b/application/ws63/user_main/spotlight/spotlight_main.c @@ -64,6 +64,8 @@ bool g_reset_factory_flag = false; static uint32_t pwm_period_cnt = 0; // PWM周期 40us static uint8_t channel_id_cw = 0; // 冷白LED通道ID static uint8_t channel_id_ww = 0; // 暖白LED通道ID +// PWM参数更新调试标志引脚是否已初始化 +static bool g_pwm_update_flag_inited = false; void save_device_data(void); void read_device_data(void); @@ -112,6 +114,7 @@ static struct fade_ctx_t { uint16_t fade_time; //s uint32_t smooth_time_us; // 渐变总时长(us) uint32_t update_interval; // 更新间隔(us) + volatile bool timer_active; // 渐变定时器是否允许自重启(竞态保护) // 打印限制器 print_limiter_t print_limiter; }; @@ -208,7 +211,7 @@ static void *fade_task(const char *arg) fade_ctx.current_cct, fade_ctx.target_cct, fade_ctx.step_cct, fade_ctx.is_closing_fade); } - // 检查是否达到目标值(恢复原始逻辑) + // 检查是否达到目标值 bool brightness_reached = (fade_ctx.step_brightness > 0) ? (fade_ctx.current_brightness >= fade_ctx.target_brightness) : (fade_ctx.current_brightness <= fade_ctx.target_brightness); @@ -216,14 +219,14 @@ static void *fade_task(const char *arg) bool cct_reached = (fade_ctx.step_cct > 0) ? (fade_ctx.current_cct >= fade_ctx.target_cct) : (fade_ctx.current_cct <= fade_ctx.target_cct); - // 更新当前值(恢复原始逻辑) + // 更新当前值 if (!brightness_reached) { fade_ctx.current_brightness += fade_ctx.step_brightness; } if (!cct_reached) { fade_ctx.current_cct += fade_ctx.step_cct; } - // 如果达到目标值,停止渐变(恢复原始逻辑) + // 达到目标,停止渐变 if (brightness_reached && cct_reached) { e_printf("[fade_task] Fade completed, Final brightness: %d, CCT: %d, closing_fade: %d\r\n", fade_ctx.target_brightness, fade_ctx.target_cct, fade_ctx.is_closing_fade); @@ -231,6 +234,7 @@ static void *fade_task(const char *arg) fade_ctx.fade_completed = true; fade_ctx.current_brightness = fade_ctx.target_brightness; fade_ctx.current_cct = fade_ctx.target_cct; + fade_ctx.timer_active = false; // 防止回调自重启 uapi_timer_stop(fade_ctx.timer_handle); } @@ -335,6 +339,10 @@ static void *breath_task(const char *arg) // 渐变定时器回调函数 static void fade_timer_callback(uintptr_t data) { + // 若已被取消或未处于渐变态,禁止回调重入自重启,避免双定时器抖动 + if (!fade_ctx.timer_active || !fade_ctx.is_fading) { + return; + } osal_sem_up(&fade_ctx.sem); // 唤醒渐变任务 fade_task uapi_timer_start(fade_ctx.timer_handle, fade_ctx.update_interval, fade_timer_callback, 0); } @@ -364,6 +372,27 @@ static void init_fade_ctx(void) // 初始化打印限制器 init_print_limiter(&fade_ctx.print_limiter, "[fade_task]"); + // 🔧 智能初始化:根据设备状态决定初始值 + if (g_device_control.read_done) { + // 数据已读取完成(正常启动) + if (g_device_control.on) { + // 开机渐变:从0开始渐变到目标亮度 + fade_ctx.current_brightness = 0; + fade_ctx.current_cct = g_device_control.cct_local; // 色温保持目标值 + e_printf("[init_fade_ctx] 开机渐变模式: brightness=0->%d, cct=%d\n", + g_device_control.brightness_local, fade_ctx.current_cct); + } else { + // 设备关闭状态 + fade_ctx.current_brightness = 0; + fade_ctx.current_cct = g_device_control.cct_local; + } + } else { + // 数据尚未读取(初始化阶段)- 保持原有逻辑 + fade_ctx.current_brightness = 0; + fade_ctx.current_cct = 0; + e_printf("[init_fade_ctx] 初始化阶段: brightness=0, cct=0\n"); + } + // 创建渐变任务 osal_kthread_lock(); fade_ctx.task_handle = osal_kthread_create((osal_kthread_handler)fade_task, NULL, "FadeTask", @@ -380,8 +409,7 @@ static void init_fade_ctx(void) // 设置默认更新间隔 fade_ctx.update_interval = FADE_INTERVAL_MIN; - // fade_ctx.current_brightness = g_device_control.brightness_local; - // fade_ctx.current_cct = g_device_control.cct_local; + fade_ctx.timer_active = false; // 默认禁用,只有开始渐变时启用 } // 初始化呼吸灯控制 @@ -491,8 +519,12 @@ void calculate_pwm_duty(device_control_t* pdevice_control) // 更新PWM输出 void update_pwm_output(bool on_state, uint16_t duty_cw_val, uint16_t duty_ww_val) { - pwm_config_t cfg_repeat = {0}; - cfg_repeat.repeat = true; + // 说明:为了避免每步“重相位 + 动态拼接”导致的低亮抖动, + // 这里改为固定相位(offset_time=0),并避免每次重复start_group。 + // 若平台支持 uapi_pwm_update_duty_ratio,可切换为更新接口;否则退回 open 配置。 + + pwm_config_t cfg = {0}; + cfg.repeat = true; uint16_t current_duty_cw = duty_cw_val; uint16_t current_duty_ww = duty_ww_val; @@ -512,29 +544,53 @@ void update_pwm_output(bool on_state, uint16_t duty_cw_val, uint16_t duty_ww_val if (!on_state) { high_cnt_cw = 0; - low_cnt_cw = pwm_period_cnt; // Ensure low_cnt is full period if off + low_cnt_cw = pwm_period_cnt; // 关灯时保持低电平整周期 high_cnt_ww = 0; - low_cnt_ww = pwm_period_cnt; // Ensure low_cnt is full period if off + low_cnt_ww = pwm_period_cnt; } - // uapi_pwm_stop_group(CONFIG_PWM_GROUP_ID); - cfg_repeat.high_time = high_cnt_cw; - cfg_repeat.low_time = low_cnt_cw; - uapi_pwm_open(channel_id_cw, &cfg_repeat); - // uapi_pwm_update_duty_ratio(channel_id_cw, cfg_repeat.low_time, cfg_repeat.high_time); +#if defined(CONFIG_PWM_USING_V150) + // V150: 先更新占空,再通过 start_group 触发加载 + (void)uapi_pwm_update_duty_ratio(channel_id_cw, low_cnt_cw, high_cnt_cw); + (void)uapi_pwm_update_duty_ratio(channel_id_ww, low_cnt_ww, high_cnt_ww); + // 需要显式触发加载 + (void)uapi_pwm_start_group(CONFIG_PWM_GROUP_ID); +#else + // V151 场景 + cfg.offset_time = 0; // 固定相位,避免相位不断重置造成抖动 +#if defined(CONFIG_PWM_PRELOAD) + // 使用预加载:配置写入影子寄存器,等当前周期结束后自动无缝切换。 + cfg.high_time = high_cnt_cw; + cfg.low_time = low_cnt_cw; + (void)uapi_pwm_config_preload(CONFIG_PWM_GROUP_ID, channel_id_cw, &cfg); - cfg_repeat.high_time = high_cnt_ww; - cfg_repeat.low_time = low_cnt_ww; - cfg_repeat.offset_time = high_cnt_cw; // WW PWM starts after CW PWM high time + cfg.high_time = high_cnt_ww; + cfg.low_time = low_cnt_ww; + (void)uapi_pwm_config_preload(CONFIG_PWM_GROUP_ID, channel_id_ww, &cfg); + // 预加载模式下不重复调用 start_group,避免打乱当前计数器与周期 +#else + // 无预加载能力时的回退:直接 open + start_group(可能导致轻微周期扰动) + cfg.high_time = high_cnt_cw; + cfg.low_time = low_cnt_cw; + (void)uapi_pwm_open(channel_id_cw, &cfg); - uapi_pwm_open(channel_id_ww, &cfg_repeat); - // uapi_pwm_update_duty_ratio(channel_id_ww, cfg_repeat.low_time, cfg_repeat.high_time); - - uapi_pwm_start_group(CONFIG_PWM_GROUP_ID); + cfg.high_time = high_cnt_ww; + cfg.low_time = low_cnt_ww; + (void)uapi_pwm_open(channel_id_ww, &cfg); + (void)uapi_pwm_start_group(CONFIG_PWM_GROUP_ID); +#endif +#endif if (should_print(&pwm_limiter)) { - e_printf("on:%d, cw:high:%u, low:%u, duty:%u/%u, ww:high:%u, low:%u, duty:%u/%u, offset_time:%u\r\n", - on_state, high_cnt_cw, low_cnt_cw, current_duty_cw, PWM_DUTY_RATIO_MAX, high_cnt_ww, low_cnt_ww, current_duty_ww, PWM_DUTY_RATIO_MAX, cfg_repeat.offset_time); + e_printf("on:%d, cw(H/L):%u/%u, duty:%u, ww(H/L):%u/%u, duty:%u\r\n", + on_state, high_cnt_cw, low_cnt_cw, current_duty_cw, + high_cnt_ww, low_cnt_ww, current_duty_ww); + } + + // 调试:每次更新PWM参数后,翻转一次 GPIO10(PWM_UPDATE_FLAG_PIN) + // 便于使用逻辑分析仪观察控制发生的时间点 + if (g_pwm_update_flag_inited) { + (void)uapi_gpio_toggle(PWM_UPDATE_FLAG_PIN); } } @@ -608,6 +664,8 @@ lab_exit: static void cancel_current_light_fade(void) { + // 竞态保护:先拉低标志再停计时器,避免回调自重启 + fade_ctx.timer_active = false; uapi_timer_stop(fade_ctx.timer_handle); // 如果我打断了渐变,则将当前值更新到目标值不然下次计算会异常 // g_device_control.cct_local = fade_ctx.current_cct; @@ -887,10 +945,17 @@ int set_light(light_ctrl_source_e source, // 如果打开灯,则需要手动将前置状态设置为关闭的样子,这样子后面计算渐变才能正常计算 if (APP_OPEN_LIGHT == source || DEV_POWER_ON == source) { g_device_control.on = true; - // fade_ctx.current_brightness = 0; + + if (DEV_POWER_ON == source) { + // 🔧 开机渐变:明确设置从0开始 + fade_ctx.current_brightness = 0; // 从0开始渐变 + fade_ctx.current_cct = g_device_control.cct_local; // 色温保持目标值 + e_printf("[set_light] 开机渐变设置: brightness=0->%d, cct=%d\n", + g_device_control.brightness_local, fade_ctx.current_cct); + } + brightness_local_target = g_device_control.brightness_local; // 色温不进行变化,只改变亮度 - fade_ctx.current_cct = g_device_control.cct_local; cct_local_target = g_device_control.cct_local; if (g_device_control.colourMode != COLOUR_MODE_DUAL) { @@ -930,6 +995,7 @@ int set_light(light_ctrl_source_e source, calculate_fade_steps(&tmp_fade_ctx); // 复用原有计算流程 memcpy(&fade_ctx, &tmp_fade_ctx, FADE_CTRL_DATA_SIZE(tmp_fade_ctx)); e_printf("start close light fade\r\n"); + fade_ctx.timer_active = true; uapi_timer_start(fade_ctx.timer_handle, fade_ctx.update_interval, fade_timer_callback, 0); start_report_task(REPORT_SWITCH | REPORT_LIGHT_MODE); req_save_device_data(); @@ -953,6 +1019,7 @@ int set_light(light_ctrl_source_e source, calculate_fade_steps(&tmp_fade_ctx); memcpy(&fade_ctx, &tmp_fade_ctx, FADE_CTRL_DATA_SIZE(tmp_fade_ctx)); e_printf("start fade\r\n"); + fade_ctx.timer_active = true; uapi_timer_start(fade_ctx.timer_handle, fade_ctx.update_interval, fade_timer_callback, 0); start_report_task(REPORT_SWITCH | REPORT_BRIGHTNESS | REPORT_CCT | REPORT_LIGHT_MODE | REPORT_COLOUR_MODE); req_save_device_data(); @@ -972,6 +1039,7 @@ int set_light(light_ctrl_source_e source, calculate_fade_steps(&tmp_fade_ctx); memcpy(&fade_ctx, &tmp_fade_ctx, FADE_CTRL_DATA_SIZE(tmp_fade_ctx)); e_printf("start fade\r\n"); + fade_ctx.timer_active = true; uapi_timer_start(fade_ctx.timer_handle, fade_ctx.update_interval, fade_timer_callback, 0); start_report_task(REPORT_BRIGHTNESS | REPORT_CCT | REPORT_LIGHT_MODE | REPORT_COLOUR_MODE); req_save_device_data(); @@ -1025,27 +1093,34 @@ static void pwm_init(pin_t pin, pin_t pin1) uapi_pin_set_mode(pin, PIN_MODE_1); uapi_pin_set_mode(pin1, PIN_MODE_1); uapi_pwm_init(); - uint32_t frequency = uapi_pwm_get_frequency(pin%8); - // uint32_t pwm_base_period_ns = 1000 * 1000 * 1000 / frequency; - // uint32_t pwm_base_period_ns = 1000 * 1000 * 1000 / (80 * 1000 * 1000);// 80MHZ - // uint32_t pwm_target_period_ns = 1000 * 1000 * 1000 / PWM_FREQUENCY; - pwm_period_cnt = ((80 * 1000 * 1000) / PWM_FREQUENCY); + // 基准时钟固定 80MHz(客户指定)。不要使用 uapi_pwm_get_frequency 返回值。 + channel_id_cw = pin % 8; + channel_id_ww = pin1 % 8; + const uint32_t base_clk = 80 * 1000 * 1000; // 80MHz 固定 + pwm_period_cnt = (base_clk / PWM_FREQUENCY); + + // 初始为关灯:高电平0,低电平整周期;固定相位 + cfg_repeat.high_time = 0; cfg_repeat.low_time = pwm_period_cnt; - // 设置PWM组 - channel_id_cw = pin%8; - channel_id_ww = pin1%8; + cfg_repeat.offset_time = 0; + + // 先打开两个通道 + (void)uapi_pwm_open(channel_id_cw, &cfg_repeat); + (void)uapi_pwm_open(channel_id_ww, &cfg_repeat); + + // 设置分组并启动(分组只需启动一次) uint8_t channel_ids[2] = {channel_id_cw, channel_id_ww}; - // uapi_pwm_open(channel_id_cw, &cfg_repeat); - // uapi_pwm_open(channel_id_ww, &cfg_repeat); - uapi_pwm_set_group(CONFIG_PWM_GROUP_ID, channel_ids, sizeof(channel_ids)); - // uapi_pwm_start_group(CONFIG_PWM_GROUP_ID); - e_printf("PWM基础时钟频率: %dHz, 目标时钟频率: %dHz, 周期计数: %d\r\n", frequency, PWM_FREQUENCY, pwm_period_cnt); + (void)uapi_pwm_set_group(CONFIG_PWM_GROUP_ID, channel_ids, sizeof(channel_ids)); + (void)uapi_pwm_start_group(CONFIG_PWM_GROUP_ID); + + e_printf("PWM基础时钟频率(固定): %uHz, 目标时钟频率: %dHz, 周期计数: %u\r\n", base_clk, PWM_FREQUENCY, pwm_period_cnt); } extern int start_hilink_ble_net_config(int32_t net_cfg_time_s); void start_net_config(void) { - e_printf("进入配网状态,上电次数:%d, is_new_device:%d\r\n", g_device_control.power_on_cnt, !g_device_control.is_net_configured); - g_device_control.power_on_cnt = 0;// + e_printf("进入配网状态,上电次数:%d, is_new设备:%d\r\n", g_device_control.power_on_cnt, !g_device_control.is_net_configured); + // 按新规则:上电计数仅能在上电超过5秒后自然清零, + // 配网流程不再主动清除 power_on_cnt。 int ret = start_hilink_ble_net_config(NET_CFG_TOTAL_TIMEOUT/1000); if (ret) { // FIXME: 这里简单恢复之前等待配网的灯效即可 @@ -1075,23 +1150,16 @@ static int handle_network_status(void) // 检查是否需要进入配网状态或恢复出厂设置 if (g_device_control.power_on_cnt >= NET_CFG_ENTRY_CNT) { + // 任何状态下达到阈值都强制解绑并恢复出厂设置 + e_printf("快速上电达到 %d 次,强制恢复出厂设置(解绑)\r\n", NET_CFG_ENTRY_CNT); + g_reset_factory_flag = true; g_device_control.power_on_cnt = 0; - if (g_device_control.is_net_configured) { - // 已出厂设备:上下电6次后恢复出厂设置并重启 - e_printf("已到达出厂设备上电次,执行恢复出厂设置\r\n"); - g_reset_factory_flag = true; - extern int HILINK_RestoreFactorySettings(void); - while (!hf_hilink_main_is_runing()) { - msleep(10); - } - HILINK_RestoreFactorySettings(); - return start_net_cfg; - } else { - // 未出厂设备:进入配网模式 - e_printf("未出厂设备上电6次,进入配网模式\r\n"); - start_net_config(); - start_net_cfg = 1; + extern int HILINK_RestoreFactorySettings(void); + while (!hf_hilink_main_is_runing()) { + msleep(10); } + HILINK_RestoreFactorySettings(); + return start_net_cfg; } else if (!g_device_control.is_net_configured) { // 未出厂设备直接进入配网 start_net_config(); @@ -1274,18 +1342,17 @@ void handle_device_offline(void) // 处理设备解绑 void handle_device_unbind(void) { - e_printf("设备被解绑,重置配网状态\r\n"); + e_printf("设备被解绑,重置配网状态:%d\r\n", g_device_control.is_bound); if (g_device_control.is_bound) { g_device_control.is_bound = false; - g_device_control.power_on_cnt = 0; // 重置上电计数 // stop_spotlight_main_task(); device_control_t tmp = DEFAULT_DEVICE_DATA;//恢复默认 // if (!g_reset_factory_flag) { // tmp.is_net_configured = g_device_control.is_net_configured; // } - g_device_control = tmp; - save_device_data(); + g_device_control = tmp; } + save_device_data(); } uint64_t startup_time = 0; @@ -1312,10 +1379,27 @@ static void *spotlight_main_task(const char *arg) unused(arg); e_printf("[spotlight_main_task] Task started\r\n"); uint32_t current_time = 0; //uapi_systick_get_ms(); + // 自愈:在HiLink Main运行后做一次绑定状态一致性检查 + static bool s_bind_consistency_checked = false; while (spotlight_main_task_running) { // 检查配网超时 check_net_cfg_timeout(); check_net_cfg_power_on_keep_time(); + + if (!s_bind_consistency_checked && hf_hilink_main_is_runing()) { + // 仅执行一次:检查应用层与HiLink SDK的绑定状态是否一致 + extern int HILINK_IsRegister(void); + extern int HILINK_RestoreFactorySettings(void); + int reg = HILINK_IsRegister(); + bool sdk_bound = (reg != 0); + bool app_bound = g_device_control.is_bound; + if (sdk_bound != app_bound) { + e_printf("[consistency] app_bound=%d, sdk_bound=%d, do factory reset\r\n", app_bound, sdk_bound); + // 触发完整流程:由HiLink框架内部清理持久化并按注册的重启回调执行 + HILINK_RestoreFactorySettings(); + } + s_bind_consistency_checked = true; + } osal_msleep(20); } @@ -1402,6 +1486,10 @@ int spotlight_main(void) { read_device_data(); // 初始化GPIO并将灯关闭 gpio_init(SWITCH_PIN); + + // 初始化 PWM 更新标志引脚(GPIO10)为普通GPIO输出,默认拉低 + gpio_init(PWM_UPDATE_FLAG_PIN); + g_pwm_update_flag_inited = true; // 初始化PWM系统 pwm_sys_init(); pwm_init(BRIGHTNESS_PIN, CCT_PIN); @@ -1476,12 +1564,23 @@ static void set_light2net_cfg_done(void) g_device_control.brightness_local = BRIGHTNESS_REMOTE2LOCAL(NET_CFG_DEFAULT_BRIGHTNESS); g_device_control.cct_local = CCT_REMOTE2LOCAL(NET_CFG_DEFAULT_CCT); g_device_control.fade_time = NET_CFG_DEFAULT_FADE_TIME; + + // 🔧 关键修复:配网完成后直接使用目标色温,不从PWM反推 + // PWM反推逻辑是错误的,因为呼吸灯PWM状态不代表用户期望的色温 fade_ctx.current_brightness = g_device_control.brightness_local; - fade_ctx.current_cct = g_device_control.cct_local; + fade_ctx.current_cct = g_device_control.cct_local; // 直接使用目标色温 + e_printf("[set_light2net_cfg_done] 配网完成,同步色温: brightness=%d, cct=%d\n", + fade_ctx.current_brightness, fade_ctx.current_cct); + + // 设置目标值(与当前值一致,避免不必要的渐变) fade_ctx.target_brightness = g_device_control.brightness_local; fade_ctx.target_cct = g_device_control.cct_local; + calculate_pwm_duty(&g_device_control); update_pwm_output(g_device_control.on, g_device_control.duty_cw, g_device_control.duty_ww); + + e_printf("[set_light2net_cfg_done] 配网完成状态同步: current_cct=%d, target_cct=%d\n", + fade_ctx.current_cct, fade_ctx.target_cct); } // 启动呼吸灯 @@ -1502,4 +1601,3 @@ lightMode_e convert_mode_for_report(lightMode_e current_mode) // 否则上报基础模式 return LIGHT_MODE_GET_BASE(current_mode); } - diff --git a/build/config/target_config/ws63/config.py b/build/config/target_config/ws63/config.py index 0d29cef..b2d5720 100755 --- a/build/config/target_config/ws63/config.py +++ b/build/config/target_config/ws63/config.py @@ -130,7 +130,7 @@ target = { '-:hal_systick', 'partition', 'partition_ws63','pmp_cfg_ws63', 'nonos_malloc', 'nonos_malloc_port', 'update_common', 'update_local', 'update_local_ws63', 'lzma_22.00', 'update_storage', 'update_common_ws63', 'update_ab_ws63', 'factory_ws63', 'efuse', 'hal_efuse_v151', 'efuse_port', 'soc_port', - 'pwm', 'hal_pwm', 'pwm_port', # ADD + # 'pwm', 'hal_pwm', 'pwm_port', # ADD ], 'ram_component_set': ['uart', "time_set", "cpu", "pinctrl", "watchdog", "security_unified",'pmp_set'], 'os': 'non-os', @@ -288,7 +288,7 @@ target = { }, 'ws63-liteos-app-iot': { 'base_target_name': 'target_ws63_app_rom_template', - 'liteos_kconfig': 'ws63_iot', #ekko add for remove indie upgrade + 'liteos_kconfig': 'ws63_iot', # EKKO add for remove indie upgrade 'os': 'liteos', 'defines': [ "USE_CMSIS_OS", @@ -336,7 +336,7 @@ target = { "CONFIG_USE_CUSTOMER_SVC_INFO", #EKKO ADD "CONFIG_DHCPS_GW", "_HSF_", - "CONFIG_PWM_USING_V150", #EKKO ADD + "CONFIG_PWM_PRELOAD", # Enable PWM preload (period-boundary update) # "ENABLE_BLE_SCAN" #open ble scan ], 'ram_component': [ @@ -379,11 +379,10 @@ target = { 'cjson', 'xo_trim_port', 'hilink', - #'app_addr_map', #ekko remove for indie upgrade - 'hilinkdevicesdk', #ekko add for remove indie upgrade - 'hilinkota', #ekko add for remove indie upgrade - 'hilinkbtsdk', #ekko add for remove indie upgrade - #'hilinkquickcfg', #ekko add for remove indie upgrade + # 'app_addr_map', # ekko remove for remove indie upgrade + 'hilinkdevicesdk', # ekko add for remove indie upgrade + 'hilinkota', # ekko add for remove indie upgrade + 'hilinkbtsdk', # ekko add for remove indie upgrade 'huks_sdk', 'deviceauth', 'little_fs', 'littlefs_adapt_ws63', diff --git a/docs/serial_control_protocol.md b/docs/serial_control_protocol.md new file mode 100644 index 0000000..ef275a2 --- /dev/null +++ b/docs/serial_control_protocol.md @@ -0,0 +1,361 @@ +# 串口控制协议 + +## 目录 + +- [1. 指令格式](#1-指令格式) + - [1.1 串口参数](#11-串口参数) +- [2. JSON 结构](#2-json-结构) + - [2.1 字段说明](#21-字段说明) + - [2.2 发送提示](#22-发送提示) + - [2.3 查询命令(AT+QUERY)](#23-查询命令atquery) +- [3. 支持服务与字段(详解)](#3-支持服务与字段详解) + - [3.1 switch(开关)](#31-switch开关) + - [3.2 brightness(亮度)](#32-brightness亮度) + - [3.3 cct(色温)](#33-cct色温) + - [3.4 lightMode(场景模式)](#34-lightmode场景模式) + - [3.5 progressSwitch(渐变时长)](#35-progressswitch渐变时长) + - [3.6 colourMode(色温模式)](#36-colourmode色温模式) +- [4. 约束](#4-约束) +- [5. 错误码](#5-错误码) + - [5.1 NotControllable(112)用法建议](#51-notcontrollable112用法建议) +- [6. 时序图示(Mermaid)](#6-时序图示mermaid) +- [7. 示例](#7-示例) + +## 1. 指令格式 + +- 下发:`AT+CTRL={JSON}\r\n` +- 查询:`AT+QUERY={JSON}\r\n` +- 回执:`OK,\r\n` 或 `ERROR,[,]\r\n` +- 异步结果:`AT+RESP={JSON}\r\n` +- 规则: + - 单行 JSON(内部换行请写成 `\n`) + - 行结束统一为 CRLF `\r\n`(Windows 兼容) + - 接收端应兼容仅 LF `\n` + +### 1.1 串口参数 +- 波特率:`9600` +- 数据位:`8` +- 校验位:`None` +- 停止位:`1` +- 流控:`None` +- 编码:`UTF-8`(无 BOM) +- 建议:行缓冲读取,按 CRLF `\r\n` 作为帧结束。 + +## 2. JSON 结构 + +- 单服务控制(必须携带 id): + - 下发:`{"id": , "sid":"", "data": {}}` + - 返回:`{"id": , "sid":"", "data": {}, "error": , "message": "<可选>"}`(`error=0` 表示成功) + +### 2.1 字段说明 +- `id`(number, u32>0,必填) + - 会话标识,用于将异步结果与请求一一对应、便于定位问题。 + - 由发起方分配与维护,建议使用一个全局自增计数(溢出后回绕到 1)。 + - 受控方不做去重与顺序检查,仅在 RESP 中原样回显。 +- `sid`(string,必填) + - 服务功能 ID。合法值:`switch`、`brightness`、`cct`、`lightMode`、`progressSwitch`、`colourMode`。 + - 区分大小写;不在集合内的值视为不支持服务。 +- `data`(object,必填) + - 具体服务的数据负载。各服务的字段与范围见“支持服务与字段(详解)”。 + - 缺字段或类型不符为业务错误,RESP `error=105`(`message` 可携带原因)。 +- `error`(number, u32,RESP 必带) + - 业务执行结果码:`0` 成功;非 0 失败。常见值见“错误码”。 +- `message`(string,可选,RESP 可带) + - 人类可读的错误或提示信息,用于调试。 + +### 2.2 发送提示 +- 多次控制:请按需多次发送 `AT+CTRL=...`,每次使用不同的 `id`。 +- ACK 超时与重发建议: + - 发送后等待 `OK,`,超时建议 `200–500 ms`。 + - 未收到 ACK 则按原 `id` 与完全相同的 JSON 重发,重发 `2–3 次`,间隔 `200–300 ms`。 + - 注意:设备不做去重,重复下发可能重复执行;建议尽量使用幂等设置(如重复设置相同亮度)。 +- 结果等待与超时: + - 收到 `OK,` 后等待 `AT+RESP`,建议超时阈值 `1–2 s`(视业务而定)。 + - 超时未收到可按失败处理并在上层重试(使用新的 `id`)。 +- 换行约定:所有发送与回执均以 CRLF `\r\n` 结束;示例为单行展示,实际发送需包含 CRLF。 + +### 2.3 查询命令(AT+QUERY) +- 作用:查询指定 `sid` 的当前状态(只读,不改变设备状态)。 +- 下发:`AT+QUERY={"id": , "sid":""}`\r\n +- 回执:`OK,`\r\n +- 异步结果:`AT+RESP={"id": , "sid":"", "data": {}, "error": , "message": "<可选>"}`\r\n +- 说明: + - `data` 与“支持服务与字段(详解)”中各 `sid` 的状态格式一致(等同于 Get 接口的输出)。 + - 若 `sid` 不支持,返回 `ERROR,104,UnsupportedSid`(协议级错误)。 + +## 3. 支持服务与字段(详解) + +### 3.1 switch(开关) +- 功能:控制灯具开/关。 +- 下发:`{"id":,"sid":"switch","data":{"on":0|1}}` +- 返回:`{"id":,"sid":"switch","data":{"on":0|1},"error":,"message":"<可选>"}` +- 参数:`on` 为 int,取值 `0` 关、`1` 开。 +- 行为:当处于“离家模式”(mode=7)时,收到 `on=1` 会先退出离家模式再开灯。 +- 示例: + - 开灯 + - 【发起方发送】`AT+CTRL={"id":101,"sid":"switch","data":{"on":1}}` + - 【受控方返回】`OK,101` 与 `AT+RESP={"id":101,"sid":"switch","data":{"on":1},"error":0}` + - 关灯 + - 【发起方发送】`AT+CTRL={"id":102,"sid":"switch","data":{"on":0}}` + - 【受控方返回】`OK,102` 与 `AT+RESP={"id":102,"sid":"switch","data":{"on":0},"error":0}` + +### 3.2 brightness(亮度) +- 功能:设置亮度百分比。 +- 下发:`{"id":,"sid":"brightness","data":{"brightness":0..100}}` +- 返回:`{"id":,"sid":"brightness","data":{"brightness":<0..100>},"error":,"message":"<可选>"}` +- 参数:`brightness` 为 int,范围 `0..100`;超出范围按边界钳制。 +- 示例: + - 设为 60%:`AT+CTRL={"id":201,"sid":"brightness","data":{"brightness":60}}` + - 最小值:`AT+CTRL={"id":202,"sid":"brightness","data":{"brightness":0}}` + - 最大值:`AT+CTRL={"id":203,"sid":"brightness","data":{"brightness":100}}` + +### 3.3 cct(色温) +- 功能:设置色温(单位 K)。 +- 下发:`{"id":,"sid":"cct","data":{"colorTemperature":2700..6000}}` +- 返回:`{"id":,"sid":"cct","data":{"colorTemperature":<2700..6000>},"error":,"message":"<可选>"}` +- 参数:`colorTemperature` 为 int,范围 `2700..6000`;超出范围按边界钳制。 +- 示例: + - 暖色 3000K:`AT+CTRL={"id":301,"sid":"cct","data":{"colorTemperature":3000}}` + - 中性 4000K:`AT+CTRL={"id":302,"sid":"cct","data":{"colorTemperature":4000}}` + - 冷色 6000K:`AT+CTRL={"id":303,"sid":"cct","data":{"colorTemperature":6000}}` + +### 3.4 lightMode(场景模式) +- 功能:按预设场景设置亮度与色温,或进入离家模式。 +- 下发:`{"id":,"sid":"lightMode","data":{"mode":0..7}}` +- 返回:`{"id":,"sid":"lightMode","data":{"mode":<0..7>},"error":,"message":"<可选>"}` +- 参数:`mode` 为 int: + - 0 customer(自定义模式) + - 1 relax (休闲模式)(约 50%,4000K) + - 2 movie(观影模式)(约 10%,3000K) + - 3 dining(用餐模式)(约 100%,4000K) + - 4 home(回家模式)(约 80%,3500K) + - 5 winter(冬天模式)(约 100%,2700K) + - 6 summer(夏天模式)(约 100%,6000K) + - 7 leave(离家模式)(直接关灯) +- 示例: + - 观影模式:`AT+CTRL={"id":401,"sid":"lightMode","data":{"mode":2}}` + - 离家模式:`AT+CTRL={"id":402,"sid":"lightMode","data":{"mode":7}}` + - 回家模式:`AT+CTRL={"id":403,"sid":"lightMode","data":{"mode":4}}` + +### 3.5 progressSwitch(渐变时长) +- 功能:设置亮度/色温变化的平滑过渡时间(秒)。 +- 下发:`{"id":,"sid":"progressSwitch","data":{"fadeTime":0..30}}` +- 返回:`{"id":,"sid":"progressSwitch","data":{"fadeTime":<0..30>},"error":,"message":"<可选>"}` +- 参数:`fadeTime` 为 int,范围 `0..30` 秒;`0` 表示尽快变化。 +- 示例: + - 设为 5 秒:`AT+CTRL={"id":501,"sid":"progressSwitch","data":{"fadeTime":5}}` + - 立即变化:`AT+CTRL={"id":502,"sid":"progressSwitch","data":{"fadeTime":0}}` + +### 3.6 colourMode(色温模式) +- 功能:设置色温工作模式。 +- 下发:`{"id":,"sid":"colourMode","data":{"mode":0|1}}` +- 返回:`{"id":,"sid":"colourMode","data":{"mode":0|1},"error":,"message":"<可选>"}` +- 参数:`mode` 为 int:`0` 单色温、`1` 双色温。 +- 行为:单色温(0)下,设备固定到预设色温(6000K);双色温(1)下,使用/保持当前色温。 +- 示例: + - 单色温:`AT+CTRL={"id":601,"sid":"colourMode","data":{"mode":0}}` + - 双色温:`AT+CTRL={"id":602,"sid":"colourMode","data":{"mode":1}}` + +## 4. 约束 +- `{JSON}` 最大长度:≤ 1024 字节(不含前缀与行尾)。 +- 单行承载,不允许真实换行;内部换行使用 `\n` 字符串。 +- 行结束采用 CRLF `\r\n`;接收端应兼容仅 LF `\n`。 +- `id` 必须存在且为 u32>0;缺失/类型不符即返回 `ERROR,105,TypeError`。 + +## 5. 错误码 +- `100` BadPrefix(未以 `AT+CTRL=` 开头) +- `101` MalformedJSON(JSON 语法错误) +- `102` EmptyJSON(空 JSON) +- `103` PayloadTooLong(负载超长) +- `104` UnsupportedSid(不支持的 `sid`) +- `105` TypeError(字段缺失或类型不符) +- `106` Busy(设备忙) +- `107` ApplyTimeout(内部执行超时) +- `112` NotControllable(非受控状态:设备处于不可被控制的模式,例如升级/维护/工厂测试/安全锁定等) +- `111` RateLimited(限频) + +### 5.1 NotControllable(112)用法建议 +- 触发场景(示例): + - 升级进行中(OTA/独立升级/固件校验) + - 维护/自检/烧录/工厂测试模式 + - 安全锁定/童锁/设备被上级网关临时锁定 + - 热保护/过温降额/低电量保护等安全策略生效 + - 正在配网/关键迁移流程,不允许外部控制 +- 返回规范: + - 收到控制后先回 ACK:`OK,` + - 业务结束后返回:`AT+RESP={"id":,"sid":"","data":{...},"error":112,"message":"<原因>"}` +- `message` 建议取值(便于前端统一展示与日志聚合): + - `upgrading`(升级中) + - `maintenance`(维护/自检) + - `factory_test`(工厂测试) + - `locked`(安全/家长锁定) + - `thermal_protect`(热保护) + - `low_power`(低电量保护) + - `net_config`(正在配网) +- 查询建议: + - `AT+QUERY` 通常仍应返回当前状态并 `error=0`;若状态不可读,再返回 `error=112` 与合适的 `message`。 + +## 6. 时序图示(Mermaid) + +1) 多次控制(均成功,异步返回 `error=0`) +```mermaid +sequenceDiagram + participant 发起方 + participant 受控方 + + 发起方->>受控方: AT+CTRL={"id":7001,"sid":"switch","data":{"on":1}}\r\n + 受控方-->>发起方: OK,7001\r\n + note over 受控方: 执行开关控制 + 受控方-->>发起方: AT+RESP={"id":7001,"sid":"switch","data":{"on":1},"error":0}\r\n + + 发起方->>受控方: AT+CTRL={"id":7201,"sid":"lightMode","data":{"mode":2}}\r\n + 受控方-->>发起方: OK,7201\r\n + note over 受控方: 执行场景模式切换 + 受控方-->>发起方: AT+RESP={"id":7201,"sid":"lightMode","data":{"mode":2},"error":0}\r\n + + 发起方->>受控方: AT+CTRL={"id":7301,"sid":"progressSwitch","data":{"fadeTime":5}}\r\n + 受控方-->>发起方: OK,7301\r\n + note over 受控方: 设置渐变时长 + 受控方-->>发起方: AT+RESP={"id":7301,"sid":"progressSwitch","data":{"fadeTime":5},"error":0}\r\n +``` + +2) 协议级错误(JSON 语法错误,直接 `ERROR`,不产生 RESP) +```mermaid +sequenceDiagram + participant 发起方 + participant 受控方 + + 发起方->>受控方: AT+CTRL={"sid":"switch","data":{"on":1} + 受控方-->>发起方: ERROR,101,MalformedJSON + note over 受控方: 帧被丢弃,无 AT+RESP +``` + +3) 业务错误(收到并受理,但参数类型错误) +```mermaid +sequenceDiagram + participant 发起方 + participant 受控方 + + 发起方->>受控方: AT+CTRL={"id":7501,"sid":"brightness","data":{"brightness":"sixty"}}\r\n + 受控方-->>发起方: OK,7501\r\n + note over 受控方: 参数校验失败(类型错误) + 受控方-->>发起方: AT+RESP={"id":7501,"sid":"brightness","data":{"brightness":50},"error":105,"message":"TypeError"}\r\n +``` + +4) 查询状态(OK + RESP) +```mermaid +sequenceDiagram + participant 发起方 + participant 受控方 + + 发起方->>受控方: AT+QUERY={"id":7101,"sid":"brightness"}\r\n + 受控方-->>发起方: OK,7101\r\n + note over 受控方: 读取并打包当前亮度状态 + 受控方-->>发起方: AT+RESP={"id":7101,"sid":"brightness","data":{"brightness":60},"error":0}\r\n +``` + +## 7. 示例 + +1) 开关:打开 +【发起方发送】 +``` +AT+CTRL={"id":7001,"sid":"switch","data":{"on":1}} +``` +【受控方返回】 +``` +OK,7001 +``` +【受控方返回】 +``` +AT+RESP={"id":7001,"sid":"switch","data":{"on":1},"error":0} +``` + +2) 查询当前亮度 +【发起方发送】 +``` +AT+QUERY={"id":7101,"sid":"brightness"} +``` +【受控方返回】 +``` +OK,7101 +``` +【受控方返回】 +``` +AT+RESP={"id":7101,"sid":"brightness","data":{"brightness":60},"error":0} +``` + +3) 场景模式:movie +【发起方发送】 +``` +AT+CTRL={"id":7201,"sid":"lightMode","data":{"mode":2}} +``` +【受控方返回】 +``` +OK,7201 +``` +【受控方返回】 +``` +AT+RESP={"id":7201,"sid":"lightMode","data":{"mode":2},"error":0} +``` + +4) 渐变时长:5 秒 +【发起方发送】 +``` +AT+CTRL={"id":7301,"sid":"progressSwitch","data":{"fadeTime":5}} +``` +【受控方返回】 +``` +OK,7301 +``` +【受控方返回】 +``` +AT+RESP={"id":7301,"sid":"progressSwitch","data":{"fadeTime":5},"error":0} +``` + +5) 色温模式:单色温 +【发起方发送】 +``` +AT+CTRL={"id":7401,"sid":"colourMode","data":{"mode":0}} +``` +【受控方返回】 +``` +OK,7401 +``` +【受控方返回】 +``` +AT+RESP={"id":7401,"sid":"colourMode","data":{"mode":0},"error":0} +``` + +6) 业务失败(类型错误) +【发起方发送】 +``` +AT+CTRL={"id":7501,"sid":"brightness","data":{"brightness":"sixty"}} +``` +【受控方返回】 +``` +OK,7501 +``` +【受控方返回】 +``` +AT+RESP={"id":7501,"sid":"brightness","data":{"brightness":50},"error":105,"message":"TypeError"} +``` + +7) 非法 JSON(协议级错误) +【发起方发送】 +``` +AT+CTRL={"sid":"switch","data":{"on":1} +``` +【受控方返回】 +``` +ERROR,101,MalformedJSON +``` + +8) 不支持的服务(协议级错误) +【发起方发送】 +``` +AT+CTRL={"id":7601,"sid":"foo","data":{"bar":1}} +``` +【受控方返回】 +``` +ERROR,104,UnsupportedSid +``` diff --git a/docs/serial_control_protocol.pdf b/docs/serial_control_protocol.pdf new file mode 100755 index 0000000..03b18dc Binary files /dev/null and b/docs/serial_control_protocol.pdf differ diff --git a/indie_build.py b/indie_build.py index 86ad443..9a0b974 100755 --- a/indie_build.py +++ b/indie_build.py @@ -124,6 +124,7 @@ def all_build(): os.makedirs(temp_dir) build_update_package(os.path.join(info.upg_output, "update.fwpkg"), temp_dir, info.upg_output) + print("gen package.zip") file_dir = os.path.dirname(os.path.abspath(__file__)) os.system("cd " + file_dir + " && python3 package.py") diff --git a/output/LPT262_hilink-SR_Switch-20251026-1.0.6.fwpkg b/output/LPT262_hilink-SR_Switch-20251026-1.0.6.fwpkg new file mode 100644 index 0000000..7cc87b4 Binary files /dev/null and b/output/LPT262_hilink-SR_Switch-20251026-1.0.6.fwpkg differ diff --git a/output/package(SR_Switch-LPT262_hilink-20251026-1.0.6).zip b/output/package(SR_Switch-LPT262_hilink-20251026-1.0.6).zip new file mode 100644 index 0000000..3f7a8e7 Binary files /dev/null and b/output/package(SR_Switch-LPT262_hilink-20251026-1.0.6).zip differ diff --git a/ws63_check_json_analysis.md b/ws63_check_json_analysis.md new file mode 100644 index 0000000..4b6bfbc --- /dev/null +++ b/ws63_check_json_analysis.md @@ -0,0 +1,440 @@ +# WS63 Check.json 文件分析完整文档 + +## 概述 + +本文档详细分析了 `ws63-liteos-hilink-check.json` 文件的生成机制、校验内容和对应关系。该文件是WS63项目独立升级功能的核心组件,用于验证HiLink固件与App-IoT固件之间的接口兼容性。 + +## 1. JSON文件基本信息 + +**文件路径**: `./output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink-check.json` + +**文件内容结构**: +```json +{ + "ver": 1, + "app": "3A23309AAF0B692188B9C56755C084522354143382C5309A2D3258C3E6C834A500B53089C7A65C43392D51DFEF32BBC125B613C6B37656F59E142F0D713AD6115955749E72C53C3BA8CD96D2C69CB534231411DEAE5C4F1782DA2760BB04E0199813CAC4CBA339BEBF3D734FBF0955235DD160C0387CA4AD28BFC8766508F627D0B46BE48319EAC371921D27D806C9F3464BF4DFBAE09ADB95232386B021EEFBD3FAD2C6B405A1E9C13BB0420CAB5EF02D3D543090162EB8E51C2E1D10F3EE4EE5FE71A0BA8DF1E95E4A36694908DDB16E08F54B606E13CACB530F71320CC9DF6C3DCB70DC6E54BDA7B3C1720E7ECCB54E88950CFC3D4D700224AB40D9E568A89571387053E9BB3D77889DAA102DB1FC4CE38751F2E6A0BD27B0EC51B8EA015CE0E475ADF3E3CDB49580E84E228803A691F39D958D3F28B0E4D53C670A27B5AB436D11F83A8AAEB6538628D32F674C398B6CF1C295BAB9499E19EA993B11D07524E025EF6FF88F42331C3BE36689468DAF74C6B811187E2D7953251B9228B8A8C32422F97C0122332BB295C110F36291A845CD2FA2D6BC34E37B7CBE23083067AE8AF9346DEEEFCC168D2D4658A94AB5049858C11E8BE4672F492DE9B4046E4B0EEEA5E3D6FE8809743B2B6111718F2038742D7C48EE81BBD5D485373D34C70868622D3233179ADED3E8C5026A2217F6938E85AA7A6754CE869A8127656D364C00F05E95FE7738AD05E79CACA11368FAE6717C71C469FC934FF221953E9F7CA301D52266EA04E8184A6D5AF66621F90139D0642D0350FA3F28ED49CF23AC20D03710992FBE7E3DBCEBD737385AE906D8D885A6118F2416A45FFA93EC3483EDADE5864419C451689382274A6DEC6FD6734D3975199C9FFB561202FEAB34F195D595E791C43315669E430FB9E7288EF87BC4498A4DF1A93918FA7C7805F7131CCF092F762F28C0A502EC955661D19C624CD0B61E88F81F9D57108A971D268273CEABEE8013FF69661C1A29BBD995111C6A64314013FBA647F77B736AE8E330F3D7157E6C3774AFBCC38D643C7108363127CB03B902C6D32C202A73EE7099A65902A14959BA60D70D055A89A6C3E489489F355E418A02EF8A543D8DF826C59AD8FABD1564AF4FEE6224FCA67974F6F63E17D3036A50120E5449669A4D792E919F88C4BBEEC64E542216D35ED1DE5A21434FC521776D37F5C50257B639AA3F47D1CC1888A19961CD6F5D7C9BEF30D5EACE785DB24268E360581533F98458376DD1E770BF3612B6D1F9C62395F48BEDE23AE1514E24C79360CE44291140AFEC5576503E6EBBC1D453DC9ED772F6AA506112D26C5B806EC4E0EAEC6A98F6F1683AC86494FA565B7CE2A0540F45AC6A8A556CDEE96B315EF45ED456EF46F1E5C234ABF92B121CCE688D2163BDF57FB6098B4742FBC504AC5E03230ADF90428D53F024F9C20964CA8AEA8A9042D1CEF0A2BA9C88A011C1004A9B8F7F9EBEDA365056947E9426D868829904D70DA74B7276D93829D151333F3506E83032A8CF68630BD5D0A39237FDF0BADD131AB77B6092987F07139588081D140EB2A0CA25BF9267FBC809457528A7C5DFA1729552A8D76B3362B9915E84795CD770EB84A3AE73DE78F04D37E8FC2F53E2F72853D57F170FC2551F7CBBB9B63D119AC4775120418F3865792FEFAAFF69038BFD3AA3C2C476B26DFB2B48F3B3904203C394782FE1C40F718001C31504539827DDC80900966524DCE965006F86EBDA9C46226BC1DA2387E2AE9565DFEE621F0DA936298371D1AD83860B4E607BB473E4103ADF872D1A3CA9EB171CF73077B4AFC2D7FC3737FAE5337290D8116CE0826F347C621502BA313EEF20F1F67D00A7EB1BB555DE7B0FCDD05C747D7615136B35079DA4BF9A6EED5F0C5448B8364FB03476275BD823C8C7F3CBCC1D0BD41C5B49538D65C00C85F9C8D50201EDA6266D7A2A375141F7F5E69E1AD06932C3A6C044EAE908D43657EA22C5FA797FE8D04592541477B362753A05", + "hilink": "BEC62B3A1B90717BABD8C9AD15D3BE1560977111A022C59CEF399A97E8B1D1D2D981EE972F4C881D0D3D30D8165FA46BEB1F07B4B8F1AECEA4335DFC7388E1234641EDD1AF31DFEDB616EBF0A9900778C03C4467D3CBCD6540F51D66CBE0FF998506BA06448D8BEB6CDA8E9E79BE4C8BF15449A4DEFEB22F342B09F03683E17D699DA475D08A2F079EDA59B99A96CD0E468B089224120EE96B86A8BB4B250B35E0E8B8A1E77FE8A13F50CF8B018FA5A91475DDA932141DAF1F5C4D9C5D7F8DC8E3CB582D0DB0EA8823396BF6BC0A30D68B968BEE77AB6014036385A68BB35701D274404A9BFA24348E7321F63FEA98B7" +} +``` + +## 2. 文件生成机制 + +### 2.1 生成触发条件 + +**触发时机**: 在构建过程的 `build_post` 阶段(构建完成后) + +**配置条件**: 编译配置中必须包含 `CONFIG_SUPPORT_HILINK_INDIE_UPGRADE` +- 当前项目状态:该配置**已被注释**(config.py:334行) +- 因此只有HiLink固件生成了check.json,App-IoT固件未生成 + +### 2.2 生成调用链 + +``` +构建系统 (build.py) + ↓ +build_post 钩子 + ↓ +entry.py:153 → dump_indie_upg_check_file() + ↓ +indie_upgrade_utils.py:324 → 生成JSON文件 +``` + +### 2.3 核心生成文件 + +**主要脚本文件**: +- `build/config/target_config/ws63/script/entry.py:153` - 构建钩子调用点 +- `build/script/utils/indie_upgrade_utils.py:324` - JSON文件生成函数 +- `build/script/utils/indie_upgrade_utils.py:292-320` - 接口校验码生成核心逻辑 + +## 3. 校验内容详解 + +### 3.1 JSON字段含义 + +| 字段 | 含义 | 数据格式 | +|------|------|----------| +| `ver` | 校验文件版本号 | 整数,当前为1 | +| `app` | App侧接口校验码串联 | CRC32十六进制字符串 | +| `hilink` | HiLink侧接口校验码串联 | CRC32十六进制字符串 | + +### 3.2 校验码生成原理 + +1. **接口函数扫描**: 通过正则表达式扫描源文件中的函数原型 + ```python + rgl = r"((?:const )?(?:unsigned )?(?:struct )?(?:enum )?\w+[ \*]*)" + \ + r"(\w+)[\s\n]*" + \ + r"(\()" + \ + r"([\s\*,\w\[\]]*?)" + \ + r"(\))" + \ + r"\s*\{" + ``` + +2. **原型标准化**: 去除参数名,保留参数类型,生成标准化函数原型字符串 + +3. **CRC32计算**: 对每个函数原型字符串计算CRC32校验码 + ```python + checksum = "{:08X}".format(zlib.crc32(prototype.encode("utf8"))) + ``` + +4. **校验码串联**: 将所有接口的8位十六进制CRC32码按顺序串联 + +### 3.3 校验码对应的编译文件 + +| JSON字段 | 编译目标 | 二进制文件 | 签名文件 | +|----------|----------|------------|----------| +| `"app"` | `ws63-liteos-app-iot` | `output/ws63/acore/ws63-liteos-app-iot/ws63-liteos-app-iot.bin` | `ws63-liteos-app-iot-sign.bin` | +| `"hilink"` | `ws63-liteos-hilink` | `output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink.bin` | `ws63-liteos-hilink-sign.bin` | + +## 4. 接口映射文件体系 + +### 4.1 App侧接口配置 + +**源文件路径**: `application/samples/wifi/hilink_indie_upgrade/address_mapping/hilinksdk/app_uapi/` + +**主要接口文件**: +- `uapi_hilink_kv_adapter.c` - KV存储适配器接口 +- `uapi_hilink_mem_adapter.c` - 内存管理适配器接口 +- `uapi_hilink_socket_adapter.c` - Socket网络适配器接口 +- `uapi_hilink_stdio_adapter.c` - 标准IO适配器接口 +- `uapi_hilink_thread_adapter.c` - 线程管理适配器接口 +- `uapi_hilink_time_adapter.c` - 时间管理适配器接口 +- `uapi_hilink_network_adapter.c` - 网络适配器接口 +- `uapi_hilink_device.c` - 设备管理接口 +- `uapi_hichain.c` - 设备认证接口 +- `uapi_hilink_ble_adapter.c` - BLE适配器接口 +- 等共52个接口适配文件 + +**生成的映射表**: `application/samples/wifi/hilink_indie_upgrade/address_mapping/application/app_function_mapping.c` + +### 4.2 HiLink侧接口配置 + +**源文件路径**: `application/samples/wifi/hilink_indie_upgrade/address_mapping/application/hilink_uapi/` + +**主要接口文件**: +- `uapi_hilink.c` - HiLink核心接口 +- `uapi_hilink_log_manage.c` - 日志管理接口 +- `uapi_hilink_device_ext.c` - 设备扩展接口 +- `uapi_ble_cfg_net_api.c` - BLE配网接口 +- `uapi_hilink_bt_function.c` - 蓝牙功能接口 +- `uapi_hilink_network_adapter.c` - 网络适配器接口 +- `uapi_hilink_socket_adapter.c` - Socket适配器接口 +- `uapi_hilink_custom.c` - 自定义功能接口 +- `uapi_hilink_sle_api.c` - SLE接口 +- `uapi_hilink_quick_netcfg_api.c` - 快速配网接口 + +**生成的映射表**: `application/samples/wifi/hilink_indie_upgrade/address_mapping/hilinksdk/hilink_function_mapping.c` + +### 4.3 接口头文件映射 + +**App侧头文件路径**: +- `application/samples/wifi/ohos_connect/hilink_adapt/adapter/include/` - 适配器接口声明 +- `application/samples/wifi/ohos_connect/hilink_adapt/product/` - 产品相关接口 +- `application/samples/wifi/ohos_connect/hilink_adapt/include/` - 通用接口声明 +- `open_source/deviceauth/interfaces/innerkits/deviceauth_lite/` - 设备认证接口 +- `kernel/liteos/liteos_v208.5.0/Huawei_LiteOS/open_source/CMSIS/CMSIS/RTOS2/Include/` - RTOS接口 +- `open_source/cjson/cjson/` - JSON库接口 +- `open_source/mbedtls/mbedtls_v3.1.0/include/` - 加密库接口 + +**HiLink侧头文件路径**: +- `application/samples/wifi/ohos_connect/hilink_adapt/include/` - HiLink核心接口声明 +- `application/samples/wifi/ohos_connect/hilink_adapt/product/` - 产品相关接口 +- `application/samples/wifi/ohos_connect/hilink_adapt/adapter/include/` - 适配器接口声明 + +## 5. 校验机制详解 + +### 5.1 接口兼容性检查函数 + +**函数位置**: `build/script/utils/indie_upgrade_utils.py:353-370` + +**检查逻辑**: +```python +def check_indie_upg_match(hilink_check_file, app_check_file): + # 读取两个check.json文件 + f = open(hilink_check_file, "r", encoding="utf-8") + hilink_check = json.loads(f.read()) + f.close() + f = open(app_check_file, "r", encoding="utf-8") + app_check = json.loads(f.read()) + f.close() + + # 版本号检查 + if hilink_check["ver"] != 1 or app_check["ver"] != 1: + print("check file ver[h:%d,a:%d] not match" % (hilink_check["ver"], app_check["ver"])) + return False + + # HiLink侧接口向后兼容检查 + if not hilink_check["hilink"].startswith(app_check["hilink"]): + prt_not_match_info("hilink", hilink_check["hilink"], app_check["hilink"], True) + return False + + # App侧接口向后兼容检查 + if not app_check["app"].startswith(hilink_check["app"]): + prt_not_match_info("app", app_check["app"], hilink_check["app"], True) + return False + + return True +``` + +### 5.2 校验失败信息输出 + +**函数位置**: `indie_upgrade_utils.py:330-350` + +校验失败时会输出具体的不匹配接口信息: +- 显示接口序号和对应的CRC32校验码 +- 帮助定位具体是哪个接口发生了变更 +- 每个CRC32码长度为8个十六进制字符 + +### 5.3 升级包构建时的校验 + +**调用位置**: `build/config/target_config/ws63/build_ws63_update.py:74` + +```python +if not check_indie_upg_match(info.hilink_check, info.app_iot_check): + print("indie upg hilink and app not match") + return False +``` + +在生成升级包时会自动进行兼容性检查,如果接口不兼容,构建过程会失败。 + +## 6. 独立升级工作流程 + +### 6.1 独立升级概述 + +独立升级允许在不更新App-IoT固件的情况下,单独升级HiLink SDK固件。这种机制可以: +- 快速修复HiLink SDK的bug +- 更新云端协议和功能 +- 减少升级包大小和升级时间 +- 降低升级风险 + +### 6.2 配置开启流程 + +根据 `indie_upg.md` 文档,开启独立升级需要: + +1. **修改配置** (`build/config/target_config/ws63/config.py`): + - 删除 `'liteos_kconfig': 'ws63_iot'` + - 在 `'defines'` 中增加 `CONFIG_SUPPORT_HILINK_INDIE_UPGRADE` + - 在 `'ram_component'` 中增加 `'app_addr_map'` + - 在 `'ram_component'` 中删除 `'hilinkdevicesdk'`、`'hilinkota'`、`'hilinkbtsdk'`、`'hilinkquickcfg'` + +2. **分别编译两个固件**: + ```bash + # 先编译HiLink固件 + python3 build.py -c ws63-liteos-hilink + + # 再编译App-IoT固件 + python3 build.py -c ws63-liteos-app-iot + + # 生成升级包 + python3 build/config/target_config/ws63/build_ws63_update.py --pkt=app_iot + ``` + +3. **使用简化编译工具**: + ```bash + # 完整编译 + python3 indie_build.py all + + # 仅编译SDK(用于独立升级) + python3 indie_build.py sdk + ``` + +### 6.3 独立升级流程 + +1. **准备已认证的App-IoT固件**: + - `ws63-liteos-app-iot-sign.bin` + - `ws63-liteos-app-iot-check.json` + +2. **更新HiLink SDK库文件**: + - 替换 `application/samples/wifi/libhilink/` 中的库文件 + +3. **执行独立编译**: + ```bash + python3 indie_build.py sdk + ``` + +4. **接口兼容性校验**: + - 自动比较新HiLink固件与已认证App-IoT固件的接口 + - 如果接口不匹配,构建失败 + - 校验通过后生成完整升级包 + +### 6.4 约束条件 + +1. **分区兼容性**: 非独立升级版本与独立升级版本不可交叉升级 +2. **接口兼容性**: 模组App程序不变时,HiLink接口不能发生变更 +3. **版本号格式**: + - 非独立升级:`1.0.0` + - 独立升级:`1.0.0_14.2.0.304` (拼接SDK版本号) + +## 7. 新增接口适配指南 + +### 7.1 接口调用机制 + +独立升级模式下,模组App和HiLink分别编译成两个固件: +- 各自维护接口地址表 +- 通过查表机制获取对方接口函数指针 +- 实现跨固件的函数调用 + +### 7.2 新增HiLink接口步骤 + +**示例接口原型**: +```c +retType TestFuncName(type1 param1, type2 param2, type3 param3); +``` + +1. **接口声明** (`application/samples/wifi/ohos_connect/hilink_adapt/include/hilink_test.h`): + ```c + retType TestFuncName(type1 param1, type2 param2, type3 param3); + ``` + +2. **查表适配实现** (`application/samples/wifi/hilink_indie_upgrade/address_mapping/application/hilink_uapi/uapi_hilink_test.c`): + ```c + retType TestFuncName(type1 param1, type2 param2, type3 param3) + { + hilink_call3(HILINK_CALL_TEST_FUNC_NAME, TestFuncName, retType, type1, param1, type2, param2, type3, param3); + return (retType)0; + } + ``` + +3. **添加到编译系统** (`application/samples/wifi/hilink_indie_upgrade/address_mapping/application/CMakeLists.txt`): + ```cmake + uapi_hilink_test.c + ``` + +4. **配置脚本识别** (`build/script/utils/indie_upgrade_utils.py`): + ```python + default_config["hilink"]["src_file"] += ("uapi_hilink_test.c",) + default_config["hilink"]["mapping_header"]["application/samples/wifi/ohos_connect/hilink_adapt/include/"] += ("hilink_test.h",) + ``` + +5. **白名单配置**(可选): + ```python + default_config["hilink"]["white_list"] += ("TestFuncName",) + ``` + +### 7.3 宏说明 + +**查表宏定义**: +- `hilink_call0` / `hilink_call0_ret_void` - 无参数函数 +- `hilink_call1` / `hilink_call1_ret_void` - 1个参数函数 +- `hilink_call2` / `hilink_call2_ret_void` - 2个参数函数 +- `hilink_call3` / `hilink_call3_ret_void` - 3个参数函数 +- `hilink_callx` / `hilink_callx_ret_void` - x个参数函数 + +**参数说明**: +1. 枚举值(如 `HILINK_CALL_TEST_FUNC_NAME`) +2. 函数名 +3. 返回值类型 +4. 参数类型和参数名交替列出 + +## 8. 相关文件清单 + +### 8.1 核心脚本文件 + +| 文件路径 | 功能描述 | +|----------|----------| +| `build/script/utils/indie_upgrade_utils.py` | 独立升级工具核心逻辑 | +| `build/config/target_config/ws63/script/entry.py` | 构建钩子入口 | +| `build/config/target_config/ws63/build_ws63_update.py` | 升级包构建脚本 | +| `build/config/target_config/ws63/config.py` | 编译配置文件 | +| `indie_build.py` | 独立升级编译工具 | +| `package.py` | 升级包生成工具 | + +### 8.2 接口映射相关文件 + +| 目录/文件 | 描述 | +|-----------|------| +| `application/samples/wifi/hilink_indie_upgrade/address_mapping/` | 接口映射根目录 | +| `application/samples/wifi/hilink_indie_upgrade/address_mapping/include/func_call_list.h` | 函数调用枚举定义(自动生成) | +| `application/samples/wifi/hilink_indie_upgrade/address_mapping/application/` | App侧接口适配 | +| `application/samples/wifi/hilink_indie_upgrade/address_mapping/hilinksdk/` | HiLink侧接口适配 | +| `application/samples/wifi/ohos_connect/hilink_adapt/` | 接口声明头文件目录 | + +### 8.3 输出文件 + +| 文件路径 | 描述 | +|----------|------| +| `output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink-check.json` | HiLink固件接口校验文件 | +| `output/ws63/acore/ws63-liteos-app-iot/ws63-liteos-app-iot-check.json` | App-IoT固件接口校验文件(需开启独立升级) | +| `output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink.bin` | HiLink固件二进制文件 | +| `output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink-sign.bin` | HiLink固件签名文件 | +| `output/ws63/acore/ws63-liteos-app-iot/ws63-liteos-app-iot.bin` | App-IoT固件二进制文件 | +| `output/ws63/acore/ws63-liteos-app-iot/ws63-liteos-app-iot-sign.bin` | App-IoT固件签名文件 | +| `output/ws63/fwpkg/ws63-liteos-app-iot/ws63-liteos-app-iot_all.fwpkg` | 完整烧录包 | +| `output/ws63/upgrade/update.fwpkg` | 升级包 | +| `output/ws63/ws63-liteos_all.zip` | 完整产物压缩包 | + +## 9. 当前项目状态 + +### 9.1 独立升级配置状态 + +**当前状态**: 独立升级功能**已关闭** + +**证据**: +- `config.py:334` 行:`"CONFIG_SUPPORT_HILINK_INDIE_UPGRADE"` 被注释 +- `config.py:291` 行:`'liteos_kconfig': 'ws63_iot'` 仍然存在 +- 只有HiLink固件生成了check.json文件 +- App-IoT固件目录下没有对应的check.json文件 + +### 9.2 编译产物状态 + +**已生成的文件**: +- ✅ `ws63-liteos-hilink.bin` (278K) +- ✅ `ws63-liteos-hilink-sign.bin` (279K) +- ✅ `ws63-liteos-hilink-check.json` (3.4K) +- ✅ `ws63-liteos-app-iot.bin` (1.7M) +- ✅ `ws63-liteos-app-iot-sign.bin` (1.7M) +- ❌ `ws63-liteos-app-iot-check.json` (未生成,因为独立升级未开启) + +## 10. 故障排查指南 + +### 10.1 check.json文件未生成 + +**可能原因**: +1. `CONFIG_SUPPORT_HILINK_INDIE_UPGRADE` 未在配置中启用 +2. 构建过程中 `build_post` 钩子未执行 +3. 接口映射文件生成失败 + +**排查步骤**: +1. 检查 `config.py` 中的配置项 +2. 查看构建日志中是否有 "create indie upg mapping files succ" 消息 +3. 检查接口源文件和头文件是否存在语法错误 + +### 10.2 接口兼容性校验失败 + +**错误信息示例**: +``` +indie upg hilink and app not match +hilink checksum not match +func 5 [A1B2C3D4:E5F6A7B8] not match +``` + +**排查步骤**: +1. 确定是哪个接口发生了变更(根据func序号) +2. 检查接口原型是否发生改变 +3. 检查参数类型、顺序、返回值类型 +4. 如果是合理的接口升级,需要重新生成App-IoT的check.json + +### 10.3 接口映射表生成失败 + +**可能原因**: +1. 源文件中函数原型格式不规范 +2. 头文件中接口声明与源文件实现不匹配 +3. 正则表达式匹配失败 + +**排查步骤**: +1. 检查函数原型是否符合标准C语法 +2. 确保头文件声明与源文件实现一致 +3. 查看构建日志中的正则匹配错误信息 + +## 11. 总结 + +`ws63-liteos-hilink-check.json` 文件是WS63项目独立升级功能的核心组件,通过CRC32校验码机制确保固件间接口兼容性。虽然当前项目未启用独立升级功能,但完整的技术框架已经就绪,可以通过简单的配置修改来启用该功能。 + +该机制的核心价值在于: +- **风险控制**: 通过接口校验防止不兼容升级 +- **灵活部署**: 支持HiLink SDK的独立快速升级 +- **开发效率**: 减少完整固件的编译和测试周期 +- **维护便利**: 清晰的接口约定和自动化校验流程 + +--- +**文档创建时间**: 2025-09-10 +**项目版本**: SR_Light_Hilink_14.2.1.312_20250714 +**分析对象**: output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink-check.json \ No newline at end of file