LoginSignup
1
0

Raspberry Pi picowでsocket APIを使う

Posted at

1. はじめに

Raspberry Pi PicoのArduino環境(earlephilhower版)では、socket APIが使えません。これは、ネットワーク機能がRTOSを使わない設定になっているからです。SDKでは、設定を変えてRTOSを使うことができます(下記リンク先参照)が、Arduino環境ではその設定がライブラリ形式で提供されていて、直接変更することはできません。
そこで、Arduino環境でsocket APIが使えるように、lwIPのビルド環境を作ってみました。

2. 設定の変更

2.1 変更が必要なパラメータ

ネットワーク機能(lwIP)がRTOS(FreeRTOS)を使用しない構成(NO_SYS=1, PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1)になっています。これをNO_SYS=0, PICO_CYW43_ARCH_FREERTOS=1に変更します。

2.2 lwIPの設定

NO_SYSの設定はファイルlwipopts.hの内で設定しています。これを直接変更するのではなく、このファイルのコピーをローカルなプロジェクト内( ${PROJECT_DIR}/include )にコピーし、includeの順番を指定することで変更を反映させます。
以下は、その他必要な設定を行ったlwipopts.hです。

lwipopts.h
lwipopts.h
#ifndef _LWIPOPTS_EXAMPLE_COMMONH_H
#define _LWIPOPTS_EXAMPLE_COMMONH_H

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

// Critical section protection
extern void noInterrupts();
extern void interrupts();
#define SYS_ARCH_DECL_PROTECT int
#define SYS_ARCH_PROTECT(lev) noInterrupts
#define SYS_ARCH_UNPROTECT(lev) interrupts

extern unsigned long get_rand_32(void);
#define LWIP_RAND() get_rand_32()

// 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)

#define NO_SYS                      0
#define LWIP_SOCKET                 1
#define MEM_LIBC_MALLOC             0

#define MEM_ALIGNMENT               4
#define MEM_SIZE                    16384
#define MEMP_NUM_TCP_SEG            32
#define MEMP_NUM_ARP_QUEUE          10
#define PBUF_POOL_SIZE              24
#define LWIP_ARP                    2
#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

// See #1285
#define MEMP_NUM_UDP_PCB            6

#if LWIP_IPV6
#define LWIP_IPV6_DHCP6             1
#endif

// NTP
extern void __setSystemTime(unsigned long long sec, unsigned long us);
#define SNTP_SET_SYSTEM_TIME_US(sec, us)  __setSystemTime(sec, us)
#define SNTP_MAX_SERVERS                  2
//#define SNTP_SERVER_ADDRESS               "pool.ntp.org"
#define SNTP_SERVER_DNS                   1

#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

#if (NO_SYS == 0)
#define TCPIP_THREAD_PRIO 6
#define TCPIP_THREAD_STACKSIZE 1024
#define DEFAULT_THREAD_STACKSIZE 1024
#define DEFAULT_RAW_RECVMBOX_SIZE 8
#define TCPIP_MBOX_SIZE 8
#define DEFAULT_TCP_RECVMBOX_SIZE 10
#define DEFAULT_ACCEPTMBOX_SIZE 6
#define LWIP_TIMEVAL_PRIVATE 0

// not necessary, can be done either way
#define LWIP_TCPIP_CORE_LOCKING_INPUT 1

// ping_thread sets socket receive timeout, so enable this feature
#define LWIP_SO_RCVTIMEO 1
#endif

#ifdef __cplusplus
}
#endif // __cplusplus

#endif /* __LWIPOPTS_H__ */
一方、 `PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1` はコマンドラインで指定されていますので、build_unflagsとbuild_flagsを使って置き換えます。 その他の設定をした `platformio.ini` の `build_flags` と `build_unflags` は以下のようになります。
platformio.ini(抜粋)
build_flags = 
  -Ic:/Users/${sysenv.USERNAME}/.platformio/packages/framework-arduinopico/pico-sdk/lib/lwip/contrib/ports/freertos/include
  -D PICO_CYW43_ARCH_FREERTOS=1
  -D LWIP_PROVIDE_ERRNO=1
  -D LWIP_TIMEVAL_PRIVATE=0

build_unflags =
  -D PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1

2.3 FreeRTOSの設定

FreeRTOSの設定は、ファイルFreeRTOSConfig.hで行います。関数xQueueGetMutexHolderが有効になるように設定します。以下が変更点です。

--- a/src/FreeRTOSConfig.h
+++ b/src/FreeRTOSConfig.h
@@ -122,6 +122,8 @@ extern void rtosFatalError(void);
 #define configSUPPORT_PICO_SYNC_INTEROP 1
 #define configSUPPORT_PICO_TIME_INTEROP 1

+#define INCLUDE_xQueueGetMutexHolder 1
+#define INCLUDE_xSemaphoreGetMutexHolder 1

 #include "rp2040_config.h"
 //#include "task.h"

FreeRTOSの設定を変更する方法は、下記リンク先をご参照ください。

3. ビルド環境の構築

3.1 lwIP

lwIPのソースファイルは、framework-arduinopico/pico-sdk以下にあります。これらをlib以下にコピーすればビルド対象となりますが、lwipopts.h以外のファイルは変更不要なので#includeだけを行う.cや.hファイルを用意する方法を採用しました。こんな感じです。

lwip_freertos.c
#include <../pico-sdk/src/rp2_common/pico_lwip/lwip_freertos.c>

ソースコードは公開準備中です。

3.2 cyw43ドライバ

cyw43ドライバ関連ファイルも同様な方式でファイルを作成します。変更が必要だったファイルはで、修正内容は以下の通りです。

--- a/src/pico_cyw43_arch/cyw43_arch_freertos.c
+++ b/src/pico_cyw43_arch/cyw43_arch_freertos.c
@@ -10,6 +10,10 @@
 #include "pico/cyw43_arch.h"
 #include "pico/cyw43_driver.h"
 #include "pico/async_context_freertos.h"
+#ifdef ARDUINO_ARCH_RP2040
+#include <FreeRTOS.h>
+#include <task.h>
+#endif

 #if CYW43_LWIP
 #include "pico/lwip_freertos.h"
@@ -40,6 +44,11 @@ async_context_t *cyw43_arch_init_default_async_context(void) {
 }

 int cyw43_arch_init(void) {
+#ifdef ARDUINO_ARCH_RP2040
+    if (xTaskGetCurrentTaskHandle() == NULL) {
+        return 0;
+    }
+#endif
     async_context_t *context = cyw43_arch_async_context();
     if (!context) {
         context = cyw43_arch_init_default_async_context();

ソースコードは公開準備中です。

4. 動作確認用プログラム

socket APIを使った単純なプログラムです。メイン側では、周期的にタスクの状態を表示させています。pico-probeを使用するため、USBシリアルポートの代わりにSerial1を使っています。

main.cpp
main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <FreeRTOS_custom.h>
#include <FreeRTOS.h>
#include <task.h>
#include <LWIP_custom.h>
#include <PICO_cyw43.h>
#ifdef ARDUINO_ARCH_RP2040
#include <pico_cyw43_arch/cyw43_arch.h>
#endif
#include <lwip/sockets.h>

#ifdef USE_TINYUSB
#include <Adafruit_TinyUSB.h>
#endif
#if __has_include("ssid_passwd.h")
#include "ssid_passwd.h"
#endif


#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif

#ifndef SOMAXCONN
#define SOMAXCONN (5)
#endif

#ifdef DEBUG_RP2040_PORT
#define SERIAL_PORT DEBUG_RP2040_PORT
#else
#define SERIAL_PORT Serial1
#endif

#define PERROR(str) my_perror(__FILE__, __LINE__, str)

static constexpr char const ssid[] = STASSID;
static constexpr char const password[] = STAPSK;

static int server_fd = -1;

static void
my_perror(const char *file, uint32_t lineno, const char *msg)
{
  SERIAL_PORT.printf("%s %d: %s %s\n", file, lineno, msg, lwip_strerr(errno));
}

static void
service_task(void *arg)
{
  static bool is_connected = false;
  static uint8_t buf[512];
  int client_fd;
  struct sockaddr_in addr_in;
  for (;;) {
    if (is_connected) {
      ssize_t n;
      n = read(client_fd, buf, sizeof(buf));
      if (n > 0) {
        write(client_fd, buf, n);
      } else {
        close(client_fd);
        is_connected = false;
        SERIAL_PORT.printf("disconnected\n");
      }
    } else {
      SERIAL_PORT.printf("waiting for connect\n");
      socklen_t addrlen = sizeof(addr_in);
      client_fd = accept(server_fd, reinterpret_cast<struct sockaddr*>(&addr_in), &addrlen);
      SERIAL_PORT.printf("connected from %s\n", IPAddress(addr_in.sin_addr.s_addr).toString().c_str());
      is_connected = true;
    }
  }
}


void
setup()
{
#ifdef ARDUINO_ARCH_RP2040
  cyw43_arch_init();
#endif
  SERIAL_PORT.begin(115200);
  while (!SERIAL_PORT) {
    ;
  }
  SERIAL_PORT.printf("build [%s %s]\n", __DATE__, __TIME__);
  WiFi.begin(ssid, password);
  SERIAL_PORT.println("");

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    SERIAL_PORT.print(".");
  }
  SERIAL_PORT.println("");
  SERIAL_PORT.print("Connected to ");
  SERIAL_PORT.println(ssid);
  SERIAL_PORT.print("IP address: ");
  SERIAL_PORT.println(WiFi.localIP());
  server_fd = socket(AF_INET, SOCK_STREAM, 0);
  struct sockaddr_in addr_in;
  addr_in.sin_family = AF_INET;
  addr_in.sin_addr.s_addr = INADDR_ANY;
  addr_in.sin_port = htons(23);
  int ret = bind(server_fd, reinterpret_cast<struct sockaddr*>(&addr_in), sizeof(addr_in));
  if (ret < 0) {
    PERROR("bind:");
  }
  ret = listen(server_fd, SOMAXCONN);
  if (ret < 0) {
    PERROR("listen:");
  }
  TaskHandle_t task_handle;
  BaseType_t result = xTaskCreateAffinitySet(service_task, "tcp_server", 0x400, NULL, 5, 0x1, &task_handle);
  if (result != pdPASS) {

  }
  static char buf[1024];
  vTaskList(buf);
  Serial.print(buf);
  SERIAL_PORT.println("task created");
}

void
loop()
{
  static char buf[1024];
  vTaskList(buf);
  SERIAL_PORT.println("\n----------------+-+-------+---------+-------+");
  SERIAL_PORT.print(buf);
  delay(5000);
}

void
loop1()
{
  SERIAL_PORT.print('#');
  delay(1000);
}

5. 動作結果

上記のプログラムを動作させると、タスク一覧が表示されます。TCP/IPのサーバタスクが接続待ち状態になっていることがわかります。

----------------+-+-------+---------+-------+
CORE0           X       4       788     2
IDLE0           X       0       235     7
IDLE1           R       0       235     8
Tmr Svc         B       2       975     6
tcpip_thr       B       6       76      10
CORE1           B       4       936     3
USB             B       6       186     1
IdleCore0       S       7       98      4
async_con       S       4       778     9
IdleCore1       S       7       98      5
tcp_serve       B       5       888     11

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0