【RT-Thread】AT组件
==================================================================================
1.初始化步骤
ESP8266
\ | /
- RT - Thread Operating System
/ | \ 4.0.3
\ | /
- RT - Thread Operating System
/ | \ 4.0.3 build Jun 15 2021
2006 - 2020 Copyright by rt-thread team
lwIP-2.1.2 initialized!
[I/sal.skt] Socket Abstraction Layer initialize success.
[I/at.clnt] AT client(V1.3.1) on device uart3 initialize success.
[D/at.dev.esp] network interface device(esp0) set up status
[D/at.dev.esp] esp0 device initialize start.
msh >[I/at.dev.esp] esp0 device wifi is disconnect.
[D/at.dev.esp] AT version:1.2.0.0(Jul 1 2016 20:04:45)
[D/at.dev.esp] SDK version:1.5.4.1(39cb9a32)
[D/at.dev.esp] Ai-Thinker Technology Co. Ltd.
[D/at.dev.esp] Dec 2 2016 14:21:16
[I/at.dev.esp] esp0 device wifi is connected.
[I/at.dev.esp] esp0 device network initialize successfully.
[E/at.clnt] execute command (AT+CIPDNS_CUR?) failed!
[W/at.dev.esp] please check and update esp0 device firmware to support the "AT+CIPDNS_CUR?" cmd.
[E/at.clnt] execute command (AT+CIPDNS_CUR?) failed!
[W/at.dev.esp] please check and update esp0 device firmware to support the "AT+CIPDNS_CUR?" cmd.
- 1.代码没有上传,一直上传失败,还是切换到使用gitee。
切换到gitee了,网速快点。 和SVN上传代码一样,不允许有空的文件夹,否则上传失败。
- 2.分析下这个AT组件的流程
要配合at_device 软件包的使用,选择at_client
- 3.怎么调用的urc里的函数
static const struct at_urc urc_table[] =
{
{"busy p", "\r\n", urc_busy_p_func},
{"busy s", "\r\n", urc_busy_s_func},
{"WIFI CONNECTED", "\r\n", urc_func},
{"WIFI DISCONNECT", "\r\n", urc_func},
};
当AT Server 遇到上面这些定义的情况,将会调用我们定义的urc_table中的执行函数(见函数client_parser代码)。
//rt-thread/components/net/at/src/at_client.c
static void client_parser(at_client_t client)
- 4.接收时 用到的正则表达式
at_response_t resp = RT_NULL;
const char *resp_expr = "%*[^\"]\"%[^\"]\"";//正则表达式,可以查询sscanf
if (at_resp_parse_line_args_by_kw(resp, "STAMAC", resp_expr, mac) <= 0)
{
LOG_E("%s device parse \"AT+CIFSR\" cmd error.", device->name);
goto __exit;
}
if (at_resp_parse_line_args_by_kw(resp, "ip", resp_expr, ip) <= 0 ||
at_resp_parse_line_args_by_kw(resp, "gateway", resp_expr, gateway) <= 0 ||
at_resp_parse_line_args_by_kw(resp, "netmask", resp_expr, netmask) <= 0)
{
LOG_E("%s device prase \"AT+CIPSTA?\" cmd error.", device->name);
goto __exit;
}
2. 初始化成功
msh >ifconfig
network interface device: esp0 (Default)
MTU: 1500
MAC: fc f5 c4 83 04 30
FLAGS: UP LINK_UP INTERNET_UP DHCP_DISABLE
ip address: 192.168.2.110
gw address: 192.168.2.1
net mask : 255.255.255.0
dns server #0: 0.0.0.0
dns server #1: 0.0.0.0
msh >
msh >
msh >ping www.baidu.com
32 bytes from 36.152.44.95 icmp_seq=0 time=12 ms
32 bytes from 36.152.44.96 icmp_seq=1 time=12 ms
32 bytes from 36.152.44.95 icmp_seq=2 time=10 ms
32 bytes from 36.152.44.95 icmp_seq=3 time=11 ms
从上面ifconfig与ping命令的执行结果看,ESP8266已经成功连接上WiFi,并能访问外部网络(比如www.baidu.com),说明我们的移植暂未出现明显问题,可以在此基础上使用BSD Socket API 编写网络应用程序了。
3.AT CLI 命令行交互模式
AT Client CLI 功能可以转发本地 shell 输入的数据到设备连接的 AT Server 串口设备上,并在本地 shell 上实时显示 AT Client 串口接收到的数据。在本地 shell 中执行 at client 命令进入 AT Client CLI 模式即可进行数据的收发。
msh >at client
======== Welcome to using RT-Thread AT command client cli ========
Cli will forward your command to server port(uart3). Press 'ESC' to exit.
AT
OK
AT+CIFSR
+CIFSR:STAIP,"192.168.2.110"
+CIFSR:STAMAC,"fc:f5:c4:83:04:30"
OK
AT+GMR
AT version:1.2.0.0(Jul 1 2016 20:04:45)
SDK version:1.5.4.1(39cb9a32)
Ai-Thinker Technology Co. Ltd.
Dec 2 2016 14:21:16
OK
- 怎么实现 AT cli client的?
rt-thread/components/net/at/src/at_cli.c
串口收发透传
MSH_CMD_EXPORT(at, RT-Thread AT component cli: at <server|client [dev_name]>);
/*1.从控制台读取字符*/
while (ESC_KEY != (ch = console_getchar()))
{
/*按下ENTER发送AT*/
else if (ch == '\r' || ch == '\n')
{
/* execute a AT request */
if (cur_line_len)
{
rt_kprintf("\n");
at_obj_exec_cmd(client, RT_NULL, "%.*s", cur_line_len, cur_line);
}
cur_line_len = 0;
}
}
/*2.从串口读取字符 使用是ringbuffer*/
at_client = rt_thread_create("at_cli", at_client_entry, RT_NULL, 512, 8, 8);
static void at_client_entry(void *param)
{
char ch;
while(1)
{
ch = client_getchar();
rt_kprintf("%c", ch);/*读取出来 再回显到控制台*/
}
}
4.建立tcp_client通讯
问题:
电脑(192.168.2.101)能ping通wifi板子(192.168.2.110),但是wifi板子却ping不通电脑;但是板子却又能ping通外网 百度?
答:电脑的防火墙要全部关闭
4.1 分析ping命令
//components/net/netdev/src//netdev.c
#ifdef NETDEV_USING_PING
int netdev_cmd_ping(char* target_name, rt_uint32_t times, rt_size_t size)
{
#define NETDEV_PING_DATA_SIZE 32
/** ping receive timeout - in milliseconds */
#define NETDEV_PING_RECV_TIMEO (2 * RT_TICK_PER_SECOND)
/** ping delay - in milliseconds */
#define NETDEV_PING_DELAY (1 * RT_TICK_PER_SECOND)
/* check netdev ping options */
#define NETDEV_PING_IS_COMMONICABLE(netdev) \
((netdev) && (netdev)->ops && (netdev)->ops->ping && \
netdev_is_up(netdev) && netdev_is_link_up(netdev)) \
struct netdev *netdev = RT_NULL;
struct netdev_ping_resp ping_resp;
int index, ret = 0;
if (size == 0)
{
size = NETDEV_PING_DATA_SIZE;
}
if (NETDEV_PING_IS_COMMONICABLE(netdev_default))
{
/* using default network interface device for ping */
netdev = netdev_default;
}
else
{
/* using first internet up status network interface device */
netdev = netdev_get_first_by_flags(NETDEV_FLAG_LINK_UP);
if (netdev == RT_NULL)
{
rt_kprintf("ping: not found available network interface device.\n");
return -RT_ERROR;
}
else if (netdev->ops == RT_NULL || netdev->ops->ping == RT_NULL)
{
rt_kprintf("ping: network interface device(%s) not support ping feature.\n", netdev->name);
return -RT_ERROR;
}
else if (!netdev_is_up(netdev) || !netdev_is_link_up(netdev))
{
rt_kprintf("ping: network interface device(%s) status error.\n", netdev->name);
return -RT_ERROR;
}
}
for (index = 0; index < times; index++)
{
int delay_tick = 0;
rt_tick_t start_tick = 0;
rt_memset(&ping_resp, 0x00, sizeof(struct netdev_ping_resp));
start_tick = rt_tick_get();
ret = netdev->ops->ping(netdev, (const char *)target_name, size, NETDEV_PING_RECV_TIMEO, &ping_resp);
if (ret == -RT_ETIMEOUT)
{
rt_kprintf("ping: from %s icmp_seq=%d timeout\n",
(ip_addr_isany(&(ping_resp.ip_addr))) ? target_name : inet_ntoa(ping_resp.ip_addr), index);
}
else if (ret == -RT_ERROR)
{
rt_kprintf("ping: unknown %s %s\n",
(ip_addr_isany(&(ping_resp.ip_addr))) ? "host" : "address",
(ip_addr_isany(&(ping_resp.ip_addr))) ? target_name : inet_ntoa(ping_resp.ip_addr));
}
else
{
if (ping_resp.ttl == 0)
{
rt_kprintf("%d bytes from %s icmp_seq=%d time=%d ms\n",
ping_resp.data_len, inet_ntoa(ping_resp.ip_addr), index, ping_resp.ticks);
}
else
{
rt_kprintf("%d bytes from %s icmp_seq=%d ttl=%d time=%d ms\n",
ping_resp.data_len, inet_ntoa(ping_resp.ip_addr), index, ping_resp.ttl, ping_resp.ticks);
}
}
/* if the response time is more than NETDEV_PING_DELAY, no need to delay */
delay_tick = ((rt_tick_get() - start_tick) > NETDEV_PING_DELAY) || (index == times) ? 0 : NETDEV_PING_DELAY;
rt_thread_delay(delay_tick);
}
return RT_EOK;
}
int netdev_ping(int argc, char **argv)
{
if (argc == 1)
{
rt_kprintf("Please input: ping <host address>\n");
}
else
{
netdev_cmd_ping(argv[1], 4, 0);
}
return 0;
}
FINSH_FUNCTION_EXPORT_ALIAS(netdev_ping, __cmd_ping, ping network host);
static int esp8266_init(struct at_device *device)
在esp8266初始化时已经绑定好了。
components/net/netdev/src//netdev.c
netdev->ops->ping(netdev, (const char *)target_name, size, NETDEV_PING_RECV_TIMEO, &ping_resp);
调用 esp8266_netdev_ping
packages/at-device-latest/class/esp8266/at_device_esp8266.c
static int esp8266_netdev_ping(struct netdev *netdev, const char *host,
size_t data_len, uint32_t timeout, struct netdev_ping_resp *ping_resp)
给esp8266单独发ping AT 命令式时的接收返回:
AT+PING=“192.168.2.101”
+5
发送AT指令 :
at_obj_exec_cmd(device->client, resp, “AT+PING=”%s"", host)
解析返回值时根据关键字“+”
at_resp_parse_line_args_by_kw(resp, “+”, “+%d”, &req_time)
//packages/at-device-latest/class/esp8266/at_device_esp8266.c
static int esp8266_netdev_ping(struct netdev *netdev, const char *host,
size_t data_len, uint32_t timeout, struct netdev_ping_resp *ping_resp)
{
#define ESP8266_PING_IP_SIZE 16
rt_err_t result = RT_EOK;
at_response_t resp = RT_NULL;
struct at_device *device = RT_NULL;
char ip_addr[ESP8266_PING_IP_SIZE] = {0};
int req_time;
RT_ASSERT(netdev);
RT_ASSERT(host);
RT_ASSERT(ping_resp);
device = at_device_get_by_name(AT_DEVICE_NAMETYPE_NETDEV, netdev->name);
if (device == RT_NULL)
{
LOG_E("get device(%s) failed.", netdev->name);
return -RT_ERROR;
}
resp = at_create_resp(64, 0, timeout);
if (resp == RT_NULL)
{
LOG_E("no memory for resp create.");
return -RT_ENOMEM;
}
/* send domain commond "AT+CIPDOMAIN=<domain name>" and wait response */
if (at_obj_exec_cmd(device->client, resp, "AT+CIPDOMAIN=\"%s\"", host) < 0)
{
result = -RT_ERROR;
goto __exit;
}
/* parse the third line of response data, get the IP address */
if (at_resp_parse_line_args_by_kw(resp, "+CIPDOMAIN:", "+CIPDOMAIN:%s", ip_addr) < 0)
{
result = -RT_ERROR;
goto __exit;
}
/* send ping commond "AT+PING=<IP>" and wait response */
if (at_obj_exec_cmd(device->client, resp, "AT+PING=\"%s\"", host) < 0)
{
result = -RT_ERROR;
goto __exit;
}
if (at_resp_parse_line_args_by_kw(resp, "+", "+%d", &req_time) < 0)
{
result = -RT_ERROR;
goto __exit;
}
if (req_time)
{
inet_aton(ip_addr, &(ping_resp->ip_addr));
ping_resp->data_len = data_len;
ping_resp->ttl = 0;
ping_resp->ticks = req_time;
}
__exit:
if (resp)
{
at_delete_resp(resp);
}
return result;
}
4.2 分析 socket 函数
/*创建一个socket,类型是SOCK_STREAM,TCP类型 */
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if(sock == -1)
{
LOG_E("Socket error\n");
rt_free(buffer);
return;
}
/* 初始化服务端地址 */
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr = *((struct in_addr *)host->h_addr);
rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
----int at_socket(int domain, int type, int protocol)
/* set AT socket receive data callback function */
sock->ops->at_set_event_cb(AT_SOCKET_EVT_RECV, at_recv_notice_cb); //线程接收串口数据时调用
sock->ops->at_set_event_cb(AT_SOCKET_EVT_CLOSED, at_closed_notice_cb);
4.3 分析 connect函数
/* 绑定socket到服务端地址 */
ret = connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));
if(ret == -1)
{
LOG_E("Connect fail\n");
closesocket(sock);
rt_free(buffer);
return;
}
LOG_I("Successfully connected to (%s , %d)\n",
inet_ntoa(server_addr.sin_addr), ntohs(server_addr.sin_port));
connect(sock, (struct sockaddr )&server_addr, sizeof(struct sockaddr));
—esp8266_socket_connect()
4.4 分析recv 函数
bytes_recv = recv(sock,buffer,BUFSZ-1,0);
#define recv(s, mem, len, flags) sal_recvfrom(s, mem, len, flags, NULL, NULL)
sal_recvfrom
---pf->skt_ops->recvfrom((int) sock->user_data, mem, len, flags, from, fromlen);
---at_recvfrom(int socket, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen)
---recv_len = at_recvpkt_get(&(sock->recvpkt_list), (char *) mem, len);
在创建at_client时注册了接收解析函数static void client_parser(at_client_t client),这个里面会有urc->func(client, client->recv_line_buf, client->recv_line_len);
我们在创建at_socket时 创建了 urc_table,其中一项是{"+IPD", ":", urc_recv_func},
而+IPD 就是串口接收的wifi数据。接收数据用
at_evt_cb_set[AT_SOCKET_EVT_RECV](socket, AT_SOCKET_EVT_RECV, recv_buf, bfsz);
放入到接收链表中。at_recvpkt_put(&(sock->recvpkt_list), buff, bfsz);
这样就形成了 client_parser 线程放数据到链表中 at_recvpkt_put,而recvfrom 从链表中拿取数据at_recvpkt_get
4.5 分析 send 函数
static char Sensor_Data[] ="The current temperature is: %3d.%d C, humidity is: %3d.%d %.";
if(rt_strncmp(buffer, "get", 3) == 0)
{
rt_sprintf(buffer, Sensor_Data, 11, 22,33, 44);
/* 发送数据到sock连接 */
send(sock, buffer, (size_t)rt_strlen(buffer), 0);
LOG_D("%s\n", buffer);
}