嵌入式上位机开发入门(十):RT-Thread 后台线程代码借鉴

张开发
2026/4/6 14:47:48 15 分钟阅读

分享文章

嵌入式上位机开发入门(十):RT-Thread 后台线程代码借鉴
目录一、前言二、后台线程的职责三、发送 AT 命令的流程四、client_parser 解析函数五、网络数据的接收处理六、总结七、结尾一、前言大家好这里是Hello_Embed。经过上一篇笔记的学习我们了解到监听、接收、发数据、建立连接这些过程的本质是发送 AT 命令完成发送侧发送 data → 阻塞等待返回结果接收侧阻塞 → 处理数据这些阻塞都由后台线程负责唤醒。二、后台线程的职责后台线程主要做以下两件事读串口分辨返回值AT 返回 → 唤醒 AT 命令发送者sender网络数据 → 先保存进 socket 的 buff再唤醒接收者receiver三、发送 AT 命令的流程以连接过程为例#defineconnect(socket,name,namelen)at_connect(socket,name,namelen)在 esp8266 的连接中有如下命令caseAT_SOCKET_UDP:if(at_obj_exec_cmd(device-client,resp,ATCIPSTART%d,\UDP\,\%s\,%d,device_socket,ip,port)0){result-RT_ERROR;}break;at_obj_exec_cmd内部对 AT 命令进行了互斥操作确保同时只能有一个 AT 命令发送rt_mutex_take(client-lock,RT_WAITING_FOREVER);// 加锁client-resp_statusAT_RESP_OK;// ... 写串口 at_vprintfln(client-device, cmd_expr, args);rt_mutex_release(client-lock);// 释放锁发送完成后阻塞等待响应if(resp!RT_NULL){if(rt_sem_take(client-resp_notice,resp-timeout)!RT_EOK){client-resp_statusAT_RESP_TIMEOUT;result-RT_ETIMEOUT;goto__exit;}if(client-resp_status!AT_RESP_OK){result-RT_ERROR;goto__exit;}}发送 AT 命令后通过信号量resp_notice阻塞等待由后台线程解析到响应后唤醒。四、client_parser 解析函数resp_notice信号量由解析函数client_parser释放staticvoidclient_parser(at_client_tclient){conststructat_urc*urc;while(1){if(at_recv_readline(client)0){if((urcget_urc_obj(client))!RT_NULL){/* 接收到特殊前缀网络数据调用对应处理函数 */if(urc-func!RT_NULL){urc-func(client,client-recv_line_buf,client-recv_line_len);}}elseif(client-resp!RT_NULL){at_response_trespclient-resp;charend_chclient-recv_line_buf[client-recv_line_len-1];client-recv_line_buf[client-recv_line_len-1]\0;if(resp-buf_lenclient-recv_line_lenresp-buf_size){/* 拷贝响应数据 */rt_memcpy(resp-bufresp-buf_len,client-recv_line_buf,client-recv_line_len);resp-buf_lenclient-recv_line_len;resp-line_counts;}/* 判断响应结果 */if(rt_memcmp(client-recv_line_buf,AT_RESP_END_OK,rt_strlen(AT_RESP_END_OK))0resp-line_num0){client-resp_statusAT_RESP_OK;}elseif(rt_strstr(client-recv_line_buf,AT_RESP_END_ERROR)){client-resp_statusAT_RESP_ERROR;}else{continue;}client-respRT_NULL;rt_sem_release(client-resp_notice);// 唤醒阻塞的发送者}}}}client_parser的核心逻辑把接收到的数据拷贝到 resp 中比较是否为 OK/ERROR设置对应状态后释放信号量唤醒前面阻塞的 AT 命令发送者。如果接收到的是特殊前缀get_urc_obj匹配则调用对应的 URC 处理函数处理网络数据。五、网络数据的接收处理URC 处理函数urc_recv_func负责解析网络数据并存入 socketstaticvoidurc_recv_func(structat_client*client,constchar*data,rt_size_tsize){intdevice_socket0;rt_size_tbfsz0;char*recv_bufRT_NULL;structat_socket*socketRT_NULL;/* 解析出 socket 编号与数据长度 */sscanf(data,IPD,%d,%d:,device_socket,(int*)bfsz);/* 分配接收 buffer */recv_buf(char*)rt_calloc(1,bfsz);/* 读串口获取网络数据 */if(at_client_obj_recv(client,recv_buf,bfsz,timeout)!bfsz){rt_free(recv_buf);return;}/* 通过硬件 socket 找到对应的软件 socket */socketat_get_base_socket(device_socket);/* 调用回调函数通知 APP */if(at_evt_cb_set[AT_SOCKET_EVT_RECV]){at_evt_cb_set[AT_SOCKET_EVT_RECV](socket,AT_SOCKET_EVT_RECV,recv_buf,bfsz);}}回调函数at_recv_notice_cb完成最后的数据存储与唤醒staticvoidat_recv_notice_cb(structat_socket*sock,at_socket_evt_tevent,constchar*buff,size_tbfsz){/* 将数据存入 socket 的接收链表 */rt_mutex_take(sock-recv_lock,RT_WAITING_FOREVER);at_recvpkt_put((sock-recvpkt_list),buff,bfsz);rt_mutex_release(sock-recv_lock);/* 唤醒等待接收数据的 APP */rt_sem_release(sock-recv_notice);at_do_event_changes(sock,AT_EVENT_RECV,RT_TRUE);}完整流程读串口 → 解析数据包 → 找到对应 socket → 存入链表 → 释放信号量唤醒 APP。六、总结场景后台线程行为收到 AT 响应OK/ERROR设置响应状态释放resp_notice唤醒发送者收到网络数据IPD 前缀解析 socket 和长度读数据存入链表释放recv_notice唤醒接收者核心设计思路互斥锁保证 AT 命令串行发送信号量实现阻塞与唤醒URC 机制区分 AT 响应与网络数据链表缓存网络数据解耦接收与处理七、结尾以后我们会仿照 RT-Thread 的框架在 FreeRTOS 上实现 AT 模块的 Socket 封装最终实现 PC 与单片机开发板的直接通信。下一篇将学习已经封装好的工程学习其中的核心思路。Hello_Embed继续带你从原理到实践掌握嵌入式上位机开发的核心技能敬请关注

更多文章