LPC0k9PROG
aka.: NXT2NXP

NXT2NXP
A LPC900 ICP-programmer (ISP2ICP bridge) based on Lego-NXT.

ASkr; update 27.9.09:
- layout (and nicer schematics) online

Features:

  • minimal hardware requirements (the NXT doesn't count, here ;)
  • uses FlashMagic (until now)
  • prototype stage; more to come

Another unexpected side project.
I decided to use some LPC903 controllers in another project. I ordered a Keil MCB900 evaluation board to program them.

For whatever reason Farnell decided to cancel my order with a "No MCB900 delivery out of the UK" statement. The article was removed from their stock, too.

Note 9/2009:
I decided that even using my brand new GALEP-5 would be too boring and well, for most hobbyists, it's far too expensive...

Well, what now?
Right, we're going to build our own programmer. For the fun of it all, we are going even further and will misuse a pbLualized Lego NXT for this and call it:

NXT2NXP

There exist two different programming methods for the LPC900 series:

  • ISP, in-system programming via serial interface; only supported by "bigger" LPCs
  • ICP, in-circuit programming via "SPI" interface; for the small ones, like my LPC903

While a direct serial (e.g. a level shifted RS232) connection is sufficient for ISP, I need the more complicated (aka.: not readily available and connectable) one: ICP...

You can hook up the ISP programmables to FlashMagic directly (serial interface) but the ICP types require additional hardware and a "translation" of ISP to ICP codes.

Obviously, all the programming specifications seem(ed) to be *pssst!* a state secret and none of the documents provided by NXP matched the actual programming algorithm.

Anyway, nobody said it can't be done ;)

The equipment...

The prototype...

The, not yet nice, schematic.
I must had a drift to the left, somehow... Click to enlarge...
(27.9.09: new, non-biocad version below)

A more justified variant.
Click to enlarge...

Thanks to a little time during an always too short weekend,
there now is a layout available too.
The RJ-12 jack and socket are TH, hence, no SMDs this time.
(Well, except for a single component. Searching image ;)
If you know any small and standard 3V3, <2%, TH regulator, let me know!


Copper has its limitations.
This one was engraved in pure gold ;-)

After I traded 2 cars, our home and 3 little ponies in for the golden PCB,
I had to save some money on the programming socket.
This was the cheapest one. EUR 64.- from Dobbertin-Elektronik.



To enter programming mode, 7 pulses on the reset pin are required after Vdd rose (1.0ms). The reset pulses are to short for direct bit-banging mechanisms in pbLua. Therefore, the RS485 interface on port 4, in conjunction with direct pin driving methods, is misused for it.

Getting into programming mode.
Click to enlarge...

Main window and settings for FlashMagic.
Select "NXP ICP bridge", here.
Do NOT use "fill flash"

Make sure you select "MCB900". Don't care about T1 and T2.

Reading the first byte of the device signature.
Note the spikes on the PDA line, coupled in from PCL. Keep your cables short!
Click to enlarge...

Looks a little better than the prototype...

If you don't want to pay about 3 EUR/piece (...) for those,
make your own cables with these in conjunction with a little heat shrink tube:

The code...
--
-- nxt2nxp v0.3 (ASkr 9/2009)
--
-- LPC90X ICP programmer (for FlashMagic, until now)
--
-- !!! pbLua >= v18c required !!!
--
--  - USB connection preferred
--  - You may need to download this source in several parts.
--    Otherwise pbLuas byte compiler may run out of memory!
--    Use the "*** for download, only ***" (followed by a garbage-collect)
--    markings as a suggested split indicator.
--


nxt=nil
collectgarbage("collect")
require("nxt_misc")
require("nxt_input")
require("nxt_rs485")
require("nxt_file")
require("nxt_display")
collectgarbage("collect")



-- *** for download, only ***
collectgarbage("collect")



gDisCol=0

gChkSum=0
gBytes=0
gAdrHi=0
gAdrLo=0
gRecType=0
gRec7=0

gTB={}



------------------------------------------------------------------------------------------------------------
-- utilWait()
--
-- Waits (blocks) for <tim> ms.
-----------------------------------------------------------------------------------------------------------
function utilWait(tim)
  local stim=nxt.TimerRead()
  repeat
  until nxt.TimerRead() > stim+tim
end


------------------------------------------------------------------------------------------------------------
-- uWait()
--
-- Waits (blocks) for ~<c> us.
-- Minimum value is ~80us
-- Use this while in a nxt.DisableNXT(1) <-> nxt.DisableNXT(0) loop!
-----------------------------------------------------------------------------------------------------------
function uWait(c)
  local i
  c=c*10
  c=(c/24)-32
  for i=1,c do
  end
end


------------------------------------------------------------------------------------------------------------
-- ICPloop
--
-----------------------------------------------------------------------------------------------------------
function ICPloop()
  local ch;
  local i

  while 1 do
    collectgarbage("collect")
    gChkSum=0
    repeat
      ch=ICPecho()

      -- baud rate detection override
      if ch=="U" then
        nxt.FileWrite(0,"U")
        nxt.FileWrite(0,"U")
      end
      -- try to enter prog mode again
      if ch=="'" then
        ICPinit()
        ICPenter()
      end
    until ch==":" or ch=="*"

    if ch=="*" then break end

    gBytes=ICPget2()
    gAdrHi=ICPget2()
    gAdrLo=ICPget2()
    gRecType=ICPget2()

    for i=0,gBytes-1 do
      gTB[i]=ICPget2()
    end

    gRec7=gChkSum

    if gRec7 ~= ICPget2() then
      print("X")
      gRecType=999
    end

    -- *** READ_VERSION ***
    if gRecType==1 then
      nxt.FileWrite(0,string.format("%02x",0x07))
      nxt.FileWrite(0,string.format("%02x",0x07))
      print(".")

    -- *** PROGRAM ***
    elseif gRecType==0 then
      ICPprogram()
      print(".")

    -- *** MISC_WRITE ***
    elseif gRecType==2 then
      ICPwrite_config()
      print(".")

    -- *** MISC_READ ***
    elseif gRecType==3 then
      ICPread_config()
      nxt.FileWrite(0,string.format("%02x",gTB[0]))
      print(".")

    -- *** ERASE ***
    elseif gRecType==4 then
      if gTB[0]==0 then
        ICPerase_page()
        print(".")
      elseif gTB[0]==1 then
        ICPerase_sector()
        print(".")
      else
        print("R")
      end

    -- *** SECTOR CRC ***
    elseif gRecType==5 then
      ICPcrcsector()
      gRecType=888

    -- *** GLOBAL CRC ***
    elseif gRecType==6 then
      ICPcrcglobal()
      gRecType=888

    -- *** CHIP_ERASE ***
    elseif gRecType==8 then
      ICPerase_global()
      print(".")

    -- *** CONTINUE ***
    elseif gRecType==999 then
      i=i

    -- *** INCORRECT RECORD TYPE ***
    elseif 1 then
      print("R")

    end -- elseif

    -- *** PRINT CRC ***
    if gRecType==888 then
      nxt.FileWrite(0,string.format("%02x",gTB[3]))
      nxt.FileWrite(0,string.format("%02x",gTB[2]))
      nxt.FileWrite(0,string.format("%02x",gTB[1]))
      nxt.FileWrite(0,string.format("%02x",gTB[0]))
      print(".")
    end

  end -- while 1

end



-- *** for download, only ***
collectgarbage("collect")



------------------------------------------------------------------------------------------------------------
-- dp()
--
-- debug print
-----------------------------------------------------------------------------------------------------------
function dp(msg)
  nxt.DisplayText(string.format("%02x",msg),gDisCol)
  gDisCol=gDisCol+20
  if gDisCol >= 100 then
    gDisCol=0
    nxt.DisplayScroll()
  end
