Модификация Ethernet Shield на W5100

X-WL
Offline
Зарегистрирован: 29.01.2018

Всем привет!

Есть Arduino Due и Ethernet Shield.

По проекту требуется принимать большое количество UDP пакетов за раз(8 по 572 байта). Причем их поток может достигать 200-300 пакетов в секунду. Вроде как проблемы со скоростью разбора пакетов нет. Порции пакетов разбираются, но обычно в среднем доходит только 4-5 из порции.(причем обычно стабильно доходят первые 3, а остальные как пойдет) Как я понимаю все дело в максимальном размере буфера на сокет(2кб). Шилд поддерживает 4 сокета. Можно ли увеличить буфер за счет этих сокетов(путем правки библиотеки) чтобы исправить ситуацию? Или дело не в буфере? И как вообще организованы эти 16кб памяти?

Заранее спасибо за помощь!

sadman41
Offline
Зарегистрирован: 19.10.2016

X-WL пишет:

Всем привет!

Есть Arduino Due и Ethernet Shield.

По проекту требуется принимать большое количество UDP пакетов за раз(8 по 572 байта). Причем их поток может достигать 200-300 пакетов в секунду. Вроде как проблемы со скоростью разбора пакетов нет. Порции пакетов разбираются, но обычно в среднем доходит только 4-5 из порции.(причем обычно стабильно доходят первые 3, а остальные как пойдет)

Похоже, что дело именно в скорости разбора пакетов, иначе бы всегда стабильно доходило только 3 пакета, все остальные бы реджектились (4*572 = 2288, что больше, чем 2кб). А для UDP реджект равносилен потере. 

К тому же о каких пакетах идет речь - о ваших, протокольных или MTU вашей сети равно 572 байта?

X-WL пишет:

Как я понимаю все дело в максимальном размере буфера на сокет(2кб). Шилд поддерживает 4 сокета. Можно ли увеличить буфер за счет этих сокетов(путем правки библиотеки) чтобы исправить ситуацию?

Теоретически - да:
 
RMSR(RX Memory Size Register) [R/W] [0x001A] [0x55] This register assigns total 8K RX Memory to each socket.
 
According to the value of S1 and S0, the memory is assigned to the sockets from socket 0 within the range of 8KB. If there is not enough memory to be assigned, the socket should not be used. The initial value is 0x55 and the 2K memory is assigned to each 4 sockets respectively.

X-WL пишет:
И как вообще организованы эти 16кб памяти?

8кб на TX в целом, 8кб на RX в целом, внутри делятся по кол-ву сокетов.

X-WL
Offline
Зарегистрирован: 29.01.2018

sadman41 пишет:

X-WL пишет:

Всем привет!

Есть Arduino Due и Ethernet Shield.

По проекту требуется принимать большое количество UDP пакетов за раз(8 по 572 байта). Причем их поток может достигать 200-300 пакетов в секунду. Вроде как проблемы со скоростью разбора пакетов нет. Порции пакетов разбираются, но обычно в среднем доходит только 4-5 из порции.(причем обычно стабильно доходят первые 3, а остальные как пойдет)

Похоже, что дело именно в скорости разбора пакетов, иначе бы всегда стабильно доходило только 3 пакета, все остальные бы реджектились (4*572 = 2288, что больше, чем 2кб). А для UDP реджект равносилен потере. 

Пакеты Artnet(DMX). Все это дело должно рулить лентой через DMA.

Мои тесты привели к тому, что Due тратит в среднем по 7мс на прростой парс пакета(перенос в буфер на плате + извлечение пары параметров).
Почему так много? Дело в SPI? Известно, что stm32f107 справляется с пачкой в 16 пакетов при 25 фпс.

Если к этому всему добавить перенос в память библиотеки и обновление данных(через DMA) на 8 лентах, то время доходит до 12мс.

sadman41 пишет:

X-WL пишет:

Как я понимаю все дело в максимальном размере буфера на сокет(2кб). Шилд поддерживает 4 сокета. Можно ли увеличить буфер за счет этих сокетов(путем правки библиотеки) чтобы исправить ситуацию?

Теоретически - да:
 
RMSR(RX Memory Size Register) [R/W] [0x001A] [0x55] This register assigns total 8K RX Memory to each socket.
 
According to the value of S1 and S0, the memory is assigned to the sockets from socket 0 within the range of 8KB. If there is not enough memory to be assigned, the socket should not be used. The initial value is 0x55 and the 2K memory is assigned to each 4 sockets respectively.
Как нужно изменить это значение, чтобы получить допустим 10кб на прием/5кб отдача (нужна только чтобы отвечать на запросы главного узла). И это на 2 сокета. Не вижу смысла использовать больше, так как соедниенение через unicast (броадкаст еще больше будет забивать буффер)?
sadman41
Offline
Зарегистрирован: 19.10.2016

X-WL пишет:

Пакеты Artnet(DMX). Все это дело должно рулить лентой через DMA.

...

Если к этому всему добавить перенос в память библиотеки и обновление данных(через DMA) на 8 лентах, то время доходит до 12мс.

В STM я нибэнимэ.

X-WL пишет:

Как нужно изменить это значение, чтобы получить допустим 10кб на прием/5кб отдача (нужна только чтобы отвечать на запросы главного узла). И это на 2 сокета. Не вижу смысла использовать больше, так как соедниенение через unicast (броадкаст еще больше будет забивать буффер)?

10кб - никак. Манипуляция в пределах 8кб: 2 кб на сокет, 2*4 кб + 2*0кб, 1*8кб+3*0кб и пр.

https://www.sparkfun.com/datasheets/DevTools/Arduino/W5100_Datasheet_v1_... PG.23

