1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-28 18:03:45 +02:00

Compare commits

...

9 Commits

Author SHA1 Message Date
Neil McKechnie
b2323f0e74 Create IO_S88.h
Experimental S88 driver code, not tested or validated.
2022-01-17 13:03:28 +00:00
Neil McKechnie
9ba43c28bf Create IO_LedChain.h
Shift register output device.
2021-12-15 14:15:00 +00:00
Neil McKechnie
58d2b69db5 Merge branch 'master' into neil-network 2021-12-01 15:41:08 +00:00
Neil McKechnie
011a5c517b Merge branch 'master' into neil-network 2021-11-24 13:08:11 +00:00
Neil McKechnie
b8ee7f034b Merge branch 'master' into neil-network 2021-11-12 00:18:09 +00:00
Neil McKechnie
13e889a82c Avoid sending ethernet responses one character at a time.
Send commands
2021-11-11 13:33:27 +00:00
Neil McKechnie
4f688938a4 Update EthernetInterface.h
Remove spurious character
2021-11-11 13:32:26 +00:00
Neil McKechnie
6a1ffdb3fa Network support 2021-11-11 13:31:59 +00:00
Neil McKechnie
8e6c232d05 Delay Turnout <H> response following movement. 2021-11-11 13:30:37 +00:00
19 changed files with 5110 additions and 14 deletions

View File

@@ -131,6 +131,7 @@ void DCCEXParser::loop(Stream &stream)
}
}
Sensor::checkAll(&stream); // Update and print changes
Turnout::loop(); // Check for outstanding turnout responses
}
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)

52
EtherCard.cpp Normal file
View File

@@ -0,0 +1,52 @@
// This code slightly follows the conventions of, but is not derived from:
// EHTERSHIELD_H library for Arduino etherShield
// Copyright (c) 2008 Xing Yu. All right reserved. (this is LGPL v2.1)
// It is however derived from the enc28j60 and ip code (which is GPL v2)
// Author: Pascal Stang
// Modified by: Guido Socher
// DHCP code: Andrew Lindsay
// Hence: GPL V2
//
// 2010-05-19 <jc@wippler.nl>
#include "EtherCard.h"
#include <stdarg.h>
#include <avr/eeprom.h>
EtherCard ether;
uint8_t EtherCard::mymac[ETH_LEN]; // my MAC address
uint8_t EtherCard::myip[IP_LEN]; // my ip address
uint8_t EtherCard::netmask[IP_LEN]; // subnet mask
uint8_t EtherCard::broadcastip[IP_LEN]; // broadcast address
uint8_t EtherCard::gwip[IP_LEN]; // gateway
uint8_t EtherCard::dhcpip[IP_LEN]; // dhcp server
uint8_t EtherCard::dnsip[IP_LEN]; // dns server
uint8_t EtherCard::hisip[IP_LEN]; // ip address of remote host
uint16_t EtherCard::delaycnt = 0; //request gateway ARP lookup
uint8_t EtherCard::begin (const uint16_t size,
const uint8_t* macaddr,
uint8_t csPin) {
copyMac(mymac, macaddr);
return initialize(size, mymac, csPin);
}
bool EtherCard::staticSetup (const uint8_t* my_ip,
const uint8_t* gw_ip,
const uint8_t* dns_ip,
const uint8_t* mask) {
if (my_ip != 0)
copyIp(myip, my_ip);
if (gw_ip != 0)
setGwIp(gw_ip);
if (dns_ip != 0)
copyIp(dnsip, dns_ip);
if(mask != 0)
copyIp(netmask, mask);
updateBroadcastAddress();
delaycnt = 0; //request gateway ARP lookup
return true;
}

238
EtherCard.h Normal file
View File

@@ -0,0 +1,238 @@
// This code slightly follows the conventions of, but is not derived from:
// EHTERSHIELD_H library for Arduino etherShield
// Copyright (c) 2008 Xing Yu. All right reserved. (this is LGPL v2.1)
// It is however derived from the enc28j60 and ip code (which is GPL v2)
// Author: Pascal Stang
// Modified by: Guido Socher
// DHCP code: Andrew Lindsay
// Hence: GPL V2
//
// 2010-05-19 <jc@wippler.nl>
//
//
// PIN Connections (Using Arduino UNO):
// VCC - 3.3V
// GND - GND
// SCK - Pin 13
// SO - Pin 12
// SI - Pin 11
// CS - Pin 8
//
/** @file */
#ifndef EtherCard_h
#define EtherCard_h
// #ifndef __PROG_TYPES_COMPAT__
// #define __PROG_TYPES_COMPAT__
// #endif
// #if ARDUINO >= 100
#include <Arduino.h> // Arduino 1.0
// #define WRITE_RESULT size_t
// #define WRITE_RETURN return 1;
// #else
// #include <WProgram.h> // Arduino 0022
// #define WRITE_RESULT void
// #define WRITE_RETURN
// #endif
// #include <avr/pgmspace.h>
#include "enc28j60.h"
// Based on the net.h file from the AVRlib library by Pascal Stang.
// Author: Guido Socher
// Copyright: GPL V2
//
// For AVRlib See http://www.procyonengineering.com/
// Used with explicit permission of Pascal Stang.
//
// 2010-05-20 <jc@wippler.nl>
// notation: _P = position of a field
// _V = value of a field
// ******* ETH *******
#define ETH_HEADER_LEN 14
#define ETH_LEN 6
// values of certain bytes:
#define ETHTYPE_ARP_H_V 0x08
#define ETHTYPE_ARP_L_V 0x06
#define ETHTYPE_IP_H_V 0x08
#define ETHTYPE_IP_L_V 0x00
// byte positions in the ethernet frame:
//
// Ethernet type field (2bytes):
#define ETH_TYPE_H_P 12
#define ETH_TYPE_L_P 13
//
#define ETH_DST_MAC 0
#define ETH_SRC_MAC 6
// ******* ARP *******
#define ETH_ARP_OPCODE_REPLY_H_V 0x0
#define ETH_ARP_OPCODE_REPLY_L_V 0x02
#define ETH_ARP_OPCODE_REQ_H_V 0x0
#define ETH_ARP_OPCODE_REQ_L_V 0x01
// start of arp header:
#define ETH_ARP_P 0xe
//
#define ETHTYPE_ARP_L_V 0x06
// arp.dst.ip
#define ETH_ARP_DST_IP_P 0x26
// arp.opcode
#define ETH_ARP_OPCODE_H_P 0x14
#define ETH_ARP_OPCODE_L_P 0x15
// arp.src.mac
#define ETH_ARP_SRC_MAC_P 0x16
#define ETH_ARP_SRC_IP_P 0x1c
#define ETH_ARP_DST_MAC_P 0x20
#define ETH_ARP_DST_IP_P 0x26
// ******* IP *******
#define IP_HEADER_LEN 20
#define IP_LEN 4
// ip.src
#define IP_SRC_P 0x1a
#define IP_DST_P 0x1e
#define IP_HEADER_LEN_VER_P 0xe
#define IP_CHECKSUM_P 0x18
#define IP_TTL_P 0x16
#define IP_FLAGS_P 0x14
#define IP_P 0xe
#define IP_TOTLEN_H_P 0x10
#define IP_TOTLEN_L_P 0x11
#define IP_PROTO_P 0x17
#define IP_PROTO_ICMP_V 1
#define IP_PROTO_TCP_V 6
// 17=0x11
#define IP_PROTO_UDP_V 17
// ******* ICMP *******
#define ICMP_TYPE_ECHOREPLY_V 0
#define ICMP_TYPE_ECHOREQUEST_V 8
//
#define ICMP_TYPE_P 0x22
#define ICMP_CHECKSUM_P 0x24
#define ICMP_CHECKSUM_H_P 0x24
#define ICMP_CHECKSUM_L_P 0x25
#define ICMP_IDENT_H_P 0x26
#define ICMP_IDENT_L_P 0x27
#define ICMP_DATA_P 0x2a
// ******* UDP *******
#define UDP_HEADER_LEN 8
//
#define UDP_SRC_PORT_H_P 0x22
#define UDP_SRC_PORT_L_P 0x23
#define UDP_DST_PORT_H_P 0x24
#define UDP_DST_PORT_L_P 0x25
//
#define UDP_LEN_H_P 0x26
#define UDP_LEN_L_P 0x27
#define UDP_CHECKSUM_H_P 0x28
#define UDP_CHECKSUM_L_P 0x29
#define UDP_DATA_P 0x2a
/** Enable DHCP.
* Setting this to zero disables the use of DHCP; if a program uses DHCP it will
* still compile but the program will not work. Saves about 60 bytes SRAM and
* 1550 bytes flash.
*/
#define ETHERCARD_DHCP 0
/** Enable client connections.
* Setting this to zero means that the program cannot issue TCP client requests
* anymore. Compilation will still work but the request will never be
* issued. Saves 4 bytes SRAM and 550 byte flash.
*/
#define ETHERCARD_TCPCLIENT 0
/** Enable TCP server functionality.
* Setting this to zero means that the program will not accept TCP client
* requests. Saves 2 bytes SRAM and 250 bytes flash.
*/
#define ETHERCARD_TCPSERVER 0
/** Enable UDP server functionality.
* If zero UDP server is disabled. It is
* still possible to register callbacks but these will never be called. Saves
* about 40 bytes SRAM and 200 bytes flash. If building with -flto this does not
* seem to save anything; maybe the linker is then smart enough to optimize the
* call away.
*/
#define ETHERCARD_UDPSERVER 0
/** Enable automatic reply to pings.
* Setting to zero means that the program will not automatically answer to
* PINGs anymore. Also the callback that can be registered to answer incoming
* pings will not be called. Saves 2 bytes SRAM and 230 bytes flash.
*/
#define ETHERCARD_ICMP 1
/** Enable use of stash.
* Setting this to zero means that the stash mechanism cannot be used. Again
* compilation will still work but the program may behave very unexpectedly.
* Saves 30 bytes SRAM and 80 bytes flash.
*/
#define ETHERCARD_STASH 0
/** This type definition defines the structure of a UDP server event handler callback function */
typedef void (*UdpServerCallback)(
uint16_t dest_port, ///< Port the packet was sent to
uint8_t src_ip[IP_LEN], ///< IP address of the sender
uint16_t src_port, ///< Port the packet was sent from
const char *data, ///< UDP payload data
uint16_t len); ///< Length of the payload data
/** This type definition defines the structure of a DHCP Option callback function */
typedef void (*DhcpOptionCallback)(
uint8_t option, ///< The option number
const byte* data, ///< DHCP option data
uint8_t len); ///< Length of the DHCP option data
/** This class provides the main interface to a ENC28J60 based network interface card and is the class most users will use.
* @note All TCP/IP client (outgoing) connections are made from source port in range 2816-3071. Do not use these source ports for other purposes.
*/
class EtherCard : public ENC28J60 {
public:
static uint8_t mymac[ETH_LEN]; ///< MAC address
static uint8_t myip[IP_LEN]; ///< IP address
static uint8_t netmask[IP_LEN]; ///< Netmask
static uint8_t broadcastip[IP_LEN]; ///< Subnet broadcast address
static uint8_t gwip[IP_LEN]; ///< Gateway
static uint8_t dhcpip[IP_LEN]; ///< DHCP server IP address
static uint8_t dnsip[IP_LEN]; ///< DNS server IP address
static uint8_t hisip[IP_LEN]; ///< DNS lookup result
static uint16_t hisport; ///< TCP port to connect to (default 80)
static bool using_dhcp; ///< True if using DHCP
static bool persist_tcp_connection; ///< False to break connections on first packet received
static uint16_t delaycnt; ///< Counts number of cycles of packetLoop when no packet received - used to trigger periodic gateway ARP request
static uint8_t begin (const uint16_t size, const uint8_t* macaddr,
uint8_t csPin = SS);
static bool staticSetup (const uint8_t* my_ip,
const uint8_t* gw_ip = 0,
const uint8_t* dns_ip = 0,
const uint8_t* mask = 0);
static void makeUdpReply (const char *data, uint8_t len, uint16_t port);
static uint16_t packetLoop (uint16_t plen);
static void setGwIp (const uint8_t *gwipaddr);
static void updateBroadcastAddress();
static uint8_t clientWaitingGw ();
static void udpPrepare (uint16_t sport, const uint8_t *dip, uint16_t dport);
static void udpTransmit (uint16_t len);
static void sendUdp (const char *data, uint8_t len, uint16_t sport,
const uint8_t *dip, uint16_t dport);
static void copyIp (uint8_t *dst, const uint8_t *src);
static void copyMac (uint8_t *dst, const uint8_t *src);
};
extern EtherCard ether; //!< Global presentation of EtherCard class
#endif

View File

@@ -178,8 +178,14 @@ void EthernetInterface::loop()
int socketOut=outboundRing->read();
if (socketOut>=0) {
int count=outboundRing->count();
uint16_t index = 0;
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
while (count > 0) {
index = 0;
for(;count>0 && index < MAX_ETH_BUFFER;count--) buffer[index++] = outboundRing->read();
clients[socketOut].write(buffer, index);
}
//for(;count>0;count--) clients[socketOut].write(outboundRing->read());
clients[socketOut].flush(); //maybe
}
}

256
IO_LedChain.h Normal file
View File

@@ -0,0 +1,256 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* This driver is designed for the LED Driver devices which are characteristically
* driven with DATAIN, CLOCK and LATCH signals, and chained one to another by connecting
* the DATAOUT pin of one device to the next device's DATAIN pin. The devices act like a
* shift register, so the data bits are sent to the first device's DATAIN pin and clocked through
* the shift register (bit by bit). Once the shift register is loaded, the data is latched
* into the devices when the LATCH signal is pulsed.
*
* Some devices drive on/off outputs, so one bit is written to the shift register for each
* output to be driven. For example, with 16 x 1-bit device, 16 bits are sent, one for each
* LED output.
*
* Other devices allow the outputs to be driven by grey-scale, with up to 16 bit resolution.
* In this case, the number of bits sent to the shift register increases drastically; for
* 12 LEDs at 16-bit resolution, a total of 172 bits must be sent for each device in the chain.
* for 24 LEDs at 12-bit resolution, 268 bits must be sent for each device.
* The most significant bit of each value is sent first.
*
* RGB LEDs may be driven by connecting an output to each of the R/G/B wires of the LED, but
* most of the driver devices sink current to ground through the LED, so an RGB LED with a
* common anode (+ve terminal) must be used with these devices. Check the datasheet for details.
*
* Device variants known:
* TLC5947: 24 x 12-bit
* TLC5940: 16 x 12-bit
* TLC6C598: 8 x 1-bit
* TLC6C5912: 12 x 1-bit
* STP24DP05: 8 x 3-bit (RGB)
* MAX6979: 16 x 1-bit
* 74HC595: 8 x 1-bit
*
* All of these devices are able to support clock pulses of 30ns or shorter with a clock rate of
* 10MHz or faster; however, the Arduino isn't capable of running this fast, and the shortest pulse
* length that is generated is 750ns, and a peak clock rate of 186kHz. Faster rates could be
* achieved by using the SPI interface, but if any other SPI device is in use then additional
* device select circuitry would be required.
*
*/
#ifndef IO_LEDCHAIN_H
#define IO_LEDCHAIN_H
#include "IODevice.h"
class LedChain : public IODevice {
private:
#ifdef ARDUINO_ARCH_AVR
class DigPin {
private:
volatile uint8_t *ptr;
uint8_t mask;
public:
DigPin() { ptr = &mask; }
DigPin(int pinNumber) {
if (pinNumber >= 0 && pinNumber <= NUM_DIGITAL_PINS) {
int port = digitalPinToPort(pinNumber);
if (port != NOT_A_PORT) {
pinMode(pinNumber, OUTPUT);
mask = digitalPinToBitMask(pinNumber);
ptr = portOutputRegister(port);
return;
}
}
// Pin not valid, set pointer to somewhere benign
ptr = &mask;
}
void setValue(bool value) {
noInterrupts();
if (value)
*ptr |= mask;
else
*ptr &= ~mask;
interrupts();
}
void pulse() {
noInterrupts();
*ptr |= mask;
*ptr &= ~mask;
interrupts();
}
};
#else
// Fall back to digitalWrite calls.
class DigPin {
private:
int _pinNumber = 0;
public:
DigPin() {};
DigPin(int pinNumber) {
pinMode(pinNumber, OUTPUT);
_pinNumber = pinNumber;
}
void setValue(bool value) {
digitalWrite(_pinNumber, value);
}
void pulse() {
digitalWrite(_pinNumber, 1);
digitalWrite(_pinNumber, 0);
}
};
#endif
// pins must be arduino GPIO pins, not extender pins or HAL pins.
DigPin _dataPin;
DigPin _clockPin;
DigPin _latchPin;
int _bitsPerPin = 0;
byte *_values;
bool _changed = true;
int _nextRegisterPin = 0;
unsigned long _lastEntryTime = 0;
public:
// Constructor performs static initialisation of the device object
LedChain (VPIN vpin, int nPins, int dataPin, int clockPin, int latchPin, int bitsPerPin=1) {
_firstVpin = vpin;
_nPins = nPins;
_dataPin = DigPin(dataPin);
_clockPin = DigPin(clockPin);
_latchPin = DigPin(latchPin);
_bitsPerPin = bitsPerPin;
if (_bitsPerPin == 1)
_values = (byte *)calloc((_nPins+7)/8, 1); // 1 byte per 8 pins (rounded up)
else
_values = (byte *)calloc(_nPins, 2); // 2 bytes per pin.
addDevice(this);
}
// Static create function provides alternative way to create object
static void create(VPIN vpin, int nPins, int dataPin, int clockPin, int latchPin, int bitsPerPin=1) {
new LedChain (vpin, nPins, dataPin, clockPin, latchPin, bitsPerPin);
}
protected:
// _begin function called to perform dynamic initialisation of the device
void _begin() override {
_dataPin.setValue(0);
_clockPin.setValue(0);
_latchPin.setValue(0);
#if defined(DIAG_IO)
_display();
#endif
}
// Digital write - write on or off.
void _write(VPIN vpin, int value) {
if (_bitsPerPin == 1) {
int pin = vpin - _firstVpin;
uint8_t *ptr = _values + pin/8;
uint8_t mask = 1 << (pin % 8);
if (value)
*ptr |= mask;
else
*ptr &= ~mask;
} else {
// Write maximum positive value (will be truncated if too large)
writeAnalogue(vpin, value ? 0x7fff : 0);
}
_changed = true;
}
// Analogue write - write the supplied value
void _writeAnalogue(VPIN vpin, int value) {
if (_bitsPerPin == 1 ) {
_write(vpin, value);
} else {
int pin = vpin - _firstVpin;
uint16_t *ptr = (uint16_t *)(_values + pin*2);
*ptr = value;
}
_changed = true;
}
// _loop function - refresh device every 100ms if anything has changed.
void _loop(unsigned long currentMicros) override {
int count = 0;
if (_changed) {
// Remember the time that this output cycle started.
if (_nextRegisterPin == 0) _lastEntryTime = currentMicros;
if (_bitsPerPin == 1) {
int pin=_nextRegisterPin;
uint8_t *ptr = _values + pin/8;
uint8_t mask = 1;
while (true) {
// For each pin, write one bit to the shift register
uint8_t value = (*ptr & mask) ? 1 : 0;
_dataPin.setValue(value);
_clockPin.pulse();
mask <<= 1;
if (mask == 0) {
if (++count >= 2) { // max of 16 pins per loop entry
_nextRegisterPin = pin;
return; // Resume on next loop entry
}
// Move to next byte
ptr++;
mask = 1;
}
if (++pin >= _nPins) break;
}
} else {
// Multiple bits per pin - up to 16 bits stored in two bytes.
int pin=_nextRegisterPin;
uint16_t *ptr = (uint16_t *)_values + pin;
while (true) {
uint16_t value = *ptr++;
// For each pin, write the requisite number of bits to the shift register
uint16_t mask = 1 << (_bitsPerPin-1);
while (mask) {
_dataPin.setValue((value & mask) ? 1 : 0);
_clockPin.pulse();
mask >>= 1;
}
if (++pin >= _nPins) break; // finished.
if (++count >= 1) { // max of 1 pin per loop entry
_nextRegisterPin = pin;
return; // Resume on next loop entry
}
}
}
// Pulse latch pin to transfer data from shift register to outputs.
_latchPin.pulse();
//_changed = false;
}
_nextRegisterPin = 0; // Restart from the beginning on next entry
delayUntil(_lastEntryTime+100000UL); // At most one update cycle per 100ms
}
void _display() override {
DIAG(F("LedChain Configured on Vpins:%d-%d DataPin:%d ClockPin:%d LatchPin:%d BitsPerOutput:%d"),
_firstVpin, _firstVpin+_nPins-1, _dataPin, _clockPin, _latchPin, _bitsPerPin);
}
};
#endif //IO_LEDCHAIN_H

473
IO_Network.h Normal file
View File

@@ -0,0 +1,473 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Each node on the network is configured with a node number in the range 0-254.
* The remoting configuration defines, for each pin to be available remotely,
* the node number and the VPIN number on that node. The configuration must
* match in all nodes, since it is used by the sending node to identify the node
* and VPIN to which a write command is to be sent, and the VPIN number for a
* sensor/input, and on the receiving node to identify the node from which a
* sensor/input value is being sourced.
*
* The node number is also used in the network driver's address
* field. Number 255 is treated as a multicast address. All stations listen on
* their own address and on the multicast address.
*
* All nodes send regular multicast packets containing the latest values of the
* sensors as they know them. On receipt of such a packet, each node extracts
* the states of the sensors which are sourced by the originating node, and
* updates the values in its own local data. Thus, each node has a copy of the
* states of all digital input pin values that are defined in the remoting
* configuration. Multicasts are sent frequently, so if one is missed
* then, like a London bus, another will be along shortly.
*
* Commands (originating from write() or writeAnalogue() calls) are sent
* immediately, directly from the originating node to the target node. This
* is done with acknowlegements enabled to maximise the probability of
* successful delivery.
*
* Usage:
* First declare, for each remote pin in the common area, the mapping onto
* a node and VPIN number. The array below assumes that the first remote
* VPIN is 4000. The REMOTEPINS definition
* should be the same on all nodes in the network. For outputs, it is the
* definition in the sending node that dictates which node and VPIN the
* action is performed on. For inputs, the value is placed into the
* VPIN location defined in the sending node (that scans the input value),
* but the value is only accepted in the receiving node if its definition
* shows that the signal originates in the sending node.
*
* Example to go into mySetup() function in mySetup.cpp:
* REMOTEPINS rpins[] = {
* {0,30,RPIN_OUT}, //4000 Node 0 GPIO pin 30 (output)
* {1,30,RPIN_IN}, //4001 Node 1 GPIO pin 30 (input)
* {1,100,RPIN_INOUT}, //4002 Node 1 Servo (PCA9685) pin (output to servo, input busy flag)
* {1,164,RPIN_IN}, //4003 Node 1 GPIO extender (MCP23017) pin (input)
* {2,164,RPIN_IN} //4004 Node 2 GPIO extender (MCP23017) pin (input)
* }
* // FirstVPIN, nPins, thisNode, pinDefs, CEPin, CSNPin
* Network::create(4000, NUMREMOTEPINS(rpins), 0, rpins, new RF24Driver(48, 49));
*
* This example defines VPINs 4000-4004 which map onto pins on nodes 0, 1 and 2.
* The network device in this case is an nRF24L01, which has to be connected to the hardware
* MISO, MOSI, SCK and CS pins of the microcontroller; in addition, the CE and
* CSN pins on the nRF24 are connected to two pins (48 and 49 above).
*
* If any of pins 4000-4004 are referenced by turnouts, outputs or sensors, or by EX-RAIL,
* then the corresponding remote pin state will be retrieved or updated.
* For example, in EX-RAIL,
* SET(4000) on node 1 or 2 will set pin 30 on Node 0 to +5V (pin is put into output mode on first write).
* AT(4001) on node 0 or 2 will wait until the sensor attached to pin 30 on Node 1 activates.
* SERVO(4002,300,2) on node 0 or 2 will reposition the servo on Node 1 PCA9685 module to position 300, and
* AT(-4002) will wait until the servo has finished moving.
*
* The following sensor definition on node 0 will map onto VPIN 4004, i.e. Node 2 VPIN 164,
* which is the first pin on the first MCP23017:
* <S 1 4004 0>
* and when a sensor attached to the pin on node 2 is activated (pin pulled down to 0V) the following
* message will be generated on node 0:
* <Q 1>
* When the sensor deactivates, the following message will be generated on node 0:
* <q 1>
*/
#ifndef IO_NETWORK_H
#define IO_NETWORK_H
#include "IODevice.h"
#include "RF24.h"
// Macros and type for creating the remote pin definitions.
// The definitions are stored in PROGMEM to reduce RAM requirements.
// The flags byte contains, in the low 2 bits, RPIN_IN, RPIN_OUT or RPIN_INOUT.
typedef struct { uint8_t node; VPIN vpin; uint8_t flags; } RPIN;
#define REMOTEPINS static const RPIN PROGMEM
#define NUMREMOTEPINS(x) (sizeof(x)/sizeof(RPIN))
enum {
RPIN_IN=1,
RPIN_OUT=2,
RPIN_INOUT=RPIN_IN|RPIN_OUT,
};
// Define interface for network driver. This should be implemented for each supported
// network type.
// class NetInterface {
// public:
// bool begin();
// bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t size);
// bool available();
// uint8_t read(uint8_t buffer[], uint8_t size);
// void loop();
// };
// Class implementing the Application-layer network functionality.
// This is implemented as an IODevice instance so it can be easily
// plugged in to the HAL framwork.
template <class NetInterface>
class Network : public IODevice {
private:
const RPIN *_pinDefs; // May need to become a far pointer!
// Time of last loop execution
unsigned long _lastExecutionTime;
// Current digital values for remoted pins, stored as a bit field
uint8_t *_pinValues;
// Number of the current node (1-254)
uint8_t _thisNode;
// Maximum size of payload (must be 32 or less for RF24)
static const uint8_t maxPayloadSize = 32;
bool _updatePending;
int _nextSendPin;
unsigned long _lastMulticastTime;
int _firstPinToSend; // must be a multiple of 8
int _numPinsToSend; // need not be a multiple of 8
NetInterface *_netDriver;
// List of network commands
enum : uint8_t {
NET_CMD_WRITE = 0,
NET_CMD_WRITEANALOGUE = 1,
NET_CMD_VALUEUPDATE = 2,
};
// Field Positions in Network Header
enum NetHeader {
IONET_SENDNODE = 0, // for VALUEUPDATE
IONET_DESTNODE = 0, // for WRITE/WRITEANALOGUE
IONET_CMDTYPE = 1,
IONET_VPIN = 2,
IONET_VPIN_H = 2,
IONET_VPIN_L = 3,
IONET_DATA = 4,
};
public:
// Constructor performs static initialisation of the device object
Network (VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], NetInterface *netDriver) {
_firstVpin = firstVpin;
_nPins = nPins;
_thisNode = thisNode;
_pinDefs = pinDefs;
_pinValues = (uint8_t *)calloc((nPins+7)/8, 1); // Allocate space for input values.
_netDriver = netDriver;
addDevice(this);
// Identify which pins are allocated to this node.
_firstPinToSend = -1;
int lastPinToSend = 0;
for (int pin=0; pin<_nPins; pin++) {
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
// Check if the pin is an input on this node?
if (node == _thisNode && (flags & RPIN_IN)) {
if (_firstPinToSend==-1) _firstPinToSend = pin;
lastPinToSend = pin;
}
//DIAG(F("Node=%d FirstPin=%d, NumPins=%d"), node, _firstPinToSend, _numPinsToSend);
}
// Round down to multiple of 8 (byte boundary).
_firstPinToSend /= 8;
_firstPinToSend *= 8;
_numPinsToSend = lastPinToSend - _firstPinToSend + 1;
// Restrict to the max that fit in a packet
_numPinsToSend = min(8*(MAX_MSG_SIZE-IONET_DATA),_numPinsToSend);
//DIAG(F("FirstPin=%d, NumPins=%d"), _firstPinToSend, _numPinsToSend);
// Prepare for first transmission
_nextSendPin = _firstPinToSend;
}
// Static create function provides alternative way to create object
static void create(VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], NetInterface *netDriver) {
new Network(firstVpin, nPins, thisNode, pinDefs, netDriver);
}
protected:
// _begin function called to perform dynamic initialisation of the device
void _begin() override {
if (_netDriver->begin(_thisNode)) {
_display();
_deviceState = DEVSTATE_NORMAL;
_lastMulticastTime = _lastExecutionTime = micros();
_updatePending = true;
} else {
// Error in initialising
DIAG(F("Network Failed to initialise"));
_deviceState = DEVSTATE_FAILED;
}
}
// _read function - just return pin value (updated in _loop when message received from remote node)
int _read(VPIN vpin) override {
int pin = vpin - _firstVpin;
uint8_t mask = 1 << (pin & 7);
int byteIndex = pin / 8;
return (_pinValues[byteIndex] & mask) ? 1 : 0;
}
// _write (digital) - send command directly to the appropriate remote node.
void _write(VPIN vpin, int value) override {
// Send message
int pin = vpin - _firstVpin;
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
#ifdef DIAG_IO
DIAG(F("Network: write(%d,%d)=>send(%d,\"write(%d,%d)\")"), vpin, value, node, remoteVpin, value);
#endif
netBuffer[IONET_DESTNODE] = node;
netBuffer[IONET_CMDTYPE] = NET_CMD_WRITE;
netBuffer[IONET_VPIN_H] = getMsb(remoteVpin);
netBuffer[IONET_VPIN_L] = getLsb(remoteVpin);
netBuffer[IONET_DATA] = (uint8_t)value;
// Set up to send to the specified node address
_netDriver->sendCommand(node, netBuffer, IONET_DATA+1);
}
}
// _writeAnalogue - send command directly to the appropriate remote node.
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
// Send message
int pin = vpin - _firstVpin;
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
#ifdef DIAG_IO
DIAG(F("Network: writeAnalogue(%d,%d,%d,%d)=>send(%d,\"writeAnalogue(%d,%d,...)\")"),
vpin, value, param1, param2, node, remoteVpin, value);
#endif
netBuffer[IONET_DESTNODE] = node;
netBuffer[IONET_CMDTYPE] = NET_CMD_WRITEANALOGUE;
netBuffer[IONET_VPIN_H] = getMsb(remoteVpin);
netBuffer[IONET_VPIN_L] = getLsb(remoteVpin);
netBuffer[IONET_DATA+0] = getMsb(value);
netBuffer[IONET_DATA+1] = getLsb(value);
netBuffer[IONET_DATA+2] = param1;
netBuffer[IONET_DATA+3] = getMsb(param2);
netBuffer[IONET_DATA+4] = getLsb(param2);
// Set up to send to the specified node address
_netDriver->sendCommand(node, netBuffer, IONET_DATA+5);
}
}
// _loop function - check for, and process, received data from RF24, and send any
// updates that are due.
void _loop(unsigned long currentMicros) override {
// Perform cyclic netdriver functions, including switching back to receive mode
// (for half-duplex network drivers) and receiving input packets.
_netDriver->loop();
// Check for incoming data
if (_netDriver->available())
processReceivedData();
// Force a data update broadcast every 1000ms irrespective of whether there are
// data changes or not.
if (currentMicros - _lastMulticastTime > (1000 * 1000UL))
_updatePending = true;
// Send out data update broadcasts once every 20ms if there are changes
if (currentMicros - _lastExecutionTime > (20 * 1000UL)) {
// Broadcast updates to all other nodes. The preparation is done in a number of
// successive calls, and when sendSensorUpdates() returns true it has completed.
if (sendSensorUpdates()) {
_lastExecutionTime = currentMicros; // Send complete, wait for next time
}
}
}
void _display() override {
DIAG(F("Network Configured on Vpins:%d-%d Node:%d%S"),
_firstVpin, _firstVpin+_nPins-1, _thisNode, (_deviceState==DEVSTATE_FAILED) ? F(" OFFLINE") : F(""));
}
private:
// Send sensor updates only if one or more locally sourced inputs that
// are mapped to remote VPINs have changed state.
//
bool sendSensorUpdates() {
// This loop is split into multiple loop() entries, so as not to hog
// the cpu for too long.
if (_numPinsToSend == 0) return true; // No pins to send from this node.
// Update the _pinValues bitfield to reflect the current values of local pins.
// Process maximum of 5 pins per entry.
uint8_t count = 5;
bool state;
// First time through, _nextSendPin is equal to _firstPinToSend.
for (int pin=_nextSendPin; pin<_firstPinToSend+_numPinsToSend; pin++) {
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
// Is the pin an input on this node?
if ((flags & RPIN_IN) && GETFLASH(&_pinDefs[pin].node) == _thisNode) {
// Local input pin, read and update current state of input
VPIN localVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (localVpin != VPIN_NONE) {
state = IODevice::read(localVpin);
uint16_t byteIndex = pin / 8;
uint8_t bitMask = 1 << (pin & 7);
uint8_t byteValue = _pinValues[byteIndex];
bool oldState = byteValue & bitMask;
if (state != oldState) {
// Store state in remote values array
if (state)
byteValue |= bitMask;
else
byteValue &= ~bitMask;
_pinValues[byteIndex] = byteValue;
_updatePending = true;
}
if (--count == 0) {
// Done enough checks for this entry, resume on next one.
_nextSendPin = pin+1;
return false;
}
}
}
}
// When we get here, we've updated the _pinValues array. See if an
// update is due.
if (_updatePending) {
// On master and on slave, send pin states to other nodes
netBuffer[IONET_SENDNODE] = _thisNode; // Originating node
netBuffer[IONET_CMDTYPE] = NET_CMD_VALUEUPDATE;
// The packet size is 32 bytes, header is 4 bytes, so 28 bytes of data.
// We can therefore send up to 224 binary states per packet.
int byteCount = (_numPinsToSend+7)/8;
VPIN remoteVpin = _firstVpin+_firstPinToSend;
netBuffer[IONET_VPIN_H] = getMsb(remoteVpin);
netBuffer[IONET_VPIN_L] = getLsb(remoteVpin);
// Copy from pinValues array into buffer. This is why _firstPinToSend must be a multiple of 8.
memcpy(&netBuffer[IONET_DATA], &_pinValues[_firstPinToSend/8], byteCount);
// Broadcast update
_netDriver->sendCommand(255, netBuffer, IONET_DATA + byteCount);
//DIAG(F("Sent %d bytes: %x %x ..."), byteCount, netBuffer[4], netBuffer[5]);
_lastMulticastTime = micros();
_updatePending = false;
}
// Set next pin ready for next entry.
_nextSendPin = _firstPinToSend;
return true; // Done all we need to for this cycle.
}
// Read next packet from the device's input buffers. Decode the message,
// and take the appropriate action.
// The packet may be a command to do an output write (digital or analogue), or
// it may be an update for digital input signals.
// For digital input signals, the values are broadcast from the node that is
// the pin source to all the other nodes.
void processReceivedData() {
// Read packet
uint8_t size = _netDriver->read(netBuffer, sizeof(netBuffer));
if (size < IONET_DATA) return; // packet too short.
// Extract command type from packet.
uint8_t command = netBuffer[IONET_CMDTYPE];
//DIAG(F("Received %d bytes, type=%d"), size, command);
// Process received data
switch (command) {
case NET_CMD_WRITE: // Digital write command
{
uint8_t targetNode = netBuffer[IONET_DESTNODE];
if (targetNode == _thisNode && size == IONET_DATA+1) {
VPIN vpin = makeWord(netBuffer[IONET_VPIN_H], netBuffer[IONET_VPIN_L]);
uint8_t state = netBuffer[IONET_DATA];
IODevice::write(vpin, state);
}
}
break;
case NET_CMD_WRITEANALOGUE: // Analogue write command
{
uint8_t targetNode = netBuffer[IONET_DESTNODE];
if (targetNode == _thisNode && size == IONET_DATA+5) {
VPIN vpin = makeWord(netBuffer[IONET_VPIN_H], netBuffer[IONET_VPIN_L]);
int value = makeWord(netBuffer[IONET_DATA], netBuffer[IONET_DATA+1]);
uint8_t param1 = netBuffer[IONET_DATA+2];
uint16_t param2 = makeWord(netBuffer[IONET_DATA+3], netBuffer[IONET_DATA+4]);
IODevice::writeAnalogue(vpin, value, param1, param2);
// Set the local value for the pin, used by isBusy(),
// and subsequently updated by the remote node.
_pinValues[vpin-_firstVpin] = true;
}
}
break;
case NET_CMD_VALUEUPDATE: // Updates of input states (sensors etc).
{
uint8_t sendingNode = netBuffer[IONET_SENDNODE];
VPIN vpin = makeWord(netBuffer[IONET_VPIN_H], netBuffer[IONET_VPIN_L]);
//DIAG(F("Node %d Size %d VPIN %d Rx States %x"), sendingNode, size, vpin, netBuffer[IONET_DATA]);
// Read through the buffer one byte at a time.
uint8_t *buffPtr = &netBuffer[IONET_DATA];
uint8_t *bitFieldPtr = &_pinValues[(vpin-_firstVpin)/8];
int currentPin = vpin - _firstVpin;
for (int byteNo=0; byteNo<size-4 && currentPin<_nPins; byteNo++) {
// Now work through the received byte examining each bit.
uint8_t byteValue = *buffPtr++;
uint8_t bitFieldValue = *bitFieldPtr;
uint8_t bitMask = 1;
for (int bitNo=0; bitNo<8 && currentPin<_nPins; bitNo++) {
// Process incoming value if it's come from the pin source node
uint8_t pinSource = GETFLASH(&_pinDefs[currentPin].node);
if (sendingNode == pinSource) {
if (byteValue & bitMask)
bitFieldValue |= bitMask;
else
bitFieldValue &= ~bitMask;
}
bitMask <<= 1;
currentPin++;
}
// Store the modified byte back
*bitFieldPtr++ = bitFieldValue;
}
}
break;
default:
break;
}
}
// Helper functions for packing/unpacking buffers.
inline uint16_t makeWord(uint8_t msb, uint8_t lsb) {
return ((uint16_t)msb << 8) | lsb;
}
inline uint8_t getMsb(uint16_t w) {
return w >> 8;
}
inline uint8_t getLsb(uint16_t w) {
return w & 0xff;
}
// Data space for actual input and output buffer.
uint8_t netBuffer[maxPayloadSize];
};
#endif //IO_NETWORK_H

536
IO_RF24.h Normal file
View File

@@ -0,0 +1,536 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* nRF24 default mode of operation:
* Channel: 108
* Bit rate: 2MHz
* CRC: 16-bit
* Power Level: High
*
* Each node on the network is configured with a node number in the range 0-254.
* The remoting configuration defines, for each pin to be available remotely,
* the node number and the VPIN number on that node. The configuration must
* match in all nodes, since it is used by the sending node to identify the node
* and VPIN to which a write command is to be sent, and the VPIN number for a
* sensor/input, and on the receiving node to identify the node from which a
* sensor/input value is being sourced.
*
* The node number is also used as the first byte of the nRF24's 5-byte address
* field. Number 255 is treated as a multicast address. All stations listen on
* their own address and on the multicast address.
*
* All nodes send regular multicast packets containing the latest values of the
* sensors as they know them. On receipt of such a packet, each node extracts
* the states of the sensors which are sourced by the originating node, and
* updates the values in its own local data. Thus, each node has a copy of the
* states of all digital input pin values that are defined in the remoting
* configuration. Multicasts are sent frequently, so if one is missed
* then, like a London bus, another will be along shortly.
*
* Commands (originating from write() or writeAnalogue() calls) are sent
* immediately, directly from the originating node to the target node. This
* is done with acknowlegements enabled to maximise the probability of
* successful delivery.
*
* The nRF24 device receives and acknowledges data packets autonomously.
* Therefore, this driver just needs to detect when a packet is received and
* read and process its contents. The time to read the packet is under 200us
* typically.
*
* The nRF24 is also capable of autonomously sending packets, processing
* acknowledgements, and generating retries. The driver writes the packet to
* the device and then waits for notification of completion (success, or retries
* exceeded) through the device's registers. Similarly, the time to write a
* packet is under 200us and, if we don't wait for the completion, we can allow
* the processor to do other things while the transmission is in progress.
* A write with ack can complete in under 600us, plus the time of turning the
* receiver off and on.
*
* Usage:
* First declare, for each remote pin in the common area, the mapping onto
* a node and VPIN number. The array below assumes that the first remote
* VPIN is 4000. The nRF24L01 device is connected to the standard SPI pins
* plus two others referred to as CE and CSN. The Arduino pin numbers used
* for these are specified in the create() call. The REMOTEPINS definition
* should be the same on all nodes in the network. For outputs, it is the
* definition in the sending node that dictates which node and VPIN the
* action is performed on. For inputs, the value is placed into the
* VPIN location defined in the sending node (that scans the input value),
* but the value is only accepted in the receiving node if its definition
* shows that the signal originates in the sending node.
*
* Example to go into mySetup() function in mySetup.cpp:
* REMOTEPINS rpins[] = {
* {0,30,RPIN_OUT}, //4000 Node 0 GPIO pin 30 (output)
* {1,30,RPIN_IN}, //4001 Node 1 GPIO pin 30 (input)
* {1,100,RPIN_INOUT}, //4002 Node 1 Servo (PCA9685) pin (output to servo, input busy flag)
* {1,164,RPIN_IN}, //4003 Node 1 GPIO extender (MCP23017) pin (input)
* {2,164,RPIN_IN} //4004 Node 2 GPIO extender (MCP23017) pin (input)
* }
* // FirstVPIN, nPins, thisNode, pinDefs, CEPin, CSNPin
* RF24Net::create(4000, NUMREMOTEPINS(rpins), 0, rpins, 48, 49);
*
* This example defines VPINs 4000-4004 which map onto pins on nodes 0, 1 and 2.
* The nRF24 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
* microcontroller; in addition, the CE and CSN pins on the nRF24 are connected to
* two pins (48 and 49 above).
*
* If any of pins 4000-4004 are referenced by turnouts, outputs or sensors, or by EX-RAIL,
* then the corresponding remote pin state will be retrieved or updated.
* For example, in EX-RAIL,
* SET(4000) on node 1 or 2 will set pin 30 on Node 0 to +5V (pin is put into output mode on first write).
* AT(4001) on node 0 or 2 will wait until the sensor attached to pin 30 on Node 1 activates.
* SERVO(4002,300,2) on node 0 or 2 will reposition the servo on Node 1 PCA9685 module to position 300, and
* AT(-4002) will wait until the servo has finished moving.
*
* The following sensor definition on node 0 will map onto VPIN 4004, i.e. Node 2 VPIN 164,
* which is the first pin on the first MCP23017:
* <S 1 4004 0>
* and when a sensor attached to the pin on node 2 is activated (pin pulled down to 0V) the following
* message will be generated on node 0:
* <Q 1>
* When the sensor deactivates, the following message will be generated on node 0:
* <q 1>
*/
#ifndef IO_RF24_H
#define IO_RF24_H
#include "IODevice.h"
#include "RF24.h"
// Macros and type for creating the remote pin definitions.
// The definitions are stored in PROGMEM to reduce RAM requirements.
// The flags byte contains, in the low 2 bits, RPIN_IN, RPIN_OUT or RPIN_INOUT.
typedef struct { uint8_t node; VPIN vpin; uint8_t flags; } RPIN;
#define REMOTEPINS static const RPIN PROGMEM
#define NUMREMOTEPINS(x) (sizeof(x)/sizeof(RPIN))
enum {
RPIN_IN=1,
RPIN_OUT=2,
RPIN_INOUT=RPIN_IN|RPIN_OUT,
};
class RF24Net : public IODevice {
private:
// pins must be arduino GPIO pins, not extender pins or HAL pins.
int _cePin = -1;
int _csnPin = -1;
const RPIN *_pinDefs; // May need to become a far pointer!
// Time of last loop execution
unsigned long _lastExecutionTime;
// Current digital values for remoted pins, stored as a bit field
uint8_t *_pinValues;
// Number of the current node (0-254)
uint8_t _thisNode;
// 5-byte nRF24L01 address. First byte will contain the node number (0-254) or 255 for broadcast
byte _address[5] = {0x00, 0xCC, 0xEE, 0xEE, 0xCC};
// Maximum size of payload (must be 32 or less)
static const uint8_t maxPayloadSize = 32;
// Current node being sent sensor data and polled
uint8_t _currentSendNode = 0;
bool _sendInProgress = false;
bool _changesPending;
int _nextSendPin = 0;
unsigned long _lastMulticastTime;
int _firstPinToSend; // must be a multiple of 8
int _numPinsToSend; // need not be a multiple of 8
RF24 _radio;
// List of network commands
enum : uint8_t {
NET_CMD_WRITE,
NET_CMD_WRITEANALOGUE,
NET_CMD_VALUEUPDATE,
};
public:
// Constructor performs static initialisation of the device object
RF24Net (VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], int cePin, int csnPin) {
_firstVpin = firstVpin;
_nPins = nPins;
_cePin = cePin;
_csnPin = csnPin;
_thisNode = thisNode;
_pinDefs = pinDefs;
_address[0] = 0x00;
_address[1] = 0xCC;
_address[2] = 0xEE;
_address[3] = 0xEE;
_address[4] = 0xCC;
_pinValues = (uint8_t *)calloc((nPins+7)/8, 1); // Allocate space for input values.
addDevice(this);
// Identify which pins are allocated to this node.
_firstPinToSend = -1;
_numPinsToSend = 0;
for (int pin=0; pin<_nPins; pin++) {
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
// Check if the pin is an input on this node?
if (node == _thisNode && (flags & RPIN_IN)) {
if (_firstPinToSend==-1) _firstPinToSend = pin;
_numPinsToSend = pin - _firstPinToSend + 1;
}
//DIAG(F("Node=%d FirstPin=%d, NumPins=%d"), node, _firstPinToSend, _numPinsToSend);
}
// Round down to multiple of 8 (byte boundary).
_firstPinToSend /= 8;
_firstPinToSend *= 8;
_nextSendPin = _firstPinToSend;
//DIAG(F("FirstPin=%d, NumPins=%d"), _firstPinToSend, _numPinsToSend);
}
// Static create function provides alternative way to create object
static void create(VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], int cePin, int csnPin) {
new RF24Net(firstVpin, nPins, thisNode, pinDefs, cePin, csnPin);
}
protected:
// _begin function called to perform dynamic initialisation of the device
void _begin() override {
#if defined(DIAG_IO)
_display();
#endif
if (_radio.begin(_cePin, _csnPin)) {
// Device initialisation OK, set up parameters
_radio.setDataRate(RF24_2MBPS);
_radio.setPALevel(RF24_PA_HIGH);
_radio.setChannel(108);
_radio.enableDynamicPayloads(); // variable length packets
_radio.setAutoAck(true);
_radio.enableDynamicAck(); // required for multicast to work
_radio.setRetries(1, 5); // Retry time=1*250+250us=500us, count=5.
// Set to listen on the address 255
_address[0] = 255;
_radio.openReadingPipe(1, _address);
// Also allow receives on own node address
_address[0] = _thisNode;
_radio.openReadingPipe(2, _address);
_radio.startListening();
_display();
_deviceState = DEVSTATE_NORMAL;
} else {
// Error in initialising
DIAG(F("nRF24L01 Failed to initialise"));
_deviceState = DEVSTATE_FAILED;
}
_lastMulticastTime = _lastExecutionTime = micros();
}
// _read function - just return _value (updated in _loop when message received from remote node)
int _read(VPIN vpin) override {
int pin = vpin - _firstVpin;
uint8_t mask = 1 << (pin & 7);
int byteIndex = pin / 8;
return (_pinValues[byteIndex] & mask) ? 1 : 0;
}
// _write (digital) - send command directly to the appropriate remote node.
void _write(VPIN vpin, int value) override {
// Send message
int pin = vpin - _firstVpin;
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
#ifdef DIAG_IO
DIAG(F("RF24: write(%d,%d)=>send(%d,\"write(%d,%d)\")"), vpin, value, node, remoteVpin, value);
#endif
outBuffer[0] = node;
outBuffer[1] = NET_CMD_WRITE;
outBuffer[2] = getMsb(remoteVpin);
outBuffer[3] = getLsb(remoteVpin);
outBuffer[4] = (uint8_t)value;
// Set up to send to the specified node address
sendCommand(node, outBuffer, 5);
}
}
// _writeAnalogue - send command directly to the appropriate remote node.
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
// Send message
int pin = vpin - _firstVpin;
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
#ifdef DIAG_IO
DIAG(F("RF24: writeAnalogue(%d,%d,%d,%d)=>send(%d,\"writeAnalogue(%d,%d,...)\")"),
vpin, value, param1, param2, node, remoteVpin, value);
#endif
outBuffer[0] = node;
outBuffer[1] = NET_CMD_WRITEANALOGUE;
outBuffer[2] = getMsb(remoteVpin);
outBuffer[3] = getLsb(remoteVpin);
outBuffer[4] = getMsb(value);
outBuffer[5] = getLsb(value);
outBuffer[6] = param1;
outBuffer[7] = getMsb(param2);
outBuffer[8] = getLsb(param2);
// Set up to send to the specified node address
sendCommand(node, outBuffer, 9);
}
}
// _loop function - check for, and process, received data from RF24, and send any
// updates that are due.
void _loop(unsigned long currentMicros) override {
// Check for incoming data
if (_radio.available(NULL))
processReceivedData();
// Force a data update broadcast every 500ms irrespective of whether there are
// data changes or not.
if (currentMicros - _lastMulticastTime > (500 * 1000UL))
_changesPending = true;
// Send out data update broadcasts once every 100ms if there are changes
if (currentMicros - _lastExecutionTime > (100 * 1000UL)) {
// Broadcast updates to all other nodes. The preparation is done in a number of
// successive calls, and when sendSensorUpdates() returns true it has completed.
if (sendSensorUpdates()) {
_lastExecutionTime = currentMicros; // Send complete, wait another 100ms
}
}
// Check if outstanding writes have completed. If so, move to Standby-I mode
// and enable the receiver.
if (_sendInProgress && _radio.isWriteFinished()) {
_sendInProgress = false;
_radio.txStandBy();
_radio.startListening();
}
}
void _display() override {
DIAG(F("nRF24L01 Configured on Vpin:%d-%d CEPin:%d CSNPin:%d"),
_firstVpin, _firstVpin+_nPins-1, _cePin, _csnPin);
}
private:
// Send sensor updates only if one or more locally sourced inputs that
// are mapped to remote VPINs have changed state.
//
bool sendSensorUpdates() {
// This loop is split into multiple loop() entries, so as not to hog
// the cpu for too long. Otherwise it could take over 2700us with 108 remote
// pins configured, for example. So we do just 5 pins per call.
// We could make digital state change notification mandatory, which would
// allow us to remove the loop altogether!
if (_numPinsToSend == 0) return true; // No pins to send from this node.
// Update the _pinValues bitfield to reflect the current values of local pins.
uint8_t count = 5;
bool state;
for (int pin=_nextSendPin; pin<_firstPinToSend+_numPinsToSend; pin++) {
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
if ((flags & RPIN_IN) && GETFLASH(&_pinDefs[pin].node) == _thisNode) {
// Local input pin, read and update current state of input
VPIN localVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (localVpin != VPIN_NONE) {
state = IODevice::read(localVpin);
uint16_t byteIndex = pin / 8;
uint8_t bitMask = 1 << (pin & 7);
uint8_t byteValue = _pinValues[byteIndex];
bool oldState = byteValue & bitMask;
if (state != oldState) {
// Store state in remote values array
if (state)
byteValue |= bitMask;
else
byteValue &= ~bitMask;
_pinValues[byteIndex] = byteValue;
_changesPending = true;
//DIAG(F("RF24 VPIN:%d Val:%d"), _firstVpin+pin, state);
}
if (--count == 0) {
// Done enough checks for this entry, resume on next one.
_nextSendPin = pin+1;
return false;
}
}
}
}
if (_changesPending) {
// On master and on slave, send pin states to other nodes
outBuffer[0] = _thisNode; // Originating node
outBuffer[1] = NET_CMD_VALUEUPDATE;
// The packet size is 32 bytes, header is 4 bytes, so 28 bytes of data.
// We can therefore send up to 224 binary states per packet.
int byteCount = _numPinsToSend/8+1;
VPIN remoteVpin = _firstVpin+_firstPinToSend;
outBuffer[2] = getMsb(remoteVpin);
outBuffer[3] = getLsb(remoteVpin);
// Copy from pinValues array into buffer. This is why _firstPinToSend must be a multiple of 8.
memcpy(&outBuffer[4], &_pinValues[_firstPinToSend/8], byteCount);
// Broadcast update
sendCommand(255, outBuffer, byteCount + 4);
//DIAG(F("Sent %d bytes: %x %x ..."), byteCount, outBuffer[4], outBuffer[5]);
_lastMulticastTime = micros();
_changesPending = false;
}
// Set next pin ready for next entry.
_nextSendPin = _firstPinToSend;
return true; // Done all we need to for this cycle.
}
// Read next packet from the device's input buffers. Decode the message,
// and take the appropriate action.
// The packet may be a command to do an output write (digital or analogue), or
// it may be an update for digital input signals.
// For digital input signals, the values are propagated from the node that is
// the pin source, via the master, to all the other nodes.
void processReceivedData() {
// Read received data from input pipe
byte size = _radio.getDynamicPayloadSize();
// if (size > maxPayloadSize) return; // Packet too long to read!!
// Read packet
_radio.read(inBuffer, size);
// Extract command type from packet.
uint8_t command = inBuffer[1];
// Process received data
switch (command) {
case NET_CMD_WRITE: // Digital write command
{
uint8_t targetNode = inBuffer[0];
if (targetNode == _thisNode) {
VPIN vpin = makeWord(inBuffer[2], inBuffer[3]);
uint8_t state = inBuffer[4];
IODevice::write(vpin, state);
} else {
sendCommand(targetNode, inBuffer, size);
}
}
break;
case NET_CMD_WRITEANALOGUE: // Analogue write command
{
uint8_t targetNode = inBuffer[0];
if (targetNode == _thisNode) {
VPIN vpin = makeWord(inBuffer[2], inBuffer[3]);
int value = makeWord(inBuffer[4], inBuffer[5]);
uint8_t param1 = inBuffer[6];
uint16_t param2 = makeWord(inBuffer[7], inBuffer[8]);
IODevice::writeAnalogue(vpin, value, param1, param2);
// Set the local value for the pin, used by isBusy(),
// and subsequently updated by the remote node.
_pinValues[vpin-_firstVpin] = true;
} else {
sendCommand(targetNode, inBuffer, size);
}
}
break;
case NET_CMD_VALUEUPDATE: // Updates of input states (sensors etc).
{
uint8_t sendingNode = inBuffer[0];
//DIAG(F("Node %d Rx %x"), sendingNode, inBuffer[4]);
VPIN vpin = makeWord(inBuffer[2], inBuffer[3]);
// Read through the buffer one byte at a time.
uint8_t *buffPtr = &inBuffer[4];
uint8_t *bitFieldPtr = &_pinValues[(vpin-_firstVpin)/8];
int currentPin = vpin - _firstVpin;
for (int byteNo=0; byteNo<size-4 && currentPin<_nPins; byteNo++) {
// Now work through the byte examining each bit.
uint8_t byteValue = *buffPtr++;
uint8_t bitMask = 1;
for (int bitNo=0; bitNo<8 && currentPin<_nPins; bitNo++) {
// Process incoming value if it's come from the pin source node
uint8_t pinSource = GETFLASH(&_pinDefs[currentPin].node);
if (sendingNode == pinSource) {
if (byteValue & bitMask)
byteValue |= bitMask;
else
byteValue &= ~bitMask;
// if (pinNode == _thisNode) { // Local pin }
}
bitMask <<= 1;
currentPin++;
}
// Store the modified byte back
*bitFieldPtr++ = byteValue;
}
}
break;
default:
break;
}
}
// Wrapper functions for RF24 send functions. If node=255, then
// the packet is to be sent as a multicast without acknowledgements.
// The multicast message takes ~400us. A further 260us is required to turn
// the receiver off and on for the transmission, totalling 660us.
// If the node is not 255, then the packet will be sent directly to the
// addressed node, with acknowledgement requested. If no acknowledgement is
// received, then the device will retry up to the defined maximum number of
// retries. This will take longer than a multicast. For example, with
// setRetries(1,3) the timeout is 500us and a maximum of 3 retries are
// carried out, so the operation will take as much as 2.26 milliseconds if
// the node in question is not responding, and as little as 890us if the
// ack is received immediately (including turning receiver on/off).
//
bool sendCommand(uint8_t node, uint8_t *buffer, uint8_t len) {
_address[0] = node;
_radio.openWritingPipe(_address);
// We have to stop the receiver before we can transmit.
_radio.stopListening();
// Copy the message into the radio and start the transmitter.
// Multicast (no ack expected) if destination node is 255.
bool ok = _radio.writeFast(buffer, len, (node==255));
// We will poll the radio later on to see when the transmit queue
// has emptied. When that happens, we will go back to receive mode.
// This prevents txStandBy() from blocking while the transmission
// is in progress.
_sendInProgress = true;;
return ok;
}
// Helper functions for packing/unpacking buffers.
inline uint16_t makeWord(uint8_t msb, uint8_t lsb) {
return ((uint16_t)msb << 8) | lsb;
}
inline uint8_t getMsb(uint16_t w) {
return w >> 8;
}
inline uint8_t getLsb(uint16_t w) {
return w & 0xff;
}
// Data space for actual input and output buffers.
uint8_t inBuffer[maxPayloadSize];
uint8_t outBuffer[maxPayloadSize];
};
#endif //IO_RF24Net4_H

232
IO_S88.h Normal file
View File

@@ -0,0 +1,232 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* The S88 Bus is essentially a shift register consisting of one or more
* S88 modules, connected one to another in one long chain. The register
* is read by the following steps:
* 1) The LOAD/PS line goes to HIGH, then the CLK line is pulsed HIGH. This
* tells the registers to acquire data from the latched parallel inputs. 2) With
* LOAD/PS still high, the RESET signal is pulsed HIGH, which clears the
* parallel input upstream latches. 3) With LOAD/PS LOW, the shift is performed
* by pulsing the CLK line high. On each pulse, the data in the shift register
* is presented, bit by bit, to the DATA-IN line.
*
* Example configuration in mySetup.cpp:
* #include "IO_S88.h"
* void mySetup() {
* S88bus::create(2500, 16, 40,41,42,43, 10);
* }
*
* This creates an S88 bus instance with 16 inputs (VPINs 2500-2515).
* The LOAD pin is 40, RESET is 41, CLK is 42 and DATA is 43. These four pins
* must be local GPIO pins (not on an I/O expander).
* The last parameter (bitTime=10) means that at least 10us is allowed for
* reading each bit in the shift register.
*
* Depending on the S88 modules being used and the cabling, the timing of the
* interface may have to be adjusted. This is done by the bit time parameter.
* The original S88 concept was based on 4014 shift register devices, which are
* easily capable of moderate speed transfers and would cope with a bit time
* around 2us. However, commercial S88 devices appear to be extremely slow in
* comparison and may need the order of tens or hundreds of microseconds. The
* symptom of a bit time that is too small is that, when you activate one input to
* the S88 module, the command station sees something other than the correct
* activation; e.g. no activations, multiple activations, the wrong pin
* activating etc.
*
* _acquireCycleTime determines the minimum time between successive acquire
* cycles of the inputs on the S88 bus. Bear in mind that the Sensor code
* includes anti-bounce logic which means that fleeting state changes may be
* ignored, so reducing the acquireCycleTime may not have the desired effect.
* Also, if the combination of bit time and number of pins is large, the
* specified cycle time may not be achieved.
*
*/
#ifndef IO_S88_H
#define IO_S88_H
#include "IODevice.h"
class S88bus : public IODevice {
private:
uint8_t _loadPin;
uint8_t _resetPin;
uint8_t _clockPin;
uint8_t _dataInPin;
uint8_t *_values; // Bit array, initialised in _begin method.
int _currentByteIndex;
unsigned long _lastAquireCycleStart;
uint16_t _pulseDelayTime; // Delay microsecs; can be tuned for the S88 hardware
uint16_t _shortDelayTime;
uint8_t _bitIndex;
unsigned long _lastPulseTime;
uint8_t _state;
uint8_t _maxBitsPerEntry;
// The acquire cycle time is a target maximum rate. If there are a lot of signals or the
// bit time is long, then the cycle time may be longer.
const unsigned long _acquireCycleTime = 20000; // target 20 milliseconds between acquire cycles
public:
S88bus(VPIN firstVpin, int nPins, uint8_t loadPin, uint8_t resetPin, uint8_t clockPin, uint8_t dataInPin, uint16_t bitTime) :
IODevice(firstVpin, nPins),
_loadPin(loadPin),
_resetPin(resetPin),
_clockPin(clockPin),
_dataInPin(dataInPin),
_currentByteIndex(0),
_lastAquireCycleStart(0),
_pulseDelayTime((bitTime+1)/2)
{
// Allocate memory for input values.
_values = (uint8_t *)calloc((nPins+7)/8, 1);
_shortDelayTime = (_pulseDelayTime > 10) ? 10 : _pulseDelayTime;
_state = 0;
// The program typically manages 30 microseconds per clock cycle
// with no waiting, so limit the number of bits so that loop doesn't
// take much more than 200us.
_maxBitsPerEntry = 200 / (30+bitTime) + 1;
addDevice(this);
}
static void create(VPIN firstVpin, int nPins, uint8_t loadPin, uint8_t resetPin, uint8_t clockPin, uint8_t dataInPin, uint16_t bitTime) {
new S88bus(firstVpin, nPins, loadPin, resetPin, clockPin, dataInPin, bitTime);
}
protected:
void _begin() override {
pinMode(_loadPin, OUTPUT);
pinMode(_resetPin, OUTPUT);
pinMode(_clockPin, OUTPUT);
pinMode(_dataInPin, INPUT);
#ifdef DIAG_IO
_display();
#endif
}
// Read method returns the latest aquired value for the nominated VPIN number.
int _read(VPIN vpin) override {
uint16_t pin = vpin - _firstVpin;
uint8_t mask = 1 << (pin % 8);
uint16_t byteIndex = pin / 8;
return (_values[byteIndex] & mask) ? 1 : 0;
}
// Loop method acquires the input states from the shift register.
// At the beginning of each acquisition cycle, instruct the bus registers to acquire the
// input states from the latches, then reset the latches. On
// subsequent loop entries, some of the input states are shifted from the
// registers, until they have all been read. Then the whole process
// resumes for the next acquisition cycle. The operations are spread over consecutive
// loop entries to restrict the amount of time taken in each entry.
void _loop(unsigned long currentMicros) override {
// If just starting a new read, then latch the input values into the S88
// registers. The active edge is the rising edge in each case, so we
// can use shorter delays for some transitions.
switch (_state) {
case 0: // Starting cycle. Set up LOAD and CLOCK pins.
_lastAquireCycleStart = currentMicros;
// Set LOAD pin
ArduinoPins::fastWriteDigital(_loadPin, HIGH);
_lastPulseTime = micros();
pulseDelay(_shortDelayTime);
// Pulse CLOCK pin to read inputs into registers
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
// Clear CLOCK, and set up RESET pin.
pulseDelay(_pulseDelayTime);
ArduinoPins::fastWriteDigital(_clockPin, LOW);
pulseDelay(_shortDelayTime);
// Pulse RESET pin to clear inputs ready for next acquisition period
ArduinoPins::fastWriteDigital(_resetPin, HIGH);
_state = 1;
delayUntil(_lastPulseTime + _pulseDelayTime);
return;
case 1: // Clear RESET and LOAD
ArduinoPins::fastWriteDigital(_resetPin, LOW);
pulseDelay(_shortDelayTime);
ArduinoPins::fastWriteDigital(_loadPin, LOW);
// Initialise variables used in reading bits.
_currentByteIndex = _bitIndex = 0;
_state = 2;
/* fallthrough */
case 2:
// Subsequent loop entries, read each bit in turn from the shiftregister.
uint8_t bitCount = 0;
while (true) {
uint8_t mask = (1 << _bitIndex);
bool newValue = ArduinoPins::fastReadDigital(_dataInPin);
#ifdef DIAG_IO
bool oldValue = _values[_currentByteIndex] & mask;
if (newValue != oldValue) DIAG(F("S88 VPIN:%d Value:%d"),
_firstVpin+_currentByteIndex*8+_bitIndex, newValue);
#endif
if (newValue)
_values[_currentByteIndex] |= mask;
else
_values[_currentByteIndex] &= ~mask;
if (++_bitIndex == 8) {
// Byte completed, so move to next one.
_currentByteIndex++;
_bitIndex = 0;
}
// Check if this cycle is complete.
if (_currentByteIndex*8 + _bitIndex >= _nPins) {
// All bits in the shift register have been read now, so
// don't read again until next acquisition cycle time
delayUntil(_lastAquireCycleStart + _acquireCycleTime);
_state = 0;
return;
}
// Clock next bit in.
pulseDelay(_pulseDelayTime);
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
pulseDelay(_pulseDelayTime);
ArduinoPins::fastWriteDigital(_clockPin, LOW);
// See if we've done all we're allowed on this entry
if (++bitCount >= _maxBitsPerEntry) {
delayUntil(_lastPulseTime + _pulseDelayTime);
return;
}
}
}
}
void _display() override {
DIAG(F("S88bus Configured on Vpins %d-%d, LOAD=%d RESET=%d CLK=%d DATAIN=%d"),
_firstVpin, _firstVpin+_nPins-1, _loadPin, _resetPin, _clockPin, _dataInPin);
}
// Helper function to delay until a minimum number of microseconds have elapsed
// since _lastPulseTime.
void pulseDelay(uint16_t duration) {
delayMicroseconds(duration);
_lastPulseTime = micros();
}
};
#endif

149
Net_ENC28J60.h Normal file
View File

@@ -0,0 +1,149 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* ENC28J60 default mode of operation:
*
* The node number is used as the last byte of the MAC address
* field, purely so that the MAC address is unique for each distinct
* node number. The ENC28J60 driver doesn't implement ARP cache,
* since it would take up a minimum of 11 bytes per node and, with
* up to 254 nodes, this would take a significant part of the RAM.
* Instead, all transmissions are sent with MAC address allOnes, i.e.
* as a broadcast. Regular sensor state updates are broadcast anyway,
* and other write/writeAnalogue command packets are relatively
* infrequent.
*
* Usage:
* Net_ENC28J60 *encDriver = new Net_ENC28J60(10);
* Network<Net_ENC28J60>::create(4000, NUMREMOTEPINS(rpins), 1, rpins, encDriver);
*
* The ENC28J60 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
* microcontroller. The CS pin is specified in the command above (e.g. 10).
* For details of the Network class configuration, see IO_Network.h.
*
*/
#ifndef NET_ENC28J60_H
#define NET_ENC28J60_H
#include "IO_Network.h"
#include "DIAG.h"
#include "EtherCard.h"
// The ethernet buffer is global to different protocol layers, to avoid almost
// all copying of data.
byte ENC28J60::buffer[74]; // Need space for 32 byte payload and 42 byte header.
class Net_ENC28J60 : public EtherCard {
private:
// pins must be arduino GPIO pins, not extender pins or HAL pins.
int _csPin;
// Number of the current node (1-254)
uint8_t _thisNode;
// 6-byte MAC address. Last byte will contain the node number (1-254).
byte _address[6] = {0xEE,0xCC,0xEE,0xEE,0xCC,0x00};
// 4-byte IP address. Last byte will contain the node number (1-254) or 255 for broadcast.
byte _ipAddress[4] = {192,168,1,0};
byte _gwAddress[4] = {192,168,1,254};
byte _netMask[4] = {255,255,255,0};
const uint16_t _port = 8900;
uint8_t _packetBytesAvailable;
public:
// Constructor performs static initialisation of the device object
Net_ENC28J60 (int csPin) {
_csPin = csPin;
_packetBytesAvailable = 0;
}
// Perform dynamic initialisation of the device
bool begin(uint8_t thisNode) {
_thisNode = thisNode;
_address[5] = _thisNode;
_ipAddress[3] = _thisNode;
if (ether.begin(sizeof(ENC28J60::buffer), _address, _csPin)) {
ether.staticSetup(_ipAddress, _gwAddress, 0, _netMask);
return true;
} else {
// Error in initialising
DIAG(F("ENC28J60 Failed to initialise"));
return false;
}
}
// The following function should be called regularly to handle incoming packets.
// Check if there is a received packet ready for reading.
bool available() {
uint16_t packetLen = ether.packetReceive();
if (packetLen > 0) {
// Packet received. First handle ICMP, ARP etc.
if (ether.packetLoop(packetLen)) {
// UDP packet to be handled. Check if it's our port number
byte *gbp = ENC28J60::buffer;
uint16_t destPort = (gbp[UDP_DST_PORT_H_P] << 8) + gbp[UDP_DST_PORT_L_P];
if (destPort == _port) {
// Yes, so mark that data is to be processed.
_packetBytesAvailable = packetLen;
return true;
}
}
}
_packetBytesAvailable = 0;
return false;
}
// Read packet from the ethernet buffer, and return the number of bytes
// that were read.
uint8_t read(uint8_t buffer[], uint8_t bufferSize) {
if (_packetBytesAvailable > 0) {
//DIAG(F("ReadPacket(%d)"), _packetBytesAvailable);
// Adjust length and pointer for UDP header
byte *gbp = ENC28J60::buffer;
int udpDataSize = (gbp[UDP_LEN_H_P] << 8) + gbp[UDP_LEN_L_P] - UDP_HEADER_LEN;
byte *udpFrame = &ENC28J60::buffer[UDP_DATA_P];
// Clear packet byte marker
_packetBytesAvailable = 0;
// Check if there's room for the data
if (bufferSize >= udpDataSize) {
memcpy(buffer, udpFrame, udpDataSize);
return udpDataSize;
}
}
return 0;
}
// Wrapper functions for ENC28J60 sendUdp function.
// The node parameter is either 1-254 (for specific nodes) or 255 (for broadcast).
// This aligns with the subnet broadcast IP address of "x.x.x.255".
bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t len) {
_ipAddress[3] = node;
ether.sendUdp((const char *)buffer, len, _port, _ipAddress, _port);
return true;
}
void loop() { }
};
#endif //NET_ENC28J60_H

131
Net_Ethernet.h Normal file
View File

