diff --git a/README b/README
--- a/README
+++ b/README
@@ -1,17 +1,17 @@
 Tools for Automotive ECU Development
 
 tovcd    -  Converts several CAN formats to VCD.
-todbc    -  Generate Vector DBC files from CAN specification.
 probe    -  Extracts configuration and probed signals from ECU.
 canview  -  View CAN traffic.
 cansend  -  Send CAN message.
 ccp      -  CAN Configuration Protocol master.
+toesb    -  Converts s-record ECU images to the Eaton Standard Binary format.
 git2cc   -  Bridge Git repositories to ClearCase.
 commit   -  Git commits with ClearQuest activity annotations.
 
 Future Tools:
 
-toesb               -  Converts s-record ECU images to the Eaton Standard Binary format.
+todbc               -  Generate Vector DBC files from CAN specification.
 vcdgrep             -  Search VCD files for signal patterns and assertion violations.
 brakedancer-layout  -  Generate a brakedancer signal layout from a VCD file.
 ecush               -  ECU development shell.
diff --git a/ecu.cabal b/ecu.cabal
--- a/ecu.cabal
+++ b/ecu.cabal
@@ -1,5 +1,5 @@
 name:    ecu
-version: 0.0.2
+version: 0.0.3
 
 category: Embedded, Utils
 
@@ -61,7 +61,7 @@
   extra-libraries:   canlib
   build-depends:     base       >= 4.0     && < 5
   ghc-options:       -W
-  extensions:        ForeignFunctionInterface
+  extensions:        ForeignFunctionInterface, FlexibleInstances
 
 executable probe
   hs-source-dirs:    src
diff --git a/src/CCP.hs b/src/CCP.hs
--- a/src/CCP.hs
+++ b/src/CCP.hs
@@ -1,8 +1,10 @@
 module Main (main) where
 
 import Control.Monad
+import Data.IORef
 import Data.Word
 import System.Environment
+import System.IO
 import Text.Printf
 
 import CAN
@@ -17,11 +19,12 @@
   , "  ccp command { option } { argument }"
   , ""
   , "COMMANDS"
-  , "  upload { option } address           Upload 4 bytes from the ECU.  Address must be 32-bit aligned."
-  , "  upload { option } address length    Upload a memory region from the ECU.  Address must be 32-bit aligned."
+  , "  download address data    Download data to ECU.  Data must be even number of hex characters."
+  , "  upload   address         Upload 4 bytes from the ECU."
+  , "  upload   address size    Upload a memory region from the ECU."
   , ""
   , "OPTIONS"
-  , "  --std    Set to standard CAN with 500K.  Default is extended CAN with 250K."
+  , "  -v    Print message transactions and diagnostic messages."
   , ""
   , "ENVIRONMENT VARIABLES"
   , "  CCP_ID_MASTER    CAN identifier for master device (this configuration tool)."
@@ -35,39 +38,168 @@
   f args = case args of
     ["-h"] -> help
     ["--help"] -> help
-    ["upload",          addr] -> f ["upload",          addr, "4"]
-    ["upload", "--std", addr] -> f ["upload", "--std", addr, "4"]
-    ["upload", "--std", addr, size] -> do
-      initCAN
-      bus <- openBus 0 Standard
-      ids <- ids
-      upload bus ids (read addr) (read size)
-      closeBus bus
+    ["download", addr, bytes] -> do
+      bytes <- parseBytes bytes
+      connection <- connect False
+      download connection (read addr) bytes
+      disconnect connection
+    ["download", "-v", addr, bytes] -> do
+      bytes <- parseBytes bytes
+      connection <- connect True
+      download connection (read addr) bytes
+      disconnect connection
+    ["upload", "-v", addr] -> f ["upload", "-v", addr, "4"]
+    ["upload", "-v", addr, size] -> do
+      connection <- connect True
+      upload connection (read addr) (read size)
+      disconnect connection
+    ["upload", addr] -> f ["upload", addr, "4"]
     ["upload", addr, size] -> do
-      initCAN
-      bus <- openBus 0 Extended
-      ids <- ids
-      upload bus ids (read addr) (read size)
-      closeBus bus
+      connection <- connect False
+      upload connection (read addr) (read size)
+      disconnect connection
     _ -> help
 
-canview :: Bus -> [Word32] -> IO ()
-canview bus ids = do
-  m <- recvMsg bus
-  case m of
-    Nothing -> canview bus ids
-    Just (t, m@(Msg id _)) -> do
-      when (null ids || elem id ids) $ putStrLn $ printf "%10i   " t ++ show m
-      canview bus ids
+data Connection = Connection
+  { cBus     :: Bus
+  , cMaster  :: Word32
+  , cSlave   :: Word32
+  , cCounter :: IORef Word8
+  , cVerbose ::  Bool
+  }
 
-ids :: IO (Word32, Word32)
-ids = do
-  master <- getEnv "CCP_ID_MASTER"
-  slave  <- getEnv "CCP_ID_SLAVE"
-  return (read master, read slave)
+connect :: Bool -> IO Connection
+connect verbose = do
+  initCAN
+  bus     <- openBus 0 Extended
+  flushRxQueue bus
+  master  <- getEnv "CCP_ID_MASTER"
+  slave   <- getEnv "CCP_ID_SLAVE"
+  counter <- newIORef 0
+  return Connection
+    { cBus     = bus
+    , cMaster  = read master
+    , cSlave   = read slave
+    , cCounter = counter
+    , cVerbose = verbose
+    }
 
-upload :: Bus -> (Word32, Word32) -> Word32 -> Word32 -> IO ()
-upload bus (master, slave) address size = do
-  printf "%08x  %08x\n" master slave
+disconnect :: Connection -> IO ()
+disconnect c = closeBus $ cBus c
+
+transact :: Connection -> Word8 -> [Word8] -> IO (Maybe [Word8])
+transact c command payload = do
+  counter <- readIORef $ cCounter c
+  writeIORef (cCounter c) $ counter + 1
+  let msg = Msg (cMaster c) $ take 8 $ command : counter : payload ++ repeat 0xff
+  when (cVerbose c) $ do
+    printf "send: %s\n" $ show msg
+    hFlush stdout
+  sendMsg (cBus c) msg
+  time <- busTime' $ cBus c
+  recv counter time
+  where
+  recv :: Word8 -> Int -> IO (Maybe [Word8])
+  recv counter time = do
+    m <- recvMsgWait (cBus c) 25    
+    case m of
+      Nothing -> return Nothing
+      Just (t, m@(Msg id payload))
+        | id == cSlave c && length payload == 8 && payload !! 0 == 0xff && payload !! 2 == counter -> do
+            if payload !! 1 == 0
+              then do
+                when (cVerbose c) $ do
+                  printf "recv: %s\n" (show m)
+                  hFlush stdout
+                return $ Just $ drop 3 payload
+              else error $ printf "error code: %02x\n" $ payload !! 1
+        | t - time > 25 -> return Nothing
+        | otherwise -> recv counter time
+
+transactRetry :: Int -> Connection -> Word8 -> [Word8] -> IO [Word8]
+transactRetry n c cmd payload
+  | n <= 0 = error "transactRetry failed to complete"
+  | otherwise = do
+      a <- transact c cmd payload
+      case a of
+        Nothing -> do
+          when (cVerbose c) $ do
+            putStrLn "retrying transaction"
+            hFlush stdout
+          transactRetry (n - 1) c cmd payload
+        Just a  -> return a
+
+setMTA0 :: Connection -> Word32 -> IO ()
+setMTA0 c address = do
+  transactRetry 5 c 0x02 $ 0 : 0 : fromWord32 address
   return ()
+
+upload :: Connection -> Word32 -> Word32 -> IO ()
+upload c address size = do
+  bytes <- upload0 address size
+  sequence_ [ printf "%08x  %s\n" address value | (address, value) <- zip [address, address + 4 ..] (pack $ map (printf "%02x") bytes :: [String]) ]
+  where
+  upload0 :: Word32 -> Word32 -> IO [Word8]
+  upload0 address size
+    | size <= 5 = do
+      a <- transact c 0x0f $ fromIntegral size : 0 : fromWord32 address
+      case a of
+        Nothing -> do
+          when (cVerbose c) $ do
+            printf "upload restart at address %08x\n" address
+            hFlush stdout
+          upload0 address size
+        Just payload -> return $ take (fromIntegral size) payload
+    | otherwise = do
+      setMTA0 c address
+      upload1 address size
+
+  upload1 :: Word32 -> Word32 -> IO [Word8]
+  upload1 address size = if size == 0 then return [] else do
+    a <- transact c 0x04 [if size > 5 then 5 else fromIntegral size]
+    case a of
+      Nothing -> do
+        when (cVerbose c) $ do
+          printf "upload restart at address %08x\n" address
+          hFlush stdout
+        upload0 address size
+      Just payload -> do
+        a <- upload1 (address + min size 5) (max size 5 - 5)
+        return $ take (fromIntegral size) payload ++ a
+
+  pack :: [String] -> [String]
+  pack (a0 : a1 : a2 : a3 : rest) = concat [a0, a1, a2, a3] : pack rest
+  pack [] = []
+  pack a = [concat a]
+       
+parseBytes :: String -> IO [Word8]
+parseBytes a = if odd $ length a then error "hex string has odd number of characters, not byte aligned" else return $ parseBytes' a
+  where
+  parseBytes' :: String -> [Word8]
+  parseBytes' ('0' : 'x' : bytes) = parseBytes' bytes
+  parseBytes' [] = []
+  parseBytes' [_] = error "hex string has odd number of characters, not byte aligned"
+  parseBytes' (a : b : c) = read ("0x" ++ [a, b]) : parseBytes' c
+
+
+download :: Connection -> Word32 -> [Word8] -> IO ()
+download c address bytes = download0 address bytes
+  where
+  download0 :: Word32 -> [Word8] -> IO ()
+  download0 address bytes = do
+    setMTA0 c address
+    download1 address bytes
+
+  download1 :: Word32 -> [Word8] -> IO ()
+  download1 address bytes = if null bytes then return () else do
+    a <- transact c (if n == 6 then 0x23 else 0x03) (if n == 6 then take 6 bytes else fromIntegral n : take n (bytes ++ [0xFF ..]))
+    case a of
+      Nothing -> do
+        when (cVerbose c) $ do
+          printf "download restart at address %08x\n" address
+          hFlush stdout
+        download0 address bytes
+      Just _ -> download1 (address + fromIntegral n) (drop n bytes)
+    where
+    n = min 6 $ length bytes
 
