Funpack4-3: 基于 MAX32655 的温湿度数据显示系统
该项目使用了MAX32655FTHR,小米温湿度计,实现了温湿度数据显示的设计,它的主要功能为:通过蓝牙读取温湿度数据,显示在LCD屏幕上。
标签
LCD
MAX32655FTHR
小米温湿度计
clr
更新2026-02-09
25

一、 项目介绍

本项目旨在构建一个低功耗、可视化的温湿度数据显示系统。核心控制器采用 Analog Devices MAX32655 超低功耗微控制器,作为 BLE Central(主机)设备,负责通过蓝牙扫描并连接远程温湿度传感器,定期读取环境数据。


MAX32655 采用超低功耗双核架构,主核(Arm Cortex-M4F (100MHz)),负责高性能计算,协处理器(RISC-V),专门分担蓝牙协议栈的时序关键任务,支持蓝牙长距离模式 (Long Range)、高吞吐量 (2Mbps) 以及多连接,能够极大降低系统功耗。开发板板载板载数字麦克风和低功耗立体声音频编解码器集成 MAX20303 PMIC,支持锂电池充电、电量计 (Fuel Gauge) 和电源路径管理DAPLink 调试器,无需额外编程器;还有128Mb FLASH,非常适合开发语音/音频类可穿戴应用。


项目目标:

  1. 使用蓝牙连接小米温湿度计
  2. 通过串口传递温湿度数据
  3. 在LCD屏幕显示温湿度数据


二. 设计思路

基于 SDK 的示例工程(ble_otac)进行修改,已经配置好了蓝牙相关配置和连接功能实现。

使用没有修改固件的小米温湿度传感器,只能通过长连接的方式获取数据;订阅需要米家的密钥,所以无法广播读取数据。

使用 CodeFusion Studio(vs code)进行开发。

1. 系统架构

系统采用 “采集-转发-显示” 的三层架构:

  1. 感知层:BLE 传感器(LYWSD03MMC)。
  2. 传输/网关层 (MAX32655)
    • 运行 BLE 协议栈,作为 Client 角色。
    • 实现自动扫描、过滤特定 MAC 地址、建立连接。
    • 利用定时器(Timer)周期性发起 GATT Read 请求。
    • 定义自定义串口通信协议,将解析后的数据封包发送。
  3. 应用/展示层 (M1w dock suit)
    • 监听 UART 端口,基于帧头/帧尾和校验和进行解包。
    • 利用 GUI 库绘制仪表盘、电池电量图标和实时数值。

2. 通信协议设计 (UART)

为了保证数据传输的可靠性,设计了一套定长二进制通信协议(10 Bytes):

  • 帧头 (2 Bytes): 0xAA 0x55 (用于数据同步)
  • 数据载荷 (5 Bytes):
    • 温度 (2 Bytes, int16, 单位 0.01°C)
    • 湿度 (1 Byte, uint8, 单位 %)
    • 电压 (2 Bytes, uint16, 单位 mV)
  • 校验和 (1 Byte): Checksum (数据载荷的累加和)
  • 帧尾 (2 Bytes): 0x0D 0x0A 

3. 软件流程图

Snipaste_2026-01-11_00-43-33.png


Snipaste_2026-01-11_00-44-18.png

三、 实现过程

1. 开发环境安装

CodeFusion Studio 提供一键式的开发环境安装,非常方便。


2. MAX32655 固件开发

基于 BLE_otac 示例工程进行深度改造。


A. 内存池优化
为了适应大量 BLE 事件和长数据包的处理,首先调整了 WSF 缓冲池配置。如果不增加 buffer 大小,在连接过程中容易出现内存分配失败导致的 HardFault。

/* 增加 buffer 大小以适应复杂 BLE 操作 */
static wsfBufPoolDesc_t mainPoolDesc[] = { { 16, 16 }, { 32, 16 }, { 192, 64 }, { 256, 32 } };


