I made a board with an Arduino nano and a ESP32 Dev kit. I connected them via I2C with voltage adaptation. I'm using arduino IDE and the related library Wire.h.

I send messages as ascii chars or native bytes from the ESP32 (master) to the Arduino (slave) and all work fine.

I have to take a decision now. I have to implement a simple protocol to send command to the Arduino, in order to ask it do some tasks or in order to ask it to send some data to the esp32 (e.g. the speed of a motor or the state of some sensors and so on).

What I would ask is your suggestion if the simple protocol has to be managed as native bytes (the natural way I2C work) or as string (managed by mean of an array). I try to explain.

In case of bytes I could send for example FF 0E14 01 to ask arduino to start the motor=FF at the speed=0E14 and direction=01. So the specific command is coded in the first byte.

What I miss is the need or not for a delimiter like "<" and ">" as used in case of string. In case of exchange of native bytes the value of "<" is "busy" in an integer and couldn't be distinguish by the parser. So what delimiter could I use?

On the other hand in case of strings I don't like the necessity to convert number "213" or "1529" in the relative integer. In case of the previous command I could send the string


having in mind the first char as command and for this specific command "S" ("S" for example) parsing the following chars until the "," and convert it in a integer and last char as direction (as byte or as char the test is the same).

I don't know... what way do you suggest and in case of native bytes what delimiters?


  • That‘s quite opinion based. To me it seems like you‘ve already figured out the pros and cons of i2c. I personally don’t recommend using a string-like protocol on i2c and stick with the common i2c protocol. You don‘t need start/stop bytes in this case
    – Sim Son
    Commented Jan 30, 2020 at 8:36

2 Answers 2


First, using a byte-value based protocol is the standard with I2C. There is no real reason to go to ASCII encoded data (The reason for doing this with Serial (UART) is normally, that the message is human readable at the PC. This is normally not the case with I2C, since only computers like the Raspberry Pi even have I2C interfaces).

About delimiters: They are only really needed for asynchronous data transfers (as UART)(to frame a full packet/message) or maybe, if you have an unknown transmission size. Both is not the case for I2C (it is a synchronous interface, that already implements transmission packages/message). You can look at the datasheet of any I2C sensor. The standard communication protocol is a command send by the master, then sending/receiving a specific number of bytes with a specific protocol, that defines, which bytes contains which information. Here you don't need a delimiter, because you already know, in what format the data will be send.

Let's take an example from your question: You want the Arduino to report the speed of your motor to the ESP. You implement the first byte of every master write transmission as a command, for example the decimal value 10 as command to report the motor speed. Then you terminate the master write transmission and request 1 byte from the Arduino (since you already know, that the motor speed has 1 byte). The Arduino knows through the command, that it should send the motor speed byte, and the ESP knows, that the byte received will be the 1 byte motor speed.

Now we define the command 11 to report the motor speed and the 3 bytes sensor value. The ESP sends 11 and then requests 4 bytes, as it knows, that the message will be 4 bytes long. The Arduino sends the data and the ESP knows, that the first byte will be the motor speed and the following 3 bytes the sensor value. No delimiter is needed, since the structure of your data is already defined through the implementation of your protocol. Start or end markers for a message are not needed, since I2C already works with distinct message/packets of data (a transmission is started with a START condition on the bus and ended with a STOP condition).

This solution is used for most I2C devices, since it is easier to implement at a low level. At the first glance for us humans it seems easier to use ASCII encoded and thus human readable data. But as you already mentioned, this makes it more complex for microcontrollers (writing parsers for ASCII data is more complex than using binary data on a fixed protocol). Keep in mind, that I2C means "Inter Integrated Circuit", so communication between integrated circuits, not communication between an integrated circuit and a human.

But in the end it is up to you to decide, which protocol you want to use.

As you might not know the concept yet: A Finite State Machine (FSM) is a good way to implement such a communication protocol on both sides. You define a state variable and based on this variable only the corresponding code is executed. You can change the state by changing the state variable.

For example on the Arduino (receiver side): First state could be the command state, where the Arduino waits for an I2C transmission. If a transmission was received, it will read it's first byte. Depending on this byte it will go into a different state, for example into the "Send motor speed" state. There it will prepare everything to send the motor speed to the master, should the master request data from the Arduino.

How to implement a FSM in detail is covered in many tutorials on the web and also in many questions on this side. You can see my answer to this question, where I explained this in more detail.

  • I appreciate all the details chrisl. I can't vote your answer as I am with low reputations yet. I will proceed as you suggested.
    – daigs
    Commented Jan 31, 2020 at 22:30
  • The only concern is about the position of the parser or finite state machine. Since the function "void receiveEvent(int howMany)" is called as an ISR routine following an interrupt, it should be as small as possible. So I shouldn't write the parser inside it. How to deal with this problem? Another question is: could I use the howMany number of byte to understand the correct length of the incoming current packet of byte? Thanks.
    – daigs
    Commented Jan 31, 2020 at 22:48
  • If the processing of the message would be too long for an ISR, the normal way is to buffer the message in an extra buffer and set a flag inside the ISR. This flag get's polled inside loop() and the processing is done there, when the flag is set.
    – chrisl
    Commented Jan 31, 2020 at 23:55
  • Yes, the parameter howMany is the number of data bytes from the transmission, e.g. the number of bytes being received.
    – chrisl
    Commented Jan 31, 2020 at 23:56
  • You can accept my answer, if you think it is correct. You don't need reputation for that. And you are the only one, that can accept an answer, since you asked this question.
    – chrisl
    Commented Jan 31, 2020 at 23:57

Before giving pointers to your question; First I will start to recommend to avoid using i2C for these kind of things and recommend UART (Serial) which is much better for communication between multiple mcu's.

The UART) Serial Gives you a stack (memory of received data) meaning that you receive data when processing other functions and act on them in arduinos' own time. UART is more then fast enough for things like this and enables longer distances then few cm, its more resilient to interference
UART (Serial) uses fewer ports and most ports can be converted into Software Serial
You will find many libraries which deal with communication even encryption over Serial
If you use write() rather then print you don't have to convert the data.
if you use correct input on your arduino, you can even read the data as it comes from your PC and much more

Anyway, to your question Most of the data communications tend to have a starting byte and the ending byte to package the pocket among common ones is 00-Start and 0xFF as end.
Now assuming that we only have one slave and single priority level etc we don't need to be too much of smartasess for package header... next I would define Process as it will let you chose the function that then can receive then next details as variables.
This option means that you will have to number the operations from 1 not 0
Major benefit is that as different function may take different number of variables (for example one only states on off, while other states vector distance) you will not have to decide on a packet length, but simply deal with any number of variables (within reason)

  • As you can‘t use all numbers between 0-255 on uart (at least you need a stop byte) you‘ll always have to convert data to a char array representation. Also asynchronous is not always an advantage as it makes a parser necessary
    – Sim Son
    Commented Jan 30, 2020 at 8:33
  • 1
    I don't get many of your points against I2C. The I2C libraries have a "stack" (which is actually a FIFO buffer, not a stack) just like the Serial library. I2C uses 2 pins just like UART. Libaries for encryption normally handle the data itself; over which interface the data is send is irrelevant for them. write() sends binary data for both interfaces; no difference here. I don't see, why you would need start and end bytes for an I2C transmission (as it already has a START and an END condition; it is already send in a packet). Serial is good for comm between 2 MCU's. More is rather difficult
    – chrisl
    Commented Jan 30, 2020 at 13:15
  • 1
    Also I don't understand everything after "Now assuming that". I just don't get the point, what you want to say there. Can you please explain, what the core point there is?
    – chrisl
    Commented Jan 30, 2020 at 13:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.