MUT protocol notes

Technical information, definitions, advice, and support for factory ECU remapping, aftermarket systems, and much more.
Post Reply
WytWun
Valued Contributor
Posts: 539
Joined: 09 Apr 2011, 23:22
State: Australian Capital Territory
Model: Mitsubishi Magna TJ AWD
Engine: 3.5-Litre 6G74 V6 24V SOHC

MUT protocol notes

Post by WytWun » 13 Nov 2016, 23:33

In the course of some other experimentation I needed to be able to have my own MUT logging code so am posting this thread to document what I've figured out.

Fortunately the basic MUT protocol itself is fairly easy to implement, however there is a wrinkle for non-Evo ECUs: pin 1 of the diagnostic connector needs to be grounded before MUT protocol negotiation can begin. Most of the MUT protocol negotiation info around the web was written based on Evo ECU behaviour, so tools like Malibu Jack's Mitsulogger and Donour Sizemore's libmut won't work with cars that require pin 1 grounded.

Control of pin 1 is implemented in the OpenPort cables by connecting this pin to the ISO-9142 L line drive circuit although this is not shown on the circuit diagrams for these cables published on the OpenECU website, which is tied to the DTR signal from the serial controller. The L line is on pin 15 of standard OBD2 diagnostic connectors; this pin is not connected to anything in most MUT compatible vehicles as far as I'm aware.

The Python script in the next post illustrates the basics of MUT logging, but there are a couple of caveats given that it is a bare bones implementation:
- it is console mode (Windows) or command line (Linux/MacOS) usage only
- no value scaling is implemented
- the output is to the screen, although the output (which is in CSV format) can be redirected to a file
- only OpenPort 1.2 and 1.3 cables are supported

This script requires the PySerial module. I have developed and tested this script on Windows (7) with Python 2.7. It should also work on Linux and MaxOS as long as appropriate serial port names are supplied to identify the OpenPort cable, and should not need much change for Python 3.x.

Because this script treats the cable as a generic serial device, there is potential for using other interface cables which also behave as serial devices, such as the ubiquitous "VAG-COM KKL" cables on Ebay, provided that the DTR signal from the serial controller can be used to control pin 1 of the diagnostic connector via appropriate circuitry.

WytWun
Valued Contributor
Posts: 539
Joined: 09 Apr 2011, 23:22
State: Australian Capital Territory
Model: Mitsubishi Magna TJ AWD
Engine: 3.5-Litre 6G74 V6 24V SOHC

Re: MUT protocol notes

Post by WytWun » 13 Nov 2016, 23:34

Simple MUT logger written in Python:

Code: Select all

# very rudimentary MUT logging utility for OpenPort 1.x cables
# NB: latency timer setting in advanced port settings needs to be
#     reduced from the default value of 16ms to 1ms for optimum
#     logging sample rates

import sys
import time
import serial
from serial.tools import list_ports as serial_listports


### constants

# MUT unit addresses, probe requests and responses
MUT_UNITS = {'ECU': (0, chr(0xFE), '\x00\x55\xEF\x85'),
             'TCU': (1, chr(0xFE), '\x00\x00\x55\x70\x85'),
            }


### helper functions

# wait in a loop until specified time elapses
def wait(s):
    clock = time.clock
    n = clock() + s
    while clock() < n:
        pass

# list available ports
def list_ports():
    print 'available ports:'
    for p in serial_listports.comports():
        print '  %s' % str(p)

# convert an integer address to the bit specification for slow init
# NB: - address bits need to be in least significant bit order
#     - include start (logic 0) bit and stop (logic 1) bit in spec
#     - duration in seconds for each group of identical bits (0.2s/bit)
def slow_init_bits(unit_address):

    # get the address bits in reverse (LSB) order and add the stop bit
    bits = list(reversed([int(c) for c in format(unit_address, '08b')]))
    bits.append(1)

    # starting with the start bit, group adjacent identical bits
    # with a count
    si_specs = []
    bv, bc = 0, 1
    for v in bits:
        if v == bv:
            bc += 1
        else:
            si_specs.append((bv, bc * 0.2))
            bv, bc = v, 1
    si_specs.append((bv, bc * 0.2))
    return si_specs


### logging routines
    
