Skip to content

Commit 48a0bb9

Browse files
James Fosterlsaca05
James Foster
andauthored
Add implementation for Wire with unit tests (#9)
* Added crude high level Wire implementation + Wire unit tests. * Forgot to add new wire test file * me dumb and fixed wire tests, though there's an anomaly in compilation * Completed wire implementation with address tracking via maps + tests * Wire implementation converted from map to deque, courtesy of James Foster * Here's a newline for you: * Cleaned up crude fixes for name clashes in wire header. Co-authored-by: Lucas Saca <[email protected]>
1 parent 8ef2fc8 commit 48a0bb9

File tree

2 files changed

+237
-79
lines changed

2 files changed

+237
-79
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include <ArduinoUnitTests.h>
2+
#include <Arduino.h>
3+
#include <Wire.h>
4+
using std::deque;
5+
6+
unittest(begin_write_end) {
7+
deque<uint8_t>* mosi = Wire.getMosi(14);
8+
assertEqual(0, mosi->size());
9+
Wire.begin();
10+
Wire.beginTransmission(14);
11+
Wire.write(0x07);
12+
Wire.write(0x0E);
13+
Wire.endTransmission();
14+
assertEqual(2, mosi->size());
15+
assertEqual(0x07, mosi->front());
16+
mosi->pop_front();
17+
assertEqual(0x0E, mosi->front());
18+
mosi->pop_front();
19+
assertEqual(0, mosi->size());
20+
}
21+
22+
unittest(readTwo_writeOne) {
23+
Wire.begin();
24+
deque<uint8_t>* miso;
25+
miso = Wire.getMiso(19);
26+
miso->push_back(0x07);
27+
miso->push_back(0x0E);
28+
miso = Wire.getMiso(34);
29+
miso->push_back(1);
30+
miso->push_back(4);
31+
miso->push_back(7);
32+
33+
assertEqual(0, Wire.requestFrom(19, 3));
34+
assertEqual(2, Wire.requestFrom(19, 2));
35+
assertEqual(2, Wire.available());
36+
assertEqual(0x07, Wire.read());
37+
assertEqual(1, Wire.available());
38+
assertEqual(0x0E, Wire.read());
39+
assertEqual(0, Wire.available());
40+
assertEqual(3, Wire.requestFrom(34, 3));
41+
assertEqual(3, Wire.available());
42+
assertEqual(1, Wire.read());
43+
assertEqual(2, Wire.available());
44+
assertEqual(4, Wire.read());
45+
assertEqual(1, Wire.available());
46+
assertEqual(7, Wire.read());
47+
assertEqual(0, Wire.available());
48+
49+
Wire.beginTransmission(47);
50+
for (int i = 1; i < 4; i++) {
51+
Wire.write(i * 2);
52+
}
53+
Wire.endTransmission();
54+
deque<uint8_t>* mosi = Wire.getMosi(47);
55+
56+
assertEqual(3, mosi->size());
57+
assertEqual(2, mosi->front());
58+
mosi->pop_front();
59+
assertEqual(2, mosi->size());
60+
assertEqual(4, mosi->front());
61+
mosi->pop_front();
62+
assertEqual(1, mosi->size());
63+
assertEqual(6, mosi->front());
64+
mosi->pop_front();
65+
assertEqual(0, mosi->size());
66+
}
67+
68+
unittest_main()

cpp/arduino/Wire.h

Lines changed: 169 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,228 @@
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+
}
133+
}
134+
uint8_t requestFrom(uint8_t address, size_t quantity) {
135+
return requestFrom(address, quantity, true);
63136
}
64137
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);
138+
return requestFrom((uint8_t)address, (size_t)quantity);
70139
}
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;
140+
uint8_t requestFrom(int address, int quantity, int stop) {
141+
return requestFrom((uint8_t)address, (size_t)quantity, (bool)stop);
77142
}
78143

79144
// 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()).
145+
// Writes data from a slave device in response to a request from a master, or
146+
// queues bytes for transmission from a master to slave device (in-between
147+
// calls to beginTransmission() and endTransmission()).
82148
size_t write(uint8_t value) {
83-
// TODO: implement
84-
return 0; // number of bytes written
149+
assert(out);
150+
assert(++(out->mosiSize) <= BUFFER_LENGTH);
151+
(out->mosiBuffer).push_back(value);
152+
return 1; // number of bytes written
153+
}
154+
size_t write(const char *str) {
155+
return str == NULL ? 0 : write((const uint8_t *)str, String(str).length());
85156
}
86-
size_t write(const char *str) { return str == NULL ? 0 : write((const uint8_t *)str, String(str).length()); }
87157
size_t write(const uint8_t *buffer, size_t size) {
88158
size_t n;
89-
for (n = 0; size && write(*buffer++) && ++n; --size);
159+
for (n = 0; size && write(*buffer++) && ++n; --size)
160+
;
90161
return n;
91162
}
92-
size_t write(const char *buffer, size_t size) { return write((const uint8_t *)buffer, size); }
163+
size_t write(const char *buffer, size_t size) {
164+
return write((const uint8_t *)buffer, size);
165+
}
93166
size_t write(unsigned long n) { return write((uint8_t)n); }
94167
size_t write(long n) { return write((uint8_t)n); }
95168
size_t write(unsigned int n) { return write((uint8_t)n); }
96169
size_t write(int n) { return write((uint8_t)n); }
97170

98171
// 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.
172+
// Returns the number of bytes available for retrieval with read(). This
173+
// should be called on a master device after a call to requestFrom() or on a
174+
// slave inside the onReceive() handler.
101175
int available(void) {
102-
// TODO: implement
103-
return 0; // number of bytes available for reading
176+
assert(in);
177+
return in->misoSize;
104178
}
105179

106180
// 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
181+
// Reads a byte that was transmitted from a slave device to a master after a
182+
// call to requestFrom() or was transmitted from a master to a slave. read()
183+
// inherits from the Stream utility class.
184+
// In the mock we simply return the next byte from the input buffer.
185+
uint8_t read(void) {
186+
uint8_t value = peek();
187+
--in->misoSize;
188+
in->misoBuffer.pop_front();
189+
return value; // The next byte received
112190
}
113-
int peek(void) {
114-
// TODO: implement
115-
return 0;
191+
192+
// part of the Stream API
193+
uint8_t peek(void) {
194+
assert(in);
195+
assert(0 < in->misoSize);
196+
return in->misoBuffer.front(); // The next byte received
116197
}
198+
199+
// part of the Stream API
117200
void flush(void) {
118-
// TODO: implement
201+
// NOTE: commented out in the megaavr repository
202+
// data already at the (mock) destination
119203
}
120204

121205
// 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-
}
206+
// Registers a function to be called when a slave device receives a
207+
// transmission from a master.
208+
// We don't (yet) support the slave role in the mock
209+
void onReceive(void (*callback)(int)) { assert(false); }
126210

127211
// 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-
}
212+
// Register a function to be called when a master requests data from this
213+
// slave device.
214+
// We don't (yet) support the slave role in the mock
215+
void onRequest(void (*callback)(void)) { assert(false); }
132216

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

138228
extern TwoWire Wire;

0 commit comments

Comments
 (0)