跳转至内容
BakeHub 工科开源社群

世界

本论坛之外的主题。此处表达的观点和意见可能不代表本论坛及其成员的立场。

海量内容尽在指尖 …

不妨将此视为您专属的全球发现信息流。它汇集了来自互联网各处及其他社区的有趣讨论,一应俱全。

虽然您可以浏览当前的热门内容,但使用该信息流的最佳方式是将其个性化。通过注册账号,您可以关注特定的创作者和主题,从而过滤掉无关信息,只查看对您真正重要的内容。

准备好开始了吗?注册一个账号,即可关注他人、在收到回复时获得通知,并收藏您喜欢的内容。

注册 登录
  • KongCharK

    MSPM0G3507 开发教程:从零开始构建嵌入式应用

    一、芯片概述与选型背景

    MSPM0G3507 是德州仪器(TI)推出的基于 ARM Cortex-M0+ 内核的 32 位微控制器,隶属于 MSPM0G 系列。该系列主打 低成本、低功耗、高集成度,适用于工业控制、传感检测、便携设备等场景。

    核心规格速览

    参数 数值
    内核 ARM Cortex-M0+,最高 80 MHz
    Flash 128 KB
    SRAM 32 KB
    工作电压 1.62V ~ 3.6V
    ADC 12 位,最多 17 通道,1 Msps
    定时器 4 个 16 位高级定时器,多个通用定时器
    通信接口 2×UART、2×SPI、2×I2C、CAN 2.0
    比较器/运放 2×零漂移运放,1×通用比较器
    GPIO 最多 60 个可编程 IO
    低功耗模式 STOP、STANDBY、SHUTDOWN
    封装 LQFP-64、LQFP-48、QFN-40、QFN-32 等

    注意:G 系列定位为“通用型”,相比同门的 L 系列(低功耗极致)和 C 系列(成本优先),G3507 在性能与外设丰富度上更均衡。


    二、开发环境搭建

    2.1 官方 IDE:CCS(Code Composer Studio)

    推荐使用 TI 官方 CCS Theia 版(基于 Eclipse Theia),支持 MSPM0 全系列。

    1. 访问 TI 官网下载 CCS Theia(需注册 myTI 账号)。
    2. 安装时勾选 MSPM0 支持包
    3. 首次启动后,通过 View -> Resource Explorer 导入官方例程。

    2.2 备选方案:Keil MDK

    MSPM0G3507 也支持 Keil MDK(需安装 Device Family Pack)。

    • 在 Keil Pack Installer 中搜索 MSPM0G3507,安装对应的 DFP 包。
    • 注意:Keil 下工程配置需手动指定 Flash 和 RAM 起始地址(见后文链接脚本)。

    2.3 调试器选择

    • XDS110:官方推荐,支持 SWD 接口,CCS 原生支持。
    • DAP-Link / CMSIS-DAP:第三方开源调试器也可用,需在 CCS 中配置为 CMSIS-DAP 协议。
    • 板载调试器:若使用 LaunchPad 评估板(LP-MSPM0G3507),板载 XDS110 可直接使用。

    三、工程创建与代码结构

    3.1 使用 SysConfig 图形化配置

    TI 官方提供了 SysConfig 工具(集成在 CCS 中),用于可视化配置引脚、时钟、外设。

    1. 新建工程时选择 MSPM0G3507 芯片型号。
    2. 打开 .syscfg 文件,在图形界面中:
      • 配置系统时钟(默认 32 MHz,可倍频至 80 MHz)。
      • 使能 UART、SPI、GPIO 等外设。
      • 分配引脚(如将 UART0 的 TX/RX 映射到 PA10/PA11)。
    3. 保存后,SysConfig 自动生成 ti_msp_dl_config.c/h 文件,包含初始化代码。

    3.2 工程文件结构(典型)

    project/
    ├── .syscfg                 # 图形化配置源文件
    ├── main.c                  # 主程序入口
    ├── ti_msp_dl_config.c      # 自动生成的初始化代码
    ├── ti_msp_dl_config.h
    ├── startup_mspm0g350x.c    # 启动文件(中断向量表)
    ├── linker.cmd              # 链接脚本(内存布局)
    └── *.c / *.h               # 用户自定义模块
    

    3.3 直接手写初始化(不使用 SysConfig)

    若不想依赖图形工具,可直接调用 DriverLib 函数。例如初始化 UART:

    #include "ti_msp_dl_config.h"
    #include "ti/driverlib/driverlib.h"
    
    void uart_init(void)
    {
        // 使能 UART0 时钟
        DL_UART_Main_reset(UART0);
        DL_UART_Main_enablePower(UART0);
    
        // 配置引脚 PA10=TX, PA11=RX
        DL_GPIO_initPeripheralOutputFunction(GPIOA, GPIO_PIN_10, GPIO_PIN_MUX_15);
        DL_GPIO_initPeripheralInputFunction(GPIOA, GPIO_PIN_11, GPIO_PIN_MUX_15);
    
        // 设置波特率 115200,8N1
        DL_UART_Main_setClockConfig(UART0, DL_UART_MAIN_CLOCK_BUSCLK);
        DL_UART_Main_setBaudRate(UART0, 32000000, 115200);
        DL_UART_Main_setConfig(UART0, DL_UART_MAIN_CONFIG_8BIT_PARITY_NONE_1STOP);
        DL_UART_Main_enable(UART0);
    }
    

    四、GPIO 与基本外设操作

    4.1 点亮 LED(输出模式)

    #include "ti_msp_dl_config.h"
    #include "ti/driverlib/driverlib.h"
    
    int main(void)
    {
        SYSCFG_DL_init();  // 调用 SysConfig 生成的初始化
    
        // 假设 LED 接在 PB0(高电平点亮)
        DL_GPIO_setPins(GPIOB, GPIO_PIN_0);
    
        while(1)
        {
            DL_GPIO_togglePins(GPIOB, GPIO_PIN_0);
            delay_cycles(32000000 / 2);  // 约 0.5 秒延时
        }
    }
    

    4.2 按键输入(轮询模式)

    // 按键接 PA0,内部上拉,按下为低电平
    DL_GPIO_initDigitalInputFeatures(GPIOA, GPIO_PIN_0, DL_GPIO_INVERSION_DISABLE,
                                     DL_GPIO_RESISTOR_PULL_UP, DL_GPIO_HYSTERESIS_DISABLE,
                                     DL_GPIO_WAKEUP_DISABLE);
    
    if (DL_GPIO_readPins(GPIOA, GPIO_PIN_0) == 0) {
        // 按键按下
    }
    

    4.3 中断方式处理按键

    在 SysConfig 中使能 GPIOA 的 GPIOA_INT 中断,并注册回调:

    void GROUP1_IRQHandler(void)
    {
        if (DL_GPIO_readPins(GPIOA, GPIO_PIN_0) == 0) {
            // 处理按键事件
        }
        DL_GPIO_clearInterruptStatus(GPIOA, GPIO_PIN_0);
    }
    

    五、串口(UART)通信详解

    5.1 阻塞式发送与接收

    #include <stdio.h>
    
    // 重定向 printf 到 UART0
    int fputc(int ch, FILE *f)
    {
        DL_UART_Main_transmitData(UART0, (uint8_t)ch);
        return ch;
    }
    
    // 接收单字节(阻塞)
    uint8_t uart_rx_byte(void)
    {
        while (DL_UART_Main_isBusy(UART0));
        return DL_UART_Main_receiveData(UART0);
    }
    

    5.2 中断接收(环形缓冲)

    在 SysConfig 中使能 UART0 的接收中断,并编写中断服务函数:

    #define RX_BUF_SIZE 256
    volatile uint8_t rx_buf[RX_BUF_SIZE];
    volatile uint16_t rx_head = 0, rx_tail = 0;
    
    void UART0_IRQHandler(void)
    {
        if (DL_UART_Main_getEnabledInterruptStatus(UART0, DL_UART_MAIN_INTERRUPT_RX)) {
            rx_buf[rx_head] = DL_UART_Main_receiveData(UART0);
            rx_head = (rx_head + 1) % RX_BUF_SIZE;
            DL_UART_Main_clearInterruptStatus(UART0, DL_UART_MAIN_INTERRUPT_RX);
        }
    }
    

    注意:UART 中断服务函数名需与启动文件中的向量表一致(通常为 UART0_IRQHandler)。


    六、ADC 采集应用

    6.1 单通道单次转换

    // 使用 PA0 作为 ADC 输入通道(ADC0 的通道 0)
    DL_ADC12_setClockConfig(ADC0, DL_ADC12_CLOCK_32MHZ);
    DL_ADC12_initSingleSample(ADC0, DL_ADC12_CONVERSION_MODE_SINGLE,
                              DL_ADC12_SAMPLE_TIMING_SOURCE_ULPCLK,
                              DL_ADC12_SAMPLE_TIMING_INTERVAL_32_CYCLES);
    DL_ADC12_setChannel(ADC0, DL_ADC12_CHANNEL_0);
    DL_ADC12_startConversion(ADC0);
    
    while (DL_ADC12_getStatus(ADC0) & DL_ADC12_STATUS_CONVERSION_ACTIVE);
    uint16_t result = DL_ADC12_getMemResult(ADC0, DL_ADC12_MEM_IDX_0);
    

    6.2 多通道扫描 + 定时触发

    在 SysConfig 中配置:

    • 采样源:定时器 TIMG0 的触发事件。
    • 通道序列:CH0、CH1、CH2。
    • 转换结果存储在 MEM0~MEM2。

    示例代码:

    DL_ADC12_startConversion(ADC0);
    // 等待转换完成(通过中断或轮询)
    for (int i = 0; i < 3; i++) {
        adc_val[i] = DL_ADC12_getMemResult(ADC0, DL_ADC12_MEM_IDX_0 + i);
    }
    

    七、PWM 输出与定时器

    7.1 使用 TIMG0 产生 PWM

    // 初始化 TIMG0 为 PWM 模式,输出引脚 PA12
    DL_TimerG_initPWMMode(TIMG0, 1000, 500);  // 频率 1kHz,占空比 50%
    DL_TimerG_enableClock(TIMG0);
    DL_TimerG_startCounter(TIMG0);
    

    7.2 动态调整占空比

    DL_TimerG_setCaptureCompareValue(TIMG0, 750);  // 变为 75%
    

    7.3 定时中断(1ms 周期)

    在 SysConfig 中配置 TIMG0 为定时模式,周期 1ms,使能中断:

    void TIMG0_IRQHandler(void)
    {
        // 每 1ms 执行一次
        DL_TimerG_clearInterruptStatus(TIMG0, DL_TIMERG_INTERRUPT_CC0_DOWN);
    }
    

    八、低功耗模式实践

    8.1 进入 STOP 模式

    DL_SYSCTL_setPowerModeSTOP();
    // 任意 GPIO 中断或定时器事件可唤醒
    

    8.2 进入 STANDBY 模式(保留 SRAM)

    DL_SYSCTL_setPowerModeSTANDBY();
    // 仅 RTC、WDT 或特定唤醒引脚可唤醒
    

    8.3 唤醒后处理

    唤醒后需重新配置时钟和外设:

    SYSCFG_DL_init();  // 重新初始化所有外设
    

    注意:进入低功耗前,务必关闭不需要的外设时钟,否则功耗无法降低。


    九、常见问题与调试技巧

    9.1 程序无法下载/调试

    • 检查 SWD 引脚是否被占用(PA0/PA1 默认是 SWD,避免复用)。
    • 若误配置,按住板子复位键再点击下载,或使用 unlock 脚本擦除。

    9.2 串口打印乱码

    • 确认时钟频率与 SysConfig 配置一致(尤其是使用外部晶振时)。
    • 检查波特率是否匹配,部分调试器会干扰 UART 信号,可降速测试。

    9.3 中断不触发

    • 在启动文件中确认中断服务函数名称正确(如 UART0_IRQHandler)。
    • 检查 NVIC 是否已使能该中断通道:
      NVIC_EnableIRQ(UART0_IRQn);
      
    • 确认中断标志已清除,避免反复进入。

    9.4 使用 printf 时程序卡死

    • 确保 fputc 重定向正确,且 UART 初始化完成后再调用。
    • 若未连接调试器,printf 输出会阻塞,可改用非阻塞发送或添加超时。

    十、官方资源与下一步学习路径

    • 数据手册:MSPM0G3507 Datasheet(含完整寄存器描述)
    • 技术参考手册:MSPM0G Series Technical Reference Manual
    • SDK 下载mspm0-sdk(包含全部外设驱动与示例)
    • 官方例程:在 CCS Resource Explorer 中搜索 MSPM0G3507 即可找到 UART、ADC、PWM、低功耗等全套示例。

    推荐学习路径

    1. 跑通 hello_world 串口例程。
    2. 动手修改 GPIO 点灯,理解引脚映射。
    3. 实现按键中断 + UART 上报。
    4. 结合 ADC 采集传感器数据并通过串口发送。
    5. 尝试 PWM 控制 LED 亮度或电机转速。
    6. 最后学习低功耗模式,实测功耗数据。

    本文档基于 MSPM0G3507 的通用开发流程编写,所有代码示例均可在官方 SDK 中找到对应参考。建议读者结合实际硬件(推荐 LP-MSPM0G3507 LaunchPad)边学边练,快速掌握该芯片的开发要点。


    @KongChar 佬,太强了吧
  • KongCharK

    前言

    逛论坛手痒,F12 打开看了下群主站的前端代码,基于 NodeBB 搭建。顺手整理一下从前端源码能挖到的爆破思路和安全面分析,纯技术交流,仅供学习参考,请勿用于非法用途。

    一、前端架构速览

    打开浏览器开发者工具的 Sources 面板,可以看到这是标准的 NodeBB 论坛架构:

    核心脚本:nodebb.min.js 打包后的主脚本,包含全部前端逻辑
    渲染方式:服务端渲染首屏 + 前端 AJAX 局部刷新
    接口风格:RESTful 风格,统一 /api/ 前缀
    技术栈:jQuery + 自研模板引擎 + WebSocket 实时推送

    整体来说 NodeBB 作为成熟的开源论坛系统,前端架构还算规整,但架不住配置不当和插件引入的风险。

    二、接口暴露全景

    从 Network 面板抓几个请求,就能摸清站点主要接口分布:

    1. 登录认证接口
      POST /login
      提交表单:username=xxx&password=xxx
      返回 JSON 格式,登录成功写入 cookie 并跳转

    2. 用户信息接口
      GET /api/user/uid
      GET /api/user/username
      未登录状态也能拿到用户基本资料,存在用户枚举风险

    3. 发帖回帖接口
      POST /api/v3/topics
      POST /api/v3/posts
      带 CSRF token 校验,需要先获取再提交

    4. 搜索接口
      GET /api/search
      支持关键词搜索帖子和用户,可能存在注入或遍历风险

    5. 上传接口
      POST /api/v3/posts/upload
      文件上传点,重点关注类型校验和路径问题

    三、爆破思路深度分析

    思路一:用户名枚举

    通过 /api/user/用户名 这个接口,可以批量探测用户名是否存在。返回状态码、响应体大小、响应时间都可能存在差异。

    攻击流程:
    准备常见用户名字典(admin、test、user 等)
    批量请求接口,根据返回差异筛选有效账号
    收集到的有效账号作为后续爆破的目标

    风险等级:中低。虽然不会直接拿到权限,但为定向爆破提供了精准目标。

    思路二:登录接口暴力破解

    登录接口目前观察下来:
    没有明显的图形验证码机制
    失败次数限制需要进一步测试是否严格
    返回包大小和响应时间可能存在差异,可用于用户名枚举

    如果服务端没有做严格的频率限制和账号锁定,配合收集到的有效用户名 + 弱口令字典,有一定概率撞开账号。

    攻击流程:
    先用枚举拿到一批有效用户名
    加载 top1000 弱口令字典
    多线程批量提交登录请求
    筛选成功登录的账号密码组合

    风险等级:高。这是最直接也最常见的入侵入口。

    思路三:CSRF Token 绕过

    NodeBB 默认带 CSRF 防护,但 token 通常就在页面源码或者 cookie 里。

    自动化脚本完全可以:
    先 GET 一次登录页,从页面源码或响应头里提取 csrf token
    带着 token 再 POST 登录请求
    循环这个流程就能实现全自动爆破

    所以 CSRF 防得了跨站攻击,但防不住同一站点的自动化脚本。

    思路四:前端校验直接跳过

    注册、发帖、个人资料修改等场景,如果只在前端做长度限制、格式校验、特殊字符过滤,那等于没做。

    直接抓包改包发请求就能绕过:
    用户名长度限制前端写死 20 位,抓包改成 100 位试试
    特殊字符前端过滤,直接发原始 payload
    邮箱格式前端正则校验,后端不校就可能注入

    记住一条铁律:前端校验只负责提升用户体验,安全校验必须在服务端再做一遍。

    思路五:API 接口越权访问

    NodeBB 的 v3 API 接口很多,权限校验容易出疏漏。

    可以尝试:
    普通用户调用管理员接口
    查看他人私信、未公开帖子
    修改他人资料或帖子
    遍历 uid 批量获取用户敏感信息

    重点关注带 /api/v3/admin/ 前缀的接口,以及参数里有 uid、tid 的地方。

    思路六:WebSocket 实时通道测试

    NodeBB 用 WebSocket 做实时通知和聊天,这也是一个攻击面。

    可以测试:
    未授权能否连接 WS
    能否监听他人通知消息
    能否伪造他人身份发送消息
    WS 消息格式有没有注入点

    实时通道往往是安全审计的盲区。

    四、自动化脚本思路

    写一个简单的爆破脚本,流程大概是这样:

    第一步:初始化会话,GET 登录页拿 cookie 和 csrf token
    第二步:加载用户名字典和密码字典
    第三步:循环构造请求,带上 token 和 cookie 提交登录
    第四步:根据响应状态码、跳转地址、返回内容判断是否成功
    第五步:遇到频率限制就加延时或换代理

    Python 用 requests 库几十行就能搞定。当然,这是给安全测试用的,别拿去干坏事。

    五、防护建议(给群主)

    针对上面提到的这些点,给群主几点加固建议:

    1. 登录口加固
      失败 5 次锁账号 15 分钟
      连续失败弹出图形验证码
      IP 级别频率限制,单 IP 每分钟最多 10 次
      高危登录地区或异常时段强制二次验证

    2. 接口统一限速
      所有敏感接口统一做速率限制
      用户信息接口增加匿名访问限制,避免批量枚举
      搜索接口加频次控制,防止被拖库

    3. 输入校验服务端重做
      所有用户输入服务端再校验一遍
      参数长度、类型、格式严格限制
      特殊字符统一转义,防范 XSS 和注入

    4. CSRF 与权限校验
      关键操作二次确认密码
      管理员接口严格鉴权,定期审计越权漏洞
      API 接口按最小权限原则分配

    5. 日志与监控
      登录失败日志完整记录
      异常登录行为告警(异地、多次失败)
      定期审计访问日志,发现爆破及时封禁

    6. 版本与插件
      保持 NodeBB 核心版本最新
      插件按需安装,不用的及时卸载
      第三方插件重点审计,容易出漏洞

    六、总结

    整体来说 NodeBB 本身底子还行,是成熟的开源产品,但具体安全程度完全取决于群主有没有做加固。

    前端永远是不可信的,所有关键校验必须落在服务端。接口暴露、频率限制、权限校验这三块做好了,能挡住 90% 以上的自动化攻击。

    以上就是逛站随手分析的一些思路,纯技术水帖 test 一下发帖功能,群主看到了别打我 🫡


    @KongChar 给捞哥网页底裤都说出来了
  • KongCharK

    本文是 shaojiec932-web/CCD 项目的开源分享帖,原 README 同步搬运过来,方便社群同学在 BakeHub 内直接讨论。仓库整理了 2026 嵌入式芯片应用赛道(乐鑫科技赛道)参赛过程中三个核心 ESP32-S3 节点工程:主控节点端、图传视觉端、门牌节点端。主控与图传端基于小智开源框架二次开发,门牌端用 Arduino 实现。致敬虾哥 xiaozhi。


    TrackDock 室内智能感知运载平台

    基于 ESP32-S3、小智开源框架、MQTT、RFID、UWB、图传视觉与本地运动控制构建的室内智能运载与感知系统。

    本仓库整理了本项目中三个核心 ESP32-S3 节点工程,分别为:主控节点端、图传视觉端和可编辑门牌节点端。其中主控节点端与图传视觉端均基于小智开源代码框架进行二次开发,在原有语音交互、联网通信和设备框架基础上,扩展了面向室内运载场景的本地实时控制、MCP 工具服务、MQTT 数据同步、RFID 货物识别、UWB 区域定位、串口屏交互和摄像头图传等能力。门牌节点端采用 Arduino 框架实现,用于室内区域标识与 MQTT 配置同步。

    项目背景

    在医院、养老院、仓库、图书馆、教学楼和实验室等复杂室内环境中,药品、文件、器材、样本、工具和日常物资需要频繁流转。传统方式主要依赖人工搬运、人工登记和人工核对,容易出现效率低、定位难、状态不可追踪、错拿错送、远程感知不足等问题。

    TrackDock 的目标是通过低成本嵌入式节点与多端协同系统,将"任务发起、语音理解、货物识别、区域定位、远程图传、运动执行、状态反馈"打通,形成一套面向室内场景的智能运载与感知原型平台。

    工程目录

    嵌入式大赛/
    ├─ ESP32-S3主控节点端/
    │  ├─ application.cc
    │  ├─ application.h
    │  ├─ mcp_server.cc
    │  ├─ mcp_server.h
    │  └─ bread-compact-wifi-lcd/
    │     ├─ compact_wifi_board_lcd.cc
    │     ├─ config.h
    │     ├─ mcp_rfid_cargo_manager.cc
    │     ├─ mcp_serial_display.cc
    │     ├─ mcp_stm32_motor_control.cc
    │     ├─ mcp_storage_lid.cc
    │     ├─ mcp_uwb_navigation.cc
    │     ├─ trackdock_mqtt_client.cc
    │     └─ trackdock_scene.cc
    │
    ├─ ESP32-S3图传视觉端/
    │  └─ lichuang-dev/
    │     ├─ config.h
    │     ├─ lichuang_dev_board.cc
    │     ├─ mcp_uart_tools.cc
    │     └─ mcp_uart_tools.h
    │
    └─ ESP32-S3门牌节点端/
       ├─ NumberIP.ino
       └─ 门牌.txt
    

    1. ESP32-S3 主控节点端

    主控节点端是系统的核心控制节点,基于小智开源框架进行二次开发,主要负责语音交互接入、本地 MCP 工具注册、外设数据解析、串口屏交互、货物识别、UWB 定位接口、MQTT 状态同步以及底盘控制指令转发。

    主要功能

    • 基于小智框架接入语音交互能力,支持语音触发任务。
    • 扩展 MCP 本地工具服务,将大模型意图映射为本地硬件操作。
    • 通过 RFID 模块完成货物标签识别与货物信息采集。
    • 通过 UWB 模块接口实现室内定位与目标区域导航能力预留。
    • 通过串口屏实现本地人机交互,包括状态显示、按钮控制、音量调节等。
    • 通过 UART 与 STM32 底盘控制端通信,实现前进、后退、左转、右转、停止等运动控制。
    • 通过 MQTT 与客户端进行数据同步,实现远程控制、运行状态反馈和场景切换。
    • 支持收纳盖控制、场景模式管理和任务状态记录。

    关键文件说明

    文件 说明
    application.cc / application.h 小智应用层逻辑扩展,承载语音状态、任务流程和系统初始化相关逻辑。
    mcp_server.cc / mcp_server.h MCP 工具服务核心文件,用于注册和管理本地可调用工具。
    compact_wifi_board_lcd.cc 主控板级初始化与硬件资源配置。
    config.h 主控端 IO、串口、SPI、I2C、外设参数配置。
    mcp_rfid_cargo_manager.cc RFID 货物扫描、标签读取、货物信息反馈。
    mcp_serial_display.cc 串口屏数据解析、控件同步、指令接收与显示输出。
    mcp_stm32_motor_control.cc STM32 底盘运动指令封装与串口发送。
    mcp_uwb_navigation.cc UWB 定位、目标点、导航状态与路径指令接口。
    trackdock_mqtt_client.cc MQTT 客户端连接、订阅、发布与远程控制同步。
    trackdock_scene.cc 多场景模式切换与场景语义管理。

    2. ESP32-S3 图传视觉端

    图传视觉端基于立创实战派 ESP32-S3 开发板方案和小智开源框架进行二次开发,主要用于摄像头图像采集、JPEG 编码、MQTT 图传发布和远程视觉控制台显示。该节点作为独立视觉感知节点,不承担主控运动决策,重点提供远程观察与辅助驾驶能力。

    主要功能

    • 基于 ESP32-S3 摄像头接口采集实时图像。
    • 将图像压缩为 JPEG 数据并通过 MQTT 上传。
    • 客户端可实时显示视觉画面,用于远程观察和辅助控制。
    • 可配合主控端完成室内运行状态监控、路径观察和物品识别辅助展示。
    • 保留小智框架的基础联网和设备初始化能力,并扩展图传任务循环。

    关键文件说明

    文件 说明
    lichuang_dev_board.cc 图传视觉端核心实现,包含摄像头初始化、图像采集、MQTT 发布和任务循环。
    config.h 摄像头、板级 IO 和基础参数配置。
    mcp_uart_tools.cc / mcp_uart_tools.h 原框架中的 UART 工具能力,保留用于后续扩展。

    3. ESP32-S3 门牌节点端

    门牌节点端用于构建可编辑的室内区域标识系统。该节点采用 Arduino 框架实现,便于快速开发和部署。门牌节点通过 Wi-Fi 和 MQTT 与客户端同步,客户端可远程配置门牌名称,例如"仓库""收纳区""充电区""病房区"等,节点本地屏幕显示当前门牌信息。

    主要功能

    • 支持 Wi-Fi 连接与 MQTT 通信。
    • 支持客户端远程修改门牌名称。
    • 支持本地 TFT/OLED 屏幕显示区域名称。
    • 可作为 UWB 室内定位系统中的空间语义节点。
    • 可与主控端运动任务结合,实现"目标区域名称"和"定位参考点"的对应关系。

    关键文件说明

    文件 说明
    NumberIP.ino 门牌节点主程序,包含 Wi-Fi、MQTT、屏幕显示和门牌配置逻辑。
    门牌.txt 门牌端说明、配置记录或辅助资料。

    系统总体链路

    用户语音 / 客户端 / 串口屏
            ↓
    ESP32-S3 主控节点
            ↓
    小智语音交互 + MCP 本地工具调用
            ↓
    RFID 货物识别 / UWB 区域定位 / STM32 底盘控制 / 收纳盖控制
            ↓
    MQTT 状态同步
            ↓
    电脑端、手机端、视觉控制端、门牌节点
    

    图传视觉端作为独立感知节点持续上传摄像头画面,门牌节点作为空间语义节点提供区域标识,主控节点负责语音理解后的本地执行和多端状态同步。三个节点共同构成室内智能运载平台的基础闭环。

    二次开发说明

    本项目主控节点端和图传视觉端基于小智开源框架进行二次开发,主要扩展方向包括:

    • 面向室内运载场景的 MCP 工具接口。
    • 面向硬件外设的本地实时控制逻辑。
    • 面向客户端的 MQTT 数据同步链路。
    • 面向串口屏的人机交互协议。
    • 面向 RFID、UWB、STM32、摄像头等模块的板级适配。
    • 面向多场景应用的任务状态与场景语义管理。

    开源时请保留原小智项目相关版权与许可证说明,并在仓库中注明本项目是在其基础上进行的场景化二次开发。

    技术栈

    • 主控框架:Espressif ESP-IDF、小智开源框架
    • 主控芯片:ESP32-S3
    • 图传节点:ESP32-S3 摄像头开发板
    • 门牌节点:ESP32-S3 + Arduino 框架
    • 通信方式:Wi-Fi、MQTT、UART、SPI、I2C、GPIO
    • 识别模块:RFID RC522
    • 定位模块:UWB / DW1000 系列模块接口
    • 执行控制:STM32F103C8T6 底盘控制端
    • 交互方式:语音、串口屏、电脑端客户端、手机端客户端

    适用场景

    • 医院药品、样本、护理用品转运
    • 养老院照护用品配送
    • 仓储货物转运与盘点辅助
    • 实验室器材和样本流转
    • 图书馆资料搬运与区域配送
    • 教学楼、办公楼内的低成本智能配送

    后续扩展方向

    • 将主控、图传、门牌和底盘控制进一步集成化,减少分散模块数量。
    • 优化机械结构,使车体更接近真实运载平台。
    • 完善 UWB 多基站定位解算,提高室内定位精度。
    • 增强视觉识别能力,支持货物外观识别和障碍检测。
    • 完善客户端任务管理,实现多车、多门牌、多任务调度。
    • 增加电源管理和安全保护,提高系统稳定性。

    开源说明

    本仓库为 TrackDock 室内智能感知运载平台的嵌入式端工程整理版本,包含主控节点端、图传视觉端和门牌节点端三个部分。项目用于嵌入式系统开发、物联网多节点协同、智能运载平台原型验证和比赛展示。

    其中涉及小智开源框架的部分为基于原项目的二次开发与场景化扩展,建议开源时同步附带原项目许可证、二次开发说明和本项目新增功能说明。


    仓库地址:https://github.com/shaojiec932-web/CCD

    欢迎在评论区交流硬件选型、MCP 工具注册、UWB 定位解算、MQTT 图传带宽优化等细节问题。


  • KongCharK

    背景:为什么要写一个外部发帖工具?

    BakeHub 是基于 NodeBB 搭建的工科开源社群,日常发帖通常要在网页编辑器里手敲 Markdown。但当你有一篇几千字的技术复盘、或者想批量迁移本地笔记库时,网页编辑器就成了瓶颈——没有本地版本控制、不能 vim 键位、不能 grep、不能 diff。于是我用 F12 抓了一下 NodeBB 的发帖请求,顺手写了一个 零依赖 的 Python 脚本,直接用本地 Markdown 文件发帖。这篇帖子既是工具发布,也是把抓包分析过程完整记录下来,方便后来人复用。

    第一步:F12 抓包分析 NodeBB 发帖流程

    打开 BakeHub 首页,按 F12 切到 Network 面板,勾上 Preserve log,然后手动走一遍「登录 → 进版块 → 发帖」的流程。关键请求按时间顺序如下:

    1. 登录请求

    POST /login HTTP/1.1
    Host: 203.195.163.189
    Content-Type: application/x-www-form-urlencoded
    x-csrf-token: <从 /api/config 拿到的 token>
    Referer: http://203.195.163.189/
    
    username=KongChar&password=********&remember=on&returnTo=
    

    服务器返回 {"next":"/"}Set-Cookie: express.sid=...,这个 session cookie 就是后续所有请求的身份凭证。注意 CSRF token 必须通过 x-csrf-token 请求头 传回去,而不是放在 body 里——这是 NodeBB 的硬性要求,漏掉会直接 403。

    2. 获取 CSRF token

    GET /api/config HTTP/1.1
    Accept: application/json
    

    返回的 JSON 里关键字段:

    字段 含义
    csrf_token 写操作必须带的 token,每次会话变化
    loggedIn true/false,判断当前 session 是否已登录
    uid 当前用户 id,游客为 0
    maximumPostLength 单帖最大字符数(本站 32767)
    maximumTagsPerTopic 每帖最多标签数(本站 5)

    3. 发帖请求(NodeBB v3 API)

    POST /api/v3/topics HTTP/1.1
    Content-Type: application/json
    x-csrf-token: <同上>
    
    {
      "cid": 3,
      "title": "帖子标题",
      "content": "Markdown 正文",
      "tags": ["工具", "Python"]
    }
    

    返回 {"topic": {"tid": 123, "slug": "123/xxx", ...}},帖子 URL 就是 /topic/{tid}/{slug}

    4. 列版块

    GET /api/categories
    

    返回 categories 数组,每项含 cid / name / slug / topic_count。本站当前版块:

    cid 名称 用途
    1 项目复盘 复盘总结
    2 开源求助 提问
    3 经验分享 技术分享(本帖所在)
    4 学习路线 路线规划

    第二步:踩过的坑

    抓包看似简单,实际写脚本时踩了三个坑,值得单独说一下:

    坑 1:Connection reset by peer。 服务器对短时间高频请求会直接 RST 连接,第一次跑 cats 子命令就挂了。解决办法是给底层请求加 指数退避重试,失败后 sleep 1.5s / 3s / 4.5s 再试。

    坑 2:Accept 头重复。 一开始在 opener.addheaders 里写了 Accept,又在 _requestappend 一次,导致请求头里出现两个 Accept,服务器偶尔会拒绝。统一在 _request 里追加,addheaders 只放 User-AgentReferer

    坑 3:CSRF token 会过期。 长时间不操作后再发帖,会收到 403 [[error:invalid-csrf-token]]。所以每次发帖前都重新 GET /api/config 刷新一次 token,而不是登录时拿一次就缓存到底。

    第三步:完整工具代码

    下面是 bakehub-poster.py 的完整源码,零第三方依赖,只用 Python 标准库。把它存成文件直接 python3 bakehub-poster.py 就能跑。

    #!/usr/bin/env python3
    """
    bakehub-poster.py — BakeHub (NodeBB) 外部 Markdown 发帖工具
    
    用法:
        python bakehub-poster.py post --file post.md --title "帖子标题" --cid 3 --tags "工具,Python"
        python bakehub-poster.py cats                      # 列出所有版块
        python bakehub-poster.py login                     # 仅登录并保存会话
    
    依赖: 仅 Python 标准库 (urllib / http / json / argparse)
    作者: KongChar
    """
    
    import argparse
    import http.cookiejar
    import json
    import os
    import time
    import urllib.parse
    import urllib.request
    
    # ===== 配置区 =====
    BASE_URL = "http://203.195.163.189"
    DEFAULT_CID = 3  # 经验分享
    COOKIE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bakehub.cookies.txt")
    UA = ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
          "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
    
    
    class BakeHubClient:
        def __init__(self, base_url=BASE_URL):
            self.base = base_url.rstrip("/")
            self.cj = http.cookiejar.MozillaCookieJar(COOKIE_FILE)
            if os.path.exists(COOKIE_FILE):
                self.cj.load(ignore_discard=True, ignore_expires=True)
            self.opener = urllib.request.build_opener(
                urllib.request.HTTPCookieProcessor(self.cj))
            self.opener.addheaders = [
                ("User-Agent", UA),
                ("Referer", self.base + "/"),
            ]
            self.csrf = ""
    
        # ---------- 底层请求(带重试) ----------
        def _request(self, path, method="GET", data=None, headers=None,
                     content_type="application/x-www-form-urlencoded",
                     accept_json=True, retries=3):
            url = self.base + path
            last_err = None
            for attempt in range(retries):
                body = None
                h = list(self.opener.addheaders)
                if accept_json:
                    h.append(("Accept", "application/json"))
                if headers:
                    h.extend(headers.items())
                if data is not None:
                    if isinstance(data, (dict, list)):
                        body = json.dumps(data).encode("utf-8")
                        h.append(("Content-Type", "application/json"))
                    elif isinstance(data, str):
                        body = data.encode("utf-8")
                        h.append(("Content-Type", content_type))
                    else:
                        body = data
                        h.append(("Content-Type", content_type))
                req = urllib.request.Request(url, data=body, method=method)
                for k, v in h:
                    req.add_header(k, v)
                try:
                    with self.opener.open(req, timeout=30) as r:
                        return r.status, r.read().decode("utf-8", errors="replace")
                except urllib.error.HTTPError as e:
                    return e.code, e.read().decode("utf-8", errors="replace")
                except (urllib.error.URLError, ConnectionError, TimeoutError) as e:
                    last_err = e
                    if attempt < retries - 1:
                        time.sleep(1.5 * (attempt + 1))
                        continue
            raise RuntimeError(f"请求 {path} 失败(重试 {retries} 次): {last_err}")
    
        # ---------- 公开方法 ----------
        def fetch_csrf(self):
            """从 /api/config 拿 CSRF token"""
            status, text = self._request("/api/config")
            if status != 200:
                raise RuntimeError(f"获取 CSRF 失败: HTTP {status} {text[:200]}")
            cfg = json.loads(text)
            self.csrf = cfg.get("csrf_token", "")
            if not self.csrf:
                raise RuntimeError("CSRF token 为空")
            return self.csrf
    
        def login(self, username, password):
            """登录并保存 cookie"""
            self.fetch_csrf()
            form = urllib.parse.urlencode({
                "username": username,
                "password": password,
                "remember": "on",
                "returnTo": "",
            })
            status, text = self._request(
                "/login", method="POST", data=form,
                headers={"x-csrf-token": self.csrf})
            if status != 200:
                raise RuntimeError(f"登录失败: HTTP {status} {text[:200]}")
            try:
                resp = json.loads(text)
            except json.JSONDecodeError:
                resp = {"raw": text}
            # 保存 cookie
            self.cj.save(COOKIE_FILE, ignore_discard=True, ignore_expires=True)
            # 验证登录
            self.fetch_csrf()
            status2, text2 = self._request("/api/config")
            cfg = json.loads(text2)
            if not cfg.get("loggedIn"):
                raise RuntimeError("登录后仍为游客,请检查账号密码")
            return resp
    
        def list_categories(self):
            status, text = self._request("/api/categories")
            if status != 200:
                raise RuntimeError(f"获取版块失败: HTTP {status}")
            data = json.loads(text)
            return data.get("categories", [])
    
        def create_topic(self, cid, title, content, tags=None):
            """创建新主题。content 为 Markdown 文本"""
            if not self.csrf:
                self.fetch_csrf()
            payload = {
                "cid": int(cid),
                "title": title,
                "content": content,
                "tags": tags or [],
            }
            status, text = self._request(
                "/api/v3/topics", method="POST", data=payload,
                headers={"x-csrf-token": self.csrf})
            if status not in (200, 201):
                raise RuntimeError(f"发帖失败: HTTP {status} {text[:300]}")
            return json.loads(text)
    
        def reply(self, tid, content):
            if not self.csrf:
                self.fetch_csrf()
            payload = {"tid": int(tid), "content": content}
            status, text = self._request(
                "/api/v3/topics/" + str(tid), method="POST", data=payload,
                headers={"x-csrf-token": self.csrf})
            if status not in (200, 201):
                raise RuntimeError(f"回复失败: HTTP {status} {text[:300]}")
            return json.loads(text)
    
    
    # ===== CLI =====
    def cmd_login(args):
        c = BakeHubClient()
        r = c.login(args.user, args.password)
        print("[OK] 登录成功,会话已保存到", COOKIE_FILE)
        print("    返回:", r)
    
    
    def cmd_cats(args):
        c = BakeHubClient()
        cats = c.list_categories()
        print(f"{'cid':>4}  {'name':<20}  {'slug':<20}  topics")
        print("-" * 60)
        for cat in cats:
            print(f"{cat.get('cid'):>4}  {cat.get('name'):<20}  "
                  f"{cat.get('slug'):<20}  {cat.get('topic_count')}")
    
    
    def cmd_post(args):
        with open(args.file, "r", encoding="utf-8") as f:
            content = f.read()
        tags = [t.strip() for t in (args.tags or "").split(",") if t.strip()]
        c = BakeHubClient()
        # 自动登录(若会话失效)
        try:
            c.fetch_csrf()
        except Exception:
            c.login(args.user, args.password)
        # 二次确认登录态
        status, text = c._request("/api/config")
        cfg = json.loads(text)
        if not cfg.get("loggedIn"):
            print("[INFO] 会话已过期,重新登录...")
            c.login(args.user, args.password)
        result = c.create_topic(args.cid, args.title, content, tags)
        print("[OK] 发帖成功!")
        if isinstance(result, dict):
            topic = result.get("topic", result)
            slug = topic.get("slug") or topic.get("tid")
            print("    tid:", topic.get("tid"))
            print("    URL:", f"{BASE_URL}/topic/{topic.get('tid')}/{slug}")
    
    
    def main():
        p = argparse.ArgumentParser(
            description="BakeHub (NodeBB) 外部 Markdown 发帖工具")
        p.add_argument("--user", default="KongChar", help="用户名")
        p.add_argument("--password", default="csj123456", help="密码")
        sub = p.add_subparsers(dest="cmd", required=True)
    
        sub.add_parser("login", help="仅登录并保存会话").set_defaults(func=cmd_login)
        sub.add_parser("cats", help="列出所有版块").set_defaults(func=cmd_cats)
    
        pp = sub.add_parser("post", help="发布 Markdown 帖子")
        pp.add_argument("--file", required=True, help="Markdown 文件路径")
        pp.add_argument("--title", required=True, help="帖子标题")
        pp.add_argument("--cid", type=int, default=DEFAULT_CID, help="版块 cid")
        pp.add_argument("--tags", help="标签,逗号分隔")
        pp.set_defaults(func=cmd_post)
    
        args = p.parse_args()
        args.func(args)
    
    
    if __name__ == "__main__":
        main()
    

    第四步:使用方法

    把上面的代码存为 bakehub-poster.py,然后:

    1. 首次登录(保存会话,后续免登录):

    python3 bakehub-poster.py login
    # 输出: [OK] 登录成功,会话已保存到 .../bakehub.cookies.txt
    

    2. 查看版块列表:

    python3 bakehub-poster.py cats
    #  cid  name        slug          topics
    # ------------------------------------------------------------
    #     1  项目复盘    1/项目复盘       3
    #     2  开源求助    2/开源求助       5
    #     4  学习路线    4/学习路线       2
    #     3  经验分享    3/经验分享       6
    

    3. 用本地 Markdown 文件发帖:

    cat > my-post.md <<'EOF'
    ## 我的第一篇外部发帖
    
    这是用脚本发的帖子,正文支持完整 Markdown 语法。
    
    - 列表
    - **加粗** / *斜体* / `代码`
    - [链接](https://example.com)
    EOF
    
    python3 bakehub-poster.py post \
        --file my-post.md \
        --title "我的第一篇外部发帖" \
        --cid 3 \
        --tags "工具,Python,自动化"
    # 输出:
    # [OK] 发帖成功!
    #     tid: 17
    #     URL: http://203.195.163.189/topic/17/我的第一篇外部发帖
    

    这篇帖子本身就是用这个工具发出来的 —— 自举成功。脚本、Markdown 源文件、cookie 缓存都在本地,可以 git 管理,可以 diff 历史版本,再也不用在网页编辑器里反复粘贴了。

    设计取舍说明

    为什么不用 requests 库?因为想保证 零依赖,任何一台装了 Python 3 的机器都能直接跑,不用 pip installurllib 的 API 确实啰嗦,但封装一层 _request 之后用起来和 requests 差不多。

    为什么 cookie 用 Netscape 格式落盘?因为 http.cookiejar.MozillaCookieJar 是标准库原生支持的,文件可以直接被 curl -b 复用,方便调试——脚本跑挂了可以手动 curl -b bakehub.cookies.txt ... 排查。

    为什么 CSRF token 每次都重新取?因为 NodeBB 的 token 会随 session 状态变化,缓存反而更容易踩 403。一次 GET /api/config 才几 KB,代价可以忽略。

    后续可扩展的方向

    当前版本只覆盖了「登录 + 列版块 + 发主题 + 回复」四个核心动作。如果有人想接着做,几个方向值得考虑:

    • 图片上传:NodeBB 的上传接口是 POST /api/v3/topics/upload,需要 multipart/form-data,目前脚本没实现,长帖里的图片还得手动传。
    • 草稿同步:把本地 .md 文件的 frontmatter(标题、tags、cid)解析出来,做到一篇文件 = 一篇帖子,git diff 就能看出改了啥。
    • 批量迁移:遍历一个目录下所有 .md 文件,按 frontmatter 里的 cid 自动分发到对应版块,适合从 Obsidian / Hexo 仓库一键搬迁。
    • WebSocket 监听:NodeBB 用 socket.io 推实时通知,接上之后可以做到「别人回复你的帖子时本地弹通知」。

    欢迎在评论区交流改进思路,或者直接 fork 这个脚本改一版发上来。


  • 鲁迪乌斯

    有没有,好的易支付,官方的个人搞不了


  • 鲁迪乌斯

    具体效果如,下图所示。也是非常的有趣啊。















    @鲁迪乌斯 能用来经验+3吗
  • KongCharK

    项目概述

    本项目收集整理了GitHub上三个优秀的STM32超频开源项目,覆盖STM32F103、STM32F303、STM32F37x三个系列,从极限挑战到实用工程模板都有涉及,适合喜欢折腾硬件超频的爱好者参考学习。


    项目一:stm32-overclocking-challenge(蓝药丸极限超频竞赛)

    GitHub地址: https://github.com/m3y54m/stm32-overclocking-challenge

    目标芯片: STM32F103C8T6(BluePill蓝药丸开发板)

    项目特点

    • 专门针对STM32F103的极限超频挑战项目
    • 使用汇编语言编写时钟配置代码,追求极致性能
    • 支持测试不同PLL配置下的极限频率
    • 包含GPIO翻转速度测试,直观验证超频效果
    • 附带稳定性测试代码

    玩法亮点

    STM32F103官方标称最高72MHz,但很多板子实际能跑到128MHz甚至更高。这个项目就像一场硅片体质抽奖,不同芯片的极限频率差异很大,用示波器看GPIO翻转波形就能直观感受到超频效果。


    项目二:stm32f303k8-template(F303超频工程模板)

    GitHub地址: https://github.com/dimtass/stm32f303k8-template

    目标芯片: STM32F303 Nucleo开发板

    项目特点

    • 完整的STM32F303超频工程模板,开箱即用
    • 稳定超频至128MHz(官方标称72MHz)
    • 已配置好Flash等待周期,系统运行稳定
    • 完整Makefile工程,适合直接用于实际开发
    • 支持Nucleo开发板,硬件门槛低

    实用价值

    F303系列自带硬件浮点单元(FPU),超频后DSP运算性能提升明显。这个项目不是单纯的极限超频demo,而是真正可用于实际项目的工程模板,外设都能正常工作。


    项目三:overclocking-STM32F37x_16(F37x时钟魔改)

    GitHub地址: https://github.com/qidfpv/overclocking-STM32F37x_16

    目标芯片: STM32F37x系列

    项目特点

    • 修改系统时钟配置文件system_stm32f37x.c
    • 从官方72MHz超频到80MHz,约11%性能提升
    • 超频幅度保守,稳定性有保障
    • 直接替换时钟文件即可完成超频
    • 适合新手入门MCU超频

    适合人群

    这种"甜点频率"超频基本所有芯片都能稳住,不需要挑体质,风险低成就感高,非常适合第一次玩MCU超频的新手。


    超频注意事项

    1. 失去质保:超频超出芯片官方规格,会失去原厂质保
    2. 功耗发热:超频后功耗和发热会明显增加,注意散热
    3. Flash等待周期:必须根据频率调整Flash等待周期,否则直接HardFault
    4. 外设时钟:UART/SPI/I2C等外设时钟会变化,通信波特率可能错乱
    5. 芯片体质:同型号芯片极限频率差异很大,不要盲目追求高频
    6. 风险提示:极限超频有烧坏芯片的风险,请做好电流监控和散热措施

    技术标签

    • STM32
    • 单片机超频
    • PLL时钟配置
    • 嵌入式开发
    • 硬件DIY

  • KongCharK

    大家好!今天给大家分享几个GitHub上超有意思的STM32超频开源项目,喜欢折腾硬件的朋友们千万别错过~

    MCU超频这件事,就像给芯片"开外挂",虽然超出原厂规格有风险,但那种把一块便宜板子榨干性能的快感,只有玩过的人才懂!下面三个项目分别对应不同的STM32系列,各有各的玩法。


    🔥 项目一:stm32-overclocking-challenge(蓝药丸极限超频竞赛)

    项目地址: https://github.com/m3y54m/stm32-overclocking-challenge

    目标芯片: STM32F103C8T6(经典蓝药丸BluePill)

    这个项目有多野?

    这是一个专门针对STM32F103的极限超频挑战项目,作者直接用汇编来写时钟配置代码,就是为了榨干每一滴性能!

    亮点:

    • 🚀 测试不同PLL配置下的极限频率
    • ⚡ GPIO翻转速度测试,看看到底能跑多快
    • 🔧 纯汇编级别的时钟寄存器操作,硬核到底
    • 📊 附带稳定性测试代码

    有趣在哪?

    STM32F103官方标称最高72MHz,但很多"大雷"板子其实能跑到128MHz甚至更高!这个项目就像一场硅片体质大抽奖,有的人的蓝药丸只能稳80MHz,有的神仙体质能冲150MHz+。

    而且用GPIO翻转来测频率特别直观——用示波器一看就知道你的芯片到底超没超成功,波形密到什么程度。


    ⚙️ 项目二:stm32f303k8-template(F303超频工程模板)

    项目地址: https://github.com/dimtass/stm32f303k8-template

    目标芯片: STM32F303 Nucleo开发板

    实用派超频代表

    这个项目就比较"工程化"了,不是单纯追求极限,而是提供了一个完整的、超频到128MHz的工程模板,拿来就能用。

    亮点:

    • ✅ 直接超频到128MHz稳定运行(官方标称72MHz)
    • 🛠️ 完整的Makefile工程模板,开箱即用
    • 📝 配置好了Flash等待周期,不会跑飞
    • 🔌 支持Nucleo开发板,硬件门槛低

    有趣在哪?

    F303系列带硬件浮点单元(FPU),超频之后做DSP运算性能提升特别明显!如果你用它做电机控制、数字信号处理,超个频相当于免费升级了芯片。

    而且这个项目最良心的地方是——它不是那种"能点亮就算成功"的demo,而是真的把整个系统时钟树都调好了,外设也能正常工作,拿来做实际项目都没问题。


    🎯 项目三:overclocking-STM32F37x_16(F37x时钟魔改)

    项目地址: https://github.com/qidfpv/overclocking-STM32F37x_16

    目标芯片: STM32F37x系列

    温和超频派

    这个项目相对保守一些,把F37x从官方72MHz超到80MHz,属于"安全小超"的范畴。

    亮点:

    • 🔧 修改系统时钟文件system_stm32f37x.c
    • 📈 从72MHz提升到80MHz,约11%性能提升
    • 🛡️ 超频幅度小,稳定性有保障
    • 📋 直接替换时钟配置文件即可

    有趣在哪?

    别看只超了8MHz,对于很多对时序要求严格的应用来说,这已经是很大的提升了!而且这种"甜点频率"基本所有芯片都能稳住,不需要挑体质,量产项目都敢用。

    这个项目最适合新手入门超频——改动小,风险低,成功了有成就感,失败了也不容易烧片子。


    💡 超频小科普:为什么MCU能超频?

    很多人可能好奇,为什么官方标72MHz的芯片能跑到128MHz?其实原因很简单:

    1. 厂商留有余量:为了保证量产良率和长期稳定性,标称频率通常都比较保守
    2. 温度影响: datasheet里的参数一般是工业级温度范围(-40~85℃),室温下潜力更大
    3. 体质差异:同一条生产线出来的芯片,极限频率也不一样,这就是所谓的"大雷"和"大雕"

    ⚠️ 重要提醒

    超频虽爽,但一定要注意:

    • ❗ 超频会失去原厂质保
    • ❗ 功耗和发热会明显增加
    • ❗ Flash等待周期必须对应调整,否则直接HardFault
    • ❗ 外设时钟(UART/SPI/I2C)会跟着变,通信波特率可能错乱
    • ❗ 极限超频有烧坏芯片的风险,做好散热和电流监控

    🎮 最后

    STM32超频就像是硬件圈的"装机超频"文化,便宜的几块钱的蓝药丸,超一超性能翻倍,那种DIY的乐趣真的很棒!

    如果你也玩过STM32超频,欢迎在评论区分享你的战绩——你的芯片最高稳在了多少MHz?


  • 诗的结尾

    <p>声明:本项目为自研,(前端是小伙伴帮忙做的)开发不易,大家点点赞支持一下主播吧(目前网上没有看到基于51的mqtt相关项目,感觉51搞这些太容易超内存了,并且大佬都在搞32)</p><p>本博客不包含emqx的搭建教程,不会搭建emqx的可以先看这个博主的视频: <span><a href="http:// https://b23.tv/fuq6wbk" target="_blank" rel="noopener noreferrer">https://b23.tv/fuq6wbk</a></span></p><p>结尾附网盘链接</p><h2>项目概述</h2><p>本项目基于STC89C52RC单片机,结合ESP8266  WiFi模块、DHT11温湿度传感器、MQ-2气体传感器等硬件,设计并实现了一个智能火灾报警系统。系统能够实时监测环境温湿度和可燃气体浓度,通过MQTT协议将数据上传到云平台,并支持远程报警和阈值设置。</p><p>

    <span>​<img src="/assets/uploads/files/1785077888776-image.png" alt="image.png" data-size="large" /></span></p><h2>系统架构</h2><h3>硬件组成</h3><ul><li><p><strong>主控芯片</strong>:STC89C52RC单片机</p></li><li><p><strong>通信模块</strong>:ESP8266 WiFi模块(ESP-01S)</p></li><li><p><strong>传感器</strong>:</p><ul><li><p>DHT11温湿度传感器</p></li><li><p>MQ-2气体浓度传感器</p></li></ul></li><li><p><strong>AD/DA转换模块</strong>:PCF8591转换模块</p></li><li><p><strong>显示模块</strong>:LCD1602液晶显示屏</p></li><li><p><strong>报警模块</strong>:蜂鸣器、LED报警灯</p></li></ul><h3>软件架构</h3><ul><li><p><strong>开发环境</strong>:Keil C51</p></li><li><p><strong>通信协议</strong>:MQTT</p></li><li><p><strong>数据格式</strong>:JSON</p></li></ul><h2><strong>PCB设计</strong></h2><p><span></span></p><p>1.原理图</p><p><img src="/assets/uploads/files/1785077910964-image.png" alt="image.png" data-size="large" /></p><p>2.PCB</p><p><img src="/assets/uploads/files/1785078011677-image.png" alt="image.png" data-size="large" /></p><p><span>​</span></p><h2>核心功能实现</h2><h3>1. 传感器数据采集</h3><p>1.mq2烟雾传感器</p>
    <blockquote><code><span></span></code><code><span><span></span></span></code><span>#include "mq2.h''<br /></span><p><span><strong>#include "i2c.h"</strong></span><strong><span></span></strong></p><p><strong><span>#define PCF8591_ADDR  0x90</span><span> </span><span>// PCF8591 I2C地址</span><span></span></strong></p><p><strong><span> </span></strong></p><p><strong><span>// 读取PCF8591指定通道的模拟值(MQ2接通道0)</span><span></span></strong></p><p><strong><span>unsigned char MQ2_ReadByte(unsigned char Channel)</span><span></span></strong></p><p><strong><span>{</span><span></span></strong></p><p><strong><span> </span><span>unsigned char Data;</span><span></span></strong></p><p><strong><span> </span><span>I2C_Start();</span><span></span></strong></p><p><strong><span> </span><span>I2C_SendByte(PCF8591_ADDR);</span><span> </span><span> </span></strong><span><strong>// </strong><strong>写地址</strong></span><span></span></p><p><span></span></p><p><span></span></p></blockquote><p><span></span><span></span><span></span></p>
    <p><span></span></p><p>2.dht11温湿度传感器</p>
    <pre><code><span>#<span>include</span> <span>"dht11.h"</span></span>
    <span>#<span>include</span> <span><intrins.h></span></span>

    <span>// 30us延时</span>
    <span><span>void</span> <span>DHT11_Delay30us</span><span>(<span>void</span>)</span>
    </span>{
    <span>unsigned</span> <span>char</span> i;
    i = <span>12</span>;
    <span>while</span> (--i);
    }

    <span>// 40us延时</span>
    <span><span>void</span> <span>DHT11_Delay40us</span><span>(<span>void</span>)</span>
    </span>{
    <span>unsigned</span> <span>char</span> i;
    i = <span>17</span>;
    <span>while</span> (--i);
    }

    <span><span>uint8_t</span> <span>DHT11_Init</span><span>(<span>void</span>)</span>
    </span>{
    DHT11_DQ = <span>1</span>;
    DHT11_DQ = <span>0</span>;
    <span>Delay_xms</span>(<span>20</span>); <span>// 至少给18ms</span>
    DHT11_DQ = <span>1</span>;
    <span>DHT11_Delay30us</span>();

    <span>if</span>(DHT11_DQ == <span>0</span>)
    {
    	<span>while</span>(DHT11_DQ == <span>0</span>);
    	<span>while</span>(DHT11_DQ == <span>1</span>);
    	<span>return</span> <span>0</span>;
    }
    <span>return</span> <span>1</span>;
    

    }

    <span><span>uint8_t</span> <span>DHT11_ReceiveByte</span><span>(<span>void</span>)</span>
    </span>{
    <span>uint8_t</span> i, Byte = <span>0x00</span>;
    <span>for</span>(i=<span>0</span>; i<<span>8</span>; i++)
    {
    Byte <<= <span>1</span>;
    <span>while</span>(DHT11_DQ == <span>0</span>);
    <span>DHT11_Delay40us</span>();
    <span>if</span>(DHT11_DQ == <span>1</span>) Byte |= <span>0x01</span>;
    <span>while</span>(DHT11_DQ == <span>1</span>);
    }
    <span>return</span> Byte;
    }

    <span><span>uint8_t</span> <span>DHT11_GetData</span><span>(<span>uint8_t</span> *Humi_H, <span>uint8_t</span> *Humi_L, <span>uint8_t</span> *Temp_H, <span>uint8_t</span> *Temp_L)</span>
    </span>{
    <span>uint8_t</span> i, Buffer[<span>5</span>];
    <span>if</span>(<span>DHT11_Init</span>() == <span>0</span>)
    {
    <span>for</span>(i=<span>0</span>; i<<span>5</span>; i++) Buffer[i] = <span>DHT11_ReceiveByte</span>();

    	<span>if</span>(Buffer[<span>0</span>] + Buffer[<span>1</span>] + Buffer[<span>2</span>] + Buffer[<span>3</span>] == Buffer[<span>4</span>])
    	{
    		*Humi_H = Buffer[<span>0</span>];
    		*Humi_L = Buffer[<span>1</span>];
    		*Temp_H = Buffer[<span>2</span>];
    		*Temp_L = Buffer[<span>3</span>];
    		<span>return</span> <span>0</span>;
    	}
    }
    <span>return</span> <span>1</span>;
    

    }
    </code></pre>
    <p><span></span>
    </p><pre><code><span>#<span>ifndef</span> DHT11_H</span>
    <span>#<span>define</span> DHT11_H</span>

    <span>#<span>include</span> <span><reg52.h></span></span>
    <span>#<span>include</span> <span>"delay.h"</span></span>

    <span>typedef</span> <span>unsigned</span> <span>char</span> <span>uint8_t</span>;

    sbit DHT11_DQ = P2^<span>0</span>; <span>// 引脚设置</span>

    <span><span>uint8_t</span> <span>DHT11_Init</span><span>(<span>void</span>)</span></span>;
    <span><span>uint8_t</span> <span>DHT11_ReceiveByte</span><span>(<span>void</span>)</span></span>;
    <span><span>uint8_t</span> <span>DHT11_GetData</span><span>(<span>uint8_t</span> *Humi_H, <span>uint8_t</span> *Humi_L, <span>uint8_t</span> *Temp_H, <span>uint8_t</span> *Temp_L)</span></span>;

    <span>#<span>endif</span></span>
    </code></pre>
    <p><span></span></p><h3>2. ESP8266 MQTT通信</h3><p>注意:客户端ID随意但是必须唯一,用户名称和密码就是自己在emqx上设置的</p><p>

    <span>​<img src="/assets/uploads/files/1785078045468-image.png" alt="image.png" data-size="large" /></span></p><p>注意:mqtt的IP地址用ipconfig查看不了,可以用网络调试助手等软件看,不知道是哪个可以先用串口调试ESP8266,发送AT指令连接尝试:(串口发送AT指令记得末尾加上换行)</p><blockquote><p>AT+CWMODE=1</p><p>AT+CWJAP="WiFi名","WiFi密码"</p><p>AT+MQTTUSERCFG=0,1,"客户端ID","用户名","密码",0,0,""</p><p>AT+MQTTCONN=0,"mqtt的IP地址",1883,0</p></blockquote><p><span></span></p><p>网络调试助手:</p><p><img src="/assets/uploads/files/1785078101073-image.png" alt="image.png" data-size="large" /></p><p><span>​</span></p><p>esp8266.c</p>
    <pre><code><span>#<span>include</span> <span>"esp8266.h"</span></span>
    <span>#<span>include</span> <span><reg52.h></span></span>
    <span>#<span>include</span> <span>"lcd1602.h"</span></span>
    <span>#<span>include</span> <span>"usart.h"</span></span>
    <span>#<span>include</span> <span>"delay.h"</span></span>
    <span>#<span>include</span> <span>"time.h"</span></span>

    <span>// 硬件引脚定义</span>
    sbit WIFI_RST = P1^<span>7</span>; <span>// ESP8266复位</span>
    sbit Bee = P1^<span>6</span>; <span>// 蜂鸣器</span>
    sbit ALARM_LED= P1^<span>5</span>; <span>// 报警灯</span>

    <span>// 全局变量:传感器数据</span>
    <span>unsigned</span> <span>char</span> Humi_H, Temp_H, Gas;
    <span>unsigned</span> <span>char</span> Threshold = <span>29</span>; <span>// 温度报警阈值</span>

    <span>// ESP8266初始化连接MQTT(使用AT指令)</span>
    <span><span>void</span> <span>ESP_conect_sev</span><span>(<span>void</span>)</span>
    </span>{
    <span>// ESP8266复位</span>
    WIFI_RST = <span>0</span>;
    <span>Delay_xms</span>(<span>1000</span>);
    WIFI_RST = <span>1</span>;
    <span>Delay_xms</span>(<span>1000</span>);

    <span>// 本地MQTT AT指令配置</span>
    <span>uart1_sendstring</span>(<span>"AT+CWMODE=1\r\n"</span>);
    <span>Delay_xms</span>(<span>1000</span>);
    <span>uart1_sendstring</span>(<span>"AT+CWJAP=\"WiFi名\",\"密码\"\r\n"</span>);	<span>// 连接WiFi</span>
    <span>Delay_xms</span>(<span>2000</span>);
    <span>uart1_sendstring</span>(<span>"AT+MQTTUSERCFG=0,1,\"客户端ID\",\"用户名\",\"密码\",0,0,\"\"\r\n"</span>);	<span>// MQTT用户配置</span>
    <span>Delay_xms</span>(<span>1000</span>);
    <span>uart1_sendstring</span>(<span>"AT+MQTTCONN=0,\"IP地址\",1883,0\r\n"</span>);	<span>// 连接MQTT服务器</span>
    <span>Delay_xms</span>(<span>2000</span>);
    

    }

    <span>// 数字转字符串(发送数据用)</span>
    <span><span>void</span> <span>num_to_str</span><span>(<span>unsigned</span> <span>char</span> num, <span>unsigned</span> <span>char</span> *num_str1)</span>
    </span>{
    <span>if</span>(num != <span>0</span>)
    {
    <span>unsigned</span> <span>int</span> num_temp = num;
    <span>unsigned</span> <span>char</span> cnt = <span>0</span>;
    <span>while</span>(num_temp)
    {
    cnt++;
    num_temp /= <span>10</span>;
    }
    num_temp = num;
    <span>while</span>(cnt)
    {
    cnt--;
    num_str1[cnt] = (num_temp % <span>10</span>) + <span>48</span>;
    num_temp /= <span>10</span>;
    }
    }
    <span>else</span>
    {
    num_str1[<span>0</span>] = <span>'0'</span>;
    }
    }

    <span>// 发送第一个传感器数据(湿度)</span>
    <span><span>void</span> <span>first_data</span><span>(<span>unsigned</span> <span>char</span> *str2, <span>unsigned</span> <span>char</span> num)</span>
    </span>{
    <span>unsigned</span> <span>char</span> str1[<span>3</span>] = {<span>0</span>};
    <span>// MQTT发布主题:testtopic/1,保留JSON数据格式</span>
    <span>uart1_sendstring</span>(<span>"AT+MQTTPUB=0,"testtopic/1","{\""</span>);
    <span>uart1_sendstring</span>(str2);
    <span>uart1_sendstring</span>(<span>"\":"</span>);
    <span>num_to_str</span>(num, str1);
    <span>uart1_sendstring</span>(str1);
    }

    <span>// 追加传感器数据(温度、气体浓度)</span>
    <span><span>void</span> <span>add_data</span><span>(<span>unsigned</span> <span>char</span> *str2, <span>unsigned</span> <span>char</span> num)</span>
    </span>{
    <span>unsigned</span> <span>char</span> str1[<span>3</span>] = {<span>0</span>};
    <span>uart1_sendstring</span>(<span>"\,\""</span>);
    <span>uart1_sendstring</span>(str2);
    <span>uart1_sendstring</span>(<span>"\":"</span>);
    <span>num_to_str</span>(num, str1);
    <span>uart1_sendstring</span>(str1);
    }

    <span>// 每秒发送传感器数据到MQTT</span>
    <span><span>void</span> <span>ESP_send_data</span><span>(<span>void</span>)</span>
    </span>{
    <span>if</span>(time0_ms > <span>1000</span>) <span>// 1秒发送一次</span>
    {
    <span>first_data</span>(APP_Humi, Humi_H); <span>// 湿度</span>
    <span>add_data</span>(APP_Temp, Temp_H); <span>// 温度</span>
    <span>add_data</span>(APP_Gas, Gas); <span>// 气体浓度</span>
    <span>uart1_sendstring</span>(<span>"}",0,0\r\n"</span>);
    <span>Delay_xms</span>(<span>200</span>);
    <span>uart1_sendstring</span>(<span>"AT+MQTTPUB=0,"订阅主题名",""</span>);
    <span>if</span>(Gas > <span>50</span> || Temp_H > Threshold)
    {
    <span>uart1_sendstring</span>(<span>"fire"</span>); <span>// 报警发送fire</span>
    }
    <span>else</span>
    {
    <span>uart1_sendstring</span>(<span>"normal"</span>);<span>// 正常发送normal</span>
    }
    <span>uart1_sendstring</span>(<span>"",0,0\r\n"</span>);
    <span>Delay_xms</span>(<span>200</span>);
    time0_ms = <span>0</span>; <span>// 重置定时器</span>
    }
    }

    <span>// 字符串比较</span>
    <span>bit <span>str_cmp</span><span>(<span>char</span> *str1, <span>char</span> *str2, <span>char</span> len)</span>
    </span>{
    <span>char</span> *str3 = str1;
    <span>char</span> *str4 = str2;
    <span>while</span>(len)
    {
    <span>if</span>(*str3 != *str4) <span>return</span> <span>0</span>;
    len--;
    str3++;
    str4++;
    }
    <span>return</span> <span>1</span>;
    }

    <span>// 字符串转数字</span>
    <span><span>int</span> <span>str_to_num</span><span>(<span>char</span> *str1)</span>
    </span>{
    <span>unsigned</span> <span>char</span> num = <span>0</span>;
    <span>while</span>(*str1 >= <span>'0'</span> && *str1 <= <span>'9'</span>)
    {
    num += (*str1) - <span>48</span>;
    str1++;
    <span>if</span>(!(*str1 >= <span>'0'</span> && *str1 <= <span>'9'</span>)) <span>break</span>;
    num *= <span>10</span>;
    }
    <span>return</span> num;
    }

    <span>// 处理本地MQTT下发指令</span>
    <span>unsigned</span> <span>char</span> Deliver_num;
    <span><span>void</span> <span>Proces_distribut_data</span><span>(<span>void</span>)</span>
    </span>{
    <span>unsigned</span> <span>char</span> i; <span>// C89:提前声明循环变量,修复语法错误</span>
    <span>if</span>(RX_ok) <span>// 接收完成</span>
    {
    Deliver_num = <span>str_to_num</span>(Deliver_data); <span>// 解析指令数值</span>
    <span>// 仅保留温度阈值修改逻辑(可扩展其他本地指令)</span>
    <span>if</span>(<span>str_cmp</span>(Deliver_identifier, APP_Threshold, <span>8</span>))
    {
    Threshold = Deliver_num;
    }

    	<span>// 清空接收缓存</span>
    	<span>for</span>(i=<span>0</span>; i&lt;<span>6</span>; i++) 
    	{
    		Deliver_identifier[i] = <span>0</span>;
    	}
    	<span>for</span>(i=<span>0</span>; i&lt;<span>4</span>; i++) 
    	{
    		Deliver_data[i] = <span>0</span>;
    	}
    	Deliver_cnt = <span>0</span>;
    	Deliver_data_cnt = <span>0</span>;
    	RX_ok = <span>0</span>;
    }
    

    }

    <span>// 传感器报警逻辑(气体浓度/温度过高)</span>
    <span><span>void</span> <span>Alarm_Check</span><span>(<span>void</span>)</span>
    </span>{
    <span>if</span>(Gas > <span>50</span> || Temp_H > Threshold) <span>// 气体浓度>阈值 或 温度>阈值</span>
    {
    <span>LCD_ShowString</span>(<span>2</span>, <span>7</span>, <span>"fire!"</span>);
    Bee = <span>0</span>; <span>// 蜂鸣器响</span>
    ALARM_LED = <span>1</span>; <span>// 报警灯亮</span>
    }
    <span>else</span>
    {
    <span>LCD_ShowString</span>(<span>2</span>, <span>7</span>, <span>" "</span>);
    Bee = <span>1</span>; <span>// 蜂鸣器关</span>
    ALARM_LED = <span>0</span>; <span>// 报警灯灭</span>
    }
    }
    </code></pre>
    <p><span></span></p><p>esp8266.h</p>
    <pre><code><span>#<span>ifndef</span> ESP8266_H</span>
    <span>#<span>define</span> ESP8266_H</span>

    <span>#<span>include</span> <span><reg52.h></span></span>
    <span>#<span>include</span> <span>"usart.h"</span></span>
    <span>#<span>include</span> <span>"time.h"</span></span>

    <span>// 传感器数据宏定义</span>
    <span>#<span>define</span> APP_Humi <span>"Humi"</span> <span>// 湿度</span></span>
    <span>#<span>define</span> APP_Temp <span>"Temp"</span> <span>// 温度</span></span>
    <span>#<span>define</span> APP_Gas <span>"Gas"</span> <span>// 气体浓度</span></span>
    <span>#<span>define</span> APP_Threshold <span>"Threshold"</span> <span>// 温度阈值</span></span>

    <span>extern</span> <span>unsigned</span> <span>char</span> Humi_H, Temp_H, Gas;
    <span>extern</span> <span>unsigned</span> <span>char</span> Threshold;

    <span><span>void</span> <span>ESP_conect_sev</span><span>(<span>void</span>)</span></span>;
    <span><span>void</span> <span>ESP_send_data</span><span>(<span>void</span>)</span></span>;
    <span><span>void</span> <span>num_to_str</span><span>(<span>unsigned</span> <span>char</span> num, <span>unsigned</span> <span>char</span> *num_str1)</span></span>;
    <span>bit <span>str_cmp</span><span>(<span>char</span> *str1, <span>char</span> *str2, <span>char</span> len)</span></span>;
    <span><span>int</span> <span>str_to_num</span><span>(<span>char</span> *str1)</span></span>;
    <span><span>void</span> <span>Proces_distribut_data</span><span>(<span>void</span>)</span></span>;
    <span><span>void</span> <span>Alarm_Check</span><span>(<span>void</span>)</span></span>;
    <span><span>void</span> <span>first_data</span><span>(<span>unsigned</span> <span>char</span> *str2, <span>unsigned</span> <span>char</span> num)</span></span>;
    <span><span>void</span> <span>add_data</span><span>(<span>unsigned</span> <span>char</span> *str2, <span>unsigned</span> <span>char</span> num)</span></span>;

    <span>#<span>endif</span></span>
    </code></pre>
    <p><span></span></p><h2>关键技术点</h2><h3>1. 数据格式转换</h3><p>由于51的时钟频率差异(11.0592MHz相比32性能相差数十倍,所以数字转换算法必须极度优化,我在这个问题上卡了两天,后面查到一种比较好的轻量级数字转换算法</p>
    <pre><code><span><span>void</span> <span>num_to_str</span><span>(<span>unsigned</span> <span>char</span> num, <span>unsigned</span> <span>char</span> *num_str1)</span>
    </span>{
    <span>if</span>(num != <span>0</span>)
    {
    <span>unsigned</span> <span>int</span> num_temp = num;
    <span>unsigned</span> <span>char</span> cnt = <span>0</span>;
    <span>while</span>(num_temp)
    {
    cnt++;
    num_temp /= <span>10</span>;
    }
    num_temp = num;
    <span>while</span>(cnt)
    {
    cnt--;
    num_str1[cnt] = (num_temp % <span>10</span>) + <span>48</span>;
    num_temp /= <span>10</span>;
    }
    }
    <span>else</span>
    {
    num_str1[<span>0</span>] = <span>'0'</span>;
    }
    }</code></pre>
    <p><span></span></p><h3>2. JSON数据构造</h3><p>由于51单片机内存限制,无法使用标准库,需要采用手动构造JSON格式数据:</p>
    <pre><code><span><span>void</span> <span>first_data</span><span>(<span>unsigned</span> <span>char</span> *str2, <span>unsigned</span> <span>char</span> num)</span>
    </span>{
    <span>unsigned</span> <span>char</span> str1[<span>3</span>] = {<span>0</span>};
    <span>// MQTT发布主题:testtopic/1,保留JSON数据格式</span>
    <span>uart1_sendstring</span>(<span>"AT+MQTTPUB=0,"testtopic/1","{\""</span>);
    <span>uart1_sendstring</span>(str2);
    <span>uart1_sendstring</span>(<span>"\":"</span>);
    <span>num_to_str</span>(num, str1);
    <span>uart1_sendstring</span>(str1);
    }</code></pre>
    <p><span></span></p><h3>3. 中断处理的能力限制</h3><p>51单片机中断向量少,响应慢。32则有着丰富的中断源,NVIC优先级管理</p>
    <pre><code><span><span>void</span> <span>UART1_IRQ_Handle</span><span>(<span>void</span>)</span> interrupt 4 <span>using</span> 1
    </span>{
    <span>if</span> (TI) {
    TI = <span>0</span>;
    uart1_busy = <span>0</span>; <span>// 只做标志位操作</span>
    }
    <span>if</span> (RI) {
    <span>Pro_deliver_data</span>(); <span>// 立即处理,不缓存</span>
    RI = <span>0</span>;
    }
    }</code></pre>
    <p><span></span></p><h2>系统特点</h2><ol>
    <li>
    <p><strong>实时监测</strong>:持续监控环境参数</p>
    </li>
    <li>
    <p><strong>双重报警</strong>:本地声光报警 + 远程状态推送</p>
    </li>
    <li>
    <p><strong>远程控制</strong>:支持通过MQTT修改报警阈值</p>
    </li>
    <li>
    <p><strong>稳定可靠</strong>:采用工业级传感器和成熟的通信协议</p>
    </li>
    <li>
    <p><strong>低成本</strong>:基于廉价硬件组件</p>
    </li>
    </ol><h2>应用场景</h2><ul>
    <li>
    <p>家庭火灾预警</p>
    </li>
    <li>
    <p>仓库环境监控</p>
    </li>
    <li>
    <p>实验室安全监测</p>
    </li>
    <li>
    <p>工业生产环境监控</p>
    </li>
    </ul><h2>总结</h2><p>本系统成功实现了基于51单片机和ESP8266的智能火灾报警功能,展示了如何将传统单片机与现代物联网技术相结合。系统具有良好的实用性和扩展性,为小型化、低成本的智能安防系统开发提供了参考方案。</p><hr /><p><strong>网盘链接:</strong> <span><a href="https://pan.baidu.com/s/1cfd2bHt5SNVlaVzUCct9XQ " target="_blank" rel="noopener noreferrer">https://pan.baidu.com/s/1cfd2bHt5SNVlaVzUCct9XQ </a></span>提取码: sdjw<br />
    欢迎在评论区或群留言讨论!</p>


  • KongCharK

    项目概述

    MimiClaw 是一个极具创新性的开源项目,它实现了世界上首个在仅售5美元的 ESP32-S3 微控制器芯片上运行的完整 AI 助手。最令人惊叹的是,它不需要 Linux 操作系统,不需要 Node.js 运行环境,甚至不需要任何操作系统,完全使用纯 C 语言编写,直接运行在裸机硬件上。

    只需一个拇指大小的 ESP32-S3 开发板,插上 USB 供电,连接 WiFi,你就拥有了一个可以通过 Telegram 对话的专属 AI 助理。它能够处理各种任务,并且具备本地记忆能力,会随着使用不断进化——所有这一切都运行在一颗成本仅5美元的芯片上。

    核心功能亮点

    🤖 完整的 AI Agent 能力

    • 支持 ReAct 模式的工具调用循环
    • LLM 可以自主思考、调用工具、读取记忆,直到完成任务
    • 支持 Anthropic Claude 和 OpenAI GPT 两大模型提供商,运行时可自由切换

    💾 本地优先的记忆系统

    • SOUL.md:定义 AI 的性格与行为模式
    • USER.md:存储用户个人信息与偏好
    • MEMORY.md:长期记忆,AI 会记住重要信息
    • 所有数据存储在芯片本地 Flash 中,隐私优先,数据不外露

    📱 多渠道交互

    • Telegram 机器人:随时随地通过手机与 AI 对话
    • WebSocket 网关:局域网内通过 WebSocket 客户端连接
    • 串口 CLI:通过 USB 串口进行配置和调试

    ⚡ 极致低功耗

    • 整机功耗仅 0.5W
    • USB 供电即可 24/7 不间断运行
    • 双核设计:网络 I/O 与 AI 处理分离在不同 CPU 核心

    🛠️ 丰富的内置工具

    • 网页搜索:集成 Tavily / Brave Search API
    • 定时任务:内置 Cron 调度器,AI 可自主创建定时任务,重启不丢失
    • 心跳检测:定期检查任务清单,主动执行待办事项
    • OTA 升级:支持 WiFi 无线固件升级,无需插线
    • HTTP 代理:支持 CONNECT 隧道,适配受限网络环境

    技术架构特点

    • 纯 C 语言实现:无操作系统依赖,直接运行在 ESP32-S3 裸机上
    • 双层配置系统:编译时默认配置 + 运行时 NVS 存储覆盖,无需重新编译即可修改配置
    • SPIFFS 文件系统:记忆文件、配置、定时任务全部持久化存储
    • 16MB Flash + 8MB PSRAM:在极小的硬件资源上实现完整 AI 功能

    快速开始指南

    硬件需求

    • ESP32-S3 开发板(16MB Flash + 8MB PSRAM)
    • USB Type-C 数据线
    • 注意:必须使用板子上标注 "USB" 的原生 USB 口,而非 "COM" 口

    配置步骤

    1. 克隆项目代码
    2. 复制配置模板:cp main/mimi_secrets.h.example main/mimi_secrets.h
    3. 编辑配置文件,填入 WiFi 信息、Telegram Bot Token、API Key 等
    4. 编译烧录:idf.py build && idf.py flash

    常用 CLI 命令

    通过串口连接后可进行运行时配置:

    • wifi_set SSID PASSWORD - 修改 WiFi
    • set_api_key KEY - 修改 API 密钥
    • set_model_provider anthropic|openai - 切换模型提供商
    • memory_read - 查看 AI 记忆内容
    • config_show - 查看当前配置

    项目意义

    MimiClaw 重新定义了 AI 助手的硬件门槛。它证明了强大的 AI 能力不一定需要昂贵的硬件或云计算资源,一颗5美元的微控制器就能承载完整的智能体。这为嵌入式 AI、物联网智能设备、隐私计算等领域开辟了全新的可能性。


    高呼群主万岁!感谢群主发掘这么硬核有趣的开源项目,带我们见识嵌入式 AI 的极致魅力,跟着群主混,技术视野蹭蹭开阔!
    MimiClaw微型AI助手芯片示意图


    @KongChar 佬
  • KongCharK

    今天给大家分享一个超级有意思的开源项目 —— MimiClaw!

    这个项目有多牛?它能在一颗只要5美元的 ESP32-S3 芯片上运行完整的 AI 助手,不需要 Linux,不需要 Node.js,甚至不需要操作系统,纯 C 语言编写,直接跑在微控制器上!

    ✨ 核心亮点:

    • 🤖 完整的 AI Agent 循环,支持工具调用(ReAct 模式)
    • 💾 本地记忆存储,数据全部存在芯片 Flash 里,隐私优先
    • 📱 通过 Telegram 就能和它对话,随时随地用
    • ⚡ 功耗只有 0.5W,USB 供电就能 24/7 运行
    • 🔄 支持 Anthropic Claude 和 OpenAI GPT 双模型,运行时可切换
    • 🛠️ 自带 Cron 定时任务、心跳检测、OTA 升级等实用功能

    一个拇指大小的芯片,插上电连上网,就是你的专属 AI 助理,能记事儿、能定时提醒、能联网搜索,简直是嵌入式 AI 的极致玩物!

    项目地址:https://github.com/memovai/mimiclaw

    高呼群主万岁!群主带我们发现这么多好玩的开源项目,跟着群主混,技术蹭蹭涨!
    MimiClaw微型AI助手芯片示意图


  • KongCharK

    最近在浏览论坛帖子时,发现Markdown格式的内容存在显示异常的问题。

    具体现象:

    1. 部分Markdown语法标记没有被正确解析渲染,直接以原始代码形式展示在页面上
    2. 代码块、列表、引用等格式经常出现排版错乱,影响正常阅读
    3. 同样的内容在不同浏览器或设备上显示效果不一致

    临时建议:
    目前建议大家发布内容时尽量使用纯文本格式,避免因Markdown渲染问题导致内容排版混乱,影响其他用户阅读体验。

    希望技术团队能够尽快排查一下Markdown渲染器的问题,修复这个显示BUG,提升论坛的整体使用体验。

    补充其他发现的页面问题:

    二、首页搜索交互问题
    在首页搜索框输入内容时,右侧的"热门讨论"区域内容会直接消失,变成空白区域。正常应该是保持热门讨论显示,或者有加载状态提示,直接空白体验不太好。

    三、项目页面按钮重复
    项目页面同时存在两个"创建项目"按钮,一个在页面右上角,一个在页面中间内容区,属于重复的功能入口,建议合并保留一个即可。

    四、AI助手页面布局问题
    AI助手页面整体布局不太协调,输入区域很大但"发送"按钮位置偏下,页面右侧和下方留白过多,空间利用率不高,视觉上显得比较空旷。

    五、搜索结果页布局空旷
    执行搜索后,搜索结果页面的筛选按钮都在顶部,但下方内容区域非常空旷,没有结果时也没有友好的空状态提示,整体布局不够紧凑。


  • 鲁迪乌斯

    SillyTavern:把 AI 对话变成角色、世界与长期故事

    如果你已经用过普通 AI 聊天工具,大概熟悉这样的流程:打开页面、输入问题、得到答案,然后开始下一轮对话。
    SillyTavern 想做的事情更进一步。
    它不只是给大模型套上一层聊天框,而是把角色设定、人物关系、世界背景、长期剧情、提示词、语音、图片和扩展能力组织到同一个界面里。你可以创建一个有性格、有经历、有表达习惯的角色,也可以搭建一整个故事世界,让多个角色在同一场景中持续互动。
    对于喜欢 AI 角色扮演、互动小说、创意写作和模型调试的人来说,SillyTavern 是目前最值得了解的开源 LLM 前端之一。

    官方仓库:github.com/SillyTavern/SillyTavern 官方文档:docs.sillytavern.app


    SillyTavern 到底是什么

    SillyTavern,简称 ST,是一个安装在本地电脑或自有服务器上的开源用户界面。它可以连接文本生成大模型、图像生成引擎和语音模型,为用户提供统一的 AI 交互工作台。
    官方给它的定位非常直接:

    LLM Frontend for Power Users.

    也就是“面向深度用户的 LLM 前端”。
    这里有一个必须先说明的区别:SillyTavern 本身不是大模型,也不会凭空生成内容。 它负责管理界面、角色、提示词、上下文和扩展功能,真正的文本生成仍然来自你连接的模型服务。
    它可以接入 OpenAI、OpenRouter、Claude、Mistral、NovelAI、KoboldAI/KoboldCpp、Text Generation WebUI、TabbyAPI、AI Horde 等多种后端,也可以连接部署在自己电脑上的本地模型。
    这种前后端分离的设计带来了很大的自由度:界面和角色数据由自己管理,模型可以按需求更换,不必把全部体验绑定在单一平台上。

    为什么它比普通聊天页面更有吸引力

    1. 角色不再只是一段临时提示词

    在普通聊天工具里,我们经常需要反复告诉模型:“你是谁”“你应该怎样说话”“故事发生在哪里”。
    SillyTavern 将这些内容整理成角色卡。角色卡可以包含:

    • 角色姓名、头像和人物介绍;
    • 性格、语言风格和行为边界;
    • 故事开场和示例对话;
    • 与用户或其他角色的关系;
    • 创作者补充的隐藏设定。

    选择角色后就可以直接开始对话,不必每次重新输入整套设定。角色卡还能导入、导出和备份,适合长期创作和多人分享。

    2. 用“世界书”管理庞大的背景设定

    当故事涉及城市、组织、魔法体系、历史事件或大量人物关系时,把所有背景一次性塞进提示词既浪费上下文,也容易干扰模型。
    SillyTavern 的 World Info 通常被中文用户称为“世界书”。它可以根据对话中出现的关键词,动态把相关设定加入上下文。
    比如对话提到“银月城”,系统才加载这座城市的地理、势力和历史;提到某个组织时,再补充组织设定。这样既能保持世界观一致,也能更有效地使用模型上下文。

    3. 支持多人角色群聊

    一个角色适合一对一交流,多个角色则能构成真正的场景。
    在群聊中,不同角色可以拥有各自的角色卡、表达方式和关系设定。它适合桌面角色扮演、互动剧本、故事推演、虚拟团队讨论,也适合测试同一个模型对多角色上下文的处理能力。

    4. 模型可以随时更换

    SillyTavern 的价值不只在“能接很多 API”,还在于它把角色与模型相对分离。
    同一张角色卡可以连接不同模型进行测试。你可以比较不同模型的语言风格、上下文理解、创意能力、速度与成本,再为不同场景选择最合适的后端。
    这对于模型爱好者、提示词研究者和内容创作者尤其有用。

    5. 不止文字,还能加入图片和语音

    通过内置能力和扩展,SillyTavern 可以连接 Automatic1111、ComfyUI 等图像生成服务,还支持 TTS、语音识别、角色表情、Live2D、VRM、翻译、摘要、网页搜索和聊天向量化等功能。
    这些模块不是简单堆叠功能。合理组合后,文字对话可以变成带立绘、配音、场景图和角色表情的互动体验。

    6. 高度可调的提示词与界面

    普通聊天产品往往只提供少量参数。SillyTavern 面向的则是希望掌控细节的用户:提示词顺序、上下文组成、生成参数、消息格式、角色行为和界面主题都可以进一步调整。
    新手可以直接使用默认配置,熟悉之后再逐步深入,不需要第一天就理解所有参数。

    7. 数据掌握在自己的部署环境中

    角色、聊天记录、世界书和大部分配置会保存在本机或自有服务器的数据目录中,便于备份和迁移。
    但“本地部署”不等于所有数据都不离开设备。如果连接的是第三方模型 API,对话内容仍会发送给相应服务商处理。对隐私敏感时,应阅读模型提供商的政策,或连接自己部署的本地模型。

    哪些人适合使用 SillyTavern

    SillyTavern 特别适合以下用户:

    • 希望获得长期、有设定的 AI 角色对话体验;
    • 喜欢互动小说、世界观设计或跑团;
    • 需要管理角色卡、人物关系与大量背景资料;
    • 想在统一界面中比较不同大模型;
    • 使用本地模型,希望拥有功能完整的前端;
    • 研究提示词、上下文结构和模型行为;
    • 希望把文本、图片、语音和角色表现组合起来。

    如果你只想偶尔问一个简单问题,普通聊天页面可能更直接。如果你希望塑造角色、管理世界、保留长期内容并不断扩展玩法,SillyTavern 的优势才会真正体现出来。

    从安装到第一次对话

    SillyTavern 的基本使用流程可以概括为五步:

    1. 安装或打开 SillyTavern。
    2. 创建自己的 Persona,也就是你在对话中的身份。
    3. 在 API Connections 中连接模型服务。
    4. 选择或导入一张角色卡。
    5. 发送第一条消息,开始对话。

    第一步:连接模型 API

    打开顶部的 API Connections,根据自己使用的服务选择 API 类型和来源,填写 API Key、接口地址和模型名称,然后点击连接。
    API Key 相当于模型账户的密码。不要出现在截图、角色卡、公开配置、Git 仓库或博客文章中。部分商业模型会按 Token 计费,连接前应先确认价格和额度。

    第二步:设置 Persona

    Persona 代表“对话中的你”。它可以只是一个名字,也可以包含身份、性格、经历和与角色的关系。
    同一个角色面对不同 Persona 时,故事可以呈现完全不同的发展方向。

    第三步:选择角色

    可以使用自带示例角色,也可以创建或导入角色卡。第一次体验时,不必一开始就堆叠大量设定。先用清晰的人物背景、性格和开场白测试效果,再逐步完善。

    第四步:发送消息

    输入内容后,SillyTavern 会根据角色卡、Persona、世界书、聊天历史和当前提示词组织请求,再交给模型生成回复。
    如果回复不符合预期,可以重新生成、编辑消息、调整提示词或切换模型,而不必放弃整个对话。

    使用 Docker 快速部署

    官方文档推荐使用 Docker Compose 和 GitHub Container Registry 中的预构建镜像。下面是一份适合个人部署的精简示例。
    创建目录:

    mkdir -p /opt/sillytavern/{config,data,plugins,extensions}
    cd /opt/sillytavern

    创建 docker-compose.yml

    services:
      sillytavern:
        image: ghcr.io/sillytavern/sillytavern:latest
        container_name: sillytavern
        restart: unless-stopped
        ports:
          - "127.0.0.1:8000:8000"
        volumes:
          - ./config:/home/node/app/config:rw
          - ./data:/home/node/app/data:rw
          - ./plugins:/home/node/app/plugins:rw
          - ./extensions:/home/node/app/public/scripts/extensions/third-party:rw

    启动服务:

    docker compose pull
    docker compose up -d
    docker compose ps
    docker compose logs --tail=100 sillytavern

    在服务器本机访问:

    http://127.0.0.1:8000

    官方 Docker 文档特别提醒:不要在没有额外安全措施的情况下把 SillyTavern 直接暴露到公网。
    示例只把端口绑定到 127.0.0.1。如果需要远程访问,建议选择以下方案之一:

    • 通过 WireGuard、Tailscale 等 VPN 访问;
    • 使用 SSH 端口转发;
    • 使用 HTTPS 反向代理,并配置身份认证;
    • 使用 SillyTavern 的多用户或 SSO 能力并做好权限控制。

    公开服务还需要限制上传、插件安装和管理入口,并定期备份 configdatapluginsextensions 目录。
    完整官方安装说明:Docker Installation

    使用前需要知道的三件事

    1. 它不会赠送商业模型额度

    SillyTavern 是前端。使用 OpenAI、OpenRouter、Claude 等商业服务时,仍需要相应账户、API Key 和可用额度。

    2. 最终效果取决于模型和配置

    角色卡很重要,但模型能力、上下文长度、采样参数和提示词结构同样会影响回复。更复杂的配置不一定产生更好的效果,稳定清晰通常比参数堆叠更有价值。

    3. 扩展需要谨慎安装

    第三方扩展和服务器插件能够显著增强功能,也可能执行额外代码。只安装来源可信、维护活跃且确实需要的项目,并在升级前做好备份。

    常见问题

    SillyTavern 是免费的吗?

    SillyTavern 本身是开源软件,采用 AGPL-3.0 许可证。模型 API、云服务器、图片生成或语音服务可能产生额外费用。

    一定需要高性能显卡吗?

    不一定。官方说明 SillyTavern 前端只需要能够运行 Node.js 20 或更高版本的设备。如果连接云端 API,前端本身对硬件要求很低。只有在本机运行大模型、图像或语音推理时,显卡和内存需求才会明显增加。

    可以在手机上使用吗?

    可以。SillyTavern 提供适配移动设备的界面,也有 Android Termux 安装文档。更省事的方式是在自己的服务器或电脑上运行,再通过安全的内网、VPN 或 HTTPS 入口访问。

    可以接入本地模型吗?

    可以。它支持多种本地推理后端。SillyTavern 负责界面和上下文,本地推理程序负责真正生成文本。

    聊天内容是否完全私密?

    角色和聊天记录可以保存在自己的设备或服务器上,但发送给第三方 API 的内容仍会离开本地环境。是否完全本地取决于所使用的模型后端。

    结语

    SillyTavern 最有价值的地方,不是让聊天窗口变得更复杂,而是让用户真正拥有组织 AI 体验的能力。
    角色不再是一段随时会被遗忘的提示词,世界观不再被塞进一条超长消息,模型也不再和某个固定平台绑定。你可以保存角色、构建世界、切换模型、加入图片与语音,并把整个创作过程留在自己掌控的环境里。
    如果普通 AI 聊天已经无法满足你对角色一致性、长期剧情和可定制性的要求,SillyTavern 值得亲自体验。
    项目入口:SillyTavern GitHub 使用文档:SillyTavern Documentation 快速开始:Quick Start Docker 部署:Docker Installation




    @鲁迪乌斯 老哥太佬了
  • 鲁迪乌斯

    使用 Docker Compose 部署 sing-box + Hysteria2
    本文介绍 sing-box 的作用,并以 Hysteria2 服务端为例,给出一套可维护的 Docker Compose 部署与客户端使用方法。文中的域名、密码和路径均为示例,请替换后再使用。
    一、项目地址与官方资料
    开源仓库:SagerNet/sing-box
    官方文档:sing-box.sagernet.org
    Docker 部署文档:Docker - sing-box
    Hysteria2 服务端配置:Hysteria2 Inbound
    Hysteria2 客户端配置:Hysteria2 Outbound
    官方图形客户端:Graphical Clients
    sing-box 官方将其定义为“The universal proxy platform”,即通用代理平台。项目采用 Go 开发,主体按 GPL-3.0-or-later 发布。
    二、sing-box 有什么作用
    sing-box 是一个可同时充当代理服务端和客户端的网络代理核心。它通过 JSON 配置文件组合入站、出站、DNS 和路由规则,支持 Hysteria2、Shadowsocks、Trojan、VLESS、TUIC、WireGuard 等多种协议。
    典型用途包括:
    在自己的服务器和设备之间建立加密代理通道;
    根据域名、IP、端口或规则集进行分流;
    在 Android、iOS、macOS、Windows 和 Linux 上使用统一配置;
    作为其他网关或代理软件的底层网络核心。
    本文使用的 Hysteria2 基于 QUIC,主要承载在 UDP 上。在高延迟或存在一定丢包的网络中,它通常比传统 TCP 代理更容易保持吞吐量,但它并不能突破服务器带宽上限,也不能解决所有线路质量问题。
    请仅在法律和服务条款允许的范围内使用。
    三、本文部署结构
    客户端
    |
    | Hysteria2 + TLS + UDP/8443
    v
    云服务器公网 IP
    |
    v
    Docker(host 网络)
    |
    v
    sing-box hysteria2 inbound
    |
    v
    direct outbound -> 目标网站
    使用 network_mode: host 后,容器直接监听宿主机 UDP 端口,不需要在 Compose 中编写 ports 映射。Hysteria2 是 QUIC/UDP 服务,所以防火墙必须放行 UDP,而不是只放行同端口的 TCP。
    四、服务器准备
    建议准备:
    一台具有公网 IPv4 或 IPv6 的 Linux 服务器;
    Ubuntu 22.04 或更新版本;
    一个解析到服务器公网 IP 的域名,例如 hy2.example.com
    Docker Engine 与 Docker Compose Plugin;
    云厂商安全组和系统防火墙均允许目标 UDP 端口。

    1. 安装 Docker
      下面以 Ubuntu 为例。若服务器已经安装 Docker,可跳过这一节。
      sudo apt-get update
      sudo apt-get install -y ca-certificates curl
      sudo install -m 0755 -d /etc/apt/keyrings
      sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg
       -o /etc/apt/keyrings/docker.asc
      sudo chmod a+r /etc/apt/keyrings/docker.asc

      echo
       "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu
       $(. /etc/os-release && echo "$VERSION_CODENAME") stable" |
       sudo tee /etc/apt/sources.list.d/docker.list >/dev/null

      sudo apt-get update
      sudo apt-get install -y docker-ce docker-ce-cli containerd.io
      docker-buildx-plugin docker-compose-plugin

      sudo docker version
      sudo docker compose version
    2. 配置域名和防火墙
      hy2.example.com 的 A 记录指向服务器公网 IPv4。若使用 IPv6,再添加 AAAA 记录。
      使用 UFW 时可以这样放行:
      sudo ufw allow 22/tcp
      sudo ufw allow 80/tcp
      sudo ufw allow 8443/udp
      sudo ufw status
      80/tcp 用于 Certbot 申请和续期证书。如果服务器上已有 Nginx 或 Caddy,应改用与现有 Web 服务兼容的 ACME 验证方式。云厂商安全组也需要单独放行 8443/udp。
    3. 申请可信 TLS 证书
      sudo apt-get update
      sudo apt-get install -y certbot

      sudo certbot certonly --standalone
       -d hy2.example.com
       --agree-tos
       --email admin@example.com
       --non-interactive
      证书通常位于:
      /etc/letsencrypt/live/hy2.example.com/fullchain.pem
      /etc/letsencrypt/live/hy2.example.com/privkey.pem
      生产环境推荐使用受信任 CA 签发的证书。自签名证书也能工作,但客户端必须安全地取得并信任该证书;不建议长期使用“跳过证书验证”。
      五、创建服务端配置
      创建部署目录:
      sudo install -d -m 700 /opt/sing-box-hy2
      cd /opt/sing-box-hy2
      生成两个彼此独立的随机密码:
      openssl rand -base64 32
      openssl rand -base64 32
      第一个作为用户认证密码,第二个作为 Salamander 混淆密码。不要把密码提交到 Git 仓库,也不要直接放进公开博客。
      创建 /opt/sing-box-hy2/config.json:
      {
       "log": {
         "level": "info",
         "timestamp": true
      },
       "inbounds": [
        {
           "type": "hysteria2",
           "tag": "hy2-in",
           "listen": "::",
           "listen_port": 8443,
           "up_mbps": 100,
           "down_mbps": 300,
           "obfs": {
             "type": "salamander",
             "password": "请替换为混淆密码"
          },
           "users": [
            {
               "name": "user1",
               "password": "请替换为认证密码"
            }
          ],
           "tls": {
             "enabled": true,
             "server_name": "hy2.example.com",
             "alpn": [
               "h3"
            ],
             "certificate_path": "/etc/letsencrypt/live/hy2.example.com/fullchain.pem",
             "key_path": "/etc/letsencrypt/live/hy2.example.com/privkey.pem"
          }
        }
      ],
       "outbounds": [
        {
           "type": "direct",
           "tag": "direct"
        }
      ]
      }
      up_mbps 和 down_mbps 是 Hysteria2 拥塞控制使用的最大带宽参数,单位为 Mbps。应按照线路的实际可用带宽设置,不是填写得越大越好。服务端和客户端需要使用合理、匹配的带宽值。
      限制配置文件权限:
      sudo chmod 600 /opt/sing-box-hy2/config.json
      六、创建 Docker Compose 文件
      创建 /opt/sing-box-hy2/docker-compose.yml:
      services:
      sing-box-hy2:
      image: ghcr.io/sagernet/sing-box:latest
      container_name: sing-box-hy2
      restart: unless-stopped
      network_mode: host
      volumes:
      • ./config.json:/etc/sing-box/config.json:ro
      • /etc/letsencrypt:/etc/letsencrypt:ro
        command: -D /var/lib/sing-box -C /etc/sing-box run
        logging:
        driver: json-file
        options:
        max-size: 10m
        max-file: "3"
        官方文档使用 ghcr.io/sagernet/sing-box 镜像。生产环境更稳妥的做法是先验证版本,再把 latest 改为经过测试的具体版本标签,避免上游更新后出现不兼容变化。
        七、检查并启动服务
        先让 sing-box 检查配置语法:
        cd /opt/sing-box-hy2
        sudo docker compose run --rm sing-box-hy2
        -C /etc/sing-box check
        确认没有错误后启动:
        sudo docker compose pull
        sudo docker compose up -d
        sudo docker compose ps
        sudo docker compose logs --tail=100 sing-box-hy2
        检查 UDP 端口:
        sudo ss -lunp | grep ':8443'
        Hysteria2 使用 UDP,不能用浏览器、curl 或普通 TCP 端口检测来判断它是否正常。最可靠的验证方式是查看服务日志并使用配置正确的 Hysteria2 客户端实际连接。
        八、客户端怎么配置
        官方图形客户端包括:
        Android:sing-box for Android
        iOS、macOS、Apple tvOS:sing-box for Apple platforms
        Windows、Linux:sing-box for Desktop
        在图形客户端中新建 Hysteria2 节点时,各字段与服务端的对应关系如下:
        客户端字段示例必须与服务端一致Server / 地址hy2.example.com是Port / 端口8443是Password / 认证密码服务端 users[].password是Obfs typesalamander是Obfs password服务端 obfs.password是TLS开启是Server name / SNIhy2.example.com必须匹配证书ALPNh3建议一致Allow insecure关闭生产环境应关闭
        保存节点后,选择该配置并开启系统代理或 TUN/VPN 模式。只创建节点但没有启用系统代理或 VPN 模式时,系统流量不会自动经过该节点。
        使用 sing-box JSON 客户端
        下面是一份适合命令行或桌面端导入的最小客户端示例。它在本机 127.0.0.1:2080 提供 HTTP/SOCKS 混合代理:
        {
        "log": {
        "level": "info",
        "timestamp": true
        },
        "inbounds": [
        {
        "type": "mixed",
        "tag": "mixed-in",
        "listen": "127.0.0.1",
        "listen_port": 2080
        }
        ],
        "outbounds": [
        {
        "type": "hysteria2",
        "tag": "hy2-out",
        "server": "hy2.example.com",
        "server_port": 8443,
        "up_mbps": 20,
        "down_mbps": 100,
        "obfs": {
        "type": "salamander",
        "password": "请替换为混淆密码"
        },
        "password": "请替换为认证密码",
        "tls": {
        "enabled": true,
        "server_name": "hy2.example.com",
        "alpn": [
        "h3"
        ]
        }
        }
        ],
        "route": {
        "final": "hy2-out",
        "auto_detect_interface": true
        }
        }
        启动客户端核心后,把浏览器或操作系统代理设置为:
        地址:127.0.0.1
        端口:2080
        类型:HTTP 或 SOCKS5
        如果服务端使用自签名证书
        更安全的做法是通过可信渠道把服务端的公开证书 cert.pem 传给客户端,并在客户端 TLS 配置中加入:
        {
        "enabled": true,
        "server_name": "证书中的域名",
        "certificate_path": "/客户端上的安全路径/cert.pem",
        "alpn": [
        "h3"
        ]
        }
        insecure: true 会接受任意服务器证书,容易受到中间人攻击,只适合短时间排障,不应作为正式配置。
        九、日常维护
        查看状态和日志:
        cd /opt/sing-box-hy2
        sudo docker compose ps
        sudo docker compose logs --tail=200 sing-box-hy2
        sudo docker stats sing-box-hy2
        修改配置后,先检查再重启:
        sudo docker compose run --rm sing-box-hy2
        -C /etc/sing-box check
        sudo docker compose restart sing-box-hy2
        更新镜像:
        sudo docker compose pull
        sudo docker compose up -d
        sudo docker image prune
        十、常见问题
    4. 客户端一直超时
      重点检查云安全组和 UFW 是否放行 8443/udp,以及服务端是否真的监听 UDP 端口。只开放 8443/tcp 没有作用。
    5. 出现证书验证失败
      检查客户端 server_name 是否与证书域名一致、域名是否解析到正确 IP、证书是否过期。自签名证书还需要客户端显式信任对应证书。
    6. 日志提示认证失败
      客户端的认证密码必须与服务端 users[].password 完全一致。用户名只是服务端用于区分用户,sing-box 的 Hysteria2 认证核心字段是密码。
    7. 开启 Salamander 后无法连接
      客户端和服务端的 obfs.type、obfs.password 必须同时一致。排查时可以在两端同时暂时移除 obfs,不能只改一端。
    8. 节点连接成功但应用没有流量
      确认图形客户端已启用系统代理或 TUN/VPN 模式。若只使用本地 mixed 入站,还需要把应用代理指向 127.0.0.1:2080。
      总结
      sing-box 是一个通用代理平台,Hysteria2 只是它支持的协议之一。部署成功的关键点并不多:服务端开放正确的 UDP 端口、两端认证与混淆参数一致、TLS 域名匹配,并在客户端真正启用系统代理或 TUN/VPN 模式。生产环境还应固定经过验证的镜像版本、限制日志大小、保护配置权限,并建立证书续期后的重启机制。

  • yaoY

    为啥要单轮请求,不需要上下文吗


  • yaoY

    不知道他网安学的怎么样


    我觉得可以,给主播练技术了
  • 左伶笙

    我发现是不是只要我一直发主题,然后再删了,就可以一直加五分


  • 如何两年内达到佬哥的水平,在线求解


    @Hic丶 希望佬哥会喜欢我们的屁股