# initialise K-line comms by sending unit address at 5 baud before
# switching to requested baud rate.
# NB: Most MUT compatible ECUs (except Evos) require that pin 1 on the
#     diagnostic connector is grounded for the duration of the logging
#     session; OpenPort 1.3 cables have this pin connected to the L line
#     circuitry which is normally driven by the serial line DTR signal.
def mut_init(port_name, unit_name, baud_rate=15625):

    # get the unit specs
    unit_addr, probe_mut, unit_resp = MUT_UNITS[unit_name]

    # init the port
    port = serial.Serial(port_name, baud_rate, timeout=1, write_timeout=1)
    port.reset_output_buffer()
    port.reset_input_buffer()
    port.dtr = False
    port.rts = False

    # pull diag pin 1 low and wait 100ms
    port.dtr = True
    wait(0.1)

    # probe with a "do nothing" MUT command; return port if MUT active
    port.timeout = 0.1
    port.write(probe_mut)
    resp = port.read(2)
    port.timeout = 1.0
    if resp and len(resp) == 2 and resp[0] == probe_mut:
        return port

    # pause to let things settle then send the slow init (5 baud)
    wait(0.5)
    for bit_value, duration in slow_init_bits(unit_addr):
        if bit_value:
            wait(duration)                 # logic 1
        else:
            port.send_break(duration)      # logic 0

    # wait for response
    resp = port.read(len(unit_resp))
    if resp == unit_resp:
        return port
    else:
        port.close()
        raise ValueError('unexpected MUT response: "%s" [%s]' % (resp, ''.join(['\\x%02x' % ord(c) for c in resp])))

# for an integer request number, get the MUT response as an integer
def mut_get(port, rq):
    rq_c = chr(rq)
    port.write(rq_c)
    resp = port.read(2)
    if resp and len(resp) == 2 and resp[0] == rq_c:
        return ord(resp[1])
    else:
        port.close()
        raise ValueError('timed out waiting for MUT response')

# repeatedly scan a list of MUT requests
def log_mut(port_name, unit_name, requests):
    clock = time.clock

    # if first request is negative, use that as a loop count
    # otherwise loop forever waiting for a keyboard interrupt
    if requests[0] < 0:
        loop_next = range(abs(requests[0]) + 1).pop
        requests = requests[1:]
    else:
        loop_next = lambda : True

    log_hdr = 'ElapsedTime,' + ','.join('MUT_%02X' % req for req in requests)
    log_fmt = '%.4f,' + ','.join('%s' for req in requests)
    clock_start = clock()
    port = mut_init(port_name, unit_name)
    print log_hdr

    try:
        while loop_next():
            log_recd = [clock() - clock_start]
            log_recd.extend(mut_get(port, req) for req in requests)
            print log_fmt % tuple(log_recd)
    except KeyboardInterrupt:
        print '### terminated ###  [elapsed time: %.4f s]' % (clock() - clock_start)
        port.close()
        sys.exit(1)

    log_recd = [clock() - clock_start]
    log_recd.extend('-' for req in requests)
    port.close()
    print log_fmt % tuple(log_recd)


### run as script

if __name__ == '__main__':

    # list of supported units
    unit_list = ['%s' % u for u in MUT_UNITS.iterkeys()]
    unit_list.sort()

    # get the port name
    try:
        port_name = sys.argv[1].upper()
        unit_name = sys.argv[2].upper()
    except IndexError:
        list_ports()
        print '\nunits: %s' % ', '.join(unit_list)
        print '\nusage:\n'
        print '  %s <port> <unit> [-loop_count] <rq_1> <rq_2> .. <rq_n>' % sys.argv[0]
        sys.exit(1)

    if unit_name not in unit_list:
        print 'ERROR: unit "%s" not one of %s' % (unit_name, ', '.join(unit_list))
        print '\nusage:\n'
        print '  %s <port> <unit> [-loop_count] <rq_1> <rq_2> .. <rq_n>' % sys.argv[0]
        sys.exit(1)

    # get the MUT request numbers as integers
    try:
        requests = [int(x) for x in sys.argv[3:]]
    except (IndexError, ValueError), e:
        print 'ERROR: no MUT request IDs found!'
        print '\nusage:\n'
        print '  %s <port> <unit> [-loop_count] <rq_1> <rq_2> .. <rq_n>' % sys.argv[0]
        sys.exit(1)

    # start logging
    log_mut(port_name, unit_name, requests)
