From 474ba24ce86f12d560db02a5dfd27977cde83209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mate=CC=8Cj=20Sychra?= Date: Wed, 21 Nov 2018 22:59:05 +0100 Subject: [PATCH 1/4] i2c_clock_stretching_fix --- cores/esp8266/core_esp8266_si2c.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cores/esp8266/core_esp8266_si2c.c b/cores/esp8266/core_esp8266_si2c.c index bb7b66ed0c..545b97c665 100644 --- a/cores/esp8266/core_esp8266_si2c.c +++ b/cores/esp8266/core_esp8266_si2c.c @@ -267,6 +267,7 @@ unsigned char twi_writeTo(unsigned char address, unsigned char * buf, unsigned i SCL_LOW(); twi_delay(twi_dcount); SCL_HIGH(); + unsigned int t=0; while(SCL_READ()==0 && (t++) Date: Sun, 9 Dec 2018 20:24:43 +0100 Subject: [PATCH 2/4] master/slave example for I2C --- .../Wire/examples/i2c-P2P-master/crc16.h | 214 ++++++++++++++ .../i2c-P2P-master/i2c-P2P-master.ino | 279 ++++++++++++++++++ libraries/Wire/examples/i2c-P2P-slave/crc16.h | 214 ++++++++++++++ .../examples/i2c-P2P-slave/i2c-P2P-slave.ino | 275 +++++++++++++++++ 4 files changed, 982 insertions(+) create mode 100644 libraries/Wire/examples/i2c-P2P-master/crc16.h create mode 100644 libraries/Wire/examples/i2c-P2P-master/i2c-P2P-master.ino create mode 100644 libraries/Wire/examples/i2c-P2P-slave/crc16.h create mode 100644 libraries/Wire/examples/i2c-P2P-slave/i2c-P2P-slave.ino diff --git a/libraries/Wire/examples/i2c-P2P-master/crc16.h b/libraries/Wire/examples/i2c-P2P-master/crc16.h new file mode 100644 index 0000000000..f745dacb8b --- /dev/null +++ b/libraries/Wire/examples/i2c-P2P-master/crc16.h @@ -0,0 +1,214 @@ +//------------------------------------------------------------------------------------- +// CRC16 support class +// Based on various examples found on the web +// Copyright (C) 2014 Vincenzo Mennella (see license.txt) +// History +// 0.1.0 31/05/2014: First public code release +// 0.1.1 17/12/2014: Minor revision and commented code +// 0.1.2 24/11/2018: Minor code style fixes (by @devyte and @suculent) +// +// License +// "MIT Open Source Software License": +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in the +// Software without restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//------------------------------------------------------------------------------------- +#ifndef CRC16_H +#define CRC16_H +#define LIBRARY_VERSION_CRC16_H "0.1.2" + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#elif defined(ARDUINO) +#include "WProgram.h" +#else +#include +#endif + +class Crc16 { +private: + //Crc parameters + uint16_t _msbMask; + uint16_t _mask; + uint16_t _xorIn; + uint16_t _xorOut; + uint16_t _polynomial; + bool _reflectIn; + bool _reflectOut; + //Crc value + uint16_t _crc; + uint8_t reflect(uint8_t data, uint8_t bits = 32); + +public: + inline Crc16() + { + //Default to XModem parameters + _reflectIn = false; + _reflectOut = false; + _polynomial = 0x1021; + _xorIn = 0x0000; + _xorOut = 0x0000; + _msbMask = 0x8000; + _mask = 0xFFFF; + _crc = _xorIn; + } + inline Crc16(bool reflectIn, bool reflectOut, uint16_t polynomial, uint16_t xorIn, uint16_t xorOut, uint16_t msbMask, uint16_t mask) + { + _reflectIn = reflectIn; + _reflectOut = reflectOut; + _polynomial = polynomial; + _xorIn = xorIn; + _xorOut = xorOut; + _msbMask = msbMask; + _mask = mask; + _crc = _xorIn; + } + inline void clearCrc(); + inline void updateCrc(uint8_t data); + inline uint16_t getCrc(); + inline unsigned int fastCrc(const uint8_t *data, uint8_t start, uint16_t length, bool reflectIn, bool reflectOut, uint16_t polynomial, uint16_t xorIn, uint16_t xorOut, uint16_t msbMask, uint16_t mask); + inline unsigned int XModemCrc(const uint8_t *data, const uint8_t start, const uint16_t length) + { + // XModem parameters: poly=0x1021 init=0x0000 refin=false refout=false xorout=0x0000 + return fastCrc(data, start, length, false, false, 0x1021, 0x0000, 0x0000, 0x8000, 0xffff); + } +}; + +//--------------------------------------------------- +// Initialize crc calculation +//--------------------------------------------------- +void Crc16::clearCrc() +{ + _crc = _xorIn; +} +//--------------------------------------------------- +// Update crc with new data +//--------------------------------------------------- +void Crc16::updateCrc(uint8_t data) +{ + if (_reflectIn != 0) { + data = (uint8_t) reflect(data, 8); + } + + int j = 0x80; + + while (j > 0) + { + uint16_t bit = (uint16_t)(_crc & _msbMask); + + _crc <<= 1; + + if ((data & j) != 0) + { + bit = (uint16_t)(bit ^ _msbMask); + } + + if (bit != 0) + { + _crc ^= _polynomial; + } + + j >>= 1; + } +} + +//--------------------------------------------------- +// Get final crc value +//--------------------------------------------------- +uint16_t Crc16::getCrc() +{ + if (_reflectOut != 0) { + _crc = (unsigned int)((reflect(_crc) ^ _xorOut) & _mask); + } + + return _crc; +} + +//--------------------------------------------------- +// Calculate generic crc code on data array +// Examples of crc 16: +// Kermit: width=16 poly=0x1021 init=0x0000 refin=true refout=true xorout=0x0000 check=0x2189 +// Modbus: width=16 poly=0x8005 init=0xffff refin=true refout=true xorout=0x0000 check=0x4b37 +// XModem: width=16 poly=0x1021 init=0x0000 refin=false refout=false xorout=0x0000 check=0x31c3 +// CCITT-False: width=16 poly=0x1021 init=0xffff refin=false refout=false xorout=0x0000 check=0x29b1 +//--------------------------------------------------- +unsigned int Crc16::fastCrc(const uint8_t *data, uint8_t start, uint16_t length, bool reflectIn, bool reflectOut, uint16_t polynomial, uint16_t xorIn, uint16_t xorOut, uint16_t msbMask, uint16_t mask) +{ + unsigned int crc = xorIn; + + int j; + uint8_t c; + unsigned int bit; + + if (length == 0) { + return crc; + } + + for (int i = start; i < (start + length); ++i) + { + c = data[i]; + + if (reflectIn != 0) + c = (uint8_t) reflect(c, 8); + + j = 0x80; + + while (j > 0) + { + bit = (unsigned int)(crc & msbMask); + crc <<= 1; + + if ((c & j) != 0) + { + bit = (unsigned int)(bit ^ msbMask); + } + + if (bit != 0) + { + crc ^= polynomial; + } + + j >>= 1; + } + } + + if (reflectOut != 0) { + crc = (unsigned int)((reflect(crc) ^ xorOut) & mask); + } + + return crc; +} + +//------------------------------------------------------- +// Reflects bit in a uint8_t +//------------------------------------------------------- +uint8_t Crc16::reflect(uint8_t data, uint8_t bits) +{ + unsigned long reflection = 0x00000000; + // Reflect the data about the center bit. + for (uint8_t bit = 0; bit < bits; bit++) + { + // If the LSB bit is set, set the reflection of it. + if ((data & 0x01) != 0) { + reflection |= (unsigned long)(1 << ((bits - 1) - bit)); + } + + data = (uint8_t)(data >> 1); + } + + return reflection; +} +#endif diff --git a/libraries/Wire/examples/i2c-P2P-master/i2c-P2P-master.ino b/libraries/Wire/examples/i2c-P2P-master/i2c-P2P-master.ino new file mode 100644 index 0000000000..260b5999ce --- /dev/null +++ b/libraries/Wire/examples/i2c-P2P-master/i2c-P2P-master.ino @@ -0,0 +1,279 @@ +/* + ESP8266 I2C master-slave communication, requires Arduno Core with I2C Slave Support (2.5.0+) + + Expects two ESP8266 devices with three pins connected: GND, SDA and SCL. This is master. + Will send "MESA" messages to predefined slave address and then expect "PONG" response, + or request to retransfer if message was misunderstood (e.g. CRC check failed). + Message can be up to 26 bytes long (plus wrapper). + + 09-12-2018: initial drop by Matej Sychra (github.com/suculent) +*/ + +#include +#include "crc16.h" + +#define SDA_PIN 4 // D1 +#define SCL_PIN 5 // D2 +#define LED_PIN 12 // D4 + +const int16_t I2C_MASTER = 0x42; +const int16_t I2C_SLAVE = 0x08; +int16_t slave_address = I2C_SLAVE; + +// Keep this structure in sync with the i2c-P2P-slave example. Should be in shared header. +struct MessageData { + uint8_t sequence; + uint16_t crc16; + uint8_t data[26]; + uint8_t terminator; +}; + +MessageData msgdata; + +Crc16 crc; +byte sequence = 0; + +bool expectPong = false; +unsigned long nextPing = millis() + 1000; + +// analytics +uint16_t errorCount = 0; +uint16_t byteCount = 0; + +// forward declarations +uint16_t calculateCRC16(const uint8_t *data, size_t length); +void sendMessage(const int seq, const String &msg); +MessageData encodeMessage(const String &bytes); +void receiveEvent(const size_t howMany); +bool validateMessage(const char* bytes, MessageData &tmp); + +// + +// should be in shared header +uint16_t calculateCRC16(uint8_t *data, size_t length) { + crc.clearCrc(); + uint16_t value = (uint16_t)crc.XModemCrc(data, 0, length); + Serial.print("crc = 0x"); + Serial.println(value, HEX); + return value; +} + +// should be in shared header +MessageData encodeMessage(const String &instring) { + + MessageData msgdata; + + msgdata.terminator = '.'; + + size_t datasize = sizeof(msgdata.data); + Serial.print("\nEncoding data of size: "); + Serial.println(datasize); + + // Generate new data set for the struct + for (size_t i = 0; i < datasize; ++i) { + if (instring.length() >= i) { + msgdata.data[i] = instring[i]; + } else { + msgdata.data[i] = '\0'; + } + Serial.print(msgdata.data[i]); + Serial.print(" "); + } + Serial.print('\n'); + msgdata.crc16 = calculateCRC16((uint8_t*) &msgdata.data[0], datasize); + return msgdata; +} + +// should be in shared header +bool validateMessage(char* message_bytes, MessageData &tmp) { + + memcpy(&tmp, message_bytes, sizeof(tmp)); + + // Validate PROTOCOL terminator + if (tmp.terminator != '.') { + Serial.print("[ERROR] Terminator invalid: '"); + Serial.print(tmp.terminator); + Serial.println("'"); + return false; + } + + int datasize = sizeof(tmp.data); + Serial.print("Data of size: "); + Serial.println(datasize); + + uint16_t data_crc = calculateCRC16((uint8_t*) &tmp.data[0], datasize); + + // Validate incoming data CRC against remote CRC + if (tmp.crc16 == data_crc) { + Serial.println("[OK] Data CRC valid."); + char inmsg[datasize]; + memcpy(inmsg, &tmp.data, datasize); + Serial.print("MASTER Incoming message: "); + Serial.println(String(inmsg)); + } else { + Serial.print("CRC-A = 0x"); + Serial.println(tmp.crc16, HEX); + Serial.print("Incoming message CRC-B = 0x"); + Serial.println(data_crc, HEX); + Serial.print("tmp CRC16: "); + Serial.println(tmp.crc16, HEX); + Serial.println("[ERROR] Request retransfer exception."); + return false; + } + + // Validate sequence number + uint8_t remote_sequence = tmp.sequence; + if (remote_sequence < sequence - 1) { + Serial.print("[WARNING] TODO: Unexpected sequence number: "); + Serial.print(remote_sequence); + Serial.print(" while local is "); + Serial.println(sequence); // TODO: return error + } + + return true; +} + +void sendMessage(const int seq, const String &msg) { + + Serial.print("[MASTER] Sending message: "); + Serial.print(msg); + + if (msg.indexOf("MESA") == 0) { + expectPong = true; + } else { + expectPong = false; + } + + Wire.beginTransmission(I2C_SLAVE); + //Sending Side + MessageData struct_data = encodeMessage(msg); + struct_data.sequence = seq; + char buf[sizeof(struct_data)]; + Serial.print(" of size "); + Serial.println(sizeof(struct_data)); + Serial.print("Encoding struct of size: "); + Serial.println(sizeof(struct_data)); + memcpy(buf, &struct_data, sizeof(struct_data)); + for (unsigned int i = 0; i < sizeof(struct_data); ++i) { + Wire.write(buf[i]); + Serial.print(buf[i]); + Serial.print(" "); + } + Wire.endTransmission(); // stop transmitting + Serial.println(""); +} + +void receiveEvent(const size_t howMany) { + + byteCount = byteCount + howMany; + + char incoming[howMany]; + incoming[howMany - 1] = '\0'; + unsigned int index = 0; + + digitalWrite(LED_PIN, LOW); + + unsigned long interval = 100; + unsigned long currentMillis = millis(); + unsigned long previousMillis = millis(); + // message duration may not exceed 100 ms (100 bytes, 3x buffer retransferred) + if (currentMillis - previousMillis > interval) { + previousMillis = currentMillis; + // this must be fast, keep free from logging to serial here if possible + while (0 < Wire.available()) { // loop through all but the last + int c = Wire.read(); // receive byte as a character + if (index < howMany) { + incoming[index] = (char)c; // save the character + } + ++index; + } + } + + Serial.println(); + Serial.print("[MASTER] Received "); + Serial.print(index); + Serial.println(" bytes from SLAVE."); + Serial.println((int)sequence); + + MessageData message; + + bool success = validateMessage(incoming, message); + + if (!success) { + Serial.println("I2C message failed to validate."); + return; + } + + char inmsg[sizeof(message.data)]; + memcpy(inmsg, &message.data, sizeof(message.data)); + if (inmsg[0] == 'R') { + String retransfer_command = String(inmsg); + retransfer_command.replace("R", ""); + int retransfer_offset = retransfer_command.toInt(); + Serial.println(sequence); + Serial.print("[MASTER] Retransfer request: "); + // dumb retransfer, only changes sequence number when expecting PONG + if (expectPong) { + sequence = retransfer_offset + 1; + } else { + // debug only, WARNING! may loose important messages in production + sequence = retransfer_offset + 1; + } + Serial.println(sequence); + } + + String instring = String(inmsg); + if (instring.indexOf("PONG")) { + // alles kitz... + expectPong = false; + } + + // Do something with the message... + digitalWrite(LED_PIN, HIGH); +} + +void setup() { + + delay(2000); + + //Wire.pins(SDA_PIN, SCL_PIN); + //Wire.begin(I2C_MASTER); + Wire.begin(SDA_PIN, SCL_PIN, I2C_MASTER); // new syntax: join i2c bus (address optional for master) + Wire.onReceive(receiveEvent); + + Serial.begin(230400); // keep serial fast as possible for debug logging + while (!Serial); + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, HIGH); +} + +unsigned long second_timer = millis() + 1000; + +void loop() { + + if (millis() > second_timer) { + Serial.print("Errors/Bytes: "); + Serial.print(errorCount); + Serial.print("/"); + Serial.print(byteCount); + Serial.println(" per second"); + errorCount = 0; + byteCount = 0; + second_timer = millis() + 1000; + } + + if (millis() > nextPing) { + digitalWrite(LED_PIN, LOW); + nextPing = millis() + 200; + Serial.print("[MASTER] Sequence ยป "); + Serial.println(sequence); + digitalWrite(LED_PIN, HIGH); + sendMessage(sequence, "MESA"); + if (expectPong) { + errorCount++; + } + expectPong = true; + sequence++; + } + +} diff --git a/libraries/Wire/examples/i2c-P2P-slave/crc16.h b/libraries/Wire/examples/i2c-P2P-slave/crc16.h new file mode 100644 index 0000000000..8e4c9115a2 --- /dev/null +++ b/libraries/Wire/examples/i2c-P2P-slave/crc16.h @@ -0,0 +1,214 @@ +//------------------------------------------------------------------------------------- +// CRC16 support class +// Based on various examples found on the web +// Copyright (C) 2014 Vincenzo Mennella (see license.txt) +// History +// 0.1.0 31/05/2014: First public code release +// 0.1.1 17/12/2014: Minor revision and commented code +// 0.1.2 24/11/2018: Minor code style fixes (by @devyte and @suculent) +// +// License +// "MIT Open Source Software License": +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in the +// Software without restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//------------------------------------------------------------------------------------- +#ifndef CRC16_H +#define CRC16_H +#define LIBRARY_VERSION_CRC16_H "0.1.2" + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#elif defined(ARDUINO) +#include "WProgram.h" +#else +#include +#endif + +class Crc16 { +private: + //Crc parameters + uint16_t _msbMask; + uint16_t _mask; + uint16_t _xorIn; + uint16_t _xorOut; + uint16_t _polynomial; + bool _reflectIn; + bool _reflectOut; + //Crc value + uint16_t _crc; + uint8_t reflect(uint8_t data, uint8_t bits = 32); + +public: + inline Crc16() + { + //Default to XModem parameters + _reflectIn = false; + _reflectOut = false; + _polynomial = 0x1021; + _xorIn = 0x0000; + _xorOut = 0x0000; + _msbMask = 0x8000; + _mask = 0xFFFF; + _crc = _xorIn; + } + inline Crc16(bool reflectIn, bool reflectOut, uint16_t polynomial, uint16_t xorIn, uint16_t xorOut, uint16_t msbMask, uint16_t mask) + { + _reflectIn = reflectIn; + _reflectOut = reflectOut; + _polynomial = polynomial; + _xorIn = xorIn; + _xorOut = xorOut; + _msbMask = msbMask; + _mask = mask; + _crc = _xorIn; + } + inline void clearCrc(); + inline void updateCrc(uint8_t data); + inline uint16_t getCrc(); + inline unsigned int fastCrc(const uint8_t *data, uint8_t start, uint16_t length, bool reflectIn, bool reflectOut, uint16_t polynomial, uint16_t xorIn, uint16_t xorOut, uint16_t msbMask, uint16_t mask); + inline unsigned int XModemCrc(const uint8_t *data, const uint8_t start, const uint16_t length) + { + // XModem parameters: poly=0x1021 init=0x0000 refin=false refout=false xorout=0x0000 + return fastCrc(data, start, length, false, false, 0x1021, 0x0000, 0x0000, 0x8000, 0xffff); + } +}; + +//--------------------------------------------------- +// Initialize crc calculation +//--------------------------------------------------- +void Crc16::clearCrc() +{ + _crc = _xorIn; +} +//--------------------------------------------------- +// Update crc with new data +//--------------------------------------------------- +void Crc16::updateCrc(uint8_t data) +{ + if (_reflectIn != 0) { + data = (uint8_t) reflect(data, 8); + } + + int j = 0x80; + + while (j > 0) + { + uint16_t bit = (uint16_t)(_crc & _msbMask); + + _crc <<= 1; + + if ((data & j) != 0) + { + bit = (uint16_t)(bit ^ _msbMask); + } + + if (bit != 0) + { + _crc ^= _polynomial; + } + + j >>= 1; + } +} + +//--------------------------------------------------- +// Get final crc value +//--------------------------------------------------- +uint16_t Crc16::getCrc() +{ + if (_reflectOut != 0) { + _crc = (unsigned int)((reflect(_crc) ^ _xorOut) & _mask); + } + + return _crc; +} + +//--------------------------------------------------- +// Calculate generic crc code on data array +// Examples of crc 16: +// Kermit: width=16 poly=0x1021 init=0x0000 refin=true refout=true xorout=0x0000 check=0x2189 +// Modbus: width=16 poly=0x8005 init=0xffff refin=true refout=true xorout=0x0000 check=0x4b37 +// XModem: width=16 poly=0x1021 init=0x0000 refin=false refout=false xorout=0x0000 check=0x31c3 +// CCITT-False: width=16 poly=0x1021 init=0xffff refin=false refout=false xorout=0x0000 check=0x29b1 +//--------------------------------------------------- +unsigned int Crc16::fastCrc(const uint8_t *data, uint8_t start, uint16_t length, bool reflectIn, bool reflectOut, uint16_t polynomial, uint16_t xorIn, uint16_t xorOut, uint16_t msbMask, uint16_t mask) +{ + unsigned int crc = xorIn; + + int j; + uint8_t c; + unsigned int bit; + + if (length == 0) { + return crc; + } + + for (int i = start; i < (start + length); i++) + { + c = data[i]; + + if (reflectIn != 0) + c = (uint8_t) reflect(c, 8); + + j = 0x80; + + while (j > 0) + { + bit = (unsigned int)(crc & msbMask); + crc <<= 1; + + if ((c & j) != 0) + { + bit = (unsigned int)(bit ^ msbMask); + } + + if (bit != 0) + { + crc ^= polynomial; + } + + j >>= 1; + } + } + + if (reflectOut != 0) { + crc = (unsigned int)((reflect(crc) ^ xorOut) & mask); + } + + return crc; +} + +//------------------------------------------------------- +// Reflects bit in a uint8_t +//------------------------------------------------------- +uint8_t Crc16::reflect(uint8_t data, uint8_t bits) +{ + unsigned long reflection = 0x00000000; + // Reflect the data about the center bit. + for (uint8_t bit = 0; bit < bits; bit++) + { + // If the LSB bit is set, set the reflection of it. + if ((data & 0x01) != 0) { + reflection |= (unsigned long)(1 << ((bits - 1) - bit)); + } + + data = (uint8_t)(data >> 1); + } + + return reflection; +} +#endif diff --git a/libraries/Wire/examples/i2c-P2P-slave/i2c-P2P-slave.ino b/libraries/Wire/examples/i2c-P2P-slave/i2c-P2P-slave.ino new file mode 100644 index 0000000000..d809f78d50 --- /dev/null +++ b/libraries/Wire/examples/i2c-P2P-slave/i2c-P2P-slave.ino @@ -0,0 +1,275 @@ +/* + ESP8266 I2C master-slave communication, requires Arduno Core with I2C Slave Support (2.5.0+) + + Expects two ESP8266 devices with three pins connected: GND, SDA and SCL. This is slave. + Will wait for "MESA" message and then respond with "PONG" message, + or request retransfer if message was misunderstood (e.g. CRC check failed). + Message can be up to 26 bytes long (plus wrapper). + + 09-12-2018: initial drop by Matej Sychra (github.com/suculent) +*/ + +#include +#include "crc16.h" + +#define SDA_PIN 4 // D1 +#define SCL_PIN 5 // D2 +#define LED_PIN 12 // D4 + +const uint16_t I2C_SLAVE = 0x08; +const uint16_t I2C_MASTER = 0x42; + +// Keep this structure in sync with the i2c-P2P-master example. Should be in shared header. +struct MessageData { + uint8_t sequence; + uint16_t crc16; + uint8_t data[26]; + uint8_t terminator; +}; + +MessageData message; +MessageData msgdata; + +Crc16 crc; +byte seq = 0; + +// forward declarations +uint16_t calculateCRC16(uint8_t *data, size_t length); +void sendEvent(int a, String msg); +void sendMessage(int seq, String msg); +void receiveEvent(const size_t howMany); +bool validateMessage(const char* bytes, MessageData &tmp); + +// + +// should be in shared header +uint16_t calculateCRC16(uint8_t *data, size_t length) { + crc.clearCrc(); + unsigned short value = crc.XModemCrc(data, 0, length); + Serial.print("crc = 0x"); Serial.println(value, HEX); + return (uint16_t)value; +} + +// should be in shared header +MessageData encodeMessage(String bytes) { + + msgdata.terminator = '.'; + + unsigned int datasize = sizeof(msgdata.data); + Serial.print("Encoding data of size: "); + Serial.println(datasize); + + // Generate new data set for the struct + for (size_t i = 0; i < datasize; ++i) { + if (bytes.length() >= i) { + msgdata.data[i] = bytes[i]; + } else { + msgdata.data[i] = '\0'; + } + Serial.print(msgdata.data[i]); + Serial.print(" "); + } + Serial.println(); + msgdata.crc16 = calculateCRC16((uint8_t*) &msgdata.data[0], datasize); + Serial.print("Outgoing Message CRC16: "); Serial.println(msgdata.crc16, HEX); + return msgdata; +} + +// should be in shared header +bool validateMessage(const char* bytes, MessageData &tmp) { + + memcpy(&tmp, bytes, sizeof(tmp)); + + // Validate terminator + if (tmp.terminator == '.') { + Serial.println("[OK] Terminator valid."); + } else { + Serial.print("[ERROR] Terminator invalid: '"); + Serial.print(tmp.terminator); + Serial.println("'"); + return false; + } + + int datasize = sizeof(tmp.data); + if (datasize != 28) { + Serial.print("Data of size: "); Serial.println(datasize); + if (datasize > 28) { + datasize = 28; + } + } + + // Calculate data CRC + uint16_t data_crc = calculateCRC16((uint8_t*) &tmp.data[0], sizeof(datasize)); + + // should be only when CRC is good like commented below + char inmsg[datasize]; + memcpy(inmsg, &tmp.data, datasize); + + // Validate incoming data CRC against remote CRC + if (tmp.crc16 == data_crc) { + Serial.println("[OK] Data CRC valid."); + Serial.print("SLAVE Incoming message: "); Serial.println(String(inmsg)); + } else { + Serial.print("CRC-A = 0x"); + Serial.println(tmp.crc16, HEX); + Serial.print("CRC-B = 0x"); + Serial.println(data_crc, HEX); + Serial.print("tmp CRC16: "); + Serial.println(tmp.crc16, HEX); + Serial.print("SLAVE Incoming message: "); Serial.println(String(inmsg)); + Serial.println("[ERROR] Request retransfer exception."); + return false; + } + + // Validate sequence number ( should be already updated from byteParser ) + uint8_t remote_sequence = tmp.sequence; + if (remote_sequence != seq) { + Serial.println((char*)tmp.data); + } + + return true; +} + +void receiveEvent(const size_t howMany) { + + Serial.print("Received "); Serial.print(howMany); Serial.println(" bytes..."); + char c; + char chars[howMany]; + chars[howMany - 1] = '\0'; + int index = 0; + + Serial.print("Local Sequence: "); Serial.println((int)seq); + digitalWrite(LED_PIN, LOW); + + bool requestRetransfer = false; + + unsigned long rtimeout = millis(); + while (millis() - rtimeout < 200) { // accept timeouts (may deprecate) + while (0 < Wire.available()) { // loop through all but the last + //digitalWrite(LED_PIN, LOW); + c = Wire.read(); + chars[index] = c; + //Serial.print(index); Serial.print(" : "); Serial.println((int)c); + Serial.print((int)c); + Serial.print(" "); + + // Parses first byte for sequence number, contains recovery logic... + if (index == 0) { + + Serial.print("Remote Sequence: "); Serial.println((int)c); + + if (c != seq && ((c > seq - 4) || (c < seq + 4)) && c != seq + 1) { + Serial.print("[DIFF] Sequence offset [!]: "); + Serial.println(c - seq); + Serial.print("Re-assigning sequence number: "); + Serial.println(seq); + seq = c; + requestRetransfer = true; + + } else if (c != seq + 1) { + + Serial.print("[+DIFF] Local Sequence: "); + Serial.println(seq); + Serial.print("[+DIFF] Sequence offset [!]: "); + Serial.println(c - seq); + requestRetransfer = true; + } + + if (seq == 0) { + Serial.print("Assigning sequence 0: "); + Serial.println(seq); + seq = c; + + } else if (c != seq + 1) { + Serial.print("Re-assigning sequence number: "); + Serial.println(seq); + seq = c; + } + seq = c; + } + + digitalWrite(LED_PIN, HIGH); + ++index; + } + } + + Serial.println(String(chars)); + + if (requestRetransfer) { + String event = String("R") + String(seq) + String('\0'); + sendMessage(seq, event); + } else { + + } + + Serial.print("Decoding data of size: "); Serial.println(sizeof(chars)); + bool success = validateMessage(chars, message); + + if (success) { + // Do something with the message. + char inmessage[5] = {0}; + memcpy(inmessage, (const char*)&message.data, 4); + String inmsg = String(inmessage); + if (inmsg.indexOf("MESA") == 0) { + String event = String("PONG"); + sendMessage(seq, event); + } + } + +} + +void sendMessage(int seq, String msg) { + Wire.beginTransmission(I2C_MASTER); // transmit to I2C device with our predefined master address + // Sending Side + MessageData struct_data = encodeMessage(msg); + struct_data.sequence = seq; + char b[sizeof(struct_data)]; + memcpy(b, &struct_data, sizeof(struct_data)); + Serial.print("Sending message of size "); Serial.print(sizeof(struct_data)); Serial.println(); + Serial.print("'"); + for (unsigned int i = 0; i < sizeof(struct_data); ++i) { + Wire.write(b[i]); + Serial.print(b[i]); + } + Serial.println("' sent..."); + Wire.endTransmission(); // stop transmitting +} + +// wire_transmit +void sendEvent(int a, String msg) { + Serial.print("Sending "); Serial.println(msg); + Wire.beginTransmission(I2C_MASTER); // transmit to master + Wire.write(a); + Wire.write(";"); // sends five bytes + Wire.write(msg.c_str()); + Wire.endTransmission(); // stop transmitting +} + +void setup() { + + Serial.begin(230400); + while (!Serial); // if you want to wait for first messages + + // Enable internal pullup (there's always one from the master side) + //digitalWrite(SDA_PIN, HIGH); + //digitalWrite(SCL_PIN, HIGH); + + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + //Wire.pins(SDA_PIN, SCL_PIN); + //Wire.begin(I2C_SLAVE); + Wire.begin(SDA_PIN, SCL_PIN, I2C_SLAVE); // new syntax: join i2c bus (address required for slave) + + delay(2000); + digitalWrite(LED_PIN, HIGH); + + Wire.onReceive(receiveEvent); + + Serial.print("I2C Slave started on address: 0x0"); + Serial.println(I2C_SLAVE, HEX); +} + +void loop() { + // nothing here, slave receives data and sends reply in own callback +} From 093de65d820c7f678a56a0d9f5e16619dacf999f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mate=CC=8Cj=20Sychra?= Date: Sun, 9 Dec 2018 21:33:06 +0100 Subject: [PATCH 3/4] callback type fix --- libraries/Wire/Wire.cpp | 14 +++++++------- libraries/Wire/Wire.h | 5 ++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index 7e86894991..9c743d62dd 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -51,7 +51,7 @@ uint8_t TwoWire::txBufferLength = 0; uint8_t TwoWire::transmitting = 0; void (*TwoWire::user_onRequest)(void); -void (*TwoWire::user_onReceive)(int); +void (*TwoWire::user_onReceive)(size_t); static int default_sda_pin = SDA; static int default_scl_pin = SCL; @@ -224,17 +224,17 @@ void TwoWire::onReceiveService(uint8_t* inBytes, size_t numBytes) // if(rxBufferIndex < rxBufferLength){ // return; // } - + // copy twi rx buffer into local read buffer // this enables new reads to happen in parallel for (uint8_t i = 0; i < numBytes; ++i) { rxBuffer[i] = inBytes[i]; } - + // set rx iterator vars rxBufferIndex = 0; rxBufferLength = numBytes; - + // alert user program user_onReceive(numBytes); } @@ -245,17 +245,17 @@ void TwoWire::onRequestService(void) if (!user_onRequest) { return; } - + // reset tx buffer iterator vars // !!! this will kill any pending pre-master sendTo() activity txBufferIndex = 0; txBufferLength = 0; - + // alert user program user_onRequest(); } -void TwoWire::onReceive( void (*function)(int) ) { +void TwoWire::onReceive( void (*function)(size_t) ) { user_onReceive = function; } diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index e697bcca07..2279fcfc74 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -68,14 +68,14 @@ class TwoWire : public Stream uint8_t requestFrom(uint8_t, uint8_t, uint8_t); uint8_t requestFrom(int, int); uint8_t requestFrom(int, int, int); - + virtual size_t write(uint8_t); virtual size_t write(const uint8_t *, size_t); virtual int available(void); virtual int read(void); virtual int peek(void); virtual void flush(void); - void onReceive( void (*)(int) ); + void onReceive( void (*)(size_t) ); void onRequest( void (*)(void) ); inline size_t write(unsigned long n) { return write((uint8_t)n); } @@ -90,4 +90,3 @@ extern TwoWire Wire; #endif #endif - From 263fe4adc3d2612d84e1bdcfd93c62c7fcf78954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mate=CC=8Cj=20Sychra?= Date: Sun, 9 Dec 2018 21:33:40 +0100 Subject: [PATCH 4/4] new begin method for slaves with custom pins --- libraries/Wire/Wire.cpp | 10 ++++++++++ libraries/Wire/Wire.h | 5 ++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index 9c743d62dd..4124b44f1a 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -69,6 +69,16 @@ void TwoWire::begin(int sda, int scl){ flush(); } +void TwoWire::begin(int sda, int scl, uint8_t address){ + default_sda_pin = sda; + default_scl_pin = scl; + twi_setAddress(address); + twi_init(sda, scl); + twi_attachSlaveTxEvent(onRequestService); + twi_attachSlaveRxEvent(onReceiveService); + flush(); +} + void TwoWire::pins(int sda, int scl){ default_sda_pin = sda; default_scl_pin = scl; diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index 2279fcfc74..3e74f3f047 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -27,8 +27,6 @@ #include #include "Stream.h" - - #define BUFFER_LENGTH 32 class TwoWire : public Stream @@ -45,12 +43,13 @@ class TwoWire : public Stream static uint8_t transmitting; static void (*user_onRequest)(void); - static void (*user_onReceive)(int); + static void (*user_onReceive)(size_t); static void onRequestService(void); static void onReceiveService(uint8_t*, size_t); public: TwoWire(); void begin(int sda, int scl); + void begin(int sda, int scl, uint8_t address); void pins(int sda, int scl) __attribute__((deprecated)); // use begin(sda, scl) in new code void begin(); void begin(uint8_t);