Skip to content

Commit d2784a2

Browse files
authored
Merge pull request #189 from lsaca05/wire
High-level wire implementation, complete with unit tests
2 parents f32ba70 + e4fd2e4 commit d2784a2

File tree

2 files changed

+251
-79
lines changed

2 files changed

+251
-79
lines changed
+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#include <ArduinoUnitTests.h>
2+
#include <Arduino.h>
3+
#include <Wire.h>
4+
using std::deque;
5+
6+
unittest(begin_write_end) {
7+
// master write buffer should be empty
8+
deque<uint8_t>* mosi = Wire.getMosi(14);
9+
assertEqual(0, mosi->size());
10+
11+
// write some random data to random slave
12+
const uint8_t randomSlaveAddr = 14;
13+
const uint8_t randomData[] = { 0x07, 0x0E };
14+
Wire.begin();
15+
Wire.beginTransmission(randomSlaveAddr);
16+
Wire.write(randomData[0]);
17+
Wire.write(randomData[1]);
18+
Wire.endTransmission();
19+
20+
// check master write buffer values
21+
assertEqual(2, mosi->size());
22+
assertEqual(randomData[0], mosi->front());
23+
mosi->pop_front();
24+
assertEqual(randomData[1], mosi->front());
25+
mosi->pop_front();
26+
assertEqual(0, mosi->size());
27+
}
28+
29+
unittest(readTwo_writeOne) {
30+
Wire.begin();
31+
deque<uint8_t>* miso;
32+
// place some values on random slaves' read buffers
33+
const int randomSlaveAddr = 19, anotherRandomSlave = 34, yetAnotherSlave = 47;
34+
const uint8_t randomData[] = { 0x07, 0x0E }, moreRandomData[] = { 1, 4, 7 };
35+
miso = Wire.getMiso(randomSlaveAddr);
36+
miso->push_back(randomData[0]);
37+
miso->push_back(randomData[1]);
38+
miso = Wire.getMiso(anotherRandomSlave);
39+
miso->push_back(moreRandomData[0]);
40+
miso->push_back(moreRandomData[1]);
41+
miso->push_back(moreRandomData[2]);
42+
43+
// check read buffers and read-related functions
44+
// request more data than is in input buffer
45+
assertEqual(0, Wire.requestFrom(randomSlaveAddr, 3));
46+
// normal use cases
47+
assertEqual(2, Wire.requestFrom(randomSlaveAddr, 2));
48+
assertEqual(2, Wire.available());
49+
assertEqual(randomData[0], Wire.read());
50+
assertEqual(1, Wire.available());
51+
assertEqual(randomData[1], Wire.read());
52+
assertEqual(0, Wire.available());
53+
assertEqual(3, Wire.requestFrom(anotherRandomSlave, 3));
54+
assertEqual(3, Wire.available());
55+
assertEqual(moreRandomData[0], Wire.read());
56+
assertEqual(2, Wire.available());
57+
assertEqual(moreRandomData[1], Wire.read());
58+
assertEqual(1, Wire.available());
59+
assertEqual(moreRandomData[2], Wire.read());
60+
assertEqual(0, Wire.available());
61+
62+
// write some arbitrary values to a third slave
63+
Wire.beginTransmission(yetAnotherSlave);
64+
for (int i = 1; i < 4; i++) {
65+
Wire.write(i * 2);
66+
}
67+
Wire.endTransmission();
68+
69+
// check master write buffer
70+
deque<uint8_t>* mosi = Wire.getMosi(yetAnotherSlave);
71+
const uint8_t expectedValues[] = { 2, 4, 6 };
72+
73+
assertEqual(3, mosi->size());
74+
assertEqual(expectedValues[0], mosi->front());
75+
mosi->pop_front();
76+
assertEqual(2, mosi->size());
77+
assertEqual(expectedValues[1], mosi->front());
78+
mosi->pop_front();
79+
assertEqual(1, mosi->size());
80+
assertEqual(expectedValues[2], mosi->front());
81+
mosi->pop_front();
82+
assertEqual(0, mosi->size());
83+
}
84+
85+
unittest_main()

cpp/arduino/Wire.h

+166-79
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,225 @@
1+
/*
2+
* The Wire Library (https://www.arduino.cc/en/Reference/Wire)
3+
* allows you to communicate with I2C/TWI devices. The general
4+
* TWI protocol supports one "master" device and many "slave"
5+
* devices that share the same two wires (SDA and SCL for data
6+
* and clock respectively).
7+
*
8+
* You initialize the library by calling begin() as a master or
9+
* begin(myAddress) as a slave (with an int from 8-127). In the
10+
* initial mock implementation we support only the master role.
11+
*
12+
* To send bytes from a master to a slave, start with
13+
* beginTransmission(slaveAddress), then use write(byte) to
14+
* enqueue data, and finish with endTransmission().
15+
*
16+
* When a master wants to read, it starts with a call to
17+
* requestFrom(slaveAddress, quantity) which blocks until the
18+
* request finishes. The return value is either 0 (if the slave
19+
* does not respond) or the number of bytes requested (which
20+
* might be more than the number sent since reading is simply
21+
* looking at a pin value at each clock tick).
22+
*
23+
* A master can write to or read from two or more slaves in
24+
* quick succession (say, during one loop() function), so our
25+
* mock needs to support preloading data to be read from multiple
26+
* slaves and archive data sent to multiple slaves.
27+
*
28+
* In the mock, this is handled by having an array of wireData_t
29+
* structures, each of which contains a deque for input and a
30+
* deque for output. You can preload data to be read and you can
31+
* look at a log of data that has been written.
32+
*/
133

234
#pragma once
335

436
#include <inttypes.h>
537
#include "Stream.h"
38+
#include <cassert>
39+
#include <deque>
40+
using std::deque;
41+
42+
const size_t SLAVE_COUNT = 128;
43+
const size_t BUFFER_LENGTH = 32;
44+
45+
struct wireData_t {
46+
uint8_t misoSize; // bytes remaining for this read
47+
uint8_t mosiSize; // bytes included in this write
48+
deque<uint8_t> misoBuffer; // master in, slave out
49+
deque<uint8_t> mosiBuffer; // master out, slave in
50+
};
51+
52+
// Some inspiration taken from
53+
// https://github.com/arduino/ArduinoCore-megaavr/blob/d2a81093ba66d22dbda14c30d146c231c5910734/libraries/Wire/src/Wire.cpp
54+
class TwoWire : public ObservableDataStream {
55+
private:
56+
bool _didBegin = false;
57+
wireData_t *in = nullptr; // pointer to current slave for writing
58+
wireData_t *out = nullptr; // pointer to current slave for reading
59+
wireData_t slaves[SLAVE_COUNT];
660

7-
class TwoWire : public ObservableDataStream
8-
{
961
public:
62+
// constructor initializes internal data
1063
TwoWire() {
64+
for (int i = 0; i < SLAVE_COUNT; ++i) {
65+
slaves[i].misoSize = 0;
66+
slaves[i].mosiSize = 0;
67+
}
1168
}
1269

1370
// https://www.arduino.cc/en/Reference/WireBegin
14-
// Initiate the Wire library and join the I2C bus as a master or slave. This should normally be called only once.
15-
void begin() {
16-
isMaster = true;
17-
}
18-
void begin(int address) {
19-
i2cAddress = address;
20-
isMaster = false;
21-
}
71+
// Initiate the Wire library and join the I2C bus as a master or slave. This
72+
// should normally be called only once.
73+
void begin() { begin(0); }
2274
void begin(uint8_t address) {
23-
begin((int)address);
24-
}
25-
void end() {
26-
// TODO: implement
75+
assert(address == 0);
76+
_didBegin = true;
2777
}
78+
void begin(int address) { begin((uint8_t)address); }
79+
// NOTE: end() is not part of the published API so we ignore it
80+
void end() {}
2881

2982
// https://www.arduino.cc/en/Reference/WireSetClock
30-
// This function modifies the clock frequency for I2C communication. I2C slave devices have no minimum working
31-
// clock frequency, however 100KHz is usually the baseline.
32-
void setClock(uint32_t) {
33-
// TODO: implement?
34-
}
83+
// This function modifies the clock frequency for I2C communication. I2C slave
84+
// devices have no minimum working clock frequency, however 100KHz is usually
85+
// the baseline.
86+
// Since the mock does not actually write pins we ignore this.
87+
void setClock(uint32_t clock) {}
3588

3689
// https://www.arduino.cc/en/Reference/WireBeginTransmission
37-
// Begin a transmission to the I2C slave device with the given address. Subsequently, queue bytes for
38-
// transmission with the write() function and transmit them by calling endTransmission().
39-
void beginTransmission(int address) {
40-
// TODO: implement
41-
}
90+
// Begin a transmission to the I2C slave device with the given address.
91+
// Subsequently, queue bytes for transmission with the write() function and
92+
// transmit them by calling endTransmission().
93+
// For the mock we update our output to the proper destination.
4294
void beginTransmission(uint8_t address) {
43-
beginTransmission((int)address);
95+
assert(_didBegin);
96+
assert(address > 0 && address < SLAVE_COUNT);
97+
assert(out == nullptr);
98+
out = &slaves[address];
99+
out->mosiSize = 0;
44100
}
101+
void beginTransmission(int address) { beginTransmission((uint8_t)address); }
45102

46103
// https://www.arduino.cc/en/Reference/WireEndTransmission
47-
// Ends a transmission to a slave device that was begun by beginTransmission() and transmits the bytes that were
48-
// queued by write().
49-
uint8_t endTransmission(uint8_t sendStop) {
50-
// TODO: implement
104+
// Ends a transmission to a slave device that was begun by beginTransmission()
105+
// and transmits the bytes that were queued by write().
106+
// In the mock we just leave the bytes there in the buffer
107+
// to be read by the testing API and we ignore the sendStop.
108+
uint8_t endTransmission(bool sendStop) {
109+
assert(_didBegin);
110+
assert(out);
111+
out = nullptr;
51112
return 0; // success
52113
}
53-
uint8_t endTransmission(void) {
54-
return endTransmission((uint8_t)true);
55-
}
114+
uint8_t endTransmission(void) { return endTransmission(true); }
56115

57116
// https://www.arduino.cc/en/Reference/WireRequestFrom
58-
// Used by the master to request bytes from a slave device. The bytes may then be retrieved with the
59-
// available() and read() functions.
60-
uint8_t requestFrom(int address, int quantity, int stop) {
61-
// TODO: implement
62-
return 0; // number of bytes returned from the slave device
117+
// Used by the master to request bytes from a slave device. The bytes may then
118+
// be retrieved with the available() and read() functions.
119+
uint8_t requestFrom(uint8_t address, size_t quantity, bool stop) {
120+
assert(_didBegin);
121+
assert(address > 0 && address < SLAVE_COUNT);
122+
assert(quantity <= BUFFER_LENGTH);
123+
in = &slaves[address];
124+
// do we have enough data in the input buffer
125+
if (quantity <= (in->misoBuffer).size()) { // enough data
126+
in->misoSize = quantity;
127+
return quantity;
128+
} else { // not enough data
129+
in->misoSize = 0;
130+
in = nullptr;
131+
return 0;
132+
}
63133
}
64134
uint8_t requestFrom(int address, int quantity) {
65-
int stop = true;
66-
return requestFrom(address, quantity, stop);
67-
}
68-
uint8_t requestFrom(uint8_t address, uint8_t quantity) {
69-
return requestFrom((int)address, (int)quantity);
135+
return requestFrom((uint8_t)address, (size_t)quantity, true);
70136
}
71-
uint8_t requestFrom(uint8_t address, uint8_t quantity, uint8_t stop) {
72-
return requestFrom((int)address, (int)quantity, (int)stop);
73-
}
74-
uint8_t requestFrom(uint8_t, uint8_t, uint32_t, uint8_t, uint8_t) {
75-
// TODO: implement
76-
return 0;
137+
uint8_t requestFrom(int address, int quantity, int stop) {
138+
return requestFrom((uint8_t)address, (size_t)quantity, (bool)stop);
77139
}
78140

79141
// https://www.arduino.cc/en/Reference/WireWrite
80-
// Writes data from a slave device in response to a request from a master, or queues bytes for transmission from a
81-
// master to slave device (in-between calls to beginTransmission() and endTransmission()).
142+
// Writes data from a slave device in response to a request from a master, or
143+
// queues bytes for transmission from a master to slave device (in-between
144+
// calls to beginTransmission() and endTransmission()).
82145
size_t write(uint8_t value) {
83-
// TODO: implement
84-
return 0; // number of bytes written
146+
assert(out);
147+
assert(++(out->mosiSize) <= BUFFER_LENGTH);
148+
(out->mosiBuffer).push_back(value);
149+
return 1; // number of bytes written
150+
}
151+
size_t write(const char *str) {
152+
return str == NULL ? 0 : write((const uint8_t *)str, String(str).length());
85153
}
86-
size_t write(const char *str) { return str == NULL ? 0 : write((const uint8_t *)str, String(str).length()); }
87154
size_t write(const uint8_t *buffer, size_t size) {
88155
size_t n;
89-
for (n = 0; size && write(*buffer++) && ++n; --size);
156+
for (n = 0; size && write(*buffer++) && ++n; --size)
157+
;
90158
return n;
91159
}
92-
size_t write(const char *buffer, size_t size) { return write((const uint8_t *)buffer, size); }
160+
size_t write(const char *buffer, size_t size) {
161+
return write((const uint8_t *)buffer, size);
162+
}
93163
size_t write(unsigned long n) { return write((uint8_t)n); }
94164
size_t write(long n) { return write((uint8_t)n); }
95165
size_t write(unsigned int n) { return write((uint8_t)n); }
96166
size_t write(int n) { return write((uint8_t)n); }
97167

98168
// https://www.arduino.cc/en/Reference/WireAvailable
99-
// Returns the number of bytes available for retrieval with read(). This should be called on a master device after a
100-
// call to requestFrom() or on a slave inside the onReceive() handler.
169+
// Returns the number of bytes available for retrieval with read(). This
170+
// should be called on a master device after a call to requestFrom() or on a
171+
// slave inside the onReceive() handler.
101172
int available(void) {
102-
// TODO: implement
103-
return 0; // number of bytes available for reading
173+
assert(in);
174+
return in->misoSize;
104175
}
105176

106177
// https://www.arduino.cc/en/Reference/WireRead
107-
// Reads a byte that was transmitted from a slave device to a master after a call to requestFrom() or was transmitted
108-
// from a master to a slave. read() inherits from the Stream utility class.
109-
int read(void) {
110-
// TODO: implement
111-
return '\0'; // The next byte received
178+
// Reads a byte that was transmitted from a slave device to a master after a
179+
// call to requestFrom() or was transmitted from a master to a slave. read()
180+
// inherits from the Stream utility class.
181+
// In the mock we simply return the next byte from the input buffer.
182+
uint8_t read(void) {
183+
uint8_t value = peek();
184+
--in->misoSize;
185+
in->misoBuffer.pop_front();
186+
return value; // The next byte received
112187
}
113-
int peek(void) {
114-
// TODO: implement
115-
return 0;
188+
189+
// part of the Stream API
190+
uint8_t peek(void) {
191+
assert(in);
192+
assert(0 < in->misoSize);
193+
return in->misoBuffer.front(); // The next byte received
116194
}
195+
196+
// part of the Stream API
117197
void flush(void) {
118-
// TODO: implement
198+
// NOTE: commented out in the megaavr repository
199+
// data already at the (mock) destination
119200
}
120201

121202
// https://www.arduino.cc/en/Reference/WireOnReceive
122-
// Registers a function to be called when a slave device receives a transmission from a master.
123-
void onReceive( void (*callback)(int) ) {
124-
// TODO: implement
125-
}
203+
// Registers a function to be called when a slave device receives a
204+
// transmission from a master.
205+
// We don't (yet) support the slave role in the mock
206+
void onReceive(void (*callback)(int)) { assert(false); }
126207

127208
// https://www.arduino.cc/en/Reference/WireOnRequest
128-
// Register a function to be called when a master requests data from this slave device.
129-
void onRequest( void (*callback)(void) ) {
130-
// TODO: implement
131-
}
209+
// Register a function to be called when a master requests data from this
210+
// slave device.
211+
// We don't (yet) support the slave role in the mock
212+
void onRequest(void (*callback)(void)) { assert(false); }
132213

133-
private:
134-
int i2cAddress;
135-
bool isMaster = false;
214+
// testing methods
215+
bool didBegin() { return _didBegin; }
216+
217+
deque<uint8_t> *getMiso(uint8_t address) {
218+
return &slaves[address].misoBuffer;
219+
}
220+
deque<uint8_t> *getMosi(uint8_t address) {
221+
return &slaves[address].mosiBuffer;
222+
}
136223
};
137224

138225
extern TwoWire Wire;

0 commit comments

Comments
 (0)