ICP programmer for obsolete LPC9xxx MCU - Page 1
Forum section: electronics / general

author: grg183
author_rank: Contributor
timestamp: December 23, 2019, 10:47:37 pm
content: I've come across an old board with the LPC9361 which I need to re-program. The interface available is ICP but I am not sure which programmer I need. All online searches point to the FDI USB-ICP-LPC9xx but this has long been obsolete. Is there any current programmer that can do ICP programming on the LPC9361?Note: removing the chip from the board is not an option and only the ICP interface is available.

author: coromonadalix
author_rank: Super Contributor
timestamp: December 25, 2019, 01:36:46 am
content: Maybe send them an email ?, you have some docs and softwares there :https://www.teamfdi.com/product-details/usb-icp-lpc9xxOh i see you asked the same thing on NXP forums and had an answergood luck

author: grg183
author_rank: Contributor
timestamp: December 25, 2019, 11:56:09 am
content: Yeah I contacted FDI and they just confirmed it is obsolete with no replacement, my understanding is this was based on an old LPC which is itself also obsolete.Similarly NXP pointed me to a bunch of obsolete stuff.I think my best bet is to build an adapter for a universal chip programmer such as the Galep-5 which supports the LPC9xxx and from there interface to the ICP pads on the PCB. I was hoping I could find a simple programmer.If anyone has one of those FDI programmers and is interested in selling it please let me know.

author: coromonadalix
author_rank: Super Contributor
timestamp: December 25, 2019, 02:08:36 pm
content: My programmer can do it by isp I have an Elnec beeprog+I dont think your near me loll You should complete your profile ?

author: pdove
author_rank: Newbie
timestamp: December 30, 2019, 01:40:20 pm
content: I made my own programmer with an Arduino let me know if you are interested and I’ll tell you how. I was using it to program an NXP P89LPC938.

author: grg183
author_rank: Contributor
timestamp: December 30, 2019, 03:28:55 pm
content: @coromonadalix Unfortunately ISP is not an option, I only have ICP programming pins accessible.@pdove If you can share any info on how you did it that would be great. I was thinking of trying to implement it myself based on the ICP spec documenthttps://www.teamfdi.com/wp-content/uploads/ICP_LPC9xx_v24.pdf

