CH582 Mesh采用self_provisioner_vendor和adv_vendor例程进行多节点通信的相关问题

实验所用文件:

CH582官方例程Mesh中的self_provisioner_vendor和adv_vendor。

配网者节点:self_provisioner_vendor

普通入网节点:adv_vendor

代码修改调整部分:配网者节点:

1、App_Init函数中注释掉测试任务,利用按键回调函数进行测试(按下按键进行数据发送,从而开始节点间数据传输)

 void App_Init(void)
 {
     // 注册事件处理函数到TMOS任务系统
     App_TaskID = TMOS_ProcessEventRegister(App_ProcessEvent);
 
     // 初始化自定义厂商模型客户端
     vendor_model_cli_init(vnd_models);
     // 同步BLE Mesh功能和参数
     blemesh_on_sync();
 
     // 初始化按键硬件
     HAL_KeyInit();
     // 配置按键回调函数
     HalKeyConfig(keyPress);
 
     // 添加一个测试任务,定时向第一个配网设备发送透传数据,定时时长为4800ms
 //    tmos_start_task(App_TaskID, APP_NODE_TEST_EVT, 4800);
 }

对keyPress函数进行修改,通过按键控制数据发送,从而开启节点间数据传输

 void keyPress(uint8_t keys)
 {
     APP_DBG("%d", keys); // 打印按键的值,用于调试。
 
     switch(keys) // 根据按键的值执行不同的操作。
     {
         default: // 默认情况下(未指定特殊键的处理逻辑时),执行以下代码:
         {
             if(3) // 如果按键为3,即评估版的S4-KEY
             {
                 // 向所有入网节点广播数据,从而开始节点间数据传输
                 uint8_t status;
                 uint8_t data[4] = {0, 1, 2, 3}; // 准备要发送的测试数据
                 status = vendor_model_cli_send(0xFFFF, data, sizeof(data));
                 if(status) // 如果发送失败,打印错误状态
                     APP_DBG("trans failed %d", status);
             }
 
 
             if(1) // 如果条件为true(这里只是示例,实际情况中会替换为具体的条件判断。)
             {
                 // 删除节点操作
                 if(app_nodes[1].node_addr) // 如果第二个节点的地址存在。
                 {
                     uint8_t status; // 定义删除状态变量。
                     APP_DBG("node1_addr %x", app_nodes[1].node_addr); // 打印节点地址,用于调试。
                     if(0) // 如果条件为false(这里只是示例,实际情况中会替换为具体的条件判断。)
                     {
                         // 通过蓝牙Mesh协议栈提供的命令来删除节点
                         status = bt_mesh_cfg_node_reset(self_prov_net_idx, app_nodes[1].node_addr);
                         if(status) // 如果删除失败
                         {
                             APP_DBG("reset failed %d", status); // 打印删除失败的状态码。
                         }
                         else
                         {
                             reset_node_addr = app_nodes[1].node_addr; // 保存被删除节点的地址。
                         }
                     }
                     if(1) // 如果条件为true(这里只是示例,实际情况中会替换为具体的条件判断。)
                     {
                         // 通过应用层自定义协议来删除节点。
                         app_mesh_manage.delete_node.cmd = CMD_DELETE_NODE; // 设置删除命令。
                         // 设置节点地址,低字节和高字节。
                         app_mesh_manage.delete_node.addr[0] = app_nodes[1].node_addr & 0xFF;
                         app_mesh_manage.delete_node.addr[1] = (app_nodes[1].node_addr >> 8) & 0xFF;
                         // 使用厂商自定义模型发送删除节点的命令,并保存返回的状态值。
                         status = vendor_model_cli_send(app_nodes[1].node_addr, app_mesh_manage.data.buf, DELETE_NODE_DATA_LEN);
                         if(status) // 如果删除失败。
                         {
                             APP_DBG("delete failed %d", status); // 打印删除失败的状态码。
                         }
                         else
                         {
                             // 启动一个定时任务,3秒后没有收到应答则认为超时。
                             tmos_start_task(App_TaskID, APP_DELETE_NODE_TIMEOUT_EVT, 4800);
                         }
                     }
                 }
             }
             break; // 结束switch语句
         }
     }
 }

2、修改vendor_model_cli_status_t结构体的内容,增加一项mydata数据,用于记录addr、rssi、ttl等信息

 typedef struct
 {
     struct vendor_model_cli_EventHdr vendor_model_cli_Hdr;
     union vendor_model_cli_Event_t   vendor_model_cli_Event;
     void* mydata;
 } vendor_model_cli_status_t;