end


------------------------------------------------------------------------------------------------------------
-- ICPinit
--
-----------------------------------------------------------------------------------------------------------
function ICPinit()
  -- Port 2
  --   dig0 -> PDA
  --   dig1 -> PCL
  --   POWER
  nxt.InputSetType(2,0)
  nxt.InputSetDir(2,1,1)
  nxt.InputSetState(2,0,0)

  -- Port 3
  --   dig0 -> n.c.
  --   dig1 -> VDD
  nxt.InputSetType(3,0)
  nxt.InputSetDir(3,1,1)
  nxt.InputSetState(3,0,0)

  -- Port 4
  --   dig0 -> n.c.
  --   dig1 -> RESET
  nxt.InputSetType(4,0)
  nxt.InputSetDir(4,1,0)

  utilWait(1000)

end



------------------------------------------------------------------------------------------------------------
-- ICPexit
--
-----------------------------------------------------------------------------------------------------------
function ICPexit()
  -- damn, someone deleted this ;)
end



------------------------------------------------------------------------------------------------------------
-- ICPenter
--
-- PureMagic ;)
-----------------------------------------------------------------------------------------------------------
function ICPenter()
  local dat=string.char(0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0)
  local i
  local j

  nxt.DisableNXT(1)

  nxt.RS485Enable(230000)   -- works fine...
--  nxt.RS485Enable(460000) -- NORMAL !!!
--  nxt.RS485Enable(921600)
  nxt.InputSetState(4,1,1)
  utilWait(1500)

  nxt.InputSetState(3,1,1)

  uWait(1000)

  nxt.RS485SendData(dat)
  nxt.InputSetState(4,1,0)

  -- for 460000 bit/s: 45
  -- for 230000 bit/s: 90
  for i=1,90 do
    j=i
  end

  nxt.InputSetState(2,0,0)
  nxt.InputSetType(4,0)

  nxt.DisableNXT(0)

end




------------------------------------------------------------------------------------------------------------
-- ICPwait128
--
-----------------------------------------------------------------------------------------------------------
function ICPwait128()
  local i

  repeat
    ICPmultishiftout({0x0f})
    i=ICPshift_in()
    -- emergency exit
    if nxt.ButtonRead() ~= 0 then
      break
    end
  until(i>127)

end


------------------------------------------------------------------------------------------------------------
-- ICPecho
--
-----------------------------------------------------------------------------------------------------------
function ICPecho()
  local ch

  ch=nxt.FileRead(0,1)
  ch=string.upper(ch)
  nxt.FileWrite(0,ch)
  return ch

end



-- *** for download, only ***
collectgarbage("collect")



------------------------------------------------------------------------------------------------------------
-- ICPascii2hex
--
-----------------------------------------------------------------------------------------------------------
function ICPascii2hex(ch)
  -- alternative:
  local i
  ch=string.byte(ch)
  if ch > 0x3f then
    ch=ch+9
  end
  i=ch/16   -- shift right and snip
  i=i*16    -- shift snipped val left
  ch=ch-i


--  ch=string.byte(ch)
--  if nxt.band(ch,0x40) ~= 0 then
--    ch=ch+9
--  end
--  ch=nxt.band(ch,0x0f)

  return ch

end


------------------------------------------------------------------------------------------------------------
-- ICPget2
--
-----------------------------------------------------------------------------------------------------------
function ICPget2()
  local ch

  ch=ICPascii2hex(ICPecho())*(2^4)
  ch=ch+ICPascii2hex(ICPecho())

  gChkSum=gChkSum-ch

  if gChkSum < 0 then
    gChkSum=gChkSum+256
  end

  return ch

end