author: jesuscf
author_rank: Frequent Contributor
timestamp: December 30, 2019, 08:25:29 pm
content: Quote from: grg183 on December 30, 2019, 03:28:55 pmA few years back I wrote a program to ICP an LPC9351 from another LPC9351. It should be portable to something that is less obsolete like an Arduino. The ICP documentation I used was more recent than the one you linked, but I can not find it with google. The one I used has the title "P89LPC900 In-Circuit Programming (ICP) Specifications" and the date is "2006 Apr 10". Here is the C program. Sorry for the mixing of tabs and spaces...Code:/* ISP2ICP.c: ISP bridge for NXP LPC900 microcontrollers. Based on"P89LPC900 In-Circuit Programming (ICP) Specifications" (2006 Apr 10)by jesuscfThis program is free software; you can redistribute it and/or modify itunder the terms of the GNU General Public License as published by theFree Software Foundation; either version 2, or (at your option) anylater version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free SoftwareFoundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */#include <stdio.h>#include <p89lpc9351.h>#define XTAL 7373000L#define BAUD 19200L#define BRGR ((XTAL/BAUD)-16)#define TIMER0_1ms_RELOAD (65536L-((XTAL)/(2*1000L)))#define TIMER0_15us_RELOAD (65536L-((XTAL)/(2*66667L))) // 1/66667Hz = 15us#define VDD P0_0#define PDA P0_1#define PCL P0_2#define RESET P0_3#define LED P0_4#define CLONE P0_5// Table 3: Programming commands#define NOP 0x00 // NOP#define WR_FMADRL 0x08 // Write address low command#define RD_FMADRL 0x09 // Read address low command#define WR_FMADRH 0x0A // Write address high command#define RD_FMADRH 0x0B // Read address high command#define WR_FMCON 0x0E // Write a command to FMCON#define RD_FMCON 0x0F // Read a command from FMCON#define WR_FMDATA_PG 0x14 // Write a command to FMDATA and increment address#define RD_FMDATA_PG 0x15 // Read a command from FMDATA and increment address#define WR_FMDATA 0x0C // Write a command to FMDATA#define RD_FMDATA 0x0D // Read a command from FMDATA#define WR_FMDATA_I 0x04 // Write a command to FMDATA and increment address#define RD_FMDATA_I 0x05 // Read a command from FMDATA and increment address// Table 4: FMCON Write commands#define LOAD 0x00 // Clear and then load the page register#define PROG 0x48 // Program page with page register command#define ERS_S 0x71 // Erase sector command#define ERS_P 0x70 // Erase page command#define CONF 0x6C // Acces user configuration information addressed by FMADRL#define CRC_G 0x1A // Calculate CRC on the entire user code command#define CRC_S 0x19 // Calculate CRC on a sector command#define CCP 0x67 // Clear configuration protection#define MAXBUFF 64unsigned char buff;unsigned char size, type, checksum;unsigned int address;char echo;bit autobaud_done=0;// ISP record types. Check (for example) NXP's manual UM10308// table 132: "In-system Programming (ISP) hex record formats"enum ISP_FUNCTION {PROGRAM,READ_VERSION,MISC_WRITE,MISC_READ,ERASE,SECTOR_CRC,GLOBAL_CRC,LOAD_BAUD,RESET_MCU} ISP_Function;unsigned char _c51_external_startup (void){// Set ports 0 and 1 to bidirectional mode.P0M1 = 0;P0M2 = 0;P1M1 = 0;P1M2 = 0;// Initialize ICP signalsVDD=1; // 1:0ff, 0:OnRESET=0;PDA=0;PCL=0;// Initialize push button and led pinsLED=1;CLONE=1; // Used as input// Initialize UART and baud rate generatorBRGCON=0;BRGR0=BRGR%0x100;BRGR1=BRGR/0x100;BRGCON=0x03;SCON=0x52;return 0;}void putchar (char c){while (!TI);TI=0;SBUF=c;}char getchar(void) // With echo!{char c;while (!RI);RI=0;c=SBUF;putchar(c);return c;}void Send_echo (void){putchar(echo);putchar('\r');putchar('\n');}void Autobaud (void){unsigned int j;RI=0;// Uses timer 0 to measure the bit width and then// computes the baud rate using BRGR=[TH0,TL0]*2-16;TMOD=0x01; // Timer 0 in 16-bit modeTR0=0; // Stop Timer 0TH0=0;TL0=0;while(RXD==0);while(RXD==1);TR0=1; // Start Timer 0while(RXD==0);TR0=0; // Stop Timer 0//Set the new baud ratej=(TH0*0x100+TL0)*2-16;BRGCON=0;BRGR0=j%0x100;BRGR1=j/0x100;BRGCON=0x03;}void Wait_ms (unsigned int n) // Wait 'n' milliseconds{TR0=0;TMOD=(TMOD&0xF0)|0x01; // Timer 0 in 16-bit modewhile(n!=0){// 1 msec delay using timer 0TR0=0;TH0=TIMER0_1ms_RELOAD/0x100;TL0=TIMER0_1ms_RELOAD%0x100;TF0=0;TR0=1;while(!TF0);n--;}}void Wait_15us (void){// 15us delay using timer 0TR0=0;TMOD=(TMOD&0xF0)|0x01; // Timer 0 in 16-bit modeTH0=TIMER0_15us_RELOAD/0x100;TL0=TIMER0_15us_RELOAD%0x100;TF0=0;TR0=1;while(!TF0);}unsigned char chartohex (char c){if(c & 0x40) c+=9; // a to f or A to Freturn (c & 0xf);}// Get a byte from the serial portunsigned char getbyte (void){unsigned char j;j=chartohex(getchar())*0x10;j|=chartohex(getchar());return j;}void Print_Hex (unsigned char c){code char ASCII2HEX[]="0123456789ABCDEF";putchar(ASCII2HEX[c/0x10]);putchar(ASCII2HEX[c%0x10]);}// IMPORTANT! For ICP_Send() and ICP_Get():// Serial clock high > 80 ns// Serial clock low > 80 ns// Serial clock Period > 200 ns////See Figure 37: Writing datavoid ICP_Send (unsigned char value){unsigned char mask=0x01;do {PCL=0;PDA=(value & mask)?1:0;mask*=2;PCL=1;} while (mask!=0); //80H * 2 = 0x100 -> 0PDA=1;}// See Figure 38: Reading dataunsigned char ICP_Get (void){unsigned char mask=0x01, j;PDA=1; // Use pin as inputj=0;do {PCL=0;if(PDA) j|=mask;mask*=2;PCL=1;} while (mask!=0); //80H * 2 = 0x100 -> 0return j;}//See section 3.0: Checking status informationvoid Check_Status (void){unsigned char j;do{ICP_Send(RD_FMCON);j=ICP_Get();}while((j & 0x80)==0x80); // Check the BUSY flag: Set during a program or erase.if(j&0x0f) echo='X'; // Any errors?}// See Figure 36: Getting into the programming mode// RESET delay from VDD active 50us Minimum// RESET HIGH time Min=1 Max=32 us// RESET LOW time 1 minimum usvoid Activate_ICP (void){unsigned char j = 0;RESET=0;PDA=0;PCL=0;VDD=1; // Make sure VDD is offWait_ms(100);PDA=0;PCL=1;VDD=0; // Turn on VDDWait_ms(100);for(j=0; j<7; j++) // Pulse reset 7 times{RESET=1;Wait_15us();RESET=0;Wait_15us();}RESET=1;Wait_ms(10);}// See Figure 26: Loading page register partially.// See Figure 27: Programming page register.void Program (void){unsigned char j, k;// NOTE: There is a "page register" of 64 bytes for some// ICs and 16 for some other ICs. The "page register" is just// a block of RAM which we load with the data to be flashed. Then// we apply the "PROG" command to execute the actual flashing.// We can start loading the "page register" at any address in// the buffer. Also note that the "LOAD" command sets all the// bytes in the "page register" to zero. This turns out to be a// good thing as in LPC900 microcontrollers zero is the erased// or blank state of flash bytes. Therefore we can flash pieces// of the current page without affecting bytes we have not set// in the same page. Also, there is an error in figure 26 as// noted bellow.//The data to flash must fit into one page (without roll over!)j=(address/0x100)&0x3f;k=((address+size)/0x100)&0x3f;if(j>k){echo='X'; // Error: data is not page alignedreturn;}// Load page registerICP_Send(WR_FMCON);ICP_Send(LOAD);ICP_Send(WR_FMADRL);ICP_Send(address%0x100);for(j=0; j<size; j++){ICP_Send(WR_FMDATA_I); // Outside the loop in figure 26ICP_Send(buff);}// Program the page registerICP_Send(WR_FMADRL);ICP_Send(address%0x100);ICP_Send(WR_FMADRH);ICP_Send(address/0x100);ICP_Send(WR_FMCON);ICP_Send(PROG);Check_Status();}void Read_Version (void){Print_Hex(0x04); // ISP versionPrint_Hex(0x04); // ICP version}// See Figure 34: Writing config bytesvoid Misc_Write (void){if(buff<0x20){if(buff==0x10){// Clear configuration protectionICP_Send(WR_FMCON);ICP_Send(CCP);ICP_Send(WR_FMDATA);ICP_Send(0x96);Check_Status(); // Not in the ISP specifications, but makes sense!}else{ICP_Send(WR_FMCON);ICP_Send(CONF);ICP_Send(WR_FMADRL);ICP_Send(buff); // Subfunction codeICP_Send(WR_FMDATA);ICP_Send(buff); // DataCheck_Status();}}else echo='X'; // There are only 1FH configuration bytes... so far!}// See Figure 33: Reading config bytesvoid Misc_Read (void){if(buff<0x20){ICP_Send(WR_FMCON);ICP_Send(CONF);ICP_Send(WR_FMADRL);ICP_Send(buff); // Subfunction codeICP_Send(RD_FMDATA);Print_Hex(ICP_Get());}else echo='X'; // There are only 1FH configuration bytes... so far!}void Erase (void){if (buff==0) // See Figure 30: Erasing single page{ICP_Send(WR_FMADRL);ICP_Send(buff);ICP_Send(WR_FMADRH);ICP_Send(buff);ICP_Send(WR_FMCON);ICP_Send(ERS_P);Check_Status();}else if (buff==1) // See Figure 29: Erasing single sector{ICP_Send(WR_FMADRH);ICP_Send(buff);ICP_Send(WR_FMCON);ICP_Send(ERS_S);Check_Status();}else echo='X'; // Erase what?}// See Figure 32: Sector CRCvoid Sector_CRC (void){unsigned char j;ICP_Send(WR_FMADRH);ICP_Send(buff);ICP_Send(WR_FMCON);ICP_Send(CRC_S);Check_Status();for(j=0; j<4; j++) // The CRC is a 32-bit number (4 bytes){ICP_Send(RD_FMDATA_I);buff=ICP_Get();}Print_Hex(buff);Print_Hex(buff);Print_Hex(buff);Print_Hex(buff);}// See Figure 31: Global CRCvoid Global_CRC (void){unsigned char j;ICP_Send(WR_FMCON);ICP_Send(CRC_G);Check_Status();for(j=0; j < 4; j++) // The CRC is a 32-bit number (4 bytes){ICP_Send(RD_FMDATA_I);buff=ICP_Get();}Print_Hex(buff);Print_Hex(buff);Print_Hex(buff);Print_Hex(buff);}void Load_Baud (void){Send_echo();while (!TI);Wait_ms(10);BRGCON=0;BRGR1 = buff;BRGR0 = buff;BRGCON = 0x03;}void Reset_MCU (void){Send_echo();while (!TI);AUXR1|=SRST;}// Sets a P89LPC9xxx (8k variants only!) to its default factory// setup by:// a) copying the bootloader of this microcontroller into the ICP-ed microcontroller.// b) Setting the default values for some registers in the ICP-ed microcontroller.//// This code is executed after the "CLONE" push button is pressed.void Factory_Default (void){#define READ_CODE(X) (*((unsigned char code *) X))unsigned int j;unsigned char k;LED=0;// Change to default baud rateBRGCON=0;BRGR0=BRGR%0x100;BRGR1=BRGR/0x100;BRGCON=0x03;autobaud_done=0; // So ISP2ICP bridge can be called againgputs("Activating ICP...\r");Activate_ICP();puts("Erasing...\r");// Erase all sectorsfor(j=0x0000; j<0x2000; j+=0x400){buff=1; // Erase sectorbuff=j/0x100;Erase();}puts("Copying...\r");// Copy this P89LPC9351 bootloader to the ICP-ed P89LPC99351for(j=0x1e00; j<0x2000; j+=MAXBUFF) // The bootloader starts at address 0x1e00{address=j;size=MAXBUFF;for(k=0; k<MAXBUFF; k++)buff=READ_CODE(j+k);Program();}puts("Clearing Configuration Protection...\r");buff=0x10; // Just in case is set, before writing to UCFG1, UCFG1, etc.Misc_Write();puts("Setting UCFG1...\r");buff=0x00;buff=0x63; // Default valueMisc_Write();puts("Setting UCFG2...\r");buff=0x01;buff=0x00; // Default valueMisc_Write();puts("Setting boot vector...\r");buff=0x02;buff=0x1f; // Default entry point of ISP bootloader (0x1f00)Misc_Write();puts("Setting boot vector start...\r");buff=0x03;buff=0x01; //0x01: Start with the boot vector addressMisc_Write();puts("Done!\r");LED=1;VDD=1;}void main (void){unsigned char j, n;char c;while(1){if(RI){c=getchar();if(autobaud_done==0){if (c!='U') Autobaud();autobaud_done=1;Activate_ICP();}// Read and process Intel hex recordsif(c==':'){echo='.'; // If everything works out, send a period.size=getbyte();checksum=size;address=getbyte();checksum+=address;address*=0x100;n=getbyte();checksum+=n;address+=n;type=getbyte();checksum+=type;for(j=0; j<size; j++){n=getbyte();if(j<MAXBUFF) buff=n; // Don't overrun the buffer!checksum+=n;}checksum+=getbyte();if(size>MAXBUFF) checksum=1; // Force a checksum errorif(checksum==0){LED=0;switch(type){case PROGRAM: Program(); break;case READ_VERSION: Read_Version(); break;case MISC_WRITE: Misc_Write(); break;case MISC_READ: Misc_Read(); break;case ERASE: Erase(); break;case SECTOR_CRC: Sector_CRC(); break;case GLOBAL_CRC: Global_CRC(); break;case LOAD_BAUD: Load_Baud(); break;case RESET_MCU: Reset_MCU(); break;default: echo='X'; break; // Unknown record type}LED=1;}else echo='X'; // Checksum errorif (type!=LOAD_BAUD) Send_echo();}}if(CLONE==0){Wait_ms(50); // Debounceif(CLONE==0){while(CLONE==0);Factory_Default();}}}}Edit: Found "P89LPC900 In-Circuit Programming (ICP) Specifications" from "2006 Apr 10" here:https://bbs.21ic.com/UploadFiles/2010-7/172328177724.pdf