@@ -0,0 +1,131 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Ethernet (W5100,W5200,W5500) default mode of operation:
*
* All transmissions are sent with MAC address allOnes, i.e.
* as a broadcast. Regular sensor state updates are broadcast anyway,
* and other write/writeAnalogue command packets are relatively
* infrequent.
*
* Usage:
* Net_Ethernet *etherDriver = new Net_Ethernet(10);
* Network<Net_Ethernet>::create(4000, NUMREMOTEPINS(rpins), 1, rpins, etherDriver);
*
* The W5x00 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
* microcontroller. The CS pin is specified in the command above (e.g. 10).
* For details of the Network class configuration, see IO_Network.h.
*
*/
#ifndef NET_ETHERNET_H
#define NET_ETHERNET_H
#include "IO_Network.h"
#include "DIAG.h"
#include "Ethernet.h"
#include "EthernetUdp.h"
class Net_Ethernet {
private:
// pins must be arduino GPIO pins, not extender pins or HAL pins.
int _csPin;
// Number of the current node (1-254)
uint8_t _thisNode;
// 4-byte IP address. Last byte will contain the node number (1-254) or 255 for broadcast.
byte _ipAddress[4] = {192,168,1,0};
const uint16_t _port = 8900;
uint8_t _packetBytesAvailable;
EthernetUDP udp;
public:
// Constructor performs static initialisation of the device object
Net_Ethernet () {
_csPin = 10; // The Ethernet driver doesn't allow CS pin to be selected.
_packetBytesAvailable = 0;
}
// Perform dynamic initialisation of the device
bool begin(uint8_t thisNode) {
_thisNode = thisNode;
if (Ethernet.hardwareStatus() != EthernetNoHardware) {
// // Set IP address.
// _ipAddress[3] = thisNode;
// Ethernet.setLocalIP(_ipAddress);
// _ipAddress[3] = 254;
// Ethernet.setGatewayIP(_ipAddress);
// IPAddress mask = IPAddress(255,255,255,0);
// Ethernet.setSubnetMask(mask);
// Begin listening on receive port
udp.begin(_port);
return true;
} else {
DIAG(F("Ethernet (W5x00) no hardware found"));
return false;
}
}
// The following function should be called regularly to handle incoming packets.
// Check if there is a received packet ready for reading.
bool available() {
uint16_t packetLen = udp.parsePacket();
if (packetLen > 0) {
// Packet received.
_packetBytesAvailable = packetLen;
return true;
}
_packetBytesAvailable = 0;
return false;
}
// Read packet from the ethernet buffer, and return the number of bytes
// that were read.
uint8_t read(uint8_t buffer[], uint8_t bufferSize) {
uint8_t bytesReceived = _packetBytesAvailable;
// Clear packet byte marker
_packetBytesAvailable = 0;
if (bytesReceived > 0) {
//DIAG(F("ReadPacket(%d)"), bytesReceived);
// Check if there's room for the data
if (bufferSize >= bytesReceived) {
return udp.read(buffer, bytesReceived);
}
}
return 0;
}
// Wrapper functions for Ethernet UDP write function.
// Since we don't know the IP address of the node, just broadcast
// over the subnet.
bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t len) {
_ipAddress[3] = 255;
udp.beginPacket(_ipAddress, _port);
udp.write(buffer, len);
udp.endPacket();
return true;
}
void loop() { }
};
#endif //NET_ETHERNET_H

174
Net_RF24.h Normal file
View File

@@ -0,0 +1,174 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* nRF24 default mode of operation:
* Channel: 108
* Bit rate: 2MHz
* CRC: 16-bit
* Power Level: High
*
* The node number is used as the first byte of the nRF24's 5-byte address
* field. Number 255 is treated as a multicast address. All stations listen on
* their own address and on the multicast address.
*
* The nRF24 device receives and acknowledges data packets autonomously.
* Therefore, this driver just needs to detect when a packet is received and
* read and process its contents. The time to read the packet is under 200us
* typically.
*
* The nRF24 is also capable of autonomously sending packets, processing
* acknowledgements, and generating retries. The driver writes the packet to
* the device and then waits for notification of completion (success, or retries
* exceeded) through the device's registers. Similarly, the time to write a
* packet is under 200us and, if we don't wait for the completion, we can allow
* the processor to do other things while the transmission is in progress.
* A write with ack can complete in under 600us, plus the time of turning the
* receiver off and on.
*
* Usage:
* Net_RF24 *rf24Driver = new Net_RF24(48, 49);
* Network<Net_RF24>::create(4000, NUMREMOTEPINS(rpins), 1, rpins, rf24Driver);
*
* The nRF24 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
* microcontroller; in addition, the CE and CSN pins on the nRF24 are connected to
* two pins (e.g. 48 and 49 above).
*
*/
#ifndef NET_RF24_H
#define NET_RF24_H
#include "IO_Network.h"
#include "DIAG.h"
class Net_RF24 : public RF24 {
private:
// pins must be arduino GPIO pins, not extender pins or HAL pins.
int _cePin;
int _csnPin;
// Number of the current node (1-254)
uint8_t _thisNode;
// 5-byte nRF24L01 address. First byte will contain the node number (0-254) or 255 for broadcast
byte _address[5];
bool _sendInProgress;
public:
// Constructor performs static initialisation of the device object
Net_RF24 (int cePin, int csnPin) {
_cePin = cePin;
_csnPin = csnPin;
// Initialise with an arbitrary address. The first byte will contain
// the node number.
_address[0] = 0x00;
_address[1] = 0xCC;
_address[2] = 0xEE;
_address[3] = 0xEE;
_address[4] = 0xCC;
}
// Perform dynamic initialisation of the device
bool begin(uint8_t thisNode) {
if (RF24::begin(_cePin, _csnPin)) {
// Device initialisation OK, set up parameters
RF24::setDataRate(RF24_2MBPS);
RF24::setPALevel(RF24_PA_HIGH);
RF24::setChannel(108);
RF24::enableDynamicPayloads(); // variable length packets
RF24::setAutoAck(true); // required for acks to work
RF24::enableDynamicAck(); // required for multicast to work
RF24::setRetries(1, 5); // Retry time=1*250+250us=500us, count=5.
_thisNode = thisNode;
// Set to listen on the address 255
_address[0] = 255;
RF24::openReadingPipe(1, _address);
// Also allow receives on own node address
_address[0] = _thisNode;
RF24::openReadingPipe(2, _address);
RF24::startListening();
_sendInProgress = false;
return true;
} else {
// Error in initialising
DIAG(F("nRF24L01 Failed to initialise"));
return false;
}
}
// Check if there is a received packet ready for reading.
bool available() {
return RF24::available();
}
// Read next packet from the device, and return the number of bytes
// that were read.
uint8_t read(uint8_t buffer[], uint8_t size) {
uint8_t len = RF24::getDynamicPayloadSize();
RF24::read(buffer, size);
return len;
}
// Wrapper functions for RF24 send functions. If node=255, then
// the packet is to be sent as a multicast without acknowledgements.
// The multicast message takes ~400us. A further 260us is required to turn
// the receiver off and on for the transmission, totalling 660us.
// If the node is not 255, then the packet will be sent directly to the
// addressed node, with acknowledgement requested. If no acknowledgement is
// received, then the device will retry up to the defined maximum number of
// retries. This will take longer than a multicast. For example, with
// setRetries(1,3) the timeout is 500us and a maximum of 3 retries are
// carried out, so the operation will take as much as 2.26 milliseconds if
// the node in question is not responding, and as little as 890us if the
// ack is received immediately (including turning receiver on/off).
//
bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t len) {
_address[0] = node;
openWritingPipe(_address);
// We have to stop the receiver before we can transmit.
RF24::stopListening();
// Copy the message into the radio and start the transmitter.
// Multicast (no ack expected) if destination node is 255.
bool ok = RF24::writeFast(buffer, len, (node==255));
// We will poll the radio later on to see when the transmit queue
// has emptied. When that happens, we will go back to receive mode.
// This prevents txStandBy() from blocking while the transmission
// is in progress.
_sendInProgress = true;;
return ok;
}
// The following function should be called regularly to ensure that the
// device goes back into listening mode when a transmission is not
// in progress. (The nRF24 is a half-duplex device and cannot be in
// transmit mode and receive mode at the same time.)
void loop() {
bool completed = RF24::isWriteFinished();
if (_sendInProgress && completed) {
_sendInProgress = false;
RF24::txStandBy();
RF24::startListening();
}
}
};
#endif //NET_RF24_H

1267
RF24.cpp Normal file

File diff suppressed because it is too large Load Diff

273
RF24.h Normal file
View File

@@ -0,0 +1,273 @@
/*
Copyright (C) 2011 J. Coliz <maniacbug@ymail.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
*/
/**
* @file RF24.h
*
* Class declaration for RF24 and helper enums
*/
#ifndef __RF24_H__
#define __RF24_H__
#define FAILURE_HANDLING
#define RF24_POWERUP_DELAY 5000
#define rf24_max(a, b) (a>b?a:b)
#define rf24_min(a, b) (a<b?a:b)
#define RF24_SPI_SPEED 10000000
#include <SPI.h>
#define _SPI SPIClass
typedef enum {
RF24_PA_MIN = 0,
RF24_PA_LOW,
RF24_PA_HIGH,
RF24_PA_MAX,
RF24_PA_ERROR
} rf24_pa_dbm_e;
typedef enum {
RF24_1MBPS = 0,
RF24_2MBPS,
RF24_250KBPS
} rf24_datarate_e;
typedef enum {
RF24_CRC_DISABLED = 0,
RF24_CRC_8,
RF24_CRC_16
} rf24_crclength_e;
/* Memory Map */
#define NRF_CONFIG 0x00
#define EN_AA 0x01
#define EN_RXADDR 0x02
#define SETUP_AW 0x03
#define SETUP_RETR 0x04
#define RF_CH 0x05
#define RF_SETUP 0x06
#define NRF_STATUS 0x07
#define OBSERVE_TX 0x08
#define CD 0x09
#define RX_ADDR_P0 0x0A
#define RX_ADDR_P1 0x0B
#define RX_ADDR_P2 0x0C
#define RX_ADDR_P3 0x0D
#define RX_ADDR_P4 0x0E
#define RX_ADDR_P5 0x0F
#define TX_ADDR 0x10
#define RX_PW_P0 0x11
// #define RX_PW_P1 0x12
// #define RX_PW_P2 0x13
// #define RX_PW_P3 0x14
// #define RX_PW_P4 0x15
// #define RX_PW_P5 0x16
#define FIFO_STATUS 0x17
#define DYNPD 0x1C
#define FEATURE 0x1D
/* Bit Mnemonics */
#define MASK_RX_DR 6
#define MASK_TX_DS 5
#define MASK_MAX_RT 4
#define EN_CRC 3
#define CRCO 2
#define PWR_UP 1
#define PRIM_RX 0
#define ENAA_P5 5
#define ENAA_P4 4
#define ENAA_P3 3
#define ENAA_P2 2
#define ENAA_P1 1
#define ENAA_P0 0
#define ERX_P5 5
#define ERX_P4 4
#define ERX_P3 3
#define ERX_P2 2
#define ERX_P1 1
#define ERX_P0 0
#define AW 0
#define ARD 4
#define ARC 0
#define PLL_LOCK 4
#define CONT_WAVE 7
#define RF_DR 3
#define RF_PWR 6
#define RX_DR 6
#define TX_DS 5
#define MAX_RT 4
#define RX_P_NO 1
#define TX_FULL 0
#define PLOS_CNT 4
#define ARC_CNT 0
#define TX_REUSE 6
#define FIFO_FULL 5
#define TX_EMPTY 4
#define RX_FULL 1
#define RX_EMPTY 0
#define DPL_P5 5
#define DPL_P4 4
#define DPL_P3 3
#define DPL_P2 2
#define DPL_P1 1
#define DPL_P0 0
#define EN_DPL 2
#define EN_ACK_PAY 1
#define EN_DYN_ACK 0
/* Instruction Mnemonics */
#define R_REGISTER 0x00
#define W_REGISTER 0x20
#define REGISTER_MASK 0x1F
#define ACTIVATE 0x50
#define R_RX_PL_WID 0x60
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define W_ACK_PAYLOAD 0xA8
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3
#define RF24_NOP 0xFF
/* Non-P omissions */
#define LNA_HCURR 0
/* P model memory Map */
#define RPD 0x09
#define W_TX_PAYLOAD_NO_ACK 0xB0
/* P model bit Mnemonics */
#define RF_DR_LOW 5
#define RF_DR_HIGH 3
#define RF_PWR_LOW 1
#define RF_PWR_HIGH 2
class RF24 {
private:
_SPI* _spi;
uint16_t ce_pin; /**< "Chip Enable" pin, activates the RX or TX role */
uint16_t csn_pin; /**< SPI Chip select */
#ifdef ARDUINO_ARCH_AVR
volatile uint8_t *cePtr;
volatile uint8_t *csnPtr;
uint8_t ceMask;
uint8_t csnMask;
#endif
uint32_t spi_speed; /**< SPI Bus Speed */
uint8_t status; /** The status byte returned from every SPI transaction */
uint8_t payload_size; /**< Fixed size of payloads */
bool dynamic_payloads_enabled; /**< Whether dynamic payloads are enabled. */
bool ack_payloads_enabled; /**< Whether ack payloads are enabled. */
uint8_t pipe0_reading_address[5]; /**< Last address set on pipe 0 for reading. */
uint8_t addr_width; /**< The address width to use - 3,4 or 5 bytes. */
uint8_t config_reg; /**< For storing the value of the NRF_CONFIG register */
bool _is_p_variant; /** For storing the result of testing the toggleFeatures() affect */
protected:
inline void beginTransaction();
inline void endTransaction();
public:
RF24(uint16_t _cepin, uint16_t _cspin, uint32_t _spi_speed = RF24_SPI_SPEED);
RF24(uint32_t _spi_speed = RF24_SPI_SPEED);
bool begin(void);
bool begin(uint16_t _cepin, uint16_t _cspin);
bool begin(_SPI* spiBus);
bool begin(_SPI* spiBus, uint16_t _cepin, uint16_t _cspin);
bool isChipConnected();
void startListening(void);
void stopListening(void);
bool available(void);
void read(void* buf, uint8_t len);
bool write(const void* buf, uint8_t len);
void openWritingPipe(const uint8_t* address);
void openReadingPipe(uint8_t number, const uint8_t* address);
void printDetails(void);
void printPrettyDetails(void);
bool available(uint8_t* pipe_num);
bool rxFifoFull();
void powerDown(void);
void powerUp(void);
bool write(const void* buf, uint8_t len, const bool multicast);
bool writeFast(const void* buf, uint8_t len);
bool writeFast(const void* buf, uint8_t len, const bool multicast);
bool writeBlocking(const void* buf, uint8_t len, uint32_t timeout);
bool isWriteFinished();
bool txStandBy();
bool txStandBy(uint32_t timeout, bool startTx = 0);
bool writeAckPayload(uint8_t pipe, const void* buf, uint8_t len);
void whatHappened(bool& tx_ok, bool& tx_fail, bool& rx_ready);
void startFastWrite(const void* buf, uint8_t len, const bool multicast, bool startTx = 1);
bool startWrite(const void* buf, uint8_t len, const bool multicast);
void reUseTX();
uint8_t flush_tx(void);
uint8_t flush_rx(void);
bool testCarrier(void);
bool testRPD(void);
bool isValid();
void closeReadingPipe(uint8_t pipe);
bool failureDetected;
void setAddressWidth(uint8_t a_width);
void setRetries(uint8_t delay, uint8_t count);
void setChannel(uint8_t channel);
uint8_t getChannel(void);
void setPayloadSize(uint8_t size);
uint8_t getPayloadSize(void);
uint8_t getDynamicPayloadSize(void);
void enableAckPayload(void);
void disableAckPayload(void);
void enableDynamicPayloads(void);
void disableDynamicPayloads(void);
void enableDynamicAck();
bool isPVariant(void);
void setAutoAck(bool enable);
void setAutoAck(uint8_t pipe, bool enable);
void setPALevel(uint8_t level, bool lnaEnable = 1);
uint8_t getPALevel(void);
uint8_t getARC(void);
bool setDataRate(rf24_datarate_e speed);
rf24_datarate_e getDataRate(void);
void setCRCLength(rf24_crclength_e length);
rf24_crclength_e getCRCLength(void);
void disableCRC(void);
void maskIRQ(bool tx_ok, bool tx_fail, bool rx_ready);
uint32_t txDelay;
uint32_t csDelay;
void startConstCarrier(rf24_pa_dbm_e level, uint8_t channel);
void stopConstCarrier(void);
void openReadingPipe(uint8_t number, uint64_t address);
void openWritingPipe(uint64_t address);
bool isAckPayloadAvailable(void);
private:
void _init_obj();
bool _init_radio();
bool _init_pins();
void csn(bool mode);
void ce(bool level);
void read_register(uint8_t reg, uint8_t* buf, uint8_t len);
uint8_t read_register(uint8_t reg);
void write_register(uint8_t reg, const uint8_t* buf, uint8_t len);
void write_register(uint8_t reg, uint8_t value, bool is_cmd_only = false);
void write_payload(const void* buf, uint8_t len, const uint8_t writeType);
void read_payload(void* buf, uint8_t len);
void toggle_features(void);
void errNotify(void);
protected:
uint8_t get_status(void);
};
#endif // __RF24_H__

View File