sadman41
Offline
Зарегистрирован: 19.10.2016

Для Wiznet W5500 можно взять 16кб на сокет:

Sn_RXBUF_SIZE configures the RX buffer block size of Socket n. Socket n RX Buffer Block size can be configured with 1,2,4,8, and 16 Kbytes. If a different size is configured, the data cannot be normally received from a peer. Although Socket n RX Buffer Block size is initially configured to 2Kbytes, user can re-configure its size using Sn_RXBUF_SIZE. The total sum of Sn_RXBUF_SIZE cannot be exceed 16Kbytes. When exceeded, the data reception error is occurred.

X-WL
Offline
Зарегистрирован: 29.01.2018

sadman41 пишет:

X-WL пишет:

Как нужно изменить это значение, чтобы получить допустим 10кб на прием/5кб отдача (нужна только чтобы отвечать на запросы главного узла). И это на 2 сокета. Не вижу смысла использовать больше, так как соедниенение через unicast (броадкаст еще больше будет забивать буффер)?

10кб - никак. Манипуляция в пределах 8кб: 2 кб на сокет, 2*4 кб + 2*0кб, 1*8кб+3*0кб и пр.

https://www.sparkfun.com/datasheets/DevTools/Arduino/W5100_Datasheet_v1_1_6.pdf PG.23

Как правильно нужно производить эту конфигурацию. В файле W5100.cpp изменил значения на 0xAA, там же TX_RX_MAX_BUF_SIZE на 4096

/*
 * Copyright (c) 2010 by Arduino LLC. All rights reserved.
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */

#include <stdio.h>
#include <string.h>

#include "w5100.h"

// W5100 controller instance
W5100Class W5100;

#define TX_RX_MAX_BUF_SIZE 4096
#define TX_BUF 0x1100
#define RX_BUF (TX_BUF + TX_RX_MAX_BUF_SIZE)

#define TXBUF_BASE 0x4000
#define RXBUF_BASE 0x6000

void W5100Class::init(void)
{
  delay(300);

#if !defined(SPI_HAS_EXTENDED_CS_PIN_HANDLING)
  SPI.begin();
  initSS();
#else
  SPI.begin(ETHERNET_SHIELD_SPI_CS);
  // Set clock to 4Mhz (W5100 should support up to about 14Mhz)
  SPI.setClockDivider(ETHERNET_SHIELD_SPI_CS, 21);
  SPI.setDataMode(ETHERNET_SHIELD_SPI_CS, SPI_MODE0);
#endif
  SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
  writeMR(1<<RST);
  writeTMSR(0xAA);
  writeRMSR(0xAA);
  SPI.endTransaction();

  for (int i=0; i<MAX_SOCK_NUM; i++) {
    SBASE[i] = TXBUF_BASE + SSIZE * i;
    RBASE[i] = RXBUF_BASE + RSIZE * i;
  }
}

uint16_t W5100Class::getTXFreeSize(SOCKET s)
{
  uint16_t val=0, val1=0;
  do {
    val1 = readSnTX_FSR(s);
    if (val1 != 0)
      val = readSnTX_FSR(s);
  } 
  while (val != val1);
  return val;
}

uint16_t W5100Class::getRXReceivedSize(SOCKET s)
{
  uint16_t val=0,val1=0;
  do {
    val1 = readSnRX_RSR(s);
    if (val1 != 0)
      val = readSnRX_RSR(s);
  } 
  while (val != val1);
  return val;
}


void W5100Class::send_data_processing(SOCKET s, const uint8_t *data, uint16_t len)
{
  // This is same as having no offset in a call to send_data_processing_offset
  send_data_processing_offset(s, 0, data, len);
}

void W5100Class::send_data_processing_offset(SOCKET s, uint16_t data_offset, const uint8_t *data, uint16_t len)
{
  uint16_t ptr = readSnTX_WR(s);
  ptr += data_offset;
  uint16_t offset = ptr & SMASK;
  uint16_t dstAddr = offset + SBASE[s];

  if (offset + len > SSIZE) 
  {
    // Wrap around circular buffer
    uint16_t size = SSIZE - offset;
    write(dstAddr, data, size);
    write(SBASE[s], data + size, len - size);
  } 
  else {
    write(dstAddr, data, len);
  }

  ptr += len;
  writeSnTX_WR(s, ptr);
}


void W5100Class::recv_data_processing(SOCKET s, uint8_t *data, uint16_t len, uint8_t peek)
{
  uint16_t ptr;
  ptr = readSnRX_RD(s);
  read_data(s, ptr, data, len);
  if (!peek)
  {
    ptr += len;
    writeSnRX_RD(s, ptr);
  }
}

void W5100Class::read_data(SOCKET s, volatile uint16_t src, volatile uint8_t *dst, uint16_t len)
{
  uint16_t size;
  uint16_t src_mask;
  uint16_t src_ptr;

  src_mask = src & RMASK;
  src_ptr = RBASE[s] + src_mask;

  if( (src_mask + len) > RSIZE ) 
  {
    size = RSIZE - src_mask;
    read(src_ptr, (uint8_t *)dst, size);
    dst += size;
    read(RBASE[s], (uint8_t *) dst, len - size);
  } 
  else
    read(src_ptr, (uint8_t *) dst, len);
}


uint8_t W5100Class::write(uint16_t _addr, uint8_t _data)
{
#if !defined(SPI_HAS_EXTENDED_CS_PIN_HANDLING)
  setSS();  
  SPI.transfer(0xF0);
  SPI.transfer(_addr >> 8);
  SPI.transfer(_addr & 0xFF);
  SPI.transfer(_data);
  resetSS();
#else
  SPI.transfer(ETHERNET_SHIELD_SPI_CS, 0xF0, SPI_CONTINUE);
  SPI.transfer(ETHERNET_SHIELD_SPI_CS, _addr >> 8, SPI_CONTINUE);
  SPI.transfer(ETHERNET_SHIELD_SPI_CS, _addr & 0xFF, SPI_CONTINUE);
  SPI.transfer(ETHERNET_SHIELD_SPI_CS, _data);
#endif
  return 1;
}

uint16_t W5100Class::write(uint16_t _addr, const uint8_t *_buf, uint16_t _len)
{
  for (uint16_t i=0; i<_len; i++)
  {
#if !defined(SPI_HAS_EXTENDED_CS_PIN_HANDLING)
    setSS();    
    SPI.transfer(0xF0);
    SPI.transfer(_addr >> 8);
    SPI.transfer(_addr & 0xFF);
    _addr++;
    SPI.transfer(_buf[i]);
    resetSS();
#else
    SPI.transfer(ETHERNET_SHIELD_SPI_CS, 0xF0, SPI_CONTINUE);
    SPI.transfer(ETHERNET_SHIELD_SPI_CS, _addr >> 8, SPI_CONTINUE);
    SPI.transfer(ETHERNET_SHIELD_SPI_CS, _addr & 0xFF, SPI_CONTINUE);
    SPI.transfer(ETHERNET_SHIELD_SPI_CS, _buf[i]);
    _addr++;
#endif
  }
  return _len;
}

uint8_t W5100Class::read(uint16_t _addr)
{
#if !defined(SPI_HAS_EXTENDED_CS_PIN_HANDLING)
  setSS();  
  SPI.transfer(0x0F);
  SPI.transfer(_addr >> 8);
  SPI.transfer(_addr & 0xFF);
  uint8_t _data = SPI.transfer(0);
  resetSS();
#else
  SPI.transfer(ETHERNET_SHIELD_SPI_CS, 0x0F, SPI_CONTINUE);
  SPI.transfer(ETHERNET_SHIELD_SPI_CS, _addr >> 8, SPI_CONTINUE);
  SPI.transfer(ETHERNET_SHIELD_SPI_CS, _addr & 0xFF, SPI_CONTINUE);
  uint8_t _data = SPI.transfer(ETHERNET_SHIELD_SPI_CS, 0);
#endif
  return _data;
}

uint16_t W5100Class::read(uint16_t _addr, uint8_t *_buf, uint16_t _len)
{
  for (uint16_t i=0; i<_len; i++)
  {
#if !defined(SPI_HAS_EXTENDED_CS_PIN_HANDLING)
    setSS();
    SPI.transfer(0x0F);
    SPI.transfer(_addr >> 8);
    SPI.transfer(_addr & 0xFF);
    _addr++;
    _buf[i] = SPI.transfer(0);
    resetSS();
#else
    SPI.transfer(ETHERNET_SHIELD_SPI_CS, 0x0F, SPI_CONTINUE);
    SPI.transfer(ETHERNET_SHIELD_SPI_CS, _addr >> 8, SPI_CONTINUE);
    SPI.transfer(ETHERNET_SHIELD_SPI_CS, _addr & 0xFF, SPI_CONTINUE);
    _buf[i] = SPI.transfer(ETHERNET_SHIELD_SPI_CS, 0);
    _addr++;
#endif
  }
  return _len;
}

void W5100Class::execCmdSn(SOCKET s, SockCMD _cmd) {
  // Send command to socket
  writeSnCR(s, _cmd);
  // Wait for command to complete
  while (readSnCR(s))
    ;
}

В W5100.h SOKETS с 4х на 2, MAX_SOCK_NUM на 2

/*
 * Copyright (c) 2010 by Arduino LLC. All rights reserved.
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */

#ifndef	W5100_H_INCLUDED
#define	W5100_H_INCLUDED

#include <SPI.h>

#define ETHERNET_SHIELD_SPI_CS 10

#define MAX_SOCK_NUM 2

typedef uint8_t SOCKET;

#define IDM_OR  0x8000
#define IDM_AR0 0x8001
#define IDM_AR1 0x8002
#define IDM_DR  0x8003
/*
class MR {
public:
  static const uint8_t RST   = 0x80;
  static const uint8_t PB    = 0x10;
  static const uint8_t PPPOE = 0x08;
  static const uint8_t LB    = 0x04;
  static const uint8_t AI    = 0x02;
  static const uint8_t IND   = 0x01;
};
*/
/*
class IR {
public:
  static const uint8_t CONFLICT = 0x80;
  static const uint8_t UNREACH  = 0x40;
  static const uint8_t PPPoE    = 0x20;
  static const uint8_t SOCK0    = 0x01;
  static const uint8_t SOCK1    = 0x02;
  static const uint8_t SOCK2    = 0x04;
  static const uint8_t SOCK3    = 0x08;
  static inline uint8_t SOCK(SOCKET ch) { return (0x01 << ch); };
};
*/

class SnMR {
public:
  static const uint8_t CLOSE  = 0x00;
  static const uint8_t TCP    = 0x01;
  static const uint8_t UDP    = 0x02;
  static const uint8_t IPRAW  = 0x03;
  static const uint8_t MACRAW = 0x04;
  static const uint8_t PPPOE  = 0x05;
  static const uint8_t ND     = 0x20;
  static const uint8_t MULTI  = 0x80;
};

