重构项目配置并添加对 SBDEMO 的支持

更新多个 ESP32 芯片(C2、C3、S3)
的版本配置为 SBDEMO
产品添加新的配置文件实现板卡和应用程序配置模块添加 CRC 实用函数修改系统日志以支持动态日志控制更新 Kconfig 和 sdkconfig 的默认设置以适应新产品
This commit is contained in:
LokLiang
2025-02-21 10:30:22 +08:00
parent 73df531ac3
commit e9b5e42ef2
28 changed files with 1561 additions and 48 deletions

447
app/config/app_config.c Normal file
View File

@@ -0,0 +1,447 @@
/**
* @file app_config.c
* @author LokLiang
* @brief app_config.h 模块源码
* @version 0.1
* @date 2023-11-242
*
* @copyright Copyright (c) 2023
*
*/
#include "app_config.h"
#include "config/board_config.h"
#include "config/version.h"
#include "app_main.h"
#include "nvs.h"
#include "utils/crc.h"
#include "os/os.h"
#include "esp_mac.h"
#undef CONFIG_SYS_LOG_LEVEL
#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_INF
#define SYS_LOG_DOMAIN "APCFG"
#include "sys_log.h"
#include "sys_types.h"
#define _NVS_KEY_MAIN "app-main" // 主应用
#define _NVS_KEY_BKUP "app-bkup" // 备份副本,用于数据校验错误时回滚
/* 数据区 -------------------------------------------------------------------------------------- */
extern nvs_handle g_nvs_hdl;
static cfg_app_t s_cfg_app;
const cfg_app_t *g_cfg_app = (const cfg_app_t *)&s_cfg_app;
const int *g_sys_log_on = &s_cfg_app.sys_log_on;
static os_work_t s_work_hdl_save;
/* 默认的程序设置代码文件 */
#include "app_config/developing.h"
#include "app_config/SBDEMO.h"
/* nvs 接口 ------------------------------------------------------------------------------------ */
static int _read_key(const char *key, void *out_value, size_t tar_length, uint32_t *crc_base);
static int _set_crc(const void *struct_base, size_t struct_length, uint32_t *crc_base);
static void _work_handler_save(void *arg);
/**
* @brief 读取指定的数据,并按给出的特性进行校验,返回校验结果
*
* @param key 在 nvs 的存储块的键值
* @param out_value[out] 输出内存
* @param tar_length 输出的内存预期的长度
* @param crc_base[io] CRC 校验值所在内存块的地址(绝对值)。输入:校验值,输出:新的正确校验值
* @retval 0 正确读取了已存储数据
* @retval 1 nvs 模块可用, CRC 校验值已更新
* @retval -1 nvs 模块不可用
*/
static int _read_key(const char *key, void *out_value, size_t tar_length, uint32_t *crc_base)
{
size_t required_size = 0;
esp_err_t err = ESP_OK;
/* 在确认已存储的数据长度前,仅获取长度以免数据溢出 */
err = nvs_get_blob(g_nvs_hdl, key, NULL, &required_size);
if (err != ESP_OK)
{
SYS_LOG_WRN("failed to read prfsdata data length, err:%d", err);
return -1;
}
if (required_size != tar_length)
{
SYS_LOG_WRN("invalid prfsdata length, length:%d, err:%d", required_size, err);
return -1;
}
/* 读出已存储的数据 */
err = nvs_get_blob(g_nvs_hdl, key, out_value, &required_size);
if (err != ESP_OK)
{
SYS_LOG_ERR("failed to read prfsdata data, err:%d", err);
return -1;
}
/* 设置并校验 CRC */
if (_set_crc(out_value, tar_length, crc_base))
{
SYS_LOG_WRN("read frs failed, incorrect crc and update");
return 1;
}
return 0;
}
/**
* @brief 校验并更新结构体中 CRC 的值。
* 这将校验整个结构(包含 CRC 本身),因此 CRC 自身的初始值为 0然后更新 CRC 的结果到结构中
*
* @param struct_base 待分析和设置的内存块
* @param struct_length 待分析和设置的内存块的实际长度
* @param crc_base[io] CRC 校验值所在内存块的地址(绝对值)
* @retval 0 校验正确
* @retval 1 校验错误,并且设置了正确的校验值
*/
static int _set_crc(const void *struct_base, size_t struct_length, uint32_t *crc_base)
{
// 校验读出来的数据的正确性
uint32_t crc_read = *crc_base;
*crc_base = 0;
*crc_base = crc32_calc(0, struct_base, struct_length);
return (crc_read != *crc_base);
}
/**
* @brief 立即执行保存 配置数据 到 nvs 的动作
*
* @param arg
*/
static void _work_handler_save(void *arg)
{
_set_crc(&s_cfg_app, sizeof(s_cfg_app), &s_cfg_app.crc32);
nvs_set_blob(g_nvs_hdl, _NVS_KEY_BKUP, &s_cfg_app, sizeof(s_cfg_app));
nvs_set_blob(g_nvs_hdl, _NVS_KEY_MAIN, &s_cfg_app, sizeof(s_cfg_app));
}
/**
* @brief 初始化。将从 nvs 读取已保存的数据到内存 s_cfg_app 中。
* 当出现以下情况时,配置数据被恢复到默认值:
* nvs 未初始化或操作失败;
* 使用 app_cfg_factory_set() 立即恢复到默认值;
* 结构体的长度 s_cfg_app 发生改变;
* APP_CONFIG_DATA_VER 定义的值发生改变;
*
* @retval 0 成功读取已有记录
* @retval 1 已恢复默认设置
* @retval -1 nvs 操作失败
*/
int app_cfg_init(void)
{
int ret = -1;
SYS_ASSERT(g_nvs_hdl != 0, "");
/* 创建延时保存的任务 */
if (!os_work_is_valid(&s_work_hdl_save))
{
os_work_create(&s_work_hdl_save, "", _work_handler_save, NULL, OS_PRIORITY_LOWER);
os_work_submit(default_os_work_q_hdl, &s_work_hdl_save, OS_WAIT_FOREVER);
}
/* 读取映射为 _NVS_KEY_MAIN 数据 */
ret = _read_key(_NVS_KEY_MAIN, &s_cfg_app, sizeof(s_cfg_app), &s_cfg_app.crc32);
if (ret)
{
/* 如果读取失败,则读取备份区并恢复 */
ret = _read_key(_NVS_KEY_BKUP, &s_cfg_app, sizeof(s_cfg_app), &s_cfg_app.crc32);
if (ret)
{
goto use_default_prfs;
}
else
{
SYS_LOG_WRN("Data verification failed, recovered from backup area");
app_cfg_do_save(0);
}
}
if (s_cfg_app.version != APP_CONFIG_DATA_VER || memcmp(s_cfg_app.product_id, PRODUCT_ID, sizeof(s_cfg_app.product_id)) != 0)
{
ret = 1;
SYS_LOG_WRN("Upgrade to the latest version");
goto use_default_prfs;
}
return ret;
use_default_prfs:
app_cfg_factory_set();
app_cfg_do_save(0);
return ret;
}
/**
* @brief 使内存的数据恢复到默认设置
*
* @return int 0
*/
int app_cfg_factory_set(void)
{
memcpy(&s_cfg_app, &s_cfg_app_default, sizeof(s_cfg_app));
/* s_cfg_app.dev_mac_str */
uint8_t macAddress[6];
ESP_ERROR_CHECK(esp_efuse_mac_get_default(macAddress));
snprintf(s_cfg_app.dev_mac_str, sizeof(s_cfg_app.dev_mac_str), "%02X%02X%02X%02X%02X%02X",
macAddress[0], macAddress[1], macAddress[2], macAddress[3], macAddress[4], macAddress[5]);
s_cfg_app.salt_random = rand();
app_cfg_do_save(1000);
return 0;
}
/**
* @brief 立即执行保存 配置数据 到 nvs 的动作
*
* @param delay_ms 0: 立即保存, > 0 多少毫秒后执行保存
* @return int @ref esp_err_t
*/
int app_cfg_do_save(uint32_t delay_ms)
{
if (delay_ms == 0)
{
os_work_suspend(&s_work_hdl_save);
_work_handler_save(NULL);
}
else
{
os_work_resume(&s_work_hdl_save, delay_ms);
}
return 0;
}
/* 写接口 -------------------------------------------------------------------------------------- */
void app_cfg_set_sys_log(bool on)
{
s_cfg_app.sys_log_on = on;
app_cfg_do_save(0);
}
void app_cfg_set_rf_mode_change(void)
{
for (;;)
{
s_cfg_app.rf_mode++;
if (s_cfg_app.rf_mode >= DATA_BRIDGE_RF_MODE_MAX)
{
s_cfg_app.rf_mode = 0;
}
#if !(CONFIG_BUILD_BLE)
if (s_cfg_app.rf_mode == DATA_BRIDGE_RF_MODE_BLE)
{
continue;
}
#endif
#if !(CONFIG_BUILD_WIFI)
if (s_cfg_app.rf_mode == DATA_BRIDGE_RF_MODE_WIFI_AP || s_cfg_app.rf_mode == DATA_BRIDGE_RF_MODE_WIFI_STA)
{
continue;
}
#endif
break;
}
app_cfg_do_save(1000);
}
int app_cfg_set_psPassword(const void *psPassword, int size)
{
if (size > sizeof(s_cfg_app.psPassword))
{
SYS_LOG_WRN("size: %u > %u", size, sizeof(s_cfg_app.psPassword));
return -1;
}
memset(s_cfg_app.psPassword, 0, sizeof(s_cfg_app.psPassword));
if (size > 0)
{
memcpy(s_cfg_app.psPassword, psPassword, size);
}
app_cfg_do_save(1000);
return 0;
}
int app_cfg_set_device_name_ble(const void *device_name_ble, int size)
{
if (size > 19 || size < 1)
{
SYS_LOG_WRN("size: %d", size);
return -1;
}
if (size > sizeof(s_cfg_app.device_name_ble) - 1)
{
SYS_LOG_WRN("size: %u > %u", size, sizeof(s_cfg_app.device_name_ble));
return -1;
}
memset(s_cfg_app.device_name_ble, 0, sizeof(s_cfg_app.device_name_ble));
if (size > 0)
{
memcpy(s_cfg_app.device_name_ble, device_name_ble, size);
}
app_cfg_do_save(0); // 由于下个操作为重启,所以立即需要保存
return 0;
}
void split_string(const char *str, size_t total_size, char delimiter, char result1[], char result2[], char result3[], char result4[], char result5[], char result6[])
{
if (str == NULL)
{
return; // 检查输入字符串是否为空
}
const char *start = str;
const char *end;
SYS_LOG_INF("start addr:%p,size:%d", &str, total_size);
uint8_t index = 0;
char *results[] = {result1, result2, result3, result4, result5, result6};
// 循环查找分隔符并拆解字符串
while (index < sizeof(results) / sizeof(char *))
{
end = strchr(start, delimiter); // 查找分隔符
if (end != NULL && (end - start) < total_size)
{
SYS_LOG_WRN("read:%d", (end - start));
size_t len = end - start; // 计算子字符串长度
strncpy(results[index], start, len); // 复制子字符串到结果数组
results[index][len] = '\0'; // 添加字符串结束符
start = end + 1; // 更新起始指针
}
else
{
size_t remind = total_size - (start - str); // 剩余字符串长度
SYS_LOG_INF("str addr:%p,start addr%p,end addr:%p,remind:%d", str, start, end, remind);
strncpy(results[index], start, remind); // 复制剩余字符串到结果数组
results[index][remind] = '\0'; // 添加字符串结束符
break; // 如果没有更多分隔符,退出循环
}
index++; // 更新索引
}
}
int app_cfg_set_rf_parameters(const void *wifi_parameters, size_t size)
{
if (wifi_parameters == NULL)
{
SYS_LOG_WRN("rf parameters is empty");
return -1;
}
// parse_rf_parameters(wifi_parameters);
split_string((char *)wifi_parameters, size, '\n',
s_cfg_app.device_name_ble,
s_cfg_app.psPassword,
s_cfg_app.app_config_wifi_para.wifi_ap_ssid,
s_cfg_app.app_config_wifi_para.wifi_ap_password,
s_cfg_app.app_config_wifi_para.wifi_sta_ssid,
s_cfg_app.app_config_wifi_para.wifi_sta_password);
SYS_LOG_DUMP(wifi_parameters, sizeof(wifi_parameters), 0, 0);
app_cfg_do_save(0); // 由于下个操作为重启,所以立即需要保存
return 0;
}
int app_cfg_set_drone_data(const void *drone_data, int size)
{
if (size > sizeof(s_cfg_app.drone_data))
{
SYS_LOG_WRN("size: %u > %u", size, sizeof(s_cfg_app.drone_data));
return -1;
}
memset(s_cfg_app.drone_data, 0, sizeof(s_cfg_app.drone_data));
if (size > 0)
{
memcpy(s_cfg_app.drone_data, drone_data, size);
}
app_cfg_do_save(1000);
return 0;
}
int app_cfg_set_wifi_setting_ardupilot_passthrough_tcp_port(uint16_t ardupilot_passthrough_tcp_port)
{
s_cfg_app.wifi_setting.ardupilot_passthrough_tcp_port = ardupilot_passthrough_tcp_port;
app_cfg_do_save(1000);
return 0;
}
int app_cfg_set_wifi_setting_ardupilot_passthrough_udp_port(uint16_t ardupilot_passthrough_udp_port)
{
s_cfg_app.wifi_setting.ardupilot_passthrough_udp_port = ardupilot_passthrough_udp_port;
app_cfg_do_save(1000);
return 0;
}
int app_cfg_set_wifi_setting_ardupilot_passthrough_udp_transfer_in_raw_mode(uint8_t ardupilot_passthrough_udp_transfer_in_raw_mode)
{
s_cfg_app.wifi_setting.ardupilot_passthrough_udp_transfer_in_raw_mode = ardupilot_passthrough_udp_transfer_in_raw_mode;
app_cfg_do_save(1000);
return 0;
}
int app_cfg_set_wifi_setting_wifi_max_connection_num(uint8_t wifi_max_connection_num)
{
s_cfg_app.wifi_setting.wifi_max_connection_num = wifi_max_connection_num;
app_cfg_do_save(1000);
return 0;
}
int app_cfg_set_wifi_setting_uartBaudRate(uint32_t uartBaudRate)
{
s_cfg_app.wifi_setting.uartBaudRate = uartBaudRate;
app_cfg_do_save(1000);
return 0;
}
int app_cfg_set_fc_protocol_type(uint8_t type)
{
s_cfg_app.fc.protocol_type = type;
app_cfg_do_save(1000);
return 0;
}
int app_cfg_set_led_strip_sw(uint8_t sw)
{
s_cfg_app.strip_sw = !!sw;
app_cfg_do_save(100);
return 0;
}
int app_cfg_set_armed2close_rf_sw(bool sw)
{
s_cfg_app.armed2close_rf_sw = !!sw;
app_cfg_do_save(100);
return 0;
}
int app_cfg_set_mac_tab(uint8_t mac[6])
{
for (int i = 0; i < __ARRAY_SIZE(s_cfg_app.mac_tab); i++)
{
if (memcmp(mac, s_cfg_app.mac_tab[i], 6) == 0)
{
return 0;
}
}
if (++s_cfg_app.mac_index >= __ARRAY_SIZE(s_cfg_app.mac_tab))
{
s_cfg_app.mac_index = 0;
}
memcpy(s_cfg_app.mac_tab[s_cfg_app.mac_index], mac, sizeof(s_cfg_app.mac_tab[0]));
app_cfg_do_save(1000);
return 0;
}

