forked from WhitehawkTailor/I2C-sniffer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main-send.cpp
383 lines (312 loc) · 10.4 KB
/
main-send.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
/**
@author Ákos Szabó (Whitehawk Tailor) - [email protected]
This is an I2C sniffer that logs traffic on I2C BUS.
IDE: Visual Studio Code + PlatformIO
Platform: Arduino for ESP32
Board: Heltec WiFi Lora32 v2
GPIO12 SDA
GPIO13 SCL
This is not connecting to the BUS as an I2C device. It is neither a Master, nor a Slave.
It just listens and logs the communication.
The application does not interrupt the I2C communication.
Two pins as input are attached to SDC and SDA lines.
Since the I2C communications runs on 400kHz so,
the tool that runs this program should be fast.
This was tested on an ESP32 bord Heltec WiFi Lora32 v2
ESP32 core runs on 240MHz.
It means there are 600 ESP32 cycles during one I2C clock tick.
The program uses interrupts to detect
the raise edge of the SCL - bit transfer
the falling edge of SDA if SCL is HIGH- START
the raise edge of SDA if SCL id HIGH - STOP
In the interrupt routines there is just a few line of code
that mainly sets the status and stores the incoming bits.
Otherwise the program gets timeout panic in interrupt handler and
restart the CPU.
v1.0
Basic operation and put message to eh serial output
v1.1
See additional parts marked with the version title v1.1
The additionals makes possible to send the logged I2C communication content to a server via HTTP request.
*/
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
//#define I2CTEST //use it to run a blinking LED test on SDA and SCL pins
#define PIN_SDA 12 //BLUE
#define PIN_SCL 13 //Yellow
#define I2C_IDLE 0
//#define I2C_START 1
#define I2C_TRX 2
//#define I2C_RESP 3
//#define I2C_STOP 4
static volatile byte i2cStatus = I2C_IDLE;//Status of the I2C BUS
static uint32_t lastStartMillis = 0;//stoe the last time
static volatile byte dataBuffer[9600];//Array for storing data of the I2C communication
static volatile uint16_t bufferPoiW=0;//points to the first empty position in the dataBufer to write
static uint16_t bufferPoiR=0;//points to the position where to start read from
static volatile byte bitCount = 0;//counter of bit appeared on the BUS
static volatile uint16_t byteCount =0;//counter of bytes were writen in one communication.
static volatile byte i2cBitD =0;//Container of the actual SDA bit
static volatile byte i2cBitD2 =0;//Container of the actual SDA bit
static volatile byte i2cBitC =0;//Container of the actual SDA bit
static volatile byte i2cClk =0;//Container of the actual SCL bit
static volatile byte i2cAck =0;//Container of the last ACK value
static volatile byte i2cCase =0;//Container of the last ACK value
static volatile uint16_t falseStart = 0;//Counter of false start events
//static volatile byte respCount =0;//Auxiliary variable to help detect next byte instead of STOP
//these variables just for statistic reasons
static volatile uint16_t sclUpCnt = 0;//Auxiliary variable to count rising SCL
static volatile uint16_t sdaUpCnt = 0;//Auxiliary variable to count rising SDA
static volatile uint16_t sdaDownCnt = 0;//Auxiliary variable to count falling SDA
//v1.1
//Store the begining of the communication string. Modify it according to your setup.
String msgToServer = "http://192.168.0.19:5544/I2cServer?ADDR=0100000&data=";
const char* ssid = "ssid";
const char* password = "pass";
const int deviceID = 1;
////////////////////////////
//// Interrupt handlers
/////////////////////////////
/**
This is the rising SCL interrupt handler
Rising SCL makes reading the SDA
*/
void IRAM_ATTR i2cTriggerOnRaisingSCL()
{
sclUpCnt++;
//is it a false trigger?
if(i2cStatus==I2C_IDLE)
{
falseStart++;
//return;//this is not clear why do we have so many false START
}
//get the value from SDA
i2cBitC = digitalRead(PIN_SDA);
//decide wherewe are and what to do with incoming data
i2cCase = 0;//normal case
if(bitCount==8)//ACK case
i2cCase = 1;
if(bitCount==7 && byteCount==0 )// R/W if the first address byte
i2cCase = 2;
bitCount++;
switch (i2cCase)
{
case 0: //normal case
dataBuffer[bufferPoiW++] = '0' + i2cBitC;//48
break;//end of case 0 general
case 1://ACK
if(i2cBitC)//1 NACK SDA HIGH
{
dataBuffer[bufferPoiW++] = '-';//45
}
else//0 ACK SDA LOW
{
dataBuffer[bufferPoiW++] = '+';//43
}
byteCount++;
bitCount=0;
break;//end of case 1 ACK
case 2:
if(i2cBitC)
{
dataBuffer[bufferPoiW++] = 'R';//82
}
else
{
dataBuffer[bufferPoiW++] = 'W';//87
}
break;//end of case 2 R/W
}//end of switch
}//END of i2cTriggerOnRaisingSCL()
/**
This is the SDA interrupt handler
This is for recognizing I2C START and STOP
This is called when the SDA line is changing
It is decided inside the function wheather it is a rising or falling change.
If SCL is on High then the falling change is a START and the rising is a STOP.
If SCL is LOW, then this is the action to set a data bit, so nothing to do.
*/
void IRAM_ATTR i2cTriggerOnChangeSDA()
{
//make sure that the SDA is in stable state
do
{
i2cBitD = digitalRead(PIN_SDA);
i2cBitD2 = digitalRead(PIN_SDA);
} while (i2cBitD!=i2cBitD2);
if(i2cBitD)//RISING if SDA is HIGH (1)
{
i2cClk = digitalRead(PIN_SCL);
if(i2cStatus=!I2C_IDLE && i2cClk==1)//If SCL still HIGH then it is a STOP sign
{
//i2cStatus = I2C_STOP;
i2cStatus = I2C_IDLE;
bitCount = 0;
byteCount = 0;
bufferPoiW--;
dataBuffer[bufferPoiW++] = 's';//115
dataBuffer[bufferPoiW++] = '\n'; //10
}
sdaUpCnt++;
}
else //FALLING if SDA is LOW
{
i2cClk = digitalRead(PIN_SCL);
if(i2cStatus==I2C_IDLE && i2cClk)//If SCL still HIGH than this is a START
{
i2cStatus = I2C_TRX;
//lastStartMillis = millis();//takes too long in an interrupt handler and caused timeout panic and CPU restart
bitCount = 0;
byteCount =0;
dataBuffer[bufferPoiW++] = 'S';//83 STOP
//i2cStatus = START;
}
sdaDownCnt++;
}
}//END of i2cTriggerOnChangeSDA()
////////////////////////////////
//// Functions
////////////////////////////////
/**
Reset all important variable
*/
void resetI2cVariable()
{
i2cStatus = I2C_IDLE;
bufferPoiW=0;
bufferPoiR=0;
bitCount =0;
falseStart = 0;
}//END of resetI2cVariable()
/**
@desc Write out the buffer to the serial console
*/
void processDataBuffer()
{
if(bufferPoiW == bufferPoiR)//There is nothing to say
return;
uint16_t pw = bufferPoiW;
//print out falseStart
Serial.printf("\nprocessDataBuffer\nSCL up: %d SDA up: %d SDA down: %d false start: %d\n", sclUpCnt, sdaUpCnt, sdaDownCnt, falseStart);
//print out the content of the buffer
for(int i=bufferPoiR; i< pw; i++)
{
Serial.write(dataBuffer[i]);
bufferPoiR++;
}
//if there is no I2C action in progress and there wasn't during the Serial.print then buffer was printed out completly and can be reset.
if(i2cStatus == I2C_IDLE && pw==bufferPoiW)
{
bufferPoiW =0;
bufferPoiR =0;
}
}//END of processDataBuffer()
/**
v1.1
@desc This method returns a String that contains everything that is available in the dataBuffer
This function also resets the buffer, so this and processDataBuffer cannot be used in the same time.
@ret String value. Returns the whole content of the dataBuffer as a string.
*/
String getStringFromDataBuffer()
{
String ret = "";
if(bufferPoiW == bufferPoiR)//There is nothing to say
return "";
uint16_t pw = bufferPoiW;
//print out falseStart
Serial.printf("\ngetStringFromDataBuffer\nSCL up: %d SDA up: %d SDA down: %d false start: %d\n", sclUpCnt, sdaUpCnt, sdaDownCnt, falseStart);
//print out the content of the buffer
for(int i=bufferPoiR; i< pw; i++)
{
ret += (char)dataBuffer[i];
bufferPoiR++;
}
//if there is no I2C action in progress and there wasn't during the Serial.print then buffer was printed out completly and can be reset.
if(i2cStatus == I2C_IDLE && pw==bufferPoiW)
{
bufferPoiW =0;
bufferPoiR =0;
}
return ret;
}//END of processDataBuffer()
/**
v1.1
This is an empty method.
Write here any communication with a server
*/
void sendMsgOut(String argStr)
{
argStr.replace(" ","");
Serial.println();
Serial.println(argStr);
Serial.println();
}
/////////////////////////////////
//// MAIN entry point of the program
/////////////////////////////////
void setup()
{
Serial.begin(115200);
#ifdef I2CTEST
pinMode(PIN_SCL, OUTPUT);
pinMode(PIN_SDA, OUTPUT);
#else
//Define pins for SCL, SDA
pinMode(PIN_SCL, INPUT_PULLUP);
pinMode(PIN_SDA, INPUT_PULLUP);
//pinMode(PIN_SCL, INPUT);
//pinMode(PIN_SDA, INPUT);
//reset variables
resetI2cVariable();
//Atach interrupt handlers to the interrupts on GPIOs
attachInterrupt(PIN_SCL, i2cTriggerOnRaisingSCL, RISING); //trigger for reading data from SDA
attachInterrupt(PIN_SDA, i2cTriggerOnChangeSDA, CHANGE); //for I2C START and STOP
//v1.1 Extension for WiFi sending
WiFi.begin(ssid, password);
Serial.println("Connecting");
while(WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
#endif
}//END of setup
/**
LOOP
v1.0
@desc Writes I2C mcommunication to the serial if there is any.
v1.1
@desc uses sendMsgOut to send I2C mcommunication to the specified server and format given by the msgToServer variable
*/
void loop()
{
#ifdef I2CTEST //do this in case it is testing the lines
digitalWrite(PIN_SCL, HIGH); //13 Yellow
digitalWrite(PIN_SDA, HIGH); //12 Blue
delay(500);
digitalWrite(PIN_SCL, HIGH); //13 Yellow
digitalWrite(PIN_SDA, LOW); //12 Blue
delay(500);
#else
//if it is in IDLE, then write out the databuffer to the serial consol
if(i2cStatus == I2C_IDLE)
{
//v1.0
//processDataBuffer();
//v1.1
//Get the content of the dataBuffer as a string and put it into a message
//Send out the message
//make sure that the msgToServer string variable is set properly above in the global variable section.
sendMsgOut( msgToServer + getStringFromDataBuffer());
//Delay the loop
Serial.print("\rStart delay ");
delay(5000);
Serial.print("\rEnd delay ");
delay(500);
}
#endif
}//END of loop