EDIT 20/11/2016: updated script to add ability to select unit (ECU, TCU etc); only ECU & TCU supported in this version but other units (see BCX's list a couple of posts down) can be added to the MUT_UNITS dictionary after some experimenting to determine the unit response bytes.
Last edited by WytWun on 20 Nov 2016, 22:23, edited 2 times in total.

WytWun
Valued Contributor
Posts: 539
Joined: 09 Apr 2011, 23:22
State: Australian Capital Territory
Model: Mitsubishi Magna TJ AWD
Engine: 3.5-Litre 6G74 V6 24V SOHC

Re: MUT protocol notes

Post by WytWun » 13 Nov 2016, 23:52

In the Python script in the previous post, I commented that the advanced port settings for the OpenPort cable need to be changed for optimum logging performance. Evoscan does this each time it connects to the cable via a programmatic interface, but the same effect can be achieved via the cable's advanced port settings in Windows because each of the FTDI chips has a unique serial number.

Without changing these settings, only slightly more than 60 MUT requests per second can be logged. With the changes in place, logging a bench ECU can achieve around 300 MUT requests per second and logging an in-car ECU with a running engine can see 180-190 MUT requests per second.

To make the changes on a Windows PC or laptop, start by plugging your OpenPort 1.2 or 1.3 cable into your machine (it doesn't have to be plugged into the car's diagnostic connector) and finding the cable in the Device Manager (via the Control Panel):
dev_mgr.png
dev_mgr.png (45.62 KiB) Viewed 2860 times
Double click on the Device Manager entry for the cable to get to the device's Properties panel:
OpenPort-1.3_properties.png
OpenPort-1.3_properties.png (28.77 KiB) Viewed 2860 times
Click on the "Change Settings" button. Depending on the version of Windows, you will probably be shown an access control dialog which may require an administrator password to proceed. Once you have got past that, you will see the same dialog but without the "Change Settings" button on the "General" tab.

Click on the "Port Settings" tab:
OpenPort-1.3_port_settings.png
OpenPort-1.3_port_settings.png (27.7 KiB) Viewed 2860 times
Click on the "Advanced..." button.

Continued in next post...
Last edited by WytWun on 14 Nov 2016, 00:12, edited 2 times in total.

WytWun
Valued Contributor
Posts: 539
Joined: 09 Apr 2011, 23:22
State: Australian Capital Territory
Model: Mitsubishi Magna TJ AWD
Engine: 3.5-Litre 6G74 V6 24V SOHC

Re: MUT protocol notes

Post by WytWun » 14 Nov 2016, 00:00

You should now be at the Advanced Settings panel for the cable, with the default settings looking like this:
OpenPort-1.3_adv_port_settings_default.png
OpenPort-1.3_adv_port_settings_default.png (36.59 KiB) Viewed 2860 times
The critical change is to the Latency Timer (msec) value which defaults to 16; this needs to be lowered to 1 (boxed in red):
OpenPort-1.3_adv_port_settings_recommended.png
OpenPort-1.3_adv_port_settings_recommended.png (46.04 KiB) Viewed 2860 times
I also recommend lowering both the Receive and Transmit USB Transfer Sizes from the default 4096 to 256 (boxed in blue in the screenshot above) however it is unlikely to affect performance if you don't.

No other changes need to be made, so click the OK button on each dialog to save the changes. These changes also won't affect Evoscan's operation in any way.

Unfortunately, changing the Latency Timer value if you are using Linux or MacOS appears not to be possible via programmatic options without using the FTDI software interface. It may be possible to adjust this setting using a Windows machine and the FTDI FT_Prog utility (which changes settings in the device's EEPROM) but I haven't tried that.

BCX
Member
Posts: 80
Joined: 01 Dec 2011, 10:19
State: South Australia
Model: Mitsubishi Galant VR4
Engine: 2.5-Litre 6A13-TT V6 24V DOHC
Transmission: 5 Speed Manual

Re: MUT protocol notes

Post by BCX » 15 Nov 2016, 22:22

Nice work.

I'll add a few things to this...

With the 5-init baud... depending on what byte you send as part of the init process will address other systems. Below is my findings based on playing with my MUT and my trusty oscilloscope

Code: Select all

public enum ECUAddress
    {
        ECU = 0x0,
        TCU = 0x1,
        IMMOBILISER = 0x89,
        SS4II = 0x92,
        TCL = 0x85,
        ECS = 0x2,
        ABS = 0x4,
        STEERING = 0x83,
        HBB = 0x91,
        AYC = 0xB,
        SRS = 0x8,
        SWS = 0x8A,
        TPMS = 0x19,
        CAMERA = 0x1A
  }
Also, there is also a large number of MUT requests and MUT commands for each vehicle system.

Requests over 0xC0 for the ECU and TCU are considered commands (MUT table is ignored), which operate various actuators for testing/diagnosis. Some of these commands do also return identification like case number, etc. I'll see if I can clean up my list and post it up.

Finally, I've written a evoscan clone for windows but it's not polished as I use it for my own use. Evoscans implementation of DTCs is terrible. Compatible with both Openport 2.0 and 1.3/VAG cables (FTDI). I'm in two minds about releasing it... mainly cos i just haven't got time to maintain it!

@wytwun - If you interested in implementing Openport 2.0 support, give me a hoy... Cant say I've tried coding in python, but if python can deal with pointers much like C/C++ then it's easy to interface with the dll.

Cheers,
Bill
8th Gen Galant VR4
MK Triton [6G74 conversion]

WytWun
Valued Contributor
Posts: 539
Joined: 09 Apr 2011, 23:22
State: Australian Capital Territory
Model: Mitsubishi Magna TJ AWD
Engine: 3.5-Litre 6G74 V6 24V SOHC

Re: MUT protocol notes

Post by WytWun » 15 Nov 2016, 22:57

Bill,

Thanks for the ECU address info! Agree about Evoscan and DTCs :(

Python has a foreign function interface, so interfacing to Windows DLLs isn't that hard and I've done a bit of that before.

Cheers,
Andy.

Post Reply