enum SockCMD {
  Sock_OPEN      = 0x01,
  Sock_LISTEN    = 0x02,
  Sock_CONNECT   = 0x04,
  Sock_DISCON    = 0x08,
  Sock_CLOSE     = 0x10,
  Sock_SEND      = 0x20,
  Sock_SEND_MAC  = 0x21,
  Sock_SEND_KEEP = 0x22,
  Sock_RECV      = 0x40
};

/*class SnCmd {
public:
  static const uint8_t OPEN      = 0x01;
  static const uint8_t LISTEN    = 0x02;
  static const uint8_t CONNECT   = 0x04;
  static const uint8_t DISCON    = 0x08;
  static const uint8_t CLOSE     = 0x10;
  static const uint8_t SEND      = 0x20;
  static const uint8_t SEND_MAC  = 0x21;
  static const uint8_t SEND_KEEP = 0x22;
  static const uint8_t RECV      = 0x40;
};
*/

class SnIR {
public:
  static const uint8_t SEND_OK = 0x10;
  static const uint8_t TIMEOUT = 0x08;
  static const uint8_t RECV    = 0x04;
  static const uint8_t DISCON  = 0x02;
  static const uint8_t CON     = 0x01;
};

class SnSR {
public:
  static const uint8_t CLOSED      = 0x00;
  static const uint8_t INIT        = 0x13;
  static const uint8_t LISTEN      = 0x14;
  static const uint8_t SYNSENT     = 0x15;
  static const uint8_t SYNRECV     = 0x16;
  static const uint8_t ESTABLISHED = 0x17;
  static const uint8_t FIN_WAIT    = 0x18;
  static const uint8_t CLOSING     = 0x1A;
  static const uint8_t TIME_WAIT   = 0x1B;
  static const uint8_t CLOSE_WAIT  = 0x1C;
  static const uint8_t LAST_ACK    = 0x1D;
  static const uint8_t UDP         = 0x22;
  static const uint8_t IPRAW       = 0x32;
  static const uint8_t MACRAW      = 0x42;
  static const uint8_t PPPOE       = 0x5F;
};

class IPPROTO {
public:
  static const uint8_t IP   = 0;
  static const uint8_t ICMP = 1;
  static const uint8_t IGMP = 2;
  static const uint8_t GGP  = 3;
  static const uint8_t TCP  = 6;
  static const uint8_t PUP  = 12;
  static const uint8_t UDP  = 17;
  static const uint8_t IDP  = 22;
  static const uint8_t ND   = 77;
  static const uint8_t RAW  = 255;
};

class W5100Class {

public:
  void init();

  /**
   * @brief	This function is being used for copy the data form Receive buffer of the chip to application buffer.
   * 
   * It calculate the actual physical address where one has to read
   * the data from Receive buffer. Here also take care of the condition while it exceed
   * the Rx memory uper-bound of socket.
   */
  void read_data(SOCKET s, volatile uint16_t src, volatile uint8_t * dst, uint16_t len);
  
  /**
   * @brief	 This function is being called by send() and sendto() function also. 
   * 
   * This function read the Tx write pointer register and after copy the data in buffer update the Tx write pointer
   * register. User should read upper byte first and lower byte later to get proper value.
   */
  void send_data_processing(SOCKET s, const uint8_t *data, uint16_t len);
  /**
   * @brief A copy of send_data_processing that uses the provided ptr for the
   *        write offset.  Only needed for the "streaming" UDP API, where
   *        a single UDP packet is built up over a number of calls to
   *        send_data_processing_ptr, because TX_WR doesn't seem to get updated
   *        correctly in those scenarios
   * @param ptr value to use in place of TX_WR.  If 0, then the value is read
   *        in from TX_WR
   * @return New value for ptr, to be used in the next call
   */
// FIXME Update documentation
  void send_data_processing_offset(SOCKET s, uint16_t data_offset, const uint8_t *data, uint16_t len);

  /**
   * @brief	This function is being called by recv() also.
   * 
   * This function read the Rx read pointer register
   * and after copy the data from receive buffer update the Rx write pointer register.
   * User should read upper byte first and lower byte later to get proper value.
   */
  void recv_data_processing(SOCKET s, uint8_t *data, uint16_t len, uint8_t peek = 0);

  inline void setGatewayIp(uint8_t *_addr);
  inline void getGatewayIp(uint8_t *_addr);

  inline void setSubnetMask(uint8_t *_addr);
  inline void getSubnetMask(uint8_t *_addr);

  inline void setMACAddress(uint8_t * addr);
  inline void getMACAddress(uint8_t * addr);

  inline void setIPAddress(uint8_t * addr);
  inline void getIPAddress(uint8_t * addr);

  inline void setRetransmissionTime(uint16_t timeout);
  inline void setRetransmissionCount(uint8_t _retry);

  void execCmdSn(SOCKET s, SockCMD _cmd);
  
  uint16_t getTXFreeSize(SOCKET s);
  uint16_t getRXReceivedSize(SOCKET s);
  

  // W5100 Registers
  // ---------------
private:
  static uint8_t write(uint16_t _addr, uint8_t _data);
  static uint16_t write(uint16_t addr, const uint8_t *buf, uint16_t len);
  static uint8_t read(uint16_t addr);
  static uint16_t read(uint16_t addr, uint8_t *buf, uint16_t len);
  
#define __GP_REGISTER8(name, address)             \
  static inline void write##name(uint8_t _data) { \
    write(address, _data);                        \
  }                                               \
  static inline uint8_t read##name() {            \
    return read(address);                         \
  }
#define __GP_REGISTER16(name, address)            \
  static void write##name(uint16_t _data) {       \
    write(address,   _data >> 8);                 \
    write(address+1, _data & 0xFF);               \
  }                                               \
  static uint16_t read##name() {                  \
    uint16_t res = read(address);                 \
    res = (res << 8) + read(address + 1);         \
    return res;                                   \
  }
#define __GP_REGISTER_N(name, address, size)      \
  static uint16_t write##name(uint8_t *_buff) {   \
    return write(address, _buff, size);           \
  }                                               \
  static uint16_t read##name(uint8_t *_buff) {    \
    return read(address, _buff, size);            \
  }

public:
  __GP_REGISTER8 (MR,     0x0000);    // Mode
  __GP_REGISTER_N(GAR,    0x0001, 4); // Gateway IP address
  __GP_REGISTER_N(SUBR,   0x0005, 4); // Subnet mask address
  __GP_REGISTER_N(SHAR,   0x0009, 6); // Source MAC address
  __GP_REGISTER_N(SIPR,   0x000F, 4); // Source IP address
  __GP_REGISTER8 (IR,     0x0015);    // Interrupt
  __GP_REGISTER8 (IMR,    0x0016);    // Interrupt Mask
  __GP_REGISTER16(RTR,    0x0017);    // Timeout address
  __GP_REGISTER8 (RCR,    0x0019);    // Retry count
  __GP_REGISTER8 (RMSR,   0x001A);    // Receive memory size
  __GP_REGISTER8 (TMSR,   0x001B);    // Transmit memory size
  __GP_REGISTER8 (PATR,   0x001C);    // Authentication type address in PPPoE mode
  __GP_REGISTER8 (PTIMER, 0x0028);    // PPP LCP Request Timer
  __GP_REGISTER8 (PMAGIC, 0x0029);    // PPP LCP Magic Number
  __GP_REGISTER_N(UIPR,   0x002A, 4); // Unreachable IP address in UDP mode
  __GP_REGISTER16(UPORT,  0x002E);    // Unreachable Port address in UDP mode
  
#undef __GP_REGISTER8
#undef __GP_REGISTER16
#undef __GP_REGISTER_N

  // W5100 Socket registers
  // ----------------------
private:
  static inline uint8_t readSn(SOCKET _s, uint16_t _addr);
  static inline uint8_t writeSn(SOCKET _s, uint16_t _addr, uint8_t _data);
  static inline uint16_t readSn(SOCKET _s, uint16_t _addr, uint8_t *_buf, uint16_t len);
  static inline uint16_t writeSn(SOCKET _s, uint16_t _addr, uint8_t *_buf, uint16_t len);

  static const uint16_t CH_BASE = 0x0400;
  static const uint16_t CH_SIZE = 0x0100;

#define __SOCKET_REGISTER8(name, address)                    \
  static inline void write##name(SOCKET _s, uint8_t _data) { \
    writeSn(_s, address, _data);                             \
  }                                                          \
  static inline uint8_t read##name(SOCKET _s) {              \
    return readSn(_s, address);                              \
  }
#define __SOCKET_REGISTER16(name, address)                   \
  static void write##name(SOCKET _s, uint16_t _data) {       \
    writeSn(_s, address,   _data >> 8);                      \
    writeSn(_s, address+1, _data & 0xFF);                    \
  }                                                          \
  static uint16_t read##name(SOCKET _s) {                    \
    uint16_t res = readSn(_s, address);                      \
    uint16_t res2 = readSn(_s,address + 1);                  \
    res = res << 8;                                          \
    res2 = res2 & 0xFF;                                      \
    res = res | res2;                                        \
    return res;                                              \
  }
#define __SOCKET_REGISTER_N(name, address, size)             \
  static uint16_t write##name(SOCKET _s, uint8_t *_buff) {   \
    return writeSn(_s, address, _buff, size);                \
  }                                                          \
  static uint16_t read##name(SOCKET _s, uint8_t *_buff) {    \
    return readSn(_s, address, _buff, size);                 \
  }
  
public:
  __SOCKET_REGISTER8(SnMR,        0x0000)        // Mode
  __SOCKET_REGISTER8(SnCR,        0x0001)        // Command
  __SOCKET_REGISTER8(SnIR,        0x0002)        // Interrupt
  __SOCKET_REGISTER8(SnSR,        0x0003)        // Status
  __SOCKET_REGISTER16(SnPORT,     0x0004)        // Source Port
  __SOCKET_REGISTER_N(SnDHAR,     0x0006, 6)     // Destination Hardw Addr
  __SOCKET_REGISTER_N(SnDIPR,     0x000C, 4)     // Destination IP Addr
  __SOCKET_REGISTER16(SnDPORT,    0x0010)        // Destination Port
  __SOCKET_REGISTER16(SnMSSR,     0x0012)        // Max Segment Size
  __SOCKET_REGISTER8(SnPROTO,     0x0014)        // Protocol in IP RAW Mode
  __SOCKET_REGISTER8(SnTOS,       0x0015)        // IP TOS
  __SOCKET_REGISTER8(SnTTL,       0x0016)        // IP TTL
  __SOCKET_REGISTER16(SnTX_FSR,   0x0020)        // TX Free Size
  __SOCKET_REGISTER16(SnTX_RD,    0x0022)        // TX Read Pointer
  __SOCKET_REGISTER16(SnTX_WR,    0x0024)        // TX Write Pointer
  __SOCKET_REGISTER16(SnRX_RSR,   0x0026)        // RX Free Size
  __SOCKET_REGISTER16(SnRX_RD,    0x0028)        // RX Read Pointer
  __SOCKET_REGISTER16(SnRX_WR,    0x002A)        // RX Write Pointer (supported?)
  
#undef __SOCKET_REGISTER8
#undef __SOCKET_REGISTER16
#undef __SOCKET_REGISTER_N


