[作品] 48“万里”树莓派小车——PicoW学习(C语言TCP通信)

lb8820265   2023-11-12 20:08 楼主

    关于PicoW的WiFi功能程序的编写,官方有介绍文件《connecting-to-the-internet-with-pico-w》,帖子最后有下载,在官方例程中也有WiFi TCP通信的相关例程,路径为Pico_W/wifi/tcp_client和tcp_server,WiFi例程默认是不会编译的,需要提前进行配置,这个在PicoW学习(C语言环境搭建、新建工程、在线调试)有介绍。这两个例程需要一起使用,也就是两个PicoW中分别烧录tcp_client和tcp_server,然后连在同一个热点中,会完成一次数据的交互。但是例程需要两个PicoW太麻烦,而且不直观,而且都只有单一的首或者发的功能。通信例程通常是“echo”例程,也就是PicoW将收到的信息发送发送方。

目标

    新建一个PicoW模块基于TCP协议、STA模式作为Server的“echo”例程工程,可以通过电脑创建TCP协议的Client与PicoW进行通信,并通过串口打印出相关信息。

工程创建

    参考前面帖子的工程创建方法,使用“pico project generato”工具,工程名为“Test_IwIP_TCP”,勾选“Console over UART”和“Background IwIP”,该库表示中断模式接收,这里还有一个“Polled IwIP”可选,这个是轮寻方式接收。“PicoW onboard LED”库这个表示这个库仅仅只能控制LED灯,后面两个库是包含了LED灯的控制的。

代码编写

    前面的工作已经将工程框架搭建完成,下面贴出主要的代码。

添加头文件

#include "lwip/tcp.h"

添加定义与全局变量

    主要是WIFI用户名和密码,这里XX需要替换为自己的。然后是TCP相关的结构体。

#define WIFI_SSID "XXXXXXXXXXX"
#define WIFI_PASSWORD "XXXXXXXXX"
#define TCP_PORT 4242
#define DEBUG_printf printf
#define BUF_SIZE 2048

typedef struct TCP_SERVER_T_ {
    struct tcp_pcb *server_pcb;
    struct tcp_pcb *client_pcb;
    bool complete;
    uint8_t buffer_sent[BUF_SIZE];
    uint8_t buffer_recv[BUF_SIZE];
    int sent_len;
    int recv_len;
    int run_count;
} TCP_SERVER_T;

main函数编写

    主要是芯片的初始化,连接WiFi路由器,运行TCP任务,进行LED灯闪烁。这里要注意如果长时间没有连上路由器,会报错。

int main()
{
    static bool LED_State=0;
    stdio_init_all();
    puts("Test IwIP TCP");
    if (cyw43_arch_init()) {
        printf("Wi-Fi init failed");
        return -1;
    }
     cyw43_arch_enable_sta_mode();
    printf("Connecting to Wi-Fi...\n");
    if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
        printf("failed to connect.\n");
        return 1;
    } else {
        printf("Connected.\n");
    }
    run_tcp_server_test();
    while (true) {
         sleep_ms(100);
        if(LED_State){
            LED_State=false;
        }else{
            LED_State=true;
        }
        cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, LED_State);
    }
    return 0;
}

TCP Server任务函数编写

    主要是TCP Server初始化,连接回调函数的编写,在连接回调函数中注册接收、发送和错误等回调函数,并给Client发送连接成功字符。

static err_t tcp_server_accept(void *arg, struct tcp_pcb *client_pcb, err_t err) {
    TCP_SERVER_T *state = (TCP_SERVER_T*)arg;
    if (err != ERR_OK || client_pcb == NULL) {
        DEBUG_printf("Failure in accept\n");
        tcp_server_result(arg, err);
        return ERR_VAL;
    }
    DEBUG_printf("Client connected\n");
    state->client_pcb = client_pcb;
    tcp_arg(client_pcb, state);
    tcp_sent(client_pcb, tcp_server_sent);
    tcp_recv(client_pcb, tcp_server_recv);
    //tcp_poll(client_pcb, tcp_server_poll, POLL_TIME_S * 2);
    tcp_err(client_pcb, tcp_server_err);
    return tcp_server_send_data(arg, state->client_pcb);
}

