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;
  }
}