Go Down

Topic: OneWire library with overdrive support (Read 6237 times) previous topic - next topic

final

Hi,

I've modified  Paul Stoffregen's OneWire library (version 2.2) to support OneWire overdrive. The credit should go mostly to Patrik Jonsson who implemented most of this onto an older version of Paul's library. All I did is merge the two libs and fix some timing issues. Patrik Jonsson's version can be found here.

I figured I'd share this with you and hope that some other people will test it and contribute to an even more stable overdrive communication. I also hope that Paul will merge this back into his next version if it proves to be stable.

General info about OneWire overdrive
Overdrive mode is about 6-7 times faster than normal mode. This is handy if you have many sensors that need frequent polling. With overdrive, a DS2408 (8 channel switch) or DS2413 (2 channel switch) can be polled in roughly 1.1ms, compared to ~7ms without overdrive. Non-overdrive and overdrive slaves can be operated on the same bus, but it needs some consideration (see separate section).

How overdrive works
Not all OneWire slaves support overdrive. If it does, a slave can be put into overdrive mode in two ways, namely:
- send 0x69 (Overdrive Match ROM command) in normal speed followed by the 8-byte ROM identifier in overdrive speed. This is done by the new function select_and_overdrive (or of course, manually).
- send 0x3c (Overdrive Skip ROM command) in normal speed. This immediately puts all overdrive capable slaves on the bus into overdrive mode.
Once a slave is in overdrive mode, all subsequent communication must take place at overdrive speed (especially future Overdrive Match ROM commands must be sent at overdrive speed). The only way to take a slave out of overdrive mode is performing a normal speed reset.

How do I know which slaves are overdrive capable?
Perform a Overdrive Skip ROM command followed by a search at overdrive speed. Only overdrive capable slaves will answer to the search.

Non-overdrive and overdrive devices on the same bus
Communication to a non-overdrive slave need to begin with a normal speed reset, which will take all overdrive devices out of overdrive mode. Therefore, at the end of communication with a normal speed slave, you should execute another normal speed reset, followed by an Overdrive Skip ROM command (at normal speed) to put all overdrive devices back into overdrive mode.

Reference
See OneWire.h and the example for details. Basically, most commands (select, read, write, read_bytes, write_bytes, reset, skip) now require an additional bool overdrive parameter, indicating whether the command should be executed at overdrive speed or not. Here's the quick list:


Development and Testing
OneWire overdrive is very timing sensitive (we sometimes need timings in the single microsecond area). I've optimized the timings to work with an Arduino DUE, which has a 84MHz processor - it's possible that slower Arduinos will need slightly different (i.e. shorter) delays because they will need more time to process the "overhead" of communication and have less time for waiting. Once we understand the timings and processing overheads a little better - with your testing feedback -, we can adjust the lib's timings, perhaps even to be based on the processor clock frequency.

The timings are found in structs in OneWire.h

I've tested the library with an Arduino DUE on a OneWire bus of 80meters and four overdrive slaves (master at 0, client1 at 20 meters, client2 at 40, client3 at 60 and client4 at 80 meters distance - the wire was a very cheap unshielded 0.2mm2 ISDN wire) over a period of 24 hours with about 800 read/write commands per second. It was relatively stable (see error checking and correction section). My data-bus was powered by the DUE's 5V outlet with a 4.7k Pull-Up resistor. This lead to a ~3.5V input voltage on the DUE's analogue in port, which is slightly out of the DUE's specs - this was more stable than using the 3.3V output for long bus distances. A higher pullup resistor was suggested by Dallas for long buses, which would potentially reduce the input voltage further. Note: This wiring could damage your DUE.

Error checking and Correction
On average, about .01% of all commands failed (this is about 1 command every minute). However, the distribution of failures was very uneven. Sometimes I had no errors for hours, sometimes I just received a single CRC error, sometimes the bus kind of "locked up" and half to all following commands failed. In my testing, I implemented a "full reset" routine, to be triggered after 5 consecutive errors, which performed 2 normal speed resets with 5ms of delay in between them, before executing another Overdrive Match ROM command. This helped to get the slaves working again - once, a few of those resets were necessary. It might also be effective to perform a very long reset (i.e. pulling the bus to low for 5ms or so), though I haven't tested this.

