みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

NuttX STM32F4Discovery+ENC28J60でEthernet

はじめに

Amazonで購入したEthernetコントローラ基板をSTM32F4Discovery + NuttXで使用できるようにする。

このコントローラ基板はENC28J60が載っている。ENC28J60はMACとPHYの機能を持っているため、ホストとなるボードはSPIで通信できればEthernetが使えるようになる。

ちなみにSTM32F4Discoveryに載っているSTM32F407VGT6MACの機能を持っているのでPHYだけのコントローラでも使用できる。

ENC28J60のドライバ

NuttXにはENC28J60のドライバは存在するが、STM32F4Discoveryの初期化処理でこのドライバを呼び出すようになっていないため、 初期化処理を修正する。

初期化処理の修正

次のファイルを修正している。

  • configs/stm32f4discovery/src/stm32_netinit.c
  • configs/stm32f4discovery/src/stm32_spi.c
  • configs/stm32f4discovery/src/stm32f4discovery.h
diff --git a/configs/stm32f4discovery/src/stm32_netinit.c b/configs/stm32f4discovery/src/stm32_netinit.c
index 36ee755f93..47a450ad5c 100644
--- a/configs/stm32f4discovery/src/stm32_netinit.c
+++ b/configs/stm32f4discovery/src/stm32_netinit.c
@@ -39,6 +39,134 @@
 
 #include <nuttx/config.h>
 
