Running Dev Diary: Creating a managed .NET Bootloader Utilities library

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
lock attach
Attachments are accessible only for community members.
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

After some growing frustration with using cybootloaderutils C library with .NET applications, I figure since we have the source code why not take a crack at porting the bootloader host library to a proper managed library written in C#?  This would serve a couple purposes:

- Hopefully much easier integration for bootloader hosts written in C#: no longer having to deal with interop services, as well as other issues that can crop up when integrating an unmanaged library into a managed project.

- It seems to be a good bit easier to debug a managed library as opposed to an unmanaged library/unsafe code.

- A someone who really only deals with C# .NET occasionally to throw together a GUI for talking to a microcontroller, doing something to increase my familiarity with .NET and OOP will almost surely be beneficial in general.

- Recently, trying to use the bootloader utils library with a .NET Core project has proven to be more trouble than expected.  Hopefully a rewrite into C# will allow for easier building across multiple .NET flavors and platforms.

I'm creating this thread as a way to keep track of progress of the project, as well as share it with others who will almost certainly have ideas for improvement.  As someone who, as mentioned above, is not overly familiar with the patterns and feature sets of OOP and C#, there will doubtless be places in which the code I port over can be improved.

This will mostly be a spare time project, so I don't have a real timeline on it, but it looks like it shouldn't be too terribly much code to work through.  First bit of progress is coming in the net post.

Current Link to VS2019 project is attached.

[11-11-20]

First Successful Bootload

API

- g_validRows now fills properly with blank data

- fixed a false error being flagged in CyBtldr_StartBootloaderOperation()

- Fixed CyBtldr_CreateGetAppStatusCmd() call inside CyBtldr_GetApplicationStatus()

  loading outBuf instead of inBuf.

- Fixed problem with SendData() and CyBtldr_ProgramRow_v1() creating bad data packets

  - Removed Array.Copy() that was messing up data packet creation in both methods

  - buf.Skip() was being given incorrect argument in SendData()

API2

- Fixed RunAction_v0() from flagging an error and aborting the load if the

  target bootloader is single-application.  Error status is now overridden to

  CYRET_SUCCESS if CyBtldr_GetApplicationStatus() returns an error, but the

  bootloader response is valid.

COMMAND

- Fixed fillData32() not properly loading the LSB (word was not being shifted)

- CyBtldr_CreateEnterBootloaderCmd() had a length zero check that would fail if securityKeyBuff is null.  Replaced with only a null check.

- Replaced cmdBuf.SetValue() call with fillData16() in CyBtldr_CreateEnterBootloaderCmd_v1().

- CyBtldr_CreateSendDataCmd() was assembling commands at the wrong index (7).

  This has been corrected to 4, where the command data should properly begin.

[11-10-20]

- Added WPF test application to VS solution

- Fixed CyBtldr_StartBootloaderOperation() not filling g_validRows properly

- CyBtldr_FromHex() brought closer to original code.  char.GetNumericValue() not working in the same way.

- CyBtldr_ReadLine now uses a StreamReader to properly read single lines

[11-9-20]

- api/api2 is "done" (untested)

- Project renamed to correct typo (was bool_netlib)

- Implemented interface for application to define communication functions

- TODO: re-evaluate all ref keywords for passing arguments.

[11-2-20]

- command.cs porting is "done" (untested)

- renamed namespace to CyBootloaderUtils

- Dropped the CyBtldr_ class name prefix.  "CyBtldr_Parse" class is now just "Parse"

[10-29-20]

- parse.cs first pass porting is done (untested, will probably need work)

- command.cs porting is in progress

Message was edited by: Kyle Trenholm - Adding 11-2-20 updated project

0 Likes
8 Replies
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

I will be using .NET Standard 2.0 for the target.  It will probably also be able to target different .NET standard versions, but I'll be doing this first build with 2.0 and worrying about earlier versions after getting a first version going.

First off, let's start with getting the Host and Bootloader status codes implemented.  These were all #define constants in the original library.  Luckily these codes also required implementing when using the unmanaged library w/ interop.  I can mostly just pull them from an existing bootloader project I have.  They will be used across the bootl_utils namespace,and are placed inside api.cs, which will probably be among the last files that actually gets its functions ported over.

namespace bootl_utils

{

    /// <summary>

    /// These are the different return codes returned by the Bootloader Host.

    /// </summary>

    public enum HostReturnCodes

    {

        /// <summary>

        /// Completed successfully

        /// </summary>

        CYRET_SUCCESS = 0x00,

        /// <summary>

        /// File is not accessable

        /// </summary>

        CYRET_ERR_FILE = 0x01,

        /// <summary>

        /// Reached the end of the file

        /// </summary>

        CYRET_ERR_EOF = 0x02,

        /// <summary>

        /// The amount of data available is outside the expected range

        /// </summary>

        CYRET_ERR_LENGTH = 0x03,

        /// <summary>

        /// The data is not of the proper form

        /// </summary>

        CYRET_ERR_DATA = 0x04,

        /// <summary>

        /// The command is not recognized

        /// </summary>

        CYRET_ERR_CMD = 0x05,

        /// <summary>

        /// The expected device does not match the detected device

        /// </summary>

        CYRET_ERR_DEVICE = 0x06,

        /// <summary>

        /// The bootloader version detected is not supported

        /// </summary>

        CYRET_ERR_VERSION = 0x07,

        /// <summary>

        /// The checksum does not match the expected value

        /// </summary>

        CYRET_ERR_CHECKSUM = 0x08,

        /// <summary>

        /// The flash array is not valid

        /// </summary>

        CYRET_ERR_ARRAY = 0x09,

        /// <summary>

        /// The flash row is not valid

        /// </summary>

        CYRET_ERR_ROW = 0x0A,

        /// <summary>

        /// The bootloader is not ready to process data

        /// </summary>

        CYRET_ERR_BTLDR = 0x0B,

        /// <summary>

        /// The application is currently marked as active

        /// </summary>

        CYRET_ERR_ACTIVE = 0x0C,

        /// <summary>

        /// An unknown error occured

        /// </summary>

        CYRET_ERR_UNKNOWN = 0x0F,

        /// <summary>

        /// The operation was aborted

        /// </summary>

        CYRET_ABORT = 0xFF,

        /// <summary>

        /// The communications object reported an error

        /// </summary>

        CYRET_ERR_COMM_MASK = 0x2000,

        /// <summary>

        /// The bootloader reported an error

        /// </summary>

        CYRET_ERR_BTLDR_MASK = 0x4000,

    }

    /// <summary>

    /// These are the different return codes returned from the bootloader

    /// </summary>

    public enum BootloaderStatusCodes

    {

        /// <summary>

        /// Completed successfully

        /// </summary>

        CYBTLDR_STAT_SUCCESS = 0x00,

        /// <summary>

        /// The provided key does not match the expected value

        /// </summary>

        CYBTLDR_STAT_ERR_KEY = 0x01,

        /// <summary>

        /// The verification of flash failed

        /// </summary>

        CYBTLDR_STAT_ERR_VERIFY = 0x02,

        /// <summary>

        /// The amount of data available is outside the expected range

        /// </summary>

        CYBTLDR_STAT_ERR_LENGTH = 0x03,

        /// <summary>

        /// The data is not of the proper form

        /// </summary>

        CYBTLDR_STAT_ERR_DATA = 0x04,

        /// <summary>

        /// The command is not recognized

        /// </summary>

        CYBTLDR_STAT_ERR_CMD = 0x05,

        /// <summary>

        /// The expected device does not match the detected device

        /// </summary>

        CYBTLDR_STAT_ERR_DEVICE = 0x06,

        /// <summary>

        /// The bootloader version detected is not supported

        /// </summary>

        CYBTLDR_STAT_ERR_CHECKSUM =  0x08,

        /// <summary>

        /// The flash array is not valid

        /// </summary>

        CYBTLDR_STAT_ERR_ARRAY = 0x09,

        /// <summary>

        /// The flash row is not valid

        /// </summary>

        CYBTLDR_STAT_ERR_ROW = 0x0A,

        /// <summary>

        /// The flash row is protected and cannot be programmed

        /// </summary>

        CYBTLDR_STAT_ERR_PROTECT = 0x0B,

        /// <summary>

        /// The application is not valid and cannot be set as active

        /// </summary>

        CYBTLDR_STAT_ERR_APP = 0x0C,

        /// <summary>

        /// The application is currently marked as active

        /// </summary>

        CYBTLDR_STAT_ERR_ACTIVE = 0x0D,

        /// <summary>

        /// An unknown error occurred

        /// </summary>

        CYBTLDR_STAT_ERR_UNK = 0x0F

    }

    public class Bootloader_Utils{

        /*Nothing here yet*/

    }

}

Next will be beginning the process of porting functions...

0 Likes

Parse.cs