static bool tcp_server_open(void *arg) {
    TCP_SERVER_T *state = (TCP_SERVER_T*)arg;
    DEBUG_printf("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), TCP_PORT);
    struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
    if (!pcb) {
        DEBUG_printf("failed to create pcb\n");
        return false;
    }
    err_t err = tcp_bind(pcb, NULL, TCP_PORT);
    if (err) {
        DEBUG_printf("failed to bind to port %u\n", TCP_PORT);
        return false;
    }
    state->server_pcb = tcp_listen_with_backlog(pcb, 1);
    if (!state->server_pcb) {
        DEBUG_printf("failed to listen\n");
        if (pcb) {
            tcp_close(pcb);
        }
        return false;
    }
    tcp_arg(state->server_pcb, state);
    tcp_accept(state->server_pcb, tcp_server_accept);
    return true;
}

void run_tcp_server_test(void) {
    TCP_SERVER_T *state = tcp_server_init();
    if (!state) {
        return;
    }
    if (!tcp_server_open(state)) {
        tcp_server_result(state, -1);
        return;
    }
}

接收发送回调函数编写

    发送回调函数,在发送成功后进入,并通过串口发送发送字节数给PC。接收回调函数,在收到数据后,将数据发回给Client,并通过串口发送接收到的数据给PC。

static err_t tcp_server_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) {
    TCP_SERVER_T *state = (TCP_SERVER_T*)arg;
    DEBUG_printf("tcp_server_sent %u\n", len);
    state->sent_len += len;
    if (state->sent_len >= BUF_SIZE) {
        // We should get the data back from the client
        state->recv_len = 0;
        DEBUG_printf("Waiting for buffer from client\n");
    }
    return ERR_OK;
}

err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) {
    TCP_SERVER_T *state = (TCP_SERVER_T*)arg;
    if (!p) {
        return tcp_server_result(arg, -1);
    }
    cyw43_arch_lwip_check();
    if (p->tot_len > 0) {
        DEBUG_printf("tcp_server_recv %s", p->payload,  err);
    err_t err = tcp_write(tpcb,p->payload, p->tot_len, TCP_WRITE_FLAG_COPY);
     if (err != ERR_OK) {
         DEBUG_printf("Failed to write data %d\n", err);
         return tcp_server_result(arg, -1);
     }
    }
    pbuf_free(p);
    return ERR_OK;
}

运行效果

    将PicoW上电,会自动连接上路由器,在路由器上查看PicoW的IP地址,可以看到PicoW的IP地址为:192.168.31.136。

image.png  

    在PC打开网络调试助手,选择TCP Clinet,设置好IP地址,远端主机端口“4242”,点击连接,编辑发送数据,点击发送,就会看到接收到相同的数据。

image.png  

    连接串口调试助手,在串口调试助手上,会有WiFi连接整个过程的信息,例如,接收和发送回调函数打印的信息。

image.png  

《connecting-to-the-internet-with-pico-w》:
connecting-to-the-internet-with-pico-w.pdf (20.45 MB)
(下载次数: 1, 2023-11-12 19:55 上传)
源码:
Test_IwIP_TCP.zip (1.59 MB)
(下载次数: 1, 2023-11-12 19:57 上传)
本帖最后由 lb8820265 于 2023-11-12 19:59 编辑
QQ:252669569

回复评论 (2)

版主大佬,如果把您这一系列学习下来,可以成全栈高手了。
点赞  2023-11-14 15:34

感谢楼主提供的技术分享,先收藏学习再发表个人意见,顶起来

点赞  2023-11-21 14:13
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复