diff --git a/SampleProjects/TestSomething/test/wire.cpp b/SampleProjects/TestSomething/test/wire.cpp new file mode 100644 index 00000000..720afb8c --- /dev/null +++ b/SampleProjects/TestSomething/test/wire.cpp @@ -0,0 +1,85 @@ +#include +#include +#include +using std::deque; + +unittest(begin_write_end) { + // master write buffer should be empty + deque* mosi = Wire.getMosi(14); + assertEqual(0, mosi->size()); + + // write some random data to random slave + const uint8_t randomSlaveAddr = 14; + const uint8_t randomData[] = { 0x07, 0x0E }; + Wire.begin(); + Wire.beginTransmission(randomSlaveAddr); + Wire.write(randomData[0]); + Wire.write(randomData[1]); + Wire.endTransmission(); + + // check master write buffer values + assertEqual(2, mosi->size()); + assertEqual(randomData[0], mosi->front()); + mosi->pop_front(); + assertEqual(randomData[1], mosi->front()); + mosi->pop_front(); + assertEqual(0, mosi->size()); +} + +unittest(readTwo_writeOne) { + Wire.begin(); + deque* miso; + // place some values on random slaves' read buffers + const int randomSlaveAddr = 19, anotherRandomSlave = 34, yetAnotherSlave = 47; + const uint8_t randomData[] = { 0x07, 0x0E }, moreRandomData[] = { 1, 4, 7 }; + miso = Wire.getMiso(randomSlaveAddr); + miso->push_back(randomData[0]); + miso->push_back(randomData[1]); + miso = Wire.getMiso(anotherRandomSlave); + miso->push_back(moreRandomData[0]); + miso->push_back(moreRandomData[1]); + miso->push_back(moreRandomData[2]); + + // check read buffers and read-related functions + // request more data than is in input buffer + assertEqual(0, Wire.requestFrom(randomSlaveAddr, 3)); + // normal use cases + assertEqual(2, Wire.requestFrom(randomSlaveAddr, 2)); + assertEqual(2, Wire.available()); + assertEqual(randomData[0], Wire.read()); + assertEqual(1, Wire.available()); + assertEqual(randomData[1], Wire.read()); + assertEqual(0, Wire.available()); + assertEqual(3, Wire.requestFrom(anotherRandomSlave, 3)); + assertEqual(3, Wire.available()); + assertEqual(moreRandomData[0], Wire.read()); + assertEqual(2, Wire.available()); + assertEqual(moreRandomData[1], Wire.read()); + assertEqual(1, Wire.available()); + assertEqual(moreRandomData[2], Wire.read()); + assertEqual(0, Wire.available()); + + // write some arbitrary values to a third slave + Wire.beginTransmission(yetAnotherSlave); + for (int i = 1; i < 4; i++) { + Wire.write(i * 2); + } + Wire.endTransmission(); + + // check master write buffer + deque* mosi = Wire.getMosi(yetAnotherSlave); + const uint8_t expectedValues[] = { 2, 4, 6 }; + + assertEqual(3, mosi->size()); + assertEqual(expectedValues[0], mosi->front()); + mosi->pop_front(); + assertEqual(2, mosi->size()); + assertEqual(expectedValues[1], mosi->front()); + mosi->pop_front(); + assertEqual(1, mosi->size()); + assertEqual(expectedValues[2], mosi->front()); + mosi->pop_front(); + assertEqual(0, mosi->size()); +} + +unittest_main() diff --git a/cpp/arduino/Wire.h b/cpp/arduino/Wire.h index c85e816e..bedf9d58 100644 --- a/cpp/arduino/Wire.h +++ b/cpp/arduino/Wire.h @@ -1,138 +1,225 @@ +/* + * The Wire Library (https://www.arduino.cc/en/Reference/Wire) + * allows you to communicate with I2C/TWI devices. The general + * TWI protocol supports one "master" device and many "slave" + * devices that share the same two wires (SDA and SCL for data + * and clock respectively). + * + * You initialize the library by calling begin() as a master or + * begin(myAddress) as a slave (with an int from 8-127). In the + * initial mock implementation we support only the master role. + * + * To send bytes from a master to a slave, start with + * beginTransmission(slaveAddress), then use write(byte) to + * enqueue data, and finish with endTransmission(). + * + * When a master wants to read, it starts with a call to + * requestFrom(slaveAddress, quantity) which blocks until the + * request finishes. The return value is either 0 (if the slave + * does not respond) or the number of bytes requested (which + * might be more than the number sent since reading is simply + * looking at a pin value at each clock tick). + * + * A master can write to or read from two or more slaves in + * quick succession (say, during one loop() function), so our + * mock needs to support preloading data to be read from multiple + * slaves and archive data sent to multiple slaves. + * + * In the mock, this is handled by having an array of wireData_t + * structures, each of which contains a deque for input and a + * deque for output. You can preload data to be read and you can + * look at a log of data that has been written. + */ #pragma once #include #include "Stream.h" +#include +#include +using std::deque; + +const size_t SLAVE_COUNT = 128; +const size_t BUFFER_LENGTH = 32; + +struct wireData_t { + uint8_t misoSize; // bytes remaining for this read + uint8_t mosiSize; // bytes included in this write + deque misoBuffer; // master in, slave out + deque mosiBuffer; // master out, slave in +}; + +// Some inspiration taken from +// https://github.com/arduino/ArduinoCore-megaavr/blob/d2a81093ba66d22dbda14c30d146c231c5910734/libraries/Wire/src/Wire.cpp +class TwoWire : public ObservableDataStream { +private: + bool _didBegin = false; + wireData_t *in = nullptr; // pointer to current slave for writing + wireData_t *out = nullptr; // pointer to current slave for reading + wireData_t slaves[SLAVE_COUNT]; -class TwoWire : public ObservableDataStream -{ public: + // constructor initializes internal data TwoWire() { + for (int i = 0; i < SLAVE_COUNT; ++i) { + slaves[i].misoSize = 0; + slaves[i].mosiSize = 0; + } } // https://www.arduino.cc/en/Reference/WireBegin - // Initiate the Wire library and join the I2C bus as a master or slave. This should normally be called only once. - void begin() { - isMaster = true; - } - void begin(int address) { - i2cAddress = address; - isMaster = false; - } + // Initiate the Wire library and join the I2C bus as a master or slave. This + // should normally be called only once. + void begin() { begin(0); } void begin(uint8_t address) { - begin((int)address); - } - void end() { - // TODO: implement + assert(address == 0); + _didBegin = true; } + void begin(int address) { begin((uint8_t)address); } + // NOTE: end() is not part of the published API so we ignore it + void end() {} // https://www.arduino.cc/en/Reference/WireSetClock - // This function modifies the clock frequency for I2C communication. I2C slave devices have no minimum working - // clock frequency, however 100KHz is usually the baseline. - void setClock(uint32_t) { - // TODO: implement? - } + // This function modifies the clock frequency for I2C communication. I2C slave + // devices have no minimum working clock frequency, however 100KHz is usually + // the baseline. + // Since the mock does not actually write pins we ignore this. + void setClock(uint32_t clock) {} // https://www.arduino.cc/en/Reference/WireBeginTransmission - // Begin a transmission to the I2C slave device with the given address. Subsequently, queue bytes for - // transmission with the write() function and transmit them by calling endTransmission(). - void beginTransmission(int address) { - // TODO: implement - } + // Begin a transmission to the I2C slave device with the given address. + // Subsequently, queue bytes for transmission with the write() function and + // transmit them by calling endTransmission(). + // For the mock we update our output to the proper destination. void beginTransmission(uint8_t address) { - beginTransmission((int)address); + assert(_didBegin); + assert(address > 0 && address < SLAVE_COUNT); + assert(out == nullptr); + out = &slaves[address]; + out->mosiSize = 0; } + void beginTransmission(int address) { beginTransmission((uint8_t)address); } // https://www.arduino.cc/en/Reference/WireEndTransmission - // Ends a transmission to a slave device that was begun by beginTransmission() and transmits the bytes that were - // queued by write(). - uint8_t endTransmission(uint8_t sendStop) { - // TODO: implement + // Ends a transmission to a slave device that was begun by beginTransmission() + // and transmits the bytes that were queued by write(). + // In the mock we just leave the bytes there in the buffer + // to be read by the testing API and we ignore the sendStop. + uint8_t endTransmission(bool sendStop) { + assert(_didBegin); + assert(out); + out = nullptr; return 0; // success } - uint8_t endTransmission(void) { - return endTransmission((uint8_t)true); - } + uint8_t endTransmission(void) { return endTransmission(true); } // https://www.arduino.cc/en/Reference/WireRequestFrom - // Used by the master to request bytes from a slave device. The bytes may then be retrieved with the - // available() and read() functions. - uint8_t requestFrom(int address, int quantity, int stop) { - // TODO: implement - return 0; // number of bytes returned from the slave device + // Used by the master to request bytes from a slave device. The bytes may then + // be retrieved with the available() and read() functions. + uint8_t requestFrom(uint8_t address, size_t quantity, bool stop) { + assert(_didBegin); + assert(address > 0 && address < SLAVE_COUNT); + assert(quantity <= BUFFER_LENGTH); + in = &slaves[address]; + // do we have enough data in the input buffer + if (quantity <= (in->misoBuffer).size()) { // enough data + in->misoSize = quantity; + return quantity; + } else { // not enough data + in->misoSize = 0; + in = nullptr; + return 0; + } } uint8_t requestFrom(int address, int quantity) { - int stop = true; - return requestFrom(address, quantity, stop); - } - uint8_t requestFrom(uint8_t address, uint8_t quantity) { - return requestFrom((int)address, (int)quantity); + return requestFrom((uint8_t)address, (size_t)quantity, true); } - uint8_t requestFrom(uint8_t address, uint8_t quantity, uint8_t stop) { - return requestFrom((int)address, (int)quantity, (int)stop); - } - uint8_t requestFrom(uint8_t, uint8_t, uint32_t, uint8_t, uint8_t) { - // TODO: implement - return 0; + uint8_t requestFrom(int address, int quantity, int stop) { + return requestFrom((uint8_t)address, (size_t)quantity, (bool)stop); } // https://www.arduino.cc/en/Reference/WireWrite - // Writes data from a slave device in response to a request from a master, or queues bytes for transmission from a - // master to slave device (in-between calls to beginTransmission() and endTransmission()). + // Writes data from a slave device in response to a request from a master, or + // queues bytes for transmission from a master to slave device (in-between + // calls to beginTransmission() and endTransmission()). size_t write(uint8_t value) { - // TODO: implement - return 0; // number of bytes written + assert(out); + assert(++(out->mosiSize) <= BUFFER_LENGTH); + (out->mosiBuffer).push_back(value); + return 1; // number of bytes written + } + size_t write(const char *str) { + return str == NULL ? 0 : write((const uint8_t *)str, String(str).length()); } - size_t write(const char *str) { return str == NULL ? 0 : write((const uint8_t *)str, String(str).length()); } size_t write(const uint8_t *buffer, size_t size) { size_t n; - for (n = 0; size && write(*buffer++) && ++n; --size); + for (n = 0; size && write(*buffer++) && ++n; --size) + ; return n; } - size_t write(const char *buffer, size_t size) { return write((const uint8_t *)buffer, size); } + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } size_t write(unsigned long n) { return write((uint8_t)n); } size_t write(long n) { return write((uint8_t)n); } size_t write(unsigned int n) { return write((uint8_t)n); } size_t write(int n) { return write((uint8_t)n); } // https://www.arduino.cc/en/Reference/WireAvailable - // Returns the number of bytes available for retrieval with read(). This should be called on a master device after a - // call to requestFrom() or on a slave inside the onReceive() handler. + // Returns the number of bytes available for retrieval with read(). This + // should be called on a master device after a call to requestFrom() or on a + // slave inside the onReceive() handler. int available(void) { - // TODO: implement - return 0; // number of bytes available for reading + assert(in); + return in->misoSize; } // https://www.arduino.cc/en/Reference/WireRead - // Reads a byte that was transmitted from a slave device to a master after a call to requestFrom() or was transmitted - // from a master to a slave. read() inherits from the Stream utility class. - int read(void) { - // TODO: implement - return '\0'; // The next byte received + // Reads a byte that was transmitted from a slave device to a master after a + // call to requestFrom() or was transmitted from a master to a slave. read() + // inherits from the Stream utility class. + // In the mock we simply return the next byte from the input buffer. + uint8_t read(void) { + uint8_t value = peek(); + --in->misoSize; + in->misoBuffer.pop_front(); + return value; // The next byte received } - int peek(void) { - // TODO: implement - return 0; + + // part of the Stream API + uint8_t peek(void) { + assert(in); + assert(0 < in->misoSize); + return in->misoBuffer.front(); // The next byte received } + + // part of the Stream API void flush(void) { - // TODO: implement + // NOTE: commented out in the megaavr repository + // data already at the (mock) destination } // https://www.arduino.cc/en/Reference/WireOnReceive - // Registers a function to be called when a slave device receives a transmission from a master. - void onReceive( void (*callback)(int) ) { - // TODO: implement - } + // Registers a function to be called when a slave device receives a + // transmission from a master. + // We don't (yet) support the slave role in the mock + void onReceive(void (*callback)(int)) { assert(false); } // https://www.arduino.cc/en/Reference/WireOnRequest - // Register a function to be called when a master requests data from this slave device. - void onRequest( void (*callback)(void) ) { - // TODO: implement - } + // Register a function to be called when a master requests data from this + // slave device. + // We don't (yet) support the slave role in the mock + void onRequest(void (*callback)(void)) { assert(false); } -private: - int i2cAddress; - bool isMaster = false; + // testing methods + bool didBegin() { return _didBegin; } + + deque *getMiso(uint8_t address) { + return &slaves[address].misoBuffer; + } + deque *getMosi(uint8_t address) { + return &slaves[address].mosiBuffer; + } }; extern TwoWire Wire;