利用上述修改后的结构体中增加的mydata变量,在vendor_message_cli_trans函数中修改厂商模型客户端状态参数

 static void vendor_message_cli_trans(struct bt_mesh_model   *model,
                                      struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
 {
     vendor_model_cli_status_t vendor_model_cli_status; // 定义厂商模型客户端状态结构体
     uint8_t                  *pData = buf->data; // 数据指针指向缓存的数据部分
     uint16_t                  len = buf->len; // 数据长度
 
     // 检查收到的TID和地址是否与期望的一致
     if((pData[0] != vendor_model_cli->cli_tid.trans_tid) ||
        (ctx->addr != vendor_model_cli->cli_tid.trans_addr))
     {
         // 更新期望的TID和地址
         vendor_model_cli->cli_tid.trans_tid = pData[0];
         vendor_model_cli->cli_tid.trans_addr = ctx->addr;
         // pData指针指向真正的数据,跳过TID
         pData++;
         len--;
 
         // 设置厂商模型客户端状态参数
         vendor_model_cli_status.vendor_model_cli_Hdr.opcode =
             OP_VENDOR_MESSAGE_TRANSPARENT_MSG;
         vendor_model_cli_status.vendor_model_cli_Hdr.status = 0;
         vendor_model_cli_status.vendor_model_cli_Event.trans.pdata = pData;
         vendor_model_cli_status.vendor_model_cli_Event.trans.len = len;
         vendor_model_cli_status.vendor_model_cli_Event.trans.addr = ctx->addr;
         vendor_model_cli_status.mydata = (void*)ctx; // 新增的mydata变量记录ctx
 
         // 如果设置了处理函数,调用之
         if(vendor_model_cli->handler)
         {
             vendor_model_cli->handler(&vendor_model_cli_status);
         }
     }
 }

修改vendor_model_cli_rsp_handler函数,在接收到消息后,打印上述获得的ctx的具体内容,即addr、rssi、ttl等,然后定义一个数据data广播给0xFFFF,即所有节点,这样在配网器节点接收到数据后就会向所有入网节点广播信息,从而获得配网器节点和各个入网节点间的rssi等信息

 static void vendor_model_cli_rsp_handler(const vendor_model_cli_status_t *val)
 {
     // 如果状态码非0,说明存在错误或者超时未收到应答
     if(val->vendor_model_cli_Hdr.status)
     {
         APP_DBG("Timeout opcode 0x%02x", val->vendor_model_cli_Hdr.opcode); // 打印超时信息及操作码
         return; // 函数返回,不处理后续内容
     }
 
     // 根据收到的操作码执行对应的处理
     if(val->vendor_model_cli_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_MSG)
     {
         // 接收数据
         struct bt_mesh_msg_ctx *ctx = (struct bt_mesh_msg_ctx *)val->mydata;
         if(ctx->addr==0x0001)
             return;
         APP_DBG("MYDATA_CLI---src:0x%04x dst:0x%04x rssi:%d app_idx:0x%x recv_ttl:%d send_ttl:%d", ctx->addr, ctx->recv_dst, ctx->recv_rssi, ctx->app_idx, ctx->recv_ttl, ctx->send_ttl);
         // 发送数据
         uint8_t status;
         uint8_t data[4] = {0, 1, 2, 3}; // 准备要发送的测试数据
         status=vendor_model_cli_send(0xFFFF,data,sizeof(data));
         if(status) // 如果发送失败,打印错误状态
             APP_DBG("trans failed %d", status);
 
         // 将透传数据复制到app_mesh_manage结构中
         tmos_memcpy(&app_mesh_manage, val->vendor_model_cli_Event.trans.pdata, val->vendor_model_cli_Event.trans.len);
 
         // 根据透传数据的首个字节判断处理逻辑
         switch(app_mesh_manage.data.buf[0])
         {
             case CMD_DELETE_NODE_ACK: // 应用层自定义删除命令应答
             {
                 // 如果接收到的数据长度不正确,打印错误信息后返回
                 if(val->vendor_model_cli_Event.trans.len != DELETE_NODE_ACK_DATA_LEN)
                 {
                     APP_DBG("Delete node ack data err!");
                     return;
                 }
 
                 // 定义节点指针,处理删除节点的应答逻辑
                 node_t *node;
                 // 停止删除节点的超时任务
                 tmos_stop_task(App_TaskID, APP_DELETE_NODE_TIMEOUT_EVT);
                 // 按地址删除节点
                 bt_mesh_node_del_by_addr(val->vendor_model_cli_Event.trans.addr);
                 // 获取相应地址的节点
                 node = node_get(val->vendor_model_cli_Event.trans.addr);
                 // 设置节点为初始化状态
                 node->stage.node = NODE_INIT;
                 // 设置节点地址为未分配
                 node->node_addr = BLE_MESH_ADDR_UNASSIGNED;
                 // 设置false,表示节点不固定(未配网状态)
                 APP_DBG("Delete node complete"); // 打印节点删除成功的信息
                 break;
             }
             // 可以添加更多的case处理其他消息类型
         }
     }
     else if(val->vendor_model_cli_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_IND)
     {
 
     }
     else if(val->vendor_model_cli_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_WRT)
     {
         // 如果是write类型的响应,这里没有做特殊处理
     }
     else
     {
         // 未知的操作码类型,打印操作码信息
         APP_DBG("Unknow opcode 0x%02x", val->vendor_model_cli_Hdr.opcode);
     }
 }

普通入网节点:

1、和配网者节点类似,修改vendor_model_srv_status_t结构体的内容,增加一项mydata数据,用于记录addr、rssi、ttl等信息

 typedef struct
 {
     struct vendor_model_srv_EventHdr vendor_model_srv_Hdr;
     union vendor_model_srv_Event_t   vendor_model_srv_Event;
     void* mydata;
 } vendor_model_srv_status_t;

2、利用上述修改后的结构体中增加的mydata变量,在vendor_message_srv_trans函数中修改厂商模型服务端状态参数

 static void vendor_message_srv_trans(struct bt_mesh_model   *model,
                                      struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
 {
     // 定义Vendor Model服务状态结构体
     vendor_model_srv_status_t vendor_model_srv_status;
     uint8_t                  *pData = buf->data;  // 指向接收数据内容的指针
     uint16_t                  len = buf->len;     // 接收数据的长度
 
     // 如果接收到的消息的事务ID或地址与期望的不同,则记录并处理该消息
     if((pData[0] != vendor_model_srv->srv_tid.trans_tid) ||
        (ctx->addr != vendor_model_srv->srv_tid.trans_addr))
     {
         // 更新期望的事务ID和来源地址
         vendor_model_srv->srv_tid.trans_tid = pData[0];
         vendor_model_srv->srv_tid.trans_addr = ctx->addr;
         // 跳过事务ID
         pData++;
         len--;
         // 设置状态结构体的操作码和状态
         vendor_model_srv_status.vendor_model_srv_Hdr.opcode =
             OP_VENDOR_MESSAGE_TRANSPARENT_MSG;
         vendor_model_srv_status.vendor_model_srv_Hdr.status = 0;
         // 设置状态结构体的事件参数,包括数据内容、长度和来源地址
         vendor_model_srv_status.vendor_model_srv_Event.trans.pdata = pData;
         vendor_model_srv_status.vendor_model_srv_Event.trans.len = len;
         vendor_model_srv_status.vendor_model_srv_Event.trans.addr = ctx->addr;
         vendor_model_srv_status.mydata=ctx; // 新增的mydata变量记录ctx
         // 如果存在处理函数,调用该函数
         if(vendor_model_srv->handler)
         {
             vendor_model_srv->handler(&vendor_model_srv_status);
         }
     }
 }

3、修改vendor_model_srv_rsp_handler函数,在接收到消息后,打印上述获得的ctx的具体内容,即addr、rssi、ttl等,然后定义一个数据data广播给0xFFFF,即所有节点,这样在入网节点接收到数据后就会向所有节点(包括配网器节点和其他入网节点)广播信息,从而获得当前入网节点和其他节点间的rssi等信息

 static void vendor_model_srv_rsp_handler(const vendor_model_srv_status_t *val)
 {
     // 如果收到的消息表明有错误或状态不正确
     if(val->vendor_model_srv_Hdr.status)
     {
         // 如果是因为应答回复超时未收到
         APP_DBG("Timeout opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
         return;
     }
     // 如果操作码表示是透传消息
     if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_MSG)
     {
         // 接收
         struct bt_mesh_msg_ctx *ctx = (struct bt_mesh_msg_ctx *)val->mydata;
         if(ctx->addr==my_addr)
             return;
         APP_DBG("MYDATA_SRV---src:0x%04x dst:0x%04x rssi:%d app_idx:0x%x recv_ttl:%d send_ttl:%d", ctx->addr, ctx->recv_dst, ctx->recv_rssi, ctx->app_idx, ctx->recv_ttl, ctx->send_ttl);
 
         // 发送
         uint8_t status;
         uint8_t data[4] = {0, 1, 2, 3}; // 准备要发送的测试数据
         status=vendor_model_srv_send(0xFFFF,data,sizeof(data));
         if(status) // 如果发送失败,打印错误状态
             APP_DBG("trans failed %d", status);
 
         // 将收到的数据复制到app_mesh_manage中
         tmos_memcpy(&app_mesh_manage, val->vendor_model_srv_Event.trans.pdata, val->vendor_model_srv_Event.trans.len);
         // 根据消息内容的第一个字节判断消息类型
         switch(app_mesh_manage.data.buf[0])
         {
             // 如果是删除节点的命令
             case CMD_DELETE_NODE:
             {
                 // 检查接收到的数据长度是否合理
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_DATA_LEN)
                 {
                     APP_DBG("Delete node data err!");
                     return;
                 }
                 uint8_t status;
                 // 收到删除命令,打印信息并发送ack回信,然后启动延时以删除本节点
                 APP_DBG("receive delete cmd, send ack and start delete node delay");
                 app_mesh_manage.delete_node_ack.cmd = CMD_DELETE_NODE_ACK;
                 status = vendor_model_srv_send(val->vendor_model_srv_Event.trans.addr,
                                                 app_mesh_manage.data.buf, DELETE_NODE_ACK_DATA_LEN);
                 // 如果发送ack失败,打印错误信息
                 if(status)
                 {
                     APP_DBG("send ack failed %d", status);
                 }
                 // 向所有节点发送命令,告知它们删除存储的相关信息
                 APP_DBG("send to all node to let them delete stored info ");
                 app_mesh_manage.delete_node_info.cmd = CMD_DELETE_NODE_INFO;
                 status = vendor_model_srv_send(BLE_MESH_ADDR_ALL_NODES,
                                                 app_mesh_manage.data.buf, DELETE_NODE_INFO_DATA_LEN);
                 // 如果发送失败,则打印错误信息
                 if(status)
                 {
                     APP_DBG("send ack failed %d", status);
                 }
                 // 启动一个任务,延时删除本节点
                 tmos_start_task(App_TaskID, APP_DELETE_LOCAL_NODE_EVT, APP_DELETE_LOCAL_NODE_DELAY);
                 break;
             }
             // 如果是有节点被删除的命令,需要删除存储的该节点信息
             case CMD_DELETE_NODE_INFO:
             {
                 // 检查接收到的数据长度是否合理
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_INFO_DATA_LEN)
                 {
                     APP_DBG("Delete node info data err!");
                     return;
                 }
                 // 记录要删除信息的地址,并启动一个任务,延时执行删除操作
                 delete_node_info_address = val->vendor_model_srv_Event.trans.addr;
                 tmos_start_task(App_TaskID, APP_DELETE_NODE_INFO_EVT, APP_DELETE_NODE_INFO_DELAY);
                 break;
             }
             // 其他情况,不是已知的命令类型
             default:
                 APP_DBG("Unknow opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
                 break;
         }
     }
     // 如果操作码表示是写入消息
     else if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_WRT)
     {
 
         // 收到write数据,打印长度和来源地址
         APP_DBG("len %d, from 0x%04x", val->vendor_model_srv_Event.write.len, val->vendor_model_srv_Event.write.addr);
 
         // 新增:循环打印完整的数据内容
         for(int i = 0; i < val->vendor_model_srv_Event.write.len; i++) {
             APP_DBG("data[%d] = 0x%02x", i, val->vendor_model_srv_Event.write.pdata[i]);
         }
 
     }
     // 如果操作码表示是确认消息
     else if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_IND)
     {
         // 发送的indicate已收到应答
     }
     // 如果操作码未知
     else
     {
         // 打印未知的操作码信息
         APP_DBG("Unknow opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
     }
 }


