PC Protocol of Suunto D9

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.

Communication

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.

Transfer

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)

Firmware versions

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)

Memory layout

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:

parameter types Description
B 8 bit Byte
Bn n Bytes
W 16 bit LSB integer
An ASCII string, max. n Bytes long

All units inside the Suunto are metric (meter/centimeter, bar)!

Memory Header

 offset |     format   | testvalue    | content
$00 B $00 empty
$01-$02 A2 “D9” Type of the dive computer, “D9”, “D6” or “D4”
$03-$0A A8 “20050228” Manufacturing date (28 Feb 2005)
$0B-$12 A8 “00001204” Serial number?
$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
$27 B $FF unused
$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?)
$2B-$2D B $04,$09,$82 ???
$2E B $82 XOR Checksum $28..$2D
$2F-$31 B .. $FF .. unused
$32-$33 B $01 $01 ???
$34-$35 B2 $00 $00 $0000 = metric, $0101 = imperial
$36 B $FF unused
$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.

offset format testvalue content
$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)
$04-$0C B .. ???
$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%)
$14 B .. ???
$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)
$24 B .. ???
$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%)
$28 B $00 ???
$29-$2A W $.. $.. PREVTPRESSURE_1
$2B-$2C W $.. $.. PREVTPRESSURE_2
$2D-$2E W $.. $.. PREVTPRESSURE_3
$2F-$30 W $.. $.. PREVTPRESSURE_4
$31-$32 W $.. $.. PREVTPRESSURE_5
$33-$34 W $.. $.. PREVTPRESSURE_6
$35-$36 W $.. $.. PREVTPRESSURE_7
$37-$38 W $.. $.. PREVTPRESSURE_8
$39-$3A W $.. $.. PREVTPRESSURE_9
$3B-$46 B .. ???
$47 B $05 Temperature recording interval (every 5 depth samples)
$48-$4B B .. ???
$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.

Event Type Parameter Function
$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.

Dive Event Description
$01 Mandatory Safety Stop
$04 Asc Warning
$06 Mandatory Safety Stop Ceiling Error
$07 ???
$09 Depth alarm
$0D Air Time Warning
$8E Rgbm Warning (End) (always before the first profile data at the beginning of the dive)