1
+ /*
2
+ I2C - Serial Passthrough
3
+ By: Paul Clark
4
+ SparkFun Electronics
5
+ Date: August 14th, 2024
6
+ License: MIT. See license file for more information.
7
+
8
+ Feel like supporting open source hardware?
9
+ Buy a board from SparkFun!
10
+ SparkFun GPS-RTK2 - ZED-F9P (GPS-15136) https://www.sparkfun.com/products/15136
11
+ SparkFun GPS-RTK-SMA - ZED-F9P (GPS-16481) https://www.sparkfun.com/products/16481
12
+ SparkFun MAX-M10S Breakout (GPS-18037) https://www.sparkfun.com/products/18037
13
+ SparkFun ZED-F9K Breakout (GPS-18719) https://www.sparkfun.com/products/18719
14
+ SparkFun ZED-F9R Breakout (GPS-16344) https://www.sparkfun.com/products/16344
15
+
16
+ Hardware Connections:
17
+ Plug a Qwiic cable into the GNSS and a RedBoard
18
+ If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425)
19
+ Connect the RedBoard to your PC using a USB cable
20
+ Connect with u-center at 115200 baud to communicate with the GNSS
21
+
22
+ How it works:
23
+ Data arriving on Serial is added to the uartI2cRingBuffer.
24
+ If the uartI2cRingBuffer contains at least uartI2cSendLimit bytes, these are 'pushed' to the GNSS I2C register 0xFF.
25
+ If the uartI2cRingBuffer contains less than uartI2cSendLimit bytes, and it is more than uartI2cSendInterval_ms
26
+ since the last send, these are 'pushed' to the GNSS I2C register 0xFF.
27
+ Every i2cReadInterval_ms, the number of available bytes in the GNSS I2C buffer is read (from registers 0xFD and 0xFE)
28
+ and stored in i2cBytesAvailable.
29
+ If GNSS data is available, it is read in blocks of i2cReadLimit bytes and stored in i2cUartRingBuffer.
30
+ If the i2cUartRingBuffer contains at least i2cUartSendLimit bytes, these are written to Serial.
31
+ If the i2cUartRingBuffer contains less than i2cUartSendLimit bytes, and it is more than i2cUartSendInterval_ms
32
+ since the last send, these are written to Serial.
33
+ */
34
+
35
+ #include " Arduino.h"
36
+
37
+ HardwareSerial &mySerial = Serial; // USB Serial. Change this if needed
38
+
39
+ #include < Wire.h>
40
+
41
+ TwoWire &myWire = Wire; // TwoWire (I2C) connection to GNSS. Change this if needed
42
+
43
+ const uint8_t gnssAddress = 0x42 ; // GNSS I2C address (unshifted)
44
+
45
+ const uint16_t ringBufferSize = 512 ; // Define the size of the two ring buffers
46
+ uint8_t i2cUartRingBuffer[ringBufferSize];
47
+ uint16_t i2cUartBufferHead = 0 ;
48
+ uint16_t i2cUartBufferTail = 0 ;
49
+ uint8_t uartI2cRingBuffer[ringBufferSize];
50
+ uint16_t uartI2cBufferHead = 0 ;
51
+ uint16_t uartI2cBufferTail = 0 ;
52
+
53
+ const unsigned long uartI2cSendInterval_ms = 50 ;
54
+ unsigned long uartI2cLastSend_ms = 0 ;
55
+ const uint16_t uartI2cSendLimit = 16 ;
56
+
57
+ const unsigned long i2cUartSendInterval_ms = 50 ;
58
+ unsigned long i2cUartLastSend_ms = 0 ;
59
+ const uint16_t i2cUartSendLimit = 16 ;
60
+
61
+ const unsigned long i2cReadInterval_ms = 50 ;
62
+ unsigned long i2cLastRead_ms = 0 ;
63
+ uint16_t i2cBytesAvailable = 0 ;
64
+ const uint16_t i2cReadLimit = 16 ;
65
+
66
+ void setup ()
67
+ {
68
+
69
+ delay (1000 ); // Wait for ESP32 to start up
70
+
71
+ mySerial.begin (115200 ); // Baud rate for u-center
72
+
73
+ myWire.begin (); // Start I2C
74
+ myWire.setClock (400000 ); // 400kHz
75
+ } // /setup
76
+
77
+ void loop ()
78
+ {
79
+
80
+ // If it is more than i2cReadInterval_ms since the last read, read how
81
+ // many bytes are available in the GNSS I2C buffer. This will leave the register
82
+ // address pointing at 0xFF.
83
+ if ((millis () > (i2cLastRead_ms + i2cReadInterval_ms)) || (i2cLastRead_ms == 0 ))
84
+ {
85
+ i2cBytesAvailable = gnssI2cAvailable ();
86
+ i2cLastRead_ms = millis ();
87
+ }
88
+
89
+ // If Serial data is available, add it to the buffer
90
+ if (Serial.available ())
91
+ addToUartI2cBuffer (Serial.read ());
92
+
93
+ // Check how many bytes are available in the uartI2cRingBuffer
94
+ uint16_t uartI2cAvailable = ringBufferAvailable (uartI2cBufferHead, uartI2cBufferTail);
95
+ if (uartI2cAvailable > 0 )
96
+ {
97
+ // We must avoid sending a single byte. Send one byte less if needed.
98
+ uint16_t bytesToSend = uartI2cAvailable;
99
+ if (bytesToSend > uartI2cSendLimit) // Limit to uartI2cSendLimit
100
+ bytesToSend = uartI2cSendLimit;
101
+ if ((uartI2cAvailable - bytesToSend) == 1 ) // If this would leave one byte in the buffer
102
+ bytesToSend--; // Send one byte less
103
+ // If uartI2cRingBuffer contains at least uartI2cSendLimit bytes, send them
104
+ if (bytesToSend >= (uartI2cSendLimit - 1 ))
105
+ {
106
+ sendI2cBytes (bytesToSend);
107
+ uartI2cLastSend_ms = millis ();
108
+ }
109
+ // Else if uartI2cRingBuffer contains data and it is more than uartI2cSendInterval_ms
110
+ // since the last send, send them
111
+ else if ((bytesToSend > 0 ) && (millis () > (uartI2cLastSend_ms + uartI2cSendInterval_ms)))
112
+ {
113
+ sendI2cBytes (bytesToSend);
114
+ uartI2cLastSend_ms = millis ();
115
+ }
116
+ }
117
+
118
+ // If the GNSS has data, read it now
119
+ if (i2cBytesAvailable > 0 )
120
+ {
121
+ // Read a maximum of i2cReadLimit, to prevent the code stalling here
122
+ uint16_t bytesToRead = i2cBytesAvailable;
123
+ if (bytesToRead > i2cReadLimit)
124
+ bytesToRead = i2cReadLimit;
125
+ if (readI2cBytes (bytesToRead))
126
+ i2cBytesAvailable -= bytesToRead;
127
+ }
128
+
129
+ // Check how much data is in the i2cUartRingBuffer
130
+ uint16_t i2cUartAvailable = ringBufferAvailable (i2cUartBufferHead, i2cUartBufferTail);
131
+ if (i2cUartAvailable > 0 )
132
+ {
133
+ uint16_t bytesToSend = i2cUartAvailable;
134
+ if (bytesToSend > i2cUartSendLimit)
135
+ bytesToSend = i2cUartSendLimit;
136
+ // If the buffer contains i2cUartSendLimit bytes, send them
137
+ if (bytesToSend == i2cUartSendLimit)
138
+ {
139
+ sendUartBytes (bytesToSend);
140
+ i2cUartLastSend_ms = millis ();
141
+ }
142
+ // Else if i2cUartRingBuffer contains data and it is more than i2cUartSendInterval_ms
143
+ // since the last send, send them
144
+ else if ((bytesToSend > 0 ) && (millis () > (i2cUartLastSend_ms + i2cUartSendInterval_ms)))
145
+ {
146
+ sendUartBytes (bytesToSend);
147
+ i2cUartLastSend_ms = millis ();
148
+ }
149
+ }
150
+ } // /loop
151
+
152
+ // Read how many bytes are available in the GNSS I2C buffer.
153
+ // This will leave the register address pointing at 0xFF.
154
+ uint16_t gnssI2cAvailable ()
155
+ {
156
+ // Get the number of bytes available from the module
157
+ uint16_t bytesAvailable = 0 ;
158
+ myWire.beginTransmission (gnssAddress);
159
+ myWire.write (0xFD ); // 0xFD (MSB) and 0xFE (LSB) are the registers that contain number of bytes available
160
+ uint8_t i2cError = myWire.endTransmission (false ); // Always send a restart command. Do not release the bus. ESP32 supports this.
161
+ if (i2cError != 0 )
162
+ {
163
+ return (0 ); // Sensor did not ACK
164
+ }
165
+
166
+ // Forcing requestFrom to use a restart would be unwise. If bytesAvailable is zero, we want to surrender the bus.
167
+ uint16_t bytesReturned = myWire.requestFrom (gnssAddress, static_cast <uint8_t >(2 ));
168
+ if (bytesReturned != 2 )
169
+ {
170
+ return (0 ); // Sensor did not return 2 bytes
171
+ }
172
+ else // if (myWire.available())
173
+ {
174
+ uint8_t msb = myWire.read ();
175
+ uint8_t lsb = myWire.read ();
176
+ bytesAvailable = (((uint16_t )msb) << 8 ) | lsb;
177
+ }
178
+
179
+ return (bytesAvailable);
180
+ } // /gnssI2cAvailable
181
+
182
+ // Add b to the uartI2cRingBuffer if space is available
183
+ bool addToUartI2cBuffer (uint8_t b)
184
+ {
185
+ if (ringBufferSpace (uartI2cBufferHead, uartI2cBufferTail) > 0 )
186
+ {
187
+ uartI2cRingBuffer[uartI2cBufferHead++] = b;
188
+ uartI2cBufferHead %= ringBufferSize; // Wrap-around
189
+ return true ;
190
+ }
191
+
192
+ return false ; // Buffer is full
193
+ }
194
+
195
+ // Send numBytes from i2cUartBuffer to Serial
196
+ // This function assumes ringBufferAvailable has been called externally
197
+ // It will read the bytes regardless
198
+ bool sendUartBytes (uint16_t numBytes)
199
+ {
200
+ if (numBytes == 0 )
201
+ return false ;
202
+
203
+ if (numBytes > i2cUartSendLimit)
204
+ numBytes = i2cUartSendLimit;
205
+
206
+ static uint8_t store[i2cUartSendLimit]; // Store the data temporarily
207
+ for (uint16_t i = 0 ; i < numBytes; i++)
208
+ store[i] = readI2cUartBuffer ();
209
+
210
+ return (mySerial.write (store, numBytes) == numBytes);
211
+ }
212
+
213
+ // Send numBytes from uartI2cBuffer to GNSS
214
+ // This function assumes ringBufferAvailable has been called externally
215
+ // It will read the bytes regardless
216
+ // Note: we cannot send a single byte. numBytes must be >= 2.
217
+ // Otherwise the write will set the register address instead.
218
+ bool sendI2cBytes (uint16_t numBytes)
219
+ {
220
+ if (numBytes < 2 )
221
+ return false ;
222
+
223
+ if (numBytes > uartI2cSendLimit)
224
+ numBytes = uartI2cSendLimit;
225
+
226
+ static uint8_t store[uartI2cSendLimit]; // Store the data temporarily
227
+ for (uint16_t i = 0 ; i < numBytes; i++)
228
+ store[i] = readUartI2cBuffer ();
229
+
230
+ // Assume the GNSS register address is already set to 0xFF
231
+ myWire.beginTransmission (gnssAddress);
232
+ myWire.write ((const uint8_t *)store, numBytes);
233
+ if (myWire.endTransmission () == 0 )
234
+ return true ;
235
+
236
+ return false ;
237
+ }
238
+
239
+ // Read numBytes from the GNSS. Store them in i2cUartRingBuffer
240
+ bool readI2cBytes (uint16_t numBytes)
241
+ {
242
+ if (numBytes == 0 )
243
+ return false ;
244
+
245
+ uint16_t bytesRequested = 0 ;
246
+ uint16_t bytesLeftToRead = numBytes;
247
+
248
+ while ((bytesLeftToRead > 0 ) && (bytesRequested < numBytes))
249
+ {
250
+ uint8_t bytesToRead;
251
+ if (bytesLeftToRead > 255 )
252
+ bytesToRead = 255 ;
253
+ else
254
+ bytesToRead = bytesLeftToRead;
255
+ if (bytesToRead > i2cReadLimit)
256
+ bytesToRead = i2cReadLimit;
257
+
258
+ uint8_t bytesReturned = myWire.requestFrom (gnssAddress, bytesToRead);
259
+
260
+ for (uint8_t i = 0 ; i < bytesReturned; i++)
261
+ {
262
+ uint8_t b = myWire.read ();
263
+ if (ringBufferSpace (i2cUartBufferHead, i2cUartBufferTail) > 0 )
264
+ {
265
+ i2cUartRingBuffer[i2cUartBufferHead++] = b;
266
+ i2cUartBufferHead %= ringBufferSize; // Wrap-around
267
+ }
268
+ else
269
+ {
270
+ // Buffer is full
271
+ }
272
+ }
273
+
274
+ bytesRequested += bytesToRead;
275
+ bytesLeftToRead -= bytesReturned;
276
+ }
277
+
278
+ return (bytesLeftToRead == 0 );
279
+ }
280
+
281
+ // Read a single byte from the uartI2cRingBuffer
282
+ // This function assumes ringBufferAvailable has been called externally
283
+ // It will read a byte regardless
284
+ uint8_t readUartI2cBuffer ()
285
+ {
286
+ uint8_t b = uartI2cRingBuffer[uartI2cBufferTail++];
287
+ uartI2cBufferTail %= ringBufferSize; // Wrap-around
288
+ return b;
289
+ }
290
+
291
+ // Read a single byte from the i2cUartRingBuffer
292
+ // This function assumes ringBufferAvailable has been called externally
293
+ // It will read a byte regardless
294
+ uint8_t readI2cUartBuffer ()
295
+ {
296
+ uint8_t b = i2cUartRingBuffer[i2cUartBufferTail++];
297
+ i2cUartBufferTail %= ringBufferSize; // Wrap-around
298
+ return b;
299
+ }
300
+
301
+ // Calculate how many ring buffer bytes are available
302
+ uint16_t ringBufferAvailable (uint16_t head, uint16_t tail)
303
+ {
304
+ if (head == tail) // If the buffer is empty
305
+ return 0 ;
306
+ if (head > tail)
307
+ { // No wrap-around
308
+ return (head - tail);
309
+ }
310
+ // Use uint32_t to make the wrap-around easier
311
+ uint32_t h = head;
312
+ uint32_t t = tail;
313
+ const uint32_t s = ringBufferSize;
314
+ return (uint16_t )((h + s) - t);
315
+ }
316
+
317
+ // Calculate how much space is available in the ring buffer
318
+ // Buffer can hold (ringBufferSize - 1) bytes
319
+ // Buffer is empty when head == tail
320
+ // Buffer is full when head is one byte behind tail
321
+ uint16_t ringBufferSpace (uint16_t head, uint16_t tail)
322
+ {
323
+ return (ringBufferSize - (ringBufferAvailable (head, tail) + 1 ));
324
+ }
0 commit comments