private:
  static const uint8_t  RST = 7; // Reset BIT

  static const int SOCKETS = 2;
  static const uint16_t SMASK = 0x07FF; // Tx buffer MASK
  static const uint16_t RMASK = 0x07FF; // Rx buffer MASK
public:
  static const uint16_t SSIZE = 4096; // Max Tx buffer size
private:
  static const uint16_t RSIZE = 4096; // Max Rx buffer size
  uint16_t SBASE[SOCKETS]; // Tx buffer base address
  uint16_t RBASE[SOCKETS]; // Rx buffer base address

private:
#if !defined(SPI_HAS_EXTENDED_CS_PIN_HANDLING)
  #define SPI_ETHERNET_SETTINGS SPISettings(4000000, MSBFIRST, SPI_MODE0)
  #if defined(ARDUINO_ARCH_AVR)
    #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
      inline static void initSS()    { DDRB  |=  _BV(4); };
      inline static void setSS()     { PORTB &= ~_BV(4); };
      inline static void resetSS()   { PORTB |=  _BV(4); };
    #elif defined(__AVR_ATmega32U4__)
      inline static void initSS()    { DDRB  |=  _BV(6); };
      inline static void setSS()     { PORTB &= ~_BV(6); };
      inline static void resetSS()   { PORTB |=  _BV(6); };
    #elif defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB162__)
      inline static void initSS()    { DDRB  |=  _BV(0); };
      inline static void setSS()     { PORTB &= ~_BV(0); };
      inline static void resetSS()   { PORTB |=  _BV(0); };
    #else
      inline static void initSS()    { DDRB  |=  _BV(2); };
      inline static void setSS()     { PORTB &= ~_BV(2); };
      inline static void resetSS()   { PORTB |=  _BV(2); };
    #endif
  #elif defined(__ARDUINO_ARC__)
	inline static void initSS() { pinMode(10, OUTPUT); };
	inline static void setSS() { digitalWrite(10, LOW); };
	inline static void resetSS() { digitalWrite(10, HIGH); };
  #else
    inline static void initSS() {
      *portModeRegister(digitalPinToPort(ETHERNET_SHIELD_SPI_CS)) |= digitalPinToBitMask(ETHERNET_SHIELD_SPI_CS);
    }
    inline static void setSS()   {
      *portOutputRegister(digitalPinToPort(ETHERNET_SHIELD_SPI_CS)) &= ~digitalPinToBitMask(ETHERNET_SHIELD_SPI_CS);
    }
    inline static void resetSS() {
      *portOutputRegister(digitalPinToPort(ETHERNET_SHIELD_SPI_CS)) |= digitalPinToBitMask(ETHERNET_SHIELD_SPI_CS);
    }
  #endif
#else
  #define SPI_ETHERNET_SETTINGS ETHERNET_SHIELD_SPI_CS,SPISettings(4000000, MSBFIRST, SPI_MODE0)
  // initSS(), setSS(), resetSS() not needed with EXTENDED_CS_PIN_HANDLING
#endif
};

extern W5100Class W5100;

uint8_t W5100Class::readSn(SOCKET _s, uint16_t _addr) {
  return read(CH_BASE + _s * CH_SIZE + _addr);
}

uint8_t W5100Class::writeSn(SOCKET _s, uint16_t _addr, uint8_t _data) {
  return write(CH_BASE + _s * CH_SIZE + _addr, _data);
}

uint16_t W5100Class::readSn(SOCKET _s, uint16_t _addr, uint8_t *_buf, uint16_t _len) {
  return read(CH_BASE + _s * CH_SIZE + _addr, _buf, _len);
}

uint16_t W5100Class::writeSn(SOCKET _s, uint16_t _addr, uint8_t *_buf, uint16_t _len) {
  return write(CH_BASE + _s * CH_SIZE + _addr, _buf, _len);
}

void W5100Class::getGatewayIp(uint8_t *_addr) {
  readGAR(_addr);
}

void W5100Class::setGatewayIp(uint8_t *_addr) {
  writeGAR(_addr);
}

void W5100Class::getSubnetMask(uint8_t *_addr) {
  readSUBR(_addr);
}

void W5100Class::setSubnetMask(uint8_t *_addr) {
  writeSUBR(_addr);
}

void W5100Class::getMACAddress(uint8_t *_addr) {
  readSHAR(_addr);
}

void W5100Class::setMACAddress(uint8_t *_addr) {
  writeSHAR(_addr);
}

void W5100Class::getIPAddress(uint8_t *_addr) {
  readSIPR(_addr);
}

void W5100Class::setIPAddress(uint8_t *_addr) {
  writeSIPR(_addr);
}

void W5100Class::setRetransmissionTime(uint16_t _timeout) {
  writeRTR(_timeout);
}

void W5100Class::setRetransmissionCount(uint8_t _retry) {
  writeRCR(_retry);
}

#endif

Принимает несколько пакетов и прекращает.
 

sadman41
Offline
Зарегистрирован: 19.10.2016

X-WL пишет:

Как правильно нужно производить эту конфигурацию. В файле W5100.cpp изменил значения на 0xAA, там же TX_RX_MAX_BUF_SIZE на 4096

...

В W5100.h SOKETS с 4х на 2, MAX_SOCK_NUM на 2

...



Принимает несколько пакетов и прекращает.

Я бы тоже с этого начал. На самом деле там, в либе, может быть еще какой-нить хардкод спрятан.

