You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

729 lines
17 KiB

/*
Code to test wiznet WIZ810MJ module
See:
<http://code.rancidbacon.com/LearningAboutArduinoWIZ810MJ>
Current features:
* Initial W5100 driver port:
+ new-style network configuration
+ socket creation/listening/closing
+ Sending/Receiving okay
+ example "echo" server (no longer)
+ example "web server" with LED flash.
* Terrible hacked-together code
Author:
follower@rancidbacon.com
License:
LGPL
Version:
20071106-0005+
*/
#include <WIZ810MJ.h>
/* --------- SPI --------- */
// Define SPI-related pins
#define PIN_DATA_OUT 11 // MOSI (Master Out / Slave In)
#define PIN_DATA_IN 12 // MISO (Master In / Slave Out)
#define PIN_SPI_CLOCK 13 // SCK (Serial Clock)
#define PIN_SLAVE_SELECT 10 // SS (Slave Select)
class SpiConfiguration {
public:
static void begin(void);
};
void SpiConfiguration::begin(void) {
/*
Configure pins and registers required for SPI communication.
*/
// Configure I/O pins
pinMode(PIN_DATA_OUT, OUTPUT);
pinMode(PIN_DATA_IN, INPUT);
pinMode(PIN_SPI_CLOCK, OUTPUT);
pinMode(PIN_SLAVE_SELECT, OUTPUT);
digitalWrite(PIN_SLAVE_SELECT, HIGH); // Disable slave
/*
Configure SPI Control Register (SPCR) (All values initially 0)
Bit Description
7 SPI Interrupt Enable -- disable (SPIE --> 0)
6 SPI Enable -- enable (SPE --> 1)
5 Data Order -- MSB 1st (DORD --> 0) (Slave specific)
4 Master/Slave Select -- master (MSTR --> 1)
3 Clock Polarity -- (CPOL --> 0) (Slave specific) ("Mode")
2 Clock Phase -- (CPHA --> 0) (Slave specific)
1 SPI Clock Rate Select 1 -- } (SPR1 --> 0)
0 SPI Clock Rate Select 0 -- } fOSC/4 (SPR0 --> 0) ("Fastest" but see SPI2X in SPSR)
*/
SPCR = (1<<SPE)| (1<<MSTR);
// Clear previous data and status (TODO: Determine if necessary/better way.)
// (Based on Playground SPI example.)
byte dummy;
dummy = SPSR;
dummy = SPDR;
delay(10);
}
SpiConfiguration SPI = SpiConfiguration();
/* ----------------------- */
/* ------ WIZ810MJ ------ */
class Wiz810MjDevice {
public:
Wiz810MjDevice(int resetPin);
void setIp(byte b0, byte b1, byte b2, byte b3);
void setMask(byte b0, byte b1, byte b2, byte b3);
void setGateway(byte b0, byte b1, byte b2, byte b3);
void setMac(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5);
private:
void _init(void);
byte * _packBuffer(byte b0, byte b1, byte b2, byte b3);
byte * _packBuffer(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5);
int _resetPin;
byte _scratchBuffer[6]; // Can we make this static?
};
Wiz810MjDevice::Wiz810MjDevice(int resetPin) {
// TODO: We should really allow the chip-select pin to be set here?
// Or require that it's defined. (Currently in the library file 'types.h'.)
_resetPin = resetPin;
_init();
}
void Wiz810MjDevice::_init(void) {
/*
Initialise the WIZ810MJ module and driver.
*/
/*
Initialise the W5100 chip
(Originally I thought it was possible for the chip
to function without a hardware reset but it
seems not to be the case.)
*/
pinMode(_resetPin, OUTPUT);
// We rely on the time between function calls to
// be long enough for the chip to recognise the
// reset.
digitalWrite(_resetPin, HIGH);
digitalWrite(_resetPin, LOW); // reset
digitalWrite(_resetPin, HIGH);
// Chip initialisation by driver
// Might be redundant following the above reset,
// as this performs a software reset.
iinchip_init();
// Initialise driver
// (This is required to configure some variables used
// internally by the driver--even if the default chip
// configuration is used.)
sysinit(0x55, 0x55);
}
byte * Wiz810MjDevice::_packBuffer(byte b0, byte b1, byte b2, byte b3) {
/*
*/
return _packBuffer(b0, b1, b2, b3, 0, 0); // Adds two bytes of padding
}
byte * Wiz810MjDevice::_packBuffer(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5) {
/*
*/
_scratchBuffer[0] = b0;
_scratchBuffer[1] = b1;
_scratchBuffer[2] = b2;
_scratchBuffer[3] = b3;
_scratchBuffer[4] = b4;
_scratchBuffer[5] = b5;
return _scratchBuffer;
}
void Wiz810MjDevice::setIp(byte b0, byte b1, byte b2, byte b3) {
/*
*/
setSIPR(_packBuffer(b0, b1, b2, b3));
}
void Wiz810MjDevice::setMask(byte b0, byte b1, byte b2, byte b3) {
/*
*/
setSUBR(_packBuffer(b0, b1, b2, b3));
}
void Wiz810MjDevice::setGateway(byte b0, byte b1, byte b2, byte b3) {
/*
*/
setGAR(_packBuffer(b0, b1, b2, b3));
}
void Wiz810MjDevice::setMac(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5) {
/*
*/
setSHAR(_packBuffer(b0, b1, b2, b3, b4, b5));
}
/* ----------------------- */
/* ---- NetworkConnection ---- */
// TODO: Make this 'NetworkServerConnection'? Or just 'ServerConnection'?
// TODO: Pull one-line methods into class definition to allow inlining?
class NetworkConnection { // Essentially a Socket wrapper
public:
NetworkConnection(uint16_t port); // TODO: Add UDP, TCP choice?
int listen();
int isConnected();
int available();
int read();
void print(uint8_t);
void close();
private:
SOCKET _socket;
static const int _MAX_SOCKETS = MAX_SOCK_NUM; // TODO: Use this.
static int _nextSocket;
};
int NetworkConnection::_nextSocket = 0;
NetworkConnection::NetworkConnection(uint16_t port) {
/*
*/
_socket = _nextSocket; // TODO: Do properly (& max)
_nextSocket++;
socket(_socket, Sn_MR_TCP, port, 0);
}
int NetworkConnection::listen() { // TODO: Make private or protected?
/*
*/
return !!::listen(_socket); // TODO: Use C++ namespaces for the driver functions?
}
int NetworkConnection::available() {
/*
Functionality matches 'Serial.available()'.
Note: If the socket is not connected then this will return 0,
but it is intended that 'isConnected()' would be checked first.
Returns:
"The number of bytes available to read ... or 0 if none are available.
If any data has come in, [...].available() will be greater than 0."
*/
// TODO: Do we want to check for 'isConnected' as well, or not?
// We have to, I guess, for valid behaviour with 'getSn_*'.
if (!isConnected()) {
return 0;
}
return getSn_RX_RSR(_socket);
}
#define NO_READ_DATA_AVAILABLE -1
int NetworkConnection::read() {
/*
Functionality matches 'Serial.read()'.
Returns:
"an int, the first byte of incoming serial data available
(or -1 if no data is available)."
Note: I thought 'recv' blocked until data was available,
but it currently seems not to block after all.
However, because I'm seeking to match the 'Serial.read'
behaviour we don't actually want to block anyway,
so we'll ignore it for the moment and handle things
ourself for now.
*/
uint8_t theByte;
if (!available()) {
return NO_READ_DATA_AVAILABLE;
}
recv(_socket, &theByte, 1);
return theByte;
}
int NetworkConnection::isConnected() {
/*
*/
// TODO: If we want the 'Network*' classes to be generic we
// would need to handle this differently:
return (getSn_SR(_socket) == SOCK_ESTABLISHED);
}
void NetworkConnection::print(uint8_t b) {
/*
*/
if (isConnected()) {
send(_socket, &b, 1);
} else {
// Just drop it if we're not connected.
}
}
void NetworkConnection::close() {
/*
*/
// TODO: Determine if we need/want the disconnect (see pg 26 W5100)
::close(_socket);
disconnect(_socket);
}
/* ----------------------- */
/* -- NetworkInterface -- */
#define HANDLE_BAD_ERROR() while (1) {};
// TODO?: Do we want to have a "default" socket accessible by 'Network.read()' etc?
class NetworkInterface {
public:
NetworkInterface(Wiz810MjDevice& networkDevice);
NetworkConnection listen(uint16_t port);
Wiz810MjDevice& device; // TODO: Make this a generic "network device" interface
};
NetworkInterface::NetworkInterface(Wiz810MjDevice& networkDevice) : device (networkDevice) {
/*
Note: The "weirdness" in this function declaration is a "initalization list",
i.e. this bit:
) : device (networkDevice) {
it is needed to give the 'device' Reference a value supplied to the method
rather than having a new 'Wiz810MjDevice' instance created.
*/
// This may not be the best place to do all this, but it'll do for now:
device.setMac(0x02,0xDE,0xAD,0xBE,0xEF,0x00);
device.setIp(169,254,254,169); // A local-link IP -- NOTE: This is out of spec.
device.setMask(255,255,0,0); // Matches local-link IP /16
device.setGateway(0,0,0,0); // We can't even guess this, so just skip it.
};
NetworkConnection NetworkInterface::listen(uint16_t port) {
/*
NOTE: For reasons I don't understand, this FAILS TO WORK if port >255.
The port that gets opened is ~(port mod 255) but if you limit
port to be uint8_t for example you don't get an error until
the port number is bigger than 'long'.
TODO: Track and fix this for port numbers > 255.
Returns a NetworkConnection listening on the specified TCP port.
*/
NetworkConnection connection = NetworkConnection(port);
// TODO: How to best handle errors?
// There's two major categories I can think of currently:
// 1) A socket wasn't available to listen in the first place
// 2) The listen attempt failed for some reason
// Using 'HANDLE_BAD_ERROR()' (currently an infinite loop) is
// not ideal but should prevent the above errors slipping by unnoticed.
if (!connection.listen()) {
HANDLE_BAD_ERROR();
}
return connection;
}
// NetworkInterface Network = NetworkInterface(...); // TODO: Make this a global
/* ----------------------- */
/* ----- EchoServer ------ */
#define ECHO_CONNECT_WAIT 0
#define ECHO_CONNECTED 1
#define ECHO_CLOSE 2
#define ECHO_HALT 3
class EchoServer {
public:
EchoServer(int port);
void next(); // TODO: Return something useful?
private:
NetworkConnection _connection; // TODO: Make public?
int _state;
};
EchoServer::EchoServer(int port) : _connection (NetworkConnection(port)) {
/*
*/
_state = ECHO_CONNECT_WAIT;
_connection.listen(); // TODO: We should be using Network.listen(...) here and in initialisation list.
}
void EchoServer::next() {
/*
*/
// TODO: Use better state machine implementation?
if (_state == ECHO_CONNECT_WAIT) {
if (_connection.isConnected()) {
_state = ECHO_CONNECTED;
} else {
// Keep waiting
}
} else if (_state == ECHO_CONNECTED) {
if (_connection.available()) {
_connection.print(_connection.read());
} else if (!_connection.isConnected()) {
// Data finished and client disconnected
_state == ECHO_CLOSE;
}
} else if (_state == ECHO_CLOSE) {
_connection.close();
_state = ECHO_HALT;
} else if (_state == ECHO_HALT) {
// Do nothing
} else {
// Unknown state, do nothing.
}
}
/* ----------------------- */
// #define PIN_RESET 9 // WIZnet module /RESET
#define PIN_RESET 8 // WIZnet module /RESET
SOCKET testSocket;
byte ip[6];
#define PIN_LED 2
void setup () {
Serial.begin(9600);
Serial.println("Setup enter...");
Serial.println("Start W5100 configuration...");
SPI.begin();
Wiz810MjDevice WIZ810MJ = Wiz810MjDevice(PIN_RESET);
NetworkInterface Network = NetworkInterface(WIZ810MJ);
Network.device.setIp(192,168,2,105);
Network.device.setMask(255,255,255,0);
Serial.println("End W5100 configuration...");
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, HIGH);
Serial.println("Setup exit...");
/**/
Serial.println("Test sockets...");
#if 0
NetworkConnection conn = Network.listen(7);
Serial.println("Waiting for connection...");
while (!conn.isConnected()) {
//Serial.print(!conn.isConnected());
delay(500);
}
Serial.println("Connected...");
while (conn.isConnected()) {
if (conn.available()) {
// Serial.print(conn.read(), BYTE);
conn.print(conn.read());
}
}
Serial.println("");
conn.close();
#endif
EchoServer server = EchoServer(7);
EchoServer server1 = EchoServer(8);
while (1) {
server.next();
server1.next();
}
Serial.println("End test and dummy loop");
while (1) {}
/**/
}
void sendBanner(uint8_t *buffer, int ledState) { // {Socket targetSocket, ) {
//strcpy((char *) buffer, "Content-Type: text/plain\n\nfoo!\n");
strcpy((char *) buffer, "HTTP/1.1 200 OK\nContent-Type: text/html\n\n<html><body bgcolor='#000000'>foo!</body></html>\n");
if (ledState) {
buffer[63] = 'F';
} else {
buffer[65] = 'F';
}
Serial.print("send result: ");
Serial.println(send(testSocket, buffer, strlen((char *)buffer)), DEC);
}
uint8_t readByte() {
uint8_t theByte;
recv(testSocket, &theByte, 1);
return theByte;
}
int readMatch(char *stringToMatch) {
/*
Routine to read and match bytes received.
(Essentially strcmp replacement without requiring a large receive buffer.)
NOTE: Failed matches drop all bytes read so far. (i.e. you can't check for
a bunch of possible matches from the same starting position).
TODO: Fix this.
Note: This blocks if there isn't enough data in the rx buffer.
*/
while (getSn_RX_RSR(testSocket) < strlen(stringToMatch)) {
// block
// TODO: Return error or wait or "too short"?
}
// TODO: Do fancy string-matching techniques to avoid reading the whole string
// on non-matches. :-)
for (int currCharIdx = 0; currCharIdx < strlen(stringToMatch); currCharIdx++) {
if (readByte() != stringToMatch[currCharIdx]) {
return 0;
}
}
return 1;
}
void loop() {
Serial.println("Test W5100 socket...");
Serial.print("Create socket result: ");
Serial.println(socket(testSocket, Sn_MR_TCP, 80, 0), DEC);
Serial.print("Socket status: ");
Serial.println(IINCHIP_READ(Sn_SR(testSocket)), HEX);
if (IINCHIP_READ(Sn_SR(testSocket)) == SOCK_CLOSED) {
Serial.println("Socket still closed, waiting...");
while (IINCHIP_READ(Sn_SR(testSocket)) == SOCK_CLOSED) {
//pass
}
}
Serial.print("Listen on socket result: ");
Serial.println(listen(testSocket), DEC);
Serial.println("Waiting for connection...");
while (getSn_SR(testSocket) == SOCK_LISTEN) {
delay(500);
}
Serial.println(getSn_SR(testSocket),HEX);
getSn_DIPR(testSocket, ip);
Serial.print("Destination IP read (last digit): ");
Serial.println(ip[3], DEC);
// TODO: Avoid buffer overflows...
#define MAX_TX_BUFFER_SIZE 200
uint8_t bytesToSend[MAX_TX_BUFFER_SIZE];
//sendBanner(bytesReceived);
int dataLength = 0;
#define STATE_GET 0
#define STATE_READ 5
#define STATE_END -2
#define STATE_ERR -1
int state = STATE_GET;
int ledState = 1;
unsigned char theByte;
while (getSn_SR(testSocket) == SOCK_ESTABLISHED) {
while (getSn_RX_RSR(testSocket) > 0) {
if (state == STATE_GET) {
if (readMatch("GET /")) {
state = STATE_READ;
} else {
state = STATE_ERR;
}
}
if (state == STATE_READ) {
theByte = readByte();
if (theByte == '0') {
digitalWrite(PIN_LED, LOW);
ledState = 0;
delay(100);
} else if (theByte == '1') {
digitalWrite(PIN_LED, HIGH);
ledState = 1;
delay(100);
} else {
// It's not a valid byte.
state = STATE_END;
}
} else {
state = STATE_ERR;
}
if ((state == STATE_ERR) || (state == STATE_END)) {
Serial.println("");
break;
}
/*
dataLength = getSn_RX_RSR(testSocket);
if (dataLength >= MAX_RX_BUFFER_SIZE) { // TODO: blah, blah...
dataLength = MAX_RX_BUFFER_SIZE-1;
}
// Serial.print("dataLength: "); Serial.println(dataLength, HEX);
//Serial.print("recv result: ");
//Serial.println(recv(testSocket, bytesReceived, dataLength), DEC); // NOTE: Throws away unread portion? No?
recv(testSocket, bytesReceived, dataLength); // TODO: Return length?
bytesReceived[dataLength]=0x00;
Serial.print((char *)bytesReceived);
//Serial.print("send result: ");
//Serial.println(send(testSocket, bytesReceived, dataLength), DEC);
*/
}
sendBanner(bytesToSend, ledState);
break;
}
close(testSocket);
disconnect(testSocket);
Serial.println("End test W5100 socket...");
}