This document describes the transfer protocol and memory layout of these suunto diving computers:
- Suunto D6 (a D9 without the air integration)
- Suunto D9
Suunto changed the protocol for these models – again. One reason is that the D9 is capable in recording not only the depth, but also pressure and temperature throughout the dive. The computers also have no longer just 8kb, but 32kb of memory for the profile information.
The interface is technically still a RS232 interface, but with a USB connector. With a device driver from the USB chipset vendor FTDI it will be recognized as a standard serial port.
The RS232 should be 9600 baud, 8N1.
The protocol for the Suunto D9 is slightly different from other Suunto computers. The first thing is that the interface is USB only. But with the standard driver is behaves like a serial port (see above). These standard drivers are available for Mac OS X and Linux, so there should be no reason to go into too much detail.
All data is send in packages to and from the computer. Every package is followed by a CRC for checksum reasons.
unsigned char checksum = 0x00; for(int i=0; i<packageLen; ++i) checksum ^= package[i];
The package starts with a command byte, followed by the length of bytes for the parameters ($00 $03 = 3 Bytes), the actual parameters and the CRC at the end. Here are examples of the most common commands:
- ReadMemory – reads memory from the Suunto
- to Suunto: $05 + $00 + $03 + addr_high + addr_low + count (1..$78) + CRC
- from Suunto: $05 + $00 + $03 + addr_high + addr_low + count (1..$78) + count Bytes + CRC
- WriteMemory – changes the memory inside the Suunto (WARNING: this can damage your watch!!!)
- to Suunto: $06 + $00 + $03 + addr_high + addr_low + count (1..$78) + CRC
- from Suunto: $06 + $00 + $03 + addr_high + addr_low + count (1..$78) + count Bytes + CRC
- GetVersion / check for connected computer
- to Suunto: $0F + $00 + $00 + CRC
- from Suunto: $0F + $00 + $00 + ID + HIGH + MID + LOW + CRC = Version HIGH.MID.LOW (ID)
- ResetMaxDepth – resets maximum depth in the history to 0.0m
- to Suunto: $20 + $00 + $00 + CRC
- from Suunto: $20 + $00 + $00 + CRC
The DiveManager Software from Suunto does the following when reading data from a D6/D9: – GetVersion() – ReadMemory($0, $78) – ReadMemory($168, $78) (Reading the memory header) – ReadMemory($F0, $78)
After that the DiveManager reads the headers of all profiles first (by following the linked list in the headers), but always $78 bytes and later fills in the holes in the memory, if necessary. This allows the DiveManager to display a progress bar for the import.
To change the personal information, it does the following: – GetVersion() – Write($015E,$28) – Read($0168,$78) – GetVersion() – Read($0000,$78)
To reset the max. depth to zero it does the following: – GetVersion() – ResetMaxDepth() – Read($00F0,$78) – Read($0168,$78) – GetVersion() – Read($0000,$78)
There have been two recalls of the Suunto D9 so far.
- The first one was a hang of the D9, which is fixed in firmware version 1.2.8 and later (serial numbers 4xxxxxxxx – 450000699)
- The second one (serial numbers below 62102582, an inacuracy of the clock in 0.6% of the cases) seemed to be fixed in firmware version 1.2.11 and later. This one also affects the Suunto D6 (serial numbers below 62103693)
The 32kb of memory is split into a $019A bytes header and the rest is a ring-buffer with the dive profiles. The ring-buffer is a stream of data, which ends at the position, that is marked in the header. At this position the computer starts writing the information from the next dive. If the write pointer reaches the value $8000, it jumps back to $019A.
I am using the following parameter types:
|B||8 bit Byte|
|W||16 bit LSB integer|
|An||ASCII string, max. n Bytes long|
All units inside the Suunto are metric (meter/centimeter, bar)!
offset | format | testvalue | content
|$01-$02||A2||“D9”||Type of the dive computer, “D9”, “D6” or “D4”|
|$03-$0A||A8||“20050228”||Manufacturing date (28 Feb 2005)|
|$13-$15||B||$00 $55 $00||???|
|$16-$22||B||.. $FF ..||unused|
|$23-$26||B4||$50 $90 $12 $34||official serial number ($50901234) Production date: 2000+5 / 09 week, watch 234|
|$28||B||$0E||model ($0E=D9, $0F=D6 (can also be $0E?), $12=D4)|
|$29-$2A||B2||$01,$02||Version (1.2.10 – well, were does the 10 come from?)|
|$2E||B||$82||XOR Checksum $28..$2D|
|$2F-$31||B||.. $FF ..||unused|
|$34-$35||B2||$00 $00||$0000 = metric, $0101 = imperial|
|$37-$3A||B||$83 $00 $82 $F2||???|
|$3B||B||$0C||XOR Checksum $32..$3A|
|$3C-$3F||B||$FE $00 $00 $00||???|
|$40-$63||B||.. $FF ..||unused|
|$64-$6F||B||..||calibration data for sensors|
|$70||B||$..||XOR Checksum $64..$6F|
|$71-$73||B||.. $FF ..||unused|
|$74-$7D||B||..||calibration data for sensors|
|$7E||B||$..||XOR Checksum $74..$7D|
|$7F-$83||B||.. $FF ..||unused|
|$84-$8D||B||..||calibration data for sensors|
|$8E||B||$..||XOR Checksum $84..$8D|
|$8F-$015D||B||.. $FF ..||unused|
|$015E-$0185||A40||..||owner name (“Suunto Diver”), $00 terminated if short than 40 bytes|
|$0186-$0187||W||$F010||max. depth ($10F0 = 4336 cm)|
|$0188-$0189||W||$AB02||total dive time ($02AB = 683 minutes)|
|$018A-$018B||W||$0C00||total number of dives ($000C = 12)|
|$018C||B||$..||XOR Checksum $0186..$018B|
|$018D-$018F||B||.. $FF ..||unused|
|$0190-$0191||W||$0A $46||Ptr to the last dive in the ring-buffer (Offset $460A)|
|$0192-$0193||W||$0C $00||Number of dives in the ring-buffer ($000C = 12)|
|$0194-$0195||W||$7E $4B||Ptr to behind the latest dive in the ring-buffer (Offset $4B7E)|
|$0196-$0197||W||$9A $01||Ptr to the oldest dive in the ring-buffer (Offset $019A)|
|$0198-$0199||W||$5C $0E||checksum ??? (if changed SDM can no longer read the profiles)|
Dive Profile Header
Every dive in the profile memory has a $4A bytes header.
|$00-$01||W||$9A $01||Offset to previous dive ($019A – this is the very first)|
|$02-$03||W||$CF $06||Offset to next dive ($06CF)|
|$0D-$0E||W||$F0 $10||max. depth ($10F0 = 4336 cm)|
|$0F-$10||W||$1E $00||dive time ($001E = 30 minutes)|
|$11-$12||W||..||if value is $FFFF, there is no pressure information for the dive|
|$13||B||$03||OLF PCT (3%)|
|$15||B||$0A||Date: hour (10 AM)|
|$16||B||$24||Date: minutes (36)|
|$17||B||$0E||Date: seconds (14)|
|$18-$19||W||$D5 $07||Date: year (2005)|
|$1A||B||$0C||Date: month (12)|
|$1B||B||$0B||Date: day (11)|
|$1C||B||$0A||Sample recording interval (every 10 seconds)|
|$1D||B||$00||Gas model (0=Air, 1=Nitrox, 2=Gauge)|
|$1E||B||$01||repetitive dive number (1)|
|$1F||B||$00||Surface time (0 minutes)|
|$20||B||$00||Surface time (0 hours)|
|$21||B||$03||RGBM PCT (3%)|
|$22||B||$00||Personal Setting (P0..P2)|
|$23||B||$00||Altitude Setting (A0..A2)|
|$25||B||$20||O2 PCT (only if in Nitrox mode, 32%)|
|$26||B||$15||O2 PCT 2 (only if in Nitrox mode, 21%)|
|$27||B||$15||O2 PCT 3 (only if in Nitrox mode, 21%)|
|$47||B||$05||Temperature recording interval (every 5 depth samples)|
|$4C-$4D||W||$4D $00||Offset to the first marker position in the profile|
Dive Profile Data
The length of the profile data can be found by looking into the profile header: the difference between the offset of the next dive to the current offset minus the size of the header ($4A).
Warning: Remember that the data is stored in a ring-buffer! The next dive pointer might actually be smaller than the current dive pointer.
To parse the profile data, you start with a time of 0:00 minutes and read to LSB 16 bit values. The first value is the current depth in cm. The second value is the tank pressure in bar * 100. Before reading the next value you increment the time by the “Sample recording interval” (Header offset $1C). Every “Temperature recording interval” (Header offset $47) there is another byte following with the temperature in degrees celcius. You stop once you’ve read the beginning of the next dive.
But, there is another twist: the dive profile can contain events, which are encoded into the profile information. For this you need check for the “Offset to the first marker position” (Header offset $4C). The first offset into the dive profile is $0000, you increment it with every byte you’ve read from the profile (4 or 5 per sample, see above). If the marker is equal to the current profile position, the next byte is an event type. You have to read all events till you will get Event $01 “Next Event Marker”. After you’ve read this one, you can continue normal parsing till you hit the next marker position.
|$01||Next Event Marker||WW||set the position of the next event marker (1st: current position, 2nd: next position)|
|$02||Surfaced||BB||Reached Surface at current timestamp + 2nd byte: seconds (for a more exact value)|
|$03||Event||BB||Other dive related events (1st: type, 2nd: time offset like in “Surfaced”)|
|$04||Bookmark/Heading||BBW||Bookmark & Heading (1st:???, 2nd: time offset like in “Surfaced”, 3rd: heading * 4, if < 0: Bookmark instead)|
The Dive Events from Event $03 are 7 bit values. If bit 7 is set, like in $8E which only seems to occur in that form, it means: Event has ended. So you can get a start and a stop time for one event.
|$01||Mandatory Safety Stop|
|$06||Mandatory Safety Stop Ceiling Error|
|$0D||Air Time Warning|
|$8E||Rgbm Warning (End) (always before the first profile data at the beginning of the dive)|