CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

Firmware update over RS232
Goto page 1, 2  Next
 
Post new topic   Reply to topic    CCS Forum Index -> Code Library
View previous topic :: View next topic  
Author Message
EdWaugh



Joined: 07 Dec 2004
Posts: 127
Location: Southampton, UK

View user's profile Send private message

Firmware update over RS232
PostPosted: Tue Jun 09, 2009 7:49 am     Reply with quote

Hi,

I have developed a modified version of the CCS loader.c which is designed to be called by the user application, it has loads of comments and the output is very verbose making it easy to understand. This does mean it takes a lot of ROM but I'm not concerned about this in my application.

There is more info here:

http://www.ccsinfo.com/forum/viewtopic.php?t=39144

cheers

ed



Code:

/*! \file firmware_updater.c
*
* Downloads firmware over RS232 and writes it to flash
*
* Based on the CCS supplied example loader.c, modified to only work
* for 18 series
*
* After each good line, the loader sends an ACKLOD character.  The
* driver uses XON/XOFF flow control.  Also, any buffer on the PC 
* UART must be turned off, or to its lowest setting, otherwise it
* will miss data. 
*
* \author Ed Waugh
* \date 09/06/2009
*/

// Some useful debugging lines
//fprintf(SENSM_STRM,"Loader End 0x%lx, Loader Size 0x%lx, Loader Addr 0x%lx\r", LOADER_END, LOADER_SIZE, LOADER_ADDR);
//fprintf(SENSM_STRM,"Flash erase size 0x%lx, Flash write size 0x%lx\r", getenv("FLASH_ERASE_SIZE"), getenv("FLASH_WRITE_SIZE"));

// Define the size of the loader in ROM and the address to write it to
#ifndef LOADER_END
   #define LOADER_END   getenv("PROGRAM_MEMORY")-1                         ///< Get the end of the program memory and put the loader there
   #define LOADER_SIZE   0x7FF                                              ///< Size of the loader functions
#endif
#define LOADER_ADDR     ( LOADER_END - LOADER_SIZE )                       ///< Address of the loader

// Set all the functions following this directive to be included in the
// loader ROM area
#pragma ORG LOADER_ADDR+10, LOADER_END default

// Serial port stream specific to this area to make the compiler create
// specific specific serial functions stored in the #ORG
#pragma use rs232(baud=115200, parity=N, UART2, bits=8, stream=LOADER_STRM, RESTART_WDT)

// Definitions
#define BUFFER_LEN_LOD  64                                                 ///< Length of a line in an Intel 8-bit hex file
#define ACKLOD 0x06                                                        ///< Acknowledge the last line
#define XON    0x11                                                        ///< Turn transmission on
#define XOFF   0x13                                                        ///< Turn transmission off

//******************************************************************************
/// Convert two hex chars to a byte
/*!
* \param[in] s String 2 chars long
* \return Byte value from hex
*/
#pragma SEPARATE                                                           // The SEPARATE directive tells the compiler not to inline this function, this reduces the ROM space required
uint8_t atoi_b16(char_t *s)

   uint8_t result = 0;
   uint8_t i;

   for (i=0; i<2; i++,s++)
   {
      if (*s >= 'A')
      {
         result = 16*result + (*s) - 'A' + 10;
      }
      else
      {
         result = 16*result + (*s) - '0';
      }
   }

   return(result);
}

//******************************************************************************
/// Copy of the string compare function
/*!
* This does not get inlined by the compiler so I have made a copy of the CCS supplied
* library function that gets included in the #org section
* \param[in] s1 Pointer to the first string
* \param[in] s2 Pointer to the second string
* \param[in] n Number of characters to compare
* \return 0 for equal, negative or positive for not equal
*/
int8_t ldr_strncmp(char_t *s1, char_t *s2, uint8_t n)
{
   for (; n > 0; s1++, s2++, n--)
   {
      if (*s1 != *s2) return((*s1 <*s2) ? -1: 1);
      else if (*s1 == '\0') return(0);
   }
   return(0);
}

