HardWire, the enhanced Arduino Wire library

cut_ST-3402W-HD.jpg

I have spent a lot of time on playing and later working on the I2C buses. I almost always used self developed libraries, both for learning and job requirement purposes. Then, when experimenting with a portability of a project in the Arduino environment, I started to trying the official Wire library, discovering some incompatibilities for my needs.

When the limitations were somehow fixed, even though the small number of modifications (it is somehow a sort of hacking of the library) I found that the my final result were so useful to me that I thought was worth to write a short article about it. But let’s start with the problem found in the Wire.

Arduino real-time buffer problem

I realized that if I wanted to make an Arduino slave, when receiving from master, it actually reads from a buffer that is already previously received. This make the communication not byte to byte controlled as I want it to be for my need.

In other words, basically when you read:


while (1 < Wire.available()) { // loop through all but the last
char c = Wire.read(); // receive byte as a character
Serial.print(c); // print the character
}

means that you will read everything that was already sent from the master. So you cannot signal to the master to stop the ongoing physical communcation in case the slave cannot receive more bytes, or you cannot take decisions depending on what byte is received.

But more generally, in this way the Atmega328 (what I have used) cannot behave like any dedicated I2C slave device: i.e. like an RTC clock, an EEPROM, which actually are making their decisions based on the current byte currently received from the bus, not after such transaction has been finished. I wanted to do the same.

In other words…

A slave Arduino cannot behave like any other dedicated slave device: this is not fair. Join the cause.

The solution is to have a library that allow me to do things in the hard way. The wire made hard. The wire more close to the hardware in a simple way. I called it the Hard Wire. Because somehow allow me to made the microcontroller to behave as I want at very low level in the I2C communication, obtaining the equivalent of an hard wired slave device.

I’ve just added to the Wire library various “probes” inside each step of the I2C data exchange. As you may know the I2C data exchange is made my many steps, where the START and STOP are just the starting and ending point of a bus cycle:

command.gif
A typical I2C bus cycle in which are transmitted (or received) two bytes

The hardware of a slave can interact and decide to do something after a START condition, ADDRESS, R/#W mode, choose if send or not the ACK, decide on run time what DATA to send and so on. When doing complex stuff and being interfaced as a slave in the I2C, can be useful to decide an action during these I2C bus steps.

With the conventional Wire library, you can take action only after the STOP or when addressed. In my projects, when used an Arduino, I need to take different action potentially every time I am in the following states:

  • Addressed in slave read mode
  • Addressed in slave write mode
  • ACK received after every byte sent
  • NACK received after byte is sent
  • ACK sent
  • START
  • STOP
  • RESTART

Then I decided to keep things simple, to re-adapt the already tested Wire library, which already handle the underlined green states.

Arduino real-time solution: runtime control

To countermeasure those kind of problems, I have placed functions inside each state of the I2C bus (those functions are the handlers, called when such I2C state happens). So that, over than the usual functions, the user can set up a fully working slave system implementing a few of other function than the usual onReceive and onRequest, where a particular action can be taken just after a byte is received or sent from the slave, and not only after we have reached the STOP condition. All by still using the same methods of the official Wire library!

How to use it

Handlers

Basically you can follow the official Wire guide. But this hard version also supports the following handlers assigned during the initialization (are listed also the official ones):

  •  Wire.onReceive(handler) -> handler takes the number of bytes received, here the data buffer can be read, since the master has terminated the communication (official from Wire)
  •  Wire.onRequest(handler) -> handler takes no parameters, but when called the slave prepares data to be sent using the standard Wire methods available, like read() and write() and whatever (official from Wire)
  •  Wire.onReceiveData(handler) -> handler takes last received byte for the slave runtime computation, but will also fill the data buffer with that byte, as the official Wire does transparently (not present in Wire, new in the WireHard)
  • Wire.onReceiveDataNack(handler) -> handler takes no parameters but is used to notify the slave, since this received byte shall not be used (not present in Wire, new in the WireHard)
  • Wire.onReceiveAdx(handler) -> handler takes no parameters, but the slave is notified to prepare itself to receive data in the next bus transaction, the official Wire does it transparently (not present in Wire, new in the WireHard)
  • Wire.onRequestData(handler) -> handler returns a byte to be sent to master. This byte can be generated from the slave in the handler at runtime without using the shared buffer. The official Wire does it transparently, retrieving the data from the buffer (not present in Wire, new in the WireHard)
  • Wire.onRequestDataNack(handler) -> handler takes no parameters. Used when the master still requires data which is not available from the shared buffer. It is done transparently in the official Wire (not present in Wire, new in the WireHard)

All the functions marked as new in the WireHard are the core difference between the official Wire library. The normal Wire handle them automatically, always reading or writing in the shared buffer, transparently to the user.

Initialization

Again, follow the official Wire guide. But this version supports a HARD_WIRE_MODE that shall be notified in the init. If mode is omitted, it is initialized following the normal Wire initialization and the additional handlers are still supported, but is not mandatory to use them. Actually, is never a mandatory to use the additional handlers.

  • Wire.begin(slaveAddress, HARD_WIRE_MODE)  -> will be supported all the additional handlers, with an undefined (vitually infinite) number of read/write cycles, as any I2C slave device would do.
  • Wire.begin(slaveAddress, NORMAL_MODE)  -> will be supported all the additional handlers, but when the shared buffer is full, it needs to be flushed or read before accepting any new incoming byte: this behavior is the same as the Wire library.

Hope this can be useful to someone as it was to me. More info and example on the GitHub page.

Download from GitHub.

Advertisements

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...