mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-02-19 23:46:02 +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.
|
// 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 "IODevice.h"
|
||||||
#include "Turnouts.h"
|
#include "Turnouts.h"
|
||||||
#include "Sensors.h"
|
#include "Sensors.h"
|
||||||
#include "IO_HCSR04.h"
|
#include "IO_HCSR04.h"
|
||||||
#include "IO_VL53L0X.h"
|
#include "IO_VL53L0X.h"
|
||||||
|
#include "DFPlayer.h"
|
||||||
|
#include "IO_Network.h"
|
||||||
// The #if directive prevent compile errors for Uno and Nano by excluding the
|
#include "Net_RF24"
|
||||||
// HAL directives from the build.
|
#include "Net_ENC28J60"
|
||||||
#if !defined(IO_NO_HAL)
|
#include "Net_Ethernet"
|
||||||
|
|
||||||
|
|
||||||
// Examples of statically defined HAL directives (alternative to the create() call).
|
// Examples of statically defined HAL directives (alternative to the create() call).
|
||||||
@ -81,49 +82,6 @@
|
|||||||
//PCF8574 gpioModule4(200, 8, 0x23, 40);
|
//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.
|
// 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
|
// 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);
|
// 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.
|
// Play mp3 files from a Micro-SD card, using a DFPlayer MP3 Module.
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
@ -209,6 +211,93 @@ void mySetup() {
|
|||||||
// DFPlayer::create(10000, 10, Serial1);
|
// 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