B. 扫描与自动连接逻辑
修改 datcScanReport 回调函数。原程序仅打印扫描结果,修改后增加了 MAC 地址比对逻辑。一旦匹配到目标 MAC (B1:17:8D:38:C1:A4),立即停止扫描并设置 doConnect = TRUE 标志,在扫描停止回调中发起连接。


static void datcScanReport(dmEvt_t *pMsg)
{
    /* 打印扫描到的所有设备 MAC,用于调试 (调试完成后可注释掉) */
    /* 注意:如果不打印,你不知道设备是否真的被扫描到了 */
     APP_TRACE_INFO6("Scan: %02x:%02x:%02x:%02x:%02x:%02x",
                     pMsg->scanReport.addr[5], pMsg->scanReport.addr[4],
                     pMsg->scanReport.addr[3], pMsg->scanReport.addr[2],
                     pMsg->scanReport.addr[1], pMsg->scanReport.addr[0]);
    /* 如果已经在连接或没在扫描,忽略 */
    if (!datcCb.scanning || !datcCb.autoConnect) {
        return;
    }


    /* === 修改: 检查 MAC 地址是否匹配 === */
    // BdaCmp 返回 TRUE 表示地址相同
    if (BdaCmp(pMsg->scanReport.addr, targetAddr)) {
       
        APP_TRACE_INFO0(">>> Target discovered! connecting... <<<");


        /* 停止扫描 */
        datcCb.autoConnect = FALSE;
        AppScanStop();

        /* 保存连接信息 */
        datcConnInfo.addrType = DmHostAddrType(pMsg->scanReport.addrType);
        memcpy(datcConnInfo.addr, pMsg->scanReport.addr, sizeof(bdAddr_t));
        datcConnInfo.dbHdl = APP_DB_HDL_NONE; // 不使用绑定数据库,直接连
        datcConnInfo.doConnect = TRUE;
    }
}


C. 定时读取机制
本项目采用主动 Read 模式。

  1. 新增事件 ID:定义 READ_TIMER_EVT (0x98)
  2. 服务发现完成:在 datcDiscCback  APP_DISC_CMPL 状态下,启动 1秒 的定时器。
  3. 消息处理循环:在 datcProcMsg 中增加对 READ_TIMER_EVT 的处理,调用 AttcReadReq 读取句柄 0x0036(传感器数据特征值)。
  4. 循环读取:收到 ATTC_READ_RSP 后,解析数据并再次启动 2秒 定时器,实现周期性轮询。


static void datcDiscCback(dmConnId_t connId, uint8_t status)
{
    switch (status) {
    case APP_DISC_INIT:
        AppDiscSetHdlList(connId, datcCb.hdlListLen, datcCb.hdlList[connId - 1]);
        break;


    case APP_DISC_READ_DATABASE_HASH:
        AppDiscReadDatabaseHash(connId);
        break;


    case APP_DISC_SEC_REQUIRED:
        AppMasterSecurityReq(connId);
        break;


    case APP_DISC_START:
        datcCb.discState[connId - 1] = DATC_DISC_GATT_SVC;
        AppDbNvmStoreCacheByHash(AppDbGetHdl(connId));
        GattDiscover(connId, pDatcGattHdlList[connId - 1]);
        break;


    case APP_DISC_FAILED:
        // 即使失败也继续,只要连接不断开
    case APP_DISC_CMPL:
        datcCb.discState[connId - 1]++;


        if (datcCb.discState[connId - 1] == DATC_DISC_GAP_SVC) {
            GapDiscover(connId, pDatcGapHdlList[connId - 1]);
        }
        else {
            /* === 发现结束 === */
            AppDiscComplete(connId, APP_DISC_CMPL);
            datcDiscGapCmpl(connId);
            AppDbNvmStoreHdlList(AppDbGetHdl(connId));
           
            // 简单的配置,其实可以跳过
            AppDiscConfigure(connId, APP_DISC_CFG_START, DATC_DISC_CFG_LIST_LEN,
                             (attcDiscCfg_t *)datcDiscCfgList, DATC_DISC_HDL_LIST_LEN,
                             datcCb.hdlList[connId - 1]);
           
            APP_TRACE_INFO0(">>> Service found, reading data <<<");


            /* === 启动定时器,1秒后开始读取 === */
            datcCb.readTimer.handlerId = datcCb.handlerId;
            datcCb.readTimer.msg.event = READ_TIMER_EVT;
            datcCb.readTimer.msg.param = connId; // 把 connId 传参
            WsfTimerStartMs(&datcCb.readTimer, 1000);
        }
        break;


    case APP_DISC_CFG_START:
    case APP_DISC_CFG_CONN_START:
        datcCb.discState[connId - 1] = DATC_DISC_SVC_MAX;
        AppDiscConfigure(connId, APP_DISC_CFG_START, DATC_DISC_CFG_LIST_LEN,
                         (attcDiscCfg_t *)datcDiscCfgList, DATC_DISC_HDL_LIST_LEN,
                         datcCb.hdlList[connId - 1]);
        break;


    case APP_DISC_CFG_CMPL:
        AppDiscComplete(connId, status);
        break;


    default:
        break;
    }
}


D. UART 数据转发
利用 MAX32655FTHR 的 P2.6/P2.7 引脚配置 LPUART。编写了 SendBinaryPacket 函数,负责将解析出的 tempRawhumidity  voltRaw 按照设计的协议进行打包、计算 Checksum 并发送。


3. m1w dock suit 开发 (MicroPython)

A. 串口数据解析
使用 MicroPython 的 machine.UART 读取数据流。为了防止串口粘包或错位,编写了 parse_uart_buffer 函数,采用滑动窗口算法查找帧头 0xAA 55,并验证校验和。

def parse_uart_buffer(buf):
"""
在缓冲区中寻找有效帧
帧格式 (10 Bytes): AA 55 TL TH H VL VH CS 0D 0A
"""
total_len = len(buf)
if total_len < 10:
return False

# 遍历缓冲区寻找帧头 0xAA 0x55
# 我们只处理最新的完整帧,忽略旧数据
# 从后往前找,或者找到最后一个完整的帧

found = False
idx = 0

# 简单的滑动窗口查找
while idx <= total_len - 10:
# 1. 检查帧头
if buf[idx] == 0xAA and buf[idx+1] == 0x55:
# 2. 检查帧尾
if buf[idx+8] == 0x0D and buf[idx+9] == 0x0A:
# 3. 校验和计算
# 数据段: buf[idx+2] 到 buf[idx+6]
payload = buf[idx+2 : idx+7]
recv_sum = buf[idx+7]

calc_sum = sum(payload) & 0xFF # 累加并取低8

if calc_sum == recv_sum:
# === 校验通过,提取数据 ===

# 温度 (int16 小端序)
raw_temp = payload[0] | (payload[1] << 8)
# 处理负数 (16位补码)
if raw_temp > 32767:
raw_temp -= 65536
sensor.temp = raw_temp / 100.0

# 湿度 (uint8)
sensor.hum = payload[2]

# 电压 (uint16 小端序)
raw_volt = payload[3] | (payload[4] << 8)
sensor.volt = raw_volt / 1000.0

sensor.last_update = time.ticks_ms()
sensor.connected = True
found = True

# 跳过这个包,继续看有没有更新的
idx += 10
continue

idx += 1

return found

B. 图形化界面 (GUI)
利用 K210 的 image  lcd 库绘制界面:

  • 状态监测:通过 time.ticks_diff 监测数据更新时间,若超过 3秒 未收到数据,界面显示 "Waiting..." 提示,增强用户体验。
  • 可视化设计:使用不同颜色区分温度(红)、湿度(蓝)和电压(绿)。
  • 动态电量条:根据电压值(2.0V - 3.0V)动态绘制矩形填充,直观展示传感器剩余电量。


四、实现效果

LCD 屏幕正确显示温湿度和电压数据。

image.png

image.png




附件下载
max32655fthr.py
m1w侧
BLE_otac.zip
MAX32655FTHR侧
团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号