1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 18:33:44 +02:00

Compare commits

...

51 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
Harald Barth
58afea135c Committing a SHA 2021-11-30 18:57:58 +00:00
Harald Barth
9018ec9757 DISABLE_EEPROM explanation 2021-11-30 19:56:09 +01:00
Harald Barth
aa734b25e4 Merge branch 'disable-eeprom' into master 2021-11-30 19:45:33 +01:00
Harald Barth
67e48d34f4 do not include config.h direct 2021-11-30 19:40:31 +01:00
Florian Becker
419822ef06 Committing a SHA 2021-11-30 10:12:49 +00:00
Florian Becker
d4ee215ae6 fix typo (#194)
replace "manu" with "many"
2021-11-30 10:12:29 +00:00
Neil McKechnie
011a5c517b Merge branch 'master' into neil-network 2021-11-24 13:08:11 +00:00
Neil McKechnie
f05b3d1730 Committing a SHA 2021-11-24 13:02:35 +00:00
Neil McKechnie
a2f8a8ec91 Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX 2021-11-24 13:00:31 +00:00
Neil McKechnie
746350b846 Update version to 3.2.0 rc5 2021-11-24 12:54:02 +00:00
Neil McKechnie
97f3450621 Simplify OLED driver initialisation.
Simplify the initialisation in the SSD1306Ascii driver, by removing some of the complex structures that were inherited from the library on which it is based.  This should also allow it to compile on the ESP32 platform.
2021-11-24 12:53:03 +00:00
Asbelos
2be3e276f9 Committing a SHA 2021-11-24 12:02:40 +00:00
Asbelos
88fa5ad37c VPIN in RMFT2::doSignal 2021-11-24 12:02:16 +00:00
Harald Barth
a4f746c00c Warn for broken configs 2021-11-22 00:41:47 +01:00
Neil McKechnie
106fb612dc Committing a SHA 2021-11-21 17:56:29 +00:00
Neil McKechnie
53113e981d Update IO_PCF8574.h
Correct handling of input in immediate mode,
2021-11-21 17:56:06 +00:00
Neil McKechnie
d7fd9e1538 Committing a SHA 2021-11-15 16:16:55 +00:00
Neil McKechnie
197228c3b0 Update version to 3.2.0 rc4 2021-11-15 16:13:54 +00:00
Neil McKechnie
620dcbf925 Update myHal.cpp_example.txt
Update examples
2021-11-15 14:58:12 +00:00
Neil McKechnie
82f121c8ef Some comment changes 2021-11-15 14:45:03 +00:00
Neil McKechnie
6c98f90151 Reduce I2C interrupt time
Reduce the time spent with interrupts disabled in I2CManager response code by enabling interrupts after the state machine has finished.
Also, some comment changes.
2021-11-15 14:30:27 +00:00
Neil McKechnie
c90ea0c6df Improve validation of parameters to non-HAL digital calls.
When testing CS in minimal HAL mode but with mySetup.h and myAutomation.h files present, I experienced freezing of the arduino because the standard pinMode, digitalWrite etc don't validate the pin number passed to them.  So I've added checks on the pin number to the configure, write and read functions in the minimal HAL.
2021-11-15 13:25:11 +00:00
Neil McKechnie
d08f14be3b Rename user module mySetup.cpp to myHal.cpp, and function mySetup() to halSetup() within it. 2021-11-15 12:50:02 +00:00
Neil McKechnie
b8ee7f034b Merge branch 'master' into neil-network 2021-11-12 00:18:09 +00:00
Neil McKechnie
fb97ba11de Committing a SHA 2021-11-12 00:09:59 +00:00
Neil McKechnie
ee5db61349 Update version.h to 3.2.0 rc3. 2021-11-12 00:06:29 +00:00
Neil McKechnie
b384d6c14d Move call to mySetup into IODevice::begin().
Ensure that HAL devices are created before use by moving the call to mySetup into IODevice::begin().  The need for this became evident when it was noted that RMFT (EX-RAIL) interacts with HAL devices during its initialisation, by enabling pull-ups on digital inputs.
Any
2021-11-12 00:05:16 +00:00
Neil McKechnie
58fe81bf06 Update EthernetInterface.h
Remove spurious character.
2021-11-11 23:59:50 +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
Harald Barth
1807189183 make it possible to disable EEPROM code to save flash space 2021-11-08 02:07:21 +01:00
Harald Barth
0e78cf6e55 Committing a SHA 2021-11-07 23:20:28 +00:00
Harald Barth
6c75563779 handle negative pins 2021-11-08 00:19:23 +01:00
Harald Barth
89dcafb2d7 Committing a SHA 2021-11-07 16:04:52 +00:00
Harald Barth
37904b5fa6 make rc1 2021-11-07 17:03:28 +01:00
Harald Barth
fbca15d2a7 Merge branch 'master-ex-rail' 2021-11-07 17:01:16 +01:00
Harald Barth
177c8c0367 Merge branch 'EX-RAIL-sensormod' 2021-11-07 16:17:22 +01:00
Harald Barth
7ea3faf177 Merge branch 'EX-RAIL' 2021-11-07 16:14:38 +01:00
Harald Barth
d3381c6b2d Committing a SHA 2021-11-07 15:05:58 +00:00
Harald Barth
8853b23f88 uopdate version.h 2021-11-07 16:04:49 +01:00
Harald Barth
a16f6c8749 configure pins correct even when HAL not used 2021-11-06 22:12:32 +01:00
Harald Barth
e3d771a24d set default pullup in EXRAIL begin code 2021-11-06 21:57:06 +01:00
Harald Barth
79ce71c2f9 Committing a SHA 2021-10-31 21:18:17 +00:00
Harald Barth
e3cbaf5f24 unknown locos should have speed forward 2021-10-31 22:17:51 +01:00
Harald Barth
250c372f5c Committing a SHA 2021-10-29 20:30:40 +00:00
Harald Barth
a9c31eb1ae YFROBOT: One more motor board with L298P 2021-10-29 22:30:01 +02:00
44 changed files with 5542 additions and 424 deletions

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@ config.h
.vscode/extensions.json
mySetup.h
mySetup.cpp
myHal.cpp
myAutomation.h
myFilter.cpp
myAutomation.h

View File

@@ -88,16 +88,9 @@ void setup()
// Start RMFT (ignored if no automnation)
RMFT::begin();
// Link to and call mySetup() function (if defined in the build in mySetup.cpp).
// The contents will depend on the user's system hardware configuration.
// The mySetup.cpp file is a standard C++ module so has access to all of the DCC++EX APIs.
extern __attribute__((weak)) void mySetup();
if (mySetup) {
mySetup();
}
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
// This can be used to create turnouts, outputs, sensors etc. throught the normal text commands.
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
#if __has_include ( "mySetup.h")
#define SETUP(cmd) serialParser.parse(F(cmd))
#include "mySetup.h"

View File

@@ -20,7 +20,9 @@
#include "DIAG.h"
#include "DCC.h"
#include "DCCWaveform.h"
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#endif
#include "GITHUB_SHA.h"
#include "version.h"
#include "FSH.h"
@@ -56,9 +58,11 @@ void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriv
// Initialise HAL layer before reading EEprom.
IODevice::begin();
#ifndef DISABLE_EEPROM
// Load stuff from EEprom
(void)EEPROM; // tell compiler not to warn this is unused
EEStore::init();
#endif
DCCWaveform::begin(mainDriver,progDriver);
}

View File

@@ -56,7 +56,9 @@ const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_DCC = 6436;
const int16_t HASH_KEYWORD_SLOW = -17209;
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
#ifndef DISABLE_EEPROM
const int16_t HASH_KEYWORD_EEPROM = -7168;
#endif
const int16_t HASH_KEYWORD_LIMIT = 27413;
const int16_t HASH_KEYWORD_MAX = 16244;
const int16_t HASH_KEYWORD_MIN = 15978;
@@ -129,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)
@@ -278,7 +281,9 @@ void DCCEXParser::parse(const FSH * cmd) {
void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
{
#ifndef DISABLE_EEPROM
(void)EEPROM; // tell compiler not to warn this is unused
#endif
if (Diag::CMD)
DIAG(F("PARSING:%s"), com);
int16_t p[MAX_COMMAND_PARAMS];
@@ -540,6 +545,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
// TODO Send stats of speed reminders table
return;
#ifndef DISABLE_EEPROM
case 'E': // STORE EPROM <E>
EEStore::store();
StringFormatter::send(stream, F("<e %d %d %d>\n"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
@@ -549,7 +555,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
EEStore::clear();
StringFormatter::send(stream, F("<O>\n"));
return;
#endif
case ' ': // < >
StringFormatter::send(stream, F("\n"));
return;
@@ -864,11 +870,13 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
delay(50); // wait for the prescaller time to expire
break; // and <X> if we didnt restart
}
#ifndef DISABLE_EEPROM
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
if (params >= 2)
EEStore::dump(p[1]);
return true;
#endif
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);

View File

@@ -18,6 +18,9 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "defines.h"
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#include "DIAG.h"
@@ -103,3 +106,4 @@ void EEStore::dump(int num) {
EEStore *EEStore::eeStore = NULL;
int EEStore::eeAddress = 0;
#endif

View File

@@ -17,6 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef DISABLE_EEPROM
#ifndef EEStore_h
#define EEStore_h
@@ -52,3 +53,4 @@ struct EEStore{
};
#endif
#endif // DISABLE_EEPROM

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
}
}

View File

@@ -23,7 +23,7 @@
#ifndef EthernetInterface_h
#define EthernetInterface_h
#include "defines.h")
#include "defines.h"
#include "DCCEXParser.h"
#include <Arduino.h>
#include <avr/pgmspace.h>

View File

@@ -1 +1 @@
#define GITHUB_SHA "50fcbc0"
#define GITHUB_SHA "9018ec9"

View File

@@ -107,11 +107,16 @@
* the loop() function is called, and may be adequate under some circumstances.
* The advantage of NOT using interrupts is that the impact of I2C upon the DCC waveform (when accurate timing mode isn't in use)
* becomes almost zero.
* This mechanism is under evaluation and should not be relied upon as yet.
*
*/
// Uncomment following line to enable Wire library instead of native I2C drivers
//#define I2C_USE_WIRE
// Uncomment following line to disable the use of interrupts by the native I2C drivers.
//#define I2C_NO_INTERRUPTS
// Default to use interrupts within the native I2C drivers.
#ifndef I2C_NO_INTERRUPTS
#define I2C_USE_INTERRUPTS
#endif

View File

@@ -129,6 +129,10 @@ uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t r
/***************************************************************************
* checkForTimeout() function, called from isBusy() and wait() to cancel
* requests that are taking too long to complete.
* This function doesn't fully work as intended so is not currently called.
* Instead we check for an I2C hang-up and report an error from
* I2CRB::wait(), but we aren't able to recover from the hang-up. Such faults
* may be caused by an I2C wire short for example.
***************************************************************************/
void I2CManagerClass::checkForTimeout() {
unsigned long currentMicros = micros();
@@ -163,7 +167,10 @@ void I2CManagerClass::loop() {
#if !defined(I2C_USE_INTERRUPTS)
handleInterrupt();
#endif
checkForTimeout();
// Timeout is now reported in I2CRB::wait(), not here.
// I've left the code, commented out, as a reminder to look at this again
// in the future.
//checkForTimeout();
}
/***************************************************************************
@@ -175,6 +182,9 @@ void I2CManagerClass::handleInterrupt() {
// Update hardware state machine
I2C_handleInterrupt();
// Enable interrupts to minimise effect on other interrupt code
interrupts();
// Check if current request has completed. If there's a current request
// and state isn't active then state contains the completion status of the request.
if (state != I2C_STATE_ACTIVE && currentRequest != NULL) {

View File

@@ -28,6 +28,10 @@
#define USE_FAST_IO
#endif
// Link to halSetup function. If not defined, the function reference will be NULL.
extern __attribute__((weak)) void halSetup();
extern __attribute__((weak)) void mySetup(); // Deprecated function name, output warning if it's declared
//==================================================================================================================
// Static methods
//------------------------------------------------------------------------------------------------------------------
@@ -57,6 +61,16 @@ void IODevice::begin() {
dev->_begin();
}
_initPhase = false;
// Check for presence of deprecated mySetup() function, and output warning.
if (mySetup)
DIAG(F("WARNING: mySetup() function should be renamed to halSetup()"));
// Call user's halSetup() function (if defined in the build in myHal.cpp).
// The contents will depend on the user's system hardware configuration.
// The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs.
if (halSetup)
halSetup();
}
// Overarching static loop() method for the IODevice subsystem. Works through the
@@ -148,6 +162,33 @@ void IODevice::_display() {
bool IODevice::configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
IODevice *dev = findDevice(vpin);
if (dev) return dev->_configure(vpin, configType, paramCount, params);
#ifdef DIAG_IO
DIAG(F("IODevice::configure(): Vpin ID %d not found!"), (int)vpin);
#endif
return false;
}
// Read value from virtual pin.
int IODevice::read(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (dev->owns(vpin))
return dev->_read(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin);
#endif
return false;
}
// Read analogue value from virtual pin.
int IODevice::readAnalogue(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (dev->owns(vpin))
return dev->_readAnalogue(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
#endif
return false;
}
@@ -160,7 +201,7 @@ void IODevice::write(VPIN vpin, int value) {
return;
}
#ifdef DIAG_IO
//DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin);
DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin);
#endif
}
@@ -179,7 +220,7 @@ void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t para
return;
}
#ifdef DIAG_IO
//DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin);
DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin);
#endif
}
@@ -258,38 +299,22 @@ bool IODevice::owns(VPIN id) {
return (id >= _firstVpin && id < _firstVpin + _nPins);
}
// Read value from virtual pin.
int IODevice::read(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (dev->owns(vpin))
return dev->_read(vpin);
}
#ifdef DIAG_IO
//DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin);
#endif
return false;
}
// Read analogue value from virtual pin.
int IODevice::readAnalogue(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (dev->owns(vpin))
return dev->_readAnalogue(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
#endif
return false;
}
#else // !defined(IO_NO_HAL)
// Minimal implementations of public HAL interface, to support Arduino pin I/O and nothing more.
void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); }
bool IODevice::configure(VPIN, ConfigTypeEnum, int, int []) { return true; }
bool IODevice::configure(VPIN pin, ConfigTypeEnum configType, int nParams, int p[]) {
if (configType!=CONFIGURE_INPUT || nParams!=1 || pin >= NUM_DIGITAL_PINS) return false;
#ifdef DIAG_IO
DIAG(F("Arduino _configurePullup Pin:%d Val:%d"), pin, p[0]);
#endif
pinMode(pin, p[0] ? INPUT_PULLUP : INPUT);
return true;
}
void IODevice::write(VPIN vpin, int value) {
if (vpin >= NUM_DIGITAL_PINS) return;
digitalWrite(vpin, value);
pinMode(vpin, OUTPUT);
}
@@ -297,7 +322,7 @@ void IODevice::writeAnalogue(VPIN, int, uint8_t, uint16_t) {}
bool IODevice::isBusy(VPIN) { return false; }
bool IODevice::hasCallback(VPIN) { return false; }
int IODevice::read(VPIN vpin) {
pinMode(vpin, INPUT_PULLUP);
if (vpin >= NUM_DIGITAL_PINS) return 0;
return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1)
}
int IODevice::readAnalogue(VPIN vpin) {
@@ -434,7 +459,7 @@ int ArduinoPins::_readAnalogue(VPIN vpin) {
interrupts();
#ifdef DIAG_IO
//DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
#endif
return value;
}

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

View File

@@ -75,7 +75,7 @@ private:
if (immediate) {
uint8_t buffer[1];
I2CManager.read(_I2CAddress, buffer, 1);
_portInputState = ((uint16_t)buffer) & 0xff;
_portInputState = buffer[0];
} else {
requestBlock.wait(); // Wait for preceding operation to complete
// Issue new request to read GPIO register
@@ -86,7 +86,7 @@ private:
// This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK)
_portInputState = ((uint16_t)inputBuffer[0]) & 0xff;
_portInputState = inputBuffer[0];
else
_portInputState = 0xff;
}

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

View File

@@ -82,5 +82,9 @@
#define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \
new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
// YFROBOT Motor Shield (V3.1)
#define YFROBOT_MOTOR_SHIELD F("YFROBOT_MOTOR_SHIELD"), \
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
#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

View File

@@ -82,7 +82,9 @@ the state of any outputs being monitored or controlled by a separate interface o
**********************************************************************/
#include "Outputs.h"
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#endif
#include "StringFormatter.h"
#include "IODevice.h"
@@ -102,10 +104,11 @@ void Output::activate(uint16_t s){
data.active = s; // if s>0, set status to active, else inactive
// set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW)
IODevice::write(data.pin, s ^ data.invert);
#ifndef DISABLE_EEPROM
// Update EEPROM if output has been stored.
if(EEStore::eeStore->data.nOutputs > 0 && num > 0)
EEPROM.put(num, data.oStatus);
#endif
}
///////////////////////////////////////////////////////////////////////////////
@@ -141,7 +144,7 @@ bool Output::remove(uint16_t n){
///////////////////////////////////////////////////////////////////////////////
// Static function to load configuration and state of all Outputs from EEPROM
#ifndef DISABLE_EEPROM
void Output::load(){
struct OutputData data;
Output *tt;
@@ -176,6 +179,7 @@ void Output::store(){
}
}
#endif
///////////////////////////////////////////////////////////////////////////////
// Static function to create an Output object

View File

@@ -48,8 +48,10 @@ public:
bool isActive();
static Output* get(uint16_t);
static bool remove(uint16_t);
#ifndef DISABLE_EEPROM
static void load();
static void store();
#endif
static Output *create(uint16_t, VPIN, int, int=0);
static Output *firstOutput;
struct OutputData data;

View File

@@ -17,7 +17,7 @@ Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital
* Control of all cab functions F0-F28 and F29-F68
* Main Track: Write configuration variable bytes and set/clear specific configuration variable (CV) bits (aka Programming on Main or POM)
* Programming Track: Same as the main track with the addition of reading configuration variable bytes
* And manu more custom features. see [What's new in CommandStation-EX?](#whats-new-in-commandstation-ex)
* And many more custom features. see [What's new in CommandStation-EX?](#whats-new-in-commandstation-ex)
# Whats in this Repository?

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

@@ -66,6 +66,16 @@ byte RMFT2::flags[MAX_FLAGS];
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
switch (opcode) {
case OPCODE_AT:
case OPCODE_AFTER:
case OPCODE_IF:
case OPCODE_IFNOT:
int16_t pin = (int16_t)GET_OPERAND(0);
if (pin<0) pin = -pin;
IODevice::configureInput((VPIN)pin,true);
}
if (opcode==OPCODE_SIGNAL) {
VPIN red=GET_OPERAND(0);
VPIN amber=GET_OPERAND(1);
@@ -710,10 +720,10 @@ void RMFT2::kill(const FSH * reason, int operand) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) return;
if (opcode!=OPCODE_SIGNAL) continue;
byte redpin=GET_OPERAND(0);
VPIN redpin=GET_OPERAND(0);
if (redpin!=id)continue;
byte amberpin=GET_OPERAND(1);
byte greenpin=GET_OPERAND(2);
VPIN amberpin=GET_OPERAND(1);
VPIN greenpin=GET_OPERAND(2);
// If amberpin is zero, synthesise amber from red+green
IODevice::write(redpin,red || (amber && (amberpin==0)));
if (amberpin) IODevice::write(amberpin,amber);

View File

@@ -89,28 +89,93 @@ const uint8_t FLASH SSD1306AsciiWire::blankPixels[30] =
{0x40, // First byte specifies data mode
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
//==============================================================================
// this section is based on https://github.com/adafruit/Adafruit_SSD1306
/** Initialization commands for a 128x32 or 128x64 SSD1306 oled display. */
const uint8_t FLASH SSD1306AsciiWire::Adafruit128xXXinit[] = {
// Init sequence for Adafruit 128x32/64 OLED module
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64 (initially)
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
SSD1306_SETSTARTLINE | 0x0, // line #0
SSD1306_CHARGEPUMP, 0x14, // internal vcc
SSD1306_MEMORYMODE, 0x02, // page mode
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
SSD1306_COMSCANDEC, // column scan direction reversed
SSD1306_SETCOMPINS, 0X12, // set COM pins
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
SSD1306_DISPLAYALLON_RESUME,
SSD1306_NORMALDISPLAY,
SSD1306_DISPLAYON
};
//------------------------------------------------------------------------------
// This section is based on https://github.com/stanleyhuangyc/MultiLCD
/** Initialization commands for a 128x64 SH1106 oled display. */
const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0X80, // set osc division
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
SSD1306_SETDISPLAYOFFSET, 0X00, // set display offset
SSD1306_SETSTARTPAGE | 0X0, // set page address
SSD1306_SETSTARTLINE | 0x0, // set start line
SH1106_SET_PUMP_MODE, SH1106_PUMP_ON, // set charge pump enable
SSD1306_SEGREMAP | 0X1, // set segment remap
SSD1306_COMSCANDEC, // Com scan direction
SSD1306_SETCOMPINS, 0X12, // set COM pins
SSD1306_SETCONTRAST, 0x80, // 128
SSD1306_SETPRECHARGE, 0X1F, // set pre-charge period
SSD1306_SETVCOMDETECT, 0x40, // set vcomh
SH1106_SET_PUMP_VOLTAGE | 0X2, // 8.0 volts
SSD1306_NORMALDISPLAY, // normal / reverse
SSD1306_DISPLAYON
};
//==============================================================================
// SSD1306AsciiWire Method Definitions
//------------------------------------------------------------------------------
// Constructor
SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) {
m_displayWidth = width;
m_displayHeight = height;
// Set size in characters in base class
lcdRows = height / 8;
lcdCols = width / 6;
m_col = 0;
m_row = 0;
m_colOffset = 0;
I2CManager.begin();
I2CManager.setClock(400000L); // Set max supported I2C speed
for (byte address = 0x3c; address <= 0x3d; address++) {
if (I2CManager.exists(address)) {
m_i2cAddr = address;
if (m_displayWidth==132 && m_displayHeight==64) {
// SH1106 display. This uses 128x64 centered within a 132x64 OLED.
m_colOffset = 2;
I2CManager.write_P(address, SH1106_132x64init, sizeof(SH1106_132x64init));
} else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) {
// SSD1306 128x64 or 128x32
I2CManager.write_P(address, Adafruit128xXXinit, sizeof(Adafruit128xXXinit));
if (m_displayHeight == 32)
I2CManager.write(address, 5, 0, // Set command mode
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap
} else {
DIAG(F("OLED configuration option not recognised"));
return;
}
// Device found
DIAG(F("%dx%d OLED display configured on I2C:x%x"), width, height, address);
if (width == 132)
begin(&SH1106_132x64, address);
else if (height == 32)
begin(&Adafruit128x32, address);
else
begin(&Adafruit128x64, address);
// Set singleton address
lcdDisplay = this;
clear();
@@ -132,23 +197,6 @@ void SSD1306AsciiWire::clearNative() {
}
}
// Initialise device
void SSD1306AsciiWire::begin(const DevType* dev, uint8_t i2cAddr) {
m_i2cAddr = i2cAddr;
m_col = 0;
m_row = 0;
const uint8_t* table = (const uint8_t*)GETFLASHW(&dev->initcmds);
uint8_t size = GETFLASH(&dev->initSize);
m_displayWidth = GETFLASH(&dev->lcdWidth);
m_displayHeight = GETFLASH(&dev->lcdHeight);
m_colOffset = GETFLASH(&dev->colOffset);
I2CManager.write_P(m_i2cAddr, table, size);
if (m_displayHeight == 32)
I2CManager.write(m_i2cAddr, 5, 0, // Set command mode
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap
}
//------------------------------------------------------------------------------
// Set cursor position (by text line)
@@ -209,82 +257,6 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
return 1;
}
//==============================================================================
// this section is based on https://github.com/adafruit/Adafruit_SSD1306
/** Initialization commands for a 128x32 or 128x64 SSD1306 oled display. */
const uint8_t FLASH SSD1306AsciiWire::Adafruit128xXXinit[] = {
// Init sequence for Adafruit 128x32/64 OLED module
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64 (initially)
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
SSD1306_SETSTARTLINE | 0x0, // line #0
SSD1306_CHARGEPUMP, 0x14, // internal vcc
SSD1306_MEMORYMODE, 0x02, // page mode
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
SSD1306_COMSCANDEC, // column scan direction reversed
SSD1306_SETCOMPINS, 0X12, // set COM pins
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
SSD1306_DISPLAYALLON_RESUME,
SSD1306_NORMALDISPLAY,
SSD1306_DISPLAYON
};
/** Initialize a 128x32 SSD1306 oled display. */
const DevType FLASH SSD1306AsciiWire::Adafruit128x32 = {
Adafruit128xXXinit,
sizeof(Adafruit128xXXinit),
128,
32,
0
};
/** Initialize a 128x64 oled display. */
const DevType FLASH SSD1306AsciiWire::Adafruit128x64 = {
Adafruit128xXXinit,
sizeof(Adafruit128xXXinit),
128,
64,
0
};
//------------------------------------------------------------------------------
// This section is based on https://github.com/stanleyhuangyc/MultiLCD
/** Initialization commands for a 128x64 SH1106 oled display. */
const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0X80, // set osc division
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
SSD1306_SETDISPLAYOFFSET, 0X00, // set display offset
SSD1306_SETSTARTPAGE | 0X0, // set page address
SSD1306_SETSTARTLINE | 0x0, // set start line
SH1106_SET_PUMP_MODE, SH1106_PUMP_ON, // set charge pump enable
SSD1306_SEGREMAP | 0X1, // set segment remap
SSD1306_COMSCANDEC, // Com scan direction
SSD1306_SETCOMPINS, 0X12, // set COM pins
SSD1306_SETCONTRAST, 0x80, // 128
SSD1306_SETPRECHARGE, 0X1F, // set pre-charge period
SSD1306_SETVCOMDETECT, 0x40, // set vcomh
SH1106_SET_PUMP_VOLTAGE | 0X2, // 8.0 volts
SSD1306_NORMALDISPLAY, // normal / reverse
SSD1306_DISPLAYON
};
/** Initialize a 132x64 oled SH1106 display. */
const DevType FLASH SSD1306AsciiWire::SH1106_132x64 = {
SH1106_132x64init,
sizeof(SH1106_132x64init),
128,
64,
2 // SH1106 is a 132x64 controller but most OLEDs are only attached
// to columns 2-129.
};
//------------------------------------------------------------------------------

View File

@@ -32,21 +32,6 @@
//#define NOLOWERCASE
//------------------------------------------------------------------------------
// Device initialization structure.
struct DevType {
/* Pointer to initialization command bytes. */
const uint8_t* initcmds;
/* Number of initialization bytes */
const uint8_t initSize;
/* Width of the display in pixels */
const uint8_t lcdWidth;
/** Height of the display in pixels. */
const uint8_t lcdHeight;
/* Column offset RAM to display. Used to pick start column of SH1106. */
const uint8_t colOffset;
};
// Constructor
class SSD1306AsciiWire : public LCDDisplay {
public:
@@ -55,25 +40,17 @@ class SSD1306AsciiWire : public LCDDisplay {
SSD1306AsciiWire(int width, int height);
// Initialize the display controller.
void begin(const DevType* dev, uint8_t i2cAddr);
void begin(uint8_t i2cAddr);
// Clear the display and set the cursor to (0, 0).
void clearNative() override;
// Set cursor to start of specified text line
void setRowNative(byte line) override;
// Initialize the display controller.
void init(const DevType* dev);
// Write one character to OLED
size_t writeNative(uint8_t c) override;
// Display characteristics / initialisation
static const DevType FLASH Adafruit128x32;
static const DevType FLASH Adafruit128x64;
static const DevType FLASH SH1106_132x64;
bool isBusy() override { return requestBlock.isBusy(); }
private:

View File

@@ -68,7 +68,9 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
#include "StringFormatter.h"
#include "Sensors.h"
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#endif
#include "IODevice.h"
@@ -275,7 +277,7 @@ bool Sensor::remove(int n){
}
///////////////////////////////////////////////////////////////////////////////
#ifndef DISABLE_EEPROM
void Sensor::load(){
struct SensorData data;
Sensor *tt;
@@ -303,7 +305,7 @@ void Sensor::store(){
EEStore::eeStore->data.nSensors++;
}
}
#endif
///////////////////////////////////////////////////////////////////////////////
Sensor *Sensor::firstSensor=NULL;
@@ -314,4 +316,4 @@ unsigned long Sensor::lastReadCycle=0;
Sensor *Sensor::firstPollSensor = NULL;
Sensor *Sensor::lastSensor = NULL;
bool Sensor::inputChangeCallbackRegistered = false;
#endif
#endif

View File

@@ -68,8 +68,10 @@ public:
Sensor *nextSensor;
void setState(int state);
#ifndef DISABLE_EEPROM
static void load();
static void store();
#endif
static Sensor *create(int id, VPIN vpin, int pullUp);
static Sensor* get(int id);
static bool remove(int id);

View File

@@ -22,7 +22,9 @@
#include "defines.h" // includes config.h
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#endif
#include "StringFormatter.h"
#include "RMFT2.h"
#include "Turnouts.h"
@@ -42,6 +44,8 @@
* Public static data
*/
/* static */ int Turnout::turnoutlistHash = 0;
/* static */ unsigned long Turnout::_lastLoopEntry = 0;
/*
* Protected static functions
@@ -67,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) {
@@ -139,26 +185,28 @@
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
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
// is always zero for LCN turnouts.
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0)
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
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
// Load all turnout objects
/* static */ void Turnout::load() {
for (uint16_t i=0; i<EEStore::eeStore->data.nTurnouts; i++) {
@@ -212,7 +260,7 @@
#endif
return tt;
}
#endif
// Display, on the specified stream, the current state of the turnout (1=thrown or 0=closed).
/* static */ void Turnout::printState(uint16_t id, Print *stream) {
Turnout *tt = get(id);
@@ -277,6 +325,7 @@
// Load a Servo turnout definition from EEPROM. The common Turnout data has already been read at this point.
Turnout *ServoTurnout::load(struct TurnoutData *turnoutData) {
#ifndef DISABLE_EEPROM
ServoTurnoutData servoTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), servoTurnoutData);
@@ -286,6 +335,10 @@
Turnout *tt = ServoTurnout::create(turnoutData->id, servoTurnoutData.vpin, servoTurnoutData.thrownPosition,
servoTurnoutData.closedPosition, servoTurnoutData.profile, turnoutData->closed);
return tt;
#else
(void)turnoutData;
return NULL;
#endif
}
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed
@@ -301,13 +354,20 @@
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() {
#ifndef DISABLE_EEPROM
// Write turnout definition and current position to EEPROM
// First write common servo data, then
// write the servo-specific data
@@ -315,6 +375,7 @@
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _servoTurnoutData);
EEStore::advance(sizeof(_servoTurnoutData));
#endif
}
/*************************************************************************************
@@ -367,6 +428,7 @@
// Load a DCC turnout definition from EEPROM. The common Turnout data has already been read at this point.
/* static */ Turnout *DCCTurnout::load(struct TurnoutData *turnoutData) {
#ifndef DISABLE_EEPROM
DCCTurnoutData dccTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), dccTurnoutData);
@@ -376,6 +438,10 @@
DCCTurnout *tt = new DCCTurnout(turnoutData->id, dccTurnoutData.address, dccTurnoutData.subAddress);
return tt;
#else
(void)turnoutData;
return NULL;
#endif
}
void DCCTurnout::print(Print *stream) {
@@ -396,6 +462,7 @@
}
void DCCTurnout::save() {
#ifndef DISABLE_EEPROM
// Write turnout definition and current position to EEPROM
// First write common servo data, then
// write the servo-specific data
@@ -403,6 +470,7 @@
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _dccTurnoutData);
EEStore::advance(sizeof(_dccTurnoutData));
#endif
}
@@ -441,6 +509,7 @@
// Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point.
/* static */ Turnout *VpinTurnout::load(struct TurnoutData *turnoutData) {
#ifndef DISABLE_EEPROM
VpinTurnoutData vpinTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), vpinTurnoutData);
@@ -450,6 +519,10 @@
VpinTurnout *tt = new VpinTurnout(turnoutData->id, vpinTurnoutData.vpin, turnoutData->closed);
return tt;
#else
(void)turnoutData;
return NULL;
#endif
}
// Report 1 for thrown, 0 for closed.
@@ -458,13 +531,24 @@
!_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
// First write common servo data, then
// write the servo-specific data
@@ -472,6 +556,7 @@
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _vpinTurnoutData);
EEStore::advance(sizeof(_vpinTurnoutData));
#endif
}

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,13 +165,16 @@ public:
inline static Turnout *first() { return _firstTurnout; }
static void loop();
#ifndef DISABLE_EEPROM
// Load all turnout definitions.
static void load();
// Load one turnout definition
static Turnout *loadTurnout();
// Save all turnout definitions
static void store();
#endif
static void printAll(Print *stream) {
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
tt->printState(stream);
@@ -201,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;
};
@@ -265,6 +279,7 @@ public:
protected:
bool setClosedInternal(bool close) override;
void save() override;
bool isPending() override;
};

View File

@@ -128,6 +128,16 @@ The configuration file for DCC-EX Command Station
// Define scroll mode as 0, 1 or 2
#define SCROLLMODE 1
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE EEPROM
//
// If you do not need the EEPROM at all, you can disable all the code that saves
// data in the EEPROM. You might want to do that if you are in a Arduino UNO
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
// to do that. Of course, then none of the EEPROM related commands works.
//
// #define DISABLE_EEPROM
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213

View File

@@ -39,19 +39,29 @@
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO))
#define BIG_RAM
#endif
#if ENABLE_WIFI && defined(BIG_RAM)
#define WIFI_ON true
#ifndef WIFI_CHANNEL
#define WIFI_CHANNEL 1
#endif
#if ENABLE_WIFI
#if defined(BIG_RAM)
#define WIFI_ON true
#ifndef WIFI_CHANNEL
#define WIFI_CHANNEL 1
#endif
#else
#warning You have defined that you want WIFI but your hardware has not enough memory to do that, so WIFI DISABLED
#define WIFI_ON false
#endif
#else
#define WIFI_ON false
#define WIFI_ON false
#endif
#if ENABLE_ETHERNET && defined(BIG_RAM)
#define ETHERNET_ON true
#if ENABLE_ETHERNET
#if defined(BIG_RAM)
#define ETHERNET_ON true
#else
#warning You have defined that you want ETHERNET but your hardware has not enough memory to do that, so ETHERNET DISABLED
#define ETHERNET_ON false
#endif
#else
#define ETHERNET_ON false
#define ETHERNET_ON false
#endif
#if WIFI_ON && ETHERNET_ON
@@ -65,8 +75,12 @@
//
#define WIFI_SERIAL_LINK_SPEED 115200
#if __has_include ( "myAutomation.h") && defined(BIG_RAM)
#define RMFT_ACTIVE
#if __has_include ( "myAutomation.h")
#if defined(BIG_RAM) || defined(DISABLE_EEPROM)
#define RMFT_ACTIVE
#else
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
#endif
#endif
#endif
#endif

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

