基于TI CC1352R cli_ftd(Thread)例程,实现开机自动调用UDP函数咨询
实现CC1352R cli_ftd例程开机自动启动Thread网络 + 自动执行UDP操作
嘿,我对TI这款CC1352R的cli_ftd Thread例程摸得挺熟的,给你一步步说怎么改,不用CLI就能实现需求:
一、先让Thread网络开机自动启动
原来的cli_ftd是靠CLI命令触发启动的,我们要把启动逻辑移到系统初始化阶段:
- 先找到Thread相关的初始化代码,一般在
app.c或者thread_main.c里,找CLI命令start对应的回调函数(比如cli_command_start),里面肯定有调用OpenThread核心API的逻辑,比如otInstanceInit、otIp6SetEnabled、otThreadSetEnabled这些。 - 把这些启动代码从CLI回调里抽出来,放到系统初始化完成后的钩子函数里——比如
appTaskInit(如果用TI-RTOS的话),或者Board_init之后的代码段里。 - 给你个示例代码参考,记得要确保硬件初始化完成后再调用:
// 初始化OpenThread单实例 otInstance *instance = otInstanceInitSingle(); if (instance != NULL) { // 启用IPv6协议栈 otIp6SetEnabled(instance, true); // 启用Thread协议栈 otThreadSetEnabled(instance, true); // 自动加入已有网络(如果设备之前保存过网络参数) otOperationalDataset dataset; if (otThreadGetOperationalDataset(instance, &dataset) == OT_ERROR_NONE) { // 有保存的数据集,直接启动网络 otThreadSetEnabled(instance, true); } else { // 新设备的话,预设网络参数(比如PAN ID、网络密钥) dataset.mPanId = 0x1234; // 自定义PAN ID // 这里可以补充设置网络密钥、信道等参数 otThreadSetOperationalDataset(instance, &dataset); otThreadSetEnabled(instance, true); } } - 做完这些,原来的CLI启动命令可以保留(不影响自动启动),也可以注释掉避免混淆。
二、开机自动执行UDP的open/bind/connect/send
重点提醒:UDP操作必须等Thread网络完全建立(设备获取到IPv6地址、成功加入网络)后再执行,不然会直接失败。所以我们要通过Thread的状态回调来触发UDP操作:
- 注册Thread状态变化回调
在Thread初始化完成后,添加回调注册代码:
otSetStateChangedCallback(instance, stateChangedCallback, instance);
- 实现状态回调函数,检测网络就绪状态
当设备从“未连接”变成Leader/Router/End Device角色时,说明网络已建立,这时触发UDP操作:
static void stateChangedCallback(otInstance *instance, uint32_t flags, void *context) { if (flags & OT_CHANGED_THREAD_ROLE) { otDeviceRole role = otThreadGetDeviceRole(instance); // 排除未连接的状态 if (role != OT_DEVICE_ROLE_DISABLED && role != OT_DEVICE_ROLE_DETACHED) { // 调用UDP初始化和发送函数 setupAndSendUdp(instance); } } }
- 实现UDP操作的核心函数
setupAndSendUdp
把open、bind、connect、send的逻辑封装在这里:
static void setupAndSendUdp(otInstance *instance) { otUdpSocket socket; otError error; // 1. 打开UDP Socket memset(&socket, 0, sizeof(socket)); error = otUdpOpen(instance, &socket, udpReceiveCallback, instance); if (error != OT_ERROR_NONE) { // 处理错误,比如打印日志(如果有日志系统的话) return; } // 2. 绑定本地端口(可选,如果需要接收对方回复的话) otSockAddr localAddr; memset(&localAddr, 0, sizeof(localAddr)); localAddr.mPort = 1234; // 自定义本地端口 error = otUdpBind(instance, &socket, &localAddr, OT_NETIF_THREAD); if (error != OT_ERROR_NONE) { otUdpClose(instance, &socket); return; } // 3. 设置远程地址(UDP的connect只是预设目标,不是TCP式的连接) otSockAddr remoteAddr; memset(&remoteAddr, 0, sizeof(remoteAddr)); // 替换成你的目标设备IPv6地址 otIp6AddressFromString("fdde:ad00:beef:0:1234:5678:9abc:def0", &remoteAddr.mAddress); remoteAddr.mPort = 5678; // 目标设备的UDP端口 error = otUdpConnect(instance, &socket, &remoteAddr); // 4. 发送UDP数据 uint8_t data[] = "Hello from CC1352R Thread Device!"; otMessage *message = otUdpNewMessage(instance, NULL); if (message != NULL) { otMessageAppend(message, data, sizeof(data)-1); // 去掉字符串末尾的'\0' // 如果connect成功就用otUdpSend,否则用otUdpSendTo指定目标 if (error == OT_ERROR_NONE) { error = otUdpSend(instance, &socket, message); } else { error = otUdpSendTo(instance, &socket, message, &remoteAddr); } if (error != OT_ERROR_NONE) { otMessageFree(instance, message); } } // 如果不需要保持Socket接收回复,可以在这里关闭;否则留着 // otUdpClose(instance, &socket); }
- 可选:实现UDP接收回调(如果需要处理回复)
static void udpReceiveCallback(void *context, otMessage *message, const otMessageInfo *messageInfo) { otInstance *instance = (otInstance *)context; uint8_t buffer[128]; size_t length = otMessageRead(message, otMessageGetOffset(message), buffer, sizeof(buffer)-1); buffer[length] = '\0'; // 这里可以添加处理逻辑,比如打印接收到的数据 // Log_info("Received UDP: %s from %s:%d", buffer, otIp6AddressToString(&messageInfo->mPeerAddr, NULL), messageInfo->mPeerPort); }
三、几个要注意的细节
- 网络参数要正确:如果是新设备,必须预设PAN ID、网络密钥、信道这些参数,不然设备没法加入网络;如果是之前连过的设备,OpenThread会自动保存参数,开机直接恢复。
- 目标IPv6地址要准确:得是Thread网络内的有效地址,可以先通过CLI命令获取目标设备的地址,再硬编码到代码里;如果需要动态发现,可以结合DNS-SD功能实现。
- 错误处理要到位:比如Socket打开失败、发送失败的情况,要做容错处理,避免程序崩溃。
- 任务上下文:如果用TI-RTOS,这些操作要在任务里执行,别在中断回调里做复杂逻辑。
内容的提问来源于stack exchange,提问作者user10989811




