PDA

View Full Version : Has anyone got the DS3234 SPI RTC working on the Teensy 3? (Beta8)



t3andy
11-26-2012, 01:13 AM
Has anyone got the DS3234 SPI RTC working on the Teensy 3? (Beta8)

I have tried two different libraries (Sparkfun's example and maniacbug) and both produced garbage.:confused:

el_supremo
11-26-2012, 01:49 AM
The Sparkfun example code works for me after I change the definition of the cs pin from 8 to 10 (second line of the code).

Pete

t3andy
11-26-2012, 02:10 AM
@el_supremo
What beta release were you using?
Are you using a breadboard with the DS3234?
What is your SPI speed for the DS3234?

el_supremo
11-26-2012, 02:50 PM
beta 8
I'm using the Sparkfun Dead-on RTC on a breadboard.
SPI speed was whatever the Sparkfun example uses - presumably the default

Pete

t3andy
11-26-2012, 03:34 PM
Thanks Pete

I finally got the SPI DS3234 RTC working intermittently using solid connections (no breadboard). The breadboard adds stray capacitance to SPI signals.
In the Sparkfun example, I had to change the SPI mode from 1 (default) to SPI mode 3. Default SPI Mode 1 never did work for me?
SPI signal "wire separation" or shielding is very critical in making this device work?

Conditions:
Teensy 3
Beta8
SPImode3
Chip select 9 or 10
Device voltage used from K20 3.3 VDC 100ma regulator
"default" SPI clock?
Sparkfun DS3234 code example

Hope this helps others trying to make this device work on the Teensy 3.

el_supremo
11-26-2012, 03:49 PM
I use SPI mode 1 and the chip is powered from the T3's 3V3. This is on a ratsnest breadboard with several other things as well. If stray capacitance was a problem this wouldn't have worked at all :D
[edit] However, I have previously had problems with breadboards which always came down to poor connections. Moving the circuit to a new breadboard always, so far, solved the problem.

Pete

Constantin
12-06-2012, 11:33 PM
I much prefer the i2c version sold in many pre-assembled forms, such as the Chronodot.

t3andy
12-14-2012, 02:26 PM
DS3234 revisited ... "The dead-on RTC" SPI DS3234 from Sparkfun on the Teensy 3 (Beta 9).

We just received "another" SPI DS3234 real time clock from Sparkfun and now
both RTCs fail to read or write reliably on the Teensy 3. We used the same sparkfun code
snippet and solid wiring connections (no breadboard) on both the Teensy 3 and on the
"standard AVR" Teensy 2 and it seems that it only works on the AVR Teensy 2?
It just could be a code timing issue or Teensy 3 I/O speed problem that needs to be
tweeked to make the RTC work on the Teensy 3.

Both SPI modes 1 & 3 work flawlessly on the Teensy 2. Since we don't have an expensive
digital logic analyzer, at our disposal, then we will drop back in using a I2C RTC on the Teesny 3.

Note: SPI digital I/O waveforms are critical in making it work. Since we were able to use
a high speed SPI SD datalogger on the Teensy 3 then its looking like a timing issue related
to using a DS3234 SPI RTC on the Teensy 3? :confused:

Note: The above thread is not correct in saying the DS3234 RTC was working reliably on the Teensy 3 for us:o


BTW ...


I much prefer the i2c version sold in many pre-assembled forms, such as the Chronodot.

DS3234 "dead-on" RTC SPI @ 4 MHz would run circles around the Chronodot at 100Khz

Constantin
12-14-2012, 08:33 PM
Well, if running circles is why you'd buy a clock over another, then the SPI version of the clock is the one for you, I guess. If, on the other hand, your aim is simply to keep accurate time, then an even a slower bus would be just fine.

I happen to like the I2C bus better since I have had zero issues with any of my I2C devices interfering with an arduino while I have had plenty with SPI. Maybe I'm too daft to follow best practices with SPI?

t3andy
12-14-2012, 11:52 PM
Well, if running circles is why you'd buy a clock over another, then the SPI version of the clock is the one for you

The DS3234 is a precision RTC +- 2.5 PPM just like the ChronoDot. My project calls for a faster access to the SPI clock data using the Teensy 3.
Unfortunately, there are library timing issues to deal with when using SPI. The wire.h library, like you said, is very stable and reliable on the Teensy 3. :(

Constantin
12-15-2012, 02:45 AM
The DS3234 is a precision RTC +- 2.5 PPM just like the ChronoDot. My project calls for a faster access to the SPI clock data using the Teensy 3.
Unfortunately, there are library timing issues to deal with when using SPI. The wire.h library, like you said, is very stable and reliable on the Teensy 3. :(

That's the thing, the actual clock implementation is the same, it's just a different interface. I used the Chronodot very happily and then went on to use the DS3231 in many projects that required accurate time stamps. My resolution need was only on the order of 1s (and so I could use the 1Hz output signal option) but I wanted those seconds to be accurate, hence the use of the DS3231 when it came to time stamping entries to a SD-based logger file.

I don't have an idea what your project required, but looking over the datasheet I see the same registers in play for the time portion of the DS3234 as the DS3231. So I am still not sure what benefit the 4MHz SPI bus speed brings over fast mode (400kHz) I2C.

IMO, SPI is a bus that is just more finicky because you can have multiple modes, a CS line is typically needed, and data sheets can be incredibly vague about the shape of the data to expect - see the ADC122S625 data sheet (http://www.ti.com/product/adc122s625) as an example. I2C works like a charm in comparison - using fixed addresses and only one 'mode'.

t3andy
12-15-2012, 03:48 AM
SPI is a bus that is just more finicky
You are very correct on that point!:(


So I am still not sure what benefit the 4MHz SPI bus speed brings over fast mode (400kHz) I2C.

Other than speed access improvement, you can update/adjust/sync the DS3234 clock on any second whereas when you update/adjust/sync the I2C clock on the DS3231/DS3231M+ you have to reset the seconds to zero which requires you to only update/adjust it only when the seconds rollover.

t3andy
04-25-2013, 09:15 PM
Update ...

We used several new Teensy 3's and two different vendors DS3234 RTC modules (Sparkfun and Geeetech).
The SPI RTC DS3234 works great in SPI mode 1 and SPI mode 3 on the standard Arduino Seeeduino
Duemilanove 328P using 3.3 VDC or 5 VDC. It still fails on the Teensy 3 using IDE beta 1.12. :(

Since we don't have an expensive logic analyzer to see what critical timing is on the Teensy 3
preventing this common SPI RTC to fail in our project then "again" we will fall back on using an I2C RTC. :(

If anybody was able to get this SPI DS3234 RTC to work on the Teensy 3, without a breadboard, please do chime in.

Test code ...



#include <SPI.h>
const int cs=10; //chip select on DUO
//const int RunLED=3; // on-board run LED
//

static const uint8_t daysInMonth[] = {
31,28,31,30,31,30,31,31,30,31,30,31 };
#define SECONDS_FROM_1970_TO_2000 946684800
#define SECONDS_PER_DAY 86400L
byte hh,mm,ss,d,m;
int yOff;

// SPI DS3234 Clock Source #2 (4 MHz) <-------<<<<<
int DS3234[7]; // added for DS3234 time date array
//DS3234[0] sec
//DS3234[1] min
//DS3234[2] hr
//DS3234[3] null
//DS3234[4] day
//DS3234[5] mon
//DS3234[6] yr

byte DS3234_Hour;
byte DS3234_Minute;
byte DS3234_Second;
byte DS3234_DOW; // null not used
byte DS3234_Day;
byte DS3234_Month;
int DS3234_Year;
unsigned long DS3234_Unix_Local;
unsigned long DS3234_Unix_GMT;

void setup() {


// pinMode(RunLED,OUTPUT);
// digitalWrite(RunLED, LOW);
// digitalWrite(cs, HIGH);
// set the cs as an output:
pinMode(cs,OUTPUT); // chip select

Serial.begin(57600);
// init time / date array
DS3234[0] = 0; // sec
DS3234[1] = 0; // min
DS3234[2] = 0; // hr
DS3234[3] = 0; // null
DS3234[4] = 0; // day
DS3234[5] = 0; // mon
DS3234[6] = 0; // yr

RTC_init();

//------------------------ Set DS3234 Time Date----------------------------------------------
// month(1-12), day(1-31), year(0-99), hour --> military time(0-23), minute(0-59), second(0-59)
SetTimeDate(4,25,13,15,44,0); // <-------------<<<<< works - comment out after use!!!!
//-------------------------------------------------------------------------------------------
}

void loop() {
//Serial.println(ReadTimeDate());
print_DS3234_time_date();

print_clock_source();
//while(1){}



//digitalWrite(RunLED, LOW);
//delay(500);
//digitalWrite(RunLED, HIGH);
//delay(500);

}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------



int RTC_init(){
pinMode(cs,OUTPUT); // chip select
// start the SPI library:
SPI.begin();
//------------------------------------------------------------------------------------------------------------------
// Use one SPI clock setting - DS3234 max is 4 MHz but there are other timing variables requirements.

//SPI.setClockDivider(SPI_CLOCK_DIV2); //
SPI.setClockDivider(SPI_CLOCK_DIV4); // 16 MHz / 4 = 4 Mhz <-- Default Teensy 2 SPI ?
//SPI.setClockDivider(SPI_CLOCK_DIV8); //
//SPI.setClockDivider(SPI_CLOCK_DIV16); //
//SPI.setClockDivider(SPI_CLOCK_DIV32); //

//SPI.setClockDivider(SPI_CLOCK_DIV64); // <---- works only on SeeedStudio 328P Arduino with IO shield max
//SPI.setClockDivider(SPI_CLOCK_DIV128); // <----- works only on SeeedStudio 328P Arduino with IO shield
//-------------------------------------------------------------------------------------------------------------------
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE3); // both mode 1 & 3 should work on the T2 (mode 1 or mode 3 works on the SeeedStudio 328P Arduino)
//set control register
digitalWrite(cs, LOW);
SPI.transfer(0x8E);
SPI.transfer(0x60); //60= disable Osciallator and Battery SQ wave @1hz, temp compensation, Alarms disable
digitalWrite(cs, HIGH);
delay(10);
}
//-------------------------------------------------------------------
int SetTimeDate(int mo, int d, int y, int h, int mi, int s){
int TimeDate [7]={s,mi,h,0,d,mo,y};
for(int i=0; i<=6;i++){
delayMicroseconds(100);
if(i==3)
i++;
int b= TimeDate[i]/10;
int a= TimeDate[i]-b*10;
if(i==2){
if (b==2)
b=B00000010;
else if (b==1)
b=B00000001;
}
TimeDate[i]= a+(b<<4);

digitalWrite(cs, LOW);
SPI.transfer(i+0x80);
SPI.transfer(TimeDate[i]);
digitalWrite(cs, HIGH);
}
}
//---------------------------------------------------------------------
String ReadTimeDate(){ // string
String temp;
int TimeDate [7]; //second,minute,hour,null,day,month,year
for(int i=0; i<=6;i++){
if(i==3)
i++;
digitalWrite(cs, LOW);
SPI.transfer(i+0x00);
unsigned int n = SPI.transfer(0x00);
digitalWrite(cs, HIGH);
int a=n & B00001111;
if(i==2){
int b=(n & B00110000)>>4; //24 hour mode
if(b==B00000010)
b=20;
else if(b==B00000001)
b=10;
TimeDate[i]=a+b;
}
else if(i==4){
int b=(n & B00110000)>>4;
TimeDate[i]=a+b*10;
}
else if(i==5){
int b=(n & B00010000)>>4;
TimeDate[i]=a+b*10;
}
else if(i==6){
int b=(n & B11110000)>>4;
TimeDate[i]=a+b*10;
}
else{
int b=(n & B01110000)>>4;
TimeDate[i]=a+b*10;
}
}
temp.concat(TimeDate[4]);
temp.concat("/") ;
temp.concat(TimeDate[5]);
temp.concat("/") ;
temp.concat(TimeDate[6]);
temp.concat(" ") ;
temp.concat(TimeDate[2]);
temp.concat(":") ;
temp.concat(TimeDate[1]);
temp.concat(":") ;
temp.concat(TimeDate[0]);
return(temp);
}
//------------------------------------------------------------------------------
//Taken from Sparfun sample (amended to return values and not a string)
//Parameter you pass must be a correctly initialized 7 or more int array
void ReadTimeArray(int* TimeDate){
//int TimeDate [7]; //second,minute,hour,null,day,month,year
for(int i=0; i<=6;i++){
if(i==3)
i++;
digitalWrite(cs, LOW);
delay(10);
SPI.transfer(i+0x00);
unsigned int n = SPI.transfer(0x00);
digitalWrite(cs, HIGH);
delay(10);
int a=n & B00001111;
if(i==2){
int b=(n & B00110000)>>4; //24 hour mode
if(b==B00000010)
b=20;
else if(b==B00000001)
b=10;
*(TimeDate +i)=a+b;
}
else if(i==4){
int b=(n & B00110000)>>4;
*(TimeDate +i)=a+b*10;
}
else if(i==5){
int b=(n & B00010000)>>4;
*(TimeDate +i)=a+b*10;
}
else if(i==6){
int b=(n & B11110000)>>4;
*(TimeDate +i)=a+b*10;
}
else{
int b=(n & B01110000)>>4;
*(TimeDate +i)=a+b*10;
}
}
}
//-----------------------------------------------

void print_DS3234_time_date(void){
ReadTimeArray(DS3234); // array
Serial.print("Time = ");
Serial.print(DS3234[2],DEC); //hr
Serial.print(":");
Serial.print(DS3234[1],DEC); // min
Serial.print(":");
Serial.println(DS3234[0],DEC); //sec

Serial.print("Date = ");
Serial.print(DS3234[5],DEC); //mon
Serial.print("/");
Serial.print(DS3234[4],DEC); //day
Serial.print("/");
Serial.println(DS3234[6] + 2000,DEC); //yr
Serial.println();
}
//----------------------------------------------

void read_DS3234_T_D(void){

int DST_TZ = -5;

ReadTimeArray(DS3234); // array
// move variables from array to Global variables
DS3234_Second = DS3234[0]; // sec
DS3234_Minute = DS3234[1]; // min
DS3234_Hour = DS3234[2]; // hr
DS3234_Day = DS3234[4]; // day
DS3234_Month = DS3234[5]; // mon
DS3234_Year = DS3234[6]; // yr

// compute unix time GMT
ss = DS3234[0]; // sec
mm = DS3234[1]; // min
hh = DS3234[2]; // hr

d = DS3234[4]; // day
m = DS3234[5]; // mon
yOff = DS3234[6]; // yr <----<<<<<

//DS3234_Unix_GMT = unixtime(); // computes RTC local time in seconds

DS3234_Unix_Local = unixtime();

if (DST_TZ == -4){
DS3234_Unix_GMT = DS3234_Unix_Local + 14400; // -4 hrs EDT
}
if (DST_TZ == -5){
DS3234_Unix_GMT = DS3234_Unix_Local + 18000; // -5 hrs EST
}

}
//-----------------------------------------------------------------

void print_clock_source(void){
read_DS3234_T_D(); // reads and computes DS3234 time values

Serial.println("------------------");
Serial.println("DS3234 Time and Date");
Serial.print( DS3234_Hour,DEC);
Serial.print(":");
Serial.print( DS3234_Minute,DEC);
Serial.print(":");
Serial.println(DS3234_Second,DEC);

Serial.print(DS3234_Month,DEC);
Serial.print("-");
Serial.print(DS3234_Day,DEC);
Serial.print("-");
Serial.println(DS3234_Year + 2000,DEC); //+2000

Serial.print("DS3234 Unix GMT secs = ");
Serial.println(DS3234_Unix_GMT,DEC);

Serial.print("DS3234 Unix Local secs = ");
Serial.println(DS3234_Unix_Local,DEC);


Serial.println("------------------");

}
//-----------------------------------------------
void unix_time_2_Date_Time(unsigned long t){
byte hh,mm,ss,d,m;
uint8_t daysPerMonth;
int yOff;
static const uint8_t daysInMonth[] = {
31,28,31,30,31,30,31,31,30,31,30,31 };

t -= SECONDS_FROM_1970_TO_2000; // bring to 2000 timestamp from 1970

ss = t % 60;
t /= 60;
mm = t % 60;
t /= 60;
hh = t % 24;
uint16_t days = t / 24;
uint8_t leap;
for (yOff = 0; ; ++yOff)
{
leap = yOff % 4 == 0;
if (days < 365U + leap)
break;
days -= 365 + leap;
}

for (m = 1; ; ++m)
{
daysPerMonth = daysInMonth[+ m - 1];

if (leap && m == 2)
++daysPerMonth;
if (days < daysPerMonth)
break;
days -= daysPerMonth;
}
d = days + 1;

Serial.println("------------------");
Serial.println("Converted Unix time to Time and Date");
Serial.print(hh,DEC);
Serial.print(":");
Serial.print(mm,DEC);
Serial.print(":");
Serial.println(ss,DEC);

Serial.print(m,DEC);
Serial.print("-");
Serial.print(d,DEC);
Serial.print("-");
Serial.println(yOff,DEC);
Serial.println("------------------");
}
//-------------------------------------------
uint8_t dayOfWeek(void)
{
uint16_t day = date2days(yOff, m, d);
return (day + 6) % 7; // Jan 1, 2000 is a Saturday, i.e. returns 6
}
//------------------------------------------
static long time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s)
{
return ((days * 24L + h) * 60 + m) * 60 + s;
}
//------------------------------------------
// number of days since 2000/01/01, valid for 2001..2099
static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d)
{
if (y >= 2000)
y -= 2000;
uint16_t days = d;
for (uint8_t i = 1; i < m; ++i)
//days += pgm_read_byte(daysInMonth + i - 1);
days += daysInMonth[ + i - 1];
if (m > 2 && y % 4 == 0)
++days;
return days + 365 * y + (y + 3) / 4 - 1;
}
//-----------------------------------------------
uint32_t unixtime(void)
{
uint32_t t;
uint16_t days = date2days(yOff, m, d);
t = time2long(days, hh, mm, ss);
t += SECONDS_FROM_1970_TO_2000; // seconds from 1970 to 2000
return t;
}
//----------------------------------------------------------

Nantonos
04-26-2013, 04:01 AM
Since we don't have an expensive logic analyzer to see what critical timing is on the Teensy 3
preventing this common SPI RTC to fail in our project then "again" we will fall back on using an I2C RTC. :(


Different people have different ideas of what constitutes expensive, but in my case I bought a Saleae Logic8 analyser (http://www.saleae.com/logic) and am finding it helpful. At 119.00 € (plus, in Europe, 20% tax and shipping) I also thought it was reasonably inexpensive.

PaulStoffregen
04-26-2013, 10:36 AM
I just ordered the Geeetech RTC module for testing. I probably won't have time to actually use it until after Maker Faire... but eventually I'll give it a try here and see if I can reproduce the problem. It's on my (admittedly very long) to-do list, so I will not forget. Plus I'll have this Geeetech module sitting around to remind me. ;)

When I do investigate, I'll use an (expensive) oscilloscope to view the actual waveforms.

Andy, could I talk you into posting a photo of how you've connected this RTC? If this is a subtle timing issue, possibly involving stray capacitance or signal crosstalk or some other weird effect, if I can closely duplicate your physical wiring, it might help.

PaulStoffregen
04-26-2013, 10:43 AM
Also, if you're still using one of the beta versions, please give this a try with the latest 1.14-rc1 (http://forum.pjrc.com/threads/23582-Teensyduino-1-14-Release-Candidate-1-Available) code. It probably won't make any difference, but just downloading a new version of the software is an easy thing to try compared to hooking up physical wires and test equipment.

t3andy
04-26-2013, 06:45 PM
Different people have different ideas of what constitutes expensive, but in my case I bought a Saleae Logic8 analyser and am finding it helpful

To troubleshoot SPI you need probably need two pieces of equipment. A logic analyzer and a good bandwidth o'scope.
The Saleae logic analyzer is very inexpensive for $150 USD but add that to a good o'scope and the total cost is ~ > $850 USD just to troubleshoot SPI. We come from the old "Arduino mindset" that everything should plain work and we don't need any expensive test equipment. We rather buy Teensy 3's than test equipment. Lets see ... $850/19 = 44 Teensy 3s! Of course, that's our own opinion.

So far, the Teensy 3 ARM functions and libraries has worked extremely well, in all of our projects, thanks to Paul S. Since the SPI SD card worked, with no problems, then the only SPI device/module we could not make work on the Teensy 3 ARM stamp was this precision SPI DS3234 RTC. :mad: Buying test equipment just to ONLY troubleshoot this SPI RTC module would be ludicrous. Even if we did find a "timing problem" then the next task would be to fix the standard SPI core library or the application PDE. Having no intimate knowledge or experience using the Freescale K20 ARM internals then really only Paul S would be qualified to fix this problem. If it turns out to be an external hardware problem then we will deal with it with a hammer. :mad:



Also, if you're still using one of the beta versions, please give this a try with the latest 1.14-rc1 code.

With 1.14 the SPI DS3234 still does not work.
Please note: The above test code was used on both microcontrollers (T3 and Arduino 328P) with no changes.
In the attached pics ... the first photo shows two 4 pin cables. The first is the pwr. & gnd. The second
is the SPI bus.


When I do investigate, I'll use an (expensive) oscilloscope to view the actual waveforms

I will bet that Agilent O'scope of yours did not come cheap.:cool:

BTW ... The Geeetech.com SPI precision RTC DS3234 is one half the cost of other vendors with free shipping worldwide.

t3andy
05-31-2013, 08:01 PM
@ Paul S



I just ordered the Geeetech RTC module for testing. I probably won't have time to actually use it until after Maker Faire... but eventually I'll give it a try here and see if I can reproduce the problem. It's on my (admittedly very long) to-do list, so I will not forget. Plus I'll have this Geeetech module sitting around to remind me.


Any luck? :rolleyes:

PaulStoffregen
06-01-2013, 11:50 AM
The board arrived from Geeetech some time ago. With all the hurry to get ready for Maker Faire and then to release 1.14, it got put into a big box of hardware waiting for me to test Arduino libraries and hasn't been touched since.

PaulStoffregen
06-01-2013, 06:29 PM
Ok, I connected the Geeetech RTC on a breadboard this morning. I ran this example code (http://www.geeetech.com/Documents/DS3234_example.pde). It appears to work perfectly. Here's a screenshot of what I'm seeing on the serial monitor:

529
(click for larger)

Here's a photo of how I connected the hardware.

530
(click for larger)

I tested with Arduino 1.0.5 and Teensyduino 1.14 using this board and the example program without any modifications. The Greeetech RTC module did not come with a battery. I tested without a battery installed.

PaulStoffregen
06-01-2013, 06:35 PM
Andy, could you try again using relatively short wires?

The Teensy 3.0 has very high bandwidth digital signals for its SPI port (much higher than AVR-based Arduino). Maybe the trouble you're seeing could be crosstalk or other signals quality issues related to the very long wires?

t3andy
06-03-2013, 12:55 AM
Maybe the trouble you're seeing could be crosstalk or other signals quality issues related to the very long wires? .

Yep --> shorten all hookup wires from 12.5" (31.75 cm) to 2"(5.08 cm).:)
These long wires worked perfect on any Arduino?:confused:
Remote panel mounting the DS3234 RTC module away from the T3 cannot be done. :(
Again, you help is very appreciated Paul "Superman" Stoffregen !!!! :cool:

BTW ... thats a very cool breadboard you have Paul.

addition: Strange, but only SPI mode 3 works but not SPI mode 1 ?

PaulStoffregen
06-03-2013, 07:35 PM
Glad I could help. :)

Teensy 3.0's ARM chip has much higher bandwidth on its pins than AVR. Fast digital signals can be problematic on long wires. Usually crosstalk and signal reflections are the main issues. You can sometimes make a pretty substantial improvement by adding a resistor between the pin and the lengthy wire. See the "Signal Quality" section on the OctoWS2811 page (http://www.pjrc.com/teensy/td_libs_OctoWS2811.html) for an example. The resistor value depends on the type of wire and the spacing between the wires, but usually resistors in the range of 50 to 300 ohms are about right. Without an oscilloscope, you're really shooting in the dark, but even getting somewhat close can make a big improvement in the signal quality compared to not having any resistor at all.

Those "clear" breadboards look nice, but I think they're actually harder to use. It's easier to see wires against an opaque white breadboard. I had this one laying around because PJRC bought samples of a couple dozen different breadboards to evaluate how well the header pins fit. The BPS and Twin ones were far superior (we're selling the BPS one now). This was left over in the pile of, well, not-so-superior breadboards. It looks cool, but it's in about the middle of the usability range. The BPS and Twin ones are definitely the best.