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