实验流程及现象:

①烧录程序到配网器节点和多个入网节点(实验采用4个普通节点)中,并将所有节点连接串口,在串口调试助手中可以看到入网节点依次分配地址为2、3、4、5。

image-20240327164520717.png

image-20240327164622909.png

。。。

image-20240327164747904.png

②评估版按下按键S4(对应keyPress为3),实现配网者节点向0xFFFF地址的所有入网节点广播消息,从而开启各节点信息传输功能。此时可以在配网者节点对应的串口调试助手中看到来自各个普通节点的信息以及彼此之间的RSSI值。同时,在各个普通节点对应的串口调试助手中也可以看到和其他节点间的数据传输过程以及彼此之间的RSSI值。

配网器的串口调试助手:

image-20240327165403690.png

入网节点2的串口调试助手:

image-20240327165615974.png

。。。

入网节点5的串口调试助手:

image-20240327165916320.png



疑问:

问题①:存在一个问题,当按下按键开始节点间通信时,刚开始的一段时间内可以看到所有节点彼此之间都可以通信,但是一段时间后,入网节点2会删除自身节点,其他节点间正常通信,请问该问题是什么原因导致的,怎么解决?现象如下所示:

image-20240327170406838.png

image-20240327193247443.png

问题②:目前是通过串口调试助手观察信息接收情况和RSSI值,且针对配网者和所有入网节点,每一个节点的串口调试助手中都能看到当前节点和其余节点间通信的RSSI值,我需要将所有节点的串口调试助手中的数据传输给电脑,在电脑中进行后续处理,已知的方法是通过串口调试助手的日志打印成txt文本,然后在电脑中就可以读取。但是如果不用串口调试助手,如何把串口中的这些打印数据传给电脑,可以利用wifi模块么?如果可以,怎么实现?


您好,问题①中,测试收发的数据包的内容是怎样的。看串口打印,是收到了删除节点指令后删除节点本身的,可能是数据包内容恰好是删除指令。正常运行不会自动触发删除节点命令。

image.png

问题②中,是希望无线传输日志到电脑上吗?

用wifi是可以的,不过电脑端的上位机需要自行编写,PCB板子上需要增加wifi模块的成本。如果不用wifi,可以利用2.4G RF射频信号来实现。mesh节点是支持2.4G RF射频+mesh组网在同一套代码里运行的,需要上传给电脑的信息可以通过2.4G射频信号发出去;在电脑端插接一个接收头,2.4G收到的信号再通过USB模拟串口来输出给上位机软件。

如果有线模式也能接受,是不希望使用USB转串口模块,MCU上的USB接口是支持模拟COM口,直接给串口助手上位机发包的。


感谢您的回复,相关问题已解决。现在我想在现有实现节点间通信的基础上考虑低功耗休眠问题,请问是要用到adv_vendor_friend朋友节点例程和adv_vendor_power例程么,因为我现在实现的节点间相互通信是一直在广播,节点接收到其他节点发送的数据后再发送,我想设置成特定时间间隔广播一段时间,然后进入休眠模式,从而降低功耗,请问应该怎么实现?


您好,adv_vendor_friend和adv_vendor_low_power代码配合使用,是走的mesh的朋友节点-低功耗节点方案,做双向通信时可以这样用。建议在长供电节点比较多的场景下使用,比如说智能楼宇内添置低功耗的温湿度传感器。低功耗节点例程,在配好网后会按搜索朋友节点的间隔唤醒;建立朋友关系后,MCU会按向朋友节点拉取消息的间隔唤醒,其他时间内没有TMOS事件即可自动休眠。