//******************************************************************************
/// The firmware loader
/*!
* Real load function could be sat anywhere inside the #org area
*/
void real_load_program (void)
{
   uint1_t  do_ACKLOD, done=FALSE;
   uint8_t  checksum, line_type, dataidx, i, count, buffidx;
   uint16_t l_addr, h_addr=0;
   uint32_t addr;
   
   // Buffers
   uint8_t  data[32];
   uint8_t buffer[BUFFER_LEN_LOD];

   // Only required for parts where the flash erase and write sizes are different
   #if (getenv("FLASH_ERASE_SIZE") > getenv("FLASH_WRITE_SIZE"))
      uint32_t next_addr = 0;
   #endif

   while (!done)  // Loop until the entire program is downloaded
   {
      buffidx = 0;  // Read into the buffer until 0x0D ('\r') is received or the buffer is full
      do
      {
         buffer[buffidx] = fgetc(LOADER_STRM);
      } while ( (buffer[buffidx++] != 0x0D) && (buffidx <= BUFFER_LEN_LOD) );

      fputc(XOFF, LOADER_STRM);                                                  // Suspend sender
      do_ACKLOD = TRUE;                                                          // Flag to indicate this is a sentence we should acknowledge

      // Only process data blocks that start with ':'
      if (buffer[0] == ':')
      {
         count = atoi_b16(&buffer[1]);                                           // Get the number of bytes from the buffer
         l_addr = make16(atoi_b16(&buffer[3]),atoi_b16(&buffer[5]));             // Get the lower 16 bits of address
         line_type = atoi_b16(&buffer[7]);                                       // Get the line type code from the string
         addr = make32(h_addr,l_addr);                                           // At the first time through h_addr is zero as we are assuming the high bytes of the addr are zero until we get a type 4 command

         if (line_type == 1)                                                     // If the line type is 1, then data is done being sent
         {
            done = TRUE;
            fprintf(LOADER_STRM,"DN\r");
            do_ACKLOD = FALSE;
         }
         else if ((addr < LOADER_ADDR || addr > LOADER_END) && addr < 0x300000)  // Don't try to overwrite the loader
         {
            checksum = 0;                                                        // Sum the bytes to find the check sum value
            for (i=1; i<(buffidx-3); i+=2)
            {
               checksum += atoi_b16 (&buffer[i]);
            }
            checksum = 0xFF - checksum + 1;

            if (checksum != atoi_b16 (&buffer[buffidx-3]))
            {
               fprintf(LOADER_STRM,"CS\r");                                      // Test the CheckSum and report failure
               do_ACKLOD = FALSE;
            }
            else
            {
               if (line_type == 0)
               {
                  // Loops through all of the data and stores it in data
                  // The last 2 bytes are the check sum, hence buffidx-3
                  for (i = 9,dataidx=0; i < buffidx-3; i += 2)
                  {
                     data[dataidx++] = atoi_b16(&buffer[i]);
                  }

                  #if (getenv("FLASH_ERASE_SIZE") > getenv("FLASH_WRITE_SIZE"))
                     fprintf(LOADER_STRM,"ES\r");
                     if ((addr!=next_addr)&&(addr&(getenv("FLASH_ERASE_SIZE")/2-1)!=0))
                     {
                        erase_program_eeprom(addr);
                     }
                     next_addr = addr + 1;
                  #endif

                  fprintf(LOADER_STRM,"WR, 0x%lx, %u, ", addr, count);
                  write_program_memory(addr, data, count);                             // Attempt a write to the program memory

                  read_program_memory(addr, buffer, count);                            // Read the program memory we just wrote into the incoming string buffer to avoid having two data buffers
                  if( ldr_strncmp(data, buffer, count) == 0) fprintf(LOADER_STRM,"ACK\r"); // Test the data data
                  else fprintf(LOADER_STRM,"NACK\r");
                  do_ACKLOD = FALSE;
               }
               else if (line_type == 4)
               {
                  h_addr = make16(atoi_b16(&buffer[9]), atoi_b16(&buffer[11]));
                  fprintf(LOADER_STRM,"HA, 0x%x\r", h_addr);
                  do_ACKLOD = FALSE;
               }
            }
         }
      }

      if (do_ACKLOD)                                                       // Only do this for sentences we have not already responded to
      {
         fputc(ACKLOD, LOADER_STRM);
      }
      fputc(XON, LOADER_STRM);                                             // Renable transmission from the terminal program
      restart_wdt();
   }

   fputc(ACKLOD, LOADER_STRM);
   fputc(XON, LOADER_STRM);

   reset_cpu();                                                            // After writing a new program we always want to reset the CPU
}

// This #ORG ends the section holding the loader (default causes all functions within
// the declaration to be put in the ROM section)
#pragma ORG default

//******************************************************************************
/// Stub load function
/*!
* Set a stub function at a specific address so we can jump to it by changing the PC
* We must always use this as the new application version that overwrites the code won't
* necessarily have the same layout
*/
#pragma ORG LOADER_ADDR, LOADER_ADDR+9
void load_program(void)
{
   real_load_program();
}
Herbert



Joined: 20 Jul 2008
Posts: 32
Location: Brisbane, Australia

View user's profile Send private message

PostPosted: Tue Sep 01, 2009 6:57 am     Reply with quote

Very good post Ed Very Happy . Helps take some of the mystery out of bootloader code Mad . To do the jump from the main application (the one that is loaded by the bootloader) back into the bootloader, I can see the way that may work is as follows -

1. Take all the stuff in your bootloader code associated with ORG LOADER_ADDR and place it in a .H file (which you would then include as part of your bootloader code).

2. Include this .H file in the main application source code.

3. Use the function goto_address( ORG LOADER_ADDR ), to the do the jump back into the bootloader from the main application.

Is this the sensible way to go? Or is there a better approach?
EdWaugh



Joined: 07 Dec 2004
Posts: 127
Location: Southampton, UK

View user's profile Send private message

bootloader
PostPosted: Tue Sep 01, 2009 7:12 am     Reply with quote

Hi Herbert,

You actually don't need to worry about this. The reset cpu instruction at the end of the loader starts the code off again from the beginning. Jumping back might be a problem as the address to jump to could change in your new program.

I guess this might be useful if you were running a kind of OS that allowed you to write program memory for 'applications' that could then be run. As long as the OS code was fixed then I don't see why your idea wouldn't work. I would be tempted to stick with the restart anyway, it's fast and easy...

Glad my post helped, I hope I understood your question and this answer is useful.

Ed
Herbert



Joined: 20 Jul 2008
Posts: 32
Location: Brisbane, Australia

View user's profile Send private message

PostPosted: Wed Sep 02, 2009 6:05 am     Reply with quote

Hi Ed,

yes I didn't make myself clear enough. In my main application I will have the ability to jump to the bootloader should I need to update the code. So with the correct set of inputs from the user, the main application goes into the bootloader and does an update. So to get from the main application to the bootloader, I need to jump to a known start address in the bootloader. Thats the rationale behind what I was proposing.

Cheers

Herbert
EdWaugh



Joined: 07 Dec 2004
Posts: 127
Location: Southampton, UK

View user's profile Send private message

PostPosted: Wed Sep 02, 2009 6:58 am     Reply with quote

Hi Herbert,

Yes this is already handled for you, the load_program function gets placed at a specific address and then calls the real_load_program function, just call load_program and you should have no problems.

Cheers

ed
evsource



Joined: 21 Nov 2006
Posts: 129

View user's profile Send private message

Re: Firmware update over RS232
PostPosted: Tue Oct 20, 2009 11:34 am     Reply with quote

EdWaugh wrote:
Hi,

I have developed a modified version of the CCS loader.c which is designed to be called by the user application, it has loads of comments and the output is very verbose making it easy to understand. This does mean it takes a lot of ROM but I'm not concerned about this in my application.

There is more info here:

http://www.ccsinfo.com/forum/viewtopic.php?t=39144

cheers

ed



Hi Ed,

I copied your code exactly, and am trying to compile with both versions 4.030 and 4.092. Both bark about "expecting a (" around line 49. I'm just using a basic program that calls the load_program() function.

Anyone else able to compile the code without problems?
EdWaugh



Joined: 07 Dec 2004
Posts: 127
Location: Southampton, UK

View user's profile Send private message

hi
PostPosted: Tue Oct 20, 2009 12:17 pm     Reply with quote

hi,

I'm not sure what this is, it is a syntax error tho so it shouldn't be too hard to track down. It's possible the copy paste caused some bits to be spread across multiple lines where they shouldn't be and that could be a problem. Otherwise it might be to do with the RS232 line settings not being supported on the part you are using.

Cheers

Ed
evsource



Joined: 21 Nov 2006
Posts: 129

View user's profile Send private message

Re: hi
PostPosted: Tue Oct 20, 2009 12:58 pm     Reply with quote

EdWaugh wrote:
hi,

I'm not sure what this is, it is a syntax error tho so it shouldn't be too hard to track down. It's possible the copy paste caused some bits to be spread across multiple lines where they shouldn't be and that could be a problem. Otherwise it might be to do with the RS232 line settings not being supported on the part you are using.

Cheers

Ed


What in the world are the _t at the end of the variables? I did a web search for "uint1_t", and this CCS forum post was like #4 in the search results. I think the compiler is hanging on this line:

uint8_t atoi_b16(char_t *s)

If I comment out that whole function, the next place it has problems is on this line:

int8_t ldr_strncmp(char_t *s1, char_t *s2, uint8_t n)

If I comment out that, the next place it has problems is on the declaration of "uint1_t".

Thanks in advance.
EdWaugh



Joined: 07 Dec 2004
Posts: 127
Location: Southampton, UK

View user's profile Send private message

PostPosted: Tue Oct 20, 2009 1:22 pm     Reply with quote

Hi Ryan,

This is because I use MISRA standard typedefs for all my variables, it's a good point that I don't supply them in my code. The _t in the definition indicates that it is a defined type, I would say it is good practice to do this for all defined types. My typedefs file is below, if you use my stuff please keep my name and email address in the comments.

Code:

/*! \file typedefs.h
*
* Shorthand for common type definitions
*
* These are based on the MISRA types and should always be used in place of the
* built in types as they offer improved portability and are more concise.
*
* const and static are not considered common but
* can be added to these defs when used
*
* \author Ed Waugh
* \date 27/12/2008
*/
#ifndef __typedef_h

   #define __typedef_h

   //#define FALSE 0
   //#define TRUE 1

   #define I2C_ACK            1                                               ///< This is part of the I2C spec for an acknowledge on a bus read
   #define I2C_NOACK          0                                               ///< This is part of the I2C spec for a not-acknowledge on a bus read

   #define SI32_MAX           2147483647                                      ///< Maximum positive value in a signed int32
   #define SI32_MIN           -2147483647                                     ///< Minimum positive value in a signed int32

   typedef char               char_t;                                         ///< Character is treated as an 8 bit unsigned int although technically it is a signed 7 bit type
   typedef unsigned int8      bool_t;                                         ///< Booleans are 8 bit unsigned although with the PICs bit level instructions it may be as fast to use int1 and this would reduce RAM requirements

   typedef signed int8        int8_t;                                         ///< Signed 8 bit integer -127 -> 127
   typedef unsigned int8      uint8_t;                                        ///< Unsigned 8 bit integer 0 -> 255

   typedef signed int16       int16_t;                                        ///< Signed 16 bit integer -32767 -> 32767
   typedef unsigned int16     uint16_t;                                       ///< Unsigned 16 bit integer 0 -> 65535

   typedef signed int32       int32_t;                                        ///< Signed 32 bit integer -2147483647 -> 2147483647
   typedef unsigned int32     uint32_t;                                       ///< Unsigned 32 bit integer 4294967296

   typedef float              float32_t;                                      ///< Floating point 32 bit number, avoid for high frequency computation using floats

#endif //__typedef_h
Fabri



Joined: 22 Aug 2005
Posts: 275

View user's profile Send private message

PostPosted: Thu Oct 22, 2009 9:04 am     Reply with quote

hi,

do you use SIOW.exe to download firmware or other software ?
EdWaugh



Joined: 07 Dec 2004
Posts: 127
Location: Southampton, UK

View user's profile Send private message

PostPosted: Thu Oct 22, 2009 9:30 am     Reply with quote

SIOW should be fine but you need to enable Xon Xoff flow control for it to work. I normally use Hyperterminal.

ed
Fabri



Joined: 22 Aug 2005
Posts: 275

View user's profile Send private message

PostPosted: Fri Oct 23, 2009 1:17 am     Reply with quote

Hi Ed,

With hyperterminal the bootloader work really fine. Till now I never used bootloader because every time I upgrade firmware by myself, without give file to my customer. Actually I thinking to introduce in my firmware the possibility of download upgrade by serial port and let customer to do so by himself. In any case I don't want he use my firmware to program new device with programmer but only upgrade my previus release. Does it possible ?
evsource



Joined: 21 Nov 2006
Posts: 129

View user's profile Send private message

PostPosted: Fri Oct 23, 2009 1:40 am     Reply with quote

Ed and I had some private messages that were exchanged, and some useful information came out of it. I'm going to copy and paste them here for the potential benefit of others:

Quote:
Hi Ed,

Thanks for answering my questions on the forum. Just another quick one (hopefully). Do you know what the implications might be of trying to use another method (CANbus) of transmitting the data instead of RS-232? I'm assuming if I follow the guidelines you shared about making sure the loader code doesn't stray outside of its bounds, then everything should be fine.

I see the line: #define LOADER_SIZE 0x7FF

How would you suggest determining how much space the loader stuff will consume? (probably a dumb question, but I really don't know how I'd go about doing that).

Thanks,

Ryan


Quote:

Hi Ryan,

