现场升级方案:NXP LPC1778采用U盘方式进行固件程序包IAP升级

[本文属原创,转载请附上原文出处链接。]

闲来无事,总结一下之前做过的U盘升级项目。一个技术人员的成长之路在于善于总结,生活也是一样扯远了,我准备了两个软件环境,一个带操作系统(UCOS)的,另一个裸机版的。随后我会附上两个程序代码(头条号好像不允许发外链)。U盘升级可以分为两部分代码:U盘读取bin文件和IAP功能两部分。大概说一下实现过程,具体IAP网上都玩坏了。

硬件环境:NXP 1788

软件环境:KEIL

实现过程:上面说了我准备了两个程序,就用裸机版的代码说一下实现流程。带操作系统的原理都是一样的。只是多创建几个任务而已。USB_HOST实现IAP升级,总的思路就是:复制bin文件到U盘->目标板断电,插上U盘->目标板上电,进入升级->运行升级程序。其实可以更具体,比如说设置升级标志或者按键。

拿到一个程序先从main开始,直接贴代码,说一大堆废话有什么用。

int main()
{
int32_t rc;
uint32_t numBlks, blkSize;
uint8_t inquiryResult[INQUIRY_LENGTH];

SystemInit();

UART_Init(57600);


Host_Init();
rc = Host_EnumDev();
if (rc == OK) {

rc = MS_Init( &blkSize, &numBlks, inquiryResult );
if (rc == OK) {
rc = FAT_Init();
if (rc == OK) {
Bin_Read();
} else {
return (0);
}
} else {
return (0);
}
} else {
return (0);
}
while(1);
}

下面分模块说一下, 前面的硬件初始化函数很简单,USB设备枚举和FAT文件系统NXP官网上都有,只需改硬件接口,Host_Init函数如下:

void Host_Init (void)
{
uint32_t HostBaseAddr;

LPC_SC->PCONP |= 0x80000000;
LPC_USB->OTGClkCtrl = 0x00000019;
while ((LPC_USB->OTGClkSt & 0x00000019) != 0x19);

LPC_USB->StCtrl = 0x1;

LPC_IOCON->P0_29 &= ~(0x07UL << 0);
LPC_IOCON->P0_30 &= ~(0x07UL << 0);
LPC_IOCON->P1_28 &= ~(0x07UL << 0);
LPC_IOCON->P1_29 &= ~(0x07UL << 0);

LPC_IOCON->P0_29 |= 0x01UL << 0;
LPC_IOCON->P0_30 |= 0x01UL << 0;
LPC_IOCON->P1_28 |= 0x01UL << 0;
LPC_IOCON->P1_29 |= 0x01UL << 0; // P1.29 -- USB_SDA1
PRINT_Log("Initializing Host Stack\n");


HostBaseAddr = HOST_BASE_ADDR;

Hcca = (volatile HCCA *)(HostBaseAddr+0x000);
TDHead = (volatile HCTD *)(HostBaseAddr+0x100);
TDTail = (volatile HCTD *)(HostBaseAddr+0x110);
EDCtrl = (volatile HCED *)(HostBaseAddr+0x120);
EDBulkIn = (volatile HCED *)(HostBaseAddr+0x130);
EDBulkOut = (volatile HCED *)(HostBaseAddr+0x140);
TDBuffer = (volatile uint8_t *)(HostBaseAddr+0x150);
FATBuffer = (volatile uint8_t *)(HostBaseAddr+0x1D0);
UserBuffer = (volatile uint8_t *)(HostBaseAddr+0x1000);


Host_EDInit(EDCtrl);
Host_EDInit(EDBulkIn);
Host_EDInit(EDBulkOut);
Host_TDInit(TDHead);
Host_TDInit(TDTail);
Host_HCCAInit(Hcca);

Host_DelayMS(50);
LPC_USB->Control = 0;
LPC_USB->ControlHeadED = 0;
LPC_USB->BulkHeadED = 0;


LPC_USB->CommandStatus = OR_CMD_STATUS_HCR;
LPC_USB->FmInterval = DEFAULT_FMINTERVAL;


LPC_USB->Control = (LPC_USB->Control & (~OR_CONTROL_HCFS)) | OR_CONTROL_HC_OPER;
LPC_USB->RhStatus = OR_RH_STATUS_LPSC;

LPC_USB->HCCA = (uint32_t)Hcca;


LPC_USB->InterruptStatus |= LPC_USB->InterruptStatus;

LPC_USB->InterruptEnable = OR_INTR_ENABLE_MIE |
OR_INTR_ENABLE_WDH |
OR_INTR_ENABLE_RHSC |
OR_INTR_ENABLE_UE;


NVIC_EnableIRQ(USB_IRQn);
NVIC_SetPriority (USB_IRQn, 0);

PRINT_Log("Host Initialized\n");
}

这段主要是USB引脚配置和USB主机初始化。Bin_Read()函数如下:

void Bin_Read (void)
{
int32_t fdr;
uint32_t bytes_read,writelen;
uint32_t dstaddr;
SelSector(APP_START_SECTOR,APP_END_SECTOR); //选择扇区
EraseSector(APP_START_SECTOR,APP_END_SECTOR);
BlankCHK(APP_START_SECTOR,APP_END_SECTOR);
SelSector(APP_START_SECTOR,APP_END_SECTOR);
PRINT_Log("\r\nstart file operations...\r\n");

fdr = FILE_Open(FILENAME_R, RDONLY);
if (fdr > 0) {
PRINT_Log("Reading from %s...\n", FILENAME_R);
for(writelen=0;writelen {
bytes_read = FILE_Read(fdr, UserBuffer, MAX_BUFFER_SIZE);
dstaddr = (uint32_t)(APP_START_ADDR + (writelen)*1024);//dst address.
SelSector(APP_START_SECTOR,APP_END_SECTOR);
RamToFlash(dstaddr,(uint32_t)UserBuffer, 1024);
Compare(dstaddr, (uint32_t)UserBuffer, 1024);
}
// printf("%x",writelen);
PRINT_Log("\r\n write file successful\r\n");
SCB->VTOR = APP_START_ADDR;
ExceuteApplication();
FILE_Close(fdr);
} else {
PRINT_Log("\r\n write file failed\r\n");
}
}

上面的代码可以分为两部分:1.从U盘读取bin文件2.IAP功能。先说IAP部分,IAP实现方法有UART,GPRS,USB等方式。要进行IAP设计,先划分FLASH扇区。LPC1788的FLASH划分如下:


现场升级方案:NXP LPC1778采用U盘方式进行固件程序包IAP升级

将flash划分为两个区,bootloader和APP区,bootloader存放升级引导程序,即我们的USB_HOST_IAP代码,根据具体的Code大小确定bootloader的扇区,APP就是用户程序即需要升级的程序代码。APP需要配置后面再说。这是我的扇区划分:

#define IAP_START_ADDR 0x00000000 // IAP开始地址
#define IAP_LOCATION 0x1FFF1FF1
#define APP_START_ADDR 0x00A000 // 用户程序起始地址
#define APP_END_ADDR 0x78000 //LPC1788 512K Flash
//#define APP_SIZE 0x10000
#define APP_START_SECTOR 10
#define APP_END_SECTOR 29 // LPC1788 512K Flash扇区

下面分别概括一下实现IAP命令的函数,IAP功能命令有准备编程扇区,复制RAM到FLASH,擦除扇区,扇区查空,读器件ID,读BOOT代码版本,比较等指令。程序要进行IAP升级,必须要先选择扇区擦除扇区之后才能写进Flash。先需要定义系统时钟,参数和一些变量。

#define IAP_FCCLK 48000

uint32_t paramin[8];
uint32_t paramout[8];
unsigned long command[5];
unsigned long result[5];
typedef void (*IAP) (unsigned int [ ] , unsigned int [ ]);

写数据之前,必须要选择需要写入的扇区,选择扇区部分代码:

uint32_t SelSector(uint8_t sec1,uint8_t sec2)
{
paramin[0] = IAP_SELECTOR;
paramin[1] = sec1;
paramin[2] = sec2;


(*(void(*)())IAP_LOCATION)(paramin,paramout);
return(paramout[0]);
}

选中扇区之后,要检查该扇区是否已经有数据,所以要擦除扇区,附代码:

uint32_t EraseSector(uint32_t sec1, uint32_t sec2)
{
paramin[0] = IAP_ERASESECTOR;
paramin[1] = sec1;
paramin[2] = sec2;
paramin[3] = IAP_FCCLK;
(*(void(*)())IAP_LOCATION)(paramin,paramout);
return(paramout[0]);
}

下来就是向flash写入数据,flash起始地址必须以256字节为分界,调用函数

uint32_t RamToFlash(uint32_t dst, uint32_t src, uint32_t no)
{
paramin[0] = IAP_RAMTOFLASH;
paramin[1] = dst;
paramin[2] = src;
paramin[3] = no;
paramin[4] = IAP_FCCLK;
(*(void(*)())IAP_LOCATION)(paramin,paramout);
return(paramout[0]);
}

写完之后要进行比较,将RAM读出来的数据和写入到flash的数据进行比较,注意flash起始地址必须字对齐,字节个数必须能被4整除,当源或目标地址包含从地址 0 开始的前 64 个字节中的任意一个地址时, 比较的结果可能不准确。因为前 64 个字节可被重新映射到 RAM:

uint32_t Compare(uint32_t dst, uint32_t src, uint32_t no)
{
paramin[0] = IAP_COMPARE;


paramin[1] = dst;
paramin[2] = src;
paramin[3] = no;
(*(void(*)())IAP_LOCATION)(paramin,paramout);
return(paramout[0]);
}

还有ExceuteApplication()部分的代码,程序写入flash之后,要重新映射向量表,从bootloader跳转到APP执行,这就要获取程序的入口地址和SP堆栈的值。如下:

__asm void ExceuteApplication(void)
{
ldr r0, =0x00A000
ldr r0, [r0]
mov sp, r0
ldr r0, =0x00A004
ldr r0, [r0]
BX r0
}

最后关闭文件系统,main里面最主要读取bin文件调用IAP功能的Bin_Read()函数说完了。最后说一下APP程序产生bin文件的配置。
关于KEIL中Target Options配置:
1.将程序入口定位到App即用户程序的入口地址;

2.User选项:Run #1填写产生bin文件路径:C:\Keil\ARM\ARMCC\bin\fromelf.exe--bin --output output\FLASH\test.bin output\FLASH\LPC177x_8x.axf;

3.C/c++选项:Optimization选择高优先级:Level3;

4.Asm选项:Define填NO_CRP,即不产生空文件夹;

5.Linker选项:勾选Use Memory layout from Target Dialog.整个工程就算建立起来了。


分享到:


相關文章: