mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-02-17 06:29:15 +01:00
Network support
This commit is contained in:
parent
8e6c232d05
commit
6a1ffdb3fa
52
EtherCard.cpp
Normal file
52
EtherCard.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
// This code slightly follows the conventions of, but is not derived from:
|
||||
// EHTERSHIELD_H library for Arduino etherShield
|
||||
// Copyright (c) 2008 Xing Yu. All right reserved. (this is LGPL v2.1)
|
||||
// It is however derived from the enc28j60 and ip code (which is GPL v2)
|
||||
// Author: Pascal Stang
|
||||
// Modified by: Guido Socher
|
||||
// DHCP code: Andrew Lindsay
|
||||
// Hence: GPL V2
|
||||
//
|
||||
// 2010-05-19 <jc@wippler.nl>
|
||||
|
||||
#include "EtherCard.h"
|
||||
#include <stdarg.h>
|
||||
#include <avr/eeprom.h>
|
||||
|
||||
EtherCard ether;
|
||||
|
||||
uint8_t EtherCard::mymac[ETH_LEN]; // my MAC address
|
||||
uint8_t EtherCard::myip[IP_LEN]; // my ip address
|
||||
uint8_t EtherCard::netmask[IP_LEN]; // subnet mask
|
||||
uint8_t EtherCard::broadcastip[IP_LEN]; // broadcast address
|
||||
uint8_t EtherCard::gwip[IP_LEN]; // gateway
|
||||
uint8_t EtherCard::dhcpip[IP_LEN]; // dhcp server
|
||||
uint8_t EtherCard::dnsip[IP_LEN]; // dns server
|
||||
uint8_t EtherCard::hisip[IP_LEN]; // ip address of remote host
|
||||
|
||||
uint16_t EtherCard::delaycnt = 0; //request gateway ARP lookup
|
||||
|
||||
uint8_t EtherCard::begin (const uint16_t size,
|
||||
const uint8_t* macaddr,
|
||||
uint8_t csPin) {
|
||||
copyMac(mymac, macaddr);
|
||||
return initialize(size, mymac, csPin);
|
||||
}
|
||||
|
||||
bool EtherCard::staticSetup (const uint8_t* my_ip,
|
||||
const uint8_t* gw_ip,
|
||||
const uint8_t* dns_ip,
|
||||
const uint8_t* mask) {
|
||||
if (my_ip != 0)
|
||||
copyIp(myip, my_ip);
|
||||
if (gw_ip != 0)
|
||||
setGwIp(gw_ip);
|
||||
if (dns_ip != 0)
|
||||
copyIp(dnsip, dns_ip);
|
||||
if(mask != 0)
|
||||
copyIp(netmask, mask);
|
||||
updateBroadcastAddress();
|
||||
delaycnt = 0; //request gateway ARP lookup
|
||||
return true;
|
||||
}
|
||||
|
238
EtherCard.h
Normal file
238
EtherCard.h
Normal file
@ -0,0 +1,238 @@
|
||||
// This code slightly follows the conventions of, but is not derived from:
|
||||
// EHTERSHIELD_H library for Arduino etherShield
|
||||
// Copyright (c) 2008 Xing Yu. All right reserved. (this is LGPL v2.1)
|
||||
// It is however derived from the enc28j60 and ip code (which is GPL v2)
|
||||
// Author: Pascal Stang
|
||||
// Modified by: Guido Socher
|
||||
// DHCP code: Andrew Lindsay
|
||||
// Hence: GPL V2
|
||||
//
|
||||
// 2010-05-19 <jc@wippler.nl>
|
||||
//
|
||||
//
|
||||
// PIN Connections (Using Arduino UNO):
|
||||
// VCC - 3.3V
|
||||
// GND - GND
|
||||
// SCK - Pin 13
|
||||
// SO - Pin 12
|
||||
// SI - Pin 11
|
||||
// CS - Pin 8
|
||||
//
|
||||
/** @file */
|
||||
|
||||
#ifndef EtherCard_h
|
||||
#define EtherCard_h
|
||||
// #ifndef __PROG_TYPES_COMPAT__
|
||||
// #define __PROG_TYPES_COMPAT__
|
||||
// #endif
|
||||
|
||||
// #if ARDUINO >= 100
|
||||
#include <Arduino.h> // Arduino 1.0
|
||||
// #define WRITE_RESULT size_t
|
||||
// #define WRITE_RETURN return 1;
|
||||
// #else
|
||||
// #include <WProgram.h> // Arduino 0022
|
||||
// #define WRITE_RESULT void
|
||||
// #define WRITE_RETURN
|
||||
// #endif
|
||||
|
||||
// #include <avr/pgmspace.h>
|
||||
#include "enc28j60.h"
|
||||
|
||||
// Based on the net.h file from the AVRlib library by Pascal Stang.
|
||||
// Author: Guido Socher
|
||||
// Copyright: GPL V2
|
||||
//
|
||||
// For AVRlib See http://www.procyonengineering.com/
|
||||
// Used with explicit permission of Pascal Stang.
|
||||
//
|
||||
// 2010-05-20 <jc@wippler.nl>
|
||||
|
||||
// notation: _P = position of a field
|
||||
// _V = value of a field
|
||||
|
||||
// ******* ETH *******
|
||||
#define ETH_HEADER_LEN 14
|
||||
#define ETH_LEN 6
|
||||
// values of certain bytes:
|
||||
#define ETHTYPE_ARP_H_V 0x08
|
||||
#define ETHTYPE_ARP_L_V 0x06
|
||||
#define ETHTYPE_IP_H_V 0x08
|
||||
#define ETHTYPE_IP_L_V 0x00
|
||||
// byte positions in the ethernet frame:
|
||||
//
|
||||
// Ethernet type field (2bytes):
|
||||
#define ETH_TYPE_H_P 12
|
||||
#define ETH_TYPE_L_P 13
|
||||
//
|
||||
#define ETH_DST_MAC 0
|
||||
#define ETH_SRC_MAC 6
|
||||
|
||||
|
||||
// ******* ARP *******
|
||||
#define ETH_ARP_OPCODE_REPLY_H_V 0x0
|
||||
#define ETH_ARP_OPCODE_REPLY_L_V 0x02
|
||||
#define ETH_ARP_OPCODE_REQ_H_V 0x0
|
||||
#define ETH_ARP_OPCODE_REQ_L_V 0x01
|
||||
// start of arp header:
|
||||
#define ETH_ARP_P 0xe
|
||||
//
|
||||
#define ETHTYPE_ARP_L_V 0x06
|
||||
// arp.dst.ip
|
||||
#define ETH_ARP_DST_IP_P 0x26
|
||||
// arp.opcode
|
||||
#define ETH_ARP_OPCODE_H_P 0x14
|
||||
#define ETH_ARP_OPCODE_L_P 0x15
|
||||
// arp.src.mac
|
||||
#define ETH_ARP_SRC_MAC_P 0x16
|
||||
#define ETH_ARP_SRC_IP_P 0x1c
|
||||
#define ETH_ARP_DST_MAC_P 0x20
|
||||
#define ETH_ARP_DST_IP_P 0x26
|
||||
|
||||
// ******* IP *******
|
||||
#define IP_HEADER_LEN 20
|
||||
#define IP_LEN 4
|
||||
// ip.src
|
||||
#define IP_SRC_P 0x1a
|
||||
#define IP_DST_P 0x1e
|
||||
#define IP_HEADER_LEN_VER_P 0xe
|
||||
#define IP_CHECKSUM_P 0x18
|
||||
#define IP_TTL_P 0x16
|
||||
#define IP_FLAGS_P 0x14
|
||||
#define IP_P 0xe
|
||||
#define IP_TOTLEN_H_P 0x10
|
||||
#define IP_TOTLEN_L_P 0x11
|
||||
|
||||
#define IP_PROTO_P 0x17
|
||||
|
||||
#define IP_PROTO_ICMP_V 1
|
||||
#define IP_PROTO_TCP_V 6
|
||||
// 17=0x11
|
||||
#define IP_PROTO_UDP_V 17
|
||||
// ******* ICMP *******
|
||||
#define ICMP_TYPE_ECHOREPLY_V 0
|
||||
#define ICMP_TYPE_ECHOREQUEST_V 8
|
||||
//
|
||||
#define ICMP_TYPE_P 0x22
|
||||
#define ICMP_CHECKSUM_P 0x24
|
||||
#define ICMP_CHECKSUM_H_P 0x24
|
||||
#define ICMP_CHECKSUM_L_P 0x25
|
||||
#define ICMP_IDENT_H_P 0x26
|
||||
#define ICMP_IDENT_L_P 0x27
|
||||
#define ICMP_DATA_P 0x2a
|
||||
|
||||
// ******* UDP *******
|
||||
#define UDP_HEADER_LEN 8
|
||||
//
|
||||
#define UDP_SRC_PORT_H_P 0x22
|
||||
#define UDP_SRC_PORT_L_P 0x23
|
||||
#define UDP_DST_PORT_H_P 0x24
|
||||
#define UDP_DST_PORT_L_P 0x25
|
||||
//
|
||||
#define UDP_LEN_H_P 0x26
|
||||
#define UDP_LEN_L_P 0x27
|
||||
#define UDP_CHECKSUM_H_P 0x28
|
||||
#define UDP_CHECKSUM_L_P 0x29
|
||||
#define UDP_DATA_P 0x2a
|
||||
|
||||
|
||||
/** Enable DHCP.
|
||||
* Setting this to zero disables the use of DHCP; if a program uses DHCP it will
|
||||
* still compile but the program will not work. Saves about 60 bytes SRAM and
|
||||
* 1550 bytes flash.
|
||||
*/
|
||||
#define ETHERCARD_DHCP 0
|
||||
|
||||
/** Enable client connections.
|
||||
* Setting this to zero means that the program cannot issue TCP client requests
|
||||
* anymore. Compilation will still work but the request will never be
|
||||
* issued. Saves 4 bytes SRAM and 550 byte flash.
|
||||
*/
|
||||
#define ETHERCARD_TCPCLIENT 0
|
||||
|
||||
/** Enable TCP server functionality.
|
||||
* Setting this to zero means that the program will not accept TCP client
|
||||
* requests. Saves 2 bytes SRAM and 250 bytes flash.
|
||||
*/
|
||||
#define ETHERCARD_TCPSERVER 0
|
||||
|
||||
/** Enable UDP server functionality.
|
||||
* If zero UDP server is disabled. It is
|
||||
* still possible to register callbacks but these will never be called. Saves
|
||||
* about 40 bytes SRAM and 200 bytes flash. If building with -flto this does not
|
||||
* seem to save anything; maybe the linker is then smart enough to optimize the
|
||||
* call away.
|
||||
*/
|
||||
#define ETHERCARD_UDPSERVER 0
|
||||
|
||||
/** Enable automatic reply to pings.
|
||||
* Setting to zero means that the program will not automatically answer to
|
||||
* PINGs anymore. Also the callback that can be registered to answer incoming
|
||||
* pings will not be called. Saves 2 bytes SRAM and 230 bytes flash.
|
||||
*/
|
||||
#define ETHERCARD_ICMP 1
|
||||
|
||||
/** Enable use of stash.
|
||||
* Setting this to zero means that the stash mechanism cannot be used. Again
|
||||
* compilation will still work but the program may behave very unexpectedly.
|
||||
* Saves 30 bytes SRAM and 80 bytes flash.
|
||||
*/
|
||||
#define ETHERCARD_STASH 0
|
||||
|
||||
|
||||
/** This type definition defines the structure of a UDP server event handler callback function */
|
||||
typedef void (*UdpServerCallback)(
|
||||
uint16_t dest_port, ///< Port the packet was sent to
|
||||
uint8_t src_ip[IP_LEN], ///< IP address of the sender
|
||||
uint16_t src_port, ///< Port the packet was sent from
|
||||
const char *data, ///< UDP payload data
|
||||
uint16_t len); ///< Length of the payload data
|
||||
|
||||
/** This type definition defines the structure of a DHCP Option callback function */
|
||||
typedef void (*DhcpOptionCallback)(
|
||||
uint8_t option, ///< The option number
|
||||
const byte* data, ///< DHCP option data
|
||||
uint8_t len); ///< Length of the DHCP option data
|
||||
|
||||
|
||||
|
||||
/** This class provides the main interface to a ENC28J60 based network interface card and is the class most users will use.
|
||||
* @note All TCP/IP client (outgoing) connections are made from source port in range 2816-3071. Do not use these source ports for other purposes.
|
||||
*/
|
||||
class EtherCard : public ENC28J60 {
|
||||
public:
|
||||
static uint8_t mymac[ETH_LEN]; ///< MAC address
|
||||
static uint8_t myip[IP_LEN]; ///< IP address
|
||||
static uint8_t netmask[IP_LEN]; ///< Netmask
|
||||
static uint8_t broadcastip[IP_LEN]; ///< Subnet broadcast address
|
||||
static uint8_t gwip[IP_LEN]; ///< Gateway
|
||||
static uint8_t dhcpip[IP_LEN]; ///< DHCP server IP address
|
||||
static uint8_t dnsip[IP_LEN]; ///< DNS server IP address
|
||||
static uint8_t hisip[IP_LEN]; ///< DNS lookup result
|
||||
static uint16_t hisport; ///< TCP port to connect to (default 80)
|
||||
static bool using_dhcp; ///< True if using DHCP
|
||||
static bool persist_tcp_connection; ///< False to break connections on first packet received
|
||||
static uint16_t delaycnt; ///< Counts number of cycles of packetLoop when no packet received - used to trigger periodic gateway ARP request
|
||||
|
||||
static uint8_t begin (const uint16_t size, const uint8_t* macaddr,
|
||||
uint8_t csPin = SS);
|
||||
static bool staticSetup (const uint8_t* my_ip,
|
||||
const uint8_t* gw_ip = 0,
|
||||
const uint8_t* dns_ip = 0,
|
||||
const uint8_t* mask = 0);
|
||||
static void makeUdpReply (const char *data, uint8_t len, uint16_t port);
|
||||
static uint16_t packetLoop (uint16_t plen);
|
||||
static void setGwIp (const uint8_t *gwipaddr);
|
||||
static void updateBroadcastAddress();
|
||||
static uint8_t clientWaitingGw ();
|
||||
static void udpPrepare (uint16_t sport, const uint8_t *dip, uint16_t dport);
|
||||
static void udpTransmit (uint16_t len);
|
||||
static void sendUdp (const char *data, uint8_t len, uint16_t sport,
|
||||
const uint8_t *dip, uint16_t dport);
|
||||
static void copyIp (uint8_t *dst, const uint8_t *src);
|
||||
static void copyMac (uint8_t *dst, const uint8_t *src);
|
||||
};
|
||||
|
||||
extern EtherCard ether; //!< Global presentation of EtherCard class
|
||||
|
||||
#endif
|
473
IO_Network.h
Normal file
473
IO_Network.h
Normal file
@ -0,0 +1,473 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Each node on the network is configured with a node number in the range 0-254.
|
||||
* The remoting configuration defines, for each pin to be available remotely,
|
||||
* the node number and the VPIN number on that node. The configuration must
|
||||
* match in all nodes, since it is used by the sending node to identify the node
|
||||
* and VPIN to which a write command is to be sent, and the VPIN number for a
|
||||
* sensor/input, and on the receiving node to identify the node from which a
|
||||
* sensor/input value is being sourced.
|
||||
*
|
||||
* The node number is also used in the network driver's address
|
||||
* field. Number 255 is treated as a multicast address. All stations listen on
|
||||
* their own address and on the multicast address.
|
||||
*
|
||||
* All nodes send regular multicast packets containing the latest values of the
|
||||
* sensors as they know them. On receipt of such a packet, each node extracts
|
||||
* the states of the sensors which are sourced by the originating node, and
|
||||
* updates the values in its own local data. Thus, each node has a copy of the
|
||||
* states of all digital input pin values that are defined in the remoting
|
||||
* configuration. Multicasts are sent frequently, so if one is missed
|
||||
* then, like a London bus, another will be along shortly.
|
||||
*
|
||||
* Commands (originating from write() or writeAnalogue() calls) are sent
|
||||
* immediately, directly from the originating node to the target node. This
|
||||
* is done with acknowlegements enabled to maximise the probability of
|
||||
* successful delivery.
|
||||
*
|
||||
* Usage:
|
||||
* First declare, for each remote pin in the common area, the mapping onto
|
||||
* a node and VPIN number. The array below assumes that the first remote
|
||||
* VPIN is 4000. The REMOTEPINS definition
|
||||
* should be the same on all nodes in the network. For outputs, it is the
|
||||
* definition in the sending node that dictates which node and VPIN the
|
||||
* action is performed on. For inputs, the value is placed into the
|
||||
* VPIN location defined in the sending node (that scans the input value),
|
||||
* but the value is only accepted in the receiving node if its definition
|
||||
* shows that the signal originates in the sending node.
|
||||
*
|
||||
* Example to go into mySetup() function in mySetup.cpp:
|
||||
* REMOTEPINS rpins[] = {
|
||||
* {0,30,RPIN_OUT}, //4000 Node 0 GPIO pin 30 (output)
|
||||
* {1,30,RPIN_IN}, //4001 Node 1 GPIO pin 30 (input)
|
||||
* {1,100,RPIN_INOUT}, //4002 Node 1 Servo (PCA9685) pin (output to servo, input busy flag)
|
||||
* {1,164,RPIN_IN}, //4003 Node 1 GPIO extender (MCP23017) pin (input)
|
||||
* {2,164,RPIN_IN} //4004 Node 2 GPIO extender (MCP23017) pin (input)
|
||||
* }
|
||||
* // FirstVPIN, nPins, thisNode, pinDefs, CEPin, CSNPin
|
||||
* Network::create(4000, NUMREMOTEPINS(rpins), 0, rpins, new RF24Driver(48, 49));
|
||||
*
|
||||
* This example defines VPINs 4000-4004 which map onto pins on nodes 0, 1 and 2.
|
||||
* The network device in this case is an nRF24L01, which has to be connected to the hardware
|
||||
* MISO, MOSI, SCK and CS pins of the microcontroller; in addition, the CE and
|
||||
* CSN pins on the nRF24 are connected to two pins (48 and 49 above).
|
||||
*
|
||||
* If any of pins 4000-4004 are referenced by turnouts, outputs or sensors, or by EX-RAIL,
|
||||
* then the corresponding remote pin state will be retrieved or updated.
|
||||
* For example, in EX-RAIL,
|
||||
* SET(4000) on node 1 or 2 will set pin 30 on Node 0 to +5V (pin is put into output mode on first write).
|
||||
* AT(4001) on node 0 or 2 will wait until the sensor attached to pin 30 on Node 1 activates.
|
||||
* SERVO(4002,300,2) on node 0 or 2 will reposition the servo on Node 1 PCA9685 module to position 300, and
|
||||
* AT(-4002) will wait until the servo has finished moving.
|
||||
*
|
||||
* The following sensor definition on node 0 will map onto VPIN 4004, i.e. Node 2 VPIN 164,
|
||||
* which is the first pin on the first MCP23017:
|
||||
* <S 1 4004 0>
|
||||
* and when a sensor attached to the pin on node 2 is activated (pin pulled down to 0V) the following
|
||||
* message will be generated on node 0:
|
||||
* <Q 1>
|
||||
* When the sensor deactivates, the following message will be generated on node 0:
|
||||
* <q 1>
|
||||
*/
|
||||
|
||||
#ifndef IO_NETWORK_H
|
||||
#define IO_NETWORK_H
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "RF24.h"
|
||||
|
||||
// Macros and type for creating the remote pin definitions.
|
||||
// The definitions are stored in PROGMEM to reduce RAM requirements.
|
||||
// The flags byte contains, in the low 2 bits, RPIN_IN, RPIN_OUT or RPIN_INOUT.
|
||||
typedef struct { uint8_t node; VPIN vpin; uint8_t flags; } RPIN;
|
||||
#define REMOTEPINS static const RPIN PROGMEM
|
||||
#define NUMREMOTEPINS(x) (sizeof(x)/sizeof(RPIN))
|
||||
enum {
|
||||
RPIN_IN=1,
|
||||
RPIN_OUT=2,
|
||||
RPIN_INOUT=RPIN_IN|RPIN_OUT,
|
||||
};
|
||||
|
||||
// Define interface for network driver. This should be implemented for each supported
|
||||
// network type.
|
||||
// class NetInterface {
|
||||
// public:
|
||||
// bool begin();
|
||||
// bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t size);
|
||||
// bool available();
|
||||
// uint8_t read(uint8_t buffer[], uint8_t size);
|
||||
// void loop();
|
||||
// };
|
||||
|
||||
// Class implementing the Application-layer network functionality.
|
||||
// This is implemented as an IODevice instance so it can be easily
|
||||
// plugged in to the HAL framwork.
|
||||
template <class NetInterface>
|
||||
class Network : public IODevice {
|
||||
|
||||
private:
|
||||
const RPIN *_pinDefs; // May need to become a far pointer!
|
||||
// Time of last loop execution
|
||||
unsigned long _lastExecutionTime;
|
||||
// Current digital values for remoted pins, stored as a bit field
|
||||
uint8_t *_pinValues;
|
||||
// Number of the current node (1-254)
|
||||
uint8_t _thisNode;
|
||||
// Maximum size of payload (must be 32 or less for RF24)
|
||||
static const uint8_t maxPayloadSize = 32;
|
||||
bool _updatePending;
|
||||
int _nextSendPin;
|
||||
unsigned long _lastMulticastTime;
|
||||
int _firstPinToSend; // must be a multiple of 8
|
||||
int _numPinsToSend; // need not be a multiple of 8
|
||||
NetInterface *_netDriver;
|
||||
|
||||
// List of network commands
|
||||
enum : uint8_t {
|
||||
NET_CMD_WRITE = 0,
|
||||
NET_CMD_WRITEANALOGUE = 1,
|
||||
NET_CMD_VALUEUPDATE = 2,
|
||||
};
|
||||
|
||||
// Field Positions in Network Header
|
||||
enum NetHeader {
|
||||
IONET_SENDNODE = 0, // for VALUEUPDATE
|
||||
IONET_DESTNODE = 0, // for WRITE/WRITEANALOGUE
|
||||
IONET_CMDTYPE = 1,
|
||||
IONET_VPIN = 2,
|
||||
IONET_VPIN_H = 2,
|
||||
IONET_VPIN_L = 3,
|
||||
IONET_DATA = 4,
|
||||
};
|
||||
|
||||
public:
|
||||
// Constructor performs static initialisation of the device object
|
||||
Network (VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], NetInterface *netDriver) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_thisNode = thisNode;
|
||||
_pinDefs = pinDefs;
|
||||
_pinValues = (uint8_t *)calloc((nPins+7)/8, 1); // Allocate space for input values.
|
||||
_netDriver = netDriver;
|
||||
addDevice(this);
|
||||
|
||||
// Identify which pins are allocated to this node.
|
||||
_firstPinToSend = -1;
|
||||
int lastPinToSend = 0;
|
||||
for (int pin=0; pin<_nPins; pin++) {
|
||||
uint8_t node = GETFLASH(&_pinDefs[pin].node);
|
||||
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
|
||||
// Check if the pin is an input on this node?
|
||||
if (node == _thisNode && (flags & RPIN_IN)) {
|
||||
if (_firstPinToSend==-1) _firstPinToSend = pin;
|
||||
lastPinToSend = pin;
|
||||
}
|
||||
//DIAG(F("Node=%d FirstPin=%d, NumPins=%d"), node, _firstPinToSend, _numPinsToSend);
|
||||
}
|
||||
// Round down to multiple of 8 (byte boundary).
|
||||
_firstPinToSend /= 8;
|
||||
_firstPinToSend *= 8;
|
||||
_numPinsToSend = lastPinToSend - _firstPinToSend + 1;
|
||||
// Restrict to the max that fit in a packet
|
||||
_numPinsToSend = min(8*(MAX_MSG_SIZE-IONET_DATA),_numPinsToSend);
|
||||
//DIAG(F("FirstPin=%d, NumPins=%d"), _firstPinToSend, _numPinsToSend);
|
||||
|
||||
// Prepare for first transmission
|
||||
_nextSendPin = _firstPinToSend;
|
||||
}
|
||||
|
||||
// Static create function provides alternative way to create object
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], NetInterface *netDriver) {
|
||||
new Network(firstVpin, nPins, thisNode, pinDefs, netDriver);
|
||||
}
|
||||
|
||||
protected:
|
||||
// _begin function called to perform dynamic initialisation of the device
|
||||
void _begin() override {
|
||||
if (_netDriver->begin(_thisNode)) {
|
||||
_display();
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
_lastMulticastTime = _lastExecutionTime = micros();
|
||||
_updatePending = true;
|
||||
} else {
|
||||
// Error in initialising
|
||||
DIAG(F("Network Failed to initialise"));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// _read function - just return pin value (updated in _loop when message received from remote node)
|
||||
int _read(VPIN vpin) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t mask = 1 << (pin & 7);
|
||||
int byteIndex = pin / 8;
|
||||
return (_pinValues[byteIndex] & mask) ? 1 : 0;
|
||||
}
|
||||
|
||||
// _write (digital) - send command directly to the appropriate remote node.
|
||||
void _write(VPIN vpin, int value) override {
|
||||
// Send message
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t node = GETFLASH(&_pinDefs[pin].node);
|
||||
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
|
||||
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
|
||||
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Network: write(%d,%d)=>send(%d,\"write(%d,%d)\")"), vpin, value, node, remoteVpin, value);
|
||||
#endif
|
||||
|
||||
netBuffer[IONET_DESTNODE] = node;
|
||||
netBuffer[IONET_CMDTYPE] = NET_CMD_WRITE;
|
||||
netBuffer[IONET_VPIN_H] = getMsb(remoteVpin);
|
||||
netBuffer[IONET_VPIN_L] = getLsb(remoteVpin);
|
||||
netBuffer[IONET_DATA] = (uint8_t)value;
|
||||
// Set up to send to the specified node address
|
||||
_netDriver->sendCommand(node, netBuffer, IONET_DATA+1);
|
||||
}
|
||||
}
|
||||
|
||||
// _writeAnalogue - send command directly to the appropriate remote node.
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
|
||||
// Send message
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t node = GETFLASH(&_pinDefs[pin].node);
|
||||
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
|
||||
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
|
||||
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Network: writeAnalogue(%d,%d,%d,%d)=>send(%d,\"writeAnalogue(%d,%d,...)\")"),
|
||||
vpin, value, param1, param2, node, remoteVpin, value);
|
||||
#endif
|
||||
|
||||
netBuffer[IONET_DESTNODE] = node;
|
||||
netBuffer[IONET_CMDTYPE] = NET_CMD_WRITEANALOGUE;
|
||||
netBuffer[IONET_VPIN_H] = getMsb(remoteVpin);
|
||||
netBuffer[IONET_VPIN_L] = getLsb(remoteVpin);
|
||||
netBuffer[IONET_DATA+0] = getMsb(value);
|
||||
netBuffer[IONET_DATA+1] = getLsb(value);
|
||||
netBuffer[IONET_DATA+2] = param1;
|
||||
netBuffer[IONET_DATA+3] = getMsb(param2);
|
||||
netBuffer[IONET_DATA+4] = getLsb(param2);
|
||||
// Set up to send to the specified node address
|
||||
_netDriver->sendCommand(node, netBuffer, IONET_DATA+5);
|
||||
}
|
||||
}
|
||||
|
||||
// _loop function - check for, and process, received data from RF24, and send any
|
||||
// updates that are due.
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
|
||||
// Perform cyclic netdriver functions, including switching back to receive mode
|
||||
// (for half-duplex network drivers) and receiving input packets.
|
||||
_netDriver->loop();
|
||||
|
||||
// Check for incoming data
|
||||
if (_netDriver->available())
|
||||
processReceivedData();
|
||||
|
||||
// Force a data update broadcast every 1000ms irrespective of whether there are
|
||||
// data changes or not.
|
||||
if (currentMicros - _lastMulticastTime > (1000 * 1000UL))
|
||||
_updatePending = true;
|
||||
|
||||
// Send out data update broadcasts once every 20ms if there are changes
|
||||
if (currentMicros - _lastExecutionTime > (20 * 1000UL)) {
|
||||
// Broadcast updates to all other nodes. The preparation is done in a number of
|
||||
// successive calls, and when sendSensorUpdates() returns true it has completed.
|
||||
if (sendSensorUpdates()) {
|
||||
_lastExecutionTime = currentMicros; // Send complete, wait for next time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("Network Configured on Vpins:%d-%d Node:%d%S"),
|
||||
_firstVpin, _firstVpin+_nPins-1, _thisNode, (_deviceState==DEVSTATE_FAILED) ? F(" OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
private:
|
||||
// Send sensor updates only if one or more locally sourced inputs that
|
||||
// are mapped to remote VPINs have changed state.
|
||||
//
|
||||
bool sendSensorUpdates() {
|
||||
// This loop is split into multiple loop() entries, so as not to hog
|
||||
// the cpu for too long.
|
||||
|
||||
if (_numPinsToSend == 0) return true; // No pins to send from this node.
|
||||
|
||||
// Update the _pinValues bitfield to reflect the current values of local pins.
|
||||
// Process maximum of 5 pins per entry.
|
||||
uint8_t count = 5;
|
||||
bool state;
|
||||
// First time through, _nextSendPin is equal to _firstPinToSend.
|
||||
for (int pin=_nextSendPin; pin<_firstPinToSend+_numPinsToSend; pin++) {
|
||||
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
|
||||
// Is the pin an input on this node?
|
||||
if ((flags & RPIN_IN) && GETFLASH(&_pinDefs[pin].node) == _thisNode) {
|
||||
// Local input pin, read and update current state of input
|
||||
VPIN localVpin = GETFLASHW(&_pinDefs[pin].vpin);
|
||||
if (localVpin != VPIN_NONE) {
|
||||
state = IODevice::read(localVpin);
|
||||
uint16_t byteIndex = pin / 8;
|
||||
uint8_t bitMask = 1 << (pin & 7);
|
||||
uint8_t byteValue = _pinValues[byteIndex];
|
||||
bool oldState = byteValue & bitMask;
|
||||
if (state != oldState) {
|
||||
// Store state in remote values array
|
||||
if (state)
|
||||
byteValue |= bitMask;
|
||||
else
|
||||
byteValue &= ~bitMask;
|
||||
_pinValues[byteIndex] = byteValue;
|
||||
_updatePending = true;
|
||||
}
|
||||
if (--count == 0) {
|
||||
// Done enough checks for this entry, resume on next one.
|
||||
_nextSendPin = pin+1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When we get here, we've updated the _pinValues array. See if an
|
||||
// update is due.
|
||||
if (_updatePending) {
|
||||
// On master and on slave, send pin states to other nodes
|
||||
netBuffer[IONET_SENDNODE] = _thisNode; // Originating node
|
||||
netBuffer[IONET_CMDTYPE] = NET_CMD_VALUEUPDATE;
|
||||
// The packet size is 32 bytes, header is 4 bytes, so 28 bytes of data.
|
||||
// We can therefore send up to 224 binary states per packet.
|
||||
int byteCount = (_numPinsToSend+7)/8;
|
||||
VPIN remoteVpin = _firstVpin+_firstPinToSend;
|
||||
netBuffer[IONET_VPIN_H] = getMsb(remoteVpin);
|
||||
netBuffer[IONET_VPIN_L] = getLsb(remoteVpin);
|
||||
|
||||
// Copy from pinValues array into buffer. This is why _firstPinToSend must be a multiple of 8.
|
||||
memcpy(&netBuffer[IONET_DATA], &_pinValues[_firstPinToSend/8], byteCount);
|
||||
|
||||
// Broadcast update
|
||||
_netDriver->sendCommand(255, netBuffer, IONET_DATA + byteCount);
|
||||
|
||||
//DIAG(F("Sent %d bytes: %x %x ..."), byteCount, netBuffer[4], netBuffer[5]);
|
||||
_lastMulticastTime = micros();
|
||||
_updatePending = false;
|
||||
}
|
||||
// Set next pin ready for next entry.
|
||||
_nextSendPin = _firstPinToSend;
|
||||
|
||||
return true; // Done all we need to for this cycle.
|
||||
}
|
||||
|
||||
// Read next packet from the device's input buffers. Decode the message,
|
||||
// and take the appropriate action.
|
||||
// The packet may be a command to do an output write (digital or analogue), or
|
||||
// it may be an update for digital input signals.
|
||||
// For digital input signals, the values are broadcast from the node that is
|
||||
// the pin source to all the other nodes.
|
||||
void processReceivedData() {
|
||||
// Read packet
|
||||
uint8_t size = _netDriver->read(netBuffer, sizeof(netBuffer));
|
||||
if (size < IONET_DATA) return; // packet too short.
|
||||
// Extract command type from packet.
|
||||
uint8_t command = netBuffer[IONET_CMDTYPE];
|
||||
//DIAG(F("Received %d bytes, type=%d"), size, command);
|
||||
// Process received data
|
||||
switch (command) {
|
||||
case NET_CMD_WRITE: // Digital write command
|
||||
{
|
||||
uint8_t targetNode = netBuffer[IONET_DESTNODE];
|
||||
if (targetNode == _thisNode && size == IONET_DATA+1) {
|
||||
VPIN vpin = makeWord(netBuffer[IONET_VPIN_H], netBuffer[IONET_VPIN_L]);
|
||||
uint8_t state = netBuffer[IONET_DATA];
|
||||
IODevice::write(vpin, state);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NET_CMD_WRITEANALOGUE: // Analogue write command
|
||||
{
|
||||
uint8_t targetNode = netBuffer[IONET_DESTNODE];
|
||||
if (targetNode == _thisNode && size == IONET_DATA+5) {
|
||||
VPIN vpin = makeWord(netBuffer[IONET_VPIN_H], netBuffer[IONET_VPIN_L]);
|
||||
int value = makeWord(netBuffer[IONET_DATA], netBuffer[IONET_DATA+1]);
|
||||
uint8_t param1 = netBuffer[IONET_DATA+2];
|
||||
uint16_t param2 = makeWord(netBuffer[IONET_DATA+3], netBuffer[IONET_DATA+4]);
|
||||
IODevice::writeAnalogue(vpin, value, param1, param2);
|
||||
// Set the local value for the pin, used by isBusy(),
|
||||
// and subsequently updated by the remote node.
|
||||
_pinValues[vpin-_firstVpin] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NET_CMD_VALUEUPDATE: // Updates of input states (sensors etc).
|
||||
{
|
||||
uint8_t sendingNode = netBuffer[IONET_SENDNODE];
|
||||
VPIN vpin = makeWord(netBuffer[IONET_VPIN_H], netBuffer[IONET_VPIN_L]);
|
||||
//DIAG(F("Node %d Size %d VPIN %d Rx States %x"), sendingNode, size, vpin, netBuffer[IONET_DATA]);
|
||||
|
||||
// Read through the buffer one byte at a time.
|
||||
uint8_t *buffPtr = &netBuffer[IONET_DATA];
|
||||
uint8_t *bitFieldPtr = &_pinValues[(vpin-_firstVpin)/8];
|
||||
|
||||
int currentPin = vpin - _firstVpin;
|
||||
for (int byteNo=0; byteNo<size-4 && currentPin<_nPins; byteNo++) {
|
||||
// Now work through the received byte examining each bit.
|
||||
uint8_t byteValue = *buffPtr++;
|
||||
uint8_t bitFieldValue = *bitFieldPtr;
|
||||
uint8_t bitMask = 1;
|
||||
for (int bitNo=0; bitNo<8 && currentPin<_nPins; bitNo++) {
|
||||
// Process incoming value if it's come from the pin source node
|
||||
uint8_t pinSource = GETFLASH(&_pinDefs[currentPin].node);
|
||||
if (sendingNode == pinSource) {
|
||||
if (byteValue & bitMask)
|
||||
bitFieldValue |= bitMask;
|
||||
else
|
||||
bitFieldValue &= ~bitMask;
|
||||
}
|
||||
bitMask <<= 1;
|
||||
currentPin++;
|
||||
}
|
||||
// Store the modified byte back
|
||||
*bitFieldPtr++ = bitFieldValue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for packing/unpacking buffers.
|
||||
inline uint16_t makeWord(uint8_t msb, uint8_t lsb) {
|
||||
return ((uint16_t)msb << 8) | lsb;
|
||||
}
|
||||
inline uint8_t getMsb(uint16_t w) {
|
||||
return w >> 8;
|
||||
}
|
||||
inline uint8_t getLsb(uint16_t w) {
|
||||
return w & 0xff;
|
||||
}
|
||||
// Data space for actual input and output buffer.
|
||||
uint8_t netBuffer[maxPayloadSize];
|
||||
|
||||
};
|
||||
|
||||
#endif //IO_NETWORK_H
|
536
IO_RF24.h
Normal file
536
IO_RF24.h
Normal file
@ -0,0 +1,536 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* nRF24 default mode of operation:
|
||||
* Channel: 108
|
||||
* Bit rate: 2MHz
|
||||
* CRC: 16-bit
|
||||
* Power Level: High
|
||||
*
|
||||
* Each node on the network is configured with a node number in the range 0-254.
|
||||
* The remoting configuration defines, for each pin to be available remotely,
|
||||
* the node number and the VPIN number on that node. The configuration must
|
||||
* match in all nodes, since it is used by the sending node to identify the node
|
||||
* and VPIN to which a write command is to be sent, and the VPIN number for a
|
||||
* sensor/input, and on the receiving node to identify the node from which a
|
||||
* sensor/input value is being sourced.
|
||||
*
|
||||
* The node number is also used as the first byte of the nRF24's 5-byte address
|
||||
* field. Number 255 is treated as a multicast address. All stations listen on
|
||||
* their own address and on the multicast address.
|
||||
*
|
||||
* All nodes send regular multicast packets containing the latest values of the
|
||||
* sensors as they know them. On receipt of such a packet, each node extracts
|
||||
* the states of the sensors which are sourced by the originating node, and
|
||||
* updates the values in its own local data. Thus, each node has a copy of the
|
||||
* states of all digital input pin values that are defined in the remoting
|
||||
* configuration. Multicasts are sent frequently, so if one is missed
|
||||
* then, like a London bus, another will be along shortly.
|
||||
*
|
||||
* Commands (originating from write() or writeAnalogue() calls) are sent
|
||||
* immediately, directly from the originating node to the target node. This
|
||||
* is done with acknowlegements enabled to maximise the probability of
|
||||
* successful delivery.
|
||||
*
|
||||
* The nRF24 device receives and acknowledges data packets autonomously.
|
||||
* Therefore, this driver just needs to detect when a packet is received and
|
||||
* read and process its contents. The time to read the packet is under 200us
|
||||
* typically.
|
||||
*
|
||||
* The nRF24 is also capable of autonomously sending packets, processing
|
||||
* acknowledgements, and generating retries. The driver writes the packet to
|
||||
* the device and then waits for notification of completion (success, or retries
|
||||
* exceeded) through the device's registers. Similarly, the time to write a
|
||||
* packet is under 200us and, if we don't wait for the completion, we can allow
|
||||
* the processor to do other things while the transmission is in progress.
|
||||
* A write with ack can complete in under 600us, plus the time of turning the
|
||||
* receiver off and on.
|
||||
*
|
||||
* Usage:
|
||||
* First declare, for each remote pin in the common area, the mapping onto
|
||||
* a node and VPIN number. The array below assumes that the first remote
|
||||
* VPIN is 4000. The nRF24L01 device is connected to the standard SPI pins
|
||||
* plus two others referred to as CE and CSN. The Arduino pin numbers used
|
||||
* for these are specified in the create() call. The REMOTEPINS definition
|
||||
* should be the same on all nodes in the network. For outputs, it is the
|
||||
* definition in the sending node that dictates which node and VPIN the
|
||||
* action is performed on. For inputs, the value is placed into the
|
||||
* VPIN location defined in the sending node (that scans the input value),
|
||||
* but the value is only accepted in the receiving node if its definition
|
||||
* shows that the signal originates in the sending node.
|
||||
*
|
||||
* Example to go into mySetup() function in mySetup.cpp:
|
||||
* REMOTEPINS rpins[] = {
|
||||
* {0,30,RPIN_OUT}, //4000 Node 0 GPIO pin 30 (output)
|
||||
* {1,30,RPIN_IN}, //4001 Node 1 GPIO pin 30 (input)
|
||||
* {1,100,RPIN_INOUT}, //4002 Node 1 Servo (PCA9685) pin (output to servo, input busy flag)
|
||||
* {1,164,RPIN_IN}, //4003 Node 1 GPIO extender (MCP23017) pin (input)
|
||||
* {2,164,RPIN_IN} //4004 Node 2 GPIO extender (MCP23017) pin (input)
|
||||
* }
|
||||
* // FirstVPIN, nPins, thisNode, pinDefs, CEPin, CSNPin
|
||||
* RF24Net::create(4000, NUMREMOTEPINS(rpins), 0, rpins, 48, 49);
|
||||
*
|
||||
* This example defines VPINs 4000-4004 which map onto pins on nodes 0, 1 and 2.
|
||||
* The nRF24 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
|
||||
* microcontroller; in addition, the CE and CSN pins on the nRF24 are connected to
|
||||
* two pins (48 and 49 above).
|
||||
*
|
||||
* If any of pins 4000-4004 are referenced by turnouts, outputs or sensors, or by EX-RAIL,
|
||||
* then the corresponding remote pin state will be retrieved or updated.
|
||||
* For example, in EX-RAIL,
|
||||
* SET(4000) on node 1 or 2 will set pin 30 on Node 0 to +5V (pin is put into output mode on first write).
|
||||
* AT(4001) on node 0 or 2 will wait until the sensor attached to pin 30 on Node 1 activates.
|
||||
* SERVO(4002,300,2) on node 0 or 2 will reposition the servo on Node 1 PCA9685 module to position 300, and
|
||||
* AT(-4002) will wait until the servo has finished moving.
|
||||
*
|
||||
* The following sensor definition on node 0 will map onto VPIN 4004, i.e. Node 2 VPIN 164,
|
||||
* which is the first pin on the first MCP23017:
|
||||
* <S 1 4004 0>
|
||||
* and when a sensor attached to the pin on node 2 is activated (pin pulled down to 0V) the following
|
||||
* message will be generated on node 0:
|
||||
* <Q 1>
|
||||
* When the sensor deactivates, the following message will be generated on node 0:
|
||||
* <q 1>
|
||||
*/
|
||||
|
||||
#ifndef IO_RF24_H
|
||||
#define IO_RF24_H
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "RF24.h"
|
||||
|
||||
// Macros and type for creating the remote pin definitions.
|
||||
// The definitions are stored in PROGMEM to reduce RAM requirements.
|
||||
// The flags byte contains, in the low 2 bits, RPIN_IN, RPIN_OUT or RPIN_INOUT.
|
||||
typedef struct { uint8_t node; VPIN vpin; uint8_t flags; } RPIN;
|
||||
#define REMOTEPINS static const RPIN PROGMEM
|
||||
#define NUMREMOTEPINS(x) (sizeof(x)/sizeof(RPIN))
|
||||
enum {
|
||||
RPIN_IN=1,
|
||||
RPIN_OUT=2,
|
||||
RPIN_INOUT=RPIN_IN|RPIN_OUT,
|
||||
};
|
||||
|
||||
class RF24Net : public IODevice {
|
||||
|
||||
private:
|
||||
// pins must be arduino GPIO pins, not extender pins or HAL pins.
|
||||
int _cePin = -1;
|
||||
int _csnPin = -1;
|
||||
const RPIN *_pinDefs; // May need to become a far pointer!
|
||||
// Time of last loop execution
|
||||
unsigned long _lastExecutionTime;
|
||||
// Current digital values for remoted pins, stored as a bit field
|
||||
uint8_t *_pinValues;
|
||||
// Number of the current node (0-254)
|
||||
uint8_t _thisNode;
|
||||
// 5-byte nRF24L01 address. First byte will contain the node number (0-254) or 255 for broadcast
|
||||
byte _address[5] = {0x00, 0xCC, 0xEE, 0xEE, 0xCC};
|
||||
// Maximum size of payload (must be 32 or less)
|
||||
static const uint8_t maxPayloadSize = 32;
|
||||
// Current node being sent sensor data and polled
|
||||
uint8_t _currentSendNode = 0;
|
||||
bool _sendInProgress = false;
|
||||
bool _changesPending;
|
||||
int _nextSendPin = 0;
|
||||
unsigned long _lastMulticastTime;
|
||||
int _firstPinToSend; // must be a multiple of 8
|
||||
int _numPinsToSend; // need not be a multiple of 8
|
||||
|
||||
RF24 _radio;
|
||||
|
||||
// List of network commands
|
||||
enum : uint8_t {
|
||||
NET_CMD_WRITE,
|
||||
NET_CMD_WRITEANALOGUE,
|
||||
NET_CMD_VALUEUPDATE,
|
||||
};
|
||||
|
||||
public:
|
||||
// Constructor performs static initialisation of the device object
|
||||
RF24Net (VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], int cePin, int csnPin) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_cePin = cePin;
|
||||
_csnPin = csnPin;
|
||||
_thisNode = thisNode;
|
||||
_pinDefs = pinDefs;
|
||||
_address[0] = 0x00;
|
||||
_address[1] = 0xCC;
|
||||
_address[2] = 0xEE;
|
||||
_address[3] = 0xEE;
|
||||
_address[4] = 0xCC;
|
||||
_pinValues = (uint8_t *)calloc((nPins+7)/8, 1); // Allocate space for input values.
|
||||
addDevice(this);
|
||||
|
||||
// Identify which pins are allocated to this node.
|
||||
_firstPinToSend = -1;
|
||||
_numPinsToSend = 0;
|
||||
for (int pin=0; pin<_nPins; pin++) {
|
||||
uint8_t node = GETFLASH(&_pinDefs[pin].node);
|
||||
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
|
||||
// Check if the pin is an input on this node?
|
||||
if (node == _thisNode && (flags & RPIN_IN)) {
|
||||
if (_firstPinToSend==-1) _firstPinToSend = pin;
|
||||
_numPinsToSend = pin - _firstPinToSend + 1;
|
||||
}
|
||||
//DIAG(F("Node=%d FirstPin=%d, NumPins=%d"), node, _firstPinToSend, _numPinsToSend);
|
||||
}
|
||||
// Round down to multiple of 8 (byte boundary).
|
||||
_firstPinToSend /= 8;
|
||||
_firstPinToSend *= 8;
|
||||
_nextSendPin = _firstPinToSend;
|
||||
//DIAG(F("FirstPin=%d, NumPins=%d"), _firstPinToSend, _numPinsToSend);
|
||||
}
|
||||
|
||||
// Static create function provides alternative way to create object
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], int cePin, int csnPin) {
|
||||
new RF24Net(firstVpin, nPins, thisNode, pinDefs, cePin, csnPin);
|
||||
}
|
||||
|
||||
protected:
|
||||
// _begin function called to perform dynamic initialisation of the device
|
||||
void _begin() override {
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
if (_radio.begin(_cePin, _csnPin)) {
|
||||
// Device initialisation OK, set up parameters
|
||||
_radio.setDataRate(RF24_2MBPS);
|
||||
_radio.setPALevel(RF24_PA_HIGH);
|
||||
_radio.setChannel(108);
|
||||
_radio.enableDynamicPayloads(); // variable length packets
|
||||
_radio.setAutoAck(true);
|
||||
_radio.enableDynamicAck(); // required for multicast to work
|
||||
_radio.setRetries(1, 5); // Retry time=1*250+250us=500us, count=5.
|
||||
|
||||
// Set to listen on the address 255
|
||||
_address[0] = 255;
|
||||
_radio.openReadingPipe(1, _address);
|
||||
// Also allow receives on own node address
|
||||
_address[0] = _thisNode;
|
||||
_radio.openReadingPipe(2, _address);
|
||||
_radio.startListening();
|
||||
|
||||
_display();
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
} else {
|
||||
// Error in initialising
|
||||
DIAG(F("nRF24L01 Failed to initialise"));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
_lastMulticastTime = _lastExecutionTime = micros();
|
||||
}
|
||||
|
||||
// _read function - just return _value (updated in _loop when message received from remote node)
|
||||
int _read(VPIN vpin) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t mask = 1 << (pin & 7);
|
||||
int byteIndex = pin / 8;
|
||||
return (_pinValues[byteIndex] & mask) ? 1 : 0;
|
||||
}
|
||||
|
||||
// _write (digital) - send command directly to the appropriate remote node.
|
||||
void _write(VPIN vpin, int value) override {
|
||||
// Send message
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t node = GETFLASH(&_pinDefs[pin].node);
|
||||
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
|
||||
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
|
||||
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("RF24: write(%d,%d)=>send(%d,\"write(%d,%d)\")"), vpin, value, node, remoteVpin, value);
|
||||
#endif
|
||||
|
||||
outBuffer[0] = node;
|
||||
outBuffer[1] = NET_CMD_WRITE;
|
||||
outBuffer[2] = getMsb(remoteVpin);
|
||||
outBuffer[3] = getLsb(remoteVpin);
|
||||
outBuffer[4] = (uint8_t)value;
|
||||
// Set up to send to the specified node address
|
||||
sendCommand(node, outBuffer, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// _writeAnalogue - send command directly to the appropriate remote node.
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
|
||||
// Send message
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t node = GETFLASH(&_pinDefs[pin].node);
|
||||
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
|
||||
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
|
||||
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("RF24: writeAnalogue(%d,%d,%d,%d)=>send(%d,\"writeAnalogue(%d,%d,...)\")"),
|
||||
vpin, value, param1, param2, node, remoteVpin, value);
|
||||
#endif
|
||||
|
||||
outBuffer[0] = node;
|
||||
outBuffer[1] = NET_CMD_WRITEANALOGUE;
|
||||
outBuffer[2] = getMsb(remoteVpin);
|
||||
outBuffer[3] = getLsb(remoteVpin);
|
||||
outBuffer[4] = getMsb(value);
|
||||
outBuffer[5] = getLsb(value);
|
||||
outBuffer[6] = param1;
|
||||
outBuffer[7] = getMsb(param2);
|
||||
outBuffer[8] = getLsb(param2);
|
||||
// Set up to send to the specified node address
|
||||
sendCommand(node, outBuffer, 9);
|
||||
}
|
||||
}
|
||||
|
||||
// _loop function - check for, and process, received data from RF24, and send any
|
||||
// updates that are due.
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
|
||||
// Check for incoming data
|
||||
if (_radio.available(NULL))
|
||||
processReceivedData();
|
||||
|
||||
// Force a data update broadcast every 500ms irrespective of whether there are
|
||||
// data changes or not.
|
||||
if (currentMicros - _lastMulticastTime > (500 * 1000UL))
|
||||
_changesPending = true;
|
||||
|
||||
// Send out data update broadcasts once every 100ms if there are changes
|
||||
if (currentMicros - _lastExecutionTime > (100 * 1000UL)) {
|
||||
// Broadcast updates to all other nodes. The preparation is done in a number of
|
||||
// successive calls, and when sendSensorUpdates() returns true it has completed.
|
||||
if (sendSensorUpdates()) {
|
||||
_lastExecutionTime = currentMicros; // Send complete, wait another 100ms
|
||||
}
|
||||
}
|
||||
|
||||
// Check if outstanding writes have completed. If so, move to Standby-I mode
|
||||
// and enable the receiver.
|
||||
if (_sendInProgress && _radio.isWriteFinished()) {
|
||||
_sendInProgress = false;
|
||||
_radio.txStandBy();
|
||||
_radio.startListening();
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("nRF24L01 Configured on Vpin:%d-%d CEPin:%d CSNPin:%d"),
|
||||
_firstVpin, _firstVpin+_nPins-1, _cePin, _csnPin);
|
||||
}
|
||||
|
||||
private:
|
||||
// Send sensor updates only if one or more locally sourced inputs that
|
||||
// are mapped to remote VPINs have changed state.
|
||||
//
|
||||
bool sendSensorUpdates() {
|
||||
// This loop is split into multiple loop() entries, so as not to hog
|
||||
// the cpu for too long. Otherwise it could take over 2700us with 108 remote
|
||||
// pins configured, for example. So we do just 5 pins per call.
|
||||
// We could make digital state change notification mandatory, which would
|
||||
// allow us to remove the loop altogether!
|
||||
|
||||
if (_numPinsToSend == 0) return true; // No pins to send from this node.
|
||||
|
||||
// Update the _pinValues bitfield to reflect the current values of local pins.
|
||||
uint8_t count = 5;
|
||||
bool state;
|
||||
for (int pin=_nextSendPin; pin<_firstPinToSend+_numPinsToSend; pin++) {
|
||||
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
|
||||
if ((flags & RPIN_IN) && GETFLASH(&_pinDefs[pin].node) == _thisNode) {
|
||||
// Local input pin, read and update current state of input
|
||||
VPIN localVpin = GETFLASHW(&_pinDefs[pin].vpin);
|
||||
if (localVpin != VPIN_NONE) {
|
||||
state = IODevice::read(localVpin);
|
||||
uint16_t byteIndex = pin / 8;
|
||||
uint8_t bitMask = 1 << (pin & 7);
|
||||
uint8_t byteValue = _pinValues[byteIndex];
|
||||
bool oldState = byteValue & bitMask;
|
||||
if (state != oldState) {
|
||||
// Store state in remote values array
|
||||
if (state)
|
||||
byteValue |= bitMask;
|
||||
else
|
||||
byteValue &= ~bitMask;
|
||||
_pinValues[byteIndex] = byteValue;
|
||||
_changesPending = true;
|
||||
//DIAG(F("RF24 VPIN:%d Val:%d"), _firstVpin+pin, state);
|
||||
}
|
||||
if (--count == 0) {
|
||||
// Done enough checks for this entry, resume on next one.
|
||||
_nextSendPin = pin+1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_changesPending) {
|
||||
// On master and on slave, send pin states to other nodes
|
||||
outBuffer[0] = _thisNode; // Originating node
|
||||
outBuffer[1] = NET_CMD_VALUEUPDATE;
|
||||
// The packet size is 32 bytes, header is 4 bytes, so 28 bytes of data.
|
||||
// We can therefore send up to 224 binary states per packet.
|
||||
int byteCount = _numPinsToSend/8+1;
|
||||
VPIN remoteVpin = _firstVpin+_firstPinToSend;
|
||||
outBuffer[2] = getMsb(remoteVpin);
|
||||
outBuffer[3] = getLsb(remoteVpin);
|
||||
|
||||
// Copy from pinValues array into buffer. This is why _firstPinToSend must be a multiple of 8.
|
||||
memcpy(&outBuffer[4], &_pinValues[_firstPinToSend/8], byteCount);
|
||||
|
||||
// Broadcast update
|
||||
sendCommand(255, outBuffer, byteCount + 4);
|
||||
|
||||
//DIAG(F("Sent %d bytes: %x %x ..."), byteCount, outBuffer[4], outBuffer[5]);
|
||||
_lastMulticastTime = micros();
|
||||
_changesPending = false;
|
||||
}
|
||||
// Set next pin ready for next entry.
|
||||
_nextSendPin = _firstPinToSend;
|
||||
|
||||
return true; // Done all we need to for this cycle.
|
||||
}
|
||||
|
||||
// Read next packet from the device's input buffers. Decode the message,
|
||||
// and take the appropriate action.
|
||||
// The packet may be a command to do an output write (digital or analogue), or
|
||||
// it may be an update for digital input signals.
|
||||
// For digital input signals, the values are propagated from the node that is
|
||||
// the pin source, via the master, to all the other nodes.
|
||||
void processReceivedData() {
|
||||
// Read received data from input pipe
|
||||
byte size = _radio.getDynamicPayloadSize();
|
||||
// if (size > maxPayloadSize) return; // Packet too long to read!!
|
||||
// Read packet
|
||||
_radio.read(inBuffer, size);
|
||||
// Extract command type from packet.
|
||||
uint8_t command = inBuffer[1];
|
||||
// Process received data
|
||||
switch (command) {
|
||||
case NET_CMD_WRITE: // Digital write command
|
||||
{
|
||||
uint8_t targetNode = inBuffer[0];
|
||||
if (targetNode == _thisNode) {
|
||||
VPIN vpin = makeWord(inBuffer[2], inBuffer[3]);
|
||||
uint8_t state = inBuffer[4];
|
||||
IODevice::write(vpin, state);
|
||||
} else {
|
||||
sendCommand(targetNode, inBuffer, size);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NET_CMD_WRITEANALOGUE: // Analogue write command
|
||||
{
|
||||
uint8_t targetNode = inBuffer[0];
|
||||
if (targetNode == _thisNode) {
|
||||
VPIN vpin = makeWord(inBuffer[2], inBuffer[3]);
|
||||
int value = makeWord(inBuffer[4], inBuffer[5]);
|
||||
uint8_t param1 = inBuffer[6];
|
||||
uint16_t param2 = makeWord(inBuffer[7], inBuffer[8]);
|
||||
IODevice::writeAnalogue(vpin, value, param1, param2);
|
||||
// Set the local value for the pin, used by isBusy(),
|
||||
// and subsequently updated by the remote node.
|
||||
_pinValues[vpin-_firstVpin] = true;
|
||||
} else {
|
||||
sendCommand(targetNode, inBuffer, size);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NET_CMD_VALUEUPDATE: // Updates of input states (sensors etc).
|
||||
{
|
||||
uint8_t sendingNode = inBuffer[0];
|
||||
//DIAG(F("Node %d Rx %x"), sendingNode, inBuffer[4]);
|
||||
VPIN vpin = makeWord(inBuffer[2], inBuffer[3]);
|
||||
|
||||
// Read through the buffer one byte at a time.
|
||||
uint8_t *buffPtr = &inBuffer[4];
|
||||
uint8_t *bitFieldPtr = &_pinValues[(vpin-_firstVpin)/8];
|
||||
|
||||
int currentPin = vpin - _firstVpin;
|
||||
for (int byteNo=0; byteNo<size-4 && currentPin<_nPins; byteNo++) {
|
||||
// Now work through the byte examining each bit.
|
||||
uint8_t byteValue = *buffPtr++;
|
||||
uint8_t bitMask = 1;
|
||||
for (int bitNo=0; bitNo<8 && currentPin<_nPins; bitNo++) {
|
||||
// Process incoming value if it's come from the pin source node
|
||||
uint8_t pinSource = GETFLASH(&_pinDefs[currentPin].node);
|
||||
if (sendingNode == pinSource) {
|
||||
if (byteValue & bitMask)
|
||||
byteValue |= bitMask;
|
||||
else
|
||||
byteValue &= ~bitMask;
|
||||
// if (pinNode == _thisNode) { // Local pin }
|
||||
}
|
||||
bitMask <<= 1;
|
||||
currentPin++;
|
||||
}
|
||||
// Store the modified byte back
|
||||
*bitFieldPtr++ = byteValue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper functions for RF24 send functions. If node=255, then
|
||||
// the packet is to be sent as a multicast without acknowledgements.
|
||||
// The multicast message takes ~400us. A further 260us is required to turn
|
||||
// the receiver off and on for the transmission, totalling 660us.
|
||||
// If the node is not 255, then the packet will be sent directly to the
|
||||
// addressed node, with acknowledgement requested. If no acknowledgement is
|
||||
// received, then the device will retry up to the defined maximum number of
|
||||
// retries. This will take longer than a multicast. For example, with
|
||||
// setRetries(1,3) the timeout is 500us and a maximum of 3 retries are
|
||||
// carried out, so the operation will take as much as 2.26 milliseconds if
|
||||
// the node in question is not responding, and as little as 890us if the
|
||||
// ack is received immediately (including turning receiver on/off).
|
||||
//
|
||||
bool sendCommand(uint8_t node, uint8_t *buffer, uint8_t len) {
|
||||
_address[0] = node;
|
||||
_radio.openWritingPipe(_address);
|
||||
// We have to stop the receiver before we can transmit.
|
||||
_radio.stopListening();
|
||||
// Copy the message into the radio and start the transmitter.
|
||||
// Multicast (no ack expected) if destination node is 255.
|
||||
bool ok = _radio.writeFast(buffer, len, (node==255));
|
||||
// We will poll the radio later on to see when the transmit queue
|
||||
// has emptied. When that happens, we will go back to receive mode.
|
||||
// This prevents txStandBy() from blocking while the transmission
|
||||
// is in progress.
|
||||
_sendInProgress = true;;
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Helper functions for packing/unpacking buffers.
|
||||
inline uint16_t makeWord(uint8_t msb, uint8_t lsb) {
|
||||
return ((uint16_t)msb << 8) | lsb;
|
||||
}
|
||||
inline uint8_t getMsb(uint16_t w) {
|
||||
return w >> 8;
|
||||
}
|
||||
inline uint8_t getLsb(uint16_t w) {
|
||||
return w & 0xff;
|
||||
}
|
||||
// Data space for actual input and output buffers.
|
||||
uint8_t inBuffer[maxPayloadSize];
|
||||
uint8_t outBuffer[maxPayloadSize];
|
||||
|
||||
};
|
||||
|
||||
#endif //IO_RF24Net4_H
|
149
Net_ENC28J60.h
Normal file
149
Net_ENC28J60.h
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* ENC28J60 default mode of operation:
|
||||
*
|
||||
* The node number is used as the last byte of the MAC address
|
||||
* field, purely so that the MAC address is unique for each distinct
|
||||
* node number. The ENC28J60 driver doesn't implement ARP cache,
|
||||
* since it would take up a minimum of 11 bytes per node and, with
|
||||
* up to 254 nodes, this would take a significant part of the RAM.
|
||||
* Instead, all transmissions are sent with MAC address allOnes, i.e.
|
||||
* as a broadcast. Regular sensor state updates are broadcast anyway,
|
||||
* and other write/writeAnalogue command packets are relatively
|
||||
* infrequent.
|
||||
*
|
||||
* Usage:
|
||||
* Net_ENC28J60 *encDriver = new Net_ENC28J60(10);
|
||||
* Network<Net_ENC28J60>::create(4000, NUMREMOTEPINS(rpins), 1, rpins, encDriver);
|
||||
*
|
||||
* The ENC28J60 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
|
||||
* microcontroller. The CS pin is specified in the command above (e.g. 10).
|
||||
* For details of the Network class configuration, see IO_Network.h.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NET_ENC28J60_H
|
||||
#define NET_ENC28J60_H
|
||||
|
||||
#include "IO_Network.h"
|
||||
#include "DIAG.h"
|
||||
#include "EtherCard.h"
|
||||
|
||||
// The ethernet buffer is global to different protocol layers, to avoid almost
|
||||
// all copying of data.
|
||||
byte ENC28J60::buffer[74]; // Need space for 32 byte payload and 42 byte header.
|
||||
|
||||
class Net_ENC28J60 : public EtherCard {
|
||||
|
||||
private:
|
||||
// pins must be arduino GPIO pins, not extender pins or HAL pins.
|
||||
int _csPin;
|
||||
// Number of the current node (1-254)
|
||||
uint8_t _thisNode;
|
||||
// 6-byte MAC address. Last byte will contain the node number (1-254).
|
||||
byte _address[6] = {0xEE,0xCC,0xEE,0xEE,0xCC,0x00};
|
||||
// 4-byte IP address. Last byte will contain the node number (1-254) or 255 for broadcast.
|
||||
byte _ipAddress[4] = {192,168,1,0};
|
||||
byte _gwAddress[4] = {192,168,1,254};
|
||||
byte _netMask[4] = {255,255,255,0};
|
||||
const uint16_t _port = 8900;
|
||||
uint8_t _packetBytesAvailable;
|
||||
|
||||
public:
|
||||
// Constructor performs static initialisation of the device object
|
||||
Net_ENC28J60 (int csPin) {
|
||||
_csPin = csPin;
|
||||
_packetBytesAvailable = 0;
|
||||
}
|
||||
|
||||
// Perform dynamic initialisation of the device
|
||||
bool begin(uint8_t thisNode) {
|
||||
_thisNode = thisNode;
|
||||
_address[5] = _thisNode;
|
||||
_ipAddress[3] = _thisNode;
|
||||
if (ether.begin(sizeof(ENC28J60::buffer), _address, _csPin)) {
|
||||
ether.staticSetup(_ipAddress, _gwAddress, 0, _netMask);
|
||||
return true;
|
||||
} else {
|
||||
// Error in initialising
|
||||
DIAG(F("ENC28J60 Failed to initialise"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The following function should be called regularly to handle incoming packets.
|
||||
// Check if there is a received packet ready for reading.
|
||||
bool available() {
|
||||
uint16_t packetLen = ether.packetReceive();
|
||||
|
||||
if (packetLen > 0) {
|
||||
// Packet received. First handle ICMP, ARP etc.
|
||||
if (ether.packetLoop(packetLen)) {
|
||||
// UDP packet to be handled. Check if it's our port number
|
||||
byte *gbp = ENC28J60::buffer;
|
||||
uint16_t destPort = (gbp[UDP_DST_PORT_H_P] << 8) + gbp[UDP_DST_PORT_L_P];
|
||||
if (destPort == _port) {
|
||||
// Yes, so mark that data is to be processed.
|
||||
_packetBytesAvailable = packetLen;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_packetBytesAvailable = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read packet from the ethernet buffer, and return the number of bytes
|
||||
// that were read.
|
||||
uint8_t read(uint8_t buffer[], uint8_t bufferSize) {
|
||||
if (_packetBytesAvailable > 0) {
|
||||
//DIAG(F("ReadPacket(%d)"), _packetBytesAvailable);
|
||||
// Adjust length and pointer for UDP header
|
||||
byte *gbp = ENC28J60::buffer;
|
||||
int udpDataSize = (gbp[UDP_LEN_H_P] << 8) + gbp[UDP_LEN_L_P] - UDP_HEADER_LEN;
|
||||
byte *udpFrame = &ENC28J60::buffer[UDP_DATA_P];
|
||||
|
||||
// Clear packet byte marker
|
||||
_packetBytesAvailable = 0;
|
||||
|
||||
// Check if there's room for the data
|
||||
if (bufferSize >= udpDataSize) {
|
||||
memcpy(buffer, udpFrame, udpDataSize);
|
||||
return udpDataSize;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Wrapper functions for ENC28J60 sendUdp function.
|
||||
// The node parameter is either 1-254 (for specific nodes) or 255 (for broadcast).
|
||||
// This aligns with the subnet broadcast IP address of "x.x.x.255".
|
||||
bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t len) {
|
||||
_ipAddress[3] = node;
|
||||
ether.sendUdp((const char *)buffer, len, _port, _ipAddress, _port);
|
||||
return true;
|
||||
}
|
||||
|
||||
void loop() { }
|
||||
|
||||
};
|
||||
|
||||
#endif //NET_ENC28J60_H
|
131
Net_Ethernet.h
Normal file
131
Net_Ethernet.h
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Ethernet (W5100,W5200,W5500) default mode of operation:
|
||||
*
|
||||
* All transmissions are sent with MAC address allOnes, i.e.
|
||||
* as a broadcast. Regular sensor state updates are broadcast anyway,
|
||||
* and other write/writeAnalogue command packets are relatively
|
||||
* infrequent.
|
||||
*
|
||||
* Usage:
|
||||
* Net_Ethernet *etherDriver = new Net_Ethernet(10);
|
||||
* Network<Net_Ethernet>::create(4000, NUMREMOTEPINS(rpins), 1, rpins, etherDriver);
|
||||
*
|
||||
* The W5x00 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
|
||||
* microcontroller. The CS pin is specified in the command above (e.g. 10).
|
||||
* For details of the Network class configuration, see IO_Network.h.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NET_ETHERNET_H
|
||||
#define NET_ETHERNET_H
|
||||
|
||||
#include "IO_Network.h"
|
||||
#include "DIAG.h"
|
||||
#include "Ethernet.h"
|
||||
#include "EthernetUdp.h"
|
||||
|
||||
class Net_Ethernet {
|
||||
|
||||
private:
|
||||
// pins must be arduino GPIO pins, not extender pins or HAL pins.
|
||||
int _csPin;
|
||||
// Number of the current node (1-254)
|
||||
uint8_t _thisNode;
|
||||
// 4-byte IP address. Last byte will contain the node number (1-254) or 255 for broadcast.
|
||||
byte _ipAddress[4] = {192,168,1,0};
|
||||
const uint16_t _port = 8900;
|
||||
uint8_t _packetBytesAvailable;
|
||||
EthernetUDP udp;
|
||||
|
||||
public:
|
||||
// Constructor performs static initialisation of the device object
|
||||
Net_Ethernet () {
|
||||
_csPin = 10; // The Ethernet driver doesn't allow CS pin to be selected.
|
||||
_packetBytesAvailable = 0;
|
||||
}
|
||||
|
||||
// Perform dynamic initialisation of the device
|
||||
bool begin(uint8_t thisNode) {
|
||||
_thisNode = thisNode;
|
||||
if (Ethernet.hardwareStatus() != EthernetNoHardware) {
|
||||
// // Set IP address.
|
||||
// _ipAddress[3] = thisNode;
|
||||
// Ethernet.setLocalIP(_ipAddress);
|
||||
// _ipAddress[3] = 254;
|
||||
// Ethernet.setGatewayIP(_ipAddress);
|
||||
// IPAddress mask = IPAddress(255,255,255,0);
|
||||
// Ethernet.setSubnetMask(mask);
|
||||
// Begin listening on receive port
|
||||
udp.begin(_port);
|
||||
return true;
|
||||
} else {
|
||||
DIAG(F("Ethernet (W5x00) no hardware found"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The following function should be called regularly to handle incoming packets.
|
||||
// Check if there is a received packet ready for reading.
|
||||
bool available() {
|
||||
uint16_t packetLen = udp.parsePacket();
|
||||
|
||||
if (packetLen > 0) {
|
||||
// Packet received.
|
||||
_packetBytesAvailable = packetLen;
|
||||
return true;
|
||||
}
|
||||
_packetBytesAvailable = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read packet from the ethernet buffer, and return the number of bytes
|
||||
// that were read.
|
||||
uint8_t read(uint8_t buffer[], uint8_t bufferSize) {
|
||||
uint8_t bytesReceived = _packetBytesAvailable;
|
||||
// Clear packet byte marker
|
||||
_packetBytesAvailable = 0;
|
||||
if (bytesReceived > 0) {
|
||||
//DIAG(F("ReadPacket(%d)"), bytesReceived);
|
||||
// Check if there's room for the data
|
||||
if (bufferSize >= bytesReceived) {
|
||||
return udp.read(buffer, bytesReceived);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Wrapper functions for Ethernet UDP write function.
|
||||
// Since we don't know the IP address of the node, just broadcast
|
||||
// over the subnet.
|
||||
bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t len) {
|
||||
_ipAddress[3] = 255;
|
||||
udp.beginPacket(_ipAddress, _port);
|
||||
udp.write(buffer, len);
|
||||
udp.endPacket();
|
||||
return true;
|
||||
}
|
||||
|
||||
void loop() { }
|
||||
|
||||
};
|
||||
|
||||
#endif //NET_ETHERNET_H
|
174
Net_RF24.h
Normal file
174
Net_RF24.h
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* nRF24 default mode of operation:
|
||||
* Channel: 108
|
||||
* Bit rate: 2MHz
|
||||
* CRC: 16-bit
|
||||
* Power Level: High
|
||||
*
|
||||
* The node number is used as the first byte of the nRF24's 5-byte address
|
||||
* field. Number 255 is treated as a multicast address. All stations listen on
|
||||
* their own address and on the multicast address.
|
||||
*
|
||||
* The nRF24 device receives and acknowledges data packets autonomously.
|
||||
* Therefore, this driver just needs to detect when a packet is received and
|
||||
* read and process its contents. The time to read the packet is under 200us
|
||||
* typically.
|
||||
*
|
||||
* The nRF24 is also capable of autonomously sending packets, processing
|
||||
* acknowledgements, and generating retries. The driver writes the packet to
|
||||
* the device and then waits for notification of completion (success, or retries
|
||||
* exceeded) through the device's registers. Similarly, the time to write a
|
||||
* packet is under 200us and, if we don't wait for the completion, we can allow
|
||||
* the processor to do other things while the transmission is in progress.
|
||||
* A write with ack can complete in under 600us, plus the time of turning the
|
||||
* receiver off and on.
|
||||
*
|
||||
* Usage:
|
||||
* Net_RF24 *rf24Driver = new Net_RF24(48, 49);
|
||||
* Network<Net_RF24>::create(4000, NUMREMOTEPINS(rpins), 1, rpins, rf24Driver);
|
||||
*
|
||||
* The nRF24 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
|
||||
* microcontroller; in addition, the CE and CSN pins on the nRF24 are connected to
|
||||
* two pins (e.g. 48 and 49 above).
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NET_RF24_H
|
||||
#define NET_RF24_H
|
||||
|
||||
#include "IO_Network.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
class Net_RF24 : public RF24 {
|
||||
|
||||
private:
|
||||
// pins must be arduino GPIO pins, not extender pins or HAL pins.
|
||||
int _cePin;
|
||||
int _csnPin;
|
||||
// Number of the current node (1-254)
|
||||
uint8_t _thisNode;
|
||||
// 5-byte nRF24L01 address. First byte will contain the node number (0-254) or 255 for broadcast
|
||||
byte _address[5];
|
||||
bool _sendInProgress;
|
||||
|
||||
|
||||
public:
|
||||
// Constructor performs static initialisation of the device object
|
||||
Net_RF24 (int cePin, int csnPin) {
|
||||
_cePin = cePin;
|
||||
_csnPin = csnPin;
|
||||
// Initialise with an arbitrary address. The first byte will contain
|
||||
// the node number.
|
||||
_address[0] = 0x00;
|
||||
_address[1] = 0xCC;
|
||||
_address[2] = 0xEE;
|
||||
_address[3] = 0xEE;
|
||||
_address[4] = 0xCC;
|
||||
}
|
||||
|
||||
// Perform dynamic initialisation of the device
|
||||
bool begin(uint8_t thisNode) {
|
||||
if (RF24::begin(_cePin, _csnPin)) {
|
||||
// Device initialisation OK, set up parameters
|
||||
RF24::setDataRate(RF24_2MBPS);
|
||||
RF24::setPALevel(RF24_PA_HIGH);
|
||||
RF24::setChannel(108);
|
||||
RF24::enableDynamicPayloads(); // variable length packets
|
||||
RF24::setAutoAck(true); // required for acks to work
|
||||
RF24::enableDynamicAck(); // required for multicast to work
|
||||
RF24::setRetries(1, 5); // Retry time=1*250+250us=500us, count=5.
|
||||
|
||||
_thisNode = thisNode;
|
||||
// Set to listen on the address 255
|
||||
_address[0] = 255;
|
||||
RF24::openReadingPipe(1, _address);
|
||||
// Also allow receives on own node address
|
||||
_address[0] = _thisNode;
|
||||
RF24::openReadingPipe(2, _address);
|
||||
RF24::startListening();
|
||||
_sendInProgress = false;
|
||||
return true;
|
||||
} else {
|
||||
// Error in initialising
|
||||
DIAG(F("nRF24L01 Failed to initialise"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is a received packet ready for reading.
|
||||
bool available() {
|
||||
return RF24::available();
|
||||
}
|
||||
|
||||
// Read next packet from the device, and return the number of bytes
|
||||
// that were read.
|
||||
uint8_t read(uint8_t buffer[], uint8_t size) {
|
||||
uint8_t len = RF24::getDynamicPayloadSize();
|
||||
RF24::read(buffer, size);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Wrapper functions for RF24 send functions. If node=255, then
|
||||
// the packet is to be sent as a multicast without acknowledgements.
|
||||
// The multicast message takes ~400us. A further 260us is required to turn
|
||||
// the receiver off and on for the transmission, totalling 660us.
|
||||
// If the node is not 255, then the packet will be sent directly to the
|
||||
// addressed node, with acknowledgement requested. If no acknowledgement is
|
||||
// received, then the device will retry up to the defined maximum number of
|
||||
// retries. This will take longer than a multicast. For example, with
|
||||
// setRetries(1,3) the timeout is 500us and a maximum of 3 retries are
|
||||
// carried out, so the operation will take as much as 2.26 milliseconds if
|
||||
// the node in question is not responding, and as little as 890us if the
|
||||
// ack is received immediately (including turning receiver on/off).
|
||||
//
|
||||
bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t len) {
|
||||
_address[0] = node;
|
||||
openWritingPipe(_address);
|
||||
// We have to stop the receiver before we can transmit.
|
||||
RF24::stopListening();
|
||||
// Copy the message into the radio and start the transmitter.
|
||||
// Multicast (no ack expected) if destination node is 255.
|
||||
bool ok = RF24::writeFast(buffer, len, (node==255));
|
||||
// We will poll the radio later on to see when the transmit queue
|
||||
// has emptied. When that happens, we will go back to receive mode.
|
||||
// This prevents txStandBy() from blocking while the transmission
|
||||
// is in progress.
|
||||
_sendInProgress = true;;
|
||||
return ok;
|
||||
}
|
||||
|
||||
// The following function should be called regularly to ensure that the
|
||||
// device goes back into listening mode when a transmission is not
|
||||
// in progress. (The nRF24 is a half-duplex device and cannot be in
|
||||
// transmit mode and receive mode at the same time.)
|
||||
void loop() {
|
||||
bool completed = RF24::isWriteFinished();
|
||||
if (_sendInProgress && completed) {
|
||||
_sendInProgress = false;
|
||||
RF24::txStandBy();
|
||||
RF24::startListening();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif //NET_RF24_H
|
273
RF24.h
Normal file
273
RF24.h
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
Copyright (C) 2011 J. Coliz <maniacbug@ymail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
version 2 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file RF24.h
|
||||
*
|
||||
* Class declaration for RF24 and helper enums
|
||||
*/
|
||||
|
||||
#ifndef __RF24_H__
|
||||
#define __RF24_H__
|
||||
|
||||
#define FAILURE_HANDLING
|
||||
#define RF24_POWERUP_DELAY 5000
|
||||
#define rf24_max(a, b) (a>b?a:b)
|
||||
#define rf24_min(a, b) (a<b?a:b)
|
||||
|
||||
#define RF24_SPI_SPEED 10000000
|
||||
|
||||
#include <SPI.h>
|
||||
#define _SPI SPIClass
|
||||
|
||||
typedef enum {
|
||||
RF24_PA_MIN = 0,
|
||||
RF24_PA_LOW,
|
||||
RF24_PA_HIGH,
|
||||
RF24_PA_MAX,
|
||||
RF24_PA_ERROR
|
||||
} rf24_pa_dbm_e;
|
||||
|
||||
typedef enum {
|
||||
RF24_1MBPS = 0,
|
||||
RF24_2MBPS,
|
||||
RF24_250KBPS
|
||||
} rf24_datarate_e;
|
||||
|
||||
typedef enum {
|
||||
RF24_CRC_DISABLED = 0,
|
||||
RF24_CRC_8,
|
||||
RF24_CRC_16
|
||||
} rf24_crclength_e;
|
||||
|
||||
/* Memory Map */
|
||||
#define NRF_CONFIG 0x00
|
||||
#define EN_AA 0x01
|
||||
#define EN_RXADDR 0x02
|
||||
#define SETUP_AW 0x03
|
||||
#define SETUP_RETR 0x04
|
||||
#define RF_CH 0x05
|
||||
#define RF_SETUP 0x06
|
||||
#define NRF_STATUS 0x07
|
||||
#define OBSERVE_TX 0x08
|
||||
#define CD 0x09
|
||||
#define RX_ADDR_P0 0x0A
|
||||
#define RX_ADDR_P1 0x0B
|
||||
#define RX_ADDR_P2 0x0C
|
||||
#define RX_ADDR_P3 0x0D
|
||||
#define RX_ADDR_P4 0x0E
|
||||
#define RX_ADDR_P5 0x0F
|
||||
#define TX_ADDR 0x10
|
||||
#define RX_PW_P0 0x11
|
||||
// #define RX_PW_P1 0x12
|
||||
// #define RX_PW_P2 0x13
|
||||
// #define RX_PW_P3 0x14
|
||||
// #define RX_PW_P4 0x15
|
||||
// #define RX_PW_P5 0x16
|
||||
#define FIFO_STATUS 0x17
|
||||
#define DYNPD 0x1C
|
||||
#define FEATURE 0x1D
|
||||
|
||||
/* Bit Mnemonics */
|
||||
#define MASK_RX_DR 6
|
||||
#define MASK_TX_DS 5
|
||||
#define MASK_MAX_RT 4
|
||||
#define EN_CRC 3
|
||||
#define CRCO 2
|
||||
#define PWR_UP 1
|
||||
#define PRIM_RX 0
|
||||
#define ENAA_P5 5
|
||||
#define ENAA_P4 4
|
||||
#define ENAA_P3 3
|
||||
#define ENAA_P2 2
|
||||
#define ENAA_P1 1
|
||||
#define ENAA_P0 0
|
||||
#define ERX_P5 5
|
||||
#define ERX_P4 4
|
||||
#define ERX_P3 3
|
||||
#define ERX_P2 2
|
||||
#define ERX_P1 1
|
||||
#define ERX_P0 0
|
||||
#define AW 0
|
||||
#define ARD 4
|
||||
#define ARC 0
|
||||
#define PLL_LOCK 4
|
||||
#define CONT_WAVE 7
|
||||
#define RF_DR 3
|
||||
#define RF_PWR 6
|
||||
#define RX_DR 6
|
||||
#define TX_DS 5
|
||||
#define MAX_RT 4
|
||||
#define RX_P_NO 1
|
||||
#define TX_FULL 0
|
||||
#define PLOS_CNT 4
|
||||
#define ARC_CNT 0
|
||||
#define TX_REUSE 6
|
||||
#define FIFO_FULL 5
|
||||
#define TX_EMPTY 4
|
||||
#define RX_FULL 1
|
||||
#define RX_EMPTY 0
|
||||
#define DPL_P5 5
|
||||
#define DPL_P4 4
|
||||
#define DPL_P3 3
|
||||
#define DPL_P2 2
|
||||
#define DPL_P1 1
|
||||
#define DPL_P0 0
|
||||
#define EN_DPL 2
|
||||
#define EN_ACK_PAY 1
|
||||
#define EN_DYN_ACK 0
|
||||
|
||||
/* Instruction Mnemonics */
|
||||
#define R_REGISTER 0x00
|
||||
#define W_REGISTER 0x20
|
||||
#define REGISTER_MASK 0x1F
|
||||
#define ACTIVATE 0x50
|
||||
#define R_RX_PL_WID 0x60
|
||||
#define R_RX_PAYLOAD 0x61
|
||||
#define W_TX_PAYLOAD 0xA0
|
||||
#define W_ACK_PAYLOAD 0xA8
|
||||
#define FLUSH_TX 0xE1
|
||||
#define FLUSH_RX 0xE2
|
||||
#define REUSE_TX_PL 0xE3
|
||||
#define RF24_NOP 0xFF
|
||||
|
||||
/* Non-P omissions */
|
||||
#define LNA_HCURR 0
|
||||
|
||||
/* P model memory Map */
|
||||
#define RPD 0x09
|
||||
#define W_TX_PAYLOAD_NO_ACK 0xB0
|
||||
|
||||
/* P model bit Mnemonics */
|
||||
#define RF_DR_LOW 5
|
||||
#define RF_DR_HIGH 3
|
||||
#define RF_PWR_LOW 1
|
||||
#define RF_PWR_HIGH 2
|
||||
|
||||
|
||||
class RF24 {
|
||||
private:
|
||||
_SPI* _spi;
|
||||
uint16_t ce_pin; /**< "Chip Enable" pin, activates the RX or TX role */
|
||||
uint16_t csn_pin; /**< SPI Chip select */
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
volatile uint8_t *cePtr;
|
||||
volatile uint8_t *csnPtr;
|
||||
uint8_t ceMask;
|
||||
uint8_t csnMask;
|
||||
#endif
|
||||
uint32_t spi_speed; /**< SPI Bus Speed */
|
||||
uint8_t status; /** The status byte returned from every SPI transaction */
|
||||
uint8_t payload_size; /**< Fixed size of payloads */
|
||||
bool dynamic_payloads_enabled; /**< Whether dynamic payloads are enabled. */
|
||||
bool ack_payloads_enabled; /**< Whether ack payloads are enabled. */
|
||||
uint8_t pipe0_reading_address[5]; /**< Last address set on pipe 0 for reading. */
|
||||
uint8_t addr_width; /**< The address width to use - 3,4 or 5 bytes. */
|
||||
uint8_t config_reg; /**< For storing the value of the NRF_CONFIG register */
|
||||
bool _is_p_variant; /** For storing the result of testing the toggleFeatures() affect */
|
||||
|
||||
|
||||
protected:
|
||||
inline void beginTransaction();
|
||||
inline void endTransaction();
|
||||
|
||||
public:
|
||||
|
||||
RF24(uint16_t _cepin, uint16_t _cspin, uint32_t _spi_speed = RF24_SPI_SPEED);
|
||||
RF24(uint32_t _spi_speed = RF24_SPI_SPEED);
|
||||
|
||||
bool begin(void);
|
||||
bool begin(uint16_t _cepin, uint16_t _cspin);
|
||||
bool begin(_SPI* spiBus);
|
||||
bool begin(_SPI* spiBus, uint16_t _cepin, uint16_t _cspin);
|
||||
bool isChipConnected();
|
||||
void startListening(void);
|
||||
void stopListening(void);
|
||||
bool available(void);
|
||||
void read(void* buf, uint8_t len);
|
||||
bool write(const void* buf, uint8_t len);
|
||||
void openWritingPipe(const uint8_t* address);
|
||||
void openReadingPipe(uint8_t number, const uint8_t* address);
|
||||
void printDetails(void);
|
||||
void printPrettyDetails(void);
|
||||
bool available(uint8_t* pipe_num);
|
||||
bool rxFifoFull();
|
||||
void powerDown(void);
|
||||
void powerUp(void);
|
||||
bool write(const void* buf, uint8_t len, const bool multicast);
|
||||
bool writeFast(const void* buf, uint8_t len);
|
||||
bool writeFast(const void* buf, uint8_t len, const bool multicast);
|
||||
bool writeBlocking(const void* buf, uint8_t len, uint32_t timeout);
|
||||
bool isWriteFinished();
|
||||
bool txStandBy();
|
||||
bool txStandBy(uint32_t timeout, bool startTx = 0);
|
||||
bool writeAckPayload(uint8_t pipe, const void* buf, uint8_t len);
|
||||
void whatHappened(bool& tx_ok, bool& tx_fail, bool& rx_ready);
|
||||
void startFastWrite(const void* buf, uint8_t len, const bool multicast, bool startTx = 1);
|
||||
bool startWrite(const void* buf, uint8_t len, const bool multicast);
|
||||
void reUseTX();
|
||||
uint8_t flush_tx(void);
|
||||
uint8_t flush_rx(void);
|
||||
bool testCarrier(void);
|
||||
bool testRPD(void);
|
||||
bool isValid();
|
||||
void closeReadingPipe(uint8_t pipe);
|
||||
bool failureDetected;
|
||||
void setAddressWidth(uint8_t a_width);
|
||||
void setRetries(uint8_t delay, uint8_t count);
|
||||
void setChannel(uint8_t channel);
|
||||
uint8_t getChannel(void);
|
||||
void setPayloadSize(uint8_t size);
|
||||
uint8_t getPayloadSize(void);
|
||||
uint8_t getDynamicPayloadSize(void);
|
||||
void enableAckPayload(void);
|
||||
void disableAckPayload(void);
|
||||
void enableDynamicPayloads(void);
|
||||
void disableDynamicPayloads(void);
|
||||
void enableDynamicAck();
|
||||
bool isPVariant(void);
|
||||
void setAutoAck(bool enable);
|
||||
void setAutoAck(uint8_t pipe, bool enable);
|
||||
void setPALevel(uint8_t level, bool lnaEnable = 1);
|
||||
uint8_t getPALevel(void);
|
||||
uint8_t getARC(void);
|
||||
bool setDataRate(rf24_datarate_e speed);
|
||||
rf24_datarate_e getDataRate(void);
|
||||
void setCRCLength(rf24_crclength_e length);
|
||||
rf24_crclength_e getCRCLength(void);
|
||||
void disableCRC(void);
|
||||
void maskIRQ(bool tx_ok, bool tx_fail, bool rx_ready);
|
||||
uint32_t txDelay;
|
||||
uint32_t csDelay;
|
||||
void startConstCarrier(rf24_pa_dbm_e level, uint8_t channel);
|
||||
void stopConstCarrier(void);
|
||||
void openReadingPipe(uint8_t number, uint64_t address);
|
||||
void openWritingPipe(uint64_t address);
|
||||
bool isAckPayloadAvailable(void);
|
||||
|
||||
private:
|
||||
void _init_obj();
|
||||
bool _init_radio();
|
||||
bool _init_pins();
|
||||
void csn(bool mode);
|
||||
void ce(bool level);
|
||||
void read_register(uint8_t reg, uint8_t* buf, uint8_t len);
|
||||
uint8_t read_register(uint8_t reg);
|
||||
void write_register(uint8_t reg, const uint8_t* buf, uint8_t len);
|
||||
void write_register(uint8_t reg, uint8_t value, bool is_cmd_only = false);
|
||||
void write_payload(const void* buf, uint8_t len, const uint8_t writeType);
|
||||
void read_payload(void* buf, uint8_t len);
|
||||
|
||||
void toggle_features(void);
|
||||
void errNotify(void);
|
||||
protected:
|
||||
uint8_t get_status(void);
|
||||
|
||||
};
|
||||
|
||||
#endif // __RF24_H__
|
611
enc28j60.cpp
Normal file
611
enc28j60.cpp
Normal file
@ -0,0 +1,611 @@
|
||||
// Microchip ENC28J60 Ethernet Interface Driver
|
||||
// Author: Guido Socher
|
||||
// Copyright: GPL V2
|
||||
//
|
||||
// Based on the enc28j60.c file from the AVRlib library by Pascal Stang.
|
||||
// For AVRlib See http://www.procyonengineering.com/
|
||||
// Used with explicit permission of Pascal Stang.
|
||||
//
|
||||
// 2010-05-20 <jc@wippler.nl>
|
||||
|
||||
#if ARDUINO >= 100
|
||||
#include <Arduino.h> // Arduino 1.0
|
||||
#else
|
||||
#include <Wprogram.h> // Arduino 0022
|
||||
#endif
|
||||
#include <SPI.h>
|
||||
#include "enc28j60.h"
|
||||
|
||||
uint16_t ENC28J60::bufferSize;
|
||||
bool ENC28J60::broadcast_enabled = false;
|
||||
bool ENC28J60::promiscuous_enabled = false;
|
||||
|
||||
// ENC28J60 Control Registers
|
||||
// Control register definitions are a combination of address,
|
||||
// bank number, and Ethernet/MAC/PHY indicator bits.
|
||||
// - Register address (bits 0-4)
|
||||
// - Bank number (bits 5-6)
|
||||
// - MAC/PHY indicator (bit 7)
|
||||
#define ADDR_MASK 0x1F
|
||||
#define BANK_MASK 0x60
|
||||
#define SPRD_MASK 0x80
|
||||
// All-bank registers
|
||||
#define EIE 0x1B
|
||||
#define EIR 0x1C
|
||||
#define ESTAT 0x1D
|
||||
#define ECON2 0x1E
|
||||
#define ECON1 0x1F
|
||||
// Bank 0 registers
|
||||
#define ERDPT (0x00|0x00)
|
||||
#define EWRPT (0x02|0x00)
|
||||
#define ETXST (0x04|0x00)
|
||||
#define ETXND (0x06|0x00)
|
||||
#define ERXST (0x08|0x00)
|
||||
#define ERXND (0x0A|0x00)
|
||||
#define ERXRDPT (0x0C|0x00)
|
||||
// #define ERXWRPT (0x0E|0x00)
|
||||
#define EDMAST (0x10|0x00)
|
||||
#define EDMAND (0x12|0x00)
|
||||
// #define EDMADST (0x14|0x00)
|
||||
#define EDMACS (0x16|0x00)
|
||||
// Bank 1 registers
|
||||
#define EHT0 (0x00|0x20)
|
||||
#define EHT1 (0x01|0x20)
|
||||
#define EHT2 (0x02|0x20)
|
||||
#define EHT3 (0x03|0x20)
|
||||
#define EHT4 (0x04|0x20)
|
||||
#define EHT5 (0x05|0x20)
|
||||
#define EHT6 (0x06|0x20)
|
||||
#define EHT7 (0x07|0x20)
|
||||
#define EPMM0 (0x08|0x20)
|
||||
#define EPMM1 (0x09|0x20)
|
||||
#define EPMM2 (0x0A|0x20)
|
||||
#define EPMM3 (0x0B|0x20)
|
||||
#define EPMM4 (0x0C|0x20)
|
||||
#define EPMM5 (0x0D|0x20)
|
||||
#define EPMM6 (0x0E|0x20)
|
||||
#define EPMM7 (0x0F|0x20)
|
||||
#define EPMCS (0x10|0x20)
|
||||
// #define EPMO (0x14|0x20)
|
||||
#define EWOLIE (0x16|0x20)
|
||||
#define EWOLIR (0x17|0x20)
|
||||
#define ERXFCON (0x18|0x20)
|
||||
#define EPKTCNT (0x19|0x20)
|
||||
// Bank 2 registers
|
||||
#define MACON1 (0x00|0x40|0x80)
|
||||
#define MACON3 (0x02|0x40|0x80)
|
||||
#define MACON4 (0x03|0x40|0x80)
|
||||
#define MABBIPG (0x04|0x40|0x80)
|
||||
#define MAIPG (0x06|0x40|0x80)
|
||||
#define MACLCON1 (0x08|0x40|0x80)
|
||||
#define MACLCON2 (0x09|0x40|0x80)
|
||||
#define MAMXFL (0x0A|0x40|0x80)
|
||||
#define MAPHSUP (0x0D|0x40|0x80)
|
||||
#define MICON (0x11|0x40|0x80)
|
||||
#define MICMD (0x12|0x40|0x80)
|
||||
#define MIREGADR (0x14|0x40|0x80)
|
||||
#define MIWR (0x16|0x40|0x80)
|
||||
#define MIRD (0x18|0x40|0x80)
|
||||
// Bank 3 registers
|
||||
#define MAADR1 (0x00|0x60|0x80)
|
||||
#define MAADR0 (0x01|0x60|0x80)
|
||||
#define MAADR3 (0x02|0x60|0x80)
|
||||
#define MAADR2 (0x03|0x60|0x80)
|
||||
#define MAADR5 (0x04|0x60|0x80)
|
||||
#define MAADR4 (0x05|0x60|0x80)
|
||||
#define EBSTSD (0x06|0x60)
|
||||
#define EBSTCON (0x07|0x60)
|
||||
#define EBSTCS (0x08|0x60)
|
||||
#define MISTAT (0x0A|0x60|0x80)
|
||||
#define EREVID (0x12|0x60)
|
||||
#define ECOCON (0x15|0x60)
|
||||
#define EFLOCON (0x17|0x60)
|
||||
#define EPAUS (0x18|0x60)
|
||||
|
||||
// ENC28J60 ERXFCON Register Bit Definitions
|
||||
#define ERXFCON_UCEN 0x80
|
||||
#define ERXFCON_ANDOR 0x40
|
||||
#define ERXFCON_CRCEN 0x20
|
||||
#define ERXFCON_PMEN 0x10
|
||||
#define ERXFCON_MPEN 0x08
|
||||
#define ERXFCON_HTEN 0x04
|
||||
#define ERXFCON_MCEN 0x02
|
||||
#define ERXFCON_BCEN 0x01
|
||||
// ENC28J60 EIE Register Bit Definitions
|
||||
#define EIE_INTIE 0x80
|
||||
#define EIE_PKTIE 0x40
|
||||
#define EIE_DMAIE 0x20
|
||||
#define EIE_LINKIE 0x10
|
||||
#define EIE_TXIE 0x08
|
||||
#define EIE_WOLIE 0x04
|
||||
#define EIE_TXERIE 0x02
|
||||
#define EIE_RXERIE 0x01
|
||||
// ENC28J60 EIR Register Bit Definitions
|
||||
#define EIR_PKTIF 0x40
|
||||
#define EIR_DMAIF 0x20
|
||||
#define EIR_LINKIF 0x10
|
||||
#define EIR_TXIF 0x08
|
||||
#define EIR_WOLIF 0x04
|
||||
#define EIR_TXERIF 0x02
|
||||
#define EIR_RXERIF 0x01
|
||||
// ENC28J60 ESTAT Register Bit Definitions
|
||||
#define ESTAT_INT 0x80
|
||||
#define ESTAT_LATECOL 0x10
|
||||
#define ESTAT_RXBUSY 0x04
|
||||
#define ESTAT_TXABRT 0x02
|
||||
#define ESTAT_CLKRDY 0x01
|
||||
// ENC28J60 ECON2 Register Bit Definitions
|
||||
#define ECON2_AUTOINC 0x80
|
||||
#define ECON2_PKTDEC 0x40
|
||||
#define ECON2_PWRSV 0x20
|
||||
#define ECON2_VRPS 0x08
|
||||
// ENC28J60 ECON1 Register Bit Definitions
|
||||
#define ECON1_TXRST 0x80
|
||||
#define ECON1_RXRST 0x40
|
||||
#define ECON1_DMAST 0x20
|
||||
#define ECON1_CSUMEN 0x10
|
||||
#define ECON1_TXRTS 0x08
|
||||
#define ECON1_RXEN 0x04
|
||||
#define ECON1_BSEL1 0x02
|
||||
#define ECON1_BSEL0 0x01
|
||||
// ENC28J60 MACON1 Register Bit Definitions
|
||||
#define MACON1_LOOPBK 0x10
|
||||
#define MACON1_TXPAUS 0x08
|
||||
#define MACON1_RXPAUS 0x04
|
||||
#define MACON1_PASSALL 0x02
|
||||
#define MACON1_MARXEN 0x01
|
||||
// ENC28J60 MACON3 Register Bit Definitions
|
||||
#define MACON3_PADCFG2 0x80
|
||||
#define MACON3_PADCFG1 0x40
|
||||
#define MACON3_PADCFG0 0x20
|
||||
#define MACON3_TXCRCEN 0x10
|
||||
#define MACON3_PHDRLEN 0x08
|
||||
#define MACON3_HFRMLEN 0x04
|
||||
#define MACON3_FRMLNEN 0x02
|
||||
#define MACON3_FULDPX 0x01
|
||||
// ENC28J60 MICMD Register Bit Definitions
|
||||
#define MICMD_MIISCAN 0x02
|
||||
#define MICMD_MIIRD 0x01
|
||||
// ENC28J60 MISTAT Register Bit Definitions
|
||||
#define MISTAT_NVALID 0x04
|
||||
#define MISTAT_SCAN 0x02
|
||||
#define MISTAT_BUSY 0x01
|
||||
|
||||
// ENC28J60 EBSTCON Register Bit Definitions
|
||||
#define EBSTCON_PSV2 0x80
|
||||
#define EBSTCON_PSV1 0x40
|
||||
#define EBSTCON_PSV0 0x20
|
||||
#define EBSTCON_PSEL 0x10
|
||||
#define EBSTCON_TMSEL1 0x08
|
||||
#define EBSTCON_TMSEL0 0x04
|
||||
#define EBSTCON_TME 0x02
|
||||
#define EBSTCON_BISTST 0x01
|
||||
|
||||
// PHY registers
|
||||
#define PHCON1 0x00
|
||||
#define PHSTAT1 0x01
|
||||
#define PHHID1 0x02
|
||||
#define PHHID2 0x03
|
||||
#define PHCON2 0x10
|
||||
#define PHSTAT2 0x11
|
||||
#define PHIE 0x12
|
||||
#define PHIR 0x13
|
||||
#define PHLCON 0x14
|
||||
|
||||
// ENC28J60 PHY PHCON1 Register Bit Definitions
|
||||
#define PHCON1_PRST 0x8000
|
||||
#define PHCON1_PLOOPBK 0x4000
|
||||
#define PHCON1_PPWRSV 0x0800
|
||||
#define PHCON1_PDPXMD 0x0100
|
||||
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
|
||||
#define PHSTAT1_PFDPX 0x1000
|
||||
#define PHSTAT1_PHDPX 0x0800
|
||||
#define PHSTAT1_LLSTAT 0x0004
|
||||
#define PHSTAT1_JBSTAT 0x0002
|
||||
// ENC28J60 PHY PHCON2 Register Bit Definitions
|
||||
#define PHCON2_FRCLINK 0x4000
|
||||
#define PHCON2_TXDIS 0x2000
|
||||
#define PHCON2_JABBER 0x0400
|
||||
#define PHCON2_HDLDIS 0x0100
|
||||
|
||||
// ENC28J60 Packet Control Byte Bit Definitions
|
||||
#define PKTCTRL_PHUGEEN 0x08
|
||||
#define PKTCTRL_PPADEN 0x04
|
||||
#define PKTCTRL_PCRCEN 0x02
|
||||
#define PKTCTRL_POVERRIDE 0x01
|
||||
|
||||
// SPI operation codes
|
||||
#define ENC28J60_READ_CTRL_REG 0x00
|
||||
#define ENC28J60_READ_BUF_MEM 0x3A
|
||||
#define ENC28J60_WRITE_CTRL_REG 0x40
|
||||
#define ENC28J60_WRITE_BUF_MEM 0x7A
|
||||
#define ENC28J60_BIT_FIELD_SET 0x80
|
||||
#define ENC28J60_BIT_FIELD_CLR 0xA0
|
||||
#define ENC28J60_SOFT_RESET 0xFF
|
||||
|
||||
// max frame length which the controller will accept:
|
||||
// (note: maximum ethernet frame length would be 1518)
|
||||
#define MAX_FRAMELEN 1500
|
||||
|
||||
#define SPI_SPEED 10000000
|
||||
|
||||
static byte Enc28j60Bank;
|
||||
static byte selectPin;
|
||||
static uint8_t selectMask;
|
||||
volatile static uint8_t *selectPort;
|
||||
|
||||
void ENC28J60::initSPI () {
|
||||
pinMode(selectPin, OUTPUT);
|
||||
digitalWrite(selectPin, HIGH);
|
||||
pinMode(MOSI, OUTPUT);
|
||||
pinMode(SCK, OUTPUT);
|
||||
pinMode(MISO, INPUT);
|
||||
|
||||
digitalWrite(MOSI, HIGH);
|
||||
digitalWrite(MOSI, LOW);
|
||||
digitalWrite(SCK, LOW);
|
||||
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
selectPort = portOutputRegister(digitalPinToPort(selectPin));
|
||||
selectMask = digitalPinToBitMask(selectPin);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void enableChip () {
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
cli();
|
||||
*selectPort &= ~selectMask;
|
||||
sei();
|
||||
#else
|
||||
digitalWrite(selectPin, LOW);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void disableChip () {
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
cli();
|
||||
*selectPort |= selectMask;
|
||||
sei();
|
||||
#else
|
||||
digitalWrite(selectPin, HIGH);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void beginTransaction() {
|
||||
SPI.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE0));
|
||||
enableChip();
|
||||
}
|
||||
|
||||
static void endTransaction() {
|
||||
disableChip();
|
||||
SPI.endTransaction();
|
||||
}
|
||||
|
||||
static byte readOp (byte op, byte address) {
|
||||
beginTransaction();
|
||||
SPI.transfer(op | (address & ADDR_MASK));
|
||||
byte result = SPI.transfer(0x00);
|
||||
if (address & 0x80)
|
||||
result = SPI.transfer(0x00);
|
||||
endTransaction();
|
||||
return result;
|
||||
}
|
||||
|
||||
static void writeOp (byte op, byte address, byte data) {
|
||||
beginTransaction();
|
||||
SPI.transfer(op | (address & ADDR_MASK));
|
||||
SPI.transfer(data);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
static void readBuf(uint16_t len, byte* data) {
|
||||
beginTransaction();
|
||||
SPI.transfer(ENC28J60_READ_BUF_MEM);
|
||||
while (len--)
|
||||
*data++ = SPI.transfer(0x00);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
static void writeBuf(uint16_t len, const byte* data) {
|
||||
beginTransaction();
|
||||
SPI.transfer(ENC28J60_WRITE_BUF_MEM);
|
||||
while (len--)
|
||||
SPI.transfer(*data++);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
static void SetBank (byte address) {
|
||||
if ((address & BANK_MASK) != Enc28j60Bank) {
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_BSEL1|ECON1_BSEL0);
|
||||
Enc28j60Bank = address & BANK_MASK;
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, Enc28j60Bank>>5);
|
||||
}
|
||||
}
|
||||
|
||||
static byte readRegByte (byte address) {
|
||||
SetBank(address);
|
||||
return readOp(ENC28J60_READ_CTRL_REG, address);
|
||||
}
|
||||
|
||||
static uint16_t readReg(byte address) {
|
||||
return readRegByte(address) + (readRegByte(address+1) << 8);
|
||||
}
|
||||
|
||||
static void writeRegByte (byte address, byte data) {
|
||||
SetBank(address);
|
||||
writeOp(ENC28J60_WRITE_CTRL_REG, address, data);
|
||||
}
|
||||
|
||||
static void writeReg(byte address, uint16_t data) {
|
||||
writeRegByte(address, data);
|
||||
writeRegByte(address + 1, data >> 8);
|
||||
}
|
||||
|
||||
static uint16_t readPhyByte (byte address) {
|
||||
writeRegByte(MIREGADR, address);
|
||||
writeRegByte(MICMD, MICMD_MIIRD);
|
||||
while (readRegByte(MISTAT) & MISTAT_BUSY)
|
||||
;
|
||||
writeRegByte(MICMD, 0x00);
|
||||
return readRegByte(MIRD+1);
|
||||
}
|
||||
|
||||
static void writePhy (byte address, uint16_t data) {
|
||||
writeRegByte(MIREGADR, address);
|
||||
writeReg(MIWR, data);
|
||||
while (readRegByte(MISTAT) & MISTAT_BUSY)
|
||||
;
|
||||
}
|
||||
|
||||
byte ENC28J60::initialize (uint16_t size, const byte* macaddr, byte csPin) {
|
||||
bufferSize = size;
|
||||
selectPin = csPin;
|
||||
initSPI();
|
||||
disableChip();
|
||||
|
||||
writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
|
||||
delay(2); // errata B7/2
|
||||
while (!readOp(ENC28J60_READ_CTRL_REG, ESTAT) & ESTAT_CLKRDY)
|
||||
;
|
||||
|
||||
writeReg(ERXST, RXSTART_INIT);
|
||||
writeReg(ERXRDPT, RXSTART_INIT);
|
||||
writeReg(ERXND, RXSTOP_INIT);
|
||||
writeReg(ETXST, TXSTART_INIT);
|
||||
writeReg(ETXND, TXSTOP_INIT);
|
||||
|
||||
// Stretch pulses for LED, LED_A=Link, LED_B=activity
|
||||
writePhy(PHLCON, 0x476);
|
||||
|
||||
writeRegByte(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN|ERXFCON_BCEN);
|
||||
writeReg(EPMM0, 0x303f);
|
||||
writeReg(EPMCS, 0xf7f9);
|
||||
writeRegByte(MACON1, MACON1_MARXEN);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, MACON3,
|
||||
MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);
|
||||
writeReg(MAIPG, 0x0C12);
|
||||
writeRegByte(MABBIPG, 0x12);
|
||||
writeReg(MAMXFL, MAX_FRAMELEN);
|
||||
writeRegByte(MAADR5, macaddr[0]);
|
||||
writeRegByte(MAADR4, macaddr[1]);
|
||||
writeRegByte(MAADR3, macaddr[2]);
|
||||
writeRegByte(MAADR2, macaddr[3]);
|
||||
writeRegByte(MAADR1, macaddr[4]);
|
||||
writeRegByte(MAADR0, macaddr[5]);
|
||||
writePhy(PHCON2, PHCON2_HDLDIS);
|
||||
SetBank(ECON1);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
|
||||
|
||||
byte rev = readRegByte(EREVID);
|
||||
// microchip forgot to step the number on the silicon when they
|
||||
// released the revision B7. 6 is now rev B7. We still have
|
||||
// to see what they do when they release B8. At the moment
|
||||
// there is no B8 out yet
|
||||
if (rev > 5) ++rev;
|
||||
return rev;
|
||||
}
|
||||
|
||||
bool ENC28J60::isLinkUp() {
|
||||
return (readPhyByte(PHSTAT2) >> 2) & 1;
|
||||
}
|
||||
|
||||
/*
|
||||
struct __attribute__((__packed__)) transmit_status_vector {
|
||||
uint16_t transmitByteCount;
|
||||
byte transmitCollisionCount : 4;
|
||||
byte transmitCrcError : 1;
|
||||
byte transmitLengthCheckError : 1;
|
||||
byte transmitLengthOutRangeError : 1;
|
||||
byte transmitDone : 1;
|
||||
byte transmitMulticast : 1;
|
||||
byte transmitBroadcast : 1;
|
||||
byte transmitPacketDefer : 1;
|
||||
byte transmitExcessiveDefer : 1;
|
||||
byte transmitExcessiveCollision : 1;
|
||||
byte transmitLateCollision : 1;
|
||||
byte transmitGiant : 1;
|
||||
byte transmitUnderrun : 1;
|
||||
uint16_t totalTransmitted;
|
||||
byte transmitControlFrame : 1;
|
||||
byte transmitPauseControlFrame : 1;
|
||||
byte backpressureApplied : 1;
|
||||
byte transmitVLAN : 1;
|
||||
byte zero : 4;
|
||||
};
|
||||
*/
|
||||
|
||||
struct transmit_status_vector {
|
||||
uint8_t bytes[7];
|
||||
};
|
||||
|
||||
#if ETHERCARD_SEND_PIPELINING
|
||||
#define BREAKORCONTINUE retry=0; continue;
|
||||
#else
|
||||
#define BREAKORCONTINUE break;
|
||||
#endif
|
||||
|
||||
void ENC28J60::packetSend(uint16_t len) {
|
||||
byte retry = 0;
|
||||
|
||||
#if ETHERCARD_SEND_PIPELINING
|
||||
goto resume_last_transmission;
|
||||
#endif
|
||||
while (1) {
|
||||
// latest errata sheet: DS80349C
|
||||
// always reset transmit logic (Errata Issue 12)
|
||||
// the Microchip TCP/IP stack implementation used to first check
|
||||
// whether TXERIF is set and only then reset the transmit logic
|
||||
// but this has been changed in later versions; possibly they
|
||||
// have a reason for this; they don't mention this in the errata
|
||||
// sheet
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST);
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST);
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, EIR, EIR_TXERIF|EIR_TXIF);
|
||||
|
||||
// prepare new transmission
|
||||
if (retry == 0) {
|
||||
writeReg(EWRPT, TXSTART_INIT);
|
||||
writeReg(ETXND, TXSTART_INIT+len);
|
||||
writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
|
||||
writeBuf(len, buffer);
|
||||
}
|
||||
|
||||
// initiate transmission
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
|
||||
#if ETHERCARD_SEND_PIPELINING
|
||||
if (retry == 0) return;
|
||||
#endif
|
||||
|
||||
resume_last_transmission:
|
||||
|
||||
// wait until transmission has finished; referring to the data sheet and
|
||||
// to the errata (Errata Issue 13; Example 1) you only need to wait until either
|
||||
// TXIF or TXERIF gets set; however this leads to hangs; apparently Microchip
|
||||
// realized this and in later implementations of their tcp/ip stack they introduced
|
||||
// a counter to avoid hangs; of course they didn't update the errata sheet
|
||||
uint16_t count = 0;
|
||||
while ((readRegByte(EIR) & (EIR_TXIF | EIR_TXERIF)) == 0 && ++count < 1000U)
|
||||
;
|
||||
|
||||
if (!(readRegByte(EIR) & EIR_TXERIF) && count < 1000U) {
|
||||
// no error; start new transmission
|
||||
BREAKORCONTINUE
|
||||
}
|
||||
|
||||
// cancel previous transmission if stuck
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
|
||||
|
||||
#if ETHERCARD_RETRY_LATECOLLISIONS == 0
|
||||
BREAKORCONTINUE
|
||||
#endif
|
||||
|
||||
// Check whether the chip thinks that a late collision occurred; the chip
|
||||
// may be wrong (Errata Issue 13); therefore we retry. We could check
|
||||
// LATECOL in the ESTAT register in order to find out whether the chip
|
||||
// thinks a late collision occurred but (Errata Issue 15) tells us that
|
||||
// this is not working. Therefore we check TSV
|
||||
transmit_status_vector tsv;
|
||||
uint16_t etxnd = readReg(ETXND);
|
||||
writeReg(ERDPT, etxnd+1);
|
||||
readBuf(sizeof(transmit_status_vector), (byte*) &tsv);
|
||||
// LATECOL is bit number 29 in TSV (starting from 0)
|
||||
|
||||
if (!((readRegByte(EIR) & EIR_TXERIF) && (tsv.bytes[3] & 1<<5) /*tsv.transmitLateCollision*/) || retry > 16U) {
|
||||
// there was some error but no LATECOL so we do not repeat
|
||||
BREAKORCONTINUE
|
||||
}
|
||||
|
||||
retry++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint16_t ENC28J60::packetReceive() {
|
||||
static uint16_t gNextPacketPtr = RXSTART_INIT;
|
||||
static bool unreleasedPacket = false;
|
||||
uint16_t len = 0;
|
||||
|
||||
if (unreleasedPacket) {
|
||||
if (gNextPacketPtr == 0)
|
||||
writeReg(ERXRDPT, RXSTOP_INIT);
|
||||
else
|
||||
writeReg(ERXRDPT, gNextPacketPtr - 1);
|
||||
unreleasedPacket = false;
|
||||
}
|
||||
|
||||
if (readRegByte(EPKTCNT) > 0) {
|
||||
writeReg(ERDPT, gNextPacketPtr);
|
||||
|
||||
struct {
|
||||
uint16_t nextPacket;
|
||||
uint16_t byteCount;
|
||||
uint16_t status;
|
||||
} header;
|
||||
|
||||
readBuf(sizeof header, (byte*) &header);
|
||||
|
||||
gNextPacketPtr = header.nextPacket;
|
||||
len = header.byteCount - 4; //remove the CRC count
|
||||
if (len>bufferSize) len=0; // discard messages too long **NMCK**
|
||||
if ((header.status & 0x80)==0)
|
||||
len = 0;
|
||||
else
|
||||
readBuf(len, buffer);
|
||||
unreleasedPacket = true;
|
||||
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// Contributed by Alex M. Based on code from: http://blog.derouineau.fr
|
||||
// /2011/07/putting-enc28j60-ethernet-controler-in-sleep-mode/
|
||||
void ENC28J60::powerDown() {
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_RXEN);
|
||||
while(readRegByte(ESTAT) & ESTAT_RXBUSY);
|
||||
while(readRegByte(ECON1) & ECON1_TXRTS);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_VRPS);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PWRSV);
|
||||
}
|
||||
|
||||
void ENC28J60::powerUp() {
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, ECON2, ECON2_PWRSV);
|
||||
while(!readRegByte(ESTAT) & ESTAT_CLKRDY);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
|
||||
}
|
||||
|
||||
void ENC28J60::enableBroadcast (bool temporary) {
|
||||
writeRegByte(ERXFCON, readRegByte(ERXFCON) | ERXFCON_BCEN);
|
||||
if(!temporary)
|
||||
broadcast_enabled = true;
|
||||
}
|
||||
|
||||
void ENC28J60::disableBroadcast (bool temporary) {
|
||||
if(!temporary)
|
||||
broadcast_enabled = false;
|
||||
if(!broadcast_enabled)
|
||||
writeRegByte(ERXFCON, readRegByte(ERXFCON) & ~ERXFCON_BCEN);
|
||||
}
|
||||
|
||||
void ENC28J60::enableMulticast () {
|
||||
writeRegByte(ERXFCON, readRegByte(ERXFCON) | ERXFCON_MCEN);
|
||||
}
|
||||
|
||||
void ENC28J60::disableMulticast () {
|
||||
writeRegByte(ERXFCON, readRegByte(ERXFCON) & ~ERXFCON_MCEN);
|
||||
}
|
||||
|
||||
void ENC28J60::enablePromiscuous (bool temporary) {
|
||||
writeRegByte(ERXFCON, readRegByte(ERXFCON) & ERXFCON_CRCEN);
|
||||
if(!temporary)
|
||||
promiscuous_enabled = true;
|
||||
}
|
||||
|
||||
void ENC28J60::disablePromiscuous (bool temporary) {
|
||||
if(!temporary)
|
||||
promiscuous_enabled = false;
|
||||
if(!promiscuous_enabled) {
|
||||
writeRegByte(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN|ERXFCON_BCEN);
|
||||
}
|
||||
}
|
207
enc28j60.h
Normal file
207
enc28j60.h
Normal file
@ -0,0 +1,207 @@
|
||||
// Microchip ENC28J60 Ethernet Interface Driver
|
||||
// Author: Pascal Stang
|
||||
// Modified by: Guido Socher
|
||||
// Copyright: GPL V2
|
||||
//
|
||||
// This driver provides initialization and transmit/receive
|
||||
// functions for the Microchip ENC28J60 10Mb Ethernet Controller and PHY.
|
||||
// This chip is novel in that it is a full MAC+PHY interface all in a 28-pin
|
||||
// chip, using an SPI interface to the host processor.
|
||||
//
|
||||
// 2010-05-20 <jc@wippler.nl>
|
||||
/** @file */
|
||||
|
||||
#ifndef ENC28J60_H
|
||||
#define ENC28J60_H
|
||||
|
||||
// buffer boundaries applied to internal 8K ram
|
||||
// the entire available packet buffer space is allocated
|
||||
|
||||
#define RXSTART_INIT 0x0000 // start of RX buffer, (must be zero, Rev. B4 Errata point 5)
|
||||
#define RXSTOP_INIT 0x0BFF // end of RX buffer, room for 2 packets
|
||||
|
||||
#define TXSTART_INIT 0x0C00 // start of TX buffer, room for 1 packet
|
||||
#define TXSTOP_INIT 0x11FF // end of TX buffer
|
||||
|
||||
#define SCRATCH_START 0x1200 // start of scratch area
|
||||
#define SCRATCH_LIMIT 0x2000 // past end of area, i.e. 3 Kb
|
||||
#define SCRATCH_PAGE_SHIFT 6 // addressing is in pages of 64 bytes
|
||||
#define SCRATCH_PAGE_SIZE (1 << SCRATCH_PAGE_SHIFT)
|
||||
#define SCRATCH_PAGE_NUM ((SCRATCH_LIMIT-SCRATCH_START) >> SCRATCH_PAGE_SHIFT)
|
||||
#define SCRATCH_MAP_SIZE (((SCRATCH_PAGE_NUM % 8) == 0) ? (SCRATCH_PAGE_NUM / 8) : (SCRATCH_PAGE_NUM/8+1))
|
||||
|
||||
// area in the enc memory that can be used via enc_malloc; by default 0 bytes; decrease SCRATCH_LIMIT in order
|
||||
// to use this functionality
|
||||
#define ENC_HEAP_START SCRATCH_LIMIT
|
||||
#define ENC_HEAP_END 0x2000
|
||||
|
||||
/** This class provide low-level interfacing with the ENC28J60 network interface. This is used by the EtherCard class and not intended for use by (normal) end users. */
|
||||
class ENC28J60 {
|
||||
public:
|
||||
static uint8_t buffer[]; //!< Data buffer (shared by receive and transmit)
|
||||
static uint16_t bufferSize; //!< Size of data buffer
|
||||
static bool broadcast_enabled; //!< True if broadcasts enabled (used to allow temporary disable of broadcast for DHCP or other internal functions)
|
||||
static bool promiscuous_enabled; //!< True if promiscuous mode enabled (used to allow temporary disable of promiscuous mode)
|
||||
|
||||
static uint8_t* tcpOffset () { return buffer + 0x36; } //!< Pointer to the start of TCP payload
|
||||
|
||||
/** @brief Initialise SPI interface
|
||||
* @note Configures Arduino pins as input / output, etc.
|
||||
*/
|
||||
static void initSPI ();
|
||||
|
||||
/** @brief Initialise network interface
|
||||
* @param size Size of data buffer
|
||||
* @param macaddr Pointer to 6 byte hardware (MAC) address
|
||||
* @param csPin Arduino pin used for chip select (enable network interface SPI bus). Default = 8
|
||||
* @return <i>uint8_t</i> ENC28J60 firmware version or zero on failure.
|
||||
*/
|
||||
static uint8_t initialize (const uint16_t size, const uint8_t* macaddr,
|
||||
uint8_t csPin = 8);
|
||||
|
||||
/** @brief Check if network link is connected
|
||||
* @return <i>bool</i> True if link is up
|
||||
*/
|
||||
static bool isLinkUp ();
|
||||
|
||||
/** @brief Sends data to network interface
|
||||
* @param len Size of data to send
|
||||
* @note Data buffer is shared by receive and transmit functions
|
||||
*/
|
||||
static void packetSend (uint16_t len);
|
||||
|
||||
/** @brief Copy received packets to data buffer
|
||||
* @return <i>uint16_t</i> Size of received data
|
||||
* @note Data buffer is shared by receive and transmit functions
|
||||
*/
|
||||
static uint16_t packetReceive ();
|
||||
|
||||
/** @brief Copy data from ENC28J60 memory
|
||||
* @param page Data page of memory
|
||||
* @param data Pointer to buffer to copy data to
|
||||
*/
|
||||
static void copyout (uint8_t page, const uint8_t* data);
|
||||
|
||||
/** @brief Copy data to ENC28J60 memory
|
||||
* @param page Data page of memory
|
||||
* @param data Pointer to buffer to copy data from
|
||||
*/
|
||||
static void copyin (uint8_t page, uint8_t* data);
|
||||
|
||||
/** @brief Get single byte of data from ENC28J60 memory
|
||||
* @param page Data page of memory
|
||||
* @param off Offset of data within page
|
||||
* @return Data value
|
||||
*/
|
||||
static uint8_t peekin (uint8_t page, uint8_t off);
|
||||
|
||||
/** @brief Put ENC28J60 in sleep mode
|
||||
*/
|
||||
static void powerDown(); // contrib by Alex M.
|
||||
|
||||
/** @brief Wake ENC28J60 from sleep mode
|
||||
*/
|
||||
static void powerUp(); // contrib by Alex M.
|
||||
|
||||
/** @brief Enable reception of broadcast messages
|
||||
* @param temporary Set true to temporarily enable broadcast
|
||||
* @note This will increase load on received data handling
|
||||
*/
|
||||
static void enableBroadcast(bool temporary = false);
|
||||
|
||||
/** @brief Disable reception of broadcast messages
|
||||
* @param temporary Set true to only disable if temporarily enabled
|
||||
* @note This will reduce load on received data handling
|
||||
*/
|
||||
static void disableBroadcast(bool temporary = false);
|
||||
|
||||
/** @brief Enables reception of multicast messages
|
||||
* @note This will increase load on received data handling
|
||||
*/
|
||||
static void enableMulticast ();
|
||||
|
||||
/** @brief Enables reception of all messages
|
||||
* @param temporary Set true to temporarily enable promiscuous
|
||||
* @note This will increase load significantly on received data handling
|
||||
* @note All messages will be accepted, even messages with destination MAC other than own
|
||||
* @note Messages with invalid CRC checksum will still be rejected
|
||||
*/
|
||||
static void enablePromiscuous (bool temporary = false);
|
||||
|
||||
/** @brief Disable reception of all messages and go back to default mode
|
||||
* @param temporary Set true to only disable if temporarily enabled
|
||||
* @note This will reduce load on received data handling
|
||||
* @note In this mode only unicast and broadcast messages will be received
|
||||
*/
|
||||
static void disablePromiscuous(bool temporary = false);
|
||||
|
||||
/** @brief Disable reception of multicast messages
|
||||
* @note This will reduce load on received data handling
|
||||
*/
|
||||
static void disableMulticast();
|
||||
|
||||
/** @brief Reset and fully initialise ENC28J60
|
||||
* @param csPin Arduino pin used for chip select (enable SPI bus)
|
||||
* @return <i>uint8_t</i> 0 on failure
|
||||
*/
|
||||
static uint8_t doBIST(uint8_t csPin = 8);
|
||||
|
||||
/** @brief Copies a slice from the current packet to RAM
|
||||
* @param dest pointer in RAM where the data is copied to
|
||||
* @param maxlength how many bytes to copy;
|
||||
* @param packetOffset where within the packet to start; if less than maxlength bytes are available only the remaining bytes are copied.
|
||||
* @return <i>uint16_t</i> the number of bytes that have been read
|
||||
* @note At the destination at least maxlength+1 bytes should be reserved because the copied content will be 0-terminated.
|
||||
*/
|
||||
static uint16_t readPacketSlice(char* dest, int16_t maxlength, int16_t packetOffset);
|
||||
|
||||
/** @brief reserves a block of RAM in the memory of the enc chip
|
||||
* @param size number of bytes to reserve
|
||||
* @return <i>uint16_t</i> start address of the block within the enc memory. 0 if the remaining memory for malloc operation is less than size.
|
||||
* @note There is no enc_free(), i.e., reserved blocks stay reserved for the duration of the program.
|
||||
* @note The total memory available for malloc-operations is determined by ENC_HEAP_END-ENC_HEAP_START, defined in enc28j60.h; by default this is 0, i.e., you have to change these values in order to use enc_malloc().
|
||||
*/
|
||||
static uint16_t enc_malloc(uint16_t size);
|
||||
|
||||
/** @brief returns the amount of memory within the enc28j60 chip that is still available for malloc.
|
||||
* @return <i>uint16_t</i> the amount of memory in bytes.
|
||||
*/
|
||||
static uint16_t enc_freemem();
|
||||
|
||||
/** @brief copies a block of data from SRAM to the enc memory
|
||||
@param dest destination address within enc memory
|
||||
@param source source pointer to a block of SRAM in the arduino
|
||||
@param num number of bytes to copy
|
||||
@note There is no sanity check. Handle with care
|
||||
*/
|
||||
static void memcpy_to_enc(uint16_t dest, void* source, int16_t num);
|
||||
|
||||
/** @brief copies a block of data from the enc memory to SRAM
|
||||
@param dest destination address within SRAM
|
||||
@param source source address within enc memory
|
||||
@param num number of bytes to copy
|
||||
*/
|
||||
static void memcpy_from_enc(void* dest, uint16_t source, int16_t num);
|
||||
};
|
||||
|
||||
// typedef ENC28J60 Ethernet; //!< Define alias Ethernet for ENC28J60
|
||||
|
||||
|
||||
/** Workaround for Errata 13.
|
||||
* The transmission hardware may drop some packets because it thinks a late collision
|
||||
* occurred (which should never happen if all cable length etc. are ok). If setting
|
||||
* this to 1 these packages will be retried a fixed number of times. Costs about 150bytes
|
||||
* of flash.
|
||||
*/
|
||||
#define ETHERCARD_RETRY_LATECOLLISIONS 0
|
||||
|
||||
/** Enable pipelining of packet transmissions.
|
||||
* If enabled the packetSend function will not block/wait until the packet is actually
|
||||
* transmitted; but instead this wait is shifted to the next time that packetSend is
|
||||
* called. This gives higher performance; however in combination with
|
||||
* ETHERCARD_RETRY_LATECOLLISIONS this may lead to problems because a packet whose
|
||||
* transmission fails because the ENC-chip thinks that it is a late collision will
|
||||
* not be retried until the next call to packetSend.
|
||||
*/
|
||||
#define ETHERCARD_SEND_PIPELINING 1
|
||||
#endif
|
@ -9,16 +9,17 @@
|
||||
// To prevent this, temporarily rename it to mySetup.txt or similar.
|
||||
//
|
||||
|
||||
// Only the #include directives relating to the devices in use need be included here.
|
||||
#include "IODevice.h"
|
||||
#include "Turnouts.h"
|
||||
#include "Sensors.h"
|
||||
#include "IO_HCSR04.h"
|
||||
#include "IO_VL53L0X.h"
|
||||
|
||||
|
||||
// The #if directive prevent compile errors for Uno and Nano by excluding the
|
||||
// HAL directives from the build.
|
||||
#if !defined(IO_NO_HAL)
|
||||
#include "DFPlayer.h"
|
||||
#include "IO_Network.h"
|
||||
#include "Net_RF24"
|
||||
#include "Net_ENC28J60"
|
||||
#include "Net_Ethernet"
|
||||
|
||||
|
||||
// Examples of statically defined HAL directives (alternative to the create() call).
|
||||
@ -81,49 +82,6 @@
|
||||
//PCF8574 gpioModule4(200, 8, 0x23, 40);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an HCSR04 ultrasonic ranging module.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// Vpin=2000 (only one VPIN per directive)
|
||||
// Number of VPINs=1
|
||||
// Arduino pin connected to TRIG=30
|
||||
// Arduino pin connected to ECHO=31
|
||||
// Minimum trigger range=20cm (VPIN goes to 1 when <20cm)
|
||||
// Maximum trigger range=25cm (VPIN goes to 0 when >25cm)
|
||||
// Note: Multiple devices can be configured by using a different ECHO pin
|
||||
// for each one. The TRIG pin can be shared between multiple devices.
|
||||
// Be aware that the 'ping' of one device may be received by another
|
||||
// device and position them accordingly!
|
||||
|
||||
//HCSR04 sonarModule1(2000, 30, 31, 20, 25);
|
||||
//HCSR04 sonarModule2(2001, 30, 32, 20, 25);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines a single VL53L0X Time-of-Flight range sensor.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// VPIN=5000
|
||||
// Number of VPINs=1
|
||||
// I2C address=0x29 (default for this chip)
|
||||
// Minimum trigger range=200mm (VPIN goes to 1 when <20cm)
|
||||
// Maximum trigger range=250mm (VPIN goes to 0 when >25cm)
|
||||
|
||||
//VL53L0X tofModule1(5000, 1, 0x29, 200, 250);
|
||||
|
||||
// For multiple VL53L0X modules, add another parameter which is a VPIN connected to the
|
||||
// module's XSHUT pin. This allows the modules to be configured, at start,
|
||||
// with distinct I2C addresses. In this case, the address 0x29 is only used during
|
||||
// initialisation to configure each device in turn with the desired unique I2C address.
|
||||
// The examples below have the modules' XSHUT pins connected to the first two pins of
|
||||
// the first MCP23017 module (164 and 165), but Arduino pins may be used instead.
|
||||
// The first module here is given I2C address 0x30 and the second is 0x31.
|
||||
|
||||
//VL53L0X tofModule1(5000, 1, 0x30, 200, 250, 164);
|
||||
//VL53L0X tofModule2(5001, 1, 0x31, 200, 250, 165);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The function mySetup() is invoked from CS if it exists within the build.
|
||||
// It is called just before mysetup.h is executed, so things set up within here can be
|
||||
@ -179,6 +137,50 @@ void mySetup() {
|
||||
// Sensor::create(i, i, 1);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an HCSR04 ultrasonic ranging module.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// Vpin=2000 (only one VPIN per directive)
|
||||
// Number of VPINs=1
|
||||
// Arduino pin connected to TRIG=30
|
||||
// Arduino pin connected to ECHO=31
|
||||
// Minimum trigger range=20cm (VPIN goes to 1 when <20cm)
|
||||
// Maximum trigger range=25cm (VPIN goes to 0 when >25cm)
|
||||
// Note: Multiple devices can be configured by using a different ECHO pin
|
||||
// for each one. The TRIG pin can be shared between multiple devices.
|
||||
// Be aware that the 'ping' of one device may be received by another
|
||||
// device and position them accordingly!
|
||||
|
||||
//HCSR04 sonarModule1(2000, 30, 31, 20, 25);
|
||||
//HCSR04 sonarModule2(2001, 30, 32, 20, 25);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// VL53L0X Time-of-Flight range sensor.
|
||||
//=======================================================================
|
||||
// The following directive defines a single VL53L0X Time-of-Flight range sensor.
|
||||
// The parameters are:
|
||||
// VPIN=5000
|
||||
// Number of VPINs=1
|
||||
// I2C address=0x29 (default for this chip)
|
||||
// Minimum trigger range=200mm (VPIN goes to 1 when <20cm)
|
||||
// Maximum trigger range=250mm (VPIN goes to 0 when >25cm)
|
||||
|
||||
//VL53L0X::create(5000, 1, 0x29, 200, 250);
|
||||
|
||||
// For multiple VL53L0X modules, add another parameter which is a VPIN connected to the
|
||||
// module's XSHUT pin. This allows the modules to be configured, at start,
|
||||
// with distinct I2C addresses. In this case, the address 0x29 is only used during
|
||||
// initialisation to configure each device in turn with the desired unique I2C address.
|
||||
// The examples below have the modules' XSHUT pins connected to the first two pins of
|
||||
// the first MCP23017 module (164 and 165), but Arduino pins may be used instead.
|
||||
// The first module here is given I2C address 0x30 and the second is 0x31.
|
||||
|
||||
//VL53L0X::create(5000, 1, 0x30, 200, 250, 164);
|
||||
//VL53L0X::create(5001, 1, 0x31, 200, 250, 165);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// Play mp3 files from a Micro-SD card, using a DFPlayer MP3 Module.
|
||||
//=======================================================================
|
||||
@ -209,6 +211,93 @@ void mySetup() {
|
||||
// DFPlayer::create(10000, 10, Serial1);
|
||||
|
||||
|
||||
}
|
||||
//=======================================================================
|
||||
// Remote (networked) VPIN Configuration
|
||||
//=======================================================================
|
||||
// Define remote pins to be used. The range of remote pins is like a common data area shared
|
||||
// between all nodes.
|
||||
// For outputs, a write to a remote VPIN causes a message to be sent to another node, which then performs
|
||||
// the write operation on the device VPIN that is local to that node.
|
||||
// For inputs, the state of remote input VPIN is read on the node where it is connected, and then
|
||||
// sent to other nodes in the system where the state is saved and processed. Updates are sent on change, and
|
||||
// also periodically if no changes.
|
||||
//
|
||||
// Currently, analogue inputs are not enabled for remote access.
|
||||
//
|
||||
// Each definition is a triple of remote node, remote pin, type, indexed by relative pin.
|
||||
// Each pin may be an input whose value is to be read before being transmitted (RPIN_IN),
|
||||
// an output which is to be triggered when written to (RPIN_OUT) or both (RPIN_INPUT),
|
||||
// such as a servo or DFPlayer device pin. Unused pins (e.g. spares to reserve contiguous
|
||||
// pin sequences) should be defined sd VPIN_NONE with 0 as the type.
|
||||
// Where possible, align the input and inout pins on an offset which is a multiple of 8
|
||||
// from the start of the remote pins, as in the example below.
|
||||
//
|
||||
// There is a limit of 224 in the number of pin states per node that are sent
|
||||
// over the network (to keep the number of sent messages to 1 x 32 bytes). This restriction
|
||||
// may be lifted in future.
|
||||
//
|
||||
// In the example below with two nodes 30 and 31,
|
||||
// a turnout object set to operate pin 4004 will operate the servo connected to
|
||||
// VPIN 100 on node 30;
|
||||
// a sensor object monitoring pin 4024 will trigger if pin D24 on node
|
||||
// 31 activates;
|
||||
// an output object associated with pin 4002 will, when set on, activate
|
||||
// pin 166 (MCP23017 pin 3) on node 30.
|
||||
//
|
||||
// All nodes on the same network should use the same REMOTEPINS setup, and the
|
||||
// node number of each node should be set to a different number.
|
||||
//
|
||||
// REMOTEPINS rpins[] = {
|
||||
// {30,164,RPIN_IN} , //4000 Node 30, first MCP23017 pin, input
|
||||
// {30,165,RPIN_IN}, //4001 Node 30, second MCP23017 pin, input
|
||||
// {30,166,RPIN_OUT}, //4002 Node 30, third MCP23017 pin, output
|
||||
// {30,166,RPIN_OUT}, //4003 Node 30, fourth MCP23017 pin, output
|
||||
// {30,100,RPIN_INOUT}, //4004 Node 30, first PCA9685 servo pin
|
||||
// {30,101,RPIN_INOUT}, //4005 Node 30, second PCA9685 servo pin
|
||||
// {30,102,RPIN_INOUT}, //4006 Node 30, third PCA9685 servo pin
|
||||
// {30,103,RPIN_INOUT}, //4007 Node 30, fourth PCA9685 servo pin
|
||||
// {30,24,RPIN_IN}, //4008 Node 30, Arduino pin D24
|
||||
// {30,25,RPIN_IN}, //4009 Node 30, Arduino pin D25
|
||||
// {30,26,RPIN_IN}, //4010 Node 30, Arduino pin D26
|
||||
// {30,27,RPIN_IN}, //4011 Node 30, Arduino pin D27
|
||||
// {30,VPIN_NONE,0}, //4012 Node 30, spare
|
||||
// {30,VPIN_NONE,0}, //4013 Node 30, spare
|
||||
// {30,VPIN_NONE,0}, //4014 Node 30, spare
|
||||
// {30,VPIN_NONE,0}, //4015 Node 30, spare
|
||||
//
|
||||
// {31,164,RPIN_IN} , //4016 Node 31, first MCP23017 pin, input
|
||||
// {31,165,RPIN_IN}, //4017 Node 31, second MCP23017 pin, input
|
||||
// {31,166,RPIN_OUT}, //4018 Node 31, third MCP23017 pin, output
|
||||
// {31,166,RPIN_OUT}, //4019 Node 31, fourth MCP23017 pin, output
|
||||
// {31,100,RPIN_INOUT}, //4020 Node 31, first PCA9685 servo pin
|
||||
// {31,101,RPIN_INOUT}, //4021 Node 31, second PCA9685 servo pin
|
||||
// {31,102,RPIN_INOUT}, //4022 Node 31, third PCA9685 servo pin
|
||||
// {31,103,RPIN_INOUT}, //4023 Node 31, fourth PCA9685 servo pin
|
||||
// {31,24,RPIN_IN}, //4024 Node 31, Arduino pin D24
|
||||
// {31,25,RPIN_IN}, //4025 Node 31, Arduino pin D25
|
||||
// {31,26,RPIN_IN}, //4026 Node 31, Arduino pin D26
|
||||
// {31,27,RPIN_IN}, //4027 Node 31, Arduino pin D27
|
||||
// {31,VPIN_NONE,0}, //4028 Node 31, spare
|
||||
// {31,VPIN_NONE,0}, //4029 Node 31, spare
|
||||
// {31,VPIN_NONE,0}, //4030 Node 31, spare
|
||||
// {31,VPIN_NONE,0} //4031 Node 31, spare
|
||||
// };
|
||||
|
||||
#endif
|
||||
// Network using RF24 wireless module, with CE on pin 48 and CS on pin 49 and node=30
|
||||
|
||||
// Net_RF24 *rf24Driver = new Net_RF24(48, 49);
|
||||
// Network<Net_RF24>::create(4000, NUMREMOTEPINS(rpins), 30, rpins, rf24Driver);
|
||||
|
||||
// Network using ENC28J60 ethernet module, with CS on pin 49 and node=30
|
||||
|
||||
// Net_ENC28J60 *encDriver = new Net_ENC28J60(49);
|
||||
// Network<Net_ENC28J60>::create(4000, NUMREMOTEPINS(rpins), 30, rpins, encDriver);
|
||||
|
||||
// Network using Arduino Ethernet library, with CS on default pin (10) and node=30
|
||||
// This would be the option selected if you already use ethernet for DCC++EX
|
||||
// commands.
|
||||
|
||||
// Net_Ethernet *etherDriver = new Net_Ethernet();
|
||||
// Network<Net_Ethernet>::create(4000, NUMREMOTEPINS(rpins), 30, rpins, etherDriver);
|
||||
|
||||
}
|
||||
|
100
net.h
Normal file
100
net.h
Normal file
@ -0,0 +1,100 @@
|
||||
// Based on the net.h file from the AVRlib library by Pascal Stang.
|
||||
// Author: Guido Socher
|
||||
// Copyright: GPL V2
|
||||
//
|
||||
// For AVRlib See http://www.procyonengineering.com/
|
||||
// Used with explicit permission of Pascal Stang.
|
||||
//
|
||||
// 2010-05-20 <jc@wippler.nl>
|
||||
|
||||
// notation: _P = position of a field
|
||||
// _V = value of a field
|
||||
|
||||
#ifndef NET_H
|
||||
#define NET_H
|
||||
|
||||
// ******* ETH *******
|
||||
#define ETH_HEADER_LEN 14
|
||||
#define ETH_LEN 6
|
||||
// values of certain bytes:
|
||||
#define ETHTYPE_ARP_H_V 0x08
|
||||
#define ETHTYPE_ARP_L_V 0x06
|
||||
#define ETHTYPE_IP_H_V 0x08
|
||||
#define ETHTYPE_IP_L_V 0x00
|
||||
// byte positions in the ethernet frame:
|
||||
//
|
||||
// Ethernet type field (2bytes):
|
||||
#define ETH_TYPE_H_P 12
|
||||
#define ETH_TYPE_L_P 13
|
||||
//
|
||||
#define ETH_DST_MAC 0
|
||||
#define ETH_SRC_MAC 6
|
||||
|
||||
|
||||
// ******* ARP *******
|
||||
#define ETH_ARP_OPCODE_REPLY_H_V 0x0
|
||||
#define ETH_ARP_OPCODE_REPLY_L_V 0x02
|
||||
#define ETH_ARP_OPCODE_REQ_H_V 0x0
|
||||
#define ETH_ARP_OPCODE_REQ_L_V 0x01
|
||||
// start of arp header:
|
||||
#define ETH_ARP_P 0xe
|
||||
//
|
||||
#define ETHTYPE_ARP_L_V 0x06
|
||||
// arp.dst.ip
|
||||
#define ETH_ARP_DST_IP_P 0x26
|
||||
// arp.opcode
|
||||
#define ETH_ARP_OPCODE_H_P 0x14
|
||||
#define ETH_ARP_OPCODE_L_P 0x15
|
||||
// arp.src.mac
|
||||
#define ETH_ARP_SRC_MAC_P 0x16
|
||||
#define ETH_ARP_SRC_IP_P 0x1c
|
||||
#define ETH_ARP_DST_MAC_P 0x20
|
||||
#define ETH_ARP_DST_IP_P 0x26
|
||||
|
||||
// ******* IP *******
|
||||
#define IP_HEADER_LEN 20
|
||||
#define IP_LEN 4
|
||||
// ip.src
|
||||
#define IP_SRC_P 0x1a
|
||||
#define IP_DST_P 0x1e
|
||||
#define IP_HEADER_LEN_VER_P 0xe
|
||||
#define IP_CHECKSUM_P 0x18
|
||||
#define IP_TTL_P 0x16
|
||||
#define IP_FLAGS_P 0x14
|
||||
#define IP_P 0xe
|
||||
#define IP_TOTLEN_H_P 0x10
|
||||
#define IP_TOTLEN_L_P 0x11
|
||||
|
||||
#define IP_PROTO_P 0x17
|
||||
|
||||
#define IP_PROTO_ICMP_V 1
|
||||
#define IP_PROTO_TCP_V 6
|
||||
// 17=0x11
|
||||
#define IP_PROTO_UDP_V 17
|
||||
// ******* ICMP *******
|
||||
#define ICMP_TYPE_ECHOREPLY_V 0
|
||||
#define ICMP_TYPE_ECHOREQUEST_V 8
|
||||
//
|
||||
#define ICMP_TYPE_P 0x22
|
||||
#define ICMP_CHECKSUM_P 0x24
|
||||
#define ICMP_CHECKSUM_H_P 0x24
|
||||
#define ICMP_CHECKSUM_L_P 0x25
|
||||
#define ICMP_IDENT_H_P 0x26
|
||||
#define ICMP_IDENT_L_P 0x27
|
||||
#define ICMP_DATA_P 0x2a
|
||||
|
||||
// ******* UDP *******
|
||||
#define UDP_HEADER_LEN 8
|
||||
//
|
||||
#define UDP_SRC_PORT_H_P 0x22
|
||||
#define UDP_SRC_PORT_L_P 0x23
|
||||
#define UDP_DST_PORT_H_P 0x24
|
||||
#define UDP_DST_PORT_L_P 0x25
|
||||
//
|
||||
#define UDP_LEN_H_P 0x26
|
||||
#define UDP_LEN_L_P 0x27
|
||||
#define UDP_CHECKSUM_H_P 0x28
|
||||
#define UDP_CHECKSUM_L_P 0x29
|
||||
#define UDP_DATA_P 0x2a
|
||||
|
||||
#endif
|
317
tcpip.cpp
Normal file
317
tcpip.cpp
Normal file
@ -0,0 +1,317 @@
|
||||
// IP, ARP, UDP and TCP functions.
|
||||
// Author: Guido Socher
|
||||
// Copyright: GPL V2
|
||||
//
|
||||
// The TCP implementation uses some size optimisations which are valid
|
||||
// only if all data can be sent in one single packet. This is however
|
||||
// not a big limitation for a microcontroller as you will anyhow use
|
||||
// small web-pages. The web server must send the entire web page in one
|
||||
// packet. The client "web browser" as implemented here can also receive
|
||||
// large pages.
|
||||
//
|
||||
// 2010-05-20 <jc@wippler.nl>
|
||||
|
||||
#include "EtherCard.h"
|
||||
#include "net.h"
|
||||
#undef word // arduino nonsense
|
||||
|
||||
#define gPB ether.buffer
|
||||
|
||||
#define PINGPATTERN 0x42
|
||||
|
||||
// Avoid spurious pgmspace warnings - http://forum.jeelabs.net/node/327
|
||||
// See also http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734
|
||||
//#undef PROGMEM
|
||||
//#define PROGMEM __attribute__(( section(".progmem.data") ))
|
||||
//#undef PSTR
|
||||
//#define PSTR(s) (__extension__({static prog_char c[] PROGMEM = (s); &c[0];}))
|
||||
|
||||
#define TCP_STATE_SENDSYN 1
|
||||
#define TCP_STATE_SYNSENT 2
|
||||
#define TCP_STATE_ESTABLISHED 3
|
||||
#define TCP_STATE_NOTUSED 4
|
||||
#define TCP_STATE_CLOSING 5
|
||||
#define TCP_STATE_CLOSED 6
|
||||
|
||||
static uint8_t destmacaddr[ETH_LEN]; // storing both dns server and destination mac addresses, but at different times because both are never needed at same time.
|
||||
static boolean waiting_for_dns_mac = false; //might be better to use bit flags and bitmask operations for these conditions
|
||||
static boolean has_dns_mac = false;
|
||||
static boolean waiting_for_dest_mac = false;
|
||||
static boolean has_dest_mac = false;
|
||||
static uint8_t gwmacaddr[ETH_LEN]; // Hardware (MAC) address of gateway router
|
||||
static uint8_t waitgwmac; // Bitwise flags of gateway router status - see below for states
|
||||
//Define gateway router ARP statuses
|
||||
#define WGW_INITIAL_ARP 1 // First request, no answer yet
|
||||
#define WGW_HAVE_GW_MAC 2 // Have gateway router MAC
|
||||
#define WGW_REFRESHING 4 // Refreshing but already have gateway MAC
|
||||
#define WGW_ACCEPT_ARP_REPLY 8 // Accept an ARP reply
|
||||
|
||||
const unsigned char arpreqhdr[] PROGMEM = { 0,1,8,0,6,4,0,1 }; // ARP request header
|
||||
const unsigned char iphdr[] PROGMEM = { 0x45,0,0,0x82,0,0,0x40,0,0x20 }; //IP header
|
||||
extern const uint8_t allOnes[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; // Used for hardware (MAC) and IP broadcast addresses
|
||||
|
||||
static void fill_checksum(uint8_t dest, uint8_t off, uint16_t len,uint8_t type) {
|
||||
const uint8_t* ptr = gPB + off;
|
||||
uint32_t sum = type==1 ? IP_PROTO_UDP_V+len-8 :
|
||||
type==2 ? IP_PROTO_TCP_V+len-8 : 0;
|
||||
while(len >1) {
|
||||
sum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1));
|
||||
ptr+=2;
|
||||
len-=2;
|
||||
}
|
||||
if (len)
|
||||
sum += ((uint32_t)*ptr)<<8;
|
||||
while (sum>>16)
|
||||
sum = (uint16_t) sum + (sum >> 16);
|
||||
uint16_t ck = ~ (uint16_t) sum;
|
||||
gPB[dest] = ck>>8;
|
||||
gPB[dest+1] = ck;
|
||||
}
|
||||
|
||||
static void setMACs (const uint8_t *mac) {
|
||||
EtherCard::copyMac(gPB + ETH_DST_MAC, mac);
|
||||
EtherCard::copyMac(gPB + ETH_SRC_MAC, EtherCard::mymac);
|
||||
}
|
||||
|
||||
static void setMACandIPs (const uint8_t *mac, const uint8_t *dst) {
|
||||
setMACs(mac);
|
||||
EtherCard::copyIp(gPB + IP_DST_P, dst);
|
||||
EtherCard::copyIp(gPB + IP_SRC_P, EtherCard::myip);
|
||||
}
|
||||
|
||||
static boolean is_lan(const uint8_t source[IP_LEN], const uint8_t destination[IP_LEN]) {
|
||||
if(source[0] == 0 || destination[0] == 0) {
|
||||
return false;
|
||||
}
|
||||
for(int i = 0; i < IP_LEN; i++)
|
||||
if((source[i] & EtherCard::netmask[i]) != (destination[i] & EtherCard::netmask[i])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t eth_type_is_arp_and_my_ip(uint16_t len) {
|
||||
return len >= 41 && gPB[ETH_TYPE_H_P] == ETHTYPE_ARP_H_V &&
|
||||
gPB[ETH_TYPE_L_P] == ETHTYPE_ARP_L_V &&
|
||||
memcmp(gPB + ETH_ARP_DST_IP_P, EtherCard::myip, IP_LEN) == 0;
|
||||
}
|
||||
|
||||
static uint8_t eth_type_is_ip_and_my_ip(uint16_t len) {
|
||||
return len >= 42 && gPB[ETH_TYPE_H_P] == ETHTYPE_IP_H_V &&
|
||||
gPB[ETH_TYPE_L_P] == ETHTYPE_IP_L_V &&
|
||||
gPB[IP_HEADER_LEN_VER_P] == 0x45 &&
|
||||
(memcmp(gPB + IP_DST_P, EtherCard::myip, IP_LEN) == 0 //not my IP
|
||||
|| (memcmp(gPB + IP_DST_P, EtherCard::broadcastip, IP_LEN) == 0) //not subnet broadcast
|
||||
|| (memcmp(gPB + IP_DST_P, allOnes, IP_LEN) == 0)); //not global broadcasts
|
||||
//!@todo Handle multicast
|
||||
}
|
||||
|
||||
static void fill_ip_hdr_checksum() {
|
||||
gPB[IP_CHECKSUM_P] = 0;
|
||||
gPB[IP_CHECKSUM_P+1] = 0;
|
||||
gPB[IP_FLAGS_P] = 0x40; // don't fragment
|
||||
gPB[IP_FLAGS_P+1] = 0; // fragment offset
|
||||
gPB[IP_TTL_P] = 64; // ttl
|
||||
fill_checksum(IP_CHECKSUM_P, IP_P, IP_HEADER_LEN,0);
|
||||
}
|
||||
|
||||
static void make_eth_ip() {
|
||||
setMACs(gPB + ETH_SRC_MAC);
|
||||
EtherCard::copyIp(gPB + IP_DST_P, gPB + IP_SRC_P);
|
||||
EtherCard::copyIp(gPB + IP_SRC_P, EtherCard::myip);
|
||||
fill_ip_hdr_checksum();
|
||||
}
|
||||
|
||||
static void make_arp_answer_from_request() {
|
||||
setMACs(gPB + ETH_SRC_MAC);
|
||||
gPB[ETH_ARP_OPCODE_H_P] = ETH_ARP_OPCODE_REPLY_H_V;
|
||||
gPB[ETH_ARP_OPCODE_L_P] = ETH_ARP_OPCODE_REPLY_L_V;
|
||||
EtherCard::copyMac(gPB + ETH_ARP_DST_MAC_P, gPB + ETH_ARP_SRC_MAC_P);
|
||||
EtherCard::copyMac(gPB + ETH_ARP_SRC_MAC_P, EtherCard::mymac);
|
||||
EtherCard::copyIp(gPB + ETH_ARP_DST_IP_P, gPB + ETH_ARP_SRC_IP_P);
|
||||
EtherCard::copyIp(gPB + ETH_ARP_SRC_IP_P, EtherCard::myip);
|
||||
EtherCard::packetSend(42);
|
||||
}
|
||||
|
||||
static void make_echo_reply_from_request(uint16_t len) {
|
||||
make_eth_ip();
|
||||
gPB[ICMP_TYPE_P] = ICMP_TYPE_ECHOREPLY_V;
|
||||
if (gPB[ICMP_CHECKSUM_P] > (0xFF-0x08))
|
||||
gPB[ICMP_CHECKSUM_P+1]++;
|
||||
gPB[ICMP_CHECKSUM_P] += 0x08;
|
||||
EtherCard::packetSend(len);
|
||||
}
|
||||
|
||||
void EtherCard::makeUdpReply (const char *data,uint8_t datalen,uint16_t port) {
|
||||
if (datalen>220)
|
||||
datalen = 220;
|
||||
gPB[IP_TOTLEN_H_P] = (IP_HEADER_LEN+UDP_HEADER_LEN+datalen) >>8;
|
||||
gPB[IP_TOTLEN_L_P] = IP_HEADER_LEN+UDP_HEADER_LEN+datalen;
|
||||
make_eth_ip();
|
||||
gPB[UDP_DST_PORT_H_P] = gPB[UDP_SRC_PORT_H_P];
|
||||
gPB[UDP_DST_PORT_L_P] = gPB[UDP_SRC_PORT_L_P];
|
||||
gPB[UDP_SRC_PORT_H_P] = port>>8;
|
||||
gPB[UDP_SRC_PORT_L_P] = port;
|
||||
gPB[UDP_LEN_H_P] = (UDP_HEADER_LEN+datalen) >> 8;
|
||||
gPB[UDP_LEN_L_P] = UDP_HEADER_LEN+datalen;
|
||||
gPB[UDP_CHECKSUM_H_P] = 0;
|
||||
gPB[UDP_CHECKSUM_L_P] = 0;
|
||||
memcpy(gPB + UDP_DATA_P, data, datalen);
|
||||
fill_checksum(UDP_CHECKSUM_H_P, IP_SRC_P, 16 + datalen,1);
|
||||
packetSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen);
|
||||
}
|
||||
|
||||
void EtherCard::udpPrepare (uint16_t sport, const uint8_t *dip, uint16_t dport) {
|
||||
if(is_lan(myip, dip)) { // this works because both dns mac and destinations mac are stored in same variable - destmacaddr
|
||||
setMACandIPs(destmacaddr, dip); // at different times. The program could have separate variable for dns mac, then here should be
|
||||
} else { // checked if dip is dns ip and separately if dip is hisip and then use correct mac.
|
||||
setMACandIPs(gwmacaddr, dip);
|
||||
}
|
||||
// see http://tldp.org/HOWTO/Multicast-HOWTO-2.html
|
||||
// multicast or broadcast address, https://github.com/njh/EtherCard/issues/59
|
||||
if ((dip[0] & 0xF0) == 0xE0 || *((unsigned long*) dip) == 0xFFFFFFFF || !memcmp(broadcastip,dip,IP_LEN))
|
||||
EtherCard::copyMac(gPB + ETH_DST_MAC, allOnes);
|
||||
gPB[ETH_TYPE_H_P] = ETHTYPE_IP_H_V;
|
||||
gPB[ETH_TYPE_L_P] = ETHTYPE_IP_L_V;
|
||||
memcpy_P(gPB + IP_P,iphdr,sizeof iphdr);
|
||||
gPB[IP_TOTLEN_H_P] = 0;
|
||||
gPB[IP_PROTO_P] = IP_PROTO_UDP_V;
|
||||
gPB[UDP_DST_PORT_H_P] = (dport>>8);
|
||||
gPB[UDP_DST_PORT_L_P] = dport;
|
||||
gPB[UDP_SRC_PORT_H_P] = (sport>>8);
|
||||
gPB[UDP_SRC_PORT_L_P] = sport;
|
||||
gPB[UDP_LEN_H_P] = 0;
|
||||
gPB[UDP_CHECKSUM_H_P] = 0;
|
||||
gPB[UDP_CHECKSUM_L_P] = 0;
|
||||
}
|
||||
|
||||
void EtherCard::udpTransmit (uint16_t datalen) {
|
||||
gPB[IP_TOTLEN_H_P] = (IP_HEADER_LEN+UDP_HEADER_LEN+datalen) >> 8;
|
||||
gPB[IP_TOTLEN_L_P] = IP_HEADER_LEN+UDP_HEADER_LEN+datalen;
|
||||
fill_ip_hdr_checksum();
|
||||
gPB[UDP_LEN_H_P] = (UDP_HEADER_LEN+datalen) >>8;
|
||||
gPB[UDP_LEN_L_P] = UDP_HEADER_LEN+datalen;
|
||||
fill_checksum(UDP_CHECKSUM_H_P, IP_SRC_P, 16 + datalen,1);
|
||||
packetSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen);
|
||||
}
|
||||
|
||||
void EtherCard::sendUdp (const char *data, uint8_t datalen, uint16_t sport,
|
||||
const uint8_t *dip, uint16_t dport) {
|
||||
udpPrepare(sport, dip, dport);
|
||||
if (datalen>220)
|
||||
datalen = 220;
|
||||
memcpy(gPB + UDP_DATA_P, data, datalen);
|
||||
udpTransmit(datalen);
|
||||
}
|
||||
|
||||
// make a arp request
|
||||
static void client_arp_whohas(uint8_t *ip_we_search) {
|
||||
setMACs(allOnes);
|
||||
gPB[ETH_TYPE_H_P] = ETHTYPE_ARP_H_V;
|
||||
gPB[ETH_TYPE_L_P] = ETHTYPE_ARP_L_V;
|
||||
memcpy_P(gPB + ETH_ARP_P, arpreqhdr, sizeof arpreqhdr);
|
||||
memset(gPB + ETH_ARP_DST_MAC_P, 0, ETH_LEN);
|
||||
EtherCard::copyMac(gPB + ETH_ARP_SRC_MAC_P, EtherCard::mymac);
|
||||
EtherCard::copyIp(gPB + ETH_ARP_DST_IP_P, ip_we_search);
|
||||
EtherCard::copyIp(gPB + ETH_ARP_SRC_IP_P, EtherCard::myip);
|
||||
EtherCard::packetSend(42);
|
||||
}
|
||||
|
||||
uint8_t EtherCard::clientWaitingGw () {
|
||||
return !(waitgwmac & WGW_HAVE_GW_MAC);
|
||||
}
|
||||
|
||||
static uint8_t client_store_mac(uint8_t *source_ip, uint8_t *mac) {
|
||||
if (memcmp(gPB + ETH_ARP_SRC_IP_P, source_ip, IP_LEN) != 0)
|
||||
return 0;
|
||||
EtherCard::copyMac(mac, gPB + ETH_ARP_SRC_MAC_P);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// static void client_gw_arp_refresh() {
|
||||
// if (waitgwmac & WGW_HAVE_GW_MAC)
|
||||
// waitgwmac |= WGW_REFRESHING;
|
||||
// }
|
||||
|
||||
void EtherCard::setGwIp (const uint8_t *gwipaddr) {
|
||||
delaycnt = 0; //request gateway ARP lookup
|
||||
waitgwmac = WGW_INITIAL_ARP; // causes an arp request in the packet loop
|
||||
copyIp(gwip, gwipaddr);
|
||||
}
|
||||
|
||||
void EtherCard::updateBroadcastAddress()
|
||||
{
|
||||
for(uint8_t i=0; i<IP_LEN; i++)
|
||||
broadcastip[i] = myip[i] | ~netmask[i];
|
||||
}
|
||||
|
||||
uint16_t EtherCard::packetLoop (uint16_t plen) {
|
||||
|
||||
if (plen==0) {
|
||||
//Check every 65536 (no-packet) cycles whether we need to retry ARP request for gateway
|
||||
if ((waitgwmac & WGW_INITIAL_ARP || waitgwmac & WGW_REFRESHING) &&
|
||||
delaycnt==0 && isLinkUp()) {
|
||||
client_arp_whohas(gwip);
|
||||
waitgwmac |= WGW_ACCEPT_ARP_REPLY;
|
||||
}
|
||||
delaycnt++;
|
||||
|
||||
//!@todo this is trying to find mac only once. Need some timeout to make another call if first one doesn't succeed.
|
||||
if(is_lan(myip, dnsip) && !has_dns_mac && !waiting_for_dns_mac) {
|
||||
client_arp_whohas(dnsip);
|
||||
waiting_for_dns_mac = true;
|
||||
}
|
||||
|
||||
//!@todo this is trying to find mac only once. Need some timeout to make another call if first one doesn't succeed.
|
||||
if(is_lan(myip, hisip) && !has_dest_mac && !waiting_for_dest_mac) {
|
||||
client_arp_whohas(hisip);
|
||||
waiting_for_dest_mac = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (eth_type_is_arp_and_my_ip(plen))
|
||||
{ //Service ARP request
|
||||
if (gPB[ETH_ARP_OPCODE_L_P]==ETH_ARP_OPCODE_REQ_L_V)
|
||||
make_arp_answer_from_request();
|
||||
if (waitgwmac & WGW_ACCEPT_ARP_REPLY && (gPB[ETH_ARP_OPCODE_L_P]==ETH_ARP_OPCODE_REPLY_L_V) && client_store_mac(gwip, gwmacaddr))
|
||||
waitgwmac = WGW_HAVE_GW_MAC;
|
||||
if (!has_dns_mac && waiting_for_dns_mac && client_store_mac(dnsip, destmacaddr)) {
|
||||
has_dns_mac = true;
|
||||
waiting_for_dns_mac = false;
|
||||
}
|
||||
if (!has_dest_mac && waiting_for_dest_mac && client_store_mac(hisip, destmacaddr)) {
|
||||
has_dest_mac = true;
|
||||
waiting_for_dest_mac = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (eth_type_is_ip_and_my_ip(plen)==0)
|
||||
{ //Not IP so ignoring
|
||||
//!@todo Add other protocols (and make each optional at compile time)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if ETHERCARD_ICMP
|
||||
if (gPB[IP_PROTO_P]==IP_PROTO_ICMP_V && gPB[ICMP_TYPE_P]==ICMP_TYPE_ECHOREQUEST_V)
|
||||
{ //Service ICMP echo request (ping)
|
||||
make_echo_reply_from_request(plen);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (plen < UDP_DATA_P || gPB[IP_PROTO_P]!=IP_PROTO_UDP_V )
|
||||
return 0; //from here on we are only interested in UDP-packets
|
||||
|
||||
return plen + UDP_DATA_P; // Offset of UDP payload.
|
||||
}
|
||||
|
||||
void EtherCard::copyIp (uint8_t *dst, const uint8_t *src) {
|
||||
memcpy(dst, src, IP_LEN);
|
||||
}
|
||||
|
||||
void EtherCard::copyMac (uint8_t *dst, const uint8_t *src) {
|
||||
memcpy(dst, src, ETH_LEN);
|
||||
}
|
Loading…
Reference in New Issue
Block a user