161
myHal.cpp_example.txt Normal file
View File

@@ -0,0 +1,161 @@
// Sample myHal.cpp file.
//
// To use this file, copy it to myHal.cpp and uncomment the directives and/or
// edit them to satisfy your requirements. If you only want to use up to
// two MCP23017 GPIO Expander modules and/or up to two PCA9685 Servo modules,
// then you don't need this file as DCC++EX configures these for free!
// Note that if the file has a .cpp extension it WILL be compiled into the build
// and the halSetup() function WILL be invoked.
//
// To prevent this, temporarily rename the file to myHal.txt or similar.
//
// Include devices you need.
#include "IODevice.h"
#include "IO_HCSR04.h" // Ultrasonic range sensor
#include "IO_VL53L0X.h" // Laser time-of-flight sensor
#include "IO_DFPlayer.h" // MP3 sound player
//==========================================================================
// The function halSetup() is invoked from CS if it exists within the build.
// The setup calls are included between the open and close braces "{ ... }".
// Comments (lines preceded by "//") are optional.
//==========================================================================
void halSetup() {
//=======================================================================
// The following directive defines a PCA9685 PWM Servo driver module.
//=======================================================================
// The parameters are:
// First Vpin=100
// Number of VPINs=16 (numbered 100-115)
// I2C address of module=0x40
//PCA9685::create(100, 16, 0x40);
//=======================================================================
// The following directive defines an MCP23017 16-port I2C GPIO Extender module.
//=======================================================================
// The parameters are:
// First Vpin=196
// Number of VPINs=16 (numbered 196-211)
// I2C address of module=0x22
//MCP23017::create(196, 16, 0x22);
// Alternative form, which allows the INT pin of the module to request a scan
// by pulling Arduino pin 40 to ground. Means that the I2C isn't being polled
// all the time, only when a change takes place. Multiple modules' INT pins
// may be connected to the same Arduino pin.
//MCP23017::create(196, 16, 0x22, 40);
//=======================================================================
// The following directive defines an MCP23008 8-port I2C GPIO Extender module.
//=======================================================================
// The parameters are:
// First Vpin=300
// Number of VPINs=8 (numbered 300-307)
// I2C address of module=0x22
//MCP23008::create(300, 8, 0x22);
//=======================================================================
// The following directive defines a PCF8574 8-port I2C GPIO Extender module.
//=======================================================================
// The parameters are:
// First Vpin=200
// Number of VPINs=8 (numbered 200-207)
// I2C address of module=0x23
//PCF8574::create(200, 8, 0x23);
// Alternative form using INT pin (see above)
//PCF8574::create(200, 8, 0x23, 40);
//=======================================================================
// The following directive defines an HCSR04 ultrasonic ranging module.
//=======================================================================
// The parameters are:
// Vpin=2000 (only one VPIN per directive)
// Number of VPINs=1
// Arduino pin connected to TRIG=30
// Arduino pin connected to ECHO=31
// Minimum trigger range=20cm (VPIN goes to 1 when <20cm)
// Maximum trigger range=25cm (VPIN goes to 0 when >25cm)
// Note: Multiple devices can be configured by using a different ECHO pin
// for each one. The TRIG pin can be shared between multiple devices.
// Be aware that the 'ping' of one device may be received by another
// device and position them accordingly!
//HCSR04::create(2000, 30, 31, 20, 25);
//HCSR04::create(2001, 30, 32, 20, 25);
//=======================================================================
// The following directive defines a single VL53L0X Time-of-Flight range sensor.
//=======================================================================
// The parameters are:
// VPIN=5000
// Number of VPINs=1
// I2C address=0x29 (default for this chip)
// Minimum trigger range=200mm (VPIN goes to 1 when <20cm)
// Maximum trigger range=250mm (VPIN goes to 0 when >25cm)
//VL53L0X::create(5000, 1, 0x29, 200, 250);
// For multiple VL53L0X modules, add another parameter which is a VPIN connected to the
// module's XSHUT pin. This allows the modules to be configured, at start,
// with distinct I2C addresses. In this case, the address 0x29 is only used during
// initialisation to configure each device in turn with the desired unique I2C address.
// The examples below have the modules' XSHUT pins connected to the first two pins of
// the first MCP23017 module (164 and 165), but Arduino pins may be used instead.
// The first module here is given I2C address 0x30 and the second is 0x31.
//VL53L0X::create(5000, 1, 0x30, 200, 250, 164);
//VL53L0X::create(5001, 1, 0x31, 200, 250, 165);
//=======================================================================
// Play mp3 files from a Micro-SD card, using a DFPlayer MP3 Module.
//=======================================================================
// Parameters:
// 10000 = first VPIN allocated.
// 10 = number of VPINs allocated.
// Serial1 = name of serial port (usually Serial1 or Serial2).
// With these parameters, up to 10 files may be played on pins 10000-10009.
// Play is started from EX-RAIL with SET(10000) for first mp3 file, SET(10001)
// for second file, etc. Play may also be initiated by writing an analogue
// value to the first pin, e.g. SERVO(10000,23,0) will play the 23rd mp3 file.
// SERVO(10000,23,30) will do the same thing, as well as setting the volume to
// 30 (maximum value).
// Play is stopped by RESET(10000) (or any other allocated VPIN).
// Volume may also be set by writing an analogue value to the second pin for the player,
// e.g. SERVO(10001,30,0) sets volume to maximum (30).
// The EX-RAIL script may check for completion of play by calling WAITFOR(pin), which will only proceed to the
// following line when the player is no longer busy.
// E.g.
// SEQUENCE(1)
// AT(164) // Wait for sensor attached to pin 164 to activate
// SET(10003) // Play fourth MP3 file
// LCD(4, "Playing") // Display message on LCD/OLED
// WAITFOR(10003) // Wait for playing to finish
// LCD(4, " ") // Clear LCD/OLED line
// FOLLOW(1) // Go back to start
// DFPlayer::create(10000, 10, Serial1);
}
#endif

View File

@@ -1,214 +0,0 @@
// Sample mySetup.cpp file.
//
// To use this file, copy it to mySetup.cpp and uncomment the directives and/or
// edit them to satisfy your requirements.
// Note that if the file has a .cpp extension it WILL be compiled into the build
// and the mySetup() function WILL be invoked.
//
// To prevent this, temporarily rename it to mySetup.txt or similar.
//
#include "IODevice.h"
#include "Turnouts.h"
#include "Sensors.h"
#include "IO_HCSR04.h"
#include "IO_VL53L0X.h"
// The #if directive prevent compile errors for Uno and Nano by excluding the
// HAL directives from the build.
#if !defined(IO_NO_HAL)
// Examples of statically defined HAL directives (alternative to the create() call).
// These have to be outside of the mySetup() function.
//=======================================================================
// The following directive defines a PCA9685 PWM Servo driver module.
//=======================================================================
// The parameters are:
// First Vpin=100
// Number of VPINs=16 (numbered 100-115)
// I2C address of module=0x40
//PCA9685 pwmModule1(100, 16, 0x40);
//=======================================================================
// The following directive defines an MCP23017 16-port I2C GPIO Extender module.
//=======================================================================
// The parameters are:
// First Vpin=196
// Number of VPINs=16 (numbered 196-211)
// I2C address of module=0x22
//MCP23017 gpioModule2(196, 16, 0x22);
// Alternative form, which allows the INT pin of the module to request a scan
// by pulling Arduino pin 40 to ground. Means that the I2C isn't being polled
// all the time, only when a change takes place. Multiple modules' INT pins
// may be connected to the same Arduino pin.
//MCP23017 gpioModule2(196, 16, 0x22, 40);
//=======================================================================
// The following directive defines an MCP23008 8-port I2C GPIO Extender module.
//=======================================================================
// The parameters are:
// First Vpin=300
// Number of VPINs=8 (numbered 300-307)
// I2C address of module=0x22
//MCP23008 gpioModule3(300, 8, 0x22);
//=======================================================================
// The following directive defines a PCF8574 8-port I2C GPIO Extender module.
//=======================================================================
// The parameters are:
// First Vpin=200
// Number of VPINs=8 (numbered 200-207)
// I2C address of module=0x23
//PCF8574 gpioModule4(200, 8, 0x23);
// Alternative form using INT pin (see above)
//PCF8574 gpioModule4(200, 8, 0x23, 40);
//=======================================================================
// The following directive defines an HCSR04 ultrasonic ranging module.
//=======================================================================
// The parameters are:
// Vpin=2000 (only one VPIN per directive)
// Number of VPINs=1
// Arduino pin connected to TRIG=30
// Arduino pin connected to ECHO=31
// Minimum trigger range=20cm (VPIN goes to 1 when <20cm)
// Maximum trigger range=25cm (VPIN goes to 0 when >25cm)
// Note: Multiple devices can be configured by using a different ECHO pin
// for each one. The TRIG pin can be shared between multiple devices.
// Be aware that the 'ping' of one device may be received by another
// device and position them accordingly!
//HCSR04 sonarModule1(2000, 30, 31, 20, 25);
//HCSR04 sonarModule2(2001, 30, 32, 20, 25);
//=======================================================================
// The following directive defines a single VL53L0X Time-of-Flight range sensor.
//=======================================================================
// The parameters are:
// VPIN=5000
// Number of VPINs=1
// I2C address=0x29 (default for this chip)
// Minimum trigger range=200mm (VPIN goes to 1 when <20cm)
// Maximum trigger range=250mm (VPIN goes to 0 when >25cm)
//VL53L0X tofModule1(5000, 1, 0x29, 200, 250);
// For multiple VL53L0X modules, add another parameter which is a VPIN connected to the
// module's XSHUT pin. This allows the modules to be configured, at start,
// with distinct I2C addresses. In this case, the address 0x29 is only used during
// initialisation to configure each device in turn with the desired unique I2C address.
// The examples below have the modules' XSHUT pins connected to the first two pins of
// the first MCP23017 module (164 and 165), but Arduino pins may be used instead.
// The first module here is given I2C address 0x30 and the second is 0x31.
//VL53L0X tofModule1(5000, 1, 0x30, 200, 250, 164);
//VL53L0X tofModule2(5001, 1, 0x31, 200, 250, 165);
//=======================================================================
// The function mySetup() is invoked from CS if it exists within the build.
// It is called just before mysetup.h is executed, so things set up within here can be
// referenced by commands in mySetup.h.
//=======================================================================
void mySetup() {
// Alternative way of creating a module driver, which has to be within the mySetup() function
// The other devices can also be created in this way. The parameter lists for the
// create() function are identical to the parameter lists for the declarations.
//MCP23017::create(196, 16, 0x22);
//=======================================================================
// Creating a Turnout
//=======================================================================
// Parameters: same as <T> command for Servo turnouts
// ID and VPIN are 100, sonar moves between positions 102 and 490 with slow profile.
// Profile may be Instant, Fast, Medium, Slow or Bounce.
//ServoTurnout::create(100, 100, 490, 102, PCA9685::Slow);
//=======================================================================
// DCC Accessory turnout
//=======================================================================
// Parameters: same as <T> command for DCC Accessory turnouts
// ID=3000
// Decoder address=23
// Decoder subaddress = 1
//DCCTurnout::create(3000, 23, 1);
//=======================================================================
// Creating a Sensor
//=======================================================================
// Parameters: As for the <S> command,
// id = 164,
// Vpin = 164 (configured above as pin 0 of an MCP23017)
// Pullup enable = 1 (enabled)
//Sensor::create(164, 164, 1);
//=======================================================================
// Way of creating lots of identical sensors in a range
//=======================================================================
//for (int i=165; i<180; i++)
// Sensor::create(i, i, 1);
//=======================================================================
// Play mp3 files from a Micro-SD card, using a DFPlayer MP3 Module.
//=======================================================================
// Parameters:
// 10000 = first VPIN allocated.
// 10 = number of VPINs allocated.
// Serial1 = name of serial port (usually Serial1 or Serial2).
// With these parameters, up to 10 files may be played on pins 10000-10009.
// Play is started from EX-RAIL with SET(10000) for first mp3 file, SET(10001)
// for second file, etc. Play may also be initiated by writing an analogue
// value to the first pin, e.g. SERVO(10000,23,0) will play the 23rd mp3 file.
// SERVO(10000,23,30) will do the same thing, as well as setting the volume to
// 30 (maximum value).
// Play is stopped by RESET(10000) (or any other allocated VPIN).
// Volume may also be set by writing an analogue value to the second pin for the player,
// e.g. SERVO(10001,30,0) sets volume to maximum (30).
// The EX-RAIL script may check for completion of play by calling WAITFOR(pin), which will only proceed to the
// following line when the player is no longer busy.
// E.g.
// SEQUENCE(1)
// AT(164) // Wait for sensor attached to pin 164 to activate
// SET(10003) // Play fourth MP3 file
// LCD(4, "Playing") // Display message on LCD/OLED
// WAITFOR(10003) // Wait for playing to finish
// LCD(4, " ") // Clear LCD/OLED line
// FOLLOW(1) // Go back to start
// DFPlayer::create(10000, 10, Serial1);
}
#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);
}

View File

@@ -3,9 +3,8 @@
#include "StringFormatter.h"
#define VERSION "3.1.7 draft"
// 3.1.7 Major functional and non-functional changes.
#define VERSION "3.2.0 rc5"
// 3.2.0 Major functional and non-functional changes.
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc).
// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
// Support for PCA9685 PWM (servo) control modules.
@@ -23,6 +22,7 @@
// from <a> command and <T> command.
// Increased use of display for showing loco decoder programming information.
// ...
// 3.1.7 Bugfix: Unknown locos should have speed forward
// 3.1.6 Make output ID two bytes and guess format/size of registered outputs found in EEPROM
// 3.1.5 Fix LCD corruption on power-up
// 3.1.4 Refactor OLED and LCD drivers and remove unused code