И я, бы, пожалуй, взял дрова с Wiznet-овского гитхаба. Всё же ближе к производителю. Может пофикшено что-нить.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Ещё добавлю, что реализация штатной библиотеки Ethernet - не айс, посмотрите в исходниках на EthernetServer::available - там проблема с выбором сокета давнишняя (может, щас уже пофиксили, хз). Если вкратце - то при интенсивных коннектах клиентами выбирается клиент с наименьшим номером сокета, а тот, который хоть и пришёл раньше, но имеет бОльший номер - курит бамбук, пока не обработаются челики с головы состава.

Это одна из причин, по которой в своё время отказался от Ethernet в проектах. Вторая причина - ESP ;)

X-WL
Offline
Зарегистрирован: 29.01.2018

Хорошо, попробую переписать под оригинальную библиотеку. Возможно это решит проблему со скоростью обработки пакета.

sadman41
Offline
Зарегистрирован: 19.10.2016

Нашел время, поэкспериментировал с W5100. До 8кб сокет тянется, но в библиотеке есть хардкод на 4 сокета по 2Kb. Вроде как я его весь обнаружил, на TCP работает, на UDP не проверял.

Так что если еще актуально - пишите, расскажу.

X-WL
Offline
Зарегистрирован: 29.01.2018

sadman41 пишет:

Нашел время, поэкспериментировал с W5100. До 8кб сокет тянется, но в библиотеке есть хардкод на 4 сокета по 2Kb. Вроде как я его весь обнаружил, на TCP работает, на UDP не проверял.

Так что если еще актуально - пишите, расскажу.

Хорошая новость! Актуально! Еще бы на UDP заработало:))

sadman41
Offline
Зарегистрирован: 19.10.2016

У меня свой форк библиотеки, который я вычищаю сейчас (в стоковой куча всякой странной ерунды болтается) и в котором делаю автомат. вычисления адресов/значений регистров, поэтому опишу в целом.

w5100.cpp:

// нигде фактически не используется, править нет смысла

#define TX_RX_MAX_BUF_SIZE 2048

// Ставим 0xAA для 2 x 4Kb , 0x03 для 1 x 8Kb
 writeTMSR(0x55);
 writeRMSR(0x55);

То, что закомментировано - было в изначальном варианте, незакомментировано - мои исправления

w5100.h
 
// Тут задаем кол-во активных сокетов
#define MAX_SOCK_NUM 4

// Добавляем для простоты вычислений
// RX & TX buffer sizes for all sockets: 8Kb / number of sockets
#define TXBUF_SIZE         (0x2000 / MAX_SOCK_NUM)
#define RXBUF_SIZE         (0x2000 / MAX_SOCK_NUM)

// SOCKETS используется в одном месте, чуть ниже, особого смысла в переменной нет, можно заменить ее на MAX_SOCK_NUM
//  static const int SOCKETS = 4;
//  static const uint16_t SMASK = 0x07FF; // Tx buffer MASK
//  static const uint16_t RMASK = 0x07FF; // Rx buffer MASK
  static const uint16_t SMASK = TXBUF_SIZE - 1; // Tx buffer MASK
  static const uint16_t RMASK = RXBUF_SIZE - 1; // Rx buffer MASK

//  static const uint16_t SSIZE = 2048; // Max Tx buffer size
//  static const uint16_t RSIZE = 2048; // Max Rx buffer size
  static const uint16_t SSIZE = TXBUF_SIZE; // Max Tx buffer size
  static const uint16_t RSIZE = TXBUF_SIZE; // Max Rx buffer size

//  uint16_t SBASE[SOCKETS]; // Tx buffer base address
//  uint16_t RBASE[SOCKETS]; // Rx buffer base address
  uint16_t SBASE[MAX_SOCK_NUM]; // Tx buffer base address
  uint16_t RBASE[MAX_SOCK_NUM]; // Rx buffer base address

Проверял так

1) С линукса в сокет писал любой мусор пачкой соотв. размера, на стороне ардуины client.available() выводил в сериал монитор.

2) На стороне linux писал строку в сокет / ждал ответа от ардуины.

Оба варианта при тестировании проблем не показали.

 

X-WL
Offline
Зарегистрирован: 29.01.2018

sadman41 пишет:

У меня свой форк библиотеки, который я вычищаю сейчас (в стоковой куча всякой странной ерунды болтается) и в котором делаю автомат. вычисления адресов/значений регистров, поэтому опишу в целом.

w5100.cpp:

// нигде фактически не используется, править нет смысла

#define TX_RX_MAX_BUF_SIZE 2048

// Ставим 0xAA для 2 x 4Kb , 0x03 для 1 x 8Kb
 writeTMSR(0x55);
 writeRMSR(0x55);

То, что закомментировано - было в изначальном варианте, незакомментировано - мои исправления

w5100.h
 
// Тут задаем кол-во активных сокетов
#define MAX_SOCK_NUM 4

// Добавляем для простоты вычислений
// RX & TX buffer sizes for all sockets: 8Kb / number of sockets
#define TXBUF_SIZE         (0x2000 / MAX_SOCK_NUM)
#define RXBUF_SIZE         (0x2000 / MAX_SOCK_NUM)

// SOCKETS используется в одном месте, чуть ниже, особого смысла в переменной нет, можно заменить ее на MAX_SOCK_NUM
//  static const int SOCKETS = 4;
//  static const uint16_t SMASK = 0x07FF; // Tx buffer MASK
//  static const uint16_t RMASK = 0x07FF; // Rx buffer MASK
  static const uint16_t SMASK = TXBUF_SIZE - 1; // Tx buffer MASK
  static const uint16_t RMASK = RXBUF_SIZE - 1; // Rx buffer MASK

