main.c - PIC Bootloader core code.¶
/** This has been modified from the original source
provided by Microchip. It now works with both the PIC24H
and PIC24F families.
Modifications by R. Reese (reese@ece.msstate.edu).
*/
/********************************************************************
* FileName: main.c
* Dependencies:
* Processor: PIC24H Family
* Hardware: Explorer 16
* Compiler: C30 2.01
* Company: Microchip Technology, Inc.
*
* Software License Agreement
*
* The software supplied herewith by Microchip Technology Incorporated
* (the "Company") for its PICmicro (C) Microcontroller is intended and
* supplied to you, the Company's customer, for use solely and
* exclusively on Microchip PICmicro Microcontroller products. The
* software is owned by the Company and/or its supplier, and is
* protected under applicable copyright laws. All rights are reserved.
* Any use in violation of the foregoing restrictions may subject the
* user to criminal sanctions under applicable laws, as well as to
* civil liability for the breach of the terms and conditions of this
* license.
*
* THIS SOFTWARE IS PROVIDED IN AN "AS IS" CONDITION. NO WARRANTIES,
* WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED
* TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. THE COMPANY SHALL NOT,
* IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL OR
* CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
*
*********************************************************************/
/*****
BE SURE THAT YOU HAVE A LINKER FILE FOR THE TARGET PROCESSOR IN THE
CURRENT DIRECTORY WITH THE STARTING PROGRAM MEMORY LOCATION CHANGED
TO 0x400!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
******/
#include "pic24_uart-small.h"
#include "pic24_clockfreq.h"
#include "pic24_serial.h"
#include "pic24_util.h"
#define COMMAND_NACK 0x00
#define COMMAND_ACK 0x01
#define COMMAND_READ_PM 0x02
#define COMMAND_WRITE_PM 0x03
#define COMMAND_WRITE_CM 0x07
#define COMMAND_RESET 0x08
#define COMMAND_READ_ID 0x09
#define COMMAND_READ_VERSION 0x011
#define COMMAND_POR_RESET 0x13
#define PROGRAM_START 0xC00 //start of application program, do not write below this address
#define VERSION_MAJOR 0x03 //this cannot be zero!
#define VERSION_MINOR 0x00
#if defined(__PIC24FK__)
#define INSTR_PER_ROW 32 //number of instructions per row
#define PM_ROW_SIZE INSTR_PER_ROW * 4 //maximum instructions transferred to RAM for programming
#define PM_ERASE_SIZE INSTR_PER_ROW*4 //can only
#elif (defined(__PIC24HJ12GP202__) || defined(__PIC24HJ12GP201__) )
//Reserved for processors with less than < 2K SRAM
this is the size of an erase page! Total byte storage will be PM_ROW_SIZE * 3 because each instruction is 3 bytes wide. A total erase page is 1536 bytes = 64*8*3. Make it half a page, or 64*4*3. Will have to detect when we cross a half page boundary and we need to do a fresh erase. If the address is evenly divisible by 1536, then do an erase.
#define INSTR_PER_ROW 64 //number of instructions per row
#define PM_ROW_SIZE INSTR_PER_ROW * 4 //number of instructions transferred to RAM to be programmed
#define PM_ERASE_SIZE INSTR_PER_ROW*8 //erases a full page, but RAM can only hold 1/2 page
#elif ( defined(__PIC24E__) || defined(__dsPIC33E__))
#define INSTR_PER_ROW 2 //number of instructions per row
#define PM_ROW_SIZE 512 //page size downloaded, will be 512 instructions
//some processors have an erase size of 512.
#if (defined(__PIC24EP32GP202__) || defined(__PIC24EP32GP203__) || defined(__PIC24EP32GP204__) )
#define PM_ERASE_SIZE 512
#elif (defined(__dsPIC33EP32GP502__) || defined(__dsPIC33EP32GP503__) || defined(__dsPIC33EP32GP504__) )
#define PM_ERASE_SIZE 512
#else
#define PM_ERASE_SIZE 1024 //default erase size.
#endif
#else
//this processor has enough memory for a full page size.
#define INSTR_PER_ROW 64 //number of instructions per row
#define PM_ROW_SIZE INSTR_PER_ROW * 8
#define PM_ERASE_SIZE INSTR_PER_ROW*8
#endif
#define CM_ROW_SIZE 12
#define CONFIG_WORD_SIZE 1
#if defined(__PIC24FK__)
//This family can only erase up to four rows instead of 8 rows, and the programming word has changed
#define PM_ROW_ERASE 0x405A //erases 4 rows
#define PM_ROW_WRITE 0x4004 //write 1 row
#elif (defined(__PIC24H__) || defined(__PIC24F__) || defined(__dsPIC33F__))
//for PIC24F, PIC24H
#define PM_ROW_ERASE 0x4042 //erase entire page (8 rows)
#define PM_ROW_WRITE 0x4001 //write 1 row
#elif (defined(__PIC24E__) || defined(__dsPIC33E__))
#define PM_ROW_ERASE 0x4003 //erase entire page
#define PM_ROW_WRITE 0x4001 //write double word
#else
#error "This family is not supported by bootloader."
#endif
#define CONFIG_WORD_WRITE 0x4000
typedef short Word16;
typedef unsigned short UWord16;
typedef long Word32;
typedef unsigned long UWord32;
typedef union tuReg32 {
UWord32 Val32;
struct {
UWord16 LW;
UWord16 HW;
} Word;
char Val[4];
} uReg32;
UWord32 ReadLatch(UWord16 addrhi, UWord16 addrlo);
void PutChar(char);
void GetChar(char *);
void WriteBuffer(char *, int);
void ReadPM(char *, uReg32);
void WritePM(char *, uReg32);
void WriteMem(UWord16 val);
void LoadAddr(UWord16 nvmadru, UWord16 nvmadr);
void WriteLatch(UWord16 addrhi, UWord16 addrlo, UWord16 wordhi, UWord16 wordlo);
void ResetDevice(void);
void ResetDeviceasPOR(void);
void Erase(UWord16 addrhi, UWord16 addrlo, UWord16 val );
#if (defined(__PIC24E__) || defined(__dsPIC33E__))
void LoadTwoWords(UWord16 addrhi, UWord16 addrlo, UWord16 wordhi, UWord16 wordlo, UWord16 word2hi, UWord16 word2lo);
void WriteMem2(UWord16 addrhi, UWord16 addrlo, UWord16 val);
#endif
This needs to be persistent so as to not step on persistent variables in the user’s program.
The * 3 is because each instruction is 3 bytes.
_PERSISTENT char Buffer[PM_ROW_SIZE*3 + 1];
Define the address at which the bootloader delay is located.
#if (defined(__PIC24E__) || defined(__dsPIC33E__))
# define DELAYADDR (0x001000L)
#else
# define DELAYADDR (0x000C00L)
#endif
int main(void) {
uReg32 Delay;
configClock();
Disable the Watch Dog Timer. The bootloader doesn’t clear the WDT during operation.
RCONbits.SWDTEN = 0;
Get the requested delay time, in seconds.
Delay.Val32 = ReadLatch(DELAYADDR >> 16, DELAYADDR & 0xFFFF);
Only enter the bootloader on a power-on reset or a master clear reset. Never enter on a software reset.
if (_SWR || (_POR == 0 && _EXTR == 0)) {
ResetDevice();
}
The UART is now needed. Set it up.
configDefaultUART(DEFAULT_BAUDRATE);
Blink the heartbeat LED.
configHeartbeat();
Configure the Timers 2-3 to generate a delay.
T2CON = 0x0000;
T3CON = 0x0000;
Use a 32-bit timer for improved resolution.
T2CONbits.T32 = 1;
Clear the Timer3 Interrupt Flag.
IFS0bits.T3IF = 0;
Disable Timer3 Interrupt Service Routine. Everything here is polling.
IEC0bits.T3IE = 0;
See if we should delay, or wait forever (0xFF delay).
if ((Delay.Val32 & 0x000000FF) != 0xFF) {
Convert seconds into timer ticks.
Delay.Val32 = ((UWord32)(FCY)) * ((UWord32)(Delay.Val[0]));
PR3 = Delay.Word.HW;
PR2 = Delay.Word.LW;
Enable the timer.
T2CONbits.TON = 1;
}
while (1) {
char Command;
GetChar(&Command);
switch (Command) {
case COMMAND_READ_PM: { /*tested*/
uReg32 SourceAddr;
GetChar(&(SourceAddr.Val[0]));
GetChar(&(SourceAddr.Val[1]));
GetChar(&(SourceAddr.Val[2]));
SourceAddr.Val[3]=0;
ReadPM(Buffer, SourceAddr);
WriteBuffer(Buffer, PM_ROW_SIZE*3);
break;
}
case COMMAND_WRITE_PM: { /* tested */
uReg32 SourceAddr;
int Size;
GetChar(&(SourceAddr.Val[0]));
GetChar(&(SourceAddr.Val[1]));
GetChar(&(SourceAddr.Val[2]));
SourceAddr.Val[3]=0;
for (Size = 0; Size < PM_ROW_SIZE*3; Size++) {
GetChar(&(Buffer[Size]));
}
if ((SourceAddr.Val32 % (PM_ERASE_SIZE*2)) == 0) {
//on page boundary, do erase.
if (SourceAddr.Val32 >= PROGRAM_START) Erase(SourceAddr.Word.HW,SourceAddr.Word.LW,PM_ROW_ERASE);
}
if (SourceAddr.Val32 >= PROGRAM_START) WritePM(Buffer, SourceAddr); /*program page */
PutChar(COMMAND_ACK); /*Send Acknowledgement */
break;
}
case COMMAND_READ_ID: {
uReg32 SourceAddr;
uReg32 Temp;
SourceAddr.Val32 = 0xFF0000;
Temp.Val32 = ReadLatch(SourceAddr.Word.HW, SourceAddr.Word.LW);
WriteBuffer(&(Temp.Val[0]), 4);
SourceAddr.Val32 = 0xFF0002;
Temp.Val32 = ReadLatch(SourceAddr.Word.HW, SourceAddr.Word.LW);
WriteBuffer(&(Temp.Val[0]), 4);
break;
}
case COMMAND_READ_VERSION: {
PutChar(VERSION_MAJOR);
PutChar(VERSION_MINOR);
PutChar(COMMAND_ACK); /*Send Acknowledgement */
break;
}
case COMMAND_POR_RESET: {
ResetDeviceasPOR();
break;
}
case COMMAND_WRITE_CM: {
int Size;
for (Size = 0; Size < CM_ROW_SIZE*3;) {
GetChar(&(Buffer[Size++]));
GetChar(&(Buffer[Size++]));
GetChar(&(Buffer[Size++]));
PutChar(COMMAND_ACK); /*Send Acknowledgement */
}
break;
}
case COMMAND_RESET: {
#if (defined (__PIC24H__) || defined(__dsPIC33F__) || defined(__PIC24FK__))
//only do this for PIC24/dsPIC33F/PIC24FK family members, PIC24F configuration
//bits are in normal program memory
uReg32 SourceAddr;
int Size;
uReg32 Temp;
for (Size = 0, SourceAddr.Val32 = 0xF80000; Size < CM_ROW_SIZE*3;
Size +=3, SourceAddr.Val32 += 2) {
if (Buffer[Size] == 0) {
Temp.Val[0]=Buffer[Size+1];
Temp.Val[1]=Buffer[Size+2];
WriteLatch( SourceAddr.Word.HW,
SourceAddr.Word.LW,
Temp.Word.HW,
Temp.Word.LW);
WriteMem(CONFIG_WORD_WRITE);
}
}
#endif
//we have programmed the device, make it think that this was a POR reset
//so that persistent variables are cleared.
ResetDeviceasPOR();
break;
}
case COMMAND_NACK: {
ResetDevice();
break;
}
default:
PutChar(COMMAND_NACK);
break;
}
}
}
/******************************************************************************/
void GetChar(char* ptrChar) {
char c;
while (1) {
doHeartbeat();
if timer expired, signal to application to jump to user code
if (IFS0bits.T3IF == 1) {
*ptrChar = COMMAND_NACK;
break;
}
check for receive errors.
if (DEFAULT_UART_FERR == 1) {
Clear framing error.
c = DEFAULT_UART_RXREG;
Panic!
__asm__ volatile ("reset");
}
must clear the overrun error to keep uart receiving.
if (DEFAULT_UART_OERR == 1) {
DEFAULT_UART_OERR = 0;
Panic!
__asm__ volatile ("reset");
}
get the data.
if (DEFAULT_UART_URXDA == 1) {
Disable timer countdown.
T2CONbits.TON = 0;
*ptrChar = DEFAULT_UART_RXREG;
break;
}
}
}
/******************************************************************************/
void ReadPM(char * ptrData, uReg32 SourceAddr) {
int Size;
uReg32 Temp;
for (Size = 0; Size < PM_ROW_SIZE; Size++) {
Temp.Val32 = ReadLatch(SourceAddr.Word.HW, SourceAddr.Word.LW);
ptrData[0] = Temp.Val[2];
ptrData[1] = Temp.Val[1];
ptrData[2] = Temp.Val[0];
ptrData = ptrData + 3;
SourceAddr.Val32 = SourceAddr.Val32 + 2;
}
}
/******************************************************************************/
void WriteBuffer(char * ptrData, int Size) {
int DataCount;
for (DataCount = 0; DataCount < Size; DataCount++) {
PutChar(ptrData[DataCount]);
}
}
/******************************************************************************/
void PutChar(char Char) {
Transmit buffer is full.
while (DEFAULT_UART_UTXBF) {
doHeartbeat();
}
DEFAULT_UART_TXREG = Char;
}
/******************************************************************************/
#if (defined(__PIC24E__) || defined(__dsPIC33E__))
//this does double word programming, works for all PIC24E, PIC33E
void WritePM(char * ptrData, uReg32 SourceAddr) {
int Size,Size1;
uReg32 Temp;
uReg32 Temp2;
for (Size = 0,Size1=0; Size < PM_ROW_SIZE; Size = Size+2) {
Temp.Val[0]=ptrData[Size1+0];
Temp.Val[1]=ptrData[Size1+1];
Temp.Val[2]=ptrData[Size1+2];
Temp.Val[3]=0;
Size1+=3;
Temp2.Val[0]=ptrData[Size1+0];
Temp2.Val[1]=ptrData[Size1+1];
Temp2.Val[2]=ptrData[Size1+2];
Temp2.Val[3]=0;
Size1+=3;
LoadTwoWords(SourceAddr.Word.HW, SourceAddr.Word.LW, Temp.Word.HW,Temp.Word.LW, Temp2.Word.HW,Temp2.Word.LW);
//WriteLatch(SourceAddr.Word.HW, SourceAddr.Word.LW,Temp.Word.HW,Temp.Word.LW);
WriteMem2(SourceAddr.Word.HW, SourceAddr.Word.LW, PM_ROW_WRITE);
SourceAddr.Val32 = SourceAddr.Val32 + 4;
}
}
#else
void WritePM(char * ptrData, uReg32 SourceAddr) {
int Size, Size1;
uReg32 Temp;
uReg32 TempAddr;
uReg32 TempData;
for (Size = 0, Size1 = 0; Size < PM_ROW_SIZE; Size++) {
Temp.Val[0]=ptrData[Size1 + 0];
Temp.Val[1]=ptrData[Size1 + 1];
Temp.Val[2]=ptrData[Size1 + 2];
Temp.Val[3]=0;
Size1 +=3;
WriteLatch(SourceAddr.Word.HW, SourceAddr.Word.LW,Temp.Word.HW,Temp.Word.LW);
Device ID errata workaround: Save data at any address that has LSB 0x18
if ((SourceAddr.Val32 & 0x0000001F) == 0x18) {
TempAddr.Val32 = SourceAddr.Val32;
TempData.Val32 = Temp.Val32;
}
if ((Size !=0) && (((Size + 1) % INSTR_PER_ROW) == 0)) {
Device ID errata workaround: Reload data at address with LSB of 0x18
WriteLatch(TempAddr.Word.HW, TempAddr.Word.LW,TempData.Word.HW,TempData.Word.LW);
WriteMem(PM_ROW_WRITE);
}
SourceAddr.Val32 = SourceAddr.Val32 + 2;
}
}
#endif