rambo / TinyWire

My modifications to TinyWire Arduino libs
284 stars 121 forks source link

OnRequest handler #1

Closed lunanigra closed 11 years ago

lunanigra commented 11 years ago

Hello, it's more a question...

Trying to set up communication between an Arduino and an ATTiny 85. I've implemented an OnRequest handler and it works fine if just sending back one single byte.

If send back more bytes (e.g. calling TinyWireS.send twice) the whole I2C communication crashes.

Not sure if my approach is wrong or how to set up communication between Arduino and ATTiny 85 beyond exchanging single bytes.

Thanks a lot, JC

rambo commented 11 years ago

You can send only one byte per read request from the master, this is I2C level constraint/requirement. So send multiple bytes you need to track what bytes have been sent and send the next byte whenever a read-request comes in.

So first try https://github.com/rambo/TinyWire/blob/master/TinyWireS/examples/attiny85_i2c_slave/attiny85_i2c_slave.ino on the tiny and https://github.com/rambo/I2C/blob/master/examples/i2crepl/i2crepl.ino on the Arduino (the tiny sketch has the test transactions I have used)

rambo commented 11 years ago

I doublechecked the code, the send() method will put stuff to the send-buffer and return, but will block if the buffer is full, waiting for free space, hitting this block inside OnRequest handler will lead to bad results (as the handler will never return and the buffer will never get emptied. calling send() twice in the handler will soon lead to this situation...

OTOH when not using the event-oriented approach it's nicer that it will simply block untill buffer has free space, as this is the way the Wire library works as well.

lunanigra commented 11 years ago

Thanks for checking again. I will try to see if your sample code does what I am looking for. Right now I'm little confused as it seems the I2C implementations on Arduino, ATTiny and Raspberry PI are quite different. I've seen you have created an improved I2C impl. for Arduino.

I will short describe what I try to build... I want to connect different ATTiny modules to a Raspberry PI (or first Arduino). Each ATTiny holds a data array. The I2C master can request this array. It should be also possible that the I2C master sends two commands to the ATTiny.

  1. A new bus address to be stored in EEPROM.
  2. An update for the data record.

The event-oriented approach sounds quite elegant. But maybe I should try a simpler way first if I otherwise run in this blocking situation.

rambo commented 11 years ago

You probably need to use event-oriented to be able to write new bus addresses reliably, I'm not 100% sure I have actually tested this feature but I did code it here: https://github.com/HelsinkiHacklab/aircores/blob/master/driver/AirCore_ATTiny/AirCore_ATTiny.ino (currenly we're focused on the arduino shields capable of running 8 cores at the same time and it works mostly fine, sometimes the I2C gets stuck [the core positions are updated 4 times a second and there are 33 cores driven by 5 boards, and there's plenty of other traffic on the bus as well], check out: https://github.com/HelsinkiHacklab/reactor/)

Anyway I suggest you have a defined register structure with the data-array starting from the first index and automatic index incementing, like in the example sketch, then after the data have the address and any other calibration bit you wish to write to EEPROM and copy the idea from the aircore driver sketch.

Then to read the data array you would do (using the repl semantics and 0x4 as the 7-bit address): [ 8 0 [ 9 r r ] supposing two value array, add as many reads as needed to get full array. in longer form what is does is issue START then the write address for the slave, write 0 for the first index of the register then issue RSTART and the read-address for the slave and then do as many reads as needed and then STOP. The slave code will auto-increment the register index at each read.

Updating the data would be even simpler: [ 8 0 be ef ] this enters write mode, sets first index and then writes the data

In the aircore example setting the slave address to 0x5 (7-bit address) would be: [ 8 2 5 1 ] The new address should take effect in next reboot of the chip (which is surprisingly hard to do via software, using ASM and jumping to 0 is not the same thing as proper reboot)

The I2C master implementation is not by me, as stated in the description, but I have made some improvements and the upstream is not in any sensible repository so I maintain my fork... Anyways it's a lot better than Wire in so many ways. I have a raspberryPI but haven't yet had time to play with it much so can't help with implementing the I2C master on it.

lunanigra commented 11 years ago

Your multi-core Arduino concept sounds quite interesting :-)

Unluckily I'm missing some basics... Don't really get your REPL semantics.

So, I've tried a simpler approach first. And it doesn't look too bad... https://github.com/haddorp/playground/blob/master/ATTiny/i2c_slave.ino

The Arduino (later Raspberry PI) send a single byte as command identifier. For writing data further bytes will follow. Otherwise the ATTiny send back array data and appends a check sum. My first test has been success and no system crash.

But I assume my code isn't optimal and that there are some points to enhance.

rambo commented 11 years ago

cores as in air-core, which is a kind of a motor (very low torque but incredibly fast and can be driven to an absolute position without any external feedback, good for dials & gauges [often used when building flight-simulator cockpits])

The semantics are the same as with bus-pirate (a very handy tool but the I2C on it chokes if slave stretches the clock): [=START, hex-numbers for bytes to send, ]=STOP, r=READ, see "in longer form" in the post above for explanation.

Resetting via watchdog does not disable the watchdog, this can lead to a reset-loop if setup() does not disable the watchdog quickly enough (at least this is what I read somewhere when looking at strategies for resetting via sw).

The "usual" way to communicate with I2C devices is to first write the number of the register (or "command byte") you wish to read or write and then either just continue writing or issue a repeated-start and send the slave read-address and start reading. This is all by convention since the I2C protocol only specifies if the request is read or write, very simple devices for example can have only one register that is read-only.

Anyway, if your code works for you then all is good, it doesn't have to be "elegant"