NXP LPC MCU ISP firmware loader example using ESS scripting language
// NXP LPC MCU ISP firmware loader
//
##require("esMcuDbNXP.ess");
// ISP statuses
//
enum EsMcuprogNxpIspStatus
{
CMD_SUCCESS = 0;
INVALID_COMMAND = 1, "Invalid command";
SRC_ADDR_ERROR = 2, "SRC_ADDR_ERROR: Source address is not on word boundary";
DST_ADDR_ERROR = 3, "DST_ADDR_ERROR: Destination address is not on a correct boundary";
SRC_ADDR_NOT_MAPPED = 4,
"SRC_ADDR_NOT_MAPPED: Source address is not mapped in the memory map; count value is taken into consideration where applicable";
DST_ADDR_NOT_MAPPED = 5,
"DST_ADDR_NOT_MAPPED: Destination address is not mapped in the memory map; count value is taken into consideration where applicable";
COUNT_ERROR = 6,
"COUNT_ERROR: Byte count is not multiple of 4 or is not a permitted value";
INVALID_SECTOR = 7,
"INVALID_SECTOR: sector number is invalid or end sector number is greater than start sector number";
SECTOR_NOT_BLANK = 8;
SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION = 9,
"SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION: Command to prepare sector for write operation was not executed";
COMPARE_ERROR = 10, "COMPARE_ERROR: Source and destination data not equal";
BUSY = 11, "BUSY: Flash programming hardware interface is busy";
PARAM_ERROR = 12, "PARAM_ERROR: Insufficient number of parameters or invalid parameter";
ADDR_ERROR = 13, "ADDR_ERROR: Address is not on word boundary";
ADDR_NOT_MAPPED = 14, "ADDR_NOT_MAPPED: Address is not mapped in the memory map; count value is taken in to consideration where applicable";
CMD_LOCKED = 15;
INVALID_CODE = 16, "INVALID_CODE: Unlock code is invalid";
INVALID_BAUD_RATE = 17, "INVALID_BAUD_RATE: Invalid baud rate setting";
INVALID_STOP_BIT = 18, "INVALID_STOP_BIT: Invalid stop bit setting";
CRP_ENABLED = 19, "Code read protection enabled";
}
const c_4_45_block = 180;
const c_responseLenMax = 128;
// LPC firmware flags
enum EsMcuprogNxpFirmwareFlag
{
WIPE = 0x01, "Wipe device before programming";
VERIFY = 0x02, "Verify data written to the device during programming";
DONT_SYNC = 0x04, "Do not synchronize with bootloader";
DETECT_ONLY = 0x08, "Just detect device ID, do not program flash";
NO_ECHO = 0x10, "Disable ISP echo responses during programming";
}
const c_uuencodeTable = B" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
// LPC programmer object
object EsMcuprogNxp
{
var m_regex,
m_flags,
m_noEcho,
m_syncRetries,
m_bootloaderVer,
m_mcuInfoIdx,
m_moniker,
m_fw;
function mcuIdxCheck()
{
if( 0 == m_mcuInfoIdx )
throw "MCU is not identified";
}
function binaryCheck()
{
if( m_fw#isEmpty() || m_fw#countGet() < c_4_45_block )
throw "Firmware image is too small";
}
function ramStartGet()
{
mcuIdxCheck();
return c_mcuprogNxpRamInfoData[
c_mcuprogNxpMcuInfoData[m_mcuInfoIdx][EsMcuprogNxpDeviceInfo$$chipVariant]
][0];
}
function ramBaseGet()
{
mcuIdxCheck();
return c_mcuprogNxpRamInfoData[
c_mcuprogNxpMcuInfoData[m_mcuInfoIdx][EsMcuprogNxpDeviceInfo$$chipVariant]
][1];
}
function ispErrorStringGet(errorCode)
{
if( errorCode in EsMcuprogNxpIspStatus )
return EsMcuprogNxpIspStatus.valueLabelGet(errorCode);
else
return EsStr::format("Unknown error: %d", errorCode);
}
function requestStringSend(chnl, requestString)
var sent, toSend, request = EsStr::toByteString(requestString);
{
toSend = request#countGet();
sent = chnl.bytesPut(request, chnl.txTimeEstimateGet(toSend));
// EsScriptDebug::log("requestStringSend sent %d, from: '%s'", sent, requestString);
if( sent != toSend )
throw EsStr::format("Could not send bootloader command '%s'; to send %d, sent %d bytes",
requestString, toSend, sent);
return sent;
}
function dataReceive(chnl, maxDataLen, wantedNrs, timeOutMilliseconds)
var idx, data, dataLen = 0, tmpData, tmpDataLen,
tm = 0, nr = 0, prev = 0, b;
{
do
{
tmpData = chnl.bytesGet(maxDataLen-dataLen, 1);
if( !tmpData#isEmpty() )
{
if( data#isEmpty() )
data = tmpData;
else
data += tmpData;
tmpDataLen = tmpData#countGet();
for(idx = dataLen; idx < dataLen+tmpDataLen; ++idx)
{
b = data[idx];
if( 0x0A == b && prev != b ) // exclude duplicate newlines
++nr;
else if( b & 0x80 )
idx = dataLen+tmpDataLen; // eof; do break for loop, but execute prev = b before it
prev = b;
}
dataLen += tmpDataLen;
}
++tm;
} while( dataLen < maxDataLen &&
tm < timeOutMilliseconds &&
nr < wantedNrs );
// EsScriptDebug::log("dataReceive(chnl, maxDataLen: %d, wantedNrs: %d, timeOutMilliseconds: %d) '%s'", maxDataLen, wantedNrs, timeOutMilliseconds, data);
return data;
}
function requestSendCmdResponseGetAndVerify(chnl, requestString, errFmt)
var response, wantedNrs, errId;
{
if( requestStringSend(chnl, requestString + "\n") )
{
if( m_noEcho )
{
m_regex$pattern = "([0-9]+)\r\n";
wantedNrs = 1;
}
else
{
m_regex$pattern = requestString + "(?:\r|\n)\n?([0-9]+)\r\n";
wantedNrs = 2;
}
response = dataReceive(chnl, c_responseLenMax, wantedNrs, 5000);
m_regex$text = EsStr::fromByteString(response);
// EsScriptDebug::log("cmd_request '%s'; cmd_response '%s'; pattern: '%s'", requestString, m_regex$text, m_regex$pattern);
if( !m_regex$matches )
throw EsStr::format(errFmt, "Wrong answer");
errId = m_regex.matchGet(1)#asULong();
if( EsMcuprogNxpIspStatus$$CMD_SUCCESS != errId )
throw EsStr::format(errFmt, ispErrorStringGet(errId));
}
return response;
}
function binaryIvtChecksumPrepare()
var idx, checksum = 0, checksumOffs, progressTask;
{
binaryCheck();
progressTask = new EsProgressMonitorTask("binaryIvtChecksumPrepare");
progressTask.pulse("Preparing IVT checksum...");
progressTask.attachTo(__scriptHost$progressMonitor);
if( EsMcuprogNxpChipVariant$$LPC2XXX == c_mcuprogNxpMcuInfoData[m_mcuInfoIdx][EsMcuprogNxpDeviceInfo$$chipVariant] )
checksumOffs = 0x14;
else
checksumOffs = 0x1C;
// Patch checksumOffs, otherwise it is not running and jumps to boot mode
// Clear the vector at checksumOffs so it doesn't affect the checksum:
for(idx = 0; idx < 4; ++idx)
m_fw[idx + checksumOffs] = 0;
// Calculate a native checksum of the little endian vector table:
for(idx = 0; idx < 32;)
{
checksum += m_fw[idx++];
checksum += m_fw[idx++] << 8;
checksum += m_fw[idx++] << 16;
checksum += m_fw[idx++] << 24;
}
// Negate the result and place in the vector at checksumOffs as little endian
// again. The resulting vector table should checksum to 0
checksum = ~checksum + 1;
checksum = checksum#asULong();
for(idx = 0; idx < 4; ++idx)
{
m_fw[idx + checksumOffs] = checksum & 0xFF;
checksum >>= 8;
}
}
function responseStringExtract(response)
var lastQuestionpos;
{
lastQuestionpos = response#findLastOf('?');
if( lastQuestionpos#isEmpty() )
lastQuestionpos = 0;
else
++lastQuestionpos;
return response#sliceGet(lastQuestionpos, response#countGet());
}
function syncWithBootloader(chnl, syncRetries, doThrow)
var requestStr, response, wdSeconds, chars, ticks, tries, synched = false, progressTask;
{
if( EsMcuprogNxpFirmwareFlag$$DONT_SYNC == (m_flags & EsMcuprogNxpFirmwareFlag$$DONT_SYNC) )
return true;
progressTask = new EsProgressMonitorTask("syncWithBootloader");
progressTask.pulse("Syncing with device bootloader...");
progressTask.attachTo(__scriptHost$progressMonitor);
for( tries = 0; tries < syncRetries; ++tries )
{
requestStringSend(chnl, "?");
response = dataReceive(chnl, c_responseLenMax, 1, 100);
response = responseStringExtract(response);
// EsScriptDebug::log("responseStringExtract '%s'", response);
if( !response#isEmpty() )
{
if( B"Bootloader\r\n" == response )
{
chars = (17 * m_fw#countGet() + 1) / 10;
wdSeconds = (10 * chars + 5) / chnl$baud + 10;
ticks = wdSeconds * ((m_moniker.m_xtalHz + 15) / 16);
requestStr = "T " + ticks;
requestStringSend(chnl, requestStr+"\n");
response = dataReceive(chnl, c_responseLenMax, 1, 100);
if( B"OK\r\n" != response )
{
if( doThrow )
throw "No answer on 'WDT Set (" + requestStr + ")' command";
else
break;
}
requestStringSend(chnl, "G 10356\r\n");
EsThreadWorker::sleep(200);
tries = 0;
}
else if( B"Synchronized\r\n" == response )
{
requestStr = "Synchronized";
synched = true;
break;
}
}
}
if( !synched )
{
if( doThrow )
throw "No answer on QM ('?')";
}
else
{
requestStringSend(chnl, requestStr+"\n");
response = dataReceive(chnl, c_responseLenMax, 2, 1000);
m_regex$pattern = requestStr + "(?:\r|\n)\n?OK\r\n";
m_regex$text = EsStr::fromByteString(response);
if( !m_regex$matches )
{
synched = false;
if( doThrow )
throw "No answer on 'Synchronized'";
}
}
return synched;
}
function checkIfInBoot(chnl)
{
return syncWithBootloader(chnl, 20, false);
}
function oscCommandSend(chnl)
var requestStr, response;
{
requestStr = m_moniker.m_xtalHz/1000;
requestStringSend(chnl, requestStr+"\n");
response = dataReceive(chnl, c_responseLenMax, 2, 1000);
m_regex$pattern = requestStr+"(?:\r|\n)\n?OK\r\n";
m_regex$text = EsStr::fromByteString(response);
if( !m_regex$matches )
throw "No answer on 'Oscillator' command";
}
function flashUnlock(chnl)
{
requestSendCmdResponseGetAndVerify(chnl, "U 23130", "Error executing 'Unlock' command: '%s'");
}
function bootcodeVerRead(chnl)
var requestStr = "K", response;
{
requestStringSend(chnl, requestStr + "\n");
response = dataReceive(chnl, c_responseLenMax, 4, 5000);
m_regex$pattern = requestStr+"(?:\r|\n)\n?0\r\n([0-9]+)\r\n([0-9]+)";
m_regex$text = EsStr::fromByteString(response);
if( !m_regex$matches )
throw "Wrong answer on Bootcode version read command";
m_bootloaderVer = m_regex.matchGet(1) + "." + m_regex.matchGet(2);
}
function partIdRead(chnl)
var requestStr = "J", response,
responseStr, id, info, infoIdx = 0, found = false;
{
requestStringSend(chnl, requestStr+"\n");
response = dataReceive(chnl, c_responseLenMax, 3, 5000);
responseStr = EsStr::fromByteString(response);
m_regex$pattern = requestStr+"(?:\r|\n)\n?0\r\n([0-9]+)";
m_regex$text = responseStr;
if( !m_regex$matches )
throw "No answer on Part ID read command";
id = m_regex.matchGet(1)#asULong();
foreach(info in c_mcuprogNxpMcuInfoData)
{
if( id == info[EsMcuprogNxpDeviceInfo$$id] )
{
m_mcuInfoIdx = infoIdx;
found = true;
break;
}
++infoIdx;
}
if( !found )
throw EsStr::format("Unknown device ID: '0x%08X'", id);
}
function flashPrepare(chnl, start, end)
var cmd = EsStr::format("P %d %d", start, end);
{
requestSendCmdResponseGetAndVerify(chnl,
cmd, "Error executing 'Flash Prepare (" + cmd + ")' command: '%s'");
}
function flashErase(chnl, start, end)
var cmd = EsStr::format("E %d %d", start, end);
{
requestSendCmdResponseGetAndVerify(chnl,
cmd, "Error executing 'Flash Erase (" + cmd + ")' command: '%s'");
}
function flashPrepareAndErase(chnl)
var endSector = 0;
{
if( m_flags & EsMcuprogNxpFirmwareFlag$$WIPE )
{
mcuIdxCheck();
endSector = c_mcuprogNxpMcuInfoData[m_mcuInfoIdx][EsMcuprogNxpDeviceInfo$$flashSectors]-1;
}
flashPrepare(chnl, 0, endSector);
flashErase(chnl, 0, endSector);
}
function flashBlockWriteRequest(chnl, blockLen)
var cmd = EsStr::format("W %d %d", ramBaseGet(), blockLen);
{
requestSendCmdResponseGetAndVerify(chnl,
cmd, "Error executing 'Write (" + cmd + ")' command: '%s'");
}
function blockWrite(chnl, pos, block, blockCrc)
var b, bCnt = 0, bb = B"\0\0\0", tmpPos, blockOffs,
response, responseStr, uue24 = 0,
requestStr = "M"; // uuencoded block length (45 + ' '(\32) = 77 = M)
{
// Uuencode one 45 byte block
for(blockOffs = 0; blockOffs < 45; ++blockOffs)
{
if( m_moniker$binaryOffs < ramStartGet() )
{ // Flash: use full memory
tmpPos = pos + block * 45 + blockOffs;
}
else
{ // RAM: Skip first 0x200 bytes, these are used by the download program in LPC21xx
tmpPos = pos + block * 45 + blockOffs + 0x200;
}
// check for proper mem access restrictions!!!
if( tmpPos < m_fw#countGet() )
b = m_fw[tmpPos];
else
b = 0;
bb[bCnt++] = b;
blockCrc += b#asULong();
if( bCnt > 2 )
{
bCnt = 0;
uue24 = (bb[0]#asULong() << 16) + (bb[1]#asULong() << 8) + bb[2]#asULong();
requestStr += c_uuencodeTable[(uue24 >> 18) & 63]#asChar();
requestStr += c_uuencodeTable[(uue24 >> 12) & 63]#asChar();
requestStr += c_uuencodeTable[(uue24 >> 6) & 63]#asChar();
requestStr += c_uuencodeTable[ uue24 & 63]#asChar();
}
}
requestStr += "\n";
//EsScriptDebug::log("blockWrite '%s'", requestStr);
requestStringSend(chnl, requestStr);
if( !m_noEcho )
{
response = dataReceive(chnl, c_responseLenMax, 1, 5000);
responseStr = EsStr::fromByteString(response);
if( responseStr != requestStr )
throw "Error writing data at " + tmpPos + ", sent '" + requestStr#asEscapedString() + "', got '" + responseStr#asEscapedString() + "'";
}
return blockCrc;
}
function blockCrcWrite(chnl, crc)
var requestStr, expectedResponse, response, expectedNrs;
{
requestStr = EsStr::format("%d\r\n", crc);
if( m_noEcho )
{
expectedNrs = 1;
expectedResponse = B"OK\r\n";
}
else
{
expectedNrs = 2;
expectedResponse = EsStr::toByteString(requestStr) + B"OK\r\n";
}
//EsScriptDebug::log("blockCrcWrite_request '%s'", requestStr);
requestStringSend(chnl, requestStr);
response = dataReceive(chnl, c_responseLenMax, expectedNrs, 5000);
//EsScriptDebug::log("blockCrcWrite_response '%s'", EsStr::fromByteString(response));
if( response != expectedResponse )
throw "Error writing block CRC";
}
function copyCommandSend(chnl, pos, copyLen)
var cmd = EsStr::format("C %d %d %d", pos, ramBaseGet(), copyLen);
{
requestSendCmdResponseGetAndVerify(chnl,
cmd, "Error executing 'Copy (" + cmd + ")' command: '%s'");
}
function verifyCommandSend(chnl, sectorStart, sectorOffs, copyLen)
var requestStr;
{
//Avoid comparing first 64 bytes.
//Because first 64 bytes are re-mapped to flash boot sector,
//and the compare result may not be correct.
if( sectorStart + sectorOffs < 64 )
requestStr = EsStr::format("M %d %d %d", 64, ramBaseGet()+(64 - sectorStart - sectorOffs), copyLen-(64 - sectorStart - sectorOffs));
else
requestStr = EsStr::format("M %d %d %d", sectorStart + sectorOffs, ramBaseGet(), copyLen);
requestSendCmdResponseGetAndVerify(chnl, requestStr, "Error executing 'Compare (" + requestStr + ")' command: '%s'");
}
function copyLenAlign(copyLen, sectorLen, destInfo)
{
// Round copyLen up to one of the following values: 512, 1024,
// 4096, 8192; but do not exceed the maximum copy size (usually
// 8192, but chip-dependent)
if(copyLen < 512)
copyLen = 512;
else if(sectorLen < 1024)
copyLen = 1024;
else if(sectorLen < 4096)
copyLen = 4096;
else
copyLen = 8192;
if(copyLen > destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize])
copyLen = destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize];
return copyLen;
}
function echoDisable(chnl)
{
requestSendCmdResponseGetAndVerify(chnl, "A 0", "Error executing 'Echo OFF' command: '%s'");
m_noEcho = true;
}
function program(chnl, isAlreadySynched)
var destInfo, sectorTable,
sector, sectorLen, sectorStart, sectorOffs, sectorChunk,
copyLen, blockCrc, pos, line, block, copyResidual, fwSize,
progressTask;
{
// copy firmware binary buffer
m_fw = m_moniker$binaryFw;
fwSize = m_fw#countGet();
binaryCheck();
// EsScriptDebug::log("Programming, already synched: %s, don't sync flag: %s", isAlreadySynched, (m_flags & EsMcuprogNxpFirmwareFlag$$DONT_SYNC));
if( !isAlreadySynched )
syncWithBootloader(chnl, m_syncRetries, true);
oscCommandSend(chnl);
flashUnlock(chnl);
bootcodeVerRead(chnl);
partIdRead(chnl);
if( m_flags & EsMcuprogNxpFirmwareFlag$$NO_ECHO )
echoDisable(chnl);
if( !(m_flags & EsMcuprogNxpFirmwareFlag$$DETECT_ONLY) )
{
binaryIvtChecksumPrepare();
destInfo = c_mcuprogNxpMcuInfoData[m_mcuInfoIdx];
sectorTable = c_mcuprogNxpSectorTableData[destInfo[EsMcuprogNxpDeviceInfo$$sectorTable]];
// In case of a download to RAM, use full RAM for downloading
// set the flash parameters to full RAM also.
// This makes sure that all code is downloaded as one big sector
if( m_moniker$binaryOffs >= ramStartGet() )
{
destInfo[EsMcuprogNxpDeviceInfo$$flashSectors] = 1;
destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize] = destInfo[EsMcuprogNxpDeviceInfo$$ramSize]*1024 - (ramBaseGet() - ramStartGet());
sectorTable = [];
sectorTable += destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize];
}
// Start with sector 1 and go upward... sector 0 containing the interrupt vectors
// will be loaded last, since it contains a checksum and device will re-enter
// bootloader mode as long as this checksum is invalid.
if( sectorTable[0] >= fwSize )
{
sector = 0;
sectorStart = 0;
}
else
{
sectorStart = sectorTable[0];
sector = 1;
}
flashPrepareAndErase(chnl);
progressTask = new EsProgressMonitorTask("program");
progressTask.initialize("Uploading firmware...", fwSize, 0);
progressTask.attachTo(__scriptHost$progressMonitor);
while(1)
{
if(sector >= destInfo[EsMcuprogNxpDeviceInfo$$flashSectors])
throw "Program too large, running out of Flash sectors";
if( m_moniker$binaryOffs < ramStartGet() ) // skip Erase when running from RAM
{
flashPrepare(chnl, sector, sector);
if( EsMcuprogNxpFirmwareFlag$$WIPE != (m_flags & EsMcuprogNxpFirmwareFlag$$WIPE) && 0 != sector ) // already erased
flashErase(chnl, sector, sector);
}
sectorLen = sectorTable[sector];
if(sectorLen > fwSize - sectorStart)
sectorLen = fwSize - sectorStart;
for(sectorOffs = 0; sectorOffs < sectorLen; sectorOffs += sectorChunk)
{
// If the Flash ROM sector size is bigger than the number of bytes
// we can copy from RAM to Flash, we must "chop up" the sector and
// copy these individually.
// This is especially needed in the case where a Flash sector is
// bigger than the amount of SRAM.
sectorChunk = sectorLen - sectorOffs;
if(sectorChunk > destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize])
sectorChunk = destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize];
// Write multiple of 45 * 4 Byte blocks to RAM, but copy maximum of on sector to Flash
// In worst case we transfer up to 180 bytes to RAM
// but then we can always use full 45 byte blocks and length is multiple of 4
copyLen = sectorChunk;
copyResidual = copyLen % c_4_45_block;
if( 0 != copyResidual )
copyLen += c_4_45_block - copyResidual;
flashBlockWriteRequest(chnl, copyLen);
blockCrc = 0;
line = 0;
// Transfer blocks of 45 * 4 bytes to RAM
for(pos = sectorStart + sectorOffs; (pos < sectorStart + sectorOffs + copyLen) && (pos < fwSize); pos += c_4_45_block)
{
for (block = 0; block < 4; ++block) // Each block 45 bytes
{
blockCrc = blockWrite(chnl, pos, block, blockCrc);
++line;
if(20 == line)
{
blockCrcWrite(chnl, blockCrc);
line = 0;
blockCrc = 0;
}
}
progressTask$position = pos;
}
if( line != 0 )
blockCrcWrite(chnl, blockCrc);
if( m_moniker$binaryOffs < ramStartGet() )
{
flashPrepare(chnl, sector, sector);
copyLen = copyLenAlign(copyLen, sectorLen, destInfo);
copyCommandSend(chnl, sectorStart + sectorOffs, copyLen);
if( m_flags & EsMcuprogNxpFirmwareFlag$$VERIFY )
verifyCommandSend(chnl, sectorStart, sectorOffs, copyLen);
}
}
if( (sectorStart + sectorLen) >= fwSize && sector !=0 )
{
sector = 0;
sectorStart = 0;
// finalize programming with sector 0
progressTask.initialize("Finalizing firmware upload...", sectorTable[sector], 0);
}
else if( sector == 0 )
break;
else
{
sectorStart += sectorTable[sector++];
progressTask$position = sectorStart;
}
}
}
}
// default ctor
new(flags, retries, moniker)
{
m_regex = new EsRegEx("", EsRegExCompileFlag$$DEFAULT);
m_flags = flags#asULong();
m_syncRetries = retries#asULong();
m_moniker = moniker;
m_noEcho = false;
}
}