------------------------------------------------------------------------------------------------------------
-- ICPmultishiftout
--
--   DIG0 -> PDA
--   DIG1 -> PCL
-----------------------------------------------------------------------------------------------------------
function ICPmultishiftout(ch)
  local i,j
  local byte

  for _,byte in ipairs(ch) do

    nxt.DisableNXT(1)

    for i=1,8 do
      nxt.InputSetState(2,0,0)      -- PCL low (NEW: was low before)
      if byte%2 == 1 then
        j=1
      else
        j=0
      end
      nxt.InputSetState(2,j,0)        -- set PDA (and keep PCL)
      nxt.InputSetState(2,j,1)        -- PCL high (but keep PDA)
      byte=byte/2
    end

    nxt.InputSetState(2,0,0)        -- PCL and PDA low

    nxt.DisableNXT(0)

  end -- end for whole table

end



------------------------------------------------------------------------------------------------------------
-- ICPshift_in
--
--   DIG0 -> PDA
--   DIG1 -> PCL
-----------------------------------------------------------------------------------------------------------
function ICPshift_in()
  local i,rbit
  local di=0

  nxt.DisableNXT(1)
  nxt.InputSetState(2,0,0)      -- PCL low
  nxt.InputSetDir(2,0,1)        -- PDA input

  for i=1,8 do
    di=di/2
    nxt.InputSetState(2,nil,1)      -- PCL high
    _,rbit=nxt.InputGetStatus(2)
    if rbit == 1 then
      di=di+128
    end
    nxt.InputSetState(2,nil,0)      -- PCL low

  end

  nxt.InputSetState(2,0,0)        -- keep both low

  nxt.DisableNXT(0)

  return di

end



-- *** for download, only ***
collectgarbage("collect")



------------------------------------------------------------------------------------------------------------
-- ICPprogram
--
-----------------------------------------------------------------------------------------------------------
function ICPprogram()
  local i

  for i=0,gBytes-1 do
    ICPmultishiftout({0x08,gAdrLo,0x0a,gAdrHi,0x0e,0x00,0x0c,gTB[i],0x0e,0x48})
    ICPwait128()

    gAdrLo=gAdrLo+1
    if gAdrLo>255 then
      gAdrLo=0
      gAdrHi=gAdrHi+1
    end

  end -- end for

end





------------------------------------------------------------------------------------------------------------
-- ICPcrcsector
--
-----------------------------------------------------------------------------------------------------------
function ICPcrcsector()
  local i

  ICPmultishiftout({0x0a,gTB[0],0x0e,0x19})
  ICPwait128()

  for i=0,3 do
    ICPmultishiftout({0x05})
    gTB[i]=ICPshift_in()
  end

end



------------------------------------------------------------------------------------------------------------
-- ICPcrcglobal
--
-----------------------------------------------------------------------------------------------------------
function ICPcrcglobal()
  local i

  ICPmultishiftout({0x0e,0x1a})
  ICPwait128()

  for i=0,3 do
    ICPmultishiftout({0x05})
    gTB[i]=ICPshift_in()
  end

end



------------------------------------------------------------------------------------------------------------
-- ICPwrite_config
--
-----------------------------------------------------------------------------------------------------------
function ICPwrite_config()
  local i=0


  if gTB[0] == 0x10 then
    ICPmultishiftout({0x0e,0x67,0x0c,0x96 })
  else
    ICPmultishiftout({0x0e,0x6c,0x08,gTB[0],0x0c,gTB[1]})
  end

  ICPwait128()

end



------------------------------------------------------------------------------------------------------------
-- ICPread_config
--
-----------------------------------------------------------------------------------------------------------
function ICPread_config()
  ICPmultishiftout({0x0e,0x6c,0x08,gTB[0],0x0d})
  gTB[0]=ICPshift_in()
end


------------------------------------------------------------------------------------------------------------
-- ICPerase_global
--
-----------------------------------------------------------------------------------------------------------
function ICPerase_global()
  local i

  ICPmultishiftout({0x0e,0x72})

