diff --git a/EtherCard.cpp b/EtherCard.cpp new file mode 100644 index 0000000..2224073 --- /dev/null +++ b/EtherCard.cpp @@ -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 + +#include "EtherCard.h" +#include +#include + +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; +} + diff --git a/EtherCard.h b/EtherCard.h new file mode 100644 index 0000000..addc94f --- /dev/null +++ b/EtherCard.h @@ -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 +// +// +// 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 1.0 +// #define WRITE_RESULT size_t +// #define WRITE_RETURN return 1; +// #else +// #include // Arduino 0022 +// #define WRITE_RESULT void +// #define WRITE_RETURN +// #endif + +// #include +#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 + +// 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 diff --git a/IO_Network.h b/IO_Network.h new file mode 100644 index 0000000..1b2bbd7 --- /dev/null +++ b/IO_Network.h @@ -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 . + */ + +/* + * 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: + * + * 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: + * + * When the sensor deactivates, the following message will be generated on node 0: + * + */ + +#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 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> 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 \ No newline at end of file diff --git a/IO_RF24.h b/IO_RF24.h new file mode 100644 index 0000000..fd70ab7 --- /dev/null +++ b/IO_RF24.h @@ -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 . + */ + +/* + * 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: + * + * 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: + * + * When the sensor deactivates, the following message will be generated on node 0: + * + */ + +#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> 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 \ No newline at end of file diff --git a/Net_ENC28J60.h b/Net_ENC28J60.h new file mode 100644 index 0000000..60d504c --- /dev/null +++ b/Net_ENC28J60.h @@ -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 . + */ + +/* + * 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::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 \ No newline at end of file diff --git a/Net_Ethernet.h b/Net_Ethernet.h new file mode 100644 index 0000000..0633bc2 --- /dev/null +++ b/Net_Ethernet.h @@ -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 . + */ + +/* + * 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::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 \ No newline at end of file diff --git a/Net_RF24.h b/Net_RF24.h new file mode 100644 index 0000000..3b616bf --- /dev/null +++ b/Net_RF24.h @@ -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 . + */ + +/* + * 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::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 \ No newline at end of file diff --git a/RF24.cpp b/RF24.cpp new file mode 100644 index 0000000..0124b3b --- /dev/null +++ b/RF24.cpp @@ -0,0 +1,1267 @@ +/* + Copyright (C) 2011 J. Coliz + Copyright (c) 2007 Stefan Engelke + Portions Copyright (C) 2011 Greg Copeland + + 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. + */ + +#include "RF24.h" + + +#ifdef SERIAL_DEBUG + #define IF_SERIAL_DEBUG(x) ({x;}) +#else + #define IF_SERIAL_DEBUG(x) +#endif // SERIAL_DEBUG + + + +/****************************************************************************/ + +void RF24::csn(bool mode) +{ +#ifdef ARDUINO_ARCH_AVR + if (!csnPtr) return; + noInterrupts(); + if (mode) + *csnPtr |= csnMask; + else + *csnPtr &= ~csnMask; + interrupts(); +#else + digitalWrite(csn_pin, mode); +#endif + delayMicroseconds(csDelay); +} + +/****************************************************************************/ + +void RF24::ce(bool level) +{ + //Allow for 3-pin use on ATTiny + if (ce_pin != csn_pin) { +#ifdef ARDUINO_ARCH_AVR + if (!cePtr) return; + noInterrupts(); + if (level) + *cePtr |= ceMask; + else + *cePtr &= ~ceMask; + interrupts(); +#else + digitalWrite(ce_pin, level); +#endif + } +} + +/****************************************************************************/ + +inline void RF24::beginTransaction() +{ + _spi->beginTransaction(SPISettings(spi_speed, MSBFIRST, SPI_MODE0)); + csn(LOW); +} + +/****************************************************************************/ + +inline void RF24::endTransaction() +{ + csn(HIGH); + _spi->endTransaction(); +} + +/****************************************************************************/ + +void RF24::read_register(uint8_t reg, uint8_t* buf, uint8_t len) +{ + beginTransaction(); + status = _spi->transfer(R_REGISTER | reg); + while (len--) { *buf++ = _spi->transfer(0xFF); } + endTransaction(); +} + +/****************************************************************************/ + +uint8_t RF24::read_register(uint8_t reg) +{ + uint8_t result; + + beginTransaction(); + status = _spi->transfer(R_REGISTER | reg); + result = _spi->transfer(0xff); + endTransaction(); + return result; +} + +/****************************************************************************/ + +void RF24::write_register(uint8_t reg, const uint8_t* buf, uint8_t len) +{ + beginTransaction(); + status = _spi->transfer(W_REGISTER | reg); + while (len--) { _spi->transfer(*buf++); } + endTransaction(); +} + +/****************************************************************************/ + +void RF24::write_register(uint8_t reg, uint8_t value, bool is_cmd_only) +{ + if (is_cmd_only) { + if (reg != RF24_NOP) { // don't print the get_status() operation + IF_SERIAL_DEBUG(printf_P(PSTR("write_register(%02x)\r\n"), reg)); + } + beginTransaction(); + status = _spi->transfer(W_REGISTER | reg); + endTransaction(); + } + else { + IF_SERIAL_DEBUG(printf_P(PSTR("write_register(%02x,%02x)\r\n"), reg, value)); + beginTransaction(); + status = _spi->transfer(W_REGISTER | reg); + _spi->transfer(value); + endTransaction(); + } +} + +/****************************************************************************/ + +void RF24::write_payload(const void* buf, uint8_t data_len, const uint8_t writeType) +{ + const uint8_t* current = reinterpret_cast(buf); + + uint8_t blank_len = !data_len ? 1 : 0; + if (!dynamic_payloads_enabled) { + data_len = rf24_min(data_len, payload_size); + blank_len = payload_size - data_len; + } + else { + data_len = rf24_min(data_len, 32); + } + + //printf("[Writing %u bytes %u blanks]",data_len,blank_len); + IF_SERIAL_DEBUG(printf("[Writing %u bytes %u blanks]\n", data_len, blank_len); ); + + beginTransaction(); + status = _spi->transfer(writeType); + while (data_len--) { _spi->transfer(*current++); } + while (blank_len--) { _spi->transfer(0); } + endTransaction(); +} + +/****************************************************************************/ + +void RF24::read_payload(void* buf, uint8_t data_len) +{ + uint8_t* current = reinterpret_cast(buf); + + uint8_t blank_len = 0; + if (!dynamic_payloads_enabled) { + data_len = rf24_min(data_len, payload_size); + blank_len = payload_size - data_len; + } + else { + data_len = rf24_min(data_len, 32); + } + + //printf("[Reading %u bytes %u blanks]",data_len,blank_len); + + IF_SERIAL_DEBUG(printf("[Reading %u bytes %u blanks]\n", data_len, blank_len); ); + + beginTransaction(); + status = _spi->transfer(R_RX_PAYLOAD); + while (data_len--) { *current++ = _spi->transfer(0xFF); } + while (blank_len--) { _spi->transfer(0xff); } + endTransaction(); +} + +/****************************************************************************/ + +uint8_t RF24::flush_rx(void) +{ + write_register(FLUSH_RX, RF24_NOP, true); + return status; +} + +/****************************************************************************/ + +uint8_t RF24::flush_tx(void) +{ + write_register(FLUSH_TX, RF24_NOP, true); + return status; +} + +/****************************************************************************/ + +uint8_t RF24::get_status(void) +{ + write_register(RF24_NOP, RF24_NOP, true); + return status; +} + +/****************************************************************************/ + +RF24::RF24(uint16_t _cepin, uint16_t _cspin, uint32_t _spi_speed) + :ce_pin(_cepin), csn_pin(_cspin), spi_speed(_spi_speed), payload_size(32), dynamic_payloads_enabled(true), addr_width(5), _is_p_variant(false), + csDelay(0) +{ + _init_obj(); +} + +/****************************************************************************/ + +RF24::RF24(uint32_t _spi_speed) + :ce_pin(0xFFFF), csn_pin(0xFFFF), spi_speed(_spi_speed), payload_size(32), dynamic_payloads_enabled(true), addr_width(5), _is_p_variant(false), + csDelay(0) +{ + _init_obj(); +} + +/****************************************************************************/ + +void RF24::_init_obj() +{ + _spi = &SPI; + + pipe0_reading_address[0] = 0; + if(spi_speed <= 35000){ //Handle old BCM2835 speed constants, default to RF24_SPI_SPEED + spi_speed = RF24_SPI_SPEED; + } +} + +/****************************************************************************/ + +void RF24::setChannel(uint8_t channel) +{ + const uint8_t max_channel = 125; + write_register(RF_CH, rf24_min(channel, max_channel)); +} + +uint8_t RF24::getChannel() +{ + + return read_register(RF_CH); +} + +/****************************************************************************/ + +void RF24::setPayloadSize(uint8_t size) +{ + // payload size must be in range [1, 32] + payload_size = rf24_max(1, rf24_min(32, size)); + + // write static payload size setting for all pipes + for (uint8_t i = 0; i < 6; ++i) + write_register(RX_PW_P0 + i, payload_size); +} + +/****************************************************************************/ + +uint8_t RF24::getPayloadSize(void) +{ + return payload_size; +} + +/****************************************************************************/ + +bool RF24::begin(_SPI* spiBus) +{ + _spi = spiBus; + if (_init_pins()) + return _init_radio(); + return false; +} + +/****************************************************************************/ + +bool RF24::begin(_SPI* spiBus, uint16_t _cepin, uint16_t _cspin) +{ + ce_pin = _cepin; + csn_pin = _cspin; + return begin(spiBus); +} + +/****************************************************************************/ + +bool RF24::begin(uint16_t _cepin, uint16_t _cspin) +{ + ce_pin = _cepin; + csn_pin = _cspin; + return begin(); +} + +/****************************************************************************/ + +bool RF24::begin(void) +{ + _spi->begin(); + return _init_pins() && _init_radio(); +} + +/****************************************************************************/ + +bool RF24::_init_pins() +{ + if (!isValid()) { + // didn't specify the CSN & CE pins to c'tor nor begin() + return false; + } + + // Initialize pins + if (ce_pin != csn_pin) { + pinMode(ce_pin, OUTPUT); + pinMode(csn_pin, OUTPUT); + } + +#ifdef ARDUINO_ARCH_AVR + if (ce_pin < NUM_DIGITAL_PINS) { + cePtr = portOutputRegister(digitalPinToPort(ce_pin)); + ceMask = digitalPinToBitMask(ce_pin); + } else + cePtr = NULL; + if (csn_pin < NUM_DIGITAL_PINS) { + csnPtr = portOutputRegister(digitalPinToPort(csn_pin)); + csnMask = digitalPinToBitMask(csn_pin); + } else + csnPtr = NULL; +#endif + + ce(LOW); + csn(HIGH); + return true; // assuming pins are connected properly +} + +/****************************************************************************/ + +bool RF24::_init_radio() +{ + // Must allow the radio time to settle else configuration bits will not necessarily stick. + // This is actually only required following power up but some settling time also appears to + // be required after resets too. For full coverage, we'll always assume the worst. + // Enabling 16b CRC is by far the most obvious case if the wrong timing is used - or skipped. + // Technically we require 4.5ms + 14us as a worst case. We'll just call it 5ms for good measure. + // WARNING: Delay is based on P-variant whereby non-P *may* require different timing. + delay(5); + + // Set 1500uS (minimum for 32B payload in ESB@250KBPS) timeouts, to make testing a little easier + // WARNING: If this is ever lowered, either 250KBS mode with AA is broken or maximum packet + // sizes must never be used. See datasheet for a more complete explanation. + setRetries(5, 15); + + // Then set the data rate to the slowest (and most reliable) speed supported by all + // hardware. + setDataRate(RF24_1MBPS); + + // detect if is a plus variant & use old toggle features command accordingly + uint8_t before_toggle = read_register(FEATURE); + toggle_features(); + uint8_t after_toggle = read_register(FEATURE); + _is_p_variant = before_toggle == after_toggle; + if (after_toggle){ + if (_is_p_variant){ + // module did not experience power-on-reset (#401) + toggle_features(); + } + // allow use of multicast parameter and dynamic payloads by default + write_register(FEATURE, 0); + } + ack_payloads_enabled = false; // ack payloads disabled by default + write_register(DYNPD, 0); // disable dynamic payloads by default (for all pipes) + dynamic_payloads_enabled = false; + write_register(EN_AA, 0x3F); // enable auto-ack on all pipes + write_register(EN_RXADDR, 3); // only open RX pipes 0 & 1 + setPayloadSize(32); // set static payload size to 32 (max) bytes by default + setAddressWidth(5); // set default address length to (max) 5 bytes + + // Set up default configuration. Callers can always change it later. + // This channel should be universally safe and not bleed over into adjacent + // spectrum. + setChannel(76); + + // Reset current status + // Notice reset and flush is the last thing we do + write_register(NRF_STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT)); + + + // Flush buffers + flush_rx(); + flush_tx(); + + // Clear CONFIG register: + // Reflect all IRQ events on IRQ pin + // Enable PTX + // Power Up + // 16-bit CRC (CRC required by auto-ack) + // Do not write CE high so radio will remain in standby I mode + // PTX should use only 22uA of power + write_register(NRF_CONFIG, (_BV(EN_CRC) | _BV(CRCO)) ); + config_reg = read_register(NRF_CONFIG); + + powerUp(); + + // if config is not set correctly then there was a bad response from module + return config_reg == (_BV(EN_CRC) | _BV(CRCO) | _BV(PWR_UP)) ? true : false; +} + +/****************************************************************************/ + +bool RF24::isChipConnected() +{ + uint8_t setup = read_register(SETUP_AW); + if (setup >= 1 && setup <= 3) { + return true; + } + + return false; +} + +/****************************************************************************/ + +bool RF24::isValid() +{ + return ce_pin != 0xFFFF && csn_pin != 0xFFFF; +} + +/****************************************************************************/ + +void RF24::startListening(void) +{ + powerUp(); + config_reg |= _BV(PRIM_RX); + write_register(NRF_CONFIG, config_reg); + write_register(NRF_STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT)); + ce(HIGH); + + // Restore the pipe0 address, if exists + if (pipe0_reading_address[0] > 0) { + write_register(RX_ADDR_P0, pipe0_reading_address, addr_width); + } else { + closeReadingPipe(0); + } +} + +/****************************************************************************/ +static const PROGMEM uint8_t child_pipe_enable[] = {ERX_P0, ERX_P1, ERX_P2, + ERX_P3, ERX_P4, ERX_P5}; + +void RF24::stopListening(void) +{ + ce(LOW); + + delayMicroseconds(txDelay); + if (ack_payloads_enabled){ + flush_tx(); + } + + config_reg &= ~_BV(PRIM_RX); + write_register(NRF_CONFIG, config_reg); + + write_register(EN_RXADDR, read_register(EN_RXADDR) | _BV(pgm_read_byte(&child_pipe_enable[0]))); // Enable RX on pipe0 +} + +/****************************************************************************/ + +void RF24::powerDown(void) +{ + ce(LOW); // Guarantee CE is low on powerDown + config_reg &= ~_BV(PWR_UP); + write_register(NRF_CONFIG,config_reg); +} + +/****************************************************************************/ + +//Power up now. Radio will not power down unless instructed by MCU for config changes etc. +void RF24::powerUp(void) +{ + // if not powered up then power up and wait for the radio to initialize + if (!(config_reg & _BV(PWR_UP))) { + config_reg |= _BV(PWR_UP); + write_register(NRF_CONFIG, config_reg); + + // For nRF24L01+ to go from power down mode to TX or RX mode it must first pass through stand-by mode. + // There must be a delay of Tpd2stby (see Table 16.) after the nRF24L01+ leaves power down mode before + // the CEis set high. - Tpd2stby can be up to 5ms per the 1.0 datasheet + delayMicroseconds(RF24_POWERUP_DELAY); + } +} + +/******************************************************************/ +#if defined(FAILURE_HANDLING) + +void RF24::errNotify() +{ + #if defined(SERIAL_DEBUG) + printf_P(PSTR("RF24 HARDWARE FAIL: Radio not responding, verify pin connections, wiring, etc.\r\n")); + #endif + #if defined(FAILURE_HANDLING) + failureDetected = 1; + #else + delay(5000); + #endif +} + +#endif +/******************************************************************/ + +//Similar to the previous write, clears the interrupt flags +bool RF24::write(const void* buf, uint8_t len, const bool multicast) +{ + //Start Writing + startFastWrite(buf, len, multicast); + + //Wait until complete or failed + #if defined(FAILURE_HANDLING) + uint32_t timer = millis(); + #endif // defined(FAILURE_HANDLING) + + while (!(get_status() & (_BV(TX_DS) | _BV(MAX_RT)))) { + #if defined(FAILURE_HANDLING) + if (millis() - timer > 95) { + errNotify(); + return 0; + } + #endif + } + + ce(LOW); + + write_register(NRF_STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT)); + + //Max retries exceeded + if (status & _BV(MAX_RT)) { + flush_tx(); // Only going to be 1 packet in the FIFO at a time using this method, so just flush + return 0; + } + //TX OK 1 or 0 + return 1; +} + +bool RF24::write(const void* buf, uint8_t len) +{ + return write(buf, len, 0); +} +/****************************************************************************/ + +//For general use, the interrupt flags are not important to clear +bool RF24::writeBlocking(const void* buf, uint8_t len, uint32_t timeout) +{ + //Block until the FIFO is NOT full. + //Keep track of the MAX retries and set auto-retry if seeing failures + //This way the FIFO will fill up and allow blocking until packets go through + //The radio will auto-clear everything in the FIFO as long as CE remains high + + uint32_t timer = millis(); // Get the time that the payload transmission started + + while ((get_status() & (_BV(TX_FULL)))) { // Blocking only if FIFO is full. This will loop and block until TX is successful or timeout + + if (status & _BV(MAX_RT)) { // If MAX Retries have been reached + reUseTX(); // Set re-transmit and clear the MAX_RT interrupt flag + if (millis() - timer > timeout) { + return 0; // If this payload has exceeded the user-defined timeout, exit and return 0 + } + } + #if defined(FAILURE_HANDLING) + if (millis() - timer > (timeout + 95)) { + errNotify(); + return 0; + } + #endif + + } + + //Start Writing + startFastWrite(buf, len, 0); // Write the payload if a buffer is clear + + return 1; // Return 1 to indicate successful transmission +} + +/****************************************************************************/ + +void RF24::reUseTX() +{ + write_register(NRF_STATUS, _BV(MAX_RT)); //Clear max retry flag + write_register(REUSE_TX_PL, RF24_NOP, true); + ce(LOW); //Re-Transfer packet + ce(HIGH); +} + +/****************************************************************************/ + +bool RF24::writeFast(const void* buf, uint8_t len, const bool multicast) +{ + //Block until the FIFO is NOT full. + //Keep track of the MAX retries and set auto-retry if seeing failures + //Return 0 so the user can control the retrys and set a timer or failure counter if required + //The radio will auto-clear everything in the FIFO as long as CE remains high + + #if defined(FAILURE_HANDLING) + uint32_t timer = millis(); + #endif + + //Blocking only if FIFO is full. This will loop and block until TX is successful or fail + while ((get_status() & (_BV(TX_FULL)))) { + if (status & _BV(MAX_RT)) { + return 0; //Return 0. The previous payload has not been retransmitted + // From the user perspective, if you get a 0, just keep trying to send the same payload + } + #if defined(FAILURE_HANDLING) + if (millis() - timer > 95) { + errNotify(); + return 0; + } + #endif + } + startFastWrite(buf, len, multicast); // Start Writing + + return 1; +} + +bool RF24::writeFast(const void* buf, uint8_t len) +{ + return writeFast(buf, len, 0); +} + +/****************************************************************************/ + +//Per the documentation, we want to set PTX Mode when not listening. Then all we do is write data and set CE high +//In this mode, if we can keep the FIFO buffers loaded, packets will transmit immediately (no 130us delay) +//Otherwise we enter Standby-II mode, which is still faster than standby mode +//Also, we remove the need to keep writing the config register over and over and delaying for 150 us each time if sending a stream of data + +void RF24::startFastWrite(const void* buf, uint8_t len, const bool multicast, bool startTx) +{ //TMRh20 + + write_payload(buf, len, multicast ? W_TX_PAYLOAD_NO_ACK : W_TX_PAYLOAD); + if (startTx) { + ce(HIGH); + } +} + +/****************************************************************************/ + +//Added the original startWrite back in so users can still use interrupts, ack payloads, etc +//Allows the library to pass all tests +bool RF24::startWrite(const void* buf, uint8_t len, const bool multicast) +{ + + // Send the payload + write_payload(buf, len, multicast ? W_TX_PAYLOAD_NO_ACK : W_TX_PAYLOAD); + ce(HIGH); + ce(LOW); + return !(status & _BV(TX_FULL)); +} + +/****************************************************************************/ + +bool RF24::rxFifoFull() +{ + return read_register(FIFO_STATUS) & _BV(RX_FULL); +} + +/****************************************************************************/ + +bool RF24::isWriteFinished() { + return (get_status() & (_BV(TX_DS) | _BV(MAX_RT))); +} + +/****************************************************************************/ + +bool RF24::txStandBy() +{ + + #if defined(FAILURE_HANDLING) + uint32_t timeout = millis(); + #endif + while (!(read_register(FIFO_STATUS) & _BV(TX_EMPTY))) { + if (status & _BV(MAX_RT)) { + write_register(NRF_STATUS, _BV(MAX_RT)); + ce(LOW); + flush_tx(); //Non blocking, flush the data + return 0; + } + #if defined(FAILURE_HANDLING) + if (millis() - timeout > 95) { + errNotify(); + return 0; + } + #endif + } + + ce(LOW); //Set STANDBY-I mode + return 1; +} + +/****************************************************************************/ + +bool RF24::txStandBy(uint32_t timeout, bool startTx) +{ + + if (startTx) { + stopListening(); + ce(HIGH); + } + uint32_t start = millis(); + + while (!(read_register(FIFO_STATUS) & _BV(TX_EMPTY))) { + if (status & _BV(MAX_RT)) { + write_register(NRF_STATUS, _BV(MAX_RT)); + ce(LOW); // Set re-transmit + ce(HIGH); + if (millis() - start >= timeout) { + ce(LOW); + flush_tx(); + return 0; + } + } + #if defined(FAILURE_HANDLING) + if (millis() - start > (timeout + 95)) { + errNotify(); + return 0; + } + #endif + } + + ce(LOW); //Set STANDBY-I mode + return 1; + +} + +/****************************************************************************/ + +void RF24::maskIRQ(bool tx, bool fail, bool rx) +{ + /* clear the interrupt flags */ + config_reg &= ~(1 << MASK_MAX_RT | 1 << MASK_TX_DS | 1 << MASK_RX_DR); + /* set the specified interrupt flags */ + config_reg |= fail << MASK_MAX_RT | tx << MASK_TX_DS | rx << MASK_RX_DR; + write_register(NRF_CONFIG, config_reg); +} + +/****************************************************************************/ + +uint8_t RF24::getDynamicPayloadSize(void) +{ + uint8_t result = read_register(R_RX_PL_WID); + + if (result > 32) { + flush_rx(); + delay(2); + return 0; + } + return result; +} + +/****************************************************************************/ + +bool RF24::available(void) +{ + return available(NULL); +} + +/****************************************************************************/ + +bool RF24::available(uint8_t* pipe_num) +{ + // get implied RX FIFO empty flag from status byte + uint8_t pipe = (get_status() >> RX_P_NO) & 0x07; + if (pipe > 5) + return 0; + + // If the caller wants the pipe number, include that + if (pipe_num) + *pipe_num = pipe; + + return 1; +} + +/****************************************************************************/ + +void RF24::read(void* buf, uint8_t len) +{ + + // Fetch the payload + read_payload(buf, len); + + //Clear the only applicable interrupt flags + write_register(NRF_STATUS, _BV(RX_DR)); + +} + +/****************************************************************************/ + +void RF24::whatHappened(bool& tx_ok, bool& tx_fail, bool& rx_ready) +{ + // Read the status & reset the status in one easy call + // Or is that such a good idea? + write_register(NRF_STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT)); + + // Report to the user what happened + tx_ok = status & _BV(TX_DS); + tx_fail = status & _BV(MAX_RT); + rx_ready = status & _BV(RX_DR); +} + +/****************************************************************************/ + +void RF24::openWritingPipe(uint64_t value) +{ + // Note that AVR 8-bit uC's store this LSB first, and the NRF24L01(+) + // expects it LSB first too, so we're good. + + write_register(RX_ADDR_P0, reinterpret_cast(&value), addr_width); + write_register(TX_ADDR, reinterpret_cast(&value), addr_width); +} + +/****************************************************************************/ +void RF24::openWritingPipe(const uint8_t* address) +{ + // Note that AVR 8-bit uC's store this LSB first, and the NRF24L01(+) + // expects it LSB first too, so we're good. + write_register(RX_ADDR_P0, address, addr_width); + write_register(TX_ADDR, address, addr_width); +} + +/****************************************************************************/ +static const PROGMEM uint8_t child_pipe[] = {RX_ADDR_P0, RX_ADDR_P1, RX_ADDR_P2, + RX_ADDR_P3, RX_ADDR_P4, RX_ADDR_P5}; + +void RF24::openReadingPipe(uint8_t child, uint64_t address) +{ + // If this is pipe 0, cache the address. This is needed because + // openWritingPipe() will overwrite the pipe 0 address, so + // startListening() will have to restore it. + if (child == 0) { + memcpy(pipe0_reading_address, &address, addr_width); + } + + if (child <= 5) { + // For pipes 2-5, only write the LSB + if (child < 2) { + write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast(&address), addr_width); + } else { + write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast(&address), 1); + } + + // Note it would be more efficient to set all of the bits for all open + // pipes at once. However, I thought it would make the calling code + // more simple to do it this way. + write_register(EN_RXADDR, read_register(EN_RXADDR) | _BV(pgm_read_byte(&child_pipe_enable[child]))); + } +} + +/****************************************************************************/ +void RF24::setAddressWidth(uint8_t a_width) +{ + + if (a_width -= 2) { + write_register(SETUP_AW, a_width % 4); + addr_width = (a_width % 4) + 2; + } else { + write_register(SETUP_AW, 0); + addr_width = 2; + } + +} + +/****************************************************************************/ + +void RF24::openReadingPipe(uint8_t child, const uint8_t* address) +{ + // If this is pipe 0, cache the address. This is needed because + // openWritingPipe() will overwrite the pipe 0 address, so + // startListening() will have to restore it. + if (child == 0) { + memcpy(pipe0_reading_address, address, addr_width); + } + if (child <= 5) { + // For pipes 2-5, only write the LSB + if (child < 2) { + write_register(pgm_read_byte(&child_pipe[child]), address, addr_width); + } else { + write_register(pgm_read_byte(&child_pipe[child]), address, 1); + } + + // Note it would be more efficient to set all of the bits for all open + // pipes at once. However, I thought it would make the calling code + // more simple to do it this way. + write_register(EN_RXADDR, read_register(EN_RXADDR) | _BV(pgm_read_byte(&child_pipe_enable[child]))); + + } +} + +/****************************************************************************/ + +void RF24::closeReadingPipe(uint8_t pipe) +{ + write_register(EN_RXADDR, read_register(EN_RXADDR) & ~_BV(pgm_read_byte(&child_pipe_enable[pipe]))); +} + +/****************************************************************************/ + +void RF24::toggle_features(void) +{ + beginTransaction(); + status = _spi->transfer(ACTIVATE); + _spi->transfer(0x73); + endTransaction(); +} + +/****************************************************************************/ + +void RF24::enableDynamicPayloads(void) +{ + // Enable dynamic payload throughout the system + + //toggle_features(); + write_register(FEATURE, read_register(FEATURE) | _BV(EN_DPL)); + + IF_SERIAL_DEBUG(printf("FEATURE=%i\r\n", read_register(FEATURE))); + + // Enable dynamic payload on all pipes + // + // Not sure the use case of only having dynamic payload on certain + // pipes, so the library does not support it. + write_register(DYNPD, read_register(DYNPD) | _BV(DPL_P5) | _BV(DPL_P4) | _BV(DPL_P3) | _BV(DPL_P2) | _BV(DPL_P1) | _BV(DPL_P0)); + + dynamic_payloads_enabled = true; +} + +/****************************************************************************/ +void RF24::disableDynamicPayloads(void) +{ + // Disables dynamic payload throughout the system. Also disables Ack Payloads + + //toggle_features(); + write_register(FEATURE, 0); + + IF_SERIAL_DEBUG(printf("FEATURE=%i\r\n", read_register(FEATURE))); + + // Disable dynamic payload on all pipes + // + // Not sure the use case of only having dynamic payload on certain + // pipes, so the library does not support it. + write_register(DYNPD, 0); + + dynamic_payloads_enabled = false; + ack_payloads_enabled = false; +} + +/****************************************************************************/ + +void RF24::enableAckPayload(void) +{ + // enable ack payloads and dynamic payload features + + if (!ack_payloads_enabled){ + write_register(FEATURE, read_register(FEATURE) | _BV(EN_ACK_PAY) | _BV(EN_DPL)); + + IF_SERIAL_DEBUG(printf("FEATURE=%i\r\n", read_register(FEATURE))); + + // Enable dynamic payload on pipes 0 & 1 + write_register(DYNPD, read_register(DYNPD) | _BV(DPL_P1) | _BV(DPL_P0)); + dynamic_payloads_enabled = true; + ack_payloads_enabled = true; + } +} + +/****************************************************************************/ + +void RF24::disableAckPayload(void) +{ + // disable ack payloads (leave dynamic payload features as is) + if (ack_payloads_enabled){ + write_register(FEATURE, read_register(FEATURE) | ~_BV(EN_ACK_PAY)); + + IF_SERIAL_DEBUG(printf("FEATURE=%i\r\n", read_register(FEATURE))); + + ack_payloads_enabled = false; + } +} + +/****************************************************************************/ + +void RF24::enableDynamicAck(void) +{ + // + // enable dynamic ack features + // + //toggle_features(); + write_register(FEATURE, read_register(FEATURE) | _BV(EN_DYN_ACK)); + + IF_SERIAL_DEBUG(printf("FEATURE=%i\r\n", read_register(FEATURE))); +} + +/****************************************************************************/ + +bool RF24::writeAckPayload(uint8_t pipe, const void* buf, uint8_t len) +{ + if (ack_payloads_enabled){ + const uint8_t* current = reinterpret_cast(buf); + + write_payload(current, len, W_ACK_PAYLOAD | (pipe & 0x07)); + return !(status & _BV(TX_FULL)); + } + return 0; +} + +/****************************************************************************/ + +bool RF24::isAckPayloadAvailable(void) +{ + return available(NULL); +} + +/****************************************************************************/ + +bool RF24::isPVariant(void) +{ + return _is_p_variant; +} + +/****************************************************************************/ + +void RF24::setAutoAck(bool enable) +{ + if (enable){ + write_register(EN_AA, 0x3F); + }else{ + write_register(EN_AA, 0); + // accomodate ACK payloads feature + if (ack_payloads_enabled){ + disableAckPayload(); + } + } +} + +/****************************************************************************/ + +void RF24::setAutoAck(uint8_t pipe, bool enable) +{ + if (pipe < 6) { + uint8_t en_aa = read_register(EN_AA); + if (enable) { + en_aa |= _BV(pipe); + }else{ + en_aa &= ~_BV(pipe); + if (ack_payloads_enabled && !pipe){ + disableAckPayload(); + } + } + write_register(EN_AA, en_aa); + } +} + +/****************************************************************************/ + +bool RF24::testCarrier(void) +{ + return (read_register(CD) & 1); +} + +/****************************************************************************/ + +bool RF24::testRPD(void) +{ + return (read_register(RPD) & 1); +} + +/****************************************************************************/ + +void RF24::setPALevel(uint8_t level, bool lnaEnable) +{ + + uint8_t setup = read_register(RF_SETUP) & 0xF8; + + if (level > 3) { // If invalid level, go to max PA + level = (RF24_PA_MAX << 1) + lnaEnable; // +1 to support the SI24R1 chip extra bit + } else { + level = (level << 1) + lnaEnable; // Else set level as requested + } + + write_register(RF_SETUP, setup |= level); // Write it to the chip +} + +/****************************************************************************/ + +uint8_t RF24::getPALevel(void) +{ + + return (read_register(RF_SETUP) & (_BV(RF_PWR_LOW) | _BV(RF_PWR_HIGH))) >> 1; +} + +/****************************************************************************/ + +uint8_t RF24::getARC(void) +{ + + return read_register(OBSERVE_TX) & 0x0F; +} + +/****************************************************************************/ + +bool RF24::setDataRate(rf24_datarate_e speed) +{ + bool result = false; + uint8_t setup = read_register(RF_SETUP); + + // HIGH and LOW '00' is 1Mbs - our default + setup &= ~(_BV(RF_DR_LOW) | _BV(RF_DR_HIGH)); + + #if !defined(F_CPU) || F_CPU > 20000000 + txDelay = 280; + #else //16Mhz Arduino + txDelay=85; + #endif + if (speed == RF24_250KBPS) { + // Must set the RF_DR_LOW to 1; RF_DR_HIGH (used to be RF_DR) is already 0 + // Making it '10'. + setup |= _BV(RF_DR_LOW); + #if !defined(F_CPU) || F_CPU > 20000000 + txDelay = 505; + #else //16Mhz Arduino + txDelay = 155; + #endif + } else { + // Set 2Mbs, RF_DR (RF_DR_HIGH) is set 1 + // Making it '01' + if (speed == RF24_2MBPS) { + setup |= _BV(RF_DR_HIGH); + #if !defined(F_CPU) || F_CPU > 20000000 + txDelay = 240; + #else // 16Mhz Arduino + txDelay = 65; + #endif + } + } + write_register(RF_SETUP, setup); + + // Verify our result + if (read_register(RF_SETUP) == setup) { + result = true; + } + return result; +} + +/****************************************************************************/ + +rf24_datarate_e RF24::getDataRate(void) +{ + rf24_datarate_e result; + uint8_t dr = read_register(RF_SETUP) & (_BV(RF_DR_LOW) | _BV(RF_DR_HIGH)); + + // switch uses RAM (evil!) + // Order matters in our case below + if (dr == _BV(RF_DR_LOW)) { + // '10' = 250KBPS + result = RF24_250KBPS; + } else if (dr == _BV(RF_DR_HIGH)) { + // '01' = 2MBPS + result = RF24_2MBPS; + } else { + // '00' = 1MBPS + result = RF24_1MBPS; + } + return result; +} + +/****************************************************************************/ + +void RF24::setCRCLength(rf24_crclength_e length) +{ + config_reg &= ~(_BV(CRCO) | _BV(EN_CRC)); + + // switch uses RAM (evil!) + if (length == RF24_CRC_DISABLED) { + // Do nothing, we turned it off above. + } else if (length == RF24_CRC_8) { + config_reg |= _BV(EN_CRC); + } else { + config_reg |= _BV(EN_CRC); + config_reg |= _BV(CRCO); + } + write_register(NRF_CONFIG, config_reg); +} + +/****************************************************************************/ + +rf24_crclength_e RF24::getCRCLength(void) +{ + rf24_crclength_e result = RF24_CRC_DISABLED; + uint8_t AA = read_register(EN_AA); + config_reg = read_register(NRF_CONFIG); + + if (config_reg & _BV(EN_CRC) || AA) { + if (config_reg & _BV(CRCO)) { + result = RF24_CRC_16; + } else { + result = RF24_CRC_8; + } + } + + return result; +} + +/****************************************************************************/ + +void RF24::disableCRC(void) +{ + config_reg &= ~_BV(EN_CRC); + write_register(NRF_CONFIG, config_reg); +} + +/****************************************************************************/ +void RF24::setRetries(uint8_t delay, uint8_t count) +{ + write_register(SETUP_RETR, rf24_min(15, delay) << ARD | rf24_min(15, count)); +} + +/****************************************************************************/ +void RF24::startConstCarrier(rf24_pa_dbm_e level, uint8_t channel) +{ + stopListening(); + write_register(RF_SETUP, read_register(RF_SETUP) | _BV(CONT_WAVE) | _BV(PLL_LOCK)); + if (isPVariant()){ + setAutoAck(0); + setRetries(0, 0); + uint8_t dummy_buf[32]; + for (uint8_t i = 0; i < 32; ++i) + dummy_buf[i] = 0xFF; + + // use write_register() instead of openWritingPipe() to bypass + // truncation of the address with the current RF24::addr_width value + write_register(TX_ADDR, reinterpret_cast(&dummy_buf), 5); + flush_tx(); // so we can write to top level + + // use write_register() instead of write_payload() to bypass + // truncation of the payload with the current RF24::payload_size value + write_register(W_TX_PAYLOAD, reinterpret_cast(&dummy_buf), 32); + + disableCRC(); + } + setPALevel(level); + setChannel(channel); + IF_SERIAL_DEBUG(printf_P(PSTR("RF_SETUP=%02x\r\n"), read_register(RF_SETUP))); + ce(HIGH); + if (isPVariant()){ + delay(1); // datasheet says 1 ms is ok in this instance + ce(LOW); + reUseTX(); + } +} + +/****************************************************************************/ +void RF24::stopConstCarrier() +{ + /* + * A note from the datasheet: + * Do not use REUSE_TX_PL together with CONT_WAVE=1. When both these + * registers are set the chip does not react when setting CE low. If + * however, both registers are set PWR_UP = 0 will turn TX mode off. + */ + powerDown(); // per datasheet recommendation (just to be safe) + write_register(RF_SETUP, read_register(RF_SETUP) & ~_BV(CONT_WAVE) & ~_BV(PLL_LOCK)); + ce(LOW); +} diff --git a/RF24.h b/RF24.h new file mode 100644 index 0000000..80fc0f5 --- /dev/null +++ b/RF24.h @@ -0,0 +1,273 @@ +/* + Copyright (C) 2011 J. Coliz + + 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 +#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__ diff --git a/enc28j60.cpp b/enc28j60.cpp new file mode 100644 index 0000000..2b36e41 --- /dev/null +++ b/enc28j60.cpp @@ -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 + +#if ARDUINO >= 100 +#include // Arduino 1.0 +#else +#include // Arduino 0022 +#endif +#include +#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); + } +} diff --git a/enc28j60.h b/enc28j60.h new file mode 100644 index 0000000..e72fd74 --- /dev/null +++ b/enc28j60.h @@ -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 +/** @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 uint8_t 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 bool 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 uint16_t 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 uint8_t 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 uint16_t 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 uint16_t 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 uint16_t 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 diff --git a/mySetup.cpp_example.txt b/mySetup.cpp_example.txt index 0efc771..7d0781f 100644 --- a/mySetup.cpp_example.txt +++ b/mySetup.cpp_example.txt @@ -9,16 +9,17 @@ // To prevent this, temporarily rename it to mySetup.txt or similar. // +// Only the #include directives relating to the devices in use need be included here. #include "IODevice.h" #include "Turnouts.h" #include "Sensors.h" #include "IO_HCSR04.h" #include "IO_VL53L0X.h" - - -// The #if directive prevent compile errors for Uno and Nano by excluding the -// HAL directives from the build. -#if !defined(IO_NO_HAL) +#include "DFPlayer.h" +#include "IO_Network.h" +#include "Net_RF24" +#include "Net_ENC28J60" +#include "Net_Ethernet" // Examples of statically defined HAL directives (alternative to the create() call). @@ -81,49 +82,6 @@ //PCF8574 gpioModule4(200, 8, 0x23, 40); -//======================================================================= -// The following directive defines an HCSR04 ultrasonic ranging module. -//======================================================================= -// The parameters are: -// Vpin=2000 (only one VPIN per directive) -// Number of VPINs=1 -// Arduino pin connected to TRIG=30 -// Arduino pin connected to ECHO=31 -// Minimum trigger range=20cm (VPIN goes to 1 when <20cm) -// Maximum trigger range=25cm (VPIN goes to 0 when >25cm) -// Note: Multiple devices can be configured by using a different ECHO pin -// for each one. The TRIG pin can be shared between multiple devices. -// Be aware that the 'ping' of one device may be received by another -// device and position them accordingly! - -//HCSR04 sonarModule1(2000, 30, 31, 20, 25); -//HCSR04 sonarModule2(2001, 30, 32, 20, 25); - - -//======================================================================= -// The following directive defines a single VL53L0X Time-of-Flight range sensor. -//======================================================================= -// The parameters are: -// VPIN=5000 -// Number of VPINs=1 -// I2C address=0x29 (default for this chip) -// Minimum trigger range=200mm (VPIN goes to 1 when <20cm) -// Maximum trigger range=250mm (VPIN goes to 0 when >25cm) - -//VL53L0X tofModule1(5000, 1, 0x29, 200, 250); - -// For multiple VL53L0X modules, add another parameter which is a VPIN connected to the -// module's XSHUT pin. This allows the modules to be configured, at start, -// with distinct I2C addresses. In this case, the address 0x29 is only used during -// initialisation to configure each device in turn with the desired unique I2C address. -// The examples below have the modules' XSHUT pins connected to the first two pins of -// the first MCP23017 module (164 and 165), but Arduino pins may be used instead. -// The first module here is given I2C address 0x30 and the second is 0x31. - -//VL53L0X tofModule1(5000, 1, 0x30, 200, 250, 164); -//VL53L0X tofModule2(5001, 1, 0x31, 200, 250, 165); - - //======================================================================= // The function mySetup() is invoked from CS if it exists within the build. // It is called just before mysetup.h is executed, so things set up within here can be @@ -179,6 +137,50 @@ void mySetup() { // Sensor::create(i, i, 1); + //======================================================================= + // The following directive defines an HCSR04 ultrasonic ranging module. + //======================================================================= + // The parameters are: + // Vpin=2000 (only one VPIN per directive) + // Number of VPINs=1 + // Arduino pin connected to TRIG=30 + // Arduino pin connected to ECHO=31 + // Minimum trigger range=20cm (VPIN goes to 1 when <20cm) + // Maximum trigger range=25cm (VPIN goes to 0 when >25cm) + // Note: Multiple devices can be configured by using a different ECHO pin + // for each one. The TRIG pin can be shared between multiple devices. + // Be aware that the 'ping' of one device may be received by another + // device and position them accordingly! + + //HCSR04 sonarModule1(2000, 30, 31, 20, 25); + //HCSR04 sonarModule2(2001, 30, 32, 20, 25); + + + //======================================================================= + // VL53L0X Time-of-Flight range sensor. + //======================================================================= + // The following directive defines a single VL53L0X Time-of-Flight range sensor. + // The parameters are: + // VPIN=5000 + // Number of VPINs=1 + // I2C address=0x29 (default for this chip) + // Minimum trigger range=200mm (VPIN goes to 1 when <20cm) + // Maximum trigger range=250mm (VPIN goes to 0 when >25cm) + + //VL53L0X::create(5000, 1, 0x29, 200, 250); + + // For multiple VL53L0X modules, add another parameter which is a VPIN connected to the + // module's XSHUT pin. This allows the modules to be configured, at start, + // with distinct I2C addresses. In this case, the address 0x29 is only used during + // initialisation to configure each device in turn with the desired unique I2C address. + // The examples below have the modules' XSHUT pins connected to the first two pins of + // the first MCP23017 module (164 and 165), but Arduino pins may be used instead. + // The first module here is given I2C address 0x30 and the second is 0x31. + + //VL53L0X::create(5000, 1, 0x30, 200, 250, 164); + //VL53L0X::create(5001, 1, 0x31, 200, 250, 165); + + //======================================================================= // Play mp3 files from a Micro-SD card, using a DFPlayer MP3 Module. //======================================================================= @@ -209,6 +211,93 @@ void mySetup() { // DFPlayer::create(10000, 10, Serial1); -} + //======================================================================= + // Remote (networked) VPIN Configuration + //======================================================================= + // Define remote pins to be used. The range of remote pins is like a common data area shared + // between all nodes. + // For outputs, a write to a remote VPIN causes a message to be sent to another node, which then performs + // the write operation on the device VPIN that is local to that node. + // For inputs, the state of remote input VPIN is read on the node where it is connected, and then + // sent to other nodes in the system where the state is saved and processed. Updates are sent on change, and + // also periodically if no changes. + // + // Currently, analogue inputs are not enabled for remote access. + // + // Each definition is a triple of remote node, remote pin, type, indexed by relative pin. + // Each pin may be an input whose value is to be read before being transmitted (RPIN_IN), + // an output which is to be triggered when written to (RPIN_OUT) or both (RPIN_INPUT), + // such as a servo or DFPlayer device pin. Unused pins (e.g. spares to reserve contiguous + // pin sequences) should be defined sd VPIN_NONE with 0 as the type. + // Where possible, align the input and inout pins on an offset which is a multiple of 8 + // from the start of the remote pins, as in the example below. + // + // There is a limit of 224 in the number of pin states per node that are sent + // over the network (to keep the number of sent messages to 1 x 32 bytes). This restriction + // may be lifted in future. + // + // In the example below with two nodes 30 and 31, + // a turnout object set to operate pin 4004 will operate the servo connected to + // VPIN 100 on node 30; + // a sensor object monitoring pin 4024 will trigger if pin D24 on node + // 31 activates; + // an output object associated with pin 4002 will, when set on, activate + // pin 166 (MCP23017 pin 3) on node 30. + // + // All nodes on the same network should use the same REMOTEPINS setup, and the + // node number of each node should be set to a different number. + // + // REMOTEPINS rpins[] = { + // {30,164,RPIN_IN} , //4000 Node 30, first MCP23017 pin, input + // {30,165,RPIN_IN}, //4001 Node 30, second MCP23017 pin, input + // {30,166,RPIN_OUT}, //4002 Node 30, third MCP23017 pin, output + // {30,166,RPIN_OUT}, //4003 Node 30, fourth MCP23017 pin, output + // {30,100,RPIN_INOUT}, //4004 Node 30, first PCA9685 servo pin + // {30,101,RPIN_INOUT}, //4005 Node 30, second PCA9685 servo pin + // {30,102,RPIN_INOUT}, //4006 Node 30, third PCA9685 servo pin + // {30,103,RPIN_INOUT}, //4007 Node 30, fourth PCA9685 servo pin + // {30,24,RPIN_IN}, //4008 Node 30, Arduino pin D24 + // {30,25,RPIN_IN}, //4009 Node 30, Arduino pin D25 + // {30,26,RPIN_IN}, //4010 Node 30, Arduino pin D26 + // {30,27,RPIN_IN}, //4011 Node 30, Arduino pin D27 + // {30,VPIN_NONE,0}, //4012 Node 30, spare + // {30,VPIN_NONE,0}, //4013 Node 30, spare + // {30,VPIN_NONE,0}, //4014 Node 30, spare + // {30,VPIN_NONE,0}, //4015 Node 30, spare + // + // {31,164,RPIN_IN} , //4016 Node 31, first MCP23017 pin, input + // {31,165,RPIN_IN}, //4017 Node 31, second MCP23017 pin, input + // {31,166,RPIN_OUT}, //4018 Node 31, third MCP23017 pin, output + // {31,166,RPIN_OUT}, //4019 Node 31, fourth MCP23017 pin, output + // {31,100,RPIN_INOUT}, //4020 Node 31, first PCA9685 servo pin + // {31,101,RPIN_INOUT}, //4021 Node 31, second PCA9685 servo pin + // {31,102,RPIN_INOUT}, //4022 Node 31, third PCA9685 servo pin + // {31,103,RPIN_INOUT}, //4023 Node 31, fourth PCA9685 servo pin + // {31,24,RPIN_IN}, //4024 Node 31, Arduino pin D24 + // {31,25,RPIN_IN}, //4025 Node 31, Arduino pin D25 + // {31,26,RPIN_IN}, //4026 Node 31, Arduino pin D26 + // {31,27,RPIN_IN}, //4027 Node 31, Arduino pin D27 + // {31,VPIN_NONE,0}, //4028 Node 31, spare + // {31,VPIN_NONE,0}, //4029 Node 31, spare + // {31,VPIN_NONE,0}, //4030 Node 31, spare + // {31,VPIN_NONE,0} //4031 Node 31, spare + // }; -#endif + // Network using RF24 wireless module, with CE on pin 48 and CS on pin 49 and node=30 + + // Net_RF24 *rf24Driver = new Net_RF24(48, 49); + // Network::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::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::create(4000, NUMREMOTEPINS(rpins), 30, rpins, etherDriver); + +} diff --git a/net.h b/net.h new file mode 100644 index 0000000..e60fc8a --- /dev/null +++ b/net.h @@ -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 + +// 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 diff --git a/tcpip.cpp b/tcpip.cpp new file mode 100644 index 0000000..e42f07f --- /dev/null +++ b/tcpip.cpp @@ -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 + +#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