//  static const uint16_t SSIZE = 2048; // Max Tx buffer size
//  static const uint16_t RSIZE = 2048; // Max Rx buffer size
  static const uint16_t SSIZE = TXBUF_SIZE; // Max Tx buffer size
  static const uint16_t RSIZE = TXBUF_SIZE; // Max Rx buffer size

//  uint16_t SBASE[SOCKETS]; // Tx buffer base address
//  uint16_t RBASE[SOCKETS]; // Rx buffer base address
  uint16_t SBASE[MAX_SOCK_NUM]; // Tx buffer base address
  uint16_t RBASE[MAX_SOCK_NUM]; // Rx buffer base address

Проверял так

1) С линукса в сокет писал любой мусор пачкой соотв. размера, на стороне ардуины client.available() выводил в сериал монитор.

2) На стороне linux писал строку в сокет / ждал ответа от ардуины.

Оба варианта при тестировании проблем не показали.

Понял, как будет доступ к плате протестирую. Спасибо, надеюсь поможет)

X-WL
Offline
Зарегистрирован: 29.01.2018

Приветствую!
Опробовал ваш вариант библиотеки. Все работает. Удается ловить и парсить по +-145 пакетов в секунду. Выходит по 6-7мс на 1 пакет(Udp.parse()+пара if на проверку принадлежности к Артнет). Как я понимаю скорость обработки уже упирается в саму шилду. Возможно ли будет дополнительно как-то повысить скорость обработки пакетов?

sadman41
Offline
Зарегистрирован: 19.10.2016

Я нашел такой документ: https://github.com/manitou48/DUEZoo/blob/master/wizperf.txt

Не знаю, насколько он правдив, на него ссылаются тут: https://forum.arduino.cc/index.php?topic=138228.0

В нем написано, что "wiznet claims only 0.3mbs for W5100, 33.3mbs for W5200" , так же там есть какие-то цифры, но я так и не понял по какой методике это считалось.

У вас же поток, если я правильно понимаю, 145*572=82,940 килобайта/сек, что составляет ~0.65mbs, что явно больше 0.3mb.

Поэтому даже и не знаю - можно ли больше выжать или нет. Я не пытался, необходимости не было. Возможно, что выцыганить перфоманса можно переписав часть библиотеки - там есть странные моменты. Может на обработку пакетов по прерыванию перейти... тогда меньше будет шансов потерять часть пакетов. Но это чисто теоретические соображения - нужно серъёзно поковыряться, потестить модель на стенде.

Вот еще тред: https://forum.pjrc.com/threads/45236-Accelerating-Arduino-Ethernet-Speed

Но суть, как я понимаю, сводится к следующему: берите МК с DMA, сетевой чип с Burst-mode и копируйте с него пачкой, а не побайтово, открывая каждый раз SPI-транзакцию, как в случае с W5100.

X-WL
Offline
Зарегистрирован: 29.01.2018

sadman41 пишет:

Я нашел такой документ: https://github.com/manitou48/DUEZoo/blob/master/wizperf.txt

Не знаю, насколько он правдив, на него ссылаются тут: https://forum.arduino.cc/index.php?topic=138228.0

В нем написано, что "wiznet claims only 0.3mbs for W5100, 33.3mbs for W5200" , так же там есть какие-то цифры, но я так и не понял по какой методике это считалось.

У вас же поток, если я правильно понимаю, 145*572=82,940 килобайта/сек, что составляет ~0.65mbs, что явно больше 0.3mb.

Поэтому даже и не знаю - можно ли больше выжать или нет. Я не пытался, необходимости не было. Возможно, что выцыганить перфоманса можно переписав часть библиотеки - там есть странные моменты. Может на обработку пакетов по прерыванию перейти... тогда меньше будет шансов потерять часть пакетов. Но это чисто теоретические соображения - нужно серъёзно поковыряться, потестить модель на стенде.

Вот еще тред: https://forum.pjrc.com/threads/45236-Accelerating-Arduino-Ethernet-Speed

Но суть, как я понимаю, сводится к следующему: берите МК с DMA, сетевой чип с Burst-mode и копируйте с него пачкой, а не побайтово, открывая каждый раз SPI-транзакцию, как в случае с W5100.

Я так понимаю можно для этих целей использовать шилду Ethernet 2(на W5200)? Будет в 2 раза больше памяти(можно принять будет еще больше пакетов) и скорость достаточная. И Плата с DMA есть, но опыта работы с ним нет. Что можно почитать на эту тему?

sadman41
Offline
Зарегистрирован: 19.10.2016

X-WL пишет:

Я так понимаю можно для этих целей использовать шилду Ethernet 2(на W5200)? Будет в 2 раза больше памяти(можно принять будет еще больше пакетов) и скорость достаточная. И Плата с DMA есть, но опыта работы с ним нет. Что можно почитать на эту тему?

Тут уже можно и запутаться.

Стоковый ардуинный Ethernet2.h поддерживает чип W5500. Родной визнетовский драйвер поддерживает и W5100 и W5200 и W5500 в одной билиотеке. 

На 5200 мне шилды не попадались, а вот на W5500 доступны по сходной цене. Но у этого чипа (или библиотеки - я пока не понял, но таки склоняюсь к своеобразной работе чипа) есть другая болезнь - он начинает резво реджектить все входящие коннекты, если сокеты заняты. Для моих поделок это проблема, поэтому шилд лежит на полочке.

Что почитать насчет DMA - я не знаю. У меня таких потребностей не было. Вы поставили себе задачу уровнем повыше, чем решают широкие массы, поэтому расписанных алгоритмов действий вы навряди найдете. Нужно сидеть, втыкать, анализировать код драйверов чипов и т.д.