由于工作需要,用到ARM7的芯片LPC2132的IAP,初次接触ARM7,历时2天时间,终于成功完成BootLoader的编写,下面和大家分享一下使用心得,方便大家的使用。
要完成BootLoader,首先得实现IAP。
LPC21XX的IAP使用比较简单,芯片手册上有具体介绍,我就不多说了。主要用到以下几个函数。
以下是一些定义:
#define IAP_ENTER_ADR 0x7FFFFFF1 // IAP入口地址定义
/*
* 定义IAP命令字
*/
#define IAP_Prepare 50
#define IAP_RAMTOFLASH 51
#define IAP_ERASESECTOR 52
#define IAP_BLANKCHK 53
#define IAP_READPARTID 54
#define IAP_BOOTCODEID 55
#define IAP_COMPARE 56
/*
* 定义IAP返回状态字
*/
#define CMD_SUCCESS 0
#define INVALID_COMMAND 1
#define SRC_ADDR_ERROR 2
#define DST_ADDR_ERROR 3
#define SRC_ADDR_NOT_MAPPED 4
#define DST_ADDR_NOT_MAPPED 5
#define COUNT_ERROR 6
#define INVALID_SECTOR 7
#define SECTOR_NOT_BLANK 8
#define
SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION 9
#define COMPARE_ERROR 10
#define BUSY 11
/*
* 定义CCLK值大小,单位为KHz
*/
#define IAP_FCCLK (60000) /* 此频率值必须同主频相同 */
#define EEPROM_STARTSEC 4
#define EEPROM_ENDSEC 8
#define EE_SUUCEESS 0
#define IAP_ERROR 1
#define PARA_BANK_ERROR 2
#define PARA_SRCADDR_ERROR 3
#define PARA_OVERFLOW_ERROR 4
//IAP写入字节数
typedef enum
{
MODE0 = 256,
MODE1 = 512,
MODE2
= 1024,
MODE3 = 4096,
}IAPWRITEMODE;
//IAP擦除的扇区
typedef enum
{
SECTOR0,
SECTOR1,
SECTOR2,
SECTOR3,
SECTOR4,
SECTOR5,
SECTOR6,
SECTOR7,
SECTOR8,
}IAPCHANNEL;
IAP函数实现:
/*
* 定义函数指针
*/
void (*IAP_Entry) (unsigned long
ulParam_tab[], unsigned long ulPesult_tab[]) = (void(*)())IAP_ENTER_ADR;
unsigned long GulParamin[8]; /* IAP入口参数缓冲区 */
unsigned long GulParamout[8];
/* IAP出口参数缓冲区 */
/*********************************************************************************************************
** Function name: sectorPrepare
** Descriptions: IAP操作扇区选择,命令代码50
** input parameters: ucSec1: 起始扇区
** ucSec2: 终止扇区
** output parameters: GulParamout[0]: IAP操作状态码,IAP返回值
** Returned value: GulParamout[0]: IAP操作状态码,IAP返回值
*********************************************************************************************************/
unsigned long sectorPrepare (unsigned char ucSec1, unsigned char ucSec2)
{
GulParamin[0] = IAP_Prepare; /* 设置命令字 */
GulParamin[1] = ucSec1; /* 设置参数 */
GulParamin[2] = ucSec2;
(*IAP_Entry)(GulParamin, GulParamout); /* 调用IAP服务程序 */
return (GulParamout[0]); /* 返回状态码 */
}
/*********************************************************************************************************
** Function name: ramCopy
** Descriptions: 复制RAM的数据到FLASH,命令代码51
** input parameters: ulDst: 目标地址,即FLASH起始地址。以512字节为分界
** ulSrc: 源地址,即RAM地址。地址必须字对齐
** ulNo: 复制字节个数,为512/1024/4096/8192
** output parameters: GulParamout[0]: IAP操作状态码,IAP返回值
** Returned value: GulParamout[0]: IAP操作状态码,IAP返回值
*********************************************************************************************************/
unsigned long ramCopy (unsigned long ulDst, unsigned long ulSrc, unsigned long ulNo)
{
GulParamin[0] = IAP_RAMTOFLASH; /* 设置命令字 */
GulParamin[1] = ulDst; /* 设置参数 */
GulParamin[2] = ulSrc;
GulParamin[3] = ulNo;
GulParamin[4] = IAP_FCCLK;
(*IAP_Entry)(GulParamin, GulParamout); /* 调用IAP服务程序 */
return (GulParamout[0]); /* 返回状态码 */
}
/*********************************************************************************************************
** Function name: sectorErase
** Descriptions: 扇区擦除,命令代码52
** input parameters: ucSec1 起始扇区
** ucSec2 终止扇区92
** output parameters: GulParamout[0]: IAP操作状态码,IAP返回值
** Returned value: GulParamout[0]: IAP操作状态码,IAP返回值
*********************************************************************************************************/
unsigned long sectorErase (unsigned char ucSec1, unsigned
char ucSec2)
{
GulParamin[0] = IAP_ERASESECTOR; /* 设置命令字 */
GulParamin[1] = ucSec1; /* 设置参数 */
GulParamin[2] = ucSec2;
GulParamin[3] = IAP_FCCLK;
(*IAP_Entry)(GulParamin, GulParamout); /* 调用IAP服务程序 */
return (GulParamout[0]); /* 返回状态码 */
}
/*********************************************************************************************************
** Function name: blankChk
** Descriptions: 扇区查空,命令代码53
** input parameters: ucSec1: 起始扇区
** ucSec2: 终止扇区92
** output parameters: GulParamout[0]: IAP操作状态码,IAP返回值
** Returned value: GulParamout[0]: IAP操作状态码,IAP返回值
*********************************************************************************************************/
unsigned long blankChk (unsigned char ucSec1, unsigned char ucSec2)
{
GulParamin[0] = IAP_BLANKCHK; /* 设置命令字 */
GulParamin[1] = ucSec1; /* 设置参数 */
GulParamin[2] = ucSec2;
(*IAP_Entry)(GulParamin, GulParamout); /* 调用IAP服务程序 */
return (GulParamout[0]); /* 返回状态码 */
}
/*********************************************************************************************************
** Function name: parIdRead
** Descriptions: 读器件标识号,命令代码54
** input parameters: 无
** output parameters: GulParamout[0]: IAP操作状态码,IAP返回值
** Returned value: GulParamout[0]: IAP操作状态码,IAP返回值
*********************************************************************************************************/
unsigned long parIdRead (void)
{
GulParamin[0] = IAP_READPARTID; /* 设置命令字 */
(*IAP_Entry)(GulParamin, GulParamout); /* 调用IAP服务程序 */
return (GulParamout[0]); /* 返回状态码 */
}
/*********************************************************************************************************
** Function name: codeIdBoot
** Descriptions: 读Boot代码版本号,命令代码55
** input parameters: 无
** output parameters: GulParamout[0]: IAP操作状态码,IAP返回值
** Returned value: GulParamout[0]: IAP操作状态码,IAP返回值
*********************************************************************************************************/
unsigned long codeIdBoot (void)
{
GulParamin[0] = IAP_BOOTCODEID; /* 设置命令字 */
(*IAP_Entry)(GulParamin, GulParamout); /* 调用IAP服务程序 */
return (GulParamout[0]); /* 返回状态码 */
}
/*********************************************************************************************************
** Function name: dataCompare
** Descriptions: 校验数据,命令代码56
** input parameters: ulDst: 目标地址,即RAM/FLASH起始地址。地址必须字对齐
** ulSrc: 源地址,即FLASH/RAM地址。地址必须字对齐
** ulNo: 待比较的字节数。计数值应当为4的倍数字
** output parameters: GulParamout[0]: IAP操作状态码,IAP返回值
** Returned value: GulParamout[0]: IAP操作状态码,IAP返回值
*********************************************************************************************************/
unsigned long dataCompare (unsigned long ulDst, unsigned long ulSrc, unsigned long ulNo)
{
GulParamin[0] = IAP_COMPARE; /* 设置命令字 */
GulParamin[1] = ulDst; /* 设置参数 */
GulParamin[2] = ulSrc;
GulParamin[3] = ulNo;
(*IAP_Entry)(GulParamin, GulParamout); /* 调用IAP服务程序 */
return (GulParamout[0]); /* 返回状态码 */
}
以下为用户区代码:
写扇区,mode一共四种,而且只能使用这四种写入方式:256、512、1024或者4096字节
uint8 eepromWrite(uint32 WriteAddr, uint8
*pBuffer, IAPWRITEMODE mode)
{
uint8 ucErr = 0;
__disable_irq();
if (WriteAddr > 64 * 1024)
{
__enable_irq();
return PARA_BANK_ERROR;
}
if ((((unsigned long)pBuffer) % 4) != 0 )
{
__enable_irq();
return PARA_SRCADDR_ERROR;
}
ucErr
= sectorPrepare(EEPROM_STARTSEC, EEPROM_ENDSEC);
ucErr
= ramCopy(WriteAddr, (unsigned long)pBuffer, mode);
ucErr
= dataCompare(WriteAddr, (unsigned long)pBuffer, mode);
if
(ucErr != CMD_SUCCESS)
{ /* IAP函数调用出错 */
__enable_irq();
return
IAP_ERROR;
}
__enable_irq();
return EE_SUUCEESS;
}
擦除扇区,一次至少擦除一个扇区,在写之前必须擦除。
uint8 eepromErase(IAPCHANNEL StartSec,
IAPCHANNEL EndSec)
{
uint8 ucErr = 0;
__disable_irq();
ucErr
= sectorPrepare(StartSec, EndSec);
ucErr
= sectorErase(StartSec, EndSec);
ucErr
= blankChk(StartSec, EndSec);
if(ucErr
!= CMD_SUCCESS)
{ /* IAP函数调用出错 */
__enable_irq();
return
IAP_ERROR;
}
__enable_irq();
return EE_SUUCEESS;
}
读扇区,读取相应地址处的数据
void eepromRead(uint32 ReadAddr, uint8
*pBuffer, uint16 NumToRead)
{
uint16 i;
for (i = 0; i < NumToRead; i++)
{
pBuffer = *(uint8 * )ReadAddr;
ReadAddr += 1;
}
}
在成功实现IAP功能后,就是BootLoader的编写了,BootLoader实现比较简单,里面完成APP的写入即可,APP的写入大家可以按需要实现,用串口等方式进行数据的传输。在APP写完后将PC指针跳转到指定位置即可。PC指针的跳转相信大家都不陌生,使用以下方法实现:
//定义一个函数类型的参数
typedef void (*iapfun)(void);
iapfun jump2app;
jump2app = (iapfun)0x4000;//APP的ROM地址
(*jump2app)();
BootLoader的编写需要注意RAM的设置。
由于IAP使用到RAM顶部的32个字节,因此KEIL中设置RAM大小时要用总大小减去0X20。我使用的单片机RAM总大小为16K,因此SIZE设置为0X3FE0。如图:
完成BootLoader的编写,下面就是APP的实现了。
在编写APP之前,我们先看一下ARM7的中断向量表:
这是ARM7中断向量表的存放位置,由于BootLoader中用到了这部分中断,因此APP中无法继续使用这个向量表,因此必须实现中断向量表的重映射,回头查看芯片手册,发现支持中断向量表的RAM映射。
首先看一下BootLoader和APP的具体存放位置:
ROM中底部存放BootLoader,接着存放APP。
RAM中底部64个字节存放32字节中断向量表以及额外的32个字节。接着留给APP的使用,最顶部32字节给IAP使用。
下面是KEIL工程的具体设置:
工程设置好后:就是中断向量表的COPY和重映射的实现。
这部分工作要在复位的时候设置好,打开启动文件,完成以下修改:
这部分在KEIL自带的启动文件中有,因此只要打开宏开关即可,如果使用的启动文件没有的话,可以自己添加,打开宏的方法:
在这之前要先完成中断向量表的COPY:
;定义RAM基地址
RAM_BASE EQU 0x40000000
; Copy Exception Vectors to Internal RAM
IF EF:RAM_INTVEC
ADR R8, Vectors
LDR R9, =RAM_BASE
LDMIA R8!, {R0-R7}
STMIA R9!, {R0-R7}
LDMIA R8!, {R0-R7}
STMIA R9!, {R0-R7}
ENDIF
为了更加通用,加一下宏开关,使用时工程中作相应设置:
完成以上所有工作即可实现BootLoader的使用,谢谢支持,四人行出品。
|