潘多拉 IOT 开发板学习(RT-Thread)—— 实验17 ESP8266 实验(学习笔记)

本文详细介绍了如何在RT-Thread中使用ESP8266模块进行网络ping操作,涉及netdev_ping(), netdev_cmd_ping(), rt_thread_mdelay()等关键函数,并展示了esp8266_netdev_ping()和esp8266_net_init()的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文代码参考 RT-Thread 官方 BSP

实验功能

例程源码:(main.c)

该实验实现的功能:Ping 一个指定的 IP 地址。

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-08-16     ZYLX         first implementation
 */

#include <rtthread.h>

int main(void)
{
    extern int netdev_ping(int argc, char **argv);

    char *cmd[] = {"esp8266_ping", "www.rt-thread.org"};

    while (1)
    {
        if (netdev_ping(2, cmd) == RT_EOK)
        {
            break;
        }
        else
        {
            rt_thread_mdelay(5000);
        }
    }

    return 0;
}

代码剖析

netdev_ping()

ping 命令,该函数主要进行参数判断,然后执行底层的 ping 命令函数。

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;
}

netdev_cmd_ping()

ping 命令函数,实际调用的是 (netdev)->ops->ping,这里的 netdev 是一个全局变量,本实验对应的网络设备肯定是 esp8266。

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 || NETDEV_PING_IS_COMMONICABLE(netdev) == 0)
        {
            rt_kprintf("ping: network interface device get error.\n");
            return -RT_ERROR;
        }
    }

    for (index = 0; index < times; index++)
    {
        rt_memset(&ping_resp, 0x00, sizeof(struct netdev_ping_resp));
        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);
            }
        }
        
        rt_thread_mdelay(NETDEV_PING_DELAY);
    }

    return RT_EOK;
}

esp8266_netdev_ping()

这便是 esp8266 底层驱动的 ping 函数,它会发送 “AT+PING=<IP>” 指令进行 ping 网测试。

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

    at_response_t resp = RT_NULL;
    rt_err_t result = RT_EOK;
    int req_time;
    char ip_addr[ESP8266_PING_IP_SIZE] = {0};

    RT_ASSERT(netdev);
    RT_ASSERT(host);
    RT_ASSERT(ping_resp);

    resp = at_create_resp(64, 0, timeout);
    if (!resp)
    {
        LOG_E("No memory for response structure!");
        return -RT_ENOMEM;
    }

    rt_mutex_take(at_event_lock, RT_WAITING_FOREVER);

    /* send domain commond "AT+CIPDOMAIN=<domain name>" and wait response */
    if (at_exec_cmd(resp, "AT+CIPDOMAIN=\"%s\"", host) < 0)
    {
        LOG_D("ping: send commond AT+CIPDOMAIN=<domain name> failed");
        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)
    {
        LOG_E("ping: get the IP address failed");
        result = -RT_ERROR;
        goto __exit;
    }

    /* send ping commond "AT+PING=<IP>" and wait response */
    if (at_exec_cmd(resp, "AT+PING=\"%s\"", host) < 0)
    {
        LOG_D("ping: unknown remote server host");
        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);
    }

    rt_mutex_release(at_event_lock);

    return result;
}

rt_thread_mdelay()

这是 RT-Thread 的毫秒级延时函数,定义如下:

rt_err_t rt_thread_mdelay(rt_int32_t ms)
{
    rt_tick_t tick;

	// 获取需要的时钟节拍
    tick = rt_tick_from_millisecond(ms);
	
	// 阻塞相应的节拍时间
    return rt_thread_sleep(tick);
}

rt_tick_from_millisecond()


/**
 * 算出 ms 对应的时钟节拍数
 * 
 *
 * @param ms the specified millisecond
 *           - Negative Number wait forever
 *           - Zero not wait
 *           - Max 0x7fffffff
 *
 * @return the calculated tick
 */
rt_tick_t rt_tick_from_millisecond(rt_int32_t ms)
{
    rt_tick_t tick;

    if (ms < 0)
    {
        tick = (rt_tick_t)RT_WAITING_FOREVER;  // -1 
    }
    else
    {
    	// 将“每秒节拍数” / 1000 * ms,算出对应的秒节拍数
        tick = RT_TICK_PER_SECOND * (ms / 1000);
		
		// 加上小于 1000ms 部分的节拍数
        tick += (RT_TICK_PER_SECOND * (ms % 1000) + 999) / 1000;
    }
    
    /* return the calculated tick */
    return tick;
}

rt_thread_sleep()

线程睡眠(挂起)函数,参数是系统节拍数:

/**
 * 该函数能让当前线程挂起一段时间(由 tick 决定)
 *
 * @param tick the sleep ticks
 *
 * @return RT_EOK
 */
rt_err_t rt_thread_sleep(rt_tick_t tick)
{
    register rt_base_t temp;
    struct rt_thread *thread;

    /* set to current thread */
    thread = rt_thread_self();
    RT_ASSERT(thread != RT_NULL);
    RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();

    /* suspend thread */
    rt_thread_suspend(thread);

    /* reset the timeout of thread timer and start it */
    rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &tick);
    rt_timer_start(&(thread->thread_timer));

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

    rt_schedule();

    /* clear error number of this thread to RT_EOK */
    if (thread->error == -RT_ETIMEOUT)
        thread->error = RT_EOK;

    return RT_EOK;
}

LOG_D()

本实验中,我们可以将 LOG_D() 视为 rt_kprintf()

#define dbg_log_line(lvl, color_n, fmt, ...)                \
    do                                                      \
    {                                                       \
        _DBG_LOG_HDR(lvl, color_n);                         \
        rt_kprintf(fmt, ##__VA_ARGS__);                     \
        _DBG_LOG_X_END;                                     \
    }                                                       \
    while (0)

LOG_D 是 RT-Thread 内核里的一个日志打印函数,详情可见:《RT-Thread 文档中心——ulog 日志》

RT-Thread 的日志 API 包括:

在这里插入图片描述

at_socket_device_init

main.c 中并没有 WiFi 模块初始化代码,这些功能都在驱动底层完成了,这类代码文件全部在 at_socket_esp8266.c 中,下面这个函数是总的初始化函数,主要完成的是 AT socket 客户端的创建和 ESP8266 的初始化。

static int at_socket_device_init(void)
{
    /* create current AT socket event */
    at_socket_event = rt_event_create("at_se", RT_IPC_FLAG_FIFO);
    if (at_socket_event == RT_NULL)
    {
        LOG_E("RT AT client port initialize failed! at_sock_event create failed!");
        return -RT_ENOMEM;
    }

    /* create current AT socket event lock */
    at_event_lock = rt_mutex_create("at_se", RT_IPC_FLAG_FIFO);
    if (at_event_lock == RT_NULL)
    {
        LOG_E("RT AT client port initialize failed! at_sock_lock create failed!");
        rt_event_delete(at_socket_event);
        return -RT_ENOMEM;
    }

    /* initialize AT client */
    at_client_init(AT_DEVICE_NAME, AT_DEVICE_RECV_BUFF_LEN);

    /* register URC data execution function  */
    at_set_urc_table(urc_table, sizeof(urc_table) / sizeof(urc_table[0]));

    /* Add esp8266 to the netdev list */
    esp8266_netdev_add(ESP8266_NETDEV_NAME);

    /* initialize esp8266 net workqueue */
    rt_delayed_work_init(&esp8266_net_work, exp8266_get_netdev_info, (void *)netdev_get_by_name(ESP8266_NETDEV_NAME));

    /* initialize esp8266 network */
    esp8266_net_init();

    /* set esp8266 AT Socket options */
    at_socket_device_register(&esp8266_socket_ops);

    return RT_EOK;
}
INIT_APP_EXPORT(at_socket_device_init);

esp8266_net_init()

esp8266 初始化函数,其实就是调用了一个线程回调函数 esp8266_init_thread_entry()

int esp8266_net_init(void)
{
#ifdef PKG_AT_INIT_BY_THREAD
    rt_thread_t tid;

    tid = rt_thread_create("esp8266_net_init", esp8266_init_thread_entry, RT_NULL, ESP8266_THREAD_STACK_SIZE, ESP8266_THREAD_PRIORITY, 20);
    if (tid)
    {
        rt_thread_startup(tid);
    }
    else
    {
        LOG_E("Create AT initialization thread fail!");
    }
#else
    esp8266_init_thread_entry(RT_NULL);
#endif

    return RT_EOK;
}

esp8266_init_thread_entry()

该函数内部主要执行了一些 AT 指令的发送,比如复位模块(AT+RST),设置 STA 模式(AT+CWMODE=1)。

static void esp8266_init_thread_entry(void *parameter)
{
    at_response_t resp = RT_NULL;
    rt_err_t result = RT_EOK;
    rt_size_t i;

    resp = at_create_resp(128, 0, rt_tick_from_millisecond(5000));
    if (!resp)
    {
        LOG_E("No memory for response structure!");
        result = -RT_ENOMEM;
        goto __exit;
    }

    rt_thread_delay(rt_tick_from_millisecond(5000));
    /* reset module */
    AT_SEND_CMD(resp, "AT+RST");
    /* reset waiting delay */
    rt_thread_delay(rt_tick_from_millisecond(1000));
    /* disable echo */
    AT_SEND_CMD(resp, "ATE0");
    /* set current mode to Wi-Fi station */
    AT_SEND_CMD(resp, "AT+CWMODE=1");
    /* get module version */
    AT_SEND_CMD(resp, "AT+GMR");
    /* show module version */
    for (i = 0; i < resp->line_counts - 1; i++)
    {
        LOG_D("%s", at_resp_get_line(resp, i + 1));
    }
    /* connect to WiFi AP */
    if (at_exec_cmd(at_resp_set_info(resp, 128, 0, 20 * RT_TICK_PER_SECOND), "AT+CWJAP=\"%s\",\"%s\"",
            AT_DEVICE_WIFI_SSID, AT_DEVICE_WIFI_PASSWORD) != RT_EOK)
    {
        LOG_E("AT network initialize failed, check ssid(%s) and password(%s).", AT_DEVICE_WIFI_SSID, AT_DEVICE_WIFI_PASSWORD);
        result = -RT_ERROR;
        goto __exit;
    }

    AT_SEND_CMD(resp, "AT+CIPMUX=1");

__exit:
    if (resp)
    {
        at_delete_resp(resp);
    }

    if (!result)
    {
        netdev_low_level_set_status(netdev_get_by_name(ESP8266_NETDEV_NAME), RT_TRUE);
        LOG_I("AT network initialize success!");
    }
    else
    {
        netdev_low_level_set_status(netdev_get_by_name(ESP8266_NETDEV_NAME), RT_FALSE);
        LOG_E("AT network initialize failed (%d)!", result);
    }
}
### RT-Thread操作系统在ESP8266上的开发与配置教程 #### 一、环境搭建 为了使RT-Thread能够在ESP8266上运行,开发者需先准备相应的工具链和编译环境。这通常涉及到安装特定版本的GCC交叉编译器以及设置好路径变量以便命令行能够识别这些工具[^1]。 对于IDE的选择方面,虽然存在多种可能性,但对于物联网项目来说,一些专门针对嵌入式系统的集成开发环境可能更为合适。例如WyliodrinSTUDIO是一个基于Chrome的应用程序,它支持软件和硬件开发,并且兼容各种微控制器平台包括ESP系列设备;而DevIoT则提供了一种通过Angular框架来编程物理硬件的方法,在某些场景下也能成为一种有趣的选择[^2]。 然而值得注意的是上述提到的产品并非专门为RT-Thread设计,因此如果目标是专注于此实时操作系统的移植工作,则建议关注官方提供的指导文档或是社区内分享的经验贴作为主要参考资料源。 #### 二、移植过程概述 当完成前期准备工作之后就可以着手于实际的操作系统移植了。一般而言这个阶段会涉及以下几个重要环节: 修改启动代码以适应新架构特性; 调整内存管理单元(MMU)配置使之匹配目标板卡资源分配情况; 实现必要的驱动层接口函数从而确保基本外设功能正常运作; 最后还需测试并优化整个系统的性能表现直至达到预期效果为止。 在此过程中可能会遇到不少挑战,比如如何处理不同厂商之间存在的细微差异等问题都需要依靠个人经验和技术积累去克服解决。不过得益于RT-Thread本身所具备的良好结构化特性和较为完善的生态体系支撑,使得这类任务变得相对容易许多。 #### 三、具体实践指南 尽管具体的实施细节取决于多个因素如使用的开发套件型号和个人偏好等,但这里还是可以给出几个通用性的提示帮助大家更好地理解和掌握这一流程: 利用已有的成功案例做参考学习是非常有效的方式之一,可以从GitHub仓库或者其他开源平台上寻找类似的工程实例加以研究分析; 积极参与到相关论坛交流群里与其他爱好者共同探讨难题也是一个不错的选择,往往能从中获得意想不到的帮助和支持; 保持耐心持续跟进最新进展同样至关重要,因为随着技术不断发展进步总会有更简便高效的解决方案被发现出来。 ```bash git clone https://github.com/RT-Thread/rt-thread.git cd rt-thread ./bsp/espressif/esp8266/samples/hello_world/build.sh ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小辉_Super

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值