-- NEW
  for i=1,5 do
    ICPmultishiftout({0x05})
    ICPshift_in()
  end

  ICPwait128()

end



-- *** for download, only ***
collectgarbage("collect")



------------------------------------------------------------------------------------------------------------
-- ICPerase_page
--
-----------------------------------------------------------------------------------------------------------
function ICPerase_page()
  local i

  ICPmultishiftout({0x08,gTB[2],0x0a,gTB[1],0x0e,0x70})
  ICPwait128()

end



------------------------------------------------------------------------------------------------------------
-- ICPerase_sector
--
-----------------------------------------------------------------------------------------------------------
function ICPerase_sector()
  local i

  ICPmultishiftout({0x0a,gTB[1],0x0e,0x71})
  ICPwait128()

end



------------------------------------------------------------------------------------------------------------
-- kill
--
-- On larger programs, like this, you may experience download problems of
-- changed functions because luas byte compiler needs too much memory...
-- Usually, there is no other way than rebooting you NXT.
-- Here's my strategy to empty the memory ;)
-----------------------------------------------------------------------------------------------------------
function kill()
  utilWait=nil
  uWait=nil
  ICPloop=nil
  dp=nil
  ICPinit=nil
  ICPexit=nil
  ICPenter=nil
  ICPwait128=nil
  ICPecho=nil
  ICPascii2hex=nil
  ICPget2=nil
  ICPmultishiftout=nil
  ICPshift_in=nil
  ICPprogram=nil
  ICPcrcsector=nil
  ICPcrcglobal=nil
  ICPwrite_config=nil
  ICPread_config=nil
  ICPerase_global=nil
  ICPerase_page=nil
  ICPerase_sector=nil
  t=nil
  collectgarbage("collect")
  print(collectgarbage("count"))
end



------------------------------------------------------------------------------------------------------------
-- run
--
-- Leaves programming mode and "starts" your LPC90X.
-- !!! Be aware of potential short-circuit conditions if you use it for more than
-- !!! programming test firmware!
-- !!! Reset and PCL pins are always outputs on NXT2NXP!
-----------------------------------------------------------------------------------------------------------
function run()
  -- hold in reset (low)
  nxt.InputSetDir(4,1,1)
  nxt.InputSetState(4,0,1)

  -- power off
  nxt.InputSetDir(3,1,1)
  nxt.InputSetState(3,0,0)

  utilWait(1500)

  -- power on
  nxt.InputSetState(3,0,1)
  -- go...
  nxt.InputSetState(4,0,0)

end



------------------------------------------------------------------------------------------------------------
-- prog
--
-- Starts programming mode.
-- Disconnect from your port and start FlashMagic afterwards...
-----------------------------------------------------------------------------------------------------------
function prog()
  local i

  for i=1,8 do
    nxt.DisplayScroll()
  end

  ICPinit()
  ICPenter()
  ICPloop()
  ICPexit()
end



-- *** for download, only ***
collectgarbage("collect")


Some quick notes on usage (I already ran out of time, again):

  • Connect your NXT via USB (or try Bluetooth by your own...)
  • Download code to your NXT (may need multiple downloads of smaller chunks)
  • Start programming mode with "prog()"
    • "*" will leave the program
    • "'" will repeat the enter program mode sequence
    • If somethings seems to lock up, hold down the orange button.
  • Disconnect from your port and start FlashMagic on the same one (settings: see above).
  • Read ID, Erase (global or block), config device, program, ...
  • Note: CRC and blank check do not work. LPC900 always sends 0x00, only...
    Looks like a limitation of the device or FlashMagic...
  • For testing purposes, you may reconnect to your NXT, hit "*" (to leave program)
    and call "run()" (read the code for more!)



Includes:
- schematic (PDF)
- placement (PDF)
- stinky BOM (TXT)
- layout, eagle (BRD)

DOWNLOAD: lpc0k9prog.zip



ASkr 7/2009
ASkr 9/2009