現場升級方案: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.整個工程就算建立起來了。


分享到:


相關文章: