Skip to content

Commit 98c384d

Browse files
committed
Create Example34_I2C_Serial_Passthrough.ino
1 parent 6cdd0e5 commit 98c384d

File tree

1 file changed

+324
-0
lines changed

1 file changed

+324
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
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

Comments
 (0)