|
|
View previous topic :: View next topic |
Author |
Message |
EdWaugh
Joined: 07 Dec 2004 Posts: 127 Location: Southampton, UK
|
Firmware update over RS232 |
Posted: Tue Jun 09, 2009 7:49 am |
|
|
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
|
|
Posted: Tue Sep 01, 2009 6:57 am |
|
|
Very good post Ed . Helps take some of the mystery out of bootloader code . 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
|
bootloader |
Posted: Tue Sep 01, 2009 7:12 am |
|
|
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
|
|
Posted: Wed Sep 02, 2009 6:05 am |
|
|
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
|
|
Posted: Wed Sep 02, 2009 6:58 am |
|
|
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
|
Re: Firmware update over RS232 |
Posted: Tue Oct 20, 2009 11:34 am |
|
|
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
|
hi |
Posted: Tue Oct 20, 2009 12:17 pm |
|
|
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
|
Re: hi |
Posted: Tue Oct 20, 2009 12:58 pm |
|
|
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
|
|
Posted: Tue Oct 20, 2009 1:22 pm |
|
|
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
|
|
Posted: Thu Oct 22, 2009 9:04 am |
|
|
hi,
do you use SIOW.exe to download firmware or other software ? |
|
|
EdWaugh
Joined: 07 Dec 2004 Posts: 127 Location: Southampton, UK
|
|
Posted: Thu Oct 22, 2009 9:30 am |
|
|
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
|
|
Posted: Fri Oct 23, 2009 1:17 am |
|
|
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
|
|
Posted: Fri Oct 23, 2009 1:40 am |
|
|
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
|
|
Posted: Fri Oct 23, 2009 1:42 am |
|
|
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
|
|
Posted: Fri Oct 23, 2009 2:04 am |
|
|
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. |
|
|
|
|
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
|