161
app/config/app_config.h Normal file
View File

@@ -0,0 +1,161 @@
/**
* @file app_config.h
* @author LokLiang
* @brief 用于应用控制程序的统一配置的数据结构
* @version 0.1
* @date 2023-11-24
*
* @copyright Copyright (c) 2023
*
* 本模块实现功能:
* 1. 统一定义应用程序需要存储的所有数据;
* 2. 依赖 nfs 模块,使当前数据与内部存储的数据实现同步;
* 3. 依赖 sh_vset 模块,并提供其对应的所有设置实现函数;
*
* 对数据的读操作:
* 上层应用:应用和模式管理模块和控制模块
* 对外提数据结构为 g_cfg_app 指针,指内部的对应的内存,程序只读;
*
* 对数据的写操作:
* 上层应用:
* 为所有成员提供专门的写入接口,内部数据被立即更新。
* 这些接口可用于 shell 的设置函数。
*
* 数据的默认值:
* 当出现以下情况时,配置数据被恢复到默认值:
* nvs 未初始化或操作失败;
* 使用 app_cfg_factory_set() 恢复到默认值;
* 结构体的长度 cfg_app_t 发生改变;
* APP_CONFIG_DATA_VER 定义的值发生改变(在 app_config.c 内部定义);
*
*/
#pragma once
#include "sys_types.h"
/* 数据结构 ------------------------------------------------------------------------------------ */
#define MAX_WIFI_STA_NUM 12 /* cfg_app_t::wifi_setting.wifi_max_connection_num 的范围最大合法值 */
#define MIN_WIFI_STA_NUM 1 /* cfg_app_t::wifi_setting.wifi_max_connection_num 的范围最小合法值 */
typedef enum // 当前的无线数据模式,由按键控制更改
{
DATA_BRIDGE_RF_MODE_OFF, // 设置数据桥接模式:关闭所有数据接口
DATA_BRIDGE_RF_MODE_BLE, // 设置数据桥接模式:仅使用 UART <==> BLE
DATA_BRIDGE_RF_MODE_WIFI_AP, // 设置数据桥接模式:仅使用 UART <==> WIFI(AP)
DATA_BRIDGE_RF_MODE_WIFI_STA, // 设置数据桥接模式:仅使用 UART <==> WIFI(STA)
DATA_BRIDGE_RF_MODE_MAX,
} data_bridge_rf_mode_t;
typedef struct __packed // 由 APP 通过命令设置, WIFI 部分的透传设置
{
uint16_t ardupilot_passthrough_tcp_port; // tcp 透传端口(本地端口)
uint16_t ardupilot_passthrough_udp_port; // udp 透传端口(本地端口)
uint8_t ardupilot_passthrough_udp_transfer_in_raw_mode; // UDP 连接下, 0: 解析飞控数据帧转发, 1: 直接转发原文
uint8_t wifi_max_connection_num; // WIFI 最多的连接数。范围 MIN_WIFI_STA_NUM..MAX_WIFI_STA_NUM
uint32_t uartBaudRate; // 串口透传波特率
} cfg_app_wifi_setting_t;
typedef struct // 设备配置统一数据结构
{
uint32_t version; // 配置版本,修改 APP_CONFIG_DATA_VER 的值后, nvs 已同步的数据被恢复到默认值
int sys_log_on; // 打开调试日志
data_bridge_rf_mode_t rf_mode; // 由按键控制切换的,当前的无线接口
char psPassword[12]; // 由 APP 通过命令设置,透传密码
char device_name_ble[32]; // 由 APP 通过命令设置, BLE 设备名,必须包含结束符
char drone_data[12]; // 由 APP 通过命令设置,表示记录配置
cfg_app_wifi_setting_t wifi_setting; // 由 APP 通过命令设置, WIFI 部分的透传设置
uint8_t wifi_ap_ipv4[4]; // WIFI AP 模式下的 IP 地址
struct
{
char wifi_ap_ssid[33]; // WIFI AP 模式下的 SSID
char wifi_ap_password[65]; // WIFI AP 模式下的 密码
char wifi_sta_ssid[33]; // WIFI STA 模式下的 SSID
char wifi_sta_password[33]; // WIFI STA 模式下的 密码
} app_config_wifi_para;
uint16_t udp_port_passthrough_broadcast; // udp 透传端口(广播端口)
uint16_t udp_port_command; // udp 命令端口(本地端口)
uint16_t tcp_port_command; // tcp 命令端口(本地端口)
uint16_t tcp_port_telnet; // tcp telnet 端口(默认23)
uint16_t tcp_port_dfu; // tcp DFU 升级服务端口(本地端口)
uint16_t tcp_port_msc; // tcp 黑匣子服务端口(本地端口)
char dev_mac_str[14]; // 通过 esp_base_mac_addr_get() 获取的 MAC 地址的字符串形式
uint32_t salt_random; // 32 位的随机值,生成唯一 ID 的附加值
struct // 功能模块开关
{
bool pwrup_light : 1; // 短亮黄灯表示启动(方便观察是否有重启)
bool ble : 1; // 使用 BLE
bool ap : 1; // 使用 WIFI AP 模式
bool sta : 1; // 使用 WIFI STA 模式
uint32_t reserve : 24;
} capacity;
struct
{
uint8_t protocol_type; // @ref fc_comm_protocol_type_t
} fc;
uint8_t strip_sw; // 记录当前灯带的模拟开关状态0: 表示切换到由飞控控制, 1: 表示切换到由本固件控制
uint8_t armed2close_rf_sw; // 解锁后自动关闭射频功能的开关0: 关, 1: 开
uint8_t mac_index; // 记录当前通过 WIFI 连接过的 APP 的 MAC 地址的序号
uint8_t mac_tab[6][6]; // 记录当前通过 WIFI 连接过的 APP 的 MAC 地址, 6 个 MAC 地址,每个 MAC 地址 6 字节,判断是否 APP 设备连接,用于决定是否停止对飞控状态监测
uint8_t product_id[6]; // 产品 ID
uint32_t reserve[32]; // 预留占位,下次更改时保证参数总长度不变,如超过预留长度后则当作新数据处理
uint32_t crc32; // 校验数据(可放于任何位置)
} cfg_app_t;
/* nvs 接口 ------------------------------------------------------------------------------------ */
int app_cfg_init(void); // 初始化
int app_cfg_factory_set(void); // 使内存的数据恢复到默认设置,注意不会被立即保存到 nvs 中
int app_cfg_do_save(uint32_t delay_ms); // 保存配置数据 到 nvs 的动作
/* 读接口 -------------------------------------------------------------------------------------- */
extern const cfg_app_t *g_cfg_app; // 指向 cfg_app_t 内存,只读,在 app_cfg_init() 执行初始化后可用
extern const int *g_sys_log_on;
/* 写接口 -------------------------------------------------------------------------------------- */
void app_cfg_set_sys_log(bool on);
void app_cfg_set_rf_mode_change(void); // 设置 g_cfg_app->rf_mode 的下一个模式
int app_cfg_set_psPassword(const void *psPassword, int size);
int app_cfg_set_device_name_ble(const void *device_name_ble, int size);
int app_cfg_set_drone_data(const void *drone_data, int size);
int app_cfg_set_wifi_setting_ardupilot_passthrough_tcp_port(uint16_t ardupilot_passthrough_tcp_port); // TCP 透传端口(本地端口)
int app_cfg_set_wifi_setting_ardupilot_passthrough_udp_port(uint16_t ardupilot_passthrough_udp_port); // UDP 透传端口(本地端口)
int app_cfg_set_wifi_setting_ardupilot_passthrough_udp_transfer_in_raw_mode(uint8_t ardupilot_passthrough_udp_transfer_in_raw_mode); // UDP 连接下,直接转发原文
int app_cfg_set_wifi_setting_wifi_max_connection_num(uint8_t wifi_max_connection_num); // WIFI 最多的连接数。范围 MIN_WIFI_STA_NUM..MAX_WIFI_STA_NUM
int app_cfg_set_wifi_setting_uartBaudRate(uint32_t uartBaudRate); // 串口透传波特率
int app_cfg_set_fc_protocol_type(uint8_t type);
int app_cfg_set_led_strip_sw(uint8_t sw);
int app_cfg_set_armed2close_rf_sw(bool sw);
int app_cfg_set_rf_parameters(const void *wifi_parameters, size_t size);
int app_cfg_set_mac_tab(uint8_t mac[6]);

View File

@@ -0,0 +1,58 @@
#if (CONFIG_PRODUCT_ID_SBDEMO)
#define _DEFAULT_DEVICE_NAME_BLE "demo V1"
#define _DEFAULT_DEVICE_NAME_WIFI "demo V1-WIFI"
#define _DEFAULT_DRONE_DATA "MODE1_1.0"
static cfg_app_t const s_cfg_app_default = {
.version = APP_CONFIG_DATA_VER, // 配置版本,修改源码中此版本的值后, nvs 已同步的数据被恢复到默认值
#if (CONFIG_BUILD_BLE)
.rf_mode = DATA_BRIDGE_RF_MODE_BLE, // 当前的无线数据模式,由按键控制更改
#elif (CONFIG_BUILD_WIFI)
.rf_mode = DATA_BRIDGE_RF_MODE_WIFI_AP, // 设置数据桥接模式:仅使用 UART <==> WIFI(AP)
#else
.rf_mode = DATA_BRIDGE_RF_MODE_OFF, // 设置数据桥接模式:关闭所有数据接口
#endif
.psPassword = "", // 透传密码
.device_name_ble = _DEFAULT_DEVICE_NAME_BLE, // BLE 设备名
.drone_data = _DEFAULT_DRONE_DATA, // 由 APP 通过命令设置,表示记录配置
/* 由 APP 通过命令设置, WIFI 部分的透传设置 */
.wifi_setting = {
.ardupilot_passthrough_tcp_port = 4278,
.ardupilot_passthrough_udp_port = 14550,
.ardupilot_passthrough_udp_transfer_in_raw_mode = 1, // UDP 连接下, 0: 解析飞控数据帧转发, 1: 直接转发原文(默认)
.wifi_max_connection_num = 1, // WIFI 最多的连接数。范围 MIN_WIFI_STA_NUM..MAX_WIFI_STA_NUM
.uartBaudRate = 115200, // 串口透传波特率
},
.wifi_ap_ipv4 = {192, 168, 1, 1}, // WIFI AP 模式下的 IP 地址
.app_config_wifi_para.wifi_ap_ssid = _DEFAULT_DEVICE_NAME_WIFI, // WIFI AP 模式下的 SSID
.app_config_wifi_para.wifi_ap_password = "", // WIFI AP 模式下的 密码
.app_config_wifi_para.wifi_sta_ssid = "eFLY", // WIFI STA 模式下的 SSID
.app_config_wifi_para.wifi_sta_password = "88888888", // WIFI STA 模式下的 密码
.udp_port_passthrough_broadcast = 14550, // udp 透传端口(广播端口) TODO
.udp_port_command = 14551, // udp 命令端口(本地端口)
.tcp_port_command = 4279, // tcp 命令端口(本地端口)
.tcp_port_telnet = 23, // tcp telnet 端口(默认23)
.tcp_port_dfu = 4280, // tcp DFU 升级服务端口(本地端口)
.tcp_port_msc = 4281, // tcp 黑匣子服务端口(本地端口)
/* 功能模块开关 */
.capacity = {
.pwrup_light = 0, // 短亮黄灯表示启动(方便观察是否有重启)
.ble = 1, // 使用 BLE
.ap = 0, // 使用 WIFI AP 模式
.sta = 0, // 使用 WIFI STA 模式
},
.armed2close_rf_sw = 1, // 解锁后自动关闭射频功能的开关0:关, 1:开
.product_id = PRODUCT_ID, // 产品 ID
};
#endif

View File

@@ -0,0 +1,58 @@
#if (CONFIG_PRODUCT_ID_DEV)
#define _DEFAULT_DEVICE_NAME_BLE "developing V1"
#define _DEFAULT_DEVICE_NAME_WIFI "developing V1-WIFI"
#define _DEFAULT_DRONE_DATA "MODE1_1.0"
static cfg_app_t const s_cfg_app_default = {
.version = APP_CONFIG_DATA_VER, // 配置版本,修改源码中此版本的值后, nvs 已同步的数据被恢复到默认值
#if (CONFIG_BUILD_BLE)
.rf_mode = DATA_BRIDGE_RF_MODE_BLE, // 当前的无线数据模式,由按键控制更改
#elif (CONFIG_BUILD_WIFI)
.rf_mode = DATA_BRIDGE_RF_MODE_WIFI_AP, // 设置数据桥接模式:仅使用 UART <==> WIFI(AP)
#else
.rf_mode = DATA_BRIDGE_RF_MODE_OFF, // 设置数据桥接模式:关闭所有数据接口
#endif
.psPassword = "", // 透传密码
.device_name_ble = _DEFAULT_DEVICE_NAME_BLE, // BLE 设备名
.drone_data = _DEFAULT_DRONE_DATA, // 由 APP 通过命令设置,表示记录配置
/* 由 APP 通过命令设置, WIFI 部分的透传设置 */
.wifi_setting = {
.ardupilot_passthrough_tcp_port = 4278,
.ardupilot_passthrough_udp_port = 14550,
.ardupilot_passthrough_udp_transfer_in_raw_mode = 1, // UDP 连接下, 0: 解析飞控数据帧转发, 1: 直接转发原文(默认)
.wifi_max_connection_num = 1, // WIFI 最多的连接数。范围 MIN_WIFI_STA_NUM..MAX_WIFI_STA_NUM
.uartBaudRate = 115200, // 串口透传波特率
},
.wifi_ap_ipv4 = {192, 168, 1, 1}, // WIFI AP 模式下的 IP 地址
.app_config_wifi_para.wifi_ap_ssid = _DEFAULT_DEVICE_NAME_WIFI, // WIFI AP 模式下的 SSID
.app_config_wifi_para.wifi_ap_password = "", // WIFI AP 模式下的 密码
.app_config_wifi_para.wifi_sta_ssid = "eFLY", // WIFI STA 模式下的 SSID
.app_config_wifi_para.wifi_sta_password = "88888888", // WIFI STA 模式下的 密码
.udp_port_passthrough_broadcast = 14550, // udp 透传端口(广播端口) TODO
.udp_port_command = 14551, // udp 命令端口(本地端口)
.tcp_port_command = 4279, // tcp 命令端口(本地端口)
.tcp_port_telnet = 23, // tcp telnet 端口(默认23)
.tcp_port_dfu = 4280, // tcp DFU 升级服务端口(本地端口)
.tcp_port_msc = 4281, // tcp 黑匣子服务端口(本地端口)
/* 功能模块开关 */
.capacity = {
.pwrup_light = 0, // 短亮黄灯表示启动(方便观察是否有重启)
.ble = 1, // 使用 BLE
.ap = 0, // 使用 WIFI AP 模式
.sta = 0, // 使用 WIFI STA 模式
},
.armed2close_rf_sw = 1, // 解锁后自动关闭射频功能的开关0:关, 1:开
.product_id = PRODUCT_ID, // 产品 ID
};
#endif

View File

@@ -1,8 +1,13 @@
#include "board_config.h"
#include "config/version.h"
#include "driver/uart.h"
#include "hal/spi_types.h"
#include "utils/crc.h"
#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_DBG
#include "esp_partition.h"
#include "spi_flash_mmap.h"
#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_ERR
#define SYS_LOG_DOMAIN "CFG"
#define CONS_ABORT()
#include "sys_log.h"
@@ -11,4 +16,239 @@
#include "board_config/devkit_esp32c3.h"
#include "board_config/devkit_esp32s3.h"
typedef struct
{
uint8_t license_code[CONFIG_LICENSE_CODE_LENGTH]; // 在烧录器设置的保存滚码的字段
uint32_t crc_32; // 从 size 成员开始到数据结束的 crc32 校验值
uint16_t size; // 整个数据结构的长度
uint16_t version; // 仅指本结构的版本代号
cfg_board_t cfg; // 板载配置数据
} __cfg_board_partition_t;
static const esp_partition_t *s_partition_hdl;
const __cfg_board_partition_t *p_cfg_board_partition;
const cfg_board_t *g_cfg_board = &s_cfg_board_default;
const uint8_t *g_license_code;
static void _read_partition_data(__cfg_board_partition_t *out, const cfg_board_t *cfg);
static const uint8_t *_read_legacy_license_code(void);
static uint32_t _get_data_crc(const __cfg_board_partition_t *cfg);
/**
* @brief 初始化,使 g_cfg_board 指向正确的
* @retval 0 指向了自定义分区中的数据
* @retval 1 在自定义分区中的数据校验失败,指向了默认的配置
*/
int board_cfg_init(void)
{
s_partition_hdl = esp_partition_find_first((esp_partition_type_t)CONFIG_CFG_BOARD_PARTITION_TYPE,
(esp_partition_subtype_t)CONFIG_CFG_BOARD_PARTITION_SUBTYPE,
CONFIG_CFG_BOARD_PARTITION_NAME);
*(int *)g_sys_log_on = 1;
if (s_partition_hdl == NULL)
{
g_license_code = _read_legacy_license_code();
SYS_LOG_ERR("esp_partition_find_first() fail");
return 1;
}
const void *cfg_board_partition;
esp_partition_mmap_handle_t mmp_data_handle;
esp_err_t err = esp_partition_mmap(s_partition_hdl, 0, 0x1000, ESP_PARTITION_MMAP_DATA, &cfg_board_partition, &mmp_data_handle);
if (err != ESP_OK)
{
g_license_code = _read_legacy_license_code();
SYS_LOG_ERR("esp_partition_mmap() fail");
return 1;
}
p_cfg_board_partition = cfg_board_partition;
g_license_code = p_cfg_board_partition->license_code;
if (p_cfg_board_partition->version != BOARD_CONFIG_DATA_VER ||
p_cfg_board_partition->size != sizeof(__cfg_board_partition_t) ||
p_cfg_board_partition->crc_32 != _get_data_crc(p_cfg_board_partition))
{
SYS_LOG_WRN("no config data, using default config");
return 1;
}
else
{
g_cfg_board = &p_cfg_board_partition->cfg;
SYS_LOG_INF("config data checked valid");
/* 如果代码区中的析载数据长度大于已有的数据区配置长度,则把多出来的部分添加到数据区中 */
if (sizeof(__cfg_board_partition_t) > p_cfg_board_partition->size &&
p_cfg_board_partition->size > sizeof(__cfg_board_partition_t) - sizeof(cfg_board_t))
{
cfg_board_t new_cfg;
memcpy(&new_cfg, &s_cfg_board_default, sizeof(new_cfg)); // 复制新的默认配置
memcpy(&new_cfg, g_cfg_board, sizeof(cfg_board_t) - (sizeof(__cfg_board_partition_t) - p_cfg_board_partition->size)); // 用旧的固化数据覆已有部分
board_cfg_fresh(&new_cfg); // 固化新配置
}
return 0;
}
}
/**
* @brief 写入激活码
*
* @param license_code
* @return int
*/
int board_license_fresh(const uint8_t license_code[CONFIG_LICENSE_CODE_LENGTH])
{
if (p_cfg_board_partition == NULL)
{
SYS_LOG_WRN("never exec board_cfg_init()");
return -1;
}
__cfg_board_partition_t cfg_buf_partition;
memcpy(&cfg_buf_partition, p_cfg_board_partition, sizeof(cfg_buf_partition));
memcpy(&cfg_buf_partition.license_code[0], license_code, sizeof(cfg_buf_partition.license_code));
cfg_buf_partition.version = BOARD_CONFIG_DATA_VER;
cfg_buf_partition.size = sizeof(cfg_buf_partition);
esp_err_t err;
err = esp_partition_erase_range(s_partition_hdl, 0, 0x1000);
if (err != ESP_OK)
{
SYS_LOG_WRN("esp_partition_erase_range() fail. err: %d", err);
return 1;
}
err = esp_partition_write_raw(s_partition_hdl, 0, &cfg_buf_partition, sizeof(cfg_buf_partition));
if (err != ESP_OK)
{
SYS_LOG_WRN("esp_partition_read_raw() fail. err: %d", err);
return 1;
}
SYS_LOG_INF("done.");
return 0;
}
/**
* @brief 把板载数据固化到自定义分区中。
* 当数据被固化后,除非擦除芯片,否则在代码区的配置数据不生效
*
* @param cfg
* @return int
*/
int board_cfg_fresh(const cfg_board_t *cfg)
{
if (p_cfg_board_partition == NULL)
{
SYS_LOG_WRN("never exec board_cfg_init()");
return -1;
}
__cfg_board_partition_t cfg_buf_partition;
_read_partition_data(&cfg_buf_partition, cfg);
esp_err_t err;
err = esp_partition_erase_range(s_partition_hdl, 0, 0x1000);
if (err != ESP_OK)
{
SYS_LOG_WRN("esp_partition_erase_range() fail. err: %d", err);
return 1;
}
err = esp_partition_write_raw(s_partition_hdl, 0, &cfg_buf_partition, sizeof(cfg_buf_partition));
if (err != ESP_OK)
{
SYS_LOG_WRN("esp_partition_read_raw() fail. err: %d", err);
return 1;
}
g_cfg_board = &p_cfg_board_partition->cfg;
SYS_LOG_INF("done.");
return 0;
}
/**
* @brief 通过终端打印 cfg 的二进制数,用于帮助制作对应 __cfg_board_partition_t 的 bin 文件。
*
* @param cfg 如果为 NULL 时则表示打印保存在自定义分区位置的数据
* @retval 0 操作成功
* @retval -1 数据校验无效
*/
int board_cfg_dump(const cfg_board_t *cfg)
{
if (p_cfg_board_partition == NULL)
{
SYS_LOG_WRN("never exec board_cfg_init()");
return -1;
}
if (cfg == NULL)
{
if (p_cfg_board_partition->size > 0x1000 ||
p_cfg_board_partition->size < CONFIG_LICENSE_CODE_LENGTH ||
p_cfg_board_partition->crc_32 != _get_data_crc(p_cfg_board_partition))
{
SYS_LOG_WRN("No internal records, currently displayed as default configuration");
}
cfg = &s_cfg_board_default;
}
__cfg_board_partition_t cfg_buf_partition;
_read_partition_data(&cfg_buf_partition, cfg);
SYS_LOG_WRN("The data in the custom partition is as follows %u bytes:\r\n", sizeof(cfg_buf_partition));
int i = 0;
int len;
const uint8_t *p = (uint8_t *)&cfg_buf_partition;
SYS_PRINT("/* license code */\r\n");
len = sizeof(cfg_buf_partition.license_code);
for (i = 0; i < len; i++)
SYS_PRINT("0x%02X,%s", *p++, i + 1 < len ? " " : "\r\n");
SYS_PRINT("/* crc, size, version */\r\n");
len = cfg_buf_partition.size - sizeof(cfg_buf_partition.license_code) - sizeof(cfg_buf_partition.cfg);
for (i = 0; i < len; i++)
SYS_PRINT("0x%02X,%s", *p++, i + 1 < len ? " " : "\r\n");
SYS_PRINT("/* board configuration (from %s area) */\r\n", cfg == &s_cfg_board_default ? "code" : "data");
len = sizeof(cfg_buf_partition.cfg);
for (i = 0; i < len; i++)
SYS_PRINT("0x%02X,%s", *p++, i + 1 < len ? " " : "\r\n");
SYS_PRINT("\r\n");
return 0;
}
static void _read_partition_data(__cfg_board_partition_t *out, const cfg_board_t *cfg)
{
memcpy(out, p_cfg_board_partition, sizeof(*out));
memcpy(&out->cfg, cfg, sizeof(out->cfg));
out->version = BOARD_CONFIG_DATA_VER;
out->size = sizeof(*out);
out->crc_32 = _get_data_crc(out);
}
/**
* @brief 旧的版本的激活码所在(历史遗留)
*
* @return const uint8_t* license_code
*/
static const uint8_t *_read_legacy_license_code(void)
{
spi_flash_mmap_handle_t handle;
const uint32_t LEGACY_LICENSE_CODE_START_ADDR = 0x3E0000;
uint32_t start = LEGACY_LICENSE_CODE_START_ADDR;
uint32_t end = LEGACY_LICENSE_CODE_START_ADDR + CONFIG_LICENSE_CODE_LENGTH;
const void *license_code;
spi_flash_mmap(LEGACY_LICENSE_CODE_START_ADDR, end - start, SPI_FLASH_MMAP_DATA, &license_code, &handle);
return license_code;
}
static uint32_t _get_data_crc(const __cfg_board_partition_t *cfg)
{
int cfg_size = cfg->size - sizeof(cfg->license_code) - sizeof(cfg->crc_32);
return crc32_calc(0, &cfg->size, cfg_size);
}

View File

@@ -19,25 +19,94 @@
#pragma once
#define CONFIG_LICENSE_CODE_LENGTH 16 /* 在烧录器设置的保存滚码的字段长度(g_license_code 指向的数据的实际长度) */
#define CONFIG_CFG_BOARD_PARTITION_NAME "bcfg" /* 对应自定义分区表 partitions.csv 中的 Name 字段 */
#define CONFIG_CFG_BOARD_PARTITION_TYPE 0x40 /* 对应自定义分区表 partitions.csv 中的 Type 字段 */
#define CONFIG_CFG_BOARD_PARTITION_SUBTYPE 0x00 /* 对应自定义分区表 partitions.csv 中的 SubType 字段 */
#include "drivers/chip/_hal.h"
#include "sdkconfig.h"
#define GPIO_USED(pin) ((pin) < 255)
#if (CONFIG_IDF_TARGET_ESP32S3) /* 带 OTG 的平台 */
#define CAP_USBOTG 1
#else
#define CAP_USBOTG 0
#endif
typedef struct // 对应 GPIO 单个引脚的输入/输出配置的基础定义
{
uint8_t pin; // 引脚号 (0~254, 255 表示不使用) --OK
uint8_t pin; // 引脚号 (0~254, 255 表示不使用)
uint8_t en_lev; // 触发电平
} cfg_board_pin_io_t;
typedef struct // LED: WS2812
{
hal_id_t spi_id; // 模拟 PWM 用的 SPI
uint8_t strip_pin[4]; // 用于驱动灯带的输出引脚。[0] 为默认脚,[1..3] 为附加的另外几路,非 255 表示有效,程序根据这些脚判断接多少根灯带
uint8_t bat_led_pin; // 用于指示电池电量的 LED 灯,非 255 表示有效
uint8_t rf_status_pin; // 板上用于指示状态的 RGB 灯珠,非 255 表示有效
} cfg_board_led_spi_t;
typedef enum __packed // 用于与飞控通讯的数据接口
{
CFG_BOARD_FC_PORT_UART = 0, // 使用串口与飞控通讯
CFG_BOARD_FC_PORT_USBH, // 使用 USB Host 与飞控通讯
} cfg_board_fc_port_t;
typedef struct // 数据结构一旦定下不可随意变更
{
char firmware_str[7];
char platform_str[13];
char board_name[32];
/* 硬件描述类 */
hal_uart_hdl_t uart_console; // 控制台
cfg_board_pin_io_t key_boot; // 启动按键
hal_uart_hdl_t uart_fc; // 与飞控连接的串口
cfg_board_led_spi_t led_spi; // LED
cfg_board_fc_port_t fc_port_type; // 用于与飞控通讯的数据接口
struct // ISP 控制脚
{
uint8_t nrst; // 复位控制引脚
uint8_t boot0; // boot0 控制引脚
} io_isp;
struct // USB OTG 引脚
{
uint8_t usb_dm;
uint8_t usb_dp;
} io_usb;
cfg_board_pin_io_t detect_usb; // 输入检测:检测飞控 USB 插入,有效电平为插入电平
cfg_board_pin_io_t key_reset; // 按键检测:开机时长安 10 秒恢复出厂设置的按键,有效电平为按下电平
cfg_board_pin_io_t key_led_strip_switch; // 按键检测:切换灯带效果,适用于灯带固件,有效电平为按下电平
cfg_board_pin_io_t key_rf_switch; // 按键检测:切换射频数据接口,适用于固定翼无线 USB 板,有效电平为按下电平
cfg_board_pin_io_t key_9v_switch; // 按键检测9伏电源开关控制适用 F7V4, 有效电平为按下电平
cfg_board_pin_io_t sw_led_strip; // 灯带控制主机的切换控制引脚,有效电平为切换为本模块控制的电平
cfg_board_pin_io_t sw_pwr_9v; // 9V 电源输出控制引脚,有效电平为打开 9V 电源的电平
cfg_board_pin_io_t sw_usb; // USB 切换的模拟开关,有效电平为切换为本模块控制的电平
cfg_board_pin_io_t led_rf_status; // 射频指示灯,单色 LED. 引脚号值为 0 时,射频连接状态通过灯带展示,值为非 0 时,射频连接状态通过这个引脚控制的 LED 展示,有效电平为点亮电平
cfg_board_pin_io_t led_strip_on; // 灯带控制主机切换的指示灯,有效电平为点亮电平
cfg_board_pin_io_t led_bat[4]; // 电池电量指示灯,有效电平为点亮电平
/* 产品功能描述类 */
} cfg_board_t;
extern const uint8_t *g_license_code; // 在烧录器设置的保存滚码的字段,其对应的长度为 CONFIG_LICENSE_CODE_LENGTH
extern const cfg_board_t *g_cfg_board; // 配置数据
int board_cfg_init(void); // 初始化,使 g_cfg_board 指向正确的
int board_license_fresh(const uint8_t license_code[CONFIG_LICENSE_CODE_LENGTH]); // 更新激活码
int board_cfg_fresh(const cfg_board_t *cfg); // 更新设置
int board_cfg_dump(const cfg_board_t *cfg);

View File

@@ -3,6 +3,9 @@
#define CONFIG_IDF_TARGET "esp32c2" /* 警告:请使用命令 idf.py set-target <参数> 选择对应的平台 */
static cfg_board_t const s_cfg_board_default = {
.firmware_str = PRODUCT_ID,
.platform_str = CONFIG_IDF_TARGET,
.board_name = "ESP32-C2-Devkitc",
/* 控制台串口 */
.uart_console = {
.pin_txd = {43, _GPIO_DIR_OUT, _GPIO_PUD_PULL_UP},
@@ -13,10 +16,17 @@ static cfg_board_t const s_cfg_board_default = {
},
/* 启动按键 */
.key_boot = {
.key_reset = {
.pin = 9, // 用于切换灯效
.en_lev = 0, // 用于切换灯效
},
.led_spi = {
.spi_id = SPI2_HOST, // 模拟 PWM 用的 SPI
.strip_pin = {10, ~0, ~0, ~0}, // 用于驱动灯带的输出引脚
.bat_led_pin = ~0, // 用于指示电池电量的 LED 灯,非 ~0 表示有效
.rf_status_pin = ~0, // 板上用于指示状态的 RGB 灯珠,非 ~0 表示有效
},
};
#endif

View File

@@ -3,6 +3,9 @@
#define CONFIG_IDF_TARGET "esp32c3" /* 警告:请使用命令 idf.py set-target <参数> 选择对应的平台 */
static cfg_board_t const s_cfg_board_default = {
.firmware_str = PRODUCT_ID,
.platform_str = CONFIG_IDF_TARGET,
.board_name = "ESP32-C3-Devkitc",
/* 控制台串口 */
.uart_console = {
.pin_txd = {43, _GPIO_DIR_OUT, _GPIO_PUD_PULL_UP},
@@ -13,10 +16,17 @@ static cfg_board_t const s_cfg_board_default = {
},
/* 启动按键 */
.key_boot = {
.key_reset = {
.pin = 9, // 用于切换灯效
.en_lev = 0, // 用于切换灯效
},
.led_spi = {
.spi_id = SPI2_HOST, // 模拟 PWM 用的 SPI
.strip_pin = {18, ~0, ~0, ~0}, // 用于驱动灯带的输出引脚
.bat_led_pin = ~0, // 用于指示电池电量的 LED 灯,非 ~0 表示有效
.rf_status_pin = ~0, // 板上用于指示状态的 RGB 灯珠,非 ~0 表示有效
},
};
#endif

View File

@@ -3,6 +3,9 @@
#define CONFIG_IDF_TARGET "esp32s3" /* 警告:请使用命令 idf.py set-target <参数> 选择对应的平台 */
static cfg_board_t const s_cfg_board_default = {
.firmware_str = PRODUCT_ID,
.platform_str = CONFIG_IDF_TARGET,
.board_name = "ESP32-S3-Devkitc",
/* 控制台串口 */
.uart_console = {
.pin_txd = {255, _GPIO_DIR_OUT, _GPIO_PUD_PULL_UP},
@@ -13,10 +16,17 @@ static cfg_board_t const s_cfg_board_default = {
},
/* 启动按键 */
.key_boot = {
.key_reset = {
.pin = 0, // 用于切换灯效
.en_lev = 0, // 用于切换灯效
},
.led_spi = {
.spi_id = SPI2_HOST, // 模拟 PWM 用的 SPI
.strip_pin = {18, ~0, ~0, ~0}, // 用于驱动灯带的输出引脚
.bat_led_pin = ~0, // 用于指示电池电量的 LED 灯,非 ~0 表示有效
.rf_status_pin = ~0, // 板上用于指示状态的 RGB 灯珠,非 ~0 表示有效
},
};
#endif

View File

@@ -10,23 +10,21 @@
#define BOARD_CONFIG_DATA_VER 0x0101 /* 对应 cfg_board_t::version */
#define APP_CONFIG_DATA_VER 0 /* 对应 cfg_app_t::version */
#define LEDSTRIP_CONFIG_DATA_VER 0x00000001 /* 对应 cfg_led_strip_t ::version */
#define FW_VERSION_MAIN 0
#define FW_VERSION_MAIN 1
#define FW_VERSION_MINOR 0
#define FW_VERSION_BUILD 1
#define FW_VERSION_BUILD 0
#elif (CONFIG_PRODUCT_ID_SBLED1)
#elif (CONFIG_PRODUCT_ID_SBDEMO)
#define PRODUCT_ID "SBLED1"
#define PRODUCT_ID "SBDEMO"
#define BOARD_CONFIG_DATA_VER 0x0101 /* 对应 cfg_board_t::version */
#define APP_CONFIG_DATA_VER 0 /* 对应 cfg_app_t::version */
#define LEDSTRIP_CONFIG_DATA_VER 0x00000001 /* 对应 cfg_led_strip_t ::version */
#define FW_VERSION_MAIN 6
#define FW_VERSION_MINOR 1
#define FW_VERSION_BUILD 2
#define FW_VERSION_MAIN 1
#define FW_VERSION_MINOR 0
#define FW_VERSION_BUILD 0
#else
#error 未定义配置