@@ -44,6 +44,8 @@
* Public static data
*/
/* static */ int Turnout::turnoutlistHash = 0;
/* static */ unsigned long Turnout::_lastLoopEntry = 0;
/*
* Protected static functions
@@ -69,6 +71,48 @@
}
turnoutlistHash++;
}
/* static */ void Turnout::loop() {
// Here, we check whether the turnout state needs updating.
// For Servo and VPIN turnouts, we need to check the isBusy() status of the device
// and when it goes to 0 we can send out a turnout notification.
// We allow a small delay before we start checking isBusy(), to allow the
// operation time to start.
unsigned long currentMillis = millis();
// Check for updates once every 100ms.
if (currentMillis - _lastLoopEntry > 100) {
_lastLoopEntry = currentMillis;
Turnout *tt = _firstTurnout;
for ( ; tt != 0; tt=tt->_nextTurnout) {
if (tt->_delayResponse > 1) {
tt->_delayResponse--;
} else if (tt->_delayResponse == 1) {
// Pointer has been triggered and grace time has elapsed, so check if completed
if (!tt->isPending()) {
tt->sendResponse();
}
}
}
}
}
// Function called when the turnout has changed.
void Turnout::sendResponse() {
turnoutlistHash++; // let withrottle know something changed
#if defined(RMFT_ACTIVE)
RMFT2::turnoutEvent(_turnoutData.id, _turnoutData.closed);
#endif
// Send message to JMRI etc. over Serial USB. This is done here
// to ensure that the message is sent when the turnout operation
// is not initiated by a Serial command.
printState(&Serial);
// Clear flag pending operation
_delayResponse = 0;
}
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
void Turnout::printState(Print *stream) {
@@ -141,7 +185,15 @@
if (!tt) return false;
bool ok = tt->setClosedInternal(closeFlag);
// Check return value to see if this type of turnout is immediate
// or if we need to check for completion.
if (ok) {
tt->sendResponse();
} else {
// Set flag indicating that the operation is pending and needs to be checked for completion
// starting in 4 loop counts (starts checking at 1).
tt->_delayResponse = 5;
}
turnoutlistHash++; // let withrottle know something changed
#ifndef DISABLE_EEPROM
@@ -151,16 +203,7 @@
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
#endif
#if defined(RMFT_ACTIVE)
RMFT2::turnoutEvent(id, closeFlag);
#endif
// Send message to JMRI etc. over Serial USB. This is done here
// to ensure that the message is sent when the turnout operation
// is not initiated by a Serial command.
tt->printState(&Serial);
}
return ok;
return true;
}
#ifndef DISABLE_EEPROM
@@ -311,10 +354,16 @@
IODevice::writeAnalogue(_servoTurnoutData.vpin,
close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile);
_turnoutData.closed = close;
return false; // Don't send update messages yet.
#else
(void)close; // avoid compiler warnings
#endif
return true;
#endif
}
// Determine if the turnout is moving into its new state.
bool ServoTurnout::isPending() {
return IODevice::isBusy(_servoTurnoutData.vpin);
}
void ServoTurnout::save() {
@@ -482,12 +531,22 @@
!_turnoutData.closed);
}
// setClosedInternal is called from the base class's setClosed() method. It returns true if the
// operation is considered to be completed, and false if it is started but not completed.
// For VPINs which are attached to servos, we ought to return false so that the confirmation
// message to JMRI is deferred until the servo movement is completed. However, if we do this
// it will also affect VPINs that are GPIO pins; when it polls the pin it will put the pin into
// input mode, which is not desirable. Hence, we return true here so that no polling takes place.
bool VpinTurnout::setClosedInternal(bool close) {
IODevice::write(_vpinTurnoutData.vpin, close);
_turnoutData.closed = close;
return true;
}
bool VpinTurnout::isPending() {
return IODevice::isBusy(_vpinTurnoutData.vpin);
}
void VpinTurnout::save() {
#ifndef DISABLE_EEPROM
// Write turnout definition and current position to EEPROM

View File

@@ -72,6 +72,9 @@ protected:
// Pointer to next turnout on linked list.
Turnout *_nextTurnout = 0;
// Delay counter for responses. If non-zero, there is a pending response to be sent.
uint8_t _delayResponse = 0;
/*
* Constructor
*/
@@ -88,6 +91,7 @@ protected:
static Turnout *_firstTurnout;
static int _turnoutlistHash;
static unsigned long _lastLoopEntry;
/*
* Virtual functions
@@ -95,7 +99,8 @@ protected:
virtual bool setClosedInternal(bool close) = 0; // Mandatory in subclass
virtual void save() {}
virtual bool isPending() { return false; };
/*
* Static functions
*/
@@ -103,7 +108,12 @@ protected:
static Turnout *get(uint16_t id);
static void add(Turnout *tt);
/*
* Other functions
*/
void sendResponse();
public:
/*
* Static data
@@ -155,6 +165,8 @@ public:
inline static Turnout *first() { return _firstTurnout; }
static void loop();
#ifndef DISABLE_EEPROM
// Load all turnout definitions.
static void load();
@@ -202,6 +214,7 @@ protected:
// ServoTurnout-specific code for throwing or closing a servo turnout.
bool setClosedInternal(bool close) override;
void save() override;
bool isPending() override;
};
@@ -266,6 +279,7 @@ public:
protected:
bool setClosedInternal(bool close) override;
void save() override;
bool isPending() override;
};

611
enc28j60.cpp Normal file
View File

@@ -0,0 +1,611 @@
// Microchip ENC28J60 Ethernet Interface Driver
// Author: Guido Socher
// Copyright: GPL V2
//
// Based on the enc28j60.c file from the AVRlib library by Pascal Stang.
// For AVRlib See http://www.procyonengineering.com/
// Used with explicit permission of Pascal Stang.
//
// 2010-05-20 <jc@wippler.nl>
#if ARDUINO >= 100
#include <Arduino.h> // Arduino 1.0
#else
#include <Wprogram.h> // Arduino 0022
#endif
#include <SPI.h>
#include "enc28j60.h"
uint16_t ENC28J60::bufferSize;
bool ENC28J60::broadcast_enabled = false;
bool ENC28J60::promiscuous_enabled = false;
// ENC28J60 Control Registers
// Control register definitions are a combination of address,
// bank number, and Ethernet/MAC/PHY indicator bits.
// - Register address (bits 0-4)
// - Bank number (bits 5-6)
// - MAC/PHY indicator (bit 7)
#define ADDR_MASK 0x1F
#define BANK_MASK 0x60
#define SPRD_MASK 0x80
// All-bank registers
#define EIE 0x1B
#define EIR 0x1C
#define ESTAT 0x1D
#define ECON2 0x1E
#define ECON1 0x1F
// Bank 0 registers
#define ERDPT (0x00|0x00)
#define EWRPT (0x02|0x00)
#define ETXST (0x04|0x00)
#define ETXND (0x06|0x00)
#define ERXST (0x08|0x00)
#define ERXND (0x0A|0x00)
#define ERXRDPT (0x0C|0x00)
// #define ERXWRPT (0x0E|0x00)
#define EDMAST (0x10|0x00)
#define EDMAND (0x12|0x00)
// #define EDMADST (0x14|0x00)
#define EDMACS (0x16|0x00)
// Bank 1 registers
#define EHT0 (0x00|0x20)
#define EHT1 (0x01|0x20)
#define EHT2 (0x02|0x20)
#define EHT3 (0x03|0x20)
#define EHT4 (0x04|0x20)
#define EHT5 (0x05|0x20)
#define EHT6 (0x06|0x20)
#define EHT7 (0x07|0x20)
#define EPMM0 (0x08|0x20)
#define EPMM1 (0x09|0x20)
#define EPMM2 (0x0A|0x20)
#define EPMM3 (0x0B|0x20)
#define EPMM4 (0x0C|0x20)
#define EPMM5 (0x0D|0x20)
#define EPMM6 (0x0E|0x20)
#define EPMM7 (0x0F|0x20)
#define EPMCS (0x10|0x20)
// #define EPMO (0x14|0x20)
#define EWOLIE (0x16|0x20)
#define EWOLIR (0x17|0x20)
#define ERXFCON (0x18|0x20)
#define EPKTCNT (0x19|0x20)
// Bank 2 registers
#define MACON1 (0x00|0x40|0x80)
#define MACON3 (0x02|0x40|0x80)
#define MACON4 (0x03|0x40|0x80)
#define MABBIPG (0x04|0x40|0x80)
#define MAIPG (0x06|0x40|0x80)
#define MACLCON1 (0x08|0x40|0x80)
#define MACLCON2 (0x09|0x40|0x80)
#define MAMXFL (0x0A|0x40|0x80)
#define MAPHSUP (0x0D|0x40|0x80)
#define MICON (0x11|0x40|0x80)
#define MICMD (0x12|0x40|0x80)
#define MIREGADR (0x14|0x40|0x80)
#define MIWR (0x16|0x40|0x80)
#define MIRD (0x18|0x40|0x80)
// Bank 3 registers
#define MAADR1 (0x00|0x60|0x80)
#define MAADR0 (0x01|0x60|0x80)
#define MAADR3 (0x02|0x60|0x80)
#define MAADR2 (0x03|0x60|0x80)
#define MAADR5 (0x04|0x60|0x80)
#define MAADR4 (0x05|0x60|0x80)
#define EBSTSD (0x06|0x60)
#define EBSTCON (0x07|0x60)
#define EBSTCS (0x08|0x60)
#define MISTAT (0x0A|0x60|0x80)
#define EREVID (0x12|0x60)
#define ECOCON (0x15|0x60)
#define EFLOCON (0x17|0x60)
#define EPAUS (0x18|0x60)
// ENC28J60 ERXFCON Register Bit Definitions
#define ERXFCON_UCEN 0x80
#define ERXFCON_ANDOR 0x40
#define ERXFCON_CRCEN 0x20
#define ERXFCON_PMEN 0x10
#define ERXFCON_MPEN 0x08
#define ERXFCON_HTEN 0x04
#define ERXFCON_MCEN 0x02
#define ERXFCON_BCEN 0x01
// ENC28J60 EIE Register Bit Definitions
#define EIE_INTIE 0x80
#define EIE_PKTIE 0x40
#define EIE_DMAIE 0x20
#define EIE_LINKIE 0x10
#define EIE_TXIE 0x08
#define EIE_WOLIE 0x04
#define EIE_TXERIE 0x02
#define EIE_RXERIE 0x01
// ENC28J60 EIR Register Bit Definitions
#define EIR_PKTIF 0x40
#define EIR_DMAIF 0x20
#define EIR_LINKIF 0x10
#define EIR_TXIF 0x08
#define EIR_WOLIF 0x04
#define EIR_TXERIF 0x02
#define EIR_RXERIF 0x01
// ENC28J60 ESTAT Register Bit Definitions
#define ESTAT_INT 0x80
#define ESTAT_LATECOL 0x10
#define ESTAT_RXBUSY 0x04
#define ESTAT_TXABRT 0x02
#define ESTAT_CLKRDY 0x01
// ENC28J60 ECON2 Register Bit Definitions
#define ECON2_AUTOINC 0x80
#define ECON2_PKTDEC 0x40
#define ECON2_PWRSV 0x20
#define ECON2_VRPS 0x08
// ENC28J60 ECON1 Register Bit Definitions
#define ECON1_TXRST 0x80
#define ECON1_RXRST 0x40
#define ECON1_DMAST 0x20
#define ECON1_CSUMEN 0x10
#define ECON1_TXRTS 0x08
#define ECON1_RXEN 0x04
#define ECON1_BSEL1 0x02
#define ECON1_BSEL0 0x01
// ENC28J60 MACON1 Register Bit Definitions
#define MACON1_LOOPBK 0x10
#define MACON1_TXPAUS 0x08
#define MACON1_RXPAUS 0x04
#define MACON1_PASSALL 0x02
#define MACON1_MARXEN 0x01
// ENC28J60 MACON3 Register Bit Definitions
#define MACON3_PADCFG2 0x80
#define MACON3_PADCFG1 0x40
#define MACON3_PADCFG0 0x20
#define MACON3_TXCRCEN 0x10
#define MACON3_PHDRLEN 0x08
#define MACON3_HFRMLEN 0x04
#define MACON3_FRMLNEN 0x02
#define MACON3_FULDPX 0x01
// ENC28J60 MICMD Register Bit Definitions
#define MICMD_MIISCAN 0x02
#define MICMD_MIIRD 0x01
// ENC28J60 MISTAT Register Bit Definitions
#define MISTAT_NVALID 0x04
#define MISTAT_SCAN 0x02
#define MISTAT_BUSY 0x01
// ENC28J60 EBSTCON Register Bit Definitions
#define EBSTCON_PSV2 0x80
#define EBSTCON_PSV1 0x40
#define EBSTCON_PSV0 0x20
#define EBSTCON_PSEL 0x10
#define EBSTCON_TMSEL1 0x08
#define EBSTCON_TMSEL0 0x04
#define EBSTCON_TME 0x02
#define EBSTCON_BISTST 0x01
// PHY registers
#define PHCON1 0x00
#define PHSTAT1 0x01
#define PHHID1 0x02
#define PHHID2 0x03
#define PHCON2 0x10
#define PHSTAT2 0x11
#define PHIE 0x12
#define PHIR 0x13
#define PHLCON 0x14
// ENC28J60 PHY PHCON1 Register Bit Definitions
#define PHCON1_PRST 0x8000
#define PHCON1_PLOOPBK 0x4000
#define PHCON1_PPWRSV 0x0800
#define PHCON1_PDPXMD 0x0100
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
#define PHSTAT1_PFDPX 0x1000
#define PHSTAT1_PHDPX 0x0800
#define PHSTAT1_LLSTAT 0x0004
#define PHSTAT1_JBSTAT 0x0002
// ENC28J60 PHY PHCON2 Register Bit Definitions
#define PHCON2_FRCLINK 0x4000
#define PHCON2_TXDIS 0x2000
#define PHCON2_JABBER 0x0400
#define PHCON2_HDLDIS 0x0100
// ENC28J60 Packet Control Byte Bit Definitions
#define PKTCTRL_PHUGEEN 0x08
#define PKTCTRL_PPADEN 0x04
#define PKTCTRL_PCRCEN 0x02
#define PKTCTRL_POVERRIDE 0x01
// SPI operation codes
#define ENC28J60_READ_CTRL_REG 0x00
#define ENC28J60_READ_BUF_MEM 0x3A
#define ENC28J60_WRITE_CTRL_REG 0x40
#define ENC28J60_WRITE_BUF_MEM 0x7A
#define ENC28J60_BIT_FIELD_SET 0x80
#define ENC28J60_BIT_FIELD_CLR 0xA0
#define ENC28J60_SOFT_RESET 0xFF
// max frame length which the controller will accept:
// (note: maximum ethernet frame length would be 1518)
#define MAX_FRAMELEN 1500
#define SPI_SPEED 10000000
static byte Enc28j60Bank;
static byte selectPin;
static uint8_t selectMask;
volatile static uint8_t *selectPort;
void ENC28J60::initSPI () {
pinMode(selectPin, OUTPUT);
digitalWrite(selectPin, HIGH);
pinMode(MOSI, OUTPUT);
pinMode(SCK, OUTPUT);
pinMode(MISO, INPUT);
digitalWrite(MOSI, HIGH);
digitalWrite(MOSI, LOW);
digitalWrite(SCK, LOW);
#ifdef ARDUINO_ARCH_AVR
selectPort = portOutputRegister(digitalPinToPort(selectPin));
selectMask = digitalPinToBitMask(selectPin);
#endif
}
static void enableChip () {
#ifdef ARDUINO_ARCH_AVR
cli();
*selectPort &= ~selectMask;
sei();
#else
digitalWrite(selectPin, LOW);
#endif
}
static void disableChip () {
#ifdef ARDUINO_ARCH_AVR
cli();
*selectPort |= selectMask;
sei();
#else
digitalWrite(selectPin, HIGH);
#endif
}
static void beginTransaction() {
SPI.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE0));
enableChip();
}
static void endTransaction() {
disableChip();
SPI.endTransaction();
}
static byte readOp (byte op, byte address) {
beginTransaction();
SPI.transfer(op | (address & ADDR_MASK));
byte result = SPI.transfer(0x00);
if (address & 0x80)
result = SPI.transfer(0x00);
endTransaction();
return result;
}
static void writeOp (byte op, byte address, byte data) {
beginTransaction();
SPI.transfer(op | (address & ADDR_MASK));
SPI.transfer(data);
endTransaction();
}
static void readBuf(uint16_t len, byte* data) {
beginTransaction();
SPI.transfer(ENC28J60_READ_BUF_MEM);
while (len--)
*data++ = SPI.transfer(0x00);
endTransaction();
}
static void writeBuf(uint16_t len, const byte* data) {
beginTransaction();
SPI.transfer(ENC28J60_WRITE_BUF_MEM);
while (len--)
SPI.transfer(*data++);
endTransaction();
}
static void SetBank (byte address) {
if ((address & BANK_MASK) != Enc28j60Bank) {
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_BSEL1|ECON1_BSEL0);
Enc28j60Bank = address & BANK_MASK;
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, Enc28j60Bank>>5);
}
}
static byte readRegByte (byte address) {
SetBank(address);
return readOp(ENC28J60_READ_CTRL_REG, address);
}
static uint16_t readReg(byte address) {
return readRegByte(address) + (readRegByte(address+1) << 8);
}
static void writeRegByte (byte address, byte data) {
SetBank(address);
writeOp(ENC28J60_WRITE_CTRL_REG, address, data);
}
static void writeReg(byte address, uint16_t data) {
writeRegByte(address, data);
writeRegByte(address + 1, data >> 8);
}
static uint16_t readPhyByte (byte address) {
writeRegByte(MIREGADR, address);
writeRegByte(MICMD, MICMD_MIIRD);
while (readRegByte(MISTAT) & MISTAT_BUSY)
;
writeRegByte(MICMD, 0x00);
return readRegByte(MIRD+1);
}
static void writePhy (byte address, uint16_t data) {
writeRegByte(MIREGADR, address);
writeReg(MIWR, data);
while (readRegByte(MISTAT) & MISTAT_BUSY)
;
}
byte ENC28J60::initialize (uint16_t size, const byte* macaddr, byte csPin) {
bufferSize = size;
selectPin = csPin;
initSPI();
disableChip();
writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
delay(2); // errata B7/2
while (!readOp(ENC28J60_READ_CTRL_REG, ESTAT) & ESTAT_CLKRDY)
;
writeReg(ERXST, RXSTART_INIT);
writeReg(ERXRDPT, RXSTART_INIT);
writeReg(ERXND, RXSTOP_INIT);
writeReg(ETXST, TXSTART_INIT);
writeReg(ETXND, TXSTOP_INIT);
// Stretch pulses for LED, LED_A=Link, LED_B=activity
writePhy(PHLCON, 0x476);
writeRegByte(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN|ERXFCON_BCEN);
writeReg(EPMM0, 0x303f);
writeReg(EPMCS, 0xf7f9);
writeRegByte(MACON1, MACON1_MARXEN);
writeOp(ENC28J60_BIT_FIELD_SET, MACON3,
MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);
writeReg(MAIPG, 0x0C12);
writeRegByte(MABBIPG, 0x12);
writeReg(MAMXFL, MAX_FRAMELEN);
writeRegByte(MAADR5, macaddr[0]);
writeRegByte(MAADR4, macaddr[1]);
writeRegByte(MAADR3, macaddr[2]);
writeRegByte(MAADR2, macaddr[3]);
writeRegByte(MAADR1, macaddr[4]);
writeRegByte(MAADR0, macaddr[5]);
writePhy(PHCON2, PHCON2_HDLDIS);
SetBank(ECON1);
writeOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
byte rev = readRegByte(EREVID);
// microchip forgot to step the number on the silicon when they
// released the revision B7. 6 is now rev B7. We still have
// to see what they do when they release B8. At the moment
// there is no B8 out yet
if (rev > 5) ++rev;
return rev;
}
bool ENC28J60::isLinkUp() {
return (readPhyByte(PHSTAT2) >> 2) & 1;
}
/*
struct __attribute__((__packed__)) transmit_status_vector {
uint16_t transmitByteCount;
byte transmitCollisionCount : 4;
byte transmitCrcError : 1;
byte transmitLengthCheckError : 1;
byte transmitLengthOutRangeError : 1;
byte transmitDone : 1;
byte transmitMulticast : 1;
byte transmitBroadcast : 1;
byte transmitPacketDefer : 1;
byte transmitExcessiveDefer : 1;
byte transmitExcessiveCollision : 1;
byte transmitLateCollision : 1;
byte transmitGiant : 1;
byte transmitUnderrun : 1;
uint16_t totalTransmitted;
byte transmitControlFrame : 1;
byte transmitPauseControlFrame : 1;
byte backpressureApplied : 1;
byte transmitVLAN : 1;
byte zero : 4;
};
*/
struct transmit_status_vector {
uint8_t bytes[7];
};
#if ETHERCARD_SEND_PIPELINING
#define BREAKORCONTINUE retry=0; continue;
#else
#define BREAKORCONTINUE break;
#endif
void ENC28J60::packetSend(uint16_t len) {
byte retry = 0;
#if ETHERCARD_SEND_PIPELINING
goto resume_last_transmission;
#endif
while (1) {
// latest errata sheet: DS80349C
// always reset transmit logic (Errata Issue 12)
// the Microchip TCP/IP stack implementation used to first check
// whether TXERIF is set and only then reset the transmit logic
// but this has been changed in later versions; possibly they
// have a reason for this; they don't mention this in the errata
// sheet
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST);
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST);
writeOp(ENC28J60_BIT_FIELD_CLR, EIR, EIR_TXERIF|EIR_TXIF);
// prepare new transmission
if (retry == 0) {
writeReg(EWRPT, TXSTART_INIT);
writeReg(ETXND, TXSTART_INIT+len);
writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
writeBuf(len, buffer);
}
// initiate transmission
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
#if ETHERCARD_SEND_PIPELINING
if (retry == 0) return;
#endif
resume_last_transmission:
// wait until transmission has finished; referring to the data sheet and
// to the errata (Errata Issue 13; Example 1) you only need to wait until either
// TXIF or TXERIF gets set; however this leads to hangs; apparently Microchip
// realized this and in later implementations of their tcp/ip stack they introduced
// a counter to avoid hangs; of course they didn't update the errata sheet
uint16_t count = 0;
while ((readRegByte(EIR) & (EIR_TXIF | EIR_TXERIF)) == 0 && ++count < 1000U)
;
if (!(readRegByte(EIR) & EIR_TXERIF) && count < 1000U) {
// no error; start new transmission
BREAKORCONTINUE
}
// cancel previous transmission if stuck
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
#if ETHERCARD_RETRY_LATECOLLISIONS == 0
BREAKORCONTINUE
#endif
// Check whether the chip thinks that a late collision occurred; the chip
// may be wrong (Errata Issue 13); therefore we retry. We could check
// LATECOL in the ESTAT register in order to find out whether the chip
// thinks a late collision occurred but (Errata Issue 15) tells us that
// this is not working. Therefore we check TSV
transmit_status_vector tsv;
uint16_t etxnd = readReg(ETXND);
writeReg(ERDPT, etxnd+1);
readBuf(sizeof(transmit_status_vector), (byte*) &tsv);
// LATECOL is bit number 29 in TSV (starting from 0)
if (!((readRegByte(EIR) & EIR_TXERIF) && (tsv.bytes[3] & 1<<5) /*tsv.transmitLateCollision*/) || retry > 16U) {
// there was some error but no LATECOL so we do not repeat
BREAKORCONTINUE
}
retry++;
}
}
uint16_t ENC28J60::packetReceive() {
static uint16_t gNextPacketPtr = RXSTART_INIT;
static bool unreleasedPacket = false;
uint16_t len = 0;
if (unreleasedPacket) {
if (gNextPacketPtr == 0)
writeReg(ERXRDPT, RXSTOP_INIT);
else
writeReg(ERXRDPT, gNextPacketPtr - 1);
unreleasedPacket = false;
}
if (readRegByte(EPKTCNT) > 0) {
writeReg(ERDPT, gNextPacketPtr);
struct {
uint16_t nextPacket;
uint16_t byteCount;
uint16_t status;
} header;
readBuf(sizeof header, (byte*) &header);
gNextPacketPtr = header.nextPacket;
len = header.byteCount - 4; //remove the CRC count
if (len>bufferSize) len=0; // discard messages too long **NMCK**
if ((header.status & 0x80)==0)
len = 0;
else
readBuf(len, buffer);
unreleasedPacket = true;
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
}
return len;
}
// Contributed by Alex M. Based on code from: http://blog.derouineau.fr
// /2011/07/putting-enc28j60-ethernet-controler-in-sleep-mode/
void ENC28J60::powerDown() {
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_RXEN);
while(readRegByte(ESTAT) & ESTAT_RXBUSY);
while(readRegByte(ECON1) & ECON1_TXRTS);
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_VRPS);
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PWRSV);
}
void ENC28J60::powerUp() {
writeOp(ENC28J60_BIT_FIELD_CLR, ECON2, ECON2_PWRSV);
while(!readRegByte(ESTAT) & ESTAT_CLKRDY);
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
}
void ENC28J60::enableBroadcast (bool temporary) {
writeRegByte(ERXFCON, readRegByte(ERXFCON) | ERXFCON_BCEN);
if(!temporary)
broadcast_enabled = true;
}
void ENC28J60::disableBroadcast (bool temporary) {
if(!temporary)
broadcast_enabled = false;
if(!broadcast_enabled)
writeRegByte(ERXFCON, readRegByte(ERXFCON) & ~ERXFCON_BCEN);
}
void ENC28J60::enableMulticast () {
writeRegByte(ERXFCON, readRegByte(ERXFCON) | ERXFCON_MCEN);
}
void ENC28J60::disableMulticast () {
writeRegByte(ERXFCON, readRegByte(ERXFCON) & ~ERXFCON_MCEN);
}
void ENC28J60::enablePromiscuous (bool temporary) {
writeRegByte(ERXFCON, readRegByte(ERXFCON) & ERXFCON_CRCEN);
if(!temporary)
promiscuous_enabled = true;
}
void ENC28J60::disablePromiscuous (bool temporary) {
if(!temporary)
promiscuous_enabled = false;
if(!promiscuous_enabled) {
writeRegByte(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN|ERXFCON_BCEN);
}
}