author: coromonadalix
author_rank: Super Contributor
timestamp: December 31, 2019, 03:18:49 am
content: Maybe some links like theses:https://www.teamfdi.com/wp-content/uploads/ICP_LPC9xx_v24.pdfhttps://handsontec.com/pdf_files/ICP_Programmer.pdf

author: grg183
author_rank: Contributor
timestamp: December 31, 2019, 11:12:23 am
content: @jesuscf Thanks! That code looks very usable and portable, will definitely give it a go.

author: PCB.Wiz
author_rank: Super Contributor
timestamp: December 31, 2019, 07:35:59 pm
content: Quote from: grg183 on December 23, 2019, 10:47:37 pmGoogle finds posts like this (in German) - does the device still contain the bootloader it ships with ?https://www.mikrocontroller.net/topic/94307

author: jesuscf
author_rank: Frequent Contributor
timestamp: January 03, 2020, 08:51:27 pm
content: Quote from: PCB.Wiz on December 31, 2019, 07:35:59 pmThere several ways of loading the flash of LPC9xx micro controllers:- In some parts, parallel programming. Option not available for OP as the IC is soldered to the board, and the particular part OP has may not even support it.- In some parts, In-System Programming (ISP). This is done via the serial port and a bootloader. Although possible in the LPC9361 assuming the bootloader had not been erased, is also not available to OP as the serial port pins are probably not available.- In all parts, In-Circuit Programming (ICP). This is done via dedicated pins in the IC. This is the only option available to OP as the ICP pins are the ones provided in the board.