Next up is finding a good place to start porting functions.  I went with Parse.cs since it seems to be a suitably low level place to start working outward (it shouldn't have any dependencies).

I figure each source code and header combination can be its own .cs file and static class (I don't think I'd ever need to instantiate it?)

namespace bootl_utils

{

        FileStream dataFile;    //FILE does not exist in C#, so let's use a FileStream instead

        const ushort maxBufferSize = 768;  //Previously a #define constant

    public static class CyBtldr_Parse

    {

        /*Ported functions will go here*/

    }

}

Functions:

We'll go right down the list of functions that need porting.

First up are some functions to convert byte arrays into 16 and 32-bit words.  These mostly only needed changes to the types.

uint8_t => byte

uint16_t => ushort

uint32_t => uint

The only curveball being the change of the uint8_t * over to byte[] and adjusting the code so we are no longer doing pointer math.

Luckily, the Skip() member of the byte[] class will return the array at the specified position, so that should give us what we need:

parse2ByteValueBigEndian()

C:

static uint16_t parse2ByteValueBigEndian(uint8_t* buf)

{

    return ((uint16_t)(buf[0] << 8)) | ((uint16_t)buf[1]);

}

C#:

private static ushort parse2ByteValueBigEndian(byte[] buf)

{

    return (ushort)(((ushort)(buf[0] << 8)) | ((ushort)buf[1]));

}

parse4ByteValueBigEndian()

C:

static uint32_t parse4ByteValueBigEndian(uint8_t* buf)

{

    return (((uint32_t)parse2ByteValueBigEndian(buf)) << 16) | ((uint32_t)parse2ByteValueBigEndian(buf + 2));

}

C#:

private static uint parse4ByteValueBigEndian(byte[] buf)

{

    return (((uint)parse2ByteValueBigEndian(buf)) << 16) | ((uint)parse2ByteValueBigEndian(buf.Skip(2).ToArray()));

}

parse2ByteValueLittleEndian()

C:

static uint16_t parse2ByteValueLittleEndian(uint8_t* buf)

{

    return ((uint16_t)buf[0]) | (((uint16_t)buf[1]) << 8);

}

C#:

private static ushort parse2ByteValueLittleEndian(byte[] buf)

{

    return (ushort)(((ushort)buf[0]) | (((ushort)buf[1]) << 8));

}

parse4ByteValueLittleEndian()

C:

static uint32_t parse4ByteValueLittleEndian(uint8_t* buf)

{

    return ((uint32_t)parse2ByteValueLittleEndian(buf)) | (((uint32_t)parse2ByteValueLittleEndian(buf + 2)) << 16);

}

C#:

private static uint parse4ByteValueLittleEndian(byte[] buf)

{

    return (uint)((parse2ByteValueLittleEndian(buf) | (parse2ByteValueLittleEndian(buf.Skip(2).ToArray()) << 16)));

}

stringEqualIgnoreCase()

Lucky for us, C# has an actual string type, so this is a nice simple conversion without having to step through the string.

Also changed the return type to bool since this function appears to be called only as part of an if() statement.

C:

static int stringEqualIgnoreCase(const char* str1, const char* str2)

{

    while (((*str1) != 0 && (*str2) != 0))

    {

        if (tolower(*str1) != tolower(*str2))

        {

            return 0;

        }

        str1++;

        str2++;

    }

    return (tolower(*str1) == tolower(*str2));

}

C#:

private static bool stringEqualIgnoreCase(string str1, string str2)

{

    if (str1.ToLower() != str2.ToLower())

    {

        return false;

    }

    else

    {

        return true;

    }

}

CyBtldr_FromHex()

Looks like another case of "C# has a function to just go ahead and do the work natively"

C:

uint8_t CyBtldr_FromHex(char value)

{

    if ('0' <= value && value <= '9')

        return (uint8_t)(value - '0');

    if ('a' <= value && value <= 'f')

        return (uint8_t)(10 + value - 'a');

    if ('A' <= value && value <= 'F')

        return (uint8_t)(10 + value - 'A');

    return 0;

}

C#:

static byte CyBtldr_FromHex(char value)

{

    /*We have a function in C# that does this, so let's use it*/

    double numval = char.GetNumericValue(value);

    if (numval != -1)

    {

        return (byte)numval;

    }

    else

    {

        return 0;

    }

}

CyBtldr_FromAscii()

Something that becomes apparent here is that all the C functions return an int, which in the C code correspond to the Host Status Codes.

I decided to have the C# functions return the status enum type directly rather than having to do a bunch of explicit casting.

Another thing that starts to come up here is of some passed in pointers being for arrays, and others being a pass by reference.  In C#, we can denote this with the ref keyword.  In this function, the rowSize and rowData are passed in by reference and modified, so we have to account for it in our C# version.  Also worth noting that you must use the ref keyword for the argument when calling the function as well.

C:

int CyBtldr_FromAscii(uint32_t bufSize, uint8_t* buffer, uint16_t* rowSize, uint8_t* rowData)

{

    uint16_t i;

    int err = CYRET_SUCCESS;

    if (bufSize & 1) // Make sure even number of bytes

        err = CYRET_ERR_LENGTH;

    else

    {

        for (i = 0; i < bufSize / 2; i++)

        {

            rowData = (CyBtldr_FromHex(buffer[i * 2]) << 4) | CyBtldr_FromHex(buffer[i * 2 + 1]);

        }

        *rowSize = i;

    }

    return err;

}

C#:

static HostReturnCodes CyBtldr_FromAscii(uint bufSize, byte[] buffer, ref ushort rowSize, ref byte[] rowData)

{

    ushort i;

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    if (bufSize % 2 != 0)

    {

        err = HostReturnCodes.CYRET_ERR_LENGTH;

    }

    else

    {

        for (i = 0; i < bufSize/2; i++)

        {

            rowData = ((byte)(CyBtldr_FromHex((char)buffer[i * 2]) << 4 | CyBtldr_FromHex((char)buffer[i * 2 + 1])));

        }

        rowSize = i;

    }

    return err;

}

CyBtldr_ReadLine()

We start to see some more changes here since we don't have feof() or fgets().  Pretty sure FileStream.Read() should give us the functionality of both functions, since it reads the file, and the return status tells us if we hit EoF.

C:

int CyBtldr_ReadLine(uint32_t* size, char* buffer)

{

    int err = CYRET_SUCCESS;

    uint32_t len;

    // line that start with '#' are assumed to be comments, continue reading if we read a comment

    do

    {

        len = 0;

        if (NULL != dataFile && !feof(dataFile))

        {

            if (NULL != fgets(buffer, MAX_BUFFER_SIZE * 2, dataFile))

            {

                len = strlen(buffer);

                while (len > 0 && ('\n' == buffer[len - 1] || '\r' == buffer[len - 1]))

                    --len;

            }

            else

                err = CYRET_ERR_EOF;

        }

        else

            err = CYRET_ERR_FILE;

    } while (err == CYRET_SUCCESS && buffer[0] == '#');

    *size = len;

    return err;

}

C#:

static HostReturnCodes CyBtldr_ReadLine(ref uint size, ref byte[] buffer)

{

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    uint len;

    do

    {

        len = 0;

        if (dataFile != null)      

        {

            if (dataFile.Read(buffer, 0, maxBufferSize * 2) != 0)  //EOF check, in C, this was a single if() above

            {

                len = (uint)buffer.Length;

                while (len > 0 && ('\n' == buffer[len - 1] || '\r' == buffer[len - 1]))

                {

                    --len;

                }

            }

            else

            {

                err = HostReturnCodes.CYRET_ERR_EOF;

            }

        }

        else

        {

            err = HostReturnCodes.CYRET_ERR_FILE;

        }

    } while (err == HostReturnCodes.CYRET_SUCCESS && buffer[0] == '#');

    size = len;

    return err;

}

CyBtldr_OpenDataFile()

Should be a simple replacement of fopen() with File.Open() and catching if there's an error.

C:

int CyBtldr_OpenDataFile(const char* file)

{

    dataFile = fopen(file, "r");

    return (NULL == dataFile)

        ? CYRET_ERR_FILE

        : CYRET_SUCCESS;

}

C#:

static HostReturnCodes CyBtldr_OpenDataFile(string file)

{

    try

    {

        dataFile = File.Open(file, FileMode.Open);

    } catch (Exception)

    {

        return HostReturnCodes.CYRET_ERR_FILE;

    }

    return HostReturnCodes.CYRET_SUCCESS;

   

}

CyBtldr_ParseCyacdFileVersion()

C:

int CyBtldr_ParseCyacdFileVersion(const char* fileName, uint32_t bufSize, uint8_t* header, uint8_t* version)

{

    // check file extension of the file, if extension is cyacd, version 0

    int index = strlen(fileName);

    int err = CYRET_SUCCESS;

    if (bufSize == 0)

    {

        err = CYRET_ERR_FILE;

    }

    while (CYRET_SUCCESS == err && fileName[--index] != '.')

    {

        if (index == 0)

            err = CYRET_ERR_FILE;

    }

    if (stringEqualIgnoreCase(fileName + index, ".cyacd2"))

    {

        if (bufSize < 2)

            err = CYRET_ERR_FILE;

        // .cyacd2 file stores version information in the first byte of the file header.

        if (CYRET_SUCCESS == err)

        {

            *version = CyBtldr_FromHex(header[0]) << 4 | CyBtldr_FromHex(header[1]);

            if (*version == 0)

                err = CYRET_ERR_DATA;

        }

    }

    else if (stringEqualIgnoreCase(fileName + index, ".cyacd"))

    {

        // .cyacd file does not contain version information

        *version = 0;

    }

    else

    {

        err = CYRET_ERR_FILE;

    }

    return err;

}

C#:

static HostReturnCodes CyBtldr_ParseCyacdFileVersion(string fileName, int bufSize, byte[] header, ref byte version)

{

    // check file extension of the file, if extension is cyacd, version 0

    int index = fileName.Length;

    char[] char_filename = fileName.ToCharArray();

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    if (bufSize == 0)

    {

        err = HostReturnCodes.CYRET_ERR_FILE;

    }

    /*TODO: we can probably refactor this to find a file extension substring rather than searching for the '.' character*/

    while (err == HostReturnCodes.CYRET_SUCCESS && char_filename[--index] != '.')

    {

        if (index == 0)

        {

            /*No file extension found*/

            err = HostReturnCodes.CYRET_ERR_FILE;

        }

    }

    if(stringEqualIgnoreCase(fileName.Substring(index), "cyacd2"))

    {

        if (bufSize < 2)

        {

            err = HostReturnCodes.CYRET_ERR_FILE;

        }

        if (err == HostReturnCodes.CYRET_SUCCESS)

        {

            version = (byte)(CyBtldr_FromHex((char)header[0]) << 4 | CyBtldr_FromHex((char)header[1]));

            if (version == 0)

            {

                err = HostReturnCodes.CYRET_ERR_DATA;

            }

        }

    }

    else if (stringEqualIgnoreCase(fileName.Substring(index), ".cyacd"))

    {

        /*.cyacd file does not contain version information*/

        version = 0;

    }

    else

    {

        err = HostReturnCodes.CYRET_ERR_FILE;

    }

    return err;

}

CyBtldr_ParseHeader()

C:

int CyBtldr_ParseHeader(uint32_t bufSize, uint8_t* buffer, uint32_t* siliconId, uint8_t* siliconRev, uint8_t* chksum)

{

    int err = CYRET_SUCCESS;

    uint16_t rowSize = 0;

    uint8_t rowData[MAX_BUFFER_SIZE];

    if (CYRET_SUCCESS == err)

    {

        err = CyBtldr_FromAscii(bufSize, buffer, &rowSize, rowData);

    }

    if (CYRET_SUCCESS == err)

    {

        if (rowSize > 5)

            *chksum = rowData[5];

        else

            *chksum = 0;

        if (rowSize > 4)

        {

            *siliconId = parse4ByteValueBigEndian(rowData);

            *siliconRev = rowData[4];

        }

        else

            err = CYRET_ERR_LENGTH;

    }

    return err;

}

C#:

static HostReturnCodes CyBtldr_ParseHeader(uint bufSize, byte[] buffer, ref uint siliconId, ref byte siliconRev, ref byte chksum)

{

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    ushort rowSize = 0;

    byte[] rowData = new byte[maxBufferSize];

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

        err = CyBtldr_FromAscii(bufSize, buffer, ref rowSize, ref rowData);

    }

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

        if (rowSize > 5)

        {

            chksum = rowData[5];

        }

        else

        {

            chksum = 0;

        }

        if (rowSize > 4)

        {

            siliconId = parse4ByteValueBigEndian(rowData);

            siliconRev = rowData[4];

        }

        else

        {

            err = HostReturnCodes.CYRET_ERR_LENGTH;

        }

    }

    return err;

}

CyBtldr_ParseHeader_v1()

C:

int CyBtldr_ParseHeader_v1(uint32_t bufSize, uint8_t* buffer, uint32_t* siliconId,

    uint8_t* siliconRev, uint8_t* chksum, uint8_t* appID, uint32_t* productID)

{

    int err = CYRET_SUCCESS;

    uint16_t rowSize = 0;

    uint8_t rowData[MAX_BUFFER_SIZE];

    if (CYRET_SUCCESS == err)

    {

        err = CyBtldr_FromAscii(bufSize, buffer, &rowSize, rowData);

    }

    if (CYRET_SUCCESS == err)

    {

        if (rowSize == 12)

        {

            *siliconId = parse4ByteValueLittleEndian(rowData + 1);

            *siliconRev = rowData[5];

            *chksum = rowData[6];

            *appID = rowData[7];

            *productID = parse4ByteValueLittleEndian(rowData + 8);

        }

        else

        {

            err = CYRET_ERR_LENGTH;

        }

    }

    return err;

}

C#:

static HostReturnCodes CyBtldr_ParseHeader_v1(uint bufSize, byte[] buffer, ref uint siliconId, ref byte siliconRev, ref byte chksum,

                                       ref byte appID, ref uint productID)

{

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    ushort rowSize = 0;

    byte[] rowData = new byte[maxBufferSize];

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

        err = CyBtldr_FromAscii(bufSize, buffer, ref rowSize, ref rowData);

    }

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

        if (rowSize == 12)

        {

            siliconId = parse4ByteValueLittleEndian(rowData.Skip(1).ToArray());

            siliconRev = rowData[5];

            chksum = rowData[6];

            appID = rowData[7];

            productID = parse4ByteValueLittleEndian(rowData.Skip(8).ToArray());

        }

        else

        {

            err = HostReturnCodes.CYRET_ERR_LENGTH;

        }

    }

    return err;

}

CyBtldr_ParseRowData()

C:

int CyBtldr_ParseRowData(uint32_t bufSize, uint8_t* buffer, uint8_t* arrayId, uint16_t* rowNum, uint8_t* rowData, uint16_t* size, uint8_t* checksum)

{

    const uint16_t MIN_SIZE = 6; //1-array, 2-addr, 2-size, 1-checksum

    const int DATA_OFFSET = 5;

    unsigned int i;

    uint16_t hexSize;

    uint8_t hexData[MAX_BUFFER_SIZE];

    int err = CYRET_SUCCESS;

    if (bufSize <= MIN_SIZE)

        err = CYRET_ERR_LENGTH;

    else if (buffer[0] == ':')

    {

        err = CyBtldr_FromAscii(bufSize - 1, &buffer[1], &hexSize, hexData);

       

        if (err == CYRET_SUCCESS)

        {

            *arrayId = hexData[0];

            *rowNum = parse2ByteValueBigEndian(hexData + 1);

            *size = parse2ByteValueBigEndian(hexData + 3);

            *checksum = (hexData[hexSize - 1]);

            if ((*size + MIN_SIZE) == hexSize)

            {

                for (i = 0; i < *size; i++)

                {

                    rowData = (hexData[DATA_OFFSET + i]);

                }

            }

            else

                err = CYRET_ERR_DATA;

        }

    }

    else

        err = CYRET_ERR_CMD;

    return err;

}

C#:

static HostReturnCodes CyBtldr_ParseRowData(uint bufSize, byte[] buffer, ref byte arrayID, ref ushort rowNum, ref ushort[] rowData, ref ushort size, ref byte checksum)

{

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    const ushort MIN_SIZE = 6;   //1-array, 2-addr, 2-size, 1-checksum

    const uint DATA_OFFSET = 5;

    ushort hexSize = 0;

    byte[] hexData = new byte[maxBufferSize];

    if (bufSize <= MIN_SIZE)

    {

        err = HostReturnCodes.CYRET_ERR_LENGTH;

    }

    else if (buffer[0] == ':')

    {

        err = CyBtldr_FromAscii(bufSize - 1, buffer.Skip(1).ToArray(), ref hexSize, ref hexData);

        if (err == HostReturnCodes.CYRET_SUCCESS)

        {

            arrayID = hexData[0];

            rowNum = parse2ByteValueBigEndian(hexData.Skip(1).ToArray());

            size = parse2ByteValueBigEndian(hexData.Skip(3).ToArray());

            checksum = (hexData[hexSize - 1]);

            if ((size + MIN_SIZE) == hexSize)

            {

                for (uint i = 0; i < size; i++)

                {

                    rowData = (hexData[DATA_OFFSET + i]);

                }

            }

            else

            {

                err = HostReturnCodes.CYRET_ERR_DATA;

            }

        }

    }

    else

    {

        err = HostReturnCodes.CYRET_ERR_CMD;

    }

    return err;

}

CyBtldr_ParseRowData_v1()

C:

int CyBtldr_ParseRowData_v1(uint32_t bufSize, uint8_t* buffer, uint32_t* address, uint8_t* rowData, uint16_t* size, uint8_t* checksum)

{

    const uint16_t MIN_SIZE = 4; //4-addr

    const int DATA_OFFSET = 4;

    unsigned int i;

    uint16_t hexSize;

    uint8_t hexData[MAX_BUFFER_SIZE];

    int err = CYRET_SUCCESS;

    if (bufSize <= MIN_SIZE)

        err = CYRET_ERR_LENGTH;

    else if (buffer[0] == ':')

    {

        err = CyBtldr_FromAscii(bufSize - 1, &buffer[1], &hexSize, hexData);

       

        if (CYRET_SUCCESS == err)

        {

            *address = parse4ByteValueLittleEndian(hexData);

            *checksum = 0;

            if (MIN_SIZE < hexSize)

            {

                *size = hexSize - MIN_SIZE;

                for (i = 0; i < *size; i++)

                {

                    rowData = (hexData[DATA_OFFSET + i]);

                    *checksum += rowData;

                }

            }

            else

                err = CYRET_ERR_DATA;

        }

    }

    else

        err = CYRET_ERR_CMD;

    return err;

}

C#:

static HostReturnCodes CyBtldr_ParseRowData_v1(uint bufSize, byte[] buffer, ref uint address, ref byte[] rowData, ref ushort size, ref byte checksum)

{

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    const ushort MIN_SIZE = 4;   //4-addr

    const uint DATA_OFFSET = 4;

    ushort hexSize = 0;

    byte[] hexData = new byte[maxBufferSize];

    if (bufSize <= MIN_SIZE)

    {

        err = HostReturnCodes.CYRET_ERR_LENGTH;

    }

    else if (buffer[0] == ';')

    {

        err = CyBtldr_FromAscii(bufSize - 1, buffer.Skip(1).ToArray(), ref hexSize, ref hexData);

        if (err == HostReturnCodes.CYRET_SUCCESS)

        {

            address = parse4ByteValueLittleEndian(hexData);

            checksum = 0;

            if (MIN_SIZE < hexSize)

            {

                size = (ushort)(hexSize - MIN_SIZE);

                for (uint i = 0; i < size; i++)

                {

                    rowData = (hexData[DATA_OFFSET + i]);

                    checksum += rowData;

                }

            }

            else

            {

                err = HostReturnCodes.CYRET_ERR_DATA;

            }

        }

    }

    else

    {

        err = HostReturnCodes.CYRET_ERR_CMD;

    }

    return err;

}

CyBtldr_ParseAppStartAndSize()

This one was pretty big and is my pick for "most likely to go wrong in testing".  Lots of string functions and file seeking that I had to swap out to what I think should be equivalent in C#.

C:

int CyBtldr_ParseAppStartAndSize_v1(uint32_t* appStart, uint32_t* appSize, uint8_t* buf)

{

    const uint32_t APPINFO_META_HEADER_SIZE = 11;

    const char APPINFO_META_HEADER[] = "@APPINFO:0x";

    const uint32_t APPINFO_META_SEPERATOR_SIZE = 3;

    const char APPINFO_META_SEPERATOR[] = ",0x";

    const char APPINFO_META_SEPERATOR_START[] = ",";

    long fp = ftell(dataFile);

    *appStart = 0xffffffff;

    *appSize = 0;

    uint32_t addr = 0;

    uint32_t rowLength;

    uint16_t rowSize;

    uint32_t seperatorIndex;

    uint8_t rowData[MAX_BUFFER_SIZE];

    uint8_t checksum;

    int err = CYRET_SUCCESS;

    uint32_t i;

    do

    {

        err = CyBtldr_ReadLine(&rowLength, buf);

        if (err == CYRET_SUCCESS)

        {

            if (buf[0] == ':')

            {

                err = CyBtldr_ParseRowData_v1(rowLength, buf, &addr, rowData, &rowSize, &checksum);

                if (addr < (*appStart))

                {

                    *appStart = addr;

                }

                (*appSize) += rowSize;

            }

            else if (rowLength >= APPINFO_META_HEADER_SIZE && strncmp(buf, APPINFO_META_HEADER, APPINFO_META_HEADER_SIZE) == 0)

            {

                // find seperator index

                seperatorIndex = strcspn(buf, APPINFO_META_SEPERATOR_START);

                if (strncmp(buf + seperatorIndex, APPINFO_META_SEPERATOR, APPINFO_META_SEPERATOR_SIZE) == 0)

                {

                    *appStart = 0;

                    *appSize = 0;

                    for (i = APPINFO_META_HEADER_SIZE; i < seperatorIndex; i++)

                    {

                        *appStart <<= 4;

                        *appStart += CyBtldr_FromHex(buf);

                    }

                    for (i = seperatorIndex + APPINFO_META_SEPERATOR_SIZE; i < rowLength; i++)

                    {

                        *appSize <<= 4;

                        *appSize += CyBtldr_FromHex(buf);

                    }

                }

                else

                {

                    err = CYRET_ERR_FILE;

                }

                break;

            }

        }

    } while (err == CYRET_SUCCESS);

    if (err == CYRET_ERR_EOF)

        err = CYRET_SUCCESS;

    // reset to the file to where we were

    if (err == CYRET_SUCCESS)

    {

        fseek(dataFile, fp, SEEK_SET);

    }

    return err;

}

C#:

static HostReturnCodes CyBtldr_ParseAppStartAndSize_v1(ref uint appStart, ref uint appSize, byte[] buf)

{

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    const uint      APPINFO_META_HEADER_SIZE = 11;

    const string    APPINFO_META_HEADER = "@APPINFO:0x";

    const uint      APPINFO_META_SEPERATOR_SIZE = 3;

    const string    APPINFO_META_SEPERATOR = ",0x";

    const string    APPINFO_META_SEPERATOR_START = ",";

    long fp = dataFile.Position;    //was ftell()

    appStart = 0xFFFFFFFF;

    appSize = 0;

    uint addr = 0;

    uint rowLength = 0;

    ushort rowSize = 0;

    uint seperatorIndex;

    byte[] rowData = new byte[maxBufferSize];

    byte checksum = 0;

    do

    {

        err = CyBtldr_ReadLine(ref rowLength, ref buf);

        if (err == HostReturnCodes.CYRET_SUCCESS)

        {

            if (buf[0] == ':')

            {

                err = CyBtldr_ParseRowData_v1(rowLength, buf, ref addr, ref rowData, ref rowSize, ref checksum);

                if (addr < appStart)

                {

                    appStart = addr;

                }

                appSize += rowSize;

            }

            else if(rowLength >= APPINFO_META_HEADER_SIZE)

            {

                System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();

                string s = enc.GetString(buf);

                if (string.Compare(s, 0, APPINFO_META_HEADER, 0, (int)APPINFO_META_HEADER_SIZE) == 0){  //was strncmp()

                    // find seperator index

                    //seperatorIndex = (uint)strcspn(s, APPINFO_META_SEPERATOR_START.ToArray());

                    seperatorIndex = (uint)s.IndexOfAny(APPINFO_META_SEPERATOR_START.ToArray());    //was strcspn()

                    if (string.Compare(s.Skip((int)seperatorIndex).ToString(), 0, APPINFO_META_SEPERATOR, 0, (int)APPINFO_META_SEPERATOR_SIZE) == 0)   //was strcspn() in cpp

                    {

                        appStart = 0;

                        appSize = 0;

                        for (int i = (int)APPINFO_META_HEADER_SIZE; i < seperatorIndex; i++)

                        {

                            appStart <<= 4;

                            appStart += CyBtldr_FromHex(s);

                        }

                        for (uint i = seperatorIndex + APPINFO_META_SEPERATOR_SIZE; i < rowLength; i++)

                        {

                            appSize <<= 4;

                            appStart += CyBtldr_FromHex(s[(int)i]);

                        }

                    }

                    else

                    {

                        err = HostReturnCodes.CYRET_ERR_FILE;

                    }

                    break;

                }

                else

                {

                    err = HostReturnCodes.CYRET_ERR_FILE;

                }

               

            }

            else

            {

                err = HostReturnCodes.CYRET_ERR_FILE;

            }

            break;

        }

    } while (err == HostReturnCodes.CYRET_SUCCESS);

    if (err == HostReturnCodes.CYRET_ERR_EOF)

    {

        err = HostReturnCodes.CYRET_SUCCESS;

    }

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

        dataFile.Seek(fp, SeekOrigin.Begin);    //was fseek()

    }

    return err;

}

CyBtldr_CloseDataFile()

Closing out Parse.cs is one that should be short and simple.

C:

int CyBtldr_CloseDataFile(void)

{

    int err = 0;

    if (NULL != dataFile)

    {

        err = fclose(dataFile);

        dataFile = NULL;

    }

    return (0 == err)

        ? CYRET_SUCCESS

        : CYRET_ERR_FILE;

}

C#:

static int CyBtldr_CloseDataFile()

{

    int err = 0;

    if (dataFile != null)

    {

        try

        {

            dataFile.Dispose();

        } catch (Exception)

        {

            err = 1;

        }

       

    }

    return (err == 0) ? (int)HostReturnCodes.CYRET_SUCCESS : (int)HostReturnCodes.CYRET_ERR_FILE;

}

It's occurring to me that this is probably going to be some especially long posts if I put up both the C and C# versions of each function.  Might only show the highlights and attach the full files in the future?

0 Likes
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

command.cs

This one had a bit more to it than the parse functions.

It also spans 800 lines after I converted it to C#, so I will not be posting every single function.  I will instead just update the OP with the latest version of the project for each update I make.  This post will most just cover the "highlights".

For command.cs we will need a new enum for the bootloader command bytes:

        public enum CommandCodes : byte

        {

            CMD_START = 0x01,

            CMD_STOP = 0x17,

            CMD_VERIFY_CHECKSUM = 0x31,

            CMD_GET_FLASH_SIZE = 0x32,

            CMD_GET_APP_STATUS = 0x33,

            CMD_ERASE_ROW = 0x34,

            CMD_SYNC = 0x35,

            CMD_SET_ACTIVE_APP = 0x36,

            CMD_SEND_DATA = 0x37,

            CMD_ENTER_BOOTLOADER = 0x38,

            CMD_PROGRAM_ROW = 0x39,

            CMD_GET_ROW_CHECKSUM = 0x3A,

            CMD_EXIT_BOOTLOADER = 0x3B,

            CMD_ERASE_DATA = 0x44,

            CMD_PROGRAM_DATA = 0x49,

            CMD_VERIFY_DATA = 0x4A,

            CMD_SET_METADATA = 0x4C,

            CMD_SET_EIV = 0x4D

        }

and for the Checksum Type:

        /*Checksum type enum, SUM or CRC*/

        public enum CyBtldr_ChecksumType

        {

            SUM_CHECKSUM = 0x00,

            CRC_CHECKSUM = 0x01

        }

        /*TODO: can probably replace this with a field/property*/

        public static CyBtldr_ChecksumType CyBtldr_Checksum = CyBtldr_ChecksumType.SUM_CHECKSUM;

As noted in my little todo comment.  I can maybe replace the CyBtldr_Checksum with a field/property.  Might be something to do later.  For now I'll just keep it as a normal variable.

Array/Pointer Stuff

First think to tackle here is the filldata16() and filldata32 functions.  Here they are in the original C:

static void fillData16(uint8_t* buf, uint16_t data)

{

    buf[0] = (uint8_t)(data);

    buf[1] = (uint8_t)(data >> 8);

}

static void fillData32(uint8_t* buf, uint32_t data)

{

    fillData16(buf, (uint16_t)data);

    fillData16(buf + 2, (uint16_t)(data >> 16));

}

They are simple enough, just splitting up 16/32 bit words into an array of bytes. You might notice there's a problem bringing this to C#:  It relies on pointer arithmatic in filldata32().  Also, most of the calls to filldata16() or filldata32() are sending pointer arguments with the index specified by addition, like the following example:

int CyBtldr_CreateEraseRowCmd(uint8_t arrayId, uint16_t rowNum, uint8_t* cmdBuf, uint32_t* cmdSize, uint32_t* resSize)

{

    const uint16_t COMMAND_DATA_SIZE = 3;

    *resSize = BASE_CMD_SIZE;

    *cmdSize = BASE_CMD_SIZE + COMMAND_DATA_SIZE;

    cmdBuf[4] = arrayId;

    fillData16(cmdBuf + 5, rowNum);    //<---Passing the index we want to place rowNum done with pointer

    return CreateCmd(cmdBuf, *cmdSize, CMD_ERASE_ROW);

}

Since C# doesn't have pointers, this obviously won't fly.  At first I was passing a second, locally declared working array to hold the result then copying it into the cmdBuf array.  After writing a couple functions this way, it seemed to be more prudent to just add an index parameter to the fillData() functions:

private static void fillData16(ref byte[] buf, ushort data, uint index)

{

    buf[index] = (byte)data;

    buf[index+1] = (byte)(data >> 8);

}

       

private static void fillData32(ref byte[] buf, uint data, uint index)

{

    fillData16(ref buf, (ushort)data, index);

    fillData16(ref buf, (ushort)data, index + 2);

}

This allows us to convert over the above CyBtldr_CreateEraseRowCmd() function like so:

public static HostReturnCodes CyBtldr_CreateEraseRowCmd(byte arrayID, ushort rowNum, ref byte[] cmdBuf, ref uint cmdSize, ref uint resSize)

{

    const ushort COMMAND_DATA_SIZE = 3;

    resSize = baseCommandSize;

    cmdSize = baseCommandSize + COMMAND_DATA_SIZE;

    cmdBuf[4] = arrayID;

    fillData16(ref cmdBuf, rowNum, 5);    //<---Sending over the index as a separate argument

    return CreateCmd(ref cmdBuf, cmdSize, CommandCodes.CMD_ERASE_ROW);

}

Writing this up now, I'm thinking there are probably methods in the BitConverter class that will perform the same role that these filldata() functions do (splitting words into a byte array).  I'll have to look into making sure things like endianness are OK.  For now I'll put it in the comments as a TODO and probably look into it later down the line.

the command functions also had numerous spots where copying data from array to array was able to be simplified by taking advantage of the Array functions in C#.

Something like this in CyBtldr_CreateEnterBootloaderCmd()

for (int i = 0; i < commandDataSize; i++)

{

    cmdBuf[i + 4] = securityKeyBuff;

}

Should be able to be replaced by:

Array.Copy(securityKeyBuff, 0, cmdBuf, 4, commandDataSize);

Checksum Stuff

command.c contains 2 checksum calculation functions.  One for 16-Bit, calculating a checksum in either 2's compliment, or CRC16-CCITT.  The other is CRC32-C.

I could have gone about simply converting the C code, but I actually have a CRC16-CCITT function already written for a sepreate application, so I decided to use that:

/// <summary>

/// Computes the 16-bit checksum for the provided command data.<br/>

/// The checksum is either 2's compliment or CRC16-CCITT

/// </summary>

/// <param name="buf">The data to compute the checksum on</param>

/// <param name="size">The number of bytes contained in buf</param>

/// <returns>The checksum for the provided data</returns>

public static ushort CyBtldr_ComputeChecksum16bit(byte[] buf, uint size)

{

        const ushort poly = 0x8408;

        ushort[] table = new ushort[256];

        ushort initialValue = 0xffff;

        ushort temp, a;

        ushort crc = initialValue;

        if (CyBtldr_Checksum == CyBtldr_ChecksumType.CRC_CHECKSUM)

        {

            for (int i = 0; i < size; ++i)

            {

                temp = 0;

                a = (ushort)(i << 8);

                for (int j = 0; j < 8; ++j)

                {

                    if (((temp ^ a) & 0x8000) != 0)

                    {

                        temp = (ushort)((temp << 1) ^ poly);

                    }

                    else

                    {

                        temp <<= 1;

                    }

                           

                    a <<= 1;

                }

                table = temp;

            }

            for (int i = 0; i < size; ++i)

            {

                crc = (ushort)((crc << 😎 ^ table[((crc >> 😎 ^ (0xff & buf))]);

            }

            return crc;

        }

        else

        {

            ushort sum = 0;

            ushort index = 0;

            while (size-- > 0)

            {

                sum += buf[index++];

            }

            return (ushort)(1 + ~sum);

        }

           

    }

The CRC32-C I converted from the original C code:

C:

uint32_t CyBtldr_ComputeChecksum32bit(uint8_t* buf, uint32_t size)

{

    enum {

        g0 = 0x82F63B78,

        g1 = (g0 >> 1) & 0x7fffffff,

        g2 = (g0 >> 2) & 0x3fffffff,

        g3 = (g0 >> 3) & 0x1fffffff,

    };

    const static uint32_t table[16] =

    {

        0,                  (uint32_t)g3,           (uint32_t)g2,           (uint32_t)(g2^g3),

        (uint32_t)g1,       (uint32_t)(g1^g3),      (uint32_t)(g1^g2),      (uint32_t)(g1^g2^g3),

        (uint32_t)g0,       (uint32_t)(g0^g3),      (uint32_t)(g0^g2),      (uint32_t)(g0^g2^g3),

        (uint32_t)(g0^g1),  (uint32_t)(g0^g1^g3),   (uint32_t)(g0^g1^g2),   (uint32_t)(g0^g1^g2^g3),

    };

    uint8_t* data = (uint8_t*)buf;

    uint32_t crc = 0xFFFFFFFF;

    while (size != 0)

    {

        int i;

        --size;

        crc = crc ^ (*data);

        ++data;

        for (i = 1; i >= 0; i--)

        {

            crc = (crc >> 4) ^ table[crc & 0xF];

        }

    }

    return ~crc;

}

C#:

//enum for calculating CRC32-C

private enum g : uint

{

    g0 = 0x82F63B78,

    g1 = (g0 >> 1) & 0x7fffffff,

    g2 = (g0 >> 2) & 0x3fffffff,

    g3 = (g0 >> 3) & 0x1fffffff,

};

/// <summary>

/// Computes the 4 byte checksum for the provided command data.<br/>

/// The checksum is computed using CRC32-C

/// </summary>

/// <param name="buf">The data to compute the checksum on</param>

/// <param name="size">The number of bytes contained in buf</param>

/// <returns>The checksum for the provided data</returns>

public static uint CyBtldr_ComputeChecksum32bit(byte[] buf, uint size)

{

    uint[] table =

    {

        0,                 (uint)g.g3,              (uint)g.g2,             (uint)(g.g2^g.g3),

        (uint)g.g1,        (uint)(g.g1^g.g3),       (uint)(g.g1^g.g2),      (uint)(g.g1^g.g2^g.g3),

        (uint)g.g0,        (uint)(g.g0^g.g3),       (uint)(g.g0^g.g2),      (uint)(g.g0^g.g2^g.g3),

        (uint)(g.g0^g.g1), (uint)(g.g0^g.g1^g.g3),  (uint)(g.g0^g.g1^g.g2), (uint)(g.g0^g.g1^g.g2^g.g3),

    };

    uint crc = 0xFFFFFFFF;

    while (size != 0)

    {

        int i;

        int bufindex = 0;

        --size;

        crc ^= buf[bufindex++];

        for (i = 1; i >= 0; i--)

        {

            crc = (crc >> 4) ^ table[crc & 0xF];

        }

    }

    return crc;

}

The Rest:

The rest of the functions in command.c mostly follow a pattern.  Commands for the bootloader have a _Create() function to assemble the command, and a _ParseBootloaderResultCmd() function to read the response from the bootloader.  I'll use Enter Bootloader as an example:

CyBtldr_CreateEnterBootloaderCommand()

C:

int CyBtldr_CreateEnterBootLoaderCmd(uint8_t* cmdBuf, uint32_t* cmdSize, uint32_t* resSize, const uint8_t* securityKeyBuf)

{

    const uint16_t RESULT_DATA_SIZE = 8;

    const uint16_t BOOTLOADER_SECURITY_KEY_SIZE = 6;

    uint16_t commandDataSize;

    uint16_t i;

    *resSize = BASE_CMD_SIZE + RESULT_DATA_SIZE;

    if (securityKeyBuf != NULL)

        commandDataSize = BOOTLOADER_SECURITY_KEY_SIZE;

    else

        commandDataSize = 0;

    *cmdSize = BASE_CMD_SIZE + commandDataSize;

    for (i = 0; i < commandDataSize; i++)

        cmdBuf[i + 4] = securityKeyBuf;

    return CreateCmd(cmdBuf, *cmdSize, CMD_ENTER_BOOTLOADER);

}

C#:

/// <summary>

/// Creates the command used to startup the bootloader<br/>

/// This command must be sent before the bootloader will respond to any other command.

/// </summary>

/// <param name="cmdBuf">Preallocated buffer to store command data in</param>

/// <param name="cmdSize">The number of bytes in the command</param>

/// <param name="resSize">The number of bytes expected in the bootloader response packet</param>

/// <param name="securityKeyBuff">The 6-Byte or null bootloader security key</param>

/// <returns>CYRET_SUCCESS - The command was constructed successfully</returns>

public static HostReturnCodes CyBtldr_CreateEnterBootloaderCommand(ref byte[] cmdBuf, ref uint cmdSize, ref uint resSize, ref byte[] securityKeyBuff)

{

    const ushort RESULT_DATA_SIZE = 8;

    const ushort BOOTLOADER_SECURITY_KEY_SIZE = 6;

    ushort commandDataSize;

    resSize = baseCommandSize + RESULT_DATA_SIZE;

    if (securityKeyBuff != null || securityKeyBuff.Length == 0) {

        commandDataSize = BOOTLOADER_SECURITY_KEY_SIZE;

    } else

    {

        commandDataSize = 0;

    }

    cmdSize = (uint)(baseCommandSize + commandDataSize);

    //for (int i = 0; i < commandDataSize; i++)

    //{

    //    cmdBuf[i + 4] = securityKeyBuff;

    //}

    /*original code above, should be able to replace w/ Array.Copy()*/

    Array.Copy(securityKeyBuff, 0, cmdBuf, 4, commandDataSize);

    return CreateCmd(ref cmdBuf, cmdSize, CommandCodes.CMD_ENTER_BOOTLOADER);

}

CyBtldr_ParseEnterBootloaderCmdResult()

C:

int CyBtldr_ParseEnterBootLoaderCmdResult(uint8_t* cmdBuf, uint32_t cmdSize, uint32_t* siliconId, uint8_t* siliconRev, uint32_t* blVersion, uint8_t* status)

{

    const uint32_t RESULT_DATA_SIZE = 8;

    int err = ParseGenericCmdResult(cmdBuf, RESULT_DATA_SIZE, cmdSize, status);

    if (CYRET_SUCCESS == err)

    {

        *siliconId = (cmdBuf[7] << 24) | (cmdBuf[6] << 16) | (cmdBuf[5] << 😎 | cmdBuf[4];

        *siliconRev = cmdBuf[8];

        *blVersion = (cmdBuf[11] << 16) | (cmdBuf[10] << 😎 | cmdBuf[9];

    }

    return err;

}

C#:

/// <summary>

/// Parses the response from the bootloader to the EnterBootloader command

/// </summary>

/// <param name="cmdBuf">The buffer containing the output from the bootloader</param>

/// <param name="cmdSize">The number of bytes in cmdBuf</param>

/// <param name="siliconID">The silicon ID of the device being communicated with</param>

/// <param name="siliconRev">The silicon Revision of the device being communicated with</param>

/// <param name="blVersion">The bootloader version being communicated with</param>

/// <param name="status">The status code returned by the bootloader</param>

/// <returns>

/// CYRET_SUCCESS    - The command was constructed successfully<br/>

/// CYRET_ERR_LENGTH - The packet does not contain enough data<br/>

/// CYRET_ERR_DATA   - The packet's contents are not correct<br/>

/// </returns>

public static HostReturnCodes CyBtldr_ParseEnterBootloaderResult(byte[] cmdBuf, uint cmdSize, ref uint siliconID,

                                                                 ref byte siliconRev, ref uint blVersion, ref byte status)

{

    const uint RESULT_DATA_SIZE = 8;

    HostReturnCodes err = ParseGenericCmdResult(cmdBuf, RESULT_DATA_SIZE, cmdSize, ref status);

    if (err == HostReturnCodes.CYRET_SUCCESS) {

        siliconID = ((uint)cmdBuf[7] << 24) | ((uint)cmdBuf[6] << 16) | ((uint)cmdBuf[5] << 😎 | cmdBuf[4];

        siliconRev = cmdBuf[8];

        blVersion = ((uint)cmdBuf[11] << 16) | ((uint)cmdBuf[10] << 😎 | cmdBuf[9];

    }

    return err;

}

We also see all these functions calling CreateCmd() and ParseGenericCmdResult().

CreateCmd()

C:

static int CreateCmd(uint8_t* cmdBuf, uint32_t cmdSize, uint8_t cmdCode)

{

    uint16_t checksum;

    cmdBuf[0] = CMD_START;

    cmdBuf[1] = cmdCode;

    fillData16(cmdBuf + 2, (uint16_t)cmdSize - BASE_CMD_SIZE);

    checksum = CyBtldr_ComputeChecksum16bit(cmdBuf, cmdSize - 3);

    fillData16(cmdBuf + cmdSize - 3, checksum);

    cmdBuf[cmdSize - 1] = CMD_STOP;

    return CYRET_SUCCESS;

}

C#:

/// <summary>

/// Assembles bootloader command packet

/// <para>

/// NOTE: If the command contains data bytes, make sure to call this after setting data bytes.<br/>

/// Otherwise the checksum here will not include the data bytes.

/// </para>

/// </summary>

/// <param name="cmdBuf"></param>

/// <param name="cmdSize"></param>

/// <param name="cmdCode"></param>

/// <returns>CYRET_SUCCESS - The command was created successfully</returns>

private static HostReturnCodes CreateCmd(ref byte[] cmdBuf, uint cmdSize, CommandCodes cmdCode)

{

    ushort checksum;

    cmdBuf[0] = (byte)CommandCodes.CMD_START;

    cmdBuf[1] = (byte)cmdCode;

    fillData16(ref cmdBuf, (ushort)(cmdSize - baseCommandSize), 2);

    checksum = CyBtldr_ComputeChecksum16bit(cmdBuf, cmdSize - 3);

    fillData16(ref cmdBuf, checksum, (ushort)((ushort)cmdSize-3));

    cmdBuf[cmdSize - 1] = (byte)CommandCodes.CMD_STOP;

    return HostReturnCodes.CYRET_SUCCESS;

}

ParseGenericCmdResult()

C:

static int ParseGenericCmdResult(uint8_t* cmdBuf, uint32_t dataSize, uint32_t expectedSize, uint8_t* status)

{

    int err = CYRET_SUCCESS;

    uint32_t cmdSize = dataSize + BASE_CMD_SIZE;

    *status = cmdBuf[1];

    if (cmdSize != expectedSize)

        err = CYRET_ERR_LENGTH;

    else if (*status != CYRET_SUCCESS)

        err = CYRET_ERR_BTLDR_MASK | (*status);

    else if (cmdBuf[0] != CMD_START || cmdBuf[2] != ((uint8_t)dataSize) || cmdBuf[3] != ((uint8_t)(dataSize >> 8)) || cmdBuf[cmdSize - 1] != CMD_STOP)

        err = CYRET_ERR_DATA;

    return err;

}

C#:

/// <summary>

/// Parses the output from any command that returns the default result packet data.

/// </summary>

/// <param name="cmdBuf">The preallocated buffer to store command data in.</param>

/// <param name="dataSize">The number of bytes in the command.</param>

/// <param name="expectedSize">Expected size of the packet</param>

/// <param name="status">The status code returned by the bootloader.</param>

/// <returns>

/// CYRET_SUCCESS    - The command was constructed successfully<br/>

/// CYRET_ERR_LENGTH - The packet does not contain enough data<br/>

/// CYRET_ERR_DATA   - The packet's contents are not correct<br/>

/// </returns>

private static HostReturnCodes ParseGenericCmdResult(byte[] cmdBuf, uint dataSize, uint expectedSize, ref byte status)

{

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    uint cmdSize = dataSize + baseCommandSize;

    status = cmdBuf[1];

    if (cmdSize != expectedSize)

    {

        err = HostReturnCodes.CYRET_ERR_LENGTH;

    } else if (status != (byte)HostReturnCodes.CYRET_SUCCESS) {

        err = (HostReturnCodes)(((ushort)HostReturnCodes.CYRET_ERR_BTLDR_MASK) | (status));

    } else if ( (cmdBuf[0] != (byte)CommandCodes.CMD_START) ||

                (cmdBuf[2] != dataSize) ||

                (cmdBuf[3] != (dataSize >> 8)) ||

                (cmdBuf[cmdSize-1] != (byte)CommandCodes.CMD_STOP))

    {

        err = HostReturnCodes.CYRET_ERR_DATA;

    }

    return err;

}

There are also a number of calls to ParseDefaultCmdResult(), which is really just a call to ParseGenericCmdResult():

/// <summary>

/// Parses bootloader response commands

/// </summary>

/// <param name="cmdBuf">The preallocated buffer containing the response packet from the bootloader</param>

/// <param name="cmdSize">The number of bytes in the command</param>

/// <param name="status">The status code returned by the bootloader</param>

/// <returns></returns>

private static HostReturnCodes CyBtldr_ParseDefaultCmdResult(byte[] cmdBuf, uint cmdSize, ref byte status)

{

    return ParseGenericCmdResult(cmdBuf, 0, cmdSize, ref status);

}

Nothing too special going on with these, but I'm wondering if I can just replace ParseDefaultCmdResult() with only calls to ParseGerericCmdResult().  For now I will leave it as is.

0 Likes
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

Starting into the conversion of api.c/h.

Running into a bit of a block trying to figure out how to replicate the original library functionality of defining OpenConnection() CloseConnection() ReadData() and WriteData() in the application and being able to call them from the library.

In the original C it's done through function pointers and interop.  Now I need to do it all in managed code.  I've got a StackOverflow question up on the topic:

https://stackoverflow.com/questions/64652826/how-should-i-port-this-struct-of-c-function-pointers-to...

It looks like there may actually be several ways to make it happen.

[EDIT]

I'm probably going to go with using an Interface per the answer from Bart in that SO question:

In the library we define how we want the communication data object to look

//In Library
public interface ICommunicationData
{
   int OpenConnection();
   int CloseConnection();
   int ReadData(byte[] dataBuf, int numBytes);
   int WriteData(byte[] dataBuf, int numBytes);
   int MaxTransferSize { get; set; }
}


In the Application you define CommunicationData as a class that conforms to the Interface defined in the library

//Application
public class SerialCommunicationData : ICommunicationData
{
   public int MaxTransferSize { get; set; }

   public int CloseConnection()
  {
   /*Define CloseConnection Here*/
   return 1;
  }

   public int OpenConnection()
  {
   /*Define OpenConnection Here*/
   return 1;
  }

   public int ReadData(byte[] dataBuf, int numBytes)
  {
   /*Define ReadData Here*/
   return 1;
  }

   public int WriteData(byte[] dataBuf, int numBytes)
  {
   /*Define WriteData Here*/
   return 1;
  }
}

0 Likes
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

Some notes and findings about reference and value types in C#

I'm starting to discover that using the ref keyword to pass arrays by reference may have been the wrong call.

Since my programmer brain mostly works in 'C' mode,  I'm used to passing something like an array of bytes to a function by reference using (uint8_t *).  I didn't realize that array objects in C# (as well as other objects like classes) are what is called "reference types".  What this means is that while they are technically passed by "value", the value of a reference type is a reference to the data, not the array data itself.  So essentially, passing a byte[] as in argument in C# is much like passing a (uint8_t *) in C.

Simple types like int, char, float are "value" types in C# and are passed to functions like you would expect in C, in the case of passing an int, you are passing the literal value of the int.  In order to pass value types by reference you either need to use the "ref" or "out" keywords.

It's a little weird to work you head around if you've spent 95% of your time working in C, but this link here helped me get at least a tenuous grasp on the concept:

Parameter passing in C#

So, at some point I'm probably going to need to adjust all the function calls with byte[] objects to no longer use the ref keyword.  It might work as is, but in all honestly I don't actually know what it will do (is it like a uint8_t**?).  I do think with how this code is structured we should still need to use "ref" for some value type arguments though.

KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

api.cs/api2.cs

The api/api2 source files were pretty strongly tied together, so i tackled them at the same time.  They will both be part of the same class, BootloaderAPI.

BootloaderAPI is a static class.  Generally I don't see much reason to not have it be a static class, as instantiating the class before use in an application just seems unnecessary?  So until I am told otherwise, static is what it will be.

The big thing to get working for this section of the library was is the communication data functions Open/Close/Send/Receive.  I mentioned in an earlier post I went with using an interface.  The pattern is defined in the library.  The implementation will fall into the application, just like the original unmanaged API.

We also have the CyBtldr_ProgressUpdate(byte arrayID, ushort rowNum) function, which, like the communication data functions, needs to be implemented in the user code, with the prototype defined in the library.  it also needs to be able to be passed into a number of functions in api2 to be called as update().  I think a delegate is what I need to implement this?:

public delegate void CyBtldr_ProgressUpdate(byte arrayID, ushort rowNum);

The user would then have to define the function like so:

public void ProgressUpdate(byte arrayID, ushort rowNum)

{

    progressBarProgress += progressBarStepSize;

    progressBar1.Value = (int)(progressBarProgress);

    this.Refresh();

    return;

}

An instance is created and is passed to the bootloader API functions as an argument much like the communication_data functions (or like a function pointer if we were still using C):

BootloaderAPI.CyBtldr_ProgressUpdate update = new BootloaderAPI.CyBtldr_ProgressUpdate(ProgressUpdate);

e.Result = BootloaderAPI.CyBtldr_Program(Chosen_File_Cyacd, null, 1, comm_data, update);

In regards to the api/api2 functions themselves, most of them were fairly straightforward and didn't present much that I hadn't seen already elsewhere in this process.

There were a few annoyances due to how C# seems to handle types.  For example this bit:

public static HostReturnCodes CyBtldr_ProgramRow(byte arrayID, ushort rowNum, ref byte[] buf, ushort size)

{

     /*snip*/

    ushort offset = 0;

    ushort subBufSize = 0;

    /*snip doing stuff*/

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

          subBufSize = (ushort)(size - offset);  //cast to ushort required.  Both arguments and the lvalue are ushort, but the result of (size-offet) is an int

    }

    /*snip*/

}

There's also lots of usage of a status variable that is a byte, but is very frequently the subject of bitwise operations with the HostReturnCodes enum.  Mostly it just meant more explicit casting:

byte status = (byte)HostReturnCodes.CYRET_SUCCESS;

/*snip - do stuff that modifies status and err*/

if (status != (byte)HostReturnCodes.CYRET_SUCCESS)

{

    err = (HostReturnCodes)status | HostReturnCodes.CYRET_ERR_BTLDR_MASK;

}

So right now I have all functions "converted", but no real testing has yet begun.  The library builds, but I will need to work up a test bench application of some sort to start seeing if any of this works.  I also have yet to go through the library code and replace all the instances of ref byte[] in function definitions with just byte[].  I may hold off on it for now mostly to observe what happens when I do use ref byte[]

I'm also seeing instances where the out keyword might be more suited than ref for some of the value type variables being passed by reference.  out does not require a value be assigned to the variable when the call is made, ref requires the variable being passed be assigned a value beforehand.  Since in most cases the function being called is assigning the value to the variable, out would probably be the "correct" modifier.

For now, the OP has been updated with the latest project.

0 Likes
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

Now begins the process of trying to get this thing to actually run and bootload correctly.

My testbench is based off an old UART bootloader based loosely off the old AN68272 C# UART Bootloader Host that unfortunately no longer exists (note to anyone from cypress reading this, having a simple example loader host w/ UART interface would still be incredibly helpful).

Right away a few issues jumped out when trying to run:

1)  In api.cs, CyBtldr_StartBootloaderOperation() filling g_validRows with 0 was not being done correctly.

I was using:

//This does not work, and the specific overload it was using was expecting a 2D array

g_validRows.SetValue(noFlashArrayData, 0, maxFlashArrays);

Rewrote it to this (basically the original C code):

for (int i = 0; i < maxFlashArrays; i++)

{

    g_validRows = noFlashArrayData;

}

I was hoping to be able to use something like Array.Fill() but that appears to not be available for some reason so I guess this will do at least for now.

2) My attempt to simplify CyBtldr_FromHex(), the new method didn't work the way I thought it would (char.GetNumericValue() doesn't seem to work the way I thought?).

I just brought it back to basically the original C code, and it works as expected:

static byte CyBtldr_FromHex(char value)

{

    if ('0' <= value && value <= '9')

        return (byte)(value - '0');

    if ('a' <= value && value <= 'f')

        return (byte)(10 + value - 'a');

    if ('A' <= value && value <= 'F')

        return (byte)(10 + value - 'A');

    return 0;

}

3) A return of this problem: What happened to the C# .NET PSoC UART bootloader? which I still think is a bug in the original bootloader_utils codebase.  For some reason it does not handle the GetApplicationStatus command appropriately.  In a project that is single application, the bootloader host is supposed to ignore the "Invalid Command" response from the PSoC to the Get AppStatus command 0x33 since it is indeed an invalid command for single app loaders.  I adjusted CyBtldr_GetApplicationStatus() to override the error with a success message if the bootloader returns "Invalid Command", but the response parses properly:

public static HostReturnCodes CyBtldr_GetApplicationStatus(byte appID, ref byte isValid, ref byte isActive)

{

    uint inSize = 0;

    uint outSize = 0;

    byte[] inBuf = new byte[Command.maxCommandSize];

    byte[] outBuf = new byte[Command.maxCommandSize];

    byte status = (byte)HostReturnCodes.CYRET_SUCCESS;

    HostReturnCodes err;

    err = Command.CyBtldr_CreateGetAppStatusCmd(appID, ref inBuf, ref inSize, ref outSize);

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

        err = CyBtldr_TransferData(ref inBuf, inSize, ref outBuf, outSize);

    }

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

         err = Command.CyBtldr_ParseGetAppStatusCmdResult(outBuf, outSize, ref isValid, ref isActive, ref status);

    }

    else if (Command.CyBtldr_TryParsePacketStatus(outBuf, outSize, ref status) == (byte)HostReturnCodes.CYRET_SUCCESS)

    {

        if ((BootloaderStatusCodes)status == BootloaderStatusCodes.CYBTLDR_STAT_ERR_CMD)

        {

             /*App Status command is invalid for single app loaders, override w/ success*/

             err = HostReturnCodes.CYRET_SUCCESS;

             status = (byte)HostReturnCodes.CYRET_SUCCESS;

        }

        else

        {

            err = (HostReturnCodes)status | HostReturnCodes.CYRET_ERR_BTLDR_MASK;

        }

    }

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

        if ((HostReturnCodes)status != HostReturnCodes.CYRET_SUCCESS)

        {

            err = (HostReturnCodes)status | HostReturnCodes.CYRET_ERR_BTLDR_MASK;

        }

    }

    return err;

}

4) CyBtldr_ReadLine() was not working correctly.  This was due to using a FileStream object rather than a StreamReader.

FileStream.Read() was reading the entire file in, where I was only looking for a single line.

StreamReader has a ReadLine() method.

This also requires a conversion from a string to byte[], since StreamReader.Readline returns a string.

Original:

static FileStream dataFile;

public static HostReturnCodes CyBtldr_ReadLine(ref uint size, ref byte[] buffer) //TODO: Possibly use out keyword for size and remove ref for buffer

{

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    uint len;

    do

    {

        len = 0;

        if (dataFile != null)    

        {

            if (dataFile.Read(buffer, 0, maxBufferSize * 2) != 0)  //EOF check, in C, this was a single if() above

            {

                len = (uint)buffer.Length;

                while (len > 0 && ('\n' == buffer[len - 1] || '\r' == buffer[len - 1]))

                {

                    --len;

                }

            }

            else

            {

                err = HostReturnCodes.CYRET_ERR_EOF;

            }

        }

        else

        {

            err = HostReturnCodes.CYRET_ERR_FILE;

        }

    } while (err == HostReturnCodes.CYRET_SUCCESS && buffer[0] == '#');

    size = len;

    return err;

}

Fixed:

static FileStream dataFile;

static StreamReader dataFileStream;

public static HostReturnCodes CyBtldr_ReadLine(ref uint size, ref byte[] buffer) //TODO: Possibly use out keyword for size and remove ref for buffer

{

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    uint len;

    string s;

    do

    {

        len = 0;

        if (dataFile != null)    

        {

            dataFileStream = new StreamReader(dataFile);

            if ((s = dataFileStream.ReadLine()) != null)  //EOF check, in C, this was a single if() above

            {

                len = (uint)s.Length;

                while (len > 0 && ('\n' == s[(int)len - 1] || '\r' == s[(int)len - 1]))

                {

                    --len;

                }

                buffer = Encoding.ASCII.GetBytes(s);

            }

            else

            {

                err = HostReturnCodes.CYRET_ERR_EOF;

            }

        }

        else

        {

            err = HostReturnCodes.CYRET_ERR_FILE;

        }

    } while (err == HostReturnCodes.CYRET_SUCCESS && buffer[0] == '#');

    size = len;

    return err;

}

I might be worth considering buffering the entire cyacd file into an array of strings rather than having to hit up the file every time I want to read another line.

Currently I'm stuck on a checksum mismatch in ProcessDataRow_v0():

private static HostReturnCodes ProcessDataRow_v0(CyBtldr_Actions action, uint rowSize, ref byte[] rowData, CyBtldr_ProgressUpdate update)

{

    byte[] buffer = new byte[Parse.maxBufferSize];

    ushort bufSize = 0;

    byte arrayID = 0;

    ushort rowNum = 0;

    byte checksum = 0;

    HostReturnCodes err;

    err = Parse.CyBtldr_ParseRowData(rowSize, rowData, ref arrayID, ref rowNum, ref buffer, ref bufSize, ref checksum); //0xCF from ParseRowData (matches cyacd)

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

        switch (action)

        {

            case CyBtldr_Actions.ERASE:

                err = CyBtldr_EraseRow(arrayID, rowNum);

                break;

            case CyBtldr_Actions.PROGRAM:

                err = CyBtldr_ProgramRow(arrayID, rowNum, ref buffer, bufSize);

                if (err != HostReturnCodes.CYRET_SUCCESS)

                {

                    break;

                }

                goto case CyBtldr_Actions.VERIFY;  //Fall through case statements are not allowed in C#, we get to use a goto!

            case CyBtldr_Actions.VERIFY:

                checksum = (byte)(checksum + arrayID + rowNum + (rowNum >> 😎 + bufSize + (bufSize >> 8));  //0xCF -> 0x08

                err = CyBtldr_VerifyRow(arrayID, rowNum, checksum); //0x93 returned from PSoC from Verify Row Command

                break;

        }

    }

    if (err == HostReturnCodes.CYRET_SUCCESS && update != null)

    {

        update(arrayID, rowNum);

    }

    return err;

}

Updated the OP with the lastest project files.  Included the WPF testbench app I've been using as well.

0 Likes
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

Good news!

I have my first successful bootload onto a PSoC5LP (CY8CKIT-050) via UART interface using the new library.

The checksum issue was caused by the Send Data packets  being assembled incorrectly.

It's easy to see here in SendData():

private static HostReturnCodes SendData(ref byte[] buf, ushort size, ref ushort offset, ushort maxRemainingDataSize, ref byte[] inBuf, ref byte[] outbuf)

{

    byte status = (byte)HostReturnCodes.CYRET_SUCCESS;

    uint inSize = 0;

    uint outSize = 0;

    // size is the total bytes of data to transfer.

    // offset is the amount of data already transfered.

    // a is maximum amount of data allowed to be left over when this function ends.

    // (we can leave some data for caller (programRow, VerifyRow,...) to send.

    // TRANSFER_HEADER_SIZE is the amount of bytes this command header takes up.

    const ushort TRANSFER_HEADER_SIZE = 7;

    ushort subBufSize = min_uint16((ushort)(g_Comm.MaxTransferSize - TRANSFER_HEADER_SIZE), size);

    HostReturnCodes err = HostReturnCodes.CYRET_SUCCESS;

    byte[] tempbuf;

    //Break row into pieces to ensure we don't send too much for the transfer protocol

    while ((err == HostReturnCodes.CYRET_SUCCESS) && ((size - offset) > maxRemainingDataSize))

    {

        //Original C Call:

        //CyBtldr_CreateSendDataCmd(&buf[*offset], subBufSize, inBuf, &inSize, &outSize);

        tempbuf = buf.Skip(size - offset).ToArray();

        err = Command.CyBtldr_CreateSendDataCmd(ref tempbuf, subBufSize, ref inBuf, ref inSize, ref outSize);

        Array.Copy(tempbuf, 0, buf, (size - offset), tempbuf.Length);

        if (err == HostReturnCodes.CYRET_SUCCESS)

        {

            err = CyBtldr_TransferData(ref inBuf, inSize, ref outbuf, outSize);

        }

        if (err == HostReturnCodes.CYRET_SUCCESS)

        {

            err = Command.CyBtldr_ParseSendDataCmdResult(outbuf, outSize, ref status);

        }

        if ((HostReturnCodes)status != HostReturnCodes.CYRET_SUCCESS)

        {

            err = (HostReturnCodes)status | HostReturnCodes.CYRET_ERR_BTLDR_MASK;

        }

        offset += subBufSize;

    }

    return err;

}

Two things:

1) check out line 22. that buf.Skip() is being given the wrong argument, and it should just be buf.Skip(offset) rather than buf.Skip(size-offset).  That was pointing us to the wrong data being placed into the packet to begin with.

2) have a look at line 24.  That Array.Copy() is completely wrong, and shouldn't be present at all.  tempbuf doesn't need to go anywhere other than into CyBtldr_CreateSendDataCmd().  buf should never be touched.  Having that Array.Copy() there was overwriting buf and presumably giving us garbage data in the command.

So between those two things, of course the checksum coming back from the PSoC after writing that data is going to be wrong.  A case of garbage in, garbage out.

CyBtldr_ProgramRow_v1() had the exact same problem, and has been corrected as well.

I also need to address the call to CyBtldr_GetApplicationStatus() again.  Last update I changed it to no longer return an error when the PSoC replies with "Invalid Command".  This actually needs to be changed again.

CyBtldr_GetApplicationStatus() is called twice inside RunAction_v0().  The first time is just before loading, the second is after loading.

The first call (before programming) looks like this:

if ((err == HostReturnCodes.CYRET_SUCCESS) && (appID != INVALID_APP))

{

    /* This will return error if bootloader is for single app */

    err = CyBtldr_GetApplicationStatus(appID, ref isValid, ref isActive);

    /* Active app can be verified, but not programmed or erased */

    if ((err == HostReturnCodes.CYRET_SUCCESS) && (action != CyBtldr_Actions.VERIFY) && (isActive != 0))

   {

       err = HostReturnCodes.CYRET_ERR_ACTIVE;

   }

}

The second call (after programming) looks like this:

if ((action == CyBtldr_Actions.PROGRAM) && (appID != INVALID_APP))

{

    err = CyBtldr_GetApplicationStatus(appID, ref isValid, ref isActive);

    if (err == HostReturnCodes.CYRET_SUCCESS)

    {

        /* If valid set the active application to what was just programmed */

        /* This is multi app */

        err = (isValid == 0) ? CyBtldr_SetApplicationStatus(appID) : HostReturnCodes.CYRET_ERR_CHECKSUM;

    }

    else if((int)(err ^ HostReturnCodes.CYRET_ERR_BTLDR_MASK) == (int)BootloaderStatusCodes.CYBTLDR_STAT_ERR_CMD)

    {

        /* Single app - restore previous CYRET_SUCCESS */

        err = HostReturnCodes.CYRET_SUCCESS;

    }

    CyBtldr_EndBootloadOperation();

}

Note the else if() after the second call.  This is what is meant to read the error message, and reset the status to CYRET_SUCCESS if we are talking to a single application bootloader.  For some reason, this is not done after the first call.

We run into an extra problem with my prior "fix" of the problem in the previous post.  if I modify CyBtldr_GetApplicationStatus() to perform this check and revert the status to CYRET_SUCCESS, it causes the code to flow as if it is a multi-app bootloader after the second call.  In my original solution, this was causing an error to be thrown at the end of the loading process.  So CyBtldr_GetApplicationStatus() DOES need to flag the error on a single app bootloader, but it must be handled after the call, not inside the function.

Applying the same code that the second call uses to the first call seems to work properly:

if ((err == HostReturnCodes.CYRET_SUCCESS) && (appID != INVALID_APP))

{

    /*Will return an error if single-app*/

    err = CyBtldr_GetApplicationStatus(appID, ref isValid, ref isActive);

    /* Active app can be verified, but not programmed or erased */

    if ((err == HostReturnCodes.CYRET_SUCCESS) && (action != CyBtldr_Actions.VERIFY) && (isActive != 0))

    {

        err = HostReturnCodes.CYRET_ERR_ACTIVE;

    }

    if ((int)(err ^ HostReturnCodes.CYRET_ERR_BTLDR_MASK) == (int)BootloaderStatusCodes.CYBTLDR_STAT_ERR_CMD)

    {

         /* Single app - restore previous CYRET_SUCCESS */

        err = HostReturnCodes.CYRET_SUCCESS;

    }

}

Most of the other issues I've found were easy fixes.

  • FillData32() was not filling LSB properly.  I forgot to left shift the data word.
  • CyBtldr_CreateEnterBootloaderCmd() had a length zero check of securityKeyBuff that would fail if securityKeyBuff is null. Replaced with only a null check.
  • Replacements of Array.SetValue() that I missed last time through.
  • CyBtldr_CreateSendDataCmd() was assembling commands at the wrong index (7). This has been corrected to 4, where the command data should properly begin.

So where to go from here?  I'll probably do some additional testing, see if I can integrate it into my bootloader application that currently uses the unmanaged version of the host utilities library.  I also still need to run through the entire project and remove "ref" keywords from array arguments, as mentioned several updates ago.  I don't think they hurt anything, but is probably more "correct" to remove them.

There's still PLENTY of things that I have not tested.

  • The _v1 functions have not been tested at all since I don't have any PSoC6 parts that use the format.  We are looking into PSoC6 for a future project, so I'll probably be testing it at some point in the future.
  • Most optional bootloader commands have not been tested.  Things like:
    • The CRC16-CCITT/CRC32 have not been tested.  I have only used the Basic Summation setting.
    • Dual-Application Loader
    • Verify Row Option
    • Fast Bootloader Validation
    • Security Key
    • Get Metadata Option

OP has been updated with the latest version of the VS2019 solution.

0 Likes