Raspberry Pi Pico W - TCP Server C 예제
Raspberry Pi Pico W에서 TCP Server C 예제를 사용하는 방법을 다루고 있습니다.
2022. 12. 25 최초작성
진행하기 전에 다음 포스트를 먼저 진행해야 합니다.
Windows에 Raspberry Pi Pico C 개발 환경 만들기
https://webnautes.tistory.com/2092
아래 링크에 있는 tcp_server 예제 프로젝트를 구성하는 방법부터 다룹니다. 코드를 수정하여 사용했습니다. 수정한 코드는 포스트에 있습니다.
https://github.com/raspberrypi/pico-examples/tree/master/pico_w/tcp_server
1. 윈도우 키를 누른 후, “ Developer Command”을 입력하여 Developer Command Prompt for VS 2022를 실행합니다.
2. 이전 포스트에서 pico를 위해 사용했던 폴더에 tcp_server 예제 프로젝트를 위한 폴더를 생성합니다.
D:\work\pico>mkdir tcp_server
D:\work\pico>cd tcp_server
3. 편의상 notepad를 사용하여 tcp_server 예제 프로젝트를 위해 필요한 파일들을 생성합니다.
D:\work\pico\tcp_server>notepad CMakeLists.txt
다음 내용을 복사해줍니다. 다음 코드에서 WIFI_SSID, WIFI_PASSWORD, TEST_TCP_SERVER_IP 부분을 수정해야 합니다..
cmake_minimum_required(VERSION 3.12) include(pico_sdk_import.cmake) set(projname "tcp_server") set(PICO_BOARD pico_w) set(WIFI_SSID "SK_WiFiA5B1") set(WIFI_PASSWORD "1909013071") set(TEST_TCP_SERVER_IP "192.168.25.45") project(${projname} C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) pico_sdk_init() add_executable(picow_tcpip_server_background picow_tcp_server.c ) target_compile_definitions(picow_tcpip_server_background PRIVATE WIFI_SSID=\"${WIFI_SSID}\" WIFI_PASSWORD=\"${WIFI_PASSWORD}\" ) target_include_directories(picow_tcpip_server_background PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts ) target_link_libraries(picow_tcpip_server_background pico_cyw43_arch_lwip_threadsafe_background pico_stdlib ) # enable usb output, disable uart output pico_enable_stdio_usb(picow_tcpip_server_background 1) pico_enable_stdio_uart(picow_tcpip_server_background 0) pico_add_extra_outputs(picow_tcpip_server_background) add_executable(picow_tcpip_server_poll picow_tcp_server.c ) target_compile_definitions(picow_tcpip_server_poll PRIVATE WIFI_SSID=\"${WIFI_SSID}\" WIFI_PASSWORD=\"${WIFI_PASSWORD}\" ) target_include_directories(picow_tcpip_server_poll PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts ) target_link_libraries(picow_tcpip_server_poll pico_cyw43_arch_lwip_poll pico_stdlib ) # enable usb output, disable uart output pico_enable_stdio_usb(picow_tcpip_server_poll 1) pico_enable_stdio_uart(picow_tcpip_server_poll 0) pico_add_extra_outputs(picow_tcpip_server_poll) |
D:\work\pico\tcp_client>notepad pico_sdk_import.cmake
다음 내용을 복사해줍니다.
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake # This can be dropped into an external project to help locate this SDK # It should be include()ed prior to project() # todo document if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") endif () if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") endif () if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") endif () set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable") set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") if (NOT PICO_SDK_PATH) if (PICO_SDK_FETCH_FROM_GIT) include(FetchContent) set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) if (PICO_SDK_FETCH_FROM_GIT_PATH) get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") endif () FetchContent_Declare( pico_sdk GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk GIT_TAG master ) if (NOT pico_sdk) message("Downloading PICO SDK") FetchContent_Populate(pico_sdk) set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) endif () set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) else () message(FATAL_ERROR "PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." ) endif () endif () get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") if (NOT EXISTS ${PICO_SDK_PATH}) message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") endif () set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK") endif () set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) include(${PICO_SDK_INIT_CMAKE_FILE}) |
D:\work\pico\tcp_client>notepad lwipopts.h
다음 내용을 복사해줍니다.
#ifndef _LWIPOPTS_H #define _LWIPOPTS_H // Generally you would define your own explicit list of lwIP options // (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html) // // This example uses a common include to avoid repetition #include "lwipopts_examples_common.h" #endif |
D:\work\pico\tcp_client>notepad lwipopts_examples_common.h
다음 내용을 복사해줍니다.
#ifndef _LWIPOPTS_EXAMPLE_COMMONH_H #define _LWIPOPTS_EXAMPLE_COMMONH_H // Common settings used in most of the pico_w examples // (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) // allow override in some examples #ifndef NO_SYS #define NO_SYS 1 #endif // allow override in some examples #ifndef LWIP_SOCKET #define LWIP_SOCKET 0 #endif #if PICO_CYW43_ARCH_POLL #define MEM_LIBC_MALLOC 1 #else // MEM_LIBC_MALLOC is incompatible with non polling versions #define MEM_LIBC_MALLOC 0 #endif #define MEM_ALIGNMENT 4 #define MEM_SIZE 4000 #define MEMP_NUM_TCP_SEG 32 #define MEMP_NUM_ARP_QUEUE 10 #define PBUF_POOL_SIZE 24 #define LWIP_ARP 1 #define LWIP_ETHERNET 1 #define LWIP_ICMP 1 #define LWIP_RAW 1 #define TCP_WND (8 * TCP_MSS) #define TCP_MSS 1460 #define TCP_SND_BUF (8 * TCP_MSS) #define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) #define LWIP_NETIF_STATUS_CALLBACK 1 #define LWIP_NETIF_LINK_CALLBACK 1 #define LWIP_NETIF_HOSTNAME 1 #define LWIP_NETCONN 0 #define MEM_STATS 0 #define SYS_STATS 0 #define MEMP_STATS 0 #define LINK_STATS 0 // #define ETH_PAD_SIZE 2 #define LWIP_CHKSUM_ALGORITHM 3 #define LWIP_DHCP 1 #define LWIP_IPV4 1 #define LWIP_TCP 1 #define LWIP_UDP 1 #define LWIP_DNS 1 #define LWIP_TCP_KEEPALIVE 1 #define LWIP_NETIF_TX_SINGLE_PBUF 1 #define DHCP_DOES_ARP_CHECK 0 #define LWIP_DHCP_DOES_ACD_CHECK 0 #ifndef NDEBUG #define LWIP_DEBUG 1 #define LWIP_STATS 1 #define LWIP_STATS_DISPLAY 1 #endif #define ETHARP_DEBUG LWIP_DBG_OFF #define NETIF_DEBUG LWIP_DBG_OFF #define PBUF_DEBUG LWIP_DBG_OFF #define API_LIB_DEBUG LWIP_DBG_OFF #define API_MSG_DEBUG LWIP_DBG_OFF #define SOCKETS_DEBUG LWIP_DBG_OFF #define ICMP_DEBUG LWIP_DBG_OFF #define INET_DEBUG LWIP_DBG_OFF #define IP_DEBUG LWIP_DBG_OFF #define IP_REASS_DEBUG LWIP_DBG_OFF #define RAW_DEBUG LWIP_DBG_OFF #define MEM_DEBUG LWIP_DBG_OFF #define MEMP_DEBUG LWIP_DBG_OFF #define SYS_DEBUG LWIP_DBG_OFF #define TCP_DEBUG LWIP_DBG_OFF #define TCP_INPUT_DEBUG LWIP_DBG_OFF #define TCP_OUTPUT_DEBUG LWIP_DBG_OFF #define TCP_RTO_DEBUG LWIP_DBG_OFF #define TCP_CWND_DEBUG LWIP_DBG_OFF #define TCP_WND_DEBUG LWIP_DBG_OFF #define TCP_FR_DEBUG LWIP_DBG_OFF #define TCP_QLEN_DEBUG LWIP_DBG_OFF #define TCP_RST_DEBUG LWIP_DBG_OFF #define UDP_DEBUG LWIP_DBG_OFF #define TCPIP_DEBUG LWIP_DBG_OFF #define PPP_DEBUG LWIP_DBG_OFF #define SLIP_DEBUG LWIP_DBG_OFF #define DHCP_DEBUG LWIP_DBG_OFF #endif /* __LWIPOPTS_H__ */ |
D:\work\pico\tcp_client>notepad picow_tcp_server.c
다음 내용을 복사해줍니다.
/** * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. * * SPDX-License-Identifier: BSD-3-Clause */ #include <stdio.h> #include "pico/stdlib.h" #include <string.h> #include <stdlib.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "lwip/pbuf.h" #include "lwip/tcp.h" #define TCP_PORT 4242 #define BUF_SIZE 2048 #define TEST_ITERATIONS 10 #define POLL_TIME_S 5 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; static TCP_SERVER_T* tcp_server_init(void) { TCP_SERVER_T *state = calloc(1, sizeof(TCP_SERVER_T)); if (!state) { printf("failed to allocate state\n"); return NULL; } return state; } static err_t tcp_server_close(void *arg) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; err_t err = ERR_OK; if (state->client_pcb != NULL) { tcp_arg(state->client_pcb, NULL); tcp_poll(state->client_pcb, NULL, 0); tcp_sent(state->client_pcb, NULL); tcp_recv(state->client_pcb, NULL); tcp_err(state->client_pcb, NULL); err = tcp_close(state->client_pcb); if (err != ERR_OK) { printf("close failed %d, calling abort\n", err); tcp_abort(state->client_pcb); err = ERR_ABRT; } state->client_pcb = NULL; } if (state->server_pcb) { tcp_arg(state->server_pcb, NULL); tcp_close(state->server_pcb); state->server_pcb = NULL; } return err; } static err_t tcp_server_result(void *arg, int status) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; if (status == 0) { printf("test success\n"); } else { printf("test failed %d\n", status); } state->complete = true; return tcp_server_close(arg); } static err_t tcp_server_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; 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; printf("Waiting for buffer from client\n"); } return ERR_OK; } err_t tcp_server_send_data(void *arg, struct tcp_pcb *tpcb, char *recv_buf) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; strcpy(state->buffer_sent, recv_buf); state->sent_len = strlen(recv_buf) + 1; // printf("Writing %ld bytes to client\n", BUF_SIZE); printf("Writing %ld bytes to client\n", strlen(state->buffer_sent)); // this method is callback from lwIP, so cyw43_arch_lwip_begin is not required, however you // can use this method to cause an assertion in debug mode, if this method is called when // cyw43_arch_lwip_begin IS needed cyw43_arch_lwip_check(); err_t err = tcp_write(tpcb, state->buffer_sent, strlen(recv_buf), TCP_WRITE_FLAG_COPY); if (err != ERR_OK) { printf("Failed to write data %d\n", err); return ERR_OK;//tcp_server_result(arg, -1); } 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 ERR_OK;//tcp_server_result(arg, -1); } // this method is callback from lwIP, so cyw43_arch_lwip_begin is not required, however you // can use this method to cause an assertion in debug mode, if this method is called when // cyw43_arch_lwip_begin IS needed cyw43_arch_lwip_check(); if (p->tot_len > 0) { printf("tcp_server_recv %d/%d err %d\n", p->tot_len, state->recv_len, err); // Receive the buffer const uint16_t buffer_left = BUF_SIZE - state->recv_len; state->recv_len += pbuf_copy_partial(p, state->buffer_recv + state->recv_len, p->tot_len > buffer_left ? buffer_left : p->tot_len, 0); tcp_recved(tpcb, p->tot_len); } pbuf_free(p); // Send buffer return tcp_server_send_data(arg, state->client_pcb, state->buffer_recv + (state->recv_len - p->tot_len)); } static err_t tcp_server_poll(void *arg, struct tcp_pcb *tpcb) { printf("tcp_server_poll_fn\n"); return tcp_server_result(arg, -1); // no response is an error? } static void tcp_server_err(void *arg, err_t err) { if (err != ERR_ABRT) { printf("tcp_client_err_fn %d\n", err); tcp_server_result(arg, err); } } 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) { printf("Failure in accept\n"); tcp_server_result(arg, err); return ERR_VAL; } printf("Client connected\n"); state->client_pcb = client_pcb; tcp_arg(client_pcb, state); tcp_recv(client_pcb, tcp_server_recv); tcp_sent(client_pcb, tcp_server_sent); //tcp_poll(client_pcb, tcp_server_poll, POLL_TIME_S * 2); tcp_err(client_pcb, tcp_server_err); return ERR_OK;//tcp_server_send_data(arg, state->client_pcb); } static bool tcp_server_open(void *arg) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; 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) { printf("failed to create pcb\n"); return false; } err_t err = tcp_bind(pcb, NULL, TCP_PORT); if (err) { printf("failed to bind to port %d\n"); return false; } state->server_pcb = tcp_listen_with_backlog(pcb, 1); if (!state->server_pcb) { 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; } while(1){//!state->complete) { // the following #ifdef is only here so this same example can be used in multiple modes; // you do not need it in your code #if PICO_CYW43_ARCH_POLL // if you are using pico_cyw43_arch_poll, then you must poll periodically from your // main loop (not from a timer) to check for WiFi driver or lwIP work that needs to be done. cyw43_arch_poll(); sleep_ms(1); // printf("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), TCP_PORT); #else // if you are not using pico_cyw43_arch_poll, then WiFI driver and lwIP work // is done via interrupt in the background. This sleep is just an example of some (blocking) // work you might be doing. sleep_ms(1000); // printf("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), TCP_PORT); #endif } free(state); } int main() { stdio_init_all(); if (cyw43_arch_init()) { printf("failed to initialise\n"); return 1; } cyw43_arch_enable_sta_mode(); printf("Connecting to WiFi...\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(); cyw43_arch_deinit(); return 0; } |
4. 이제 빌드를 진행합니다.
D:\work\pico\tcp_server>mkdir build
D:\work\pico\tcp_server>cd build
D:\work\pico\tcp_server>cmake -G "NMake Makefiles" ..
D:\work\pico\tcp_server>nmake
5. 다음 2개의 파일 중 하나를 Raspberry Pi Pico W 보드에 옮겨줍니다. 본 포스트에서는 picow_tcpip_client_poll.uf2를 사용하여 테스트를 진행했습니다. Raspberry Pi Pico W 보드의 버튼을 누른채 USB에 다시 연결해준후 복사해주면 됩니다.
6. MobaXterm으로 시리얼 포트 연결하면 보드에서 다음과 같은 메시지가 출력됩니다.
보이지 않으면 Raspberry Pi Pico W를 재연결하고 다음 화면에서 R을 누르세요.
7. TCP 클라이언트 프로그램을 실행합니다. 본 포스트에서는 간단한 파이썬 코드를 사용했습니다.
HOST, PORT 부분은 6번에서 확인한 서버 IP와 포트 번호로 바꿔야 합니다.
import socket HOST = '192.168.25.6' PORT = 4242 client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client_socket.connect((HOST, PORT)) # 키보드로 입력한 문자열을 서버로 전송하고 # 서버에서 에코되어 돌아오는 메시지를 받으면 화면에 출력합니다. # quit를 입력할 때 까지 반복합니다. while True: message = input('Enter Message : ') if message == 'quit': break client_socket.send(message.encode()) data = client_socket.recv(1024) print('Received from the server :',repr(data.decode())) client_socket.close() |
8. 시리얼 포트 터미널에 Client connected라는 메시지가 보입니다.
파이썬 프로그램에는 Enter Message :가 출력됩니다.
9. 이제 파이썬 프로그램에서 문자열을 입력한 후, 엔터를 눌러보면 에코된 문자열이 다시 출력됩니다.
시리얼 포트 터미널에는 수신받은 문자열의 길이가 출력됩니다.
10. 몇가지 문자열을 더 테스트해본 결과입니다.
파이썬 프로그램 로그
시리얼 포트 터미널 로그
파이썬 프로그램에서 quit를 입력하면 접속 종료후, Raspberry Pi Pico W에서 접속을 끊어지는 부분이 좀더 확인이 필요합니다.
그래도 다시 접속해보면 정상적으로 문자열이 에코되기는 합니다.