+#ifdef CONFIG_ENC28J60
+#include <stdint.h>
+#include <stdio.h>
+#include <debug.h>
+
+#include <nuttx/spi/spi.h>
+#include <nuttx/net/enc28j60.h>
+
+#include <arch/board/board.h>
+
+#include "chip.h"
+#include "up_arch.h"
+#include "up_internal.h"
+#include "stm32_spi.h"
+
+#include "stm32f4discovery.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+/* Configuration ************************************************************/
+/* ENC28J60
+ *
+ * --- ------ -------------- -----------------------------------------------------
+ * PIN NAME   SIGNAL         NOTES
+ * --- ------ -------------- -----------------------------------------------------
+ *
+ * 29  PA4    PA4-SPI1-NSS   10Mbit ENC28J60
+ * 30  PA5    PA5-SPI1-SCK   10Mbit ENC28J60
+ * 31  PA6    PA6-SPI1-MISO  10Mbit ENC28J60
+ * 32  PA7    PA7-SPI1-MOSI  10Mbit ENC28J60
+ * 34  PE4    (no name)      10Mbps ENC28J60 Interrupt
+ */
+
+/* ENC28J60 is on SPI1 */
+
+#ifndef CONFIG_STM32_SPI1
+# error "Need CONFIG_STM32_SPI1 in the configuration"
+#endif
+
+/* SPI Assumptions **********************************************************/
+
+#define ENC28J60_SPI_PORTNO 1   /* On SPI1 */
+#define ENC28J60_DEVNO      0   /* Only one ENC28J60 */
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct stm32_lower_s
+{
+  const struct enc_lower_s lower;    /* Low-level MCU interface */
+  xcpt_t                   handler;  /* ENC28J60 interrupt handler */
+  FAR void                *arg;      /* Argument that accompanies the interrupt */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int  up_attach(FAR const struct enc_lower_s *lower, xcpt_t handler,
+                      FAR void *arg);
+static void up_enable(FAR const struct enc_lower_s *lower);
+static void up_disable(FAR const struct enc_lower_s *lower);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* The ENC28J60 normal provides interrupts to the MCU via a GPIO pin.  The
+ * following structure provides an MCU-independent mechanixm for controlling
+ * the ENC28J60 GPIO interrupt.
+ */
+
+static struct stm32_lower_s g_enclower =
+{
+  .lower =
+  {
+    .attach  = up_attach,
+    .enable  = up_enable,
+    .disable = up_disable
+  },
+  .handler = NULL,
+  .arg     = NULL
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: struct enc_lower_s methods
+ ****************************************************************************/
+
+static int up_attach(FAR const struct enc_lower_s *lower, xcpt_t handler,
+                     FAR void *arg)
+{
+  FAR struct stm32_lower_s *priv = (FAR struct stm32_lower_s *)lower;
+
+  /* Just save the handler for use when the interrupt is enabled */
+
+  priv->handler = handler;
+  priv->arg     = arg;
+  return OK;
+}
+
+static void up_enable(FAR const struct enc_lower_s *lower)
+{
+  FAR struct stm32_lower_s *priv = (FAR struct stm32_lower_s *)lower;
+
+  DEBUGASSERT(priv->handler);
+  (void)stm32_gpiosetevent(GPIO_ENC28J60_INTR, false, true, true,
+                           priv->handler, priv->arg);
+}
+
+/* REVISIT:  Since the interrupt is completely torn down, not just disabled,
+ * in interrupt requests that occurs while the interrupt is disabled will be
+ * lost.
+ */
+
+static void up_disable(FAR const struct enc_lower_s *lower)
+{
+  (void)stm32_gpiosetevent(GPIO_ENC28J60_INTR, false, true, true,
+                           NULL, NULL);
+}
+#endif /* CONFIG_ENC28J60 */
+
+
 /************************************************************************************
  * Public Functions
  ************************************************************************************/
@@ -50,6 +178,35 @@
 #if defined(CONFIG_NET) && !defined(CONFIG_NETDEV_LATEINIT)
 void up_netinitialize(void)
 {
+#ifdef CONFIG_ENC28J60
+  FAR struct spi_dev_s *spi;
+  int ret;
+
+  /* Assumptions:
+   * 1) ENC28J60 pins were configured in up_spi.c early in the boot-up phase.
+   * 2) Clocking for the SPI1 peripheral was also provided earlier in boot-up.
+   */
+
+  spi = stm32_spibus_initialize(ENC28J60_SPI_PORTNO);
+  if (!spi)
+    {
+      nerr("ERROR: Failed to initialize SPI port %d\n", ENC28J60_SPI_PORTNO);
+      return;
+    }
+
+  /* Bind the SPI port to the ENC28J60 driver */
+
+  ret = enc_initialize(spi, &g_enclower.lower, ENC28J60_DEVNO);
+  if (ret < 0)
+    {
+      nerr("ERROR: Failed to bind SPI port %d ENC28J60 device %d: %d\n",
+           ENC28J60_SPI_PORTNO, ENC28J60_DEVNO, ret);
+      return;
+    }
+
+  ninfo("Bound SPI port %d to ENC28J60 device %d\n",
+        ENC28J60_SPI_PORTNO, ENC28J60_DEVNO);
+#endif /* CONFIG_ENC28J60 */
 }
 #endif
 
diff --git a/configs/stm32f4discovery/src/stm32_spi.c b/configs/stm32f4discovery/src/stm32_spi.c
index a9be4c1d2d..0cafcc5aa0 100644
--- a/configs/stm32f4discovery/src/stm32_spi.c
+++ b/configs/stm32f4discovery/src/stm32_spi.c
@@ -70,7 +70,12 @@
 void weak_function stm32_spidev_initialize(void)
 {
 #ifdef CONFIG_STM32_SPI1
+# ifdef  CONFIG_ENC28J60
+  (void)stm32_configgpio(GPIO_ENC28J60_CS);  /* ENC28J60 chip select */
+  (void)stm32_configgpio(GPIO_ENC28J60_INTR);
+# else
   (void)stm32_configgpio(GPIO_CS_MEMS);    /* MEMS chip select */
+# endif
 #endif
 #if defined(CONFIG_STM32_SPI2) && defined(CONFIG_SENSORS_MAX31855)
   (void)stm32_configgpio(GPIO_MAX31855_CS); /* MAX31855 chip select */
@@ -126,6 +131,14 @@ void stm32_spi1select(FAR struct spi_dev_s *dev, uint32_t devid, bool selected)
 {
   spiinfo("devid: %d CS: %s\n", (int)devid, selected ? "assert" : "de-assert");
 
+#ifdef CONFIG_ENC28J60
+  if (devid == SPIDEV_ETHERNET(0))
+    {
+      /* Set the GPIO low to select and high to de-select */
+
+      stm32_gpiowrite(GPIO_ENC28J60_CS, !selected);
+    }
+#endif
 #ifdef CONFIG_LCD_ST7567
   if (devid == SPIDEV_DISPLAY(0))
     {
diff --git a/configs/stm32f4discovery/src/stm32f4discovery.h b/configs/stm32f4discovery/src/stm32f4discovery.h
index 80c269039c..c673ce1b76 100644
--- a/configs/stm32f4discovery/src/stm32f4discovery.h
+++ b/configs/stm32f4discovery/src/stm32f4discovery.h
@@ -286,6 +286,17 @@
 #define GPIO_CS_XEN1210   (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\
                            GPIO_OUTPUT_SET|GPIO_PORTA|GPIO_PIN4)
 
+/* ENC28J60 Ethernet PHY */
+#ifdef CONFIG_ENC28J60
+/* PA1 */
+#  define GPIO_ENC28J60_CS    (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\
+                               GPIO_OUTPUT_SET|GPIO_PORTA|GPIO_PIN4)
+/* PE4 */
+#  define GPIO_ENC28J60_INTR  (GPIO_INPUT|GPIO_FLOAT|GPIO_EXTI| \
+                               GPIO_OPENDRAIN|GPIO_PORTE|GPIO_PIN4)
+#endif
+
+
 /* USB OTG FS
  *
  * PA9  OTG_FS_VBUS VBUS sensing (also connected to the green LED)

コンフィグレーション

次のコンフィグレーションを全て有効化する。

CONFIG_NET

Networking Support
  -> Networking Support

CONFIG_NET_TCP

Networking Support
  -> Networking support
    -> TCP/IP Networking

CONFIG_NET_UDP

Networking Support
  -> Networking support
    -> UDP Networking

CONFIG_NET_BROADCAST

Networking Support
  -> Networking support
    -> UDP Networking
      -> Disable UDP/IP Stack
        -> UDP broadcast Rx support

CONFIG_NETDEV_PHY_IOCTL

これを忘れるとifup/ifdownコマンドが効かない

Networking Support
  -> Networking support
    -> Network Device Operations
      -> Enable PHY ioctl()

CONFIG_NET_ICMP

Networking Support
  -> Networking support
    -> ICMP Networking Support

CONFIG_NET_ICMP_SOCKET

Networking Support
  -> Networking support
    -> ICMP Networking Support
      -> IPPROTO_ICMP socket support

CONFIG_NETUTILS_PING

Application Configuration
  -> Network Utilities
    -> ICMP ping support

CONFIG_SYSTEM_PING

Application Configuration
  -> System Libraries and NSH Add-Ons
    -> ICMP 'ping' command

CONFIG_NSH_IPADDR

192.168.10.100に設定

Application Configuration
  -> NSH Library
    -> NSH Library
      -> Networking Configuration
        -> Network initialization
          -> IP Address Configuration
            -> Target IPv4 address

0xc0a80a64に設定

CONFIG_NSH_DRIPADDR

192.168.10.1に設定

Application Configuration
  -> NSH Library
    -> NSH Library
      -> Networking Configuration
        -> Network initialization
          -> IP Address Configuration
            -> Router IPv4 address

0xc0a80a01に設定

CONFIG_NSH_NOMAC

Application Configuration
  -> NSH Library
    -> NSH Library
      -> Networking Configuration
        -> Network initialization
          -> ardware has no MAC address

CONFIG_DISABLE_POLL=n

POLLの無効化を解除する。

Device Drivers
  -> Disable driver poll interfaces

CONFIG_NETDEVICES

Device Drivers
  -> Network Device/PHY Support

CONFIG_ENC28J60

Device Drivers
  -> Network Device/PHY Support
    -> ENC28J60

CONFIG_SCHED_HPWORK

RTOS Features
  -> Work queue support
    -> High priority (kernel) worker thread

CONFIG_SCHED_LPWORK

RTOS Features
  -> Work queue support
    -> Low priority (kernel) worker thread

コンフィグの作成

毎回これを手動で設定するのは面倒なのでコンフィグを作成する。

今回は予めnshを使用していることを想定して作業する。

defconfigの作成

$ make savedefconfig

stm32f4discovery/nsh_enc28j60の作成

これをconfigs/stm32f4discovery/nsh_enc28j60において、tools/configure.shで指定できるようにする。

$ mkdir -p configs/stm32f4discovery/nsh_enc28j60
$ cp defconfig configs/stm32f4discovery/nsh_enc28j60/

コンフィグの使用

作成したコンフィグ使う場合には次のようにする。

$ make distclean
$ tools/configure.sh -l stm32f4discovery/nsh_enc28j60

ボードとの接続

ENC28J60 STM32F4DISCOVERY
5V 5V
LNT(INT) PE4
SO PA6
ST(SI) PA7
SCK PA5
CS PA4
GND GND

RSTなどいくつか使用していないピンがあるが、これで問題なく使用できる。

動作確認

対抗となるPCのIPアドレス192.168.10.1などに設定しpingを実行する。

次のようになればOK。

$ ping 192.168.10.100
PING 192.168.10.100 (192.168.10.100) 56(84) bytes of data.
64 bytes from 192.168.10.100: icmp_seq=2 ttl=64 time=1.00 ms
64 bytes from 192.168.10.100: icmp_seq=3 ttl=64 time=0.567 ms
64 bytes from 192.168.10.100: icmp_seq=4 ttl=64 time=0.596 ms

反対に、STM32F4Discovery側からは次のようになればOK。

nsh> ping 192.168.10.1
PING 192.168.10.1 56 bytes of data
56 bytes from 192.168.10.1: icmp_seq=0 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=1 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=2 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=3 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=4 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=5 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=6 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=7 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=8 time=0 ms
56 bytes from 192.168.10.1: icmp_seq=9 time=0 ms
10 packets transmitted, 10 received, 0% packet loss, time 10100 ms

リポジトリのクローン

この環境を組み込んだリポジトリのクローンをGithubに作成した。

使用する場合はnuttxのリポジトリとして次のものを使用する。

$ git clone https://github.com/mickey-happygolucky/nuttx.git -b enc28j60
$ git clone https://bitbucket.org/nuttx/apps.git
$ cd nuttx
$ tools/configure.sh -l configs/stm32f4discovery/nsh_enc28j60
$ make -j4
$ st-flash write nuttx.bin 0x8000000