207
enc28j60.h Normal file
View File

@@ -0,0 +1,207 @@
// Microchip ENC28J60 Ethernet Interface Driver
// Author: Pascal Stang
// Modified by: Guido Socher
// Copyright: GPL V2
//
// This driver provides initialization and transmit/receive
// functions for the Microchip ENC28J60 10Mb Ethernet Controller and PHY.
// This chip is novel in that it is a full MAC+PHY interface all in a 28-pin
// chip, using an SPI interface to the host processor.
//
// 2010-05-20 <jc@wippler.nl>
/** @file */
#ifndef ENC28J60_H
#define ENC28J60_H
// buffer boundaries applied to internal 8K ram
// the entire available packet buffer space is allocated
#define RXSTART_INIT 0x0000 // start of RX buffer, (must be zero, Rev. B4 Errata point 5)
#define RXSTOP_INIT 0x0BFF // end of RX buffer, room for 2 packets
#define TXSTART_INIT 0x0C00 // start of TX buffer, room for 1 packet
#define TXSTOP_INIT 0x11FF // end of TX buffer
#define SCRATCH_START 0x1200 // start of scratch area
#define SCRATCH_LIMIT 0x2000 // past end of area, i.e. 3 Kb
#define SCRATCH_PAGE_SHIFT 6 // addressing is in pages of 64 bytes
#define SCRATCH_PAGE_SIZE (1 << SCRATCH_PAGE_SHIFT)
#define SCRATCH_PAGE_NUM ((SCRATCH_LIMIT-SCRATCH_START) >> SCRATCH_PAGE_SHIFT)
#define SCRATCH_MAP_SIZE (((SCRATCH_PAGE_NUM % 8) == 0) ? (SCRATCH_PAGE_NUM / 8) : (SCRATCH_PAGE_NUM/8+1))
// area in the enc memory that can be used via enc_malloc; by default 0 bytes; decrease SCRATCH_LIMIT in order
// to use this functionality
#define ENC_HEAP_START SCRATCH_LIMIT
#define ENC_HEAP_END 0x2000
/** This class provide low-level interfacing with the ENC28J60 network interface. This is used by the EtherCard class and not intended for use by (normal) end users. */
class ENC28J60 {
public:
static uint8_t buffer[]; //!< Data buffer (shared by receive and transmit)
static uint16_t bufferSize; //!< Size of data buffer
static bool broadcast_enabled; //!< True if broadcasts enabled (used to allow temporary disable of broadcast for DHCP or other internal functions)
static bool promiscuous_enabled; //!< True if promiscuous mode enabled (used to allow temporary disable of promiscuous mode)
static uint8_t* tcpOffset () { return buffer + 0x36; } //!< Pointer to the start of TCP payload
/** @brief Initialise SPI interface
* @note Configures Arduino pins as input / output, etc.
*/
static void initSPI ();
/** @brief Initialise network interface
* @param size Size of data buffer
* @param macaddr Pointer to 6 byte hardware (MAC) address
* @param csPin Arduino pin used for chip select (enable network interface SPI bus). Default = 8
* @return <i>uint8_t</i> ENC28J60 firmware version or zero on failure.
*/
static uint8_t initialize (const uint16_t size, const uint8_t* macaddr,
uint8_t csPin = 8);
/** @brief Check if network link is connected
* @return <i>bool</i> True if link is up
*/
static bool isLinkUp ();
/** @brief Sends data to network interface
* @param len Size of data to send
* @note Data buffer is shared by receive and transmit functions
*/
static void packetSend (uint16_t len);
/** @brief Copy received packets to data buffer
* @return <i>uint16_t</i> Size of received data
* @note Data buffer is shared by receive and transmit functions
*/
static uint16_t packetReceive ();
/** @brief Copy data from ENC28J60 memory
* @param page Data page of memory
* @param data Pointer to buffer to copy data to
*/
static void copyout (uint8_t page, const uint8_t* data);
/** @brief Copy data to ENC28J60 memory
* @param page Data page of memory
* @param data Pointer to buffer to copy data from
*/
static void copyin (uint8_t page, uint8_t* data);
/** @brief Get single byte of data from ENC28J60 memory
* @param page Data page of memory
* @param off Offset of data within page
* @return Data value
*/
static uint8_t peekin (uint8_t page, uint8_t off);
/** @brief Put ENC28J60 in sleep mode
*/
static void powerDown(); // contrib by Alex M.
/** @brief Wake ENC28J60 from sleep mode
*/
static void powerUp(); // contrib by Alex M.
/** @brief Enable reception of broadcast messages
* @param temporary Set true to temporarily enable broadcast
* @note This will increase load on received data handling
*/
static void enableBroadcast(bool temporary = false);
/** @brief Disable reception of broadcast messages
* @param temporary Set true to only disable if temporarily enabled
* @note This will reduce load on received data handling
*/
static void disableBroadcast(bool temporary = false);
/** @brief Enables reception of multicast messages
* @note This will increase load on received data handling
*/
static void enableMulticast ();
/** @brief Enables reception of all messages
* @param temporary Set true to temporarily enable promiscuous
* @note This will increase load significantly on received data handling
* @note All messages will be accepted, even messages with destination MAC other than own
* @note Messages with invalid CRC checksum will still be rejected
*/
static void enablePromiscuous (bool temporary = false);
/** @brief Disable reception of all messages and go back to default mode
* @param temporary Set true to only disable if temporarily enabled
* @note This will reduce load on received data handling
* @note In this mode only unicast and broadcast messages will be received
*/
static void disablePromiscuous(bool temporary = false);
/** @brief Disable reception of multicast messages
* @note This will reduce load on received data handling
*/
static void disableMulticast();
/** @brief Reset and fully initialise ENC28J60
* @param csPin Arduino pin used for chip select (enable SPI bus)
* @return <i>uint8_t</i> 0 on failure
*/
static uint8_t doBIST(uint8_t csPin = 8);
/** @brief Copies a slice from the current packet to RAM
* @param dest pointer in RAM where the data is copied to
* @param maxlength how many bytes to copy;
* @param packetOffset where within the packet to start; if less than maxlength bytes are available only the remaining bytes are copied.
* @return <i>uint16_t</i> the number of bytes that have been read
* @note At the destination at least maxlength+1 bytes should be reserved because the copied content will be 0-terminated.
*/
static uint16_t readPacketSlice(char* dest, int16_t maxlength, int16_t packetOffset);
/** @brief reserves a block of RAM in the memory of the enc chip
* @param size number of bytes to reserve
* @return <i>uint16_t</i> start address of the block within the enc memory. 0 if the remaining memory for malloc operation is less than size.
* @note There is no enc_free(), i.e., reserved blocks stay reserved for the duration of the program.
* @note The total memory available for malloc-operations is determined by ENC_HEAP_END-ENC_HEAP_START, defined in enc28j60.h; by default this is 0, i.e., you have to change these values in order to use enc_malloc().
*/
static uint16_t enc_malloc(uint16_t size);
/** @brief returns the amount of memory within the enc28j60 chip that is still available for malloc.
* @return <i>uint16_t</i> the amount of memory in bytes.
*/
static uint16_t enc_freemem();
/** @brief copies a block of data from SRAM to the enc memory
@param dest destination address within enc memory
@param source source pointer to a block of SRAM in the arduino
@param num number of bytes to copy
@note There is no sanity check. Handle with care
*/
static void memcpy_to_enc(uint16_t dest, void* source, int16_t num);
/** @brief copies a block of data from the enc memory to SRAM
@param dest destination address within SRAM
@param source source address within enc memory
@param num number of bytes to copy
*/
static void memcpy_from_enc(void* dest, uint16_t source, int16_t num);
};
// typedef ENC28J60 Ethernet; //!< Define alias Ethernet for ENC28J60
/** Workaround for Errata 13.
* The transmission hardware may drop some packets because it thinks a late collision
* occurred (which should never happen if all cable length etc. are ok). If setting
* this to 1 these packages will be retried a fixed number of times. Costs about 150bytes
* of flash.
*/
#define ETHERCARD_RETRY_LATECOLLISIONS 0
/** Enable pipelining of packet transmissions.
* If enabled the packetSend function will not block/wait until the packet is actually
* transmitted; but instead this wait is shifted to the next time that packetSend is
* called. This gives higher performance; however in combination with
* ETHERCARD_RETRY_LATECOLLISIONS this may lead to problems because a packet whose
* transmission fails because the ENC-chip thinks that it is a late collision will
* not be retried until the next call to packetSend.
*/
#define ETHERCARD_SEND_PIPELINING 1
#endif

100
net.h Normal file
View File

