CANopenNode / CANopenNode

CANopen protocol stack

Home Page:https://canopennode.github.io/CANopenNode/index.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CANOpen firmware update

geoffrey-vl opened this issue · comments

One of the specific features I've been looking at is that of applying firmware updates.
There are some ways to do that. Most imply having a separated bootloader and application. The bootloader's task is that it verifies applies the firmware update. Apply the bootloader is either enabling another partition, or effectively writing the new application over the older one.
Sometimes the application fetches the firmware update candidate, sometimes that task is up to the bootloader using some kind of minimal CANOpen implementation.
Both have their pro's and con's but that is not what I would like to discuss.

I've noticed the CiA paper 814:

Implementation and user guideline for CiA 417 - part 1: bootloader

Part of the paper describes from a user point of view the functionality of applying fwupdates over CANOpen through the bootloader. So some question come to mind:

  • I'm wondering how 'default' this fwupdate principle actually is?
  • Does everybody do it that way, i.e. is it standardized?
  • Or is it just some sort of recommendation, which the document has applied through means of giving a good example?
  • As far as I know, CANOpenNode doesn't include such feature. How could it be introduced? Should it follow any standardization?

I can't really speak to your first point since I have not worked extensively with any bootloaders. But CiA 814 seems to just define a few common sense checks like "make sure you are writing the the device you think you are"

After skimming CiA 417 I do not think it is merely a recommendation. CiA 417 specifically states when something is out of the scope of the document and up to the end user (like the node-ID assignment section). That logically supports the rest of the document, and by extension CiA 814, being not just a recommendation. If I was to pick a standard to follow it would certainly be the one suggested by CiA.

As to your last point, I think adding a bootloader feature to CANopenNode would be out of the scope of the project. It would make more sense for an existing bootloader project to support CANopen, and thus use CANopenNode.

Thanks for the heads up.

Regarding the latter, that's correct, I would think the same, off course its not about simply adding CANOpenNode to an existing bootloader. Most of the time you would want a minimal version of that stack as the bootloader is always kept very compact. Because there is also the application and it all has to fit you internal flash. The binary size optimizations is just one thing though, which would not always be required. What puzzles me the most is what is required to 'download' the firmware. What is already in the stack, and what needs to be added. The latter is the most important, since you could go with whatever bootloader you'd want: either one with minimal CANopen stack, or an even more basic bootloader that does only the verification / flashing.

Hi,

Firmware update is specified in DSP 302-3. This is quite comprehensive and gives a flowchart how updating is supposed to work and the OD entries you have to implement. 0x1F50/0x01 is just one of them.

CiA 814/417 is for lifts, so you can't rely on that to be implemented in anything else.

I've used CANopenNode for our bootloader too, but with everything removed except what ist absolute necessary and SDO block download (to make it faster...). This way, I was able to get the entire bootloader with flashing and some features in ~12kB STM32/GCC ARM.

@martinwag I'm curious about how the node IDs, baud settings, etc. are handled for the bootloader--is there a completely separate object dictionary for the bootloader? I haven't had a chance to try it out yet, but one of the major limitations that I've noticed with the stock STM32 CAN bootloader is that it only supports one node at a time.

It seems like the ideal solution from the end-application perspective would be for the bootloader to use the same configuration settings as the application for the baud,node ID, etc. so that you could update FW after the device is in a completed system (possibly with multiple other devices with the same CANopenNode bootloader). Can't help but feel a bit uneasy though blurring the lines between the bootloader and application code.

Putting the device into bootloader mode and changing the settings via SDO is an option, but that adds a process step that wouldn't always be necessary (and thus easy to overlook). Anything you can share on this would be appreciated.

In general, having a standardized/template for a CANopenNode bootloader implementation that could be tailored for individual platforms would be a great resource.

Hi,

Yes, I have a completely separate OD for the bootloader. This way, I can ensure that all our devices share the same OD inside bootlaoder (actually the entire bootloader...) and distributing/preprogramming/updating the bootloader is simple.

You already need to pass the information "bootloader requested" from your firmware to bootloader. Just add node ID, baud rate and so on to that block.
I reccomend you to add a way to set node ID and/or baud rate in bootloader nevertheless, in case you can't boot your firmware (CRC error, download error, not loaded yet :-), ... ).

Thanks for the feedback. In between other things I've had some time to experiment with object 1F50h.
To do a program download I've added following things to the stack:

diff --git a/stack/CO_SDO.c b/stack/CO_SDO.c
index 531168f..a289f98 100644
--- a/stack/CO_SDO.c
+++ b/stack/CO_SDO.c
@@ -288,6 +288,53 @@ static CO_SDO_abortCode_t CO_ODF_1200(CO_ODF_arg_t *ODF_arg){
 }


+
+/*
+ * Function for accessing _SDO program download (index 1f50).
+ *
+ * When master sends more bytes to this object than defined in
+ * CO_SDO_BUFFER_SIZE, multiple segments are received by this node.
+ * For example, sending 38 bytes results in the following segments:
+ * RX segment 1: datalength=28, first=true, last=false, offset=0, dataLengthTotal=38
+ * RX segment 2: datalength=10, first=false, last=true, offset=28, dataLengthTotal=38
+
+ * For more information see file CO_SDO.h.
+ */
+static CO_SDO_abortCode_t CO_ODF_1F50(CO_ODF_arg_t *ODF_arg);
+static CO_SDO_abortCode_t CO_ODF_1F50(CO_ODF_arg_t *ODF_arg){
+    CO_SDO_abortCode_t ret = CO_SDO_AB_NONE;
+    static uint8_t progbuff[1024];
+
+    // allow on subindex 1 only
+    if(ODF_arg->subIndex != 0x01)
+        return ret;
+    //only dowloading is supported
+    if(ODF_arg->reading)
+        return ret;
+
+    if(ODF_arg->firstSegment) {
+        //download started
+        printf("FW Download started\n");
+        memset(progbuff, 0, sizeof(progbuff));
+    }
+
+    //copy into buffer
+    printf("copying data (datalength=%LU, first=%s, last=%s, offset=%LU, dataLengthTotal=%LU) \n", ODF_arg->dataLength, (ODF_arg->firstSegment ? "true":"false"), (ODF_arg->lastSegment ? "true":"false"), ODF_arg->offset, ODF_arg->dataLengthTotal);
+    for(int i=0; i<ODF_arg->dataLength; i++) {
+        uint16_t offset = ODF_arg->offset + i;
+        progbuff[offset] = ODF_arg->data[i];
+    }
+
+    if(ODF_arg->lastSegment) {
+        //download finished
+        printf("FW Download finished\n");
+        printf("FW = %s\n", (char *)progbuff);
+    }
+
+    return ret;
+}
+
+
 /******************************************************************************/
 CO_ReturnError_t CO_SDO_init(
         CO_SDO_t               *SDO,
@@ -343,6 +390,7 @@ CO_ReturnError_t CO_SDO_init(
     /* Configure Object dictionary entry at index 0x1200 */
     if(ObjDictIndex_SDOServerParameter == OD_H1200_SDO_SERVER_PARAM){
         CO_OD_configure(SDO, ObjDictIndex_SDOServerParameter, CO_ODF_1200, (void*)&SDO->nodeId, 0U, 0U);
+        CO_OD_configure(SDO, OD_H1F50_PRGRMDWNLOAD_OBJECT, CO_ODF_1F50, 0U, 0U, 0U);
     }

     if((COB_IDClientToServer & 0x80000000) != 0 || (COB_IDServerToClient & 0x80000000) != 0 ){
diff --git a/stack/CO_SDO.h b/stack/CO_SDO.h
index 33dc42c..eb0429b 100644
--- a/stack/CO_SDO.h
+++ b/stack/CO_SDO.h
@@ -434,7 +434,8 @@ typedef enum{
     OD_H1A00_TXPDO_1_MAPPING      = 0x1A00U,/**< TXPDO mapping parameters */
     OD_H1A01_TXPDO_2_MAPPING      = 0x1A01U,/**< TXPDO mapping parameters */
     OD_H1A02_TXPDO_3_MAPPING      = 0x1A02U,/**< TXPDO mapping parameters */
-    OD_H1A03_TXPDO_4_MAPPING      = 0x1A03U /**< TXPDO mapping parameters */
+    OD_H1A03_TXPDO_4_MAPPING      = 0x1A03U,/**< TXPDO mapping parameters */
+    OD_H1F50_PRGRMDWNLOAD_OBJECT  = 0x1F50U /**< Program download object */
 }CO_ObjDicId_t;

The OD has following changes:

diff --git a/CO_OD.c b/CO_OD.c
index 231a7be..dd3f0c4 100644
--- a/CO_OD.c
+++ b/CO_OD.c
@@ -135,6 +135,7 @@ struct sCO_OD_EEPROM CO_OD_EEPROM = {
 /*1A02*/ {0x0, 0x0L, 0x0L, 0x0L, 0x0L, 0x0L, 0x0L, 0x0L, 0x0L},
 /*1A03*/ {0x0, 0x0L, 0x0L, 0x0L, 0x0L, 0x0L, 0x0L, 0x0L, 0x0L}},
 //
+/*1F50*/ {0x1, 0x0},
 /*1F80*/ 0x04L, //no auto startup of slave node
 /*2101*/ CANOPEN_NODE_ID,
 /*2102*/ 0xFA,
@@ -285,13 +286,16 @@ struct sCO_OD_EEPROM CO_OD_EEPROM = {
            {(void*)&CO_OD_ROM.TPDOMappingParameter[3].mappedObject6, 0x8D,  4},
            {(void*)&CO_OD_ROM.TPDOMappingParameter[3].mappedObject7, 0x8D,  4},
            {(void*)&CO_OD_ROM.TPDOMappingParameter[3].mappedObject8, 0x8D,  4}};
+/*0x1F50*/ const CO_OD_entryRecord_t OD_record1F50[2] = {
+           {(void*)&CO_OD_ROM.ProgramDownload.maxSubIndex, 0x06,  1},
+           {0, 0x0E, 0}};
 /*0x2120*/ const CO_OD_entryRecord_t OD_record2120[6] = {
            {(void*)&CO_OD_RAM.testVar.maxSubIndex, 0x06,  1},
            {(void*)&CO_OD_RAM.testVar.I64, 0xBE,  8},
@@ -339,6 +343,7 @@ const CO_OD_entry_t CO_OD[CO_OD_NoOfElements] = {
 {0x1A01, 0x08, 0x00,  0, (void*)&OD_record1A01},
 {0x1A02, 0x08, 0x00,  0, (void*)&OD_record1A02},
 {0x1A03, 0x08, 0x00,  0, (void*)&OD_record1A03},
+{0x1F50, 0x02, 0x00,  0, (void*)&OD_record1F50},
 {0x1F80, 0x00, 0x8D,  4, (void*)&CO_OD_ROM.NMTStartup},
 {0x2100, 0x00, 0x36, 10, (void*)&CO_OD_RAM.errorStatusBits[0]},
 {0x2101, 0x00, 0x0D,  1, (void*)&CO_OD_ROM.CANNodeID},
diff --git a/CO_OD.h b/CO_OD.h
index 6ae91e3..8dc21ae 100644
--- a/CO_OD.h
+++ b/CO_OD.h
@@ -80,7 +80,7 @@
 *******************************************************************************/
    #define CO_NO_SYNC                     1   //Associated objects: 1005, 1006, 1007, 2103, 2104
    #define CO_NO_EMERGENCY                1   //Associated objects: 1014, 1015
-   #define CO_NO_SDO_SERVER               1   //Associated objects: 1200
+   #define CO_NO_SDO_SERVER               1   //Associated objects: 1200, 1F50
    #define CO_NO_SDO_CLIENT               0
    #define CO_NO_RPDO                     4   //Associated objects: 1400, 1401, 1402, 1403, 1600, 1601, 1602, 1603
    #define CO_NO_TPDO                     4   //Associated objects: 1800, 1801, 1802, 1803, 1A00, 1A01, 1A02, 1A03
@@ -92,7 +92,7 @@
 /*******************************************************************************
    OBJECT DICTIONARY
 *******************************************************************************/
-   #define CO_OD_NoOfElements             55
+   #define CO_OD_NoOfElements             56


 /*******************************************************************************
@@ -152,6 +152,11 @@
                uint32_t     mappedObject8;
                }              OD_TPDOMappingParameter_t;

+/*1F50[1]   */ typedef struct{
+               uint8_t      maxSubIndex;
+               domain_t         domain_t;
+               }              OD_ProgramDownload_t;
+
 /*2120      */ typedef struct{
                uint8_t      maxSubIndex;
                int64_t      I64;
@@ -234,6 +239,7 @@ struct sCO_OD_ROM{
 /*1600[4]   */ OD_RPDOMappingParameter_t RPDOMappingParameter[4];
 /*1800[4]   */ OD_TPDOCommunicationParameter_t TPDOCommunicationParameter[4];
 /*1A00[4]   */ OD_TPDOMappingParameter_t TPDOMappingParameter[4];
+/*1F50      */ OD_ProgramDownload_t ProgramDownload;
 /*1F80      */ uint32_t     NMTStartup;
 /*2101      */ uint8_t      CANNodeID;
 /*2102      */ uint16_t     CANBitRate;

This way I can download 1024 bytes:

./canopencomm 0x0A w 0x1f50 1 d 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A

The download handler implementation is just for testing purposes.
In the end people always would need to implement their own handler depending on the storage system they have available. I guess what CANopenNode can do is making sure that when the object is created within the OD, it is also being picked up by the stack. The download handler implementation however would need to be a callback or some sort of thing outside of the stack. In that case users only need to hook up their handler to get the feature without adjusting the stack.

In my case the bootloader only swaps applications, the downloading is handled by the application.

In the end I've completely moved the implementation from CANopenNode library into the application as it currently makes good sense. The data handling and writing to storage device is just very application specific.

My demo application can be found here.

In my case I'll use a generic bootloader that handles the new firmware file, instead of creating a CANOpen bootloader. The demo application auto restarts after the download has finished. As I have noticed in CiA814, the flow is a bit different in that master decides to reboot the node. My final application will try to do that.

I am aware this is an old and closed thread.
Would it be possible for someone to please assist me with porting this to the latest CANOPENNODE_V4, in particular the 0x1F50 object with newer OD Editor (v4.0-51-g2d9b1ad)? I am new to CANopen and still finding my way around. Thanks

commented

I don't have experience with bootloader, but I think, this may be relevant from v4:
https://github.com/CANopenNode/CANopenDemo/blob/master/demo/domainDemo.c

I think I see how to do this with the domain example.
But I am unsure how to get the SDO client working correctly. How does the target node SDO Server ID (0x1280 sub index 3) get added to the client to server COB ID (0x1280 sub index 1) when CO_SDOclientDownload() is called?
or must the COB ID client to server be configured in the OD editor directly? For example should I make it 0x601 (for node ID 1)?

commented

You can use cocomm, where SDO client is already implemented. If you like to make own SDO client - that is advanced topic...