我想设置成特定时间间隔广播一段时间,然后进入休眠模式,从而降低功耗”这个需求,本身可以通过adv_vendor开HAL_SLEEP去实现,不过由于休眠期间收不到包,收包没有保障,所以只建议在只往外发包,单向发包的场景下使用;另外该场景下需要每隔24h打开至少持续10s的接收扫描,用于同步网络。


基础实验:

本节实验内容采用自配网例程adv_vendor_self_provision进行实验。

使用官方提供的例程,利用Mesh组网实现通信(3个节点,从节点1发送一个自定义数据给节点2,节点2收到数据后获取节点1和节点2的RSSI值,并结合节点1和节点2的地址,打包成一个数据包data,并将该数据包发送给节点3,节点3收到数据data后将该数据解析并打印,然后再次打包该数据data传输给节点2,节点2收到来自节点3发送的数据data后同样进行解析打印和再次打包,然后发送给节点1,最终在节点1处接收到该数据包,解析并打印(在节点1的串口调试助手中看到数据包的具体内容)。

实验芯片连接如下所示,其中节点1连接一个功耗分析仪,用于观察电流:

1.png

实验所用文件:

CH582官方例程Mesh中的自配网例程adv_vendor_self_provision。

代码修改部分:代码通用修改:

1)节点自配网时设置自身地址self_prov_addr。

 // 下面这些常量定义了自配网所需的一些索引和地址信息
 const uint16_t self_prov_net_idx = 0x0000;      // 自配网所用的net key索引
 const uint16_t self_prov_app_idx = 0x0001;      // 自配网所用的app key索引
 const uint32_t self_prov_iv_index = 0x00000000; // 自配网的IV索引
 const uint16_t self_prov_addr = 0x0001;         // 自配网的自身主元素地址-----不同节点要定义不同的节点地址,比如:节点3---0x0003
 const uint8_t  self_prov_flags = 0x00;          // 自配网期间的Key更新标志,0表示不在更新状态
 const uint16_t vendor_sub_addr = 0xC001;        // 自定义模型的订阅组地址

2)节点入网时添加一段打印函数,用于帮助查看节点入网情况。

 /*********************************************************************
  * @fn      node_cfg_process
  *
  * @brief   寻找一个空闲节点,并且执行配置流程
  *
  * @param   node        - 空的节点指针
  * @param   net_idx     - 网络键索引
  * @param   addr        - 设备在网络中的地址
  * @param   num_elem    - 元素的数量
  *
  * @return  返回节点类型指针,如果失败则返回NULL
  */
 static node_t *node_cfg_process(node_t *node, uint16_t net_idx, uint16_t addr, uint8_t num_elem)
 {
     // 此处假定 app_nodes 指向了一个足够大的节点数组
     node = app_nodes;
 
     // 初始化节点配置
     node->net_idx = net_idx;    // 设置网络键索引
     node->node_addr = addr;     // 设置网络地址
     node->elem_count = num_elem; // 设置元素数量
 
     // 以下内容为增加的打印函数
     APP_DBG("节点已添加到NVS");
     APP_DBG("节点序号%:d",net_idx);
     APP_DBG("节点地址:%d",addr);
     APP_DBG("节点元素数量:%d",num_elem);
 
     return node;                // 返回节点指针
 }


首端节点(节点1)代码修改:

1)节点需要发送一个自定义数据包,用于开启数据发送功能,这里选择在APP_Init函数中加一个测试时间来实现,代码如下:

 /*********************************************************************
  * @fn      App_Init
  *
  * @brief   实现应用层初始化
  *
  * @return  无返回值
  */
 void App_Init()
 {
     // 注册处理事件的任务ID
     App_TaskID = TMOS_ProcessEventRegister(App_ProcessEvent);
 
     // 初始化自定义模型服务器
     vendor_model_srv_init(vnd_models);
     // 同步BLE Mesh状态并启用功能
     blemesh_on_sync();
     // 初始化按键处理器
 //    HAL_KeyInit();
     // 配置按键回调
 //    HalKeyConfig(keyPress);
 //     添加一个测试任务,定时向第一个配网设备发送透传数据,定时时长为4800ms
     tmos_start_task(App_TaskID, APP_NODE_TEST_EVT, 4800);
 }
 
 /*********************************************************************
  * @fn      App_ProcessEvent
  *
  * @brief   处理由TMOS来的应用层事件
  *
  * @param   task_id  - TMOS分配的任务ID
  * @param   events   - 要处理的事件,这是一个位图,可能包含一个以上的事件
  *
  * @return  返回未处理的事件
  */
 static uint16_t App_ProcessEvent(uint8_t task_id, uint16_t events)
 {
     // 处理节点配置相关的任务事件
     if(events & APP_NODE_EVT)
     {
         // 配置本地网络信息
         cfg_local_net_info();
         // 将事件标记为已处理
         return (events ^ APP_NODE_EVT);
     }
 
     // 处理测试任务事件
     if(events & APP_NODE_TEST_EVT)
     {
         uint8_t status;
         uint8_t data[4] = {0, 1, 2, 3}; // 准备要发送的测试数据
         status = vendor_model_srv_send(vendor_sub_addr, data, sizeof(data));
         if(status) // 如果发送失败,打印错误状态
             APP_DBG("trans failed %d", status);
 
         tmos_start_task(App_TaskID, APP_NODE_TEST_EVT, 3000);
 
         return (events ^ APP_NODE_TEST_EVT);
     }
 
     // 如果设定删除节点的超时事件已到,则处理超时逻辑
     if(events & APP_DELETE_NODE_TIMEOUT_EVT)
     {
         // 通过应用层自定义协议未能删除节点的超时处理,可以在此添加更多的处理逻辑
         APP_DBG("Delete node failed");
         // 将事件标记为已处理
         return (events ^ APP_DELETE_NODE_TIMEOUT_EVT);
     }
 
     // 如果收到删除本地节点的命令则执行删除流程
     if(events & APP_DELETE_LOCAL_NODE_EVT)
     {
         // 收到删除命令,开始删除本地网络信息
         APP_DBG("Delete local node");
         // 重置本地的网络状态
         bt_mesh_reset();
         // 将事件标记为已处理
         return (events ^ APP_DELETE_LOCAL_NODE_EVT);
     }
 
     // 如果设定了删除存储的节点信息事件,则执行删除流程
     if(events & APP_DELETE_NODE_INFO_EVT)
     {
         // 删除已存储的被删除节点的信息
         bt_mesh_delete_node_info(delete_node_info_address, app_comp.elem_count);
         APP_DBG("Delete stored node info complete");
         // 将事件标记为已处理
         return (events ^ APP_DELETE_NODE_INFO_EVT);
     }
 
     // 丢弃未能识别的事件
     return 0;
 }