@@ -0,0 +1,100 @@
// Based on the net.h file from the AVRlib library by Pascal Stang.
// Author: Guido Socher
// Copyright: GPL V2
//
// For AVRlib See http://www.procyonengineering.com/
// Used with explicit permission of Pascal Stang.
//
// 2010-05-20 <jc@wippler.nl>
// notation: _P = position of a field
// _V = value of a field
#ifndef NET_H
#define NET_H
// ******* ETH *******
#define ETH_HEADER_LEN 14
#define ETH_LEN 6
// values of certain bytes:
#define ETHTYPE_ARP_H_V 0x08
#define ETHTYPE_ARP_L_V 0x06
#define ETHTYPE_IP_H_V 0x08
#define ETHTYPE_IP_L_V 0x00
// byte positions in the ethernet frame:
//
// Ethernet type field (2bytes):
#define ETH_TYPE_H_P 12
#define ETH_TYPE_L_P 13
//
#define ETH_DST_MAC 0
#define ETH_SRC_MAC 6
// ******* ARP *******
#define ETH_ARP_OPCODE_REPLY_H_V 0x0
#define ETH_ARP_OPCODE_REPLY_L_V 0x02
#define ETH_ARP_OPCODE_REQ_H_V 0x0
#define ETH_ARP_OPCODE_REQ_L_V 0x01
// start of arp header:
#define ETH_ARP_P 0xe
//
#define ETHTYPE_ARP_L_V 0x06
// arp.dst.ip
#define ETH_ARP_DST_IP_P 0x26
// arp.opcode
#define ETH_ARP_OPCODE_H_P 0x14
#define ETH_ARP_OPCODE_L_P 0x15
// arp.src.mac
#define ETH_ARP_SRC_MAC_P 0x16
#define ETH_ARP_SRC_IP_P 0x1c
#define ETH_ARP_DST_MAC_P 0x20
#define ETH_ARP_DST_IP_P 0x26
// ******* IP *******
#define IP_HEADER_LEN 20
#define IP_LEN 4
// ip.src
#define IP_SRC_P 0x1a
#define IP_DST_P 0x1e
#define IP_HEADER_LEN_VER_P 0xe
#define IP_CHECKSUM_P 0x18
#define IP_TTL_P 0x16
#define IP_FLAGS_P 0x14
#define IP_P 0xe
#define IP_TOTLEN_H_P 0x10
#define IP_TOTLEN_L_P 0x11
#define IP_PROTO_P 0x17
#define IP_PROTO_ICMP_V 1
#define IP_PROTO_TCP_V 6
// 17=0x11
#define IP_PROTO_UDP_V 17
// ******* ICMP *******
#define ICMP_TYPE_ECHOREPLY_V 0
#define ICMP_TYPE_ECHOREQUEST_V 8
//
#define ICMP_TYPE_P 0x22
#define ICMP_CHECKSUM_P 0x24
#define ICMP_CHECKSUM_H_P 0x24
#define ICMP_CHECKSUM_L_P 0x25
#define ICMP_IDENT_H_P 0x26
#define ICMP_IDENT_L_P 0x27
#define ICMP_DATA_P 0x2a
// ******* UDP *******
#define UDP_HEADER_LEN 8
//
#define UDP_SRC_PORT_H_P 0x22
#define UDP_SRC_PORT_L_P 0x23
#define UDP_DST_PORT_H_P 0x24
#define UDP_DST_PORT_L_P 0x25
//
#define UDP_LEN_H_P 0x26
#define UDP_LEN_L_P 0x27
#define UDP_CHECKSUM_H_P 0x28
#define UDP_CHECKSUM_L_P 0x29
#define UDP_DATA_P 0x2a
#endif

317
tcpip.cpp Normal file
View File

@@ -0,0 +1,317 @@
// IP, ARP, UDP and TCP functions.
// Author: Guido Socher
// Copyright: GPL V2
//
// The TCP implementation uses some size optimisations which are valid
// only if all data can be sent in one single packet. This is however
// not a big limitation for a microcontroller as you will anyhow use
// small web-pages. The web server must send the entire web page in one
// packet. The client "web browser" as implemented here can also receive
// large pages.
//
// 2010-05-20 <jc@wippler.nl>
#include "EtherCard.h"
#include "net.h"
#undef word // arduino nonsense
#define gPB ether.buffer
#define PINGPATTERN 0x42
// Avoid spurious pgmspace warnings - http://forum.jeelabs.net/node/327
// See also http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734
//#undef PROGMEM
//#define PROGMEM __attribute__(( section(".progmem.data") ))
//#undef PSTR
//#define PSTR(s) (__extension__({static prog_char c[] PROGMEM = (s); &c[0];}))
#define TCP_STATE_SENDSYN 1
#define TCP_STATE_SYNSENT 2
#define TCP_STATE_ESTABLISHED 3
#define TCP_STATE_NOTUSED 4
#define TCP_STATE_CLOSING 5
#define TCP_STATE_CLOSED 6
static uint8_t destmacaddr[ETH_LEN]; // storing both dns server and destination mac addresses, but at different times because both are never needed at same time.
static boolean waiting_for_dns_mac = false; //might be better to use bit flags and bitmask operations for these conditions
static boolean has_dns_mac = false;
static boolean waiting_for_dest_mac = false;
static boolean has_dest_mac = false;
static uint8_t gwmacaddr[ETH_LEN]; // Hardware (MAC) address of gateway router
static uint8_t waitgwmac; // Bitwise flags of gateway router status - see below for states
//Define gateway router ARP statuses
#define WGW_INITIAL_ARP 1 // First request, no answer yet
#define WGW_HAVE_GW_MAC 2 // Have gateway router MAC
#define WGW_REFRESHING 4 // Refreshing but already have gateway MAC
#define WGW_ACCEPT_ARP_REPLY 8 // Accept an ARP reply
const unsigned char arpreqhdr[] PROGMEM = { 0,1,8,0,6,4,0,1 }; // ARP request header
const unsigned char iphdr[] PROGMEM = { 0x45,0,0,0x82,0,0,0x40,0,0x20 }; //IP header
extern const uint8_t allOnes[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; // Used for hardware (MAC) and IP broadcast addresses
static void fill_checksum(uint8_t dest, uint8_t off, uint16_t len,uint8_t type) {
const uint8_t* ptr = gPB + off;
uint32_t sum = type==1 ? IP_PROTO_UDP_V+len-8 :
type==2 ? IP_PROTO_TCP_V+len-8 : 0;
while(len >1) {
sum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1));
ptr+=2;
len-=2;
}
if (len)
sum += ((uint32_t)*ptr)<<8;
while (sum>>16)
sum = (uint16_t) sum + (sum >> 16);
uint16_t ck = ~ (uint16_t) sum;
gPB[dest] = ck>>8;
gPB[dest+1] = ck;
}
static void setMACs (const uint8_t *mac) {
EtherCard::copyMac(gPB + ETH_DST_MAC, mac);
EtherCard::copyMac(gPB + ETH_SRC_MAC, EtherCard::mymac);
}
static void setMACandIPs (const uint8_t *mac, const uint8_t *dst) {
setMACs(mac);
EtherCard::copyIp(gPB + IP_DST_P, dst);
EtherCard::copyIp(gPB + IP_SRC_P, EtherCard::myip);
}
static boolean is_lan(const uint8_t source[IP_LEN], const uint8_t destination[IP_LEN]) {
if(source[0] == 0 || destination[0] == 0) {
return false;
}
for(int i = 0; i < IP_LEN; i++)
if((source[i] & EtherCard::netmask[i]) != (destination[i] & EtherCard::netmask[i])) {
return false;
}
return true;
}
static uint8_t eth_type_is_arp_and_my_ip(uint16_t len) {
return len >= 41 && gPB[ETH_TYPE_H_P] == ETHTYPE_ARP_H_V &&
gPB[ETH_TYPE_L_P] == ETHTYPE_ARP_L_V &&
memcmp(gPB + ETH_ARP_DST_IP_P, EtherCard::myip, IP_LEN) == 0;
}
static uint8_t eth_type_is_ip_and_my_ip(uint16_t len) {
return len >= 42 && gPB[ETH_TYPE_H_P] == ETHTYPE_IP_H_V &&
gPB[ETH_TYPE_L_P] == ETHTYPE_IP_L_V &&
gPB[IP_HEADER_LEN_VER_P] == 0x45 &&
(memcmp(gPB + IP_DST_P, EtherCard::myip, IP_LEN) == 0 //not my IP
|| (memcmp(gPB + IP_DST_P, EtherCard::broadcastip, IP_LEN) == 0) //not subnet broadcast
|| (memcmp(gPB + IP_DST_P, allOnes, IP_LEN) == 0)); //not global broadcasts
//!@todo Handle multicast
}
static void fill_ip_hdr_checksum() {
gPB[IP_CHECKSUM_P] = 0;
gPB[IP_CHECKSUM_P+1] = 0;
gPB[IP_FLAGS_P] = 0x40; // don't fragment
gPB[IP_FLAGS_P+1] = 0; // fragment offset
gPB[IP_TTL_P] = 64; // ttl
fill_checksum(IP_CHECKSUM_P, IP_P, IP_HEADER_LEN,0);
}
static void make_eth_ip() {
setMACs(gPB + ETH_SRC_MAC);
EtherCard::copyIp(gPB + IP_DST_P, gPB + IP_SRC_P);
EtherCard::copyIp(gPB + IP_SRC_P, EtherCard::myip);
fill_ip_hdr_checksum();
}
static void make_arp_answer_from_request() {
setMACs(gPB + ETH_SRC_MAC);
gPB[ETH_ARP_OPCODE_H_P] = ETH_ARP_OPCODE_REPLY_H_V;
gPB[ETH_ARP_OPCODE_L_P] = ETH_ARP_OPCODE_REPLY_L_V;
EtherCard::copyMac(gPB + ETH_ARP_DST_MAC_P, gPB + ETH_ARP_SRC_MAC_P);
EtherCard::copyMac(gPB + ETH_ARP_SRC_MAC_P, EtherCard::mymac);
EtherCard::copyIp(gPB + ETH_ARP_DST_IP_P, gPB + ETH_ARP_SRC_IP_P);
EtherCard::copyIp(gPB + ETH_ARP_SRC_IP_P, EtherCard::myip);
EtherCard::packetSend(42);
}
static void make_echo_reply_from_request(uint16_t len) {
make_eth_ip();
gPB[ICMP_TYPE_P] = ICMP_TYPE_ECHOREPLY_V;
if (gPB[ICMP_CHECKSUM_P] > (0xFF-0x08))
gPB[ICMP_CHECKSUM_P+1]++;
gPB[ICMP_CHECKSUM_P] += 0x08;
EtherCard::packetSend(len);
}
void EtherCard::makeUdpReply (const char *data,uint8_t datalen,uint16_t port) {
if (datalen>220)
datalen = 220;
gPB[IP_TOTLEN_H_P] = (IP_HEADER_LEN+UDP_HEADER_LEN+datalen) >>8;
gPB[IP_TOTLEN_L_P] = IP_HEADER_LEN+UDP_HEADER_LEN+datalen;
make_eth_ip();
gPB[UDP_DST_PORT_H_P] = gPB[UDP_SRC_PORT_H_P];
gPB[UDP_DST_PORT_L_P] = gPB[UDP_SRC_PORT_L_P];
gPB[UDP_SRC_PORT_H_P] = port>>8;
gPB[UDP_SRC_PORT_L_P] = port;
gPB[UDP_LEN_H_P] = (UDP_HEADER_LEN+datalen) >> 8;
gPB[UDP_LEN_L_P] = UDP_HEADER_LEN+datalen;
gPB[UDP_CHECKSUM_H_P] = 0;
gPB[UDP_CHECKSUM_L_P] = 0;
memcpy(gPB + UDP_DATA_P, data, datalen);
fill_checksum(UDP_CHECKSUM_H_P, IP_SRC_P, 16 + datalen,1);
packetSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen);
}
void EtherCard::udpPrepare (uint16_t sport, const uint8_t *dip, uint16_t dport) {
if(is_lan(myip, dip)) { // this works because both dns mac and destinations mac are stored in same variable - destmacaddr
setMACandIPs(destmacaddr, dip); // at different times. The program could have separate variable for dns mac, then here should be
} else { // checked if dip is dns ip and separately if dip is hisip and then use correct mac.
setMACandIPs(gwmacaddr, dip);
}
// see http://tldp.org/HOWTO/Multicast-HOWTO-2.html
// multicast or broadcast address, https://github.com/njh/EtherCard/issues/59
if ((dip[0] & 0xF0) == 0xE0 || *((unsigned long*) dip) == 0xFFFFFFFF || !memcmp(broadcastip,dip,IP_LEN))
EtherCard::copyMac(gPB + ETH_DST_MAC, allOnes);
gPB[ETH_TYPE_H_P] = ETHTYPE_IP_H_V;
gPB[ETH_TYPE_L_P] = ETHTYPE_IP_L_V;
memcpy_P(gPB + IP_P,iphdr,sizeof iphdr);
gPB[IP_TOTLEN_H_P] = 0;
gPB[IP_PROTO_P] = IP_PROTO_UDP_V;
gPB[UDP_DST_PORT_H_P] = (dport>>8);
gPB[UDP_DST_PORT_L_P] = dport;
gPB[UDP_SRC_PORT_H_P] = (sport>>8);
gPB[UDP_SRC_PORT_L_P] = sport;
gPB[UDP_LEN_H_P] = 0;
gPB[UDP_CHECKSUM_H_P] = 0;
gPB[UDP_CHECKSUM_L_P] = 0;
}
void EtherCard::udpTransmit (uint16_t datalen) {
gPB[IP_TOTLEN_H_P] = (IP_HEADER_LEN+UDP_HEADER_LEN+datalen) >> 8;
gPB[IP_TOTLEN_L_P] = IP_HEADER_LEN+UDP_HEADER_LEN+datalen;
fill_ip_hdr_checksum();
gPB[UDP_LEN_H_P] = (UDP_HEADER_LEN+datalen) >>8;
gPB[UDP_LEN_L_P] = UDP_HEADER_LEN+datalen;
fill_checksum(UDP_CHECKSUM_H_P, IP_SRC_P, 16 + datalen,1);
packetSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen);
}
void EtherCard::sendUdp (const char *data, uint8_t datalen, uint16_t sport,
const uint8_t *dip, uint16_t dport) {
udpPrepare(sport, dip, dport);
if (datalen>220)
datalen = 220;
memcpy(gPB + UDP_DATA_P, data, datalen);
udpTransmit(datalen);
}
// make a arp request
static void client_arp_whohas(uint8_t *ip_we_search) {
setMACs(allOnes);
gPB[ETH_TYPE_H_P] = ETHTYPE_ARP_H_V;
gPB[ETH_TYPE_L_P] = ETHTYPE_ARP_L_V;
memcpy_P(gPB + ETH_ARP_P, arpreqhdr, sizeof arpreqhdr);
memset(gPB + ETH_ARP_DST_MAC_P, 0, ETH_LEN);
EtherCard::copyMac(gPB + ETH_ARP_SRC_MAC_P, EtherCard::mymac);
EtherCard::copyIp(gPB + ETH_ARP_DST_IP_P, ip_we_search);
EtherCard::copyIp(gPB + ETH_ARP_SRC_IP_P, EtherCard::myip);
EtherCard::packetSend(42);
}
uint8_t EtherCard::clientWaitingGw () {
return !(waitgwmac & WGW_HAVE_GW_MAC);
}
static uint8_t client_store_mac(uint8_t *source_ip, uint8_t *mac) {
if (memcmp(gPB + ETH_ARP_SRC_IP_P, source_ip, IP_LEN) != 0)
return 0;
EtherCard::copyMac(mac, gPB + ETH_ARP_SRC_MAC_P);
return 1;
}
// static void client_gw_arp_refresh() {
// if (waitgwmac & WGW_HAVE_GW_MAC)
// waitgwmac |= WGW_REFRESHING;
// }
void EtherCard::setGwIp (const uint8_t *gwipaddr) {
delaycnt = 0; //request gateway ARP lookup
waitgwmac = WGW_INITIAL_ARP; // causes an arp request in the packet loop
copyIp(gwip, gwipaddr);
}
void EtherCard::updateBroadcastAddress()
{
for(uint8_t i=0; i<IP_LEN; i++)
broadcastip[i] = myip[i] | ~netmask[i];
}
uint16_t EtherCard::packetLoop (uint16_t plen) {
if (plen==0) {
//Check every 65536 (no-packet) cycles whether we need to retry ARP request for gateway
if ((waitgwmac & WGW_INITIAL_ARP || waitgwmac & WGW_REFRESHING) &&
delaycnt==0 && isLinkUp()) {
client_arp_whohas(gwip);
waitgwmac |= WGW_ACCEPT_ARP_REPLY;
}
delaycnt++;
//!@todo this is trying to find mac only once. Need some timeout to make another call if first one doesn't succeed.
if(is_lan(myip, dnsip) && !has_dns_mac && !waiting_for_dns_mac) {
client_arp_whohas(dnsip);
waiting_for_dns_mac = true;
}
//!@todo this is trying to find mac only once. Need some timeout to make another call if first one doesn't succeed.
if(is_lan(myip, hisip) && !has_dest_mac && !waiting_for_dest_mac) {
client_arp_whohas(hisip);
waiting_for_dest_mac = true;
}
return 0;
}
if (eth_type_is_arp_and_my_ip(plen))
{ //Service ARP request
if (gPB[ETH_ARP_OPCODE_L_P]==ETH_ARP_OPCODE_REQ_L_V)
make_arp_answer_from_request();
if (waitgwmac & WGW_ACCEPT_ARP_REPLY && (gPB[ETH_ARP_OPCODE_L_P]==ETH_ARP_OPCODE_REPLY_L_V) && client_store_mac(gwip, gwmacaddr))
waitgwmac = WGW_HAVE_GW_MAC;
if (!has_dns_mac && waiting_for_dns_mac && client_store_mac(dnsip, destmacaddr)) {
has_dns_mac = true;
waiting_for_dns_mac = false;
}
if (!has_dest_mac && waiting_for_dest_mac && client_store_mac(hisip, destmacaddr)) {
has_dest_mac = true;
waiting_for_dest_mac = false;
}
return 0;
}
if (eth_type_is_ip_and_my_ip(plen)==0)
{ //Not IP so ignoring
//!@todo Add other protocols (and make each optional at compile time)
return 0;
}
#if ETHERCARD_ICMP
if (gPB[IP_PROTO_P]==IP_PROTO_ICMP_V && gPB[ICMP_TYPE_P]==ICMP_TYPE_ECHOREQUEST_V)
{ //Service ICMP echo request (ping)
make_echo_reply_from_request(plen);
return 0;
}
#endif
if (plen < UDP_DATA_P || gPB[IP_PROTO_P]!=IP_PROTO_UDP_V )
return 0; //from here on we are only interested in UDP-packets
return plen + UDP_DATA_P; // Offset of UDP payload.
}
void EtherCard::copyIp (uint8_t *dst, const uint8_t *src) {
memcpy(dst, src, IP_LEN);
}
void EtherCard::copyMac (uint8_t *dst, const uint8_t *src) {
memcpy(dst, src, ETH_LEN);
}