I don't think there would be any problem with using CAN to do the upload. All you need to do is make sure any receiving routines you need to talk to the CAN interface have a copy inside the #ORG directive in the firmware file. This means your code will carry two copies of those functions. You will need to increase that loader size variable to make it fit as you suggest.

If space was a problem you could reference the CAN routines in the firmware loader from the rest of your code using the same technique as I do with the loader() function. However, when loading over RS232 (or CAN) the firmware loader functions cannot be updated, they remain fixed after the first time you program them with a real programmer. If you are developing your routines still this is a hassle so you would be better off having a simple routine in the firmware loader and maybe a more complex one with whatever comms stuff you need in the rest of your code.

Cheers

Ed


Quote:

Ed,

Thanks so much for taking the time to answer me. I'll try not to be a pest, and just ask a couple more questions. What you've said so far clears up a lot.

One thing I'm still a little fuzzy on - you say the only time the loader functions will be placed in ROM when the program is loaded with the hardware programmer. If subsequent code (to be loaded with the comm link) has the loader code #include'd, what will become of it? Will the loader code already now resident on the chip just ignore that?

Thanks again,

Ryan


Quote:
Hi Ryan,

Yes if you look at the loader code it deliberately avoids overwriting itself using the LOADER_SIZE include. This is because the PIC fetches instructions directly from the flash, so if you tried to reprogram the firmware loader while running it you might go to fetch the next instruction only to find you just rewrote it with something different! This is a crash and leaves the firmware loader in a half rewritten state that no longer works.

You have to write the firmware loader (and if you want the rest of the code) with a hardware programmer and then avoid writing over the loader when you use it to reprogram. This shouldn't matter too much as you will hopefully sort it early in the development and it won't change. It does mean that any functions it needs must not move when the application code is reprogrammed, for example with fputc(), as the loader would try and jump to an instruction that is now not where it expects it to be (crash).

So you have to either keep the only copy of the common functions stored in the protected #org of the loader and reference them from the main code or you make a new copy of the functions for the loader, which is more ROM but means your application code does not depend on something that is 'fixed'.

Could you copy these answers into the thread? I think they might help others.

Cheers

ed


Quote:
Hi Ed,

Okay, this makes much more sense now. I actually figured out the part about not overwriting the loader code just after I asked about it. But your clarification helps more.

You mention having a copy of the functions used, in the #org statement. Doesn't the keyword "default" do just that? From the help file, "If the keyword DEFAULT is used then this address range is used for all functions user and compiler generated from this point in the file until a #ORG DEFAULT is encountered (no address range). If a compiler function is called from the generated code while DEFAULT is in effect the compiler generates a new version of the function within the specified address range."

Is that not the case?

Yes, I can copy our conversation into the forum thread - it will certainly benefit someone else too. I should have kept it there in the first place.

Thanks again,

Ryan

Quote:

Hi Ryan,

Yes you're right about the default flag for built in functions, but any of your functions like decode_CAN_message_and_test_CRC() will need to be added by hand as a copy, like I do with ldr_strncmp() in my code.

Cheers

Ed


Quote:
What if I declare them #inline? Will that take care of that problem?

-Ryan


Quote:
I hadn't considered this before but it is possible except of course you could end up with two different versions of a function on a device, the one programmed with the loader and the newer one added with the application code, this might not be a technical problem but could be confusing.

Also, using #inline would not save you any ROM space you would still end up with at least two copies and maybe more if you call the same function multiple times from within the loader. #inline is also usually more of a suggestion to the compiler and it might ignore you if it thinks it knows better (at least sophisticated optimising compilers do).

I realise that having two copies seems a little inelegant but my conclusion was that it was easier especially when someone else might need to look at your code and understand it...
evsource



Joined: 21 Nov 2006
Posts: 129

View user's profile Send private message

PostPosted: Fri Oct 23, 2009 1:42 am     Reply with quote

Now for a new question:

Things are working great - a very small program loads up without any problems. However, a large program halts the upload at program memory location 0xF0. Anything specific that generally happens there?
evsource



Joined: 21 Nov 2006
Posts: 129

View user's profile Send private message

PostPosted: Fri Oct 23, 2009 2:04 am     Reply with quote

evsource wrote:
Now for a new question:

Things are working great - a very small program loads up without any problems. However, a large program halts the upload at program memory location 0xF0. Anything specific that generally happens there?


I took a look at the list file, and it appears the RS-232 stuff happens there. I commented out all printf's and putc's in the loader code section, and now it works perfectly, even on a large program.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> Code Library All times are GMT - 6 Hours
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group