2)节点1作为首端节点,除了通开启发送数据功能,还要设置接收数据时的相应处理:当接收到来自节点2的信息pdata时,解析数据包pdata,并打印数据包内容。

 /*********************************************************************
  * @fn      vendor_model_srv_rsp_handler
  *
  * @brief   处理自定义模型服务的回调事件
  *
  * @param   val     - 回调参数,包含消息类型、数据内容、长度、来源地址
  *
  * @return  无返回值
  */
 static void vendor_model_srv_rsp_handler(const vendor_model_srv_status_t *val)
 {
     // 检查命令执行状态,如果超时未收到应答则打印提示信息
     if(val->vendor_model_srv_Hdr.status)
     {
         APP_DBG("Timeout opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
         return;
     }
     // 根据操作码执行相应的操作
     if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_MSG)
     {
         // 接收
         struct bt_mesh_msg_ctx *ctx = (struct bt_mesh_msg_ctx *)val->mydata;
 
         if(ctx->addr==0x0002)
         {
             // 确保接收到的数据长度至少为5个字节
             if(val->vendor_model_srv_Event.trans.len >= 5) {
                 // 解析数据
                 uint16_t src_addr = val->vendor_model_srv_Event.trans.pdata[0] + (val->vendor_model_srv_Event.trans.pdata[1] << 8);
                 uint16_t my_addr = val->vendor_model_srv_Event.trans.pdata[2] + (val->vendor_model_srv_Event.trans.pdata[3] << 8);
                 int8_t rssi = val->vendor_model_srv_Event.trans.pdata[4];
 
                 // 打印解析后的数据
                 APP_DBG("src: 0x%04x, my_addr: 0x%04x, rssi: %d", src_addr, my_addr, rssi);
             } else {
                 APP_DBG("Received data is too short.");
             }
         }
 
         // 根据收到的消息内容进行处理
         tmos_memcpy(&app_mesh_manage, val->vendor_model_srv_Event.trans.pdata, val->vendor_model_srv_Event.trans.len);
         // 判断消息类型并执行相应的逻辑
         switch(app_mesh_manage.data.buf[0])
         {
             // 处理节点删除命令
             case CMD_DELETE_NODE:
             {
                 // 校验数据长度
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_DATA_LEN)
                 {
                     APP_DBG("Delete node data err!");
                     return;
                 }
                 uint8_t status;
                 APP_DBG("receive delete cmd, send ack and start delete node delay");
                 // 设置回复命令并发送应答
                 app_mesh_manage.delete_node_ack.cmd = CMD_DELETE_NODE_ACK;
                 status = vendor_model_srv_send(val->vendor_model_srv_Event.trans.addr,
                                                app_mesh_manage.data.buf, DELETE_NODE_ACK_DATA_LEN);
                 if(status)
                 {
                     APP_DBG("send ack failed %d", status);
                 }
                 // 发送全网命令以删除存储信息,并设置删除延迟
                 APP_DBG("send to all nodes to let them delete stored info ");
                 app_mesh_manage.delete_node_info.cmd = CMD_DELETE_NODE_INFO;
                 status = vendor_model_srv_send(BLE_MESH_ADDR_ALL_NODES,
                                                app_mesh_manage.data.buf, DELETE_NODE_INFO_DATA_LEN);
                 if(status)
                 {
                     APP_DBG("send failed %d", status);
                 }
                 tmos_start_task(App_TaskID, APP_DELETE_LOCAL_NODE_EVT, APP_DELETE_LOCAL_NODE_DELAY);
                 break;
             }
 
             // 处理节点删除的应答命令
             case CMD_DELETE_NODE_ACK:
             {
                 // 检查数据长度
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_ACK_DATA_LEN)
                 {
                     APP_DBG("Delete node ack data err!");
                     return;
                 }
                 // 删除完毕,取消删除超时定时器
                 tmos_stop_task(App_TaskID, APP_DELETE_NODE_TIMEOUT_EVT);
                 APP_DBG("Delete node complete");
                 break;
             }
 
             // 处理命令以删除存储的节点信息
             case CMD_DELETE_NODE_INFO:
             {
                 // 检查数据长度
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_INFO_DATA_LEN)
                 {
                     APP_DBG("Delete node info data err!");
                     return;
                 }
                 // 记录删除的节点地址并设置删除信息事件
                 delete_node_info_address = val->vendor_model_srv_Event.trans.addr;
                 tmos_start_task(App_TaskID, APP_DELETE_NODE_INFO_EVT, APP_DELETE_NODE_INFO_DELAY);
                 break;
             }
         }
     }
     else if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_WRT)
     {
         // 收到write请求时的相关处理,暂时未定义
     }
     else if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_IND)
     {
         // 当indicate消息得到确认时的相关处理,暂时未定义
     }
     else
     {
         // 打印未知操作码的信息
         APP_DBG("Unknown opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
     }
 }

中间节点(节点2)代码修改:

 /*********************************************************************
  * @fn      vendor_model_srv_rsp_handler
  *
  * @brief   处理自定义模型服务的回调事件
  *
  * @param   val     - 回调参数,包含消息类型、数据内容、长度、来源地址
  *
  * @return  无返回值
  */
 static void vendor_model_srv_rsp_handler(const vendor_model_srv_status_t *val)
 {
     // 检查命令执行状态,如果超时未收到应答则打印提示信息
     if(val->vendor_model_srv_Hdr.status)
     {
         APP_DBG("Timeout opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
         return;
     }
     // 根据操作码执行相应的操作
     if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_MSG)
     {
         // 接收
         struct bt_mesh_msg_ctx *ctx = (struct bt_mesh_msg_ctx *)val->mydata;
         // 当接收到来自0x0001节点的信息后,会向0x0003节点发送信息
         if(ctx->addr==0x0001)
         {
             // 发送
             uint8_t status;
             // 准备要发送的数据
             uint8_t sendData[5]; // 2 bytes for ctx->addr, 2 bytes for my_addr, and 1 byte for ctx->recv_rssi
             // 打包数据
             // 注意: 这里假设系统为小端字节序,如果是大端字节序需要调整
             memcpy(sendData, &ctx->addr, 2); // 复制ctx->addr
             memcpy(sendData + 2, &self_prov_addr, 2); // 复制my_addr
             // 由于rssi是int8_t,直接将其值赋给数组的相应位置
             sendData[4] = ctx->recv_rssi; // 复制ctx->recv_rssi
             status=vendor_model_srv_send(0x0003,sendData,sizeof(sendData));
             if(status) // 如果发送失败,打印错误状态
                 APP_DBG("trans failed %d", status);
         }
 
         // 当接收到来自0x0003节点的信息pdata后,会向0x0001节点发送该pdata信息
         if(ctx->addr==0x0003)
         {
             // 确保接收到的数据长度至少为5个字节
             if(val->vendor_model_srv_Event.trans.len >= 5) {
                 // 解析数据
                 uint16_t src_addr = val->vendor_model_srv_Event.trans.pdata[0] + (val->vendor_model_srv_Event.trans.pdata[1] << 8);
                 uint16_t my_addr = val->vendor_model_srv_Event.trans.pdata[2] + (val->vendor_model_srv_Event.trans.pdata[3] << 8);
                 int8_t rssi = val->vendor_model_srv_Event.trans.pdata[4];
 
                 // 打印解析后的数据
                 APP_DBG("src: 0x%04x, my_addr: 0x%04x, rssi: %d", src_addr, my_addr, rssi);
 
                 // 将该解析数据pdata发送给0x0001
                 uint8_t status;
                 // 准备要发送的数据
                 uint8_t sendData[5]; // 2 bytes for src_addr, 2 bytes for my_addr, and 1 byte for rssi
                 // 打包数据
                 // 注意: 这里假设系统为小端字节序,如果是大端字节序需要调整
                 memcpy(sendData, &src_addr, 2); // 复制ctx->addr
                 memcpy(sendData + 2, &my_addr, 2); // 复制my_addr
                 // 由于rssi是int8_t,直接将其值赋给数组的相应位置
                 sendData[4] = rssi; // 复制ctx->recv_rssi
                 status=vendor_model_srv_send(0x0001,sendData,sizeof(sendData));
                 if(status) // 如果发送失败,打印错误状态
                     APP_DBG("trans failed %d", status);
 
             } else {
                 APP_DBG("Received data is too short.");
             }
         }
         // 根据收到的消息内容进行处理
         tmos_memcpy(&app_mesh_manage, val->vendor_model_srv_Event.trans.pdata, val->vendor_model_srv_Event.trans.len);
         // 判断消息类型并执行相应的逻辑
         switch(app_mesh_manage.data.buf[0])
         {
             // 处理节点删除命令
             case CMD_DELETE_NODE:
             {
                 // 校验数据长度
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_DATA_LEN)
                 {
                     APP_DBG("Delete node data err!");
                     return;
                 }
                 uint8_t status;
                 APP_DBG("receive delete cmd, send ack and start delete node delay");
                 // 设置回复命令并发送应答
                 app_mesh_manage.delete_node_ack.cmd = CMD_DELETE_NODE_ACK;
                 status = vendor_model_srv_send(val->vendor_model_srv_Event.trans.addr,
                                                app_mesh_manage.data.buf, DELETE_NODE_ACK_DATA_LEN);
                 if(status)
                 {
                     APP_DBG("send ack failed %d", status);
                 }
                 // 发送全网命令以删除存储信息,并设置删除延迟
                 APP_DBG("send to all nodes to let them delete stored info ");
                 app_mesh_manage.delete_node_info.cmd = CMD_DELETE_NODE_INFO;
                 status = vendor_model_srv_send(BLE_MESH_ADDR_ALL_NODES,
                                                app_mesh_manage.data.buf, DELETE_NODE_INFO_DATA_LEN);
                 if(status)
                 {
                     APP_DBG("send failed %d", status);
                 }
                 tmos_start_task(App_TaskID, APP_DELETE_LOCAL_NODE_EVT, APP_DELETE_LOCAL_NODE_DELAY);
                 break;
             }
 
             // 处理节点删除的应答命令
             case CMD_DELETE_NODE_ACK:
             {
                 // 检查数据长度
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_ACK_DATA_LEN)
                 {
                     APP_DBG("Delete node ack data err!");
                     return;
                 }
                 // 删除完毕,取消删除超时定时器
                 tmos_stop_task(App_TaskID, APP_DELETE_NODE_TIMEOUT_EVT);
                 APP_DBG("Delete node complete");
                 break;
             }
 
             // 处理命令以删除存储的节点信息
             case CMD_DELETE_NODE_INFO:
             {
                 // 检查数据长度
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_INFO_DATA_LEN)
                 {
                     APP_DBG("Delete node info data err!");
                     return;
                 }
                 // 记录删除的节点地址并设置删除信息事件
                 delete_node_info_address = val->vendor_model_srv_Event.trans.addr;
                 tmos_start_task(App_TaskID, APP_DELETE_NODE_INFO_EVT, APP_DELETE_NODE_INFO_DELAY);
                 break;
             }
         }
     }
     else if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_WRT)
     {
         // 收到write请求时的相关处理,暂时未定义
     }
     else if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_IND)
     {
         // 当indicate消息得到确认时的相关处理,暂时未定义
     }
     else
     {
         // 打印未知操作码的信息
         APP_DBG("Unknown opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
     }
 }

尾端节点(节点3)代码修改:

 /*********************************************************************
  * @fn      vendor_model_srv_rsp_handler
  *
  * @brief   处理自定义模型服务的回调事件
  *
  * @param   val     - 回调参数,包含消息类型、数据内容、长度、来源地址
  *
  * @return  无返回值
  */
 static void vendor_model_srv_rsp_handler(const vendor_model_srv_status_t *val)
 {
     // 检查命令执行状态,如果超时未收到应答则打印提示信息
     if(val->vendor_model_srv_Hdr.status)
     {
         APP_DBG("Timeout opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
         return;
     }
     // 根据操作码执行相应的操作
     if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_MSG)
     {
         // 接收
         struct bt_mesh_msg_ctx *ctx = (struct bt_mesh_msg_ctx *)val->mydata;
 
         // 当接收到来自0x0002节点的信息后,会向0x0002节点发送信息
         if(ctx->addr==0x0002)
         {
             // 确保接收到的数据长度至少为5个字节
             if(val->vendor_model_srv_Event.trans.len >= 5) {
                 // 解析数据
                 uint16_t src_addr = val->vendor_model_srv_Event.trans.pdata[0] + (val->vendor_model_srv_Event.trans.pdata[1] << 8);
                 uint16_t my_addr = val->vendor_model_srv_Event.trans.pdata[2] + (val->vendor_model_srv_Event.trans.pdata[3] << 8);
                 int8_t rssi = val->vendor_model_srv_Event.trans.pdata[4];
 
                 // 打印解析后的数据
                 APP_DBG("src: 0x%04x, my_addr: 0x%04x, rssi: %d", src_addr, my_addr, rssi);
 
                 // 将该解析数据pdata发送给0x0002
                 uint8_t status;
                 // 准备要发送的数据
                 uint8_t sendData[5]; // 2 bytes for src_addr, 2 bytes for my_addr, and 1 byte for rssi
                 // 打包数据
                 // 注意: 这里假设系统为小端字节序,如果是大端字节序需要调整
                 memcpy(sendData, &src_addr, 2); // 复制ctx->addr
                 memcpy(sendData + 2, &my_addr, 2); // 复制my_addr
                 // 由于rssi是int8_t,直接将其值赋给数组的相应位置
                 sendData[4] = rssi; // 复制ctx->recv_rssi
                 status=vendor_model_srv_send(0x0002,sendData,sizeof(sendData));
                 if(status) // 如果发送失败,打印错误状态
                     APP_DBG("trans failed %d", status);
 
             } else {
                 APP_DBG("Received data is too short.");
             }
         }
 
         // 根据收到的消息内容进行处理
         tmos_memcpy(&app_mesh_manage, val->vendor_model_srv_Event.trans.pdata, val->vendor_model_srv_Event.trans.len);
         // 判断消息类型并执行相应的逻辑
         switch(app_mesh_manage.data.buf[0])
         {
             // 处理节点删除命令
             case CMD_DELETE_NODE:
             {
                 // 校验数据长度
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_DATA_LEN)
                 {
                     APP_DBG("Delete node data err!");
                     return;
                 }
                 uint8_t status;
                 APP_DBG("receive delete cmd, send ack and start delete node delay");
                 // 设置回复命令并发送应答
                 app_mesh_manage.delete_node_ack.cmd = CMD_DELETE_NODE_ACK;
                 status = vendor_model_srv_send(val->vendor_model_srv_Event.trans.addr,
                                                app_mesh_manage.data.buf, DELETE_NODE_ACK_DATA_LEN);
                 if(status)
                 {
                     APP_DBG("send ack failed %d", status);
                 }
                 // 发送全网命令以删除存储信息,并设置删除延迟
                 APP_DBG("send to all nodes to let them delete stored info ");
                 app_mesh_manage.delete_node_info.cmd = CMD_DELETE_NODE_INFO;
                 status = vendor_model_srv_send(BLE_MESH_ADDR_ALL_NODES,
                                                app_mesh_manage.data.buf, DELETE_NODE_INFO_DATA_LEN);
                 if(status)
                 {
                     APP_DBG("send failed %d", status);
                 }
                 tmos_start_task(App_TaskID, APP_DELETE_LOCAL_NODE_EVT, APP_DELETE_LOCAL_NODE_DELAY);
                 break;
             }
 
             // 处理节点删除的应答命令
             case CMD_DELETE_NODE_ACK:
             {
                 // 检查数据长度
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_ACK_DATA_LEN)
                 {
                     APP_DBG("Delete node ack data err!");
                     return;
                 }
                 // 删除完毕,取消删除超时定时器
                 tmos_stop_task(App_TaskID, APP_DELETE_NODE_TIMEOUT_EVT);
                 APP_DBG("Delete node complete");
                 break;
             }
 
             // 处理命令以删除存储的节点信息
             case CMD_DELETE_NODE_INFO:
             {
                 // 检查数据长度
                 if(val->vendor_model_srv_Event.trans.len != DELETE_NODE_INFO_DATA_LEN)
                 {
                     APP_DBG("Delete node info data err!");
                     return;
                 }
                 // 记录删除的节点地址并设置删除信息事件
                 delete_node_info_address = val->vendor_model_srv_Event.trans.addr;
                 tmos_start_task(App_TaskID, APP_DELETE_NODE_INFO_EVT, APP_DELETE_NODE_INFO_DELAY);
                 break;
             }
         }
     }
     else if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_WRT)
     {
         // 收到write请求时的相关处理,暂时未定义
     }
     else if(val->vendor_model_srv_Hdr.opcode == OP_VENDOR_MESSAGE_TRANSPARENT_IND)
     {
         // 当indicate消息得到确认时的相关处理,暂时未定义
     }
     else
     {
         // 打印未知操作码的信息
         APP_DBG("Unknown opcode 0x%02x", val->vendor_model_srv_Hdr.opcode);
     }
 }


基础实验的实验现象:

节点1所连功耗分析仪显示如下:

12ms时间显示比例下:

2.png

120ms时间显示比例下:

3.png

节点2串口调试助手:

4.png

节点3串口调试助手:

5.png

问题:

在实现上述业务的基础上,我想降低节点的功耗,考虑的是参考文章在TMOS系统中手动管理休眠 和文章CH582开启睡眠模式下低功耗测试来实现,尝试在节点1的测试事件APP_NODE_TEST_EVT中发送测试数据后添加一段相关代码,具体如下:

     // 处理测试任务事件
     if(events & APP_NODE_TEST_EVT)
     {
         uint8_t status;
         uint8_t data[4] = {0, 1, 2, 3}; // 准备要发送的测试数据
         status = vendor_model_srv_send(vendor_sub_addr, data, sizeof(data));
         if(status) // 如果发送失败,打印错误状态
             APP_DBG("trans failed %d", status);
 
         tmos_start_task(App_TaskID, APP_NODE_TEST_EVT, 3000);
 
         PRINT("sleep mode sleep \n");
 
         DelayMs(5);
 
         RTC_TMRFunCfg(Period_2_S);/* 定时1s的RTC闹钟 */
         PWR_PeriphWakeUpCfg( ENABLE, RB_SLP_RTC_WAKE, Short_Delay );/* 使能RTC中断唤醒源 */
 
         PFIC_EnableIRQ(RTC_IRQn);/* 使能RTC中断 */
         /* 注意当主频为80M时,Sleep睡眠唤醒中断不可调用flash内代码 */
         LowPower_Sleep(RB_PWR_RAM30K | RB_PWR_RAM2K); /* 只保留30+2K SRAM 供电 */
         HSECFG_Current(HSE_RCur_100);                 /* 降为额定电流(低功耗函数中提升了HSE偏置电流) */
         PFIC_DisableIRQ(RTC_IRQn);
 
         DelayMs(5);
         PRINT("wake.. \n");
         DelayMs(20);
 
         // 清除测试任务事件标志
         return (events ^ APP_NODE_TEST_EVT);
     }

RTC中断处理函数修改如下:

 /*******************************************************************************
  * @fn      RTC_IRQHandler
  *
  * @brief   RTC中断处理
  *
  * @param   None.
  *
  * @return  None.
  */
 __INTERRUPT
 __HIGH_CODE
 void RTC_IRQHandler(void)
 {
 //    R8_RTC_FLAG_CTRL = (RB_RTC_TMR_CLR | RB_RTC_TRIG_CLR);
 //    RTCTigFlag = 1;
 
     if (RTC_GetITFlag(RTC_TMR_EVENT))
     {
         RTC_ClearITFlag(RTC_TMR_EVENT);
     }
 }

然后重新烧录程序,观察现象如下:

6.png

串口调试助手中观察到节点1休眠后会复位,代码PRINT("wake.. \n");打印的wake提示也没有,同时在功耗仪上观察电流如下所示:

7.png

请问这是什么原因,如何解决,从而实现我所提业务的低功耗处理?



您好,如果您使用的是今年1月更新的CH583EVT,注意下方代码涉及到休眠:

image.png


对节点1加入休眠处理(GPIO引脚,PB4低电平唤醒):

代码添加如下:

app.c文件末尾处加入GPIOA_IRQHandler中断函数:

 /*********************************************************************
  * @fn      GPIOA_IRQHandler
  *
  * @brief   GPIOA中断函数
  *
  * @return  none
  */
 __INTERRUPT
 __HIGH_CODE
 void GPIOB_IRQHandler(void)
 {
     GPIOB_ClearITFlagBit(GPIO_Pin_4);
 
 }

并对App_ProcessEvent函数修改如下:参考文章在TMOS系统中手动管理休眠加入如下代码

     // 处理测试任务事件
     if(events & APP_NODE_TEST_EVT)
     {
         uint8_t status;
         uint8_t data[4] = {0, 1, 2, 3}; // 准备要发送的测试数据
         status = vendor_model_srv_send(vendor_sub_addr, data, sizeof(data));
         if(status) // 如果发送失败,打印错误状态
             APP_DBG("trans failed %d", status);
 
         // 下列代码为参考文章添加的代码
         PRINT("sleep mode sleep \n");
         DelayMs(5);
         tmos_stop_task(halTaskID, HAL_REG_INIT_EVENT);  //默认2分钟一次的校准先暂停
         sys_safe_access_enable();
         R8_CK32K_CONFIG &= ~(RB_CLK_INT32K_PON | RB_CLK_XT32K_PON); //关闭32K晶振电源,更省电
         sys_safe_access_disable();
         LowPower_Sleep(RB_PWR_RAM2K | RB_PWR_RAM30K | RB_PWR_EXTEND); //保留24+2K的SRAM的供电
         sys_safe_access_enable();
         R8_CK32K_CONFIG |= RB_CLK_INT32K_PON;       //32K电源打开,供TMOS系统使用
         sys_safe_access_disable();
         HSECFG_Current(HSE_RCur_100);                 // 降为额定电流(低功耗函数中提升了HSE偏置电流)
         DelayMs(5);     //这里建议给一定的延时,延时足够到下方的打印能正常就好
         PRINT("wake.. \n");
         tmos_set_event(halTaskID, HAL_REG_INIT_EVENT);      //先校准RF,再开广播
 
         
         // 重新添加测试任务事件,下次执行时间间隔为6400ms
         tmos_start_task(App_TaskID, APP_NODE_TEST_EVT, 3200);
         // 清除测试任务事件标志
         return (events ^ APP_NODE_TEST_EVT);
     }

app_main.c文件中代码修改如下:在主函数中加入GPIO唤醒的代码

 int main(void)
 {
 #if(defined(DCDC_ENABLE)) && (DCDC_ENABLE == TRUE)
     PWR_DCDCCfg(ENABLE);
 #endif
 
 /********GPIO唤醒的配置,加在主函数初始化中********/
     GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU);
     GPIOB_ITModeCfg(GPIO_Pin_4, GPIO_ITMode_FallEdge); // 下降沿唤醒
     PFIC_EnableIRQ(GPIO_B_IRQn);
     PWR_PeriphWakeUpCfg(ENABLE, RB_SLP_GPIO_WAKE, Long_Delay);
 /******************************************/
 
 
 
     SetSysClock(CLK_SOURCE_PLL_60MHz);
 
 #if CONFIG_BLE_MESH_LOW_POWER   //或者判断HAL_SLEEP是否启用,都行。使能低功耗节点功能时,需要一并启用HAL_SLEEP
     GPIOA_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);
     GPIOB_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);
 #endif
 
 #ifdef DEBUG
     GPIOA_SetBits(bTXD1);
     GPIOA_ModeCfg(bTXD1, GPIO_ModeOut_PP_5mA);
     UART1_DefInit();
 #endif
     PRINT("%s\n", VER_LIB);
     PRINT("%s\n", VER_MESH_LIB);
     CH58X_BLEInit();
     HAL_Init();
     bt_mesh_lib_init();
     App_Init();
     Main_Circulation();
 }

当节点1加入到网络后,进入休眠状态,当PB4引脚接地后,产生一个下降沿,唤醒节点1,节点1收到来自节点2发送的数据包并打印其内容,然后进入休眠状态。当再次将PB4引脚接地后,会重复上述过程,如下图所示。10.png此时可以在功耗分析中观察到电流变化如下图所示:

11.png

现在我想用RTC唤醒,而不是GPIO唤醒,我根据您给出的建议注释了MCU.c文件中的那一行代码,然后在上述app.c代码基础上添加一段RTC唤醒的代码,并注释掉app_main.c中GPIO唤醒的代码:

     // 处理测试任务事件
     if(events & APP_NODE_TEST_EVT)
     {
         uint8_t status;
         uint8_t data[4] = {0, 1, 2, 3}; // 准备要发送的测试数据
         status = vendor_model_srv_send(vendor_sub_addr, data, sizeof(data));
         if(status) // 如果发送失败,打印错误状态
             APP_DBG("trans failed %d", status);
 
         // RTC唤醒添加的代码
         RTC_TMRFunCfg(Period_1_S);   /* 定时1s的RTC闹钟 */
         PFIC_EnableIRQ(RTC_IRQn);   /* 使能RTC中断 */
         PWR_PeriphWakeUpCfg( ENABLE, RB_SLP_RTC_WAKE, Long_Delay );   /* 使能RTC中断唤醒源 */
         
         // 下列代码为参考文章添加的代码
         PRINT("sleep mode sleep \n");
         DelayMs(5);
         tmos_stop_task(halTaskID, HAL_REG_INIT_EVENT);  //默认2分钟一次的校准先暂停
         sys_safe_access_enable();
         R8_CK32K_CONFIG &= ~(RB_CLK_INT32K_PON | RB_CLK_XT32K_PON); //关闭32K晶振电源,更省电
         sys_safe_access_disable();
         LowPower_Sleep(RB_PWR_RAM2K | RB_PWR_RAM30K | RB_PWR_EXTEND); //保留24+2K的SRAM的供电
         sys_safe_access_enable();
         R8_CK32K_CONFIG |= RB_CLK_INT32K_PON;       //32K电源打开,供TMOS系统使用
         sys_safe_access_disable();
         HSECFG_Current(HSE_RCur_100);                 // 降为额定电流(低功耗函数中提升了HSE偏置电流)
         DelayMs(5);     //这里建议给一定的延时,延时足够到下方的打印能正常就好
         PRINT("wake.. \n");
         tmos_set_event(halTaskID, HAL_REG_INIT_EVENT);      //先校准RF,再开广播
 
         
         // 重新添加测试任务事件,下次执行时间间隔为6400ms
         tmos_start_task(App_TaskID, APP_NODE_TEST_EVT, 3200);
         // 清除测试任务事件标志
         return (events ^ APP_NODE_TEST_EVT);
     }

重新跑例程后,发现虽然休眠后不会复位,但是休眠后也不会唤醒,节点1串口显示如下:13.png功耗仪也显示后面一直在休眠,如下所示:

12.png

请问这个问题可能是什么原因导致的,应该如何处理,另外文章在TMOS系统中手动管理休眠只有GPIO唤醒方式,RTC定时唤醒怎么实现,我想参考一下?



您好,博客中的场景是仅用GPIO唤醒的场景,使用RTC唤醒就不要关闭32K的电源了,不关闭一直跑那下面重新打开32K电源的代码块也不用了。

RTC只能由32K时钟提供,唤醒MCU也是需要32K时钟的。

image.png


只有登录才能回复,可以选择微信账号登录