All in all, it's crucial that you check the responses of your slaves (for DS2413 / DS2408, writings: do you get a proper 0xAA back? readings: is the read-back correct and/or does the CRC16 match). This is of course true for both overdrive and non-overdrive communication.

Debug the timings
It's important to know what kind of errors you get.
- if you get only 1s as answer (the answer-byte is 0xff), it's likely that the slave hasn't received your command or thinks the command is not finished yet. This means that the slave won't answer at all, never pull the bus to low, and the Arduino will only read 1s. Also, your tW1L might be too long.
- if you mistake some 1s for 0s, your tMSP is probably too short (or your pull-up resistor too slow).
- if you mistake some 0s for 1s, your tMSP is probably too long.

Hope some of you find this helpful,

final

ntruchsess

now that's cool :-)
I hope you don't mind if I merge this with my enhancements to OneWire-library on Github and include this in OneWire-feature of ConfigurableFirmata if I can proove this to runs stable.

- Norbert

ntruchsess

At a first glance:

I'm not sure whether it's smart to add the overdrive-parameter to all methods as this breaks backwards-compatibility with existing code. Also in terms of execution-time it also might be more efficient to add a method 'overdrive(bool overdrive)' to swich overdrive on/off and not pass an additional byte on the stack on every bit to write, but I have to test this as the compiler might optimize by leaving this in a register bypassing the stack anyway.

- Norbert

robtillaart


It would be great to have a small scan-program that can see if a specific DS device supports overdrive.
(like those I2C scanners)

Does the most discussed sensor temperature sensor DS18B20 support it? 
(would be useful in asynchronous mode)

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

final

#4
Nov 29, 2013, 08:50 pm Last Edit: Nov 29, 2013, 09:05 pm by final Reason: 1
@Norbert: I thought about this, too. In order to keep backwards compatibility, one could simply put the overdrive parameter as the last parameter of each function and default it to false. This wouldn't break any old code.

One could also introduce, say, a global var __overdrive that would determine the communication speed.

@robtillaart: the DS18B20 does not support overdrive - though it wouldn't make much sense in that particular device, since it takes ~700ms for it to convert the temperature. Not much difference if reading a temperature takes 714ms (700ms conversion time and two communication interaction) or 702ms (if the two interactions take place at overdrive speed). Usually, it's sufficient to take the temperature once a minute or so.

I think the main reason for using overdrive is if you have a time-critical sensor - say, a light switch - that you need to poll as often as possible

robtillaart


@robtillaart: the DS18B20 does not support overdrive - though it wouldn't make much sense in that particular device, since it takes ~700ms for it to convert the temperature. Not much difference if reading a temperature takes 714ms (700ms conversion time and two communication interaction) or 702ms (if the two interactions take place at overdrive speed). Usually, it's sufficient to take the temperature once a minute or so.

I have added async commands to the Dallas Temperature Control Library (v. 3.7 iirc) which does not wait for the conversion.
It now has a command to start the conversion, a command to check if conversion ready, and a read command.
This way it does not block for 700 millis (@12bit), I have only the interaction of (your numbers) 14 milliseconds.
If I can bring that back to 2-3 milliseconds would be a gain ~5x.

The major added value:
As the One wire lib enables/disables interrupts (several times see code) to keep timing for the handshakes right an overdrive mode would minimize the time interrupts are disabled.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Markus_L811

Really nice, did you test it with an Teensy 3 allready?

May I bring the overdrive inside the OneWire Slave Library some day but I think an 16Mhz AVR would not fit it, so it would only make sense for an Due or Teensy 3 and above

Markus_L811

So the overdrive implementation  is in progress...

Markus_L811

#8
Jun 17, 2014, 05:00 pm Last Edit: Jun 17, 2014, 05:15 pm by Markus_L811 Reason: 1
Hi final,

I study the lib from you. I think at this Part there is something wrong.
Quote

uint8_t OneWire::reset(bool overdrive)
{
   IO_REG_TYPE mask = bitmask;
   volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg;
   uint8_t r;
   uint8_t retries = 125;

   noInterrupts();
   DIRECT_MODE_INPUT(reg, mask);
   interrupts();
   // wait until the wire is high... just in case
   do {
      if (--retries == 0) return 0;
      //delayMicroseconds(2);
      delayNanoseconds<2000>();
   } while ( !DIRECT_READ(reg, mask));

   noInterrupts();
   DIRECT_WRITE_LOW(reg, mask);
   DIRECT_MODE_OUTPUT(reg, mask);   // drive output low
   interrupts();
   //delayMicroseconds(480);
   if(overdrive)
      delayNanoseconds<overdrive_timings::t_rstl>();
   else
      delayNanoseconds<normal_timings::t_rstl>();
   noInterrupts();
   DIRECT_MODE_INPUT(reg, mask);   // allow it to float
   //delayMicroseconds(70);
   if(overdrive)
      delayNanoseconds<overdrive_timings::t_msp>();
   else
      delayNanoseconds<normal_timings::t_msp>();
   r = !DIRECT_READ(reg, mask);
   interrupts();
   if(overdrive)
      delayNanoseconds<overdrive_timings::t_pdh+
             overdrive_timings::t_pdl+
             overdrive_timings::t_rec-
             overdrive_timings::t_msp>();

   else
      //delayMicroseconds(410);
      delayNanoseconds<normal_timings::t_pdh+ // 60000
             normal_timings::t_pdl+             //240000
             normal_timings::t_rec-             //  5000
             normal_timings::t_msp>();          // 70000

   return r;
}

In the red part here is the wrong timing for delay there must stand ::t_rsth - ::msp not the parts from ::t_pdh + ::t_pdl - ::t_msp. Also wrong in the library from Patrik Jonsson.

Based on APP126 from Maxim/Dalllas t_rsth takes 480us and after 70us in this range the master takes a sample

So this musst be the correct version.

Quote

....
      delayNanoseconds<normal_timings::t_msp>();
   r = !DIRECT_READ(reg, mask);
   interrupts();
   if(overdrive)
      delayNanoseconds<overdrive_timings::t_rsth-
             overdrive_timings::t_msp>();

   else
      //delayMicroseconds(410);
      delayNanoseconds<normal_timings::t_rsth- //  480000
             normal_timings::t_msp>();           //   70000

   return r;
}

final

Ok, I'm taking this out of the DS2413 datasheet (http://datasheets.maximintegrated.com/en/ds/DS2413.pdf, page 13). For reference (my own mostly :) ):
tPDH = 2 - 8.2us
tPDL = 8 - 32us
tMSP = 9.1 - 10us
tREC = 2 - 5us

The code in question is for calculating the remainder of the tRSTH as seen in the diagram of page 13 (we have already done the reset pulse, waited for tMSP and read the precense signal). According to the datasheet, it says:

Quote
The tRSTH windows must be at least the sum of tPDHMAX, tPDLMAX and tRECMIN

we have already waited for tMSP before, so the time we have to wait is (roughly) tPDH + tPDL + tREC - tMSP, which is exactly what the code you marked red is doing.

tRSTH is not defined in the datasheet as a value, but only be the passage I've quoted. So I guess it should be defined as 8.2 + 32 + 2 = 42.2us. In my library, it's defined with 48us, which is "wrong", but the "at least" point should not affect functionality. But I guess this can be reduced to 43us. So, tRSTH should be at least 43us

The problem seems to be that I don't take the max values but the "middle" values. with the values in my library, we have tPDH + tPDL + tREC = 7 + 27 + 2 = 36 < 43! eeeh, bug!

Your solution waits for 48us, which is >=43, and therefore works

So, after all this writing (which I needed mostly for myself to get my head back into OneWire): You're absolutely right, Markus. This is a bug and it should be fixed. Maybe, while you're on it, also set tRSTH to 43us. According to the datasheet, this should still work. If you still have issues, go back to 48us.

Hope this helps,
final

Go Up