EEC VI Power PC

Ford information and tools can be found here
User avatar
Ralmo94
Posts: 25
Joined: Thu Mar 12, 2026 10:00 pm
cars: 1995 towncar
1994 k1500
1998 k2500 7.4
2002 Tahoe 5.3
2004 Silverado 1500
2008 F250 5.4

Re: EEC VI Power PC

Post by Ralmo94 »

pman92 wrote: Sun Jul 05, 2026 10:47 pm

It sounds like what your doing will be a great learning experience, I'm not saying you shouldn't be doing it. Just pointing out if your end goal is just read/write bins then there are already options available to save you the development.
I appreciate it. I actually plan to experiment quite a bit with patches and things that may brick a PCM and potentially if I miss wire something smoke it, so I need my own free to use tool for it. I have absolutely nothing but hobby time in this so far and no real money. Even my bench pcm was purchased as a spare for the truck. If I do fry it can get another one easily and cheaply at the self service pull yard.

It already has been a tremendous learning experience so far. Not only the pcm specifics, but also how much you can and can't rely on AI, what they will confidently hallucinate, and when to stop allowing the same one to debug it's own issues.

I figured I would share my knowledge as I get it as there really is not a lot of reliable detailed information on these. At some point when my software is stable enough I will share it too, if there is interest in it. Just not sure if it will be absolutely free or not, I may set up a patreon or something and require a donation with no minimum, or do the tuner pro thing and nag wait until paid for, haven't decided mostly because it is not stable yet.
pman92 wrote: Sun Jul 05, 2026 10:47 pm GDS / KWP2000 and UDS are all very similar, with subtle differences that might trip you up (particularly with UDS which is much newer). You can find documentation on google for reference:
https://www.cds.caltech.edu/~murray/dgc ... 2003.0.pdf

I've never actually fully checked out the write process of the PCM myself. But from my work on other modules from same age ford vehicles here is Australia, I suspect the erase might be done with a mode 0xB1 diagnostic command - which is a ford mode specific to GDS and not part of UDS.

Next time I am writing one with PCMflash I will get a datalog of it and post it up.
That would be interesting, it takes someone more knowledgeable than me to make sense of it, but I am very curious.
OEM boxes. Open questions. Closed source.
The engine knows what it wants. The ECU knows what it got. :hmm:
If the bootloader can read it, so can we. :gear:
The silicon doesn't lie — it's just not telling the whole story. :silent:
User avatar
Ralmo94
Posts: 25
Joined: Thu Mar 12, 2026 10:00 pm
cars: 1995 towncar
1994 k1500
1998 k2500 7.4
2002 Tahoe 5.3
2004 Silverado 1500
2008 F250 5.4

Re: EEC VI Power PC

Post by Ralmo94 »

I need to correct something I said on here
Another thing I figured out is since this is a truck platform and not a standard car regulations and it does not respond to obd2 mode 9 requests for vin or calibration ID. Hpt apparently obtains this info with a proprietary ford diagnostics.
That was false

Here is a script I ran against it

Code: Select all

#!/usr/bin/env python3
"""
Minimal J2534 CAN poller — read-only
- Mode 01 PID 00 (supported PIDs)
- Mode 09 PID 00 (supported Mode9)
- Mode 09 PID 02 VIN
- Mode 09 PID 04 CALID
- Mode 09 PID 06 CVN
"""

import ctypes, time, sys
from datetime import datetime

# ==== J2534 constants (from SAE J2534 FEB2002) ====
PROTOCOL_CAN = 0x00000005 # raw CAN, we do ISO-TP ourselves
BAUD_500K = 500000
PASS_FILTER = 0x00001
STATUS_NOERROR = 0

TX_ID = 0x7E0 # physical request
RX_ID = 0x7E8 # typical ECU response (0x7E8-0x7EF also valid)

class PASSTHRU_MSG(ctypes.Structure):
    _fields_ = [
        ("ProtocolID", ctypes.c_ulong),
        ("RxStatus", ctypes.c_ulong),
        ("TxFlags", ctypes.c_ulong),
        ("Timestamp", ctypes.c_ulong),
        ("DataSize", ctypes.c_ulong),
        ("ExtraDataIndex", ctypes.c_ulong),
        ("Data", ctypes.c_ubyte * 4128)
    ]

def hexd(b): return ' '.join(f'{x:02X}' for x in b)

class J2534:
    def __init__(self, dll_path):
        self.dll = ctypes.WinDLL(dll_path)
        self.dev = ctypes.c_ulong()
        self.chan = ctypes.c_ulong()

    def open(self):
        assert self.dll.PassThruOpen(None, ctypes.byref(self.dev)) == STATUS_NOERROR
    def connect(self):
        assert self.dll.PassThruConnect(self.dev, PROTOCOL_CAN, 0, BAUD_500K, ctypes.byref(self.chan)) == STATUS_NOERROR
    def close(self):
        self.dll.PassThruDisconnect(self.chan)
        self.dll.PassThruClose(self.dev)

    def start_filter(self):
        # pass only 0x7E8-0x7EF
        mask = PASSTHRU_MSG(); pat = PASSTHRU_MSG()
        for m in (mask, pat):
            m.ProtocolID = PROTOCOL_CAN
            m.DataSize = 4
        # mask = 0x7F8, pattern = 0x7E8
        for i, b in enumerate([(0x7F8>>24)&0xFF, (0x7F8>>16)&0xFF, (0x7F8>>8)&0xFF, 0x7F8&0xFF]):
            mask.Data[i] = b
        for i, b in enumerate([(0x7E8>>24)&0xFF, (0x7E8>>16)&0xFF, (0x7E8>>8)&0xFF, 0x7E8&0xFF]):
            pat.Data[i] = b
        fid = ctypes.c_ulong()
        self.dll.PassThruStartMsgFilter(self.chan, PASS_FILTER, ctypes.byref(mask), ctypes.byref(pat), None, ctypes.byref(fid))

    def send_can(self, can_id, data8):
        msg = PASSTHRU_MSG()
        msg.ProtocolID = PROTOCOL_CAN
        msg.DataSize = 4 + len(data8)
        msg.Data[0] = (can_id>>24)&0xFF; msg.Data[1] = (can_id>>16)&0xFF
        msg.Data[2] = (can_id>>8)&0xFF; msg.Data[3] = can_id&0xFF
        for i,b in enumerate(data8): msg.Data[4+i]=b
        n = ctypes.c_ulong(1)
        self.dll.PassThruWriteMsgs(self.chan, ctypes.byref(msg), ctypes.byref(n), 100)

    def read_can(self, timeout_ms=50):
        msg = PASSTHRU_MSG(); n = ctypes.c_ulong(1)
        if self.dll.PassThruReadMsgs(self.chan, ctypes.byref(msg), ctypes.byref(n), timeout_ms)!= STATUS_NOERROR or n.value==0:
            return None
        can_id = (msg.Data[0]<<24)|(msg.Data[1]<<16)|(msg.Data[2]<<8)|msg.Data[3]
        data = bytes(msg.Data[4:4+msg.DataSize-4])
        return can_id, data

    def flush(self):
        while self.read_can(10): pass

# ==== ISO-TP ====
def iso_tp_request(j, service, pid, tx=TX_ID, rx=RX_ID, timeout=1.0):
    j.flush()
    # build single-frame request: PCI=0x02, [svc,pid]
    req = bytes([0x02, service, pid, 0,0,0,0,0])
    j.send_can(tx, req)

    start = time.time()
    buf = bytearray()
    expected_len = None
    seq = 1

    while time.time()-start < timeout:
        r = j.read_can(50)
        if not r: continue
        cid, data = r
        if cid!= rx or not data: continue

        pci = data[0]
        # Single Frame
        if (pci & 0xF0) == 0x00:
            length = pci & 0x0F
            return data[1:1+length] # includes SID,PID,...
        # First Frame
        if (pci & 0xF0) == 0x10:
            if len(data) < 2: continue
            expected_len = ((pci & 0x0F)<<8) | data[1]
            buf.extend(data[2:8]) # take up to 6 bytes
            # send FC to TX_ID
            fc = bytes([0x30, 0x00, 0x0A, 0,0,0]) # BS=0, ST=10ms
            j.send_can(tx, fc)
            continue
        # Consecutive Frame
        if (pci & 0xF0) == 0x20:
            if expected_len is None: continue
            if (pci & 0x0F)!= seq:
                seq = pci & 0x0F # resync
            buf.extend(data[1:8])
            seq = (seq+1) & 0x0F
            if len(buf) >= expected_len:
                return bytes(buf[:expected_len])
        # Negative Response (inside SF)
        if len(data) >= 3 and data[1] == 0x7F:
            return data[1:4] # 7F, sid, nrc
    return None

# ==== decoders (correct offsets) ====
def decode_0100(p):
    if not p or p[0]!=0x41 or p[1]!=0x00: return "bad"
    return f"01-20 supported: {hexd(p[2:6])}"

def decode_0900(p):
    if not p or p[0]!=0x49 or p[1]!=0x00: return "bad"
    return f"09-00 supported: {hexd(p[2:6])}"

def decode_vin(p):
    # p = 49 02 01 [17 ascii]
    if not p or p[0]!=0x49 or p[1]!=0x02: return "bad"
    vin = p[3:3+17].decode('ascii', errors='ignore').strip('\x00')
    return f"VIN: {vin}"

def decode_calid(p):
    if not p or p[0]!=0x49 or p[1]!=0x04: return "bad"
    cal = p[2:].decode('ascii', errors='ignore').strip('\x00')
    return f"CALID: {cal}"

def decode_cvn(p):
    if not p or p[0]!=0x49 or p[1]!=0x06: return "bad"
    cvns = [hexd(p[i:i+4]) for i in range(2, len(p), 4)]
    return f"CVN: {' '.join(cvns)}"

def main():
    dll = r"C:\Program Files (x86)\OBDX Pro\J2534\OBDX Pro FT\OBDXFT_J2534_32bit.dll"
    j = J2534(dll)
    j.open(); j.connect(); j.start_filter()
    print("Connected on CAN 500k")

    tests = [
        (0x01,0x00, decode_0100, "Mode1 PIDs"),
        (0x09,0x00, decode_0900, "Mode9 PIDs"),
        (0x09,0x02, decode_vin, "VIN"),
        (0x09,0x04, decode_calid,"CALID"),
        (0x09,0x06, decode_cvn, "CVN"),
    ]

    for svc,pid,dec,name in tests:
        resp = iso_tp_request(j, svc, pid)
        if resp and len(resp)>=2 and resp[0]==0x7F:
            print(f"{name}: NRC {resp[2]:02X}")
        else:
            print(f"{name}: {dec(resp) if resp else 'no response'}")
        time.sleep(0.1)

    j.close()

if __name__ == "__main__":
    main()
And the result

Code: Select all

Connected on CAN 500k
Mode1 PIDs: 01-20 supported: BF 9F B9 97
Mode9 PIDs: 09-00 supported: 55 00 00 00
VIN: VIN: 1FTSW215X8EE00420
CALID: CALID: TDDG8N2.HEX
CVN: CVN: 01 FF E0 51 DC
Notice the pcm changed it's VIN to match the truck.
If HPT actually over wrote where I read the vin, I wonder if it sends a custom bootloader to do a write, or a proprietary ford command that hasn't been openly documented yet. I plan to pull the bin and check it out
OEM boxes. Open questions. Closed source.
The engine knows what it wants. The ECU knows what it got. :hmm:
If the bootloader can read it, so can we. :gear:
The silicon doesn't lie — it's just not telling the whole story. :silent:
User avatar
Ralmo94
Posts: 25
Joined: Thu Mar 12, 2026 10:00 pm
cars: 1995 towncar
1994 k1500
1998 k2500 7.4
2002 Tahoe 5.3
2004 Silverado 1500
2008 F250 5.4

Re: EEC VI Power PC

Post by Ralmo94 »

So I pulled the bin back out of it and opened it in a Hex editor
I searched for the truck vin and it showed up at 0x100C0
I loaded the erased bin, and went there and it was erased.
I also searched for the spare PCMs vin and it wasn't found in either file.

I don't remember the documented write address cut off at the moment, But it appears the vin is outside of that
image1.jpg
image2.jpg
You do not have the required permissions to view the files attached to this post.
OEM boxes. Open questions. Closed source.
The engine knows what it wants. The ECU knows what it got. :hmm:
If the bootloader can read it, so can we. :gear:
The silicon doesn't lie — it's just not telling the whole story. :silent:
User avatar
jakka
Posts: 130
Joined: Mon Dec 11, 2023 1:51 am
cars: 6FPAAAJGSW9E86101
Location: Aus

Re: EEC VI Power PC

Post by jakka »

Try this out and see if it will flash your USA Spanish Oak https://github.com/jakka351/FG-Falcon/b ... 20Tool.exe