设计并实现一个boot,需要用到如下知识点,
1. 体会boot的作用
2. 新增闪存控制器学习
3. 串口知识复习
4. FLASH&SPI知识复习
5. 状态机知识点复习
6. 初识上位机软件
7. xmodem通信协议学习
8. 交互界面设计
**************************
Press Ctrl+c into bootmenu.
. . . . . <-- 此处每秒打印一个点,打印完5个点后,进入APP,打印期间按下Ctrl+c进入boot主界面
按相应的数字,进入相应的功能界面
**************************
welcome to boot
**************************
0. help
1. reboot
2. get app by uart
3. update
4. update and reboot
5. dump app in flash
6. dump app in rom
**************************
0. help
1. reboot
2. get app by uart
3. update
4. update and reboot
5. dump app in flash
6. dump app in rom
**************************
booting...
**************************
please wait a moment to get the app.
**************************
getting app,press q stop.
<---选择Transfer/Send Xmodem../选择文件/确定
Starting xmodem transfer. Press Ctrl+C to cancel.
Transferring test.bin...
100% 6 KB 6 KB/sec 00:00:01 50 Errors
**************************
get app file sucess.
press 0 get help.
**************************
updating...
**************************
update ok, press 0 get help.
**************************
updating...
**************************
update ok, press 0 get help.
**************************
booting...
**************************
app dumpping from flash...
<---选择本地文件,Transfer/Recive Xmodem../选择文件/确定
Starting xmodem transfer. Press Ctrl+C to cancel.
Transferring D:\aaa.bin...
62 KB 6 KB/sec 00:00:10 0 Errors
**************************
app dumpping ok, press 0 get help.
**************************
app dumpping from rom...
<---选择本地文件,Transfer/Recive Xmodem../选择文件/确定
Starting xmodem transfer. Press Ctrl+C to cancel.
Transferring D:\aaa.bin...
62 KB 6 KB/sec 00:00:10 0 Errors
**************************
app dumpping ok, press 0 get help.
如下图,通过用户输入指令,分别进入不同功能,显示不同引导界面,并在该状态下完成相应子功能。
所谓引导至APP,其实就做两件事,一是重置MSP指针,二是重置PC指针。具体原因在OS章节有说明。
typedef void (*pAppFunction)(void);
#define BOOT_APP_MAIN_ADDR 0x800E000
static VOID BOOT_GoToApp(VOID)
{
pAppFunction Jump_To_Application;
unsigned long jumpAddress;
//跳转至用户代码
jumpAddress = *(volatile U32*)(BOOT_APP_MAIN_ADDR + 4);
Jump_To_Application = (pAppFunction)jumpAddress;
//初始化用户程序的堆栈指针
__set_MSP(*(volatile U32*) BOOT_APP_MAIN_ADDR);
Jump_To_Application();
}
DRV_IWDG_Init(); /* 利用看门狗复位芯片 */
文件传输采用Xmodem协议,该协议非常简单,如下,
报文格式:
#define XMODEM_SOH 0x01 /* Xmodem数据头 */
#define XMODEM_STX 0x02 /* 1K-Xmodem数据头 */
#define XMODEM_EOT 0x04 /* 发送结束 */
#define XMODEM_ACK 0x06 /* 认可响应 */
#define XMODEM_NAK 0x15 /* 不认可响应 */
#define XMODEM_CAN 0x18 /* 撤销传送 */
#define XMODEM_CTRLZ 0x1A /* 填充数据包 */
#define XMODEM_TIMEOUT 2000
#define XMODEM_MAXPKTLEN 133
#define XMODEM_PKTBUFLEN 128
static U16 XMODEM_Crc16(IN const U8 *buf, IN U8 len)
{
U8 i = 0;
U16 crc = 0;
while (len--)
{
crc ^= *buf++ << 8;
for (i = 0; i < 8; ++i)
{
if( crc & 0x8000 )
{
crc = (crc << 1) ^ 0x1021;
}
else
{
crc = crc << 1;
}
}
}
return crc;
}
static S32 XMODEM_Check(IN BOOL isCrc, IN const U8 *buf, U8 sz)
{
U16 crc = 0;
U16 tcrc = 0;
U8 i = 0;
U8 cks = 0;
if (TRUE == isCrc)
{
crc = XMODEM_Crc16(buf, sz);
tcrc = (buf[sz]<<8)+buf[sz+1];
if (crc != tcrc)
{
APP_ERROR("%u, %u", crc, tcrc);
return OS_ERROR;
}
}
else
{
for (i = 0; i < sz; ++i)
{
cks += buf[i];
}
if (cks != buf[sz])
{
APP_ERROR("%u, %u", cks, buf[sz]);
return OS_ERROR;
}
}
return OS_OK;
}
static S32 XMODEM_GetOnePkt(IN U8 pktNum)
{
U8 ch = 0;
S32 ret = OS_OK;
U8 i = 0;
U8 xbuff[XMODEM_MAXPKTLEN] = {0};
for (i = 1; i < XMODEM_MAXPKTLEN; i++)
{
ret = DRV_UART1_GetChar(XMODEM_TIMEOUT, &ch);
if (ret != OS_OK)
{
APP_ERROR("ret=%d", ret);
return OS_ERROR;
}
xbuff[i-1] = ch;
}
if (xbuff[0] != (U8)(~xbuff[1]))
{
APP_ERROR("%u,%u", xbuff[0], xbuff[1]);
return OS_ERROR;
}
ret = XMODEM_Check(TRUE, &xbuff[2], XMODEM_PKTBUFLEN);
if (ret != OS_OK)
{
APP_ERROR("ret=%d", ret);
return OS_ERROR;
}
if (pktNum != xbuff[0])
{
(VOID)APP_FILE_Write(APP_FILE_FD_APPINFLASH, &xbuff[2], XMODEM_PKTBUFLEN);
}
return OS_OK;
}
S32 APP_XMODEM_Recive(VOID)
{
U8 ch = 0;
S32 ret = OS_OK;
U8 pktNum = 0;
ret = DRV_UART1_GetChar(XMODEM_TIMEOUT, &ch);
if (ret != OS_OK)
{
APP_ERROR("ret=%d", ret);
return OS_CONTINUE;
}
switch (ch)
{
case XMODEM_SOH:
ret = XMODEM_GetOnePkt(pktNum);
if (ret != OS_OK)
{
DRV_UART1_PutChar(XMODEM_NAK);
break;
}
pktNum++;
DRV_UART1_PutChar(XMODEM_ACK);
break;
case XMODEM_EOT:
DRV_UART1_PutChar(XMODEM_ACK);
return OS_OK;
case XMODEM_CAN:
DRV_UART1_PutChar(XMODEM_ACK);
return OS_ERROR;
case 'q':
return OS_ERROR;
}
return OS_CONTINUE;
}
static VOID XMODEM_FillPkt(IN U8 cmd , IN U8 index, IN U8 *buffer)
{
U16 crc = 0;
buffer[0] = cmd;
buffer[1] = index;
buffer[2] = (U8)(~index);
crc = XMODEM_Crc16(&buffer[3], XMODEM_PKTBUFLEN);
buffer[XMODEM_PKTBUFLEN+3] = (crc >> 8);
buffer[XMODEM_PKTBUFLEN+4] = crc;
return;
}
static S32 XMODEM_SendSohPkt(IN U8 fd)
{
BOOL resend = FALSE;
U8 index = 1;
U8 ch = 0;
S32 ret = OS_OK;
U8 buffer[XMODEM_MAXPKTLEN] = {0};
U8 retryCount = 0;
for (;;)
{
if (FALSE == resend)
{
retryCount = 0;
memset(buffer, 0, XMODEM_MAXPKTLEN);
ret = APP_FILE_Read(fd, XMODEM_PKTBUFLEN, &buffer[3]);
if (ret != OS_OK)
{
return OS_OK;
}
XMODEM_FillPkt(XMODEM_SOH, index, buffer);
}
DRV_UART1_SendBuf(buffer, XMODEM_MAXPKTLEN);
ret = DRV_UART1_GetChar(XMODEM_TIMEOUT, &ch);
if (ret != OS_OK)
{
resend = TRUE;
retryCount++;
}
switch (ch)
{
case XMODEM_ACK:
index++;
resend = FALSE;
break;
case XMODEM_NAK:
resend = TRUE;
retryCount++;
break;
case XMODEM_CAN:
return OS_ERROR;
}
if (retryCount > 16)
{
return OS_ERROR;
}
}
}
static VOID XMODEM_SendEotPkt(VOID)
{
U8 retryCount = 0;
S32 ret = OS_OK;
U8 buffer[XMODEM_MAXPKTLEN] = {0};
U8 ch = 0;
XMODEM_FillPkt(XMODEM_EOT, 1, buffer);
DRV_UART1_SendBuf(buffer, XMODEM_MAXPKTLEN);
while (1)
{
retryCount++;
ret = DRV_UART1_GetChar(XMODEM_TIMEOUT, &ch);
if (ret != OS_OK)
{
DRV_UART1_SendBuf(buffer, XMODEM_MAXPKTLEN);
}
else
{
switch (ch)
{
case XMODEM_ACK:
return;
case XMODEM_NAK:
DRV_UART1_SendBuf(buffer, XMODEM_MAXPKTLEN);
break;
case XMODEM_CAN:
return;
}
}
if (retryCount > 16)
{
return;
}
}
}
static VOID XMODEM_SendCanPkt(VOID)
{
U8 buffer[XMODEM_MAXPKTLEN] = {0};
XMODEM_FillPkt(XMODEM_CAN, 1, buffer);
DRV_UART1_SendBuf(buffer, XMODEM_MAXPKTLEN);
}
S32 APP_XMODEM_Send(IN U8 fd)
{
S32 ret = OS_OK;
ret = XMODEM_SendSohPkt(fd);
if (OS_OK == ret)
{
XMODEM_SendEotPkt();
return OS_OK;
}
else if (OS_ERROR == ret)
{
XMODEM_SendCanPkt();
return OS_ERROR;
}
return OS_OK;
}
本地文件分两种,
一种是FLASH读写,在前面已经说过了,
另一种是ROM读写,即FMC,本节会说明下
本质上这两种读写是一样的,因此做了一个抽象层用于同一访问接口
文件读写抽象层,提供open,write,read,seek文件操作接口
VOID APP_FILE_Open(IN U8 fd)
{
if (APP_FILE_FD_APPINFLASH == fd)
{
DRV_GD25Q40_BulkErase();
gFileInFlashAddrWrite = 0;
gFileInFlashAddrRead = 0;
}
else if (APP_FILE_FD_APPINROM == fd)
{
DRV_FMC_Erase(APP_FILE_INROM_MAX, APP_FILE_INROM_START);
gFileInRomAddrWrite = APP_FILE_INROM_START;
gFileInRomAddrRead = APP_FILE_INROM_START;
}
}
S32 APP_FILE_Write(IN U8 fd, IN U8 *buffer, IN U16 numByteToWrite)
{
if (APP_FILE_FD_APPINFLASH == fd)
{
if (APP_FILE_INFLASH_MAX <= (gFileInFlashAddrWrite + numByteToWrite))
{
return OS_ERROR;
}
DRV_GD25Q40_BufferWrite(buffer, gFileInFlashAddrWrite, numByteToWrite);
gFileInFlashAddrWrite += numByteToWrite;
}
else if (APP_FILE_FD_APPINROM == fd)
{
if ((APP_FILE_INROM_MAX+APP_FILE_INROM_START) <= (gFileInRomAddrWrite + numByteToWrite))
{
return OS_ERROR;
}
DRV_FMC_WriteBuffer(gFileInRomAddrWrite, buffer, numByteToWrite);
gFileInRomAddrWrite += numByteToWrite;
}
return OS_OK;
}
S32 APP_FILE_Read(IN U8 fd, U16 numByteToRead, OUT U8 *buffer)
{
if (APP_FILE_FD_APPINFLASH == fd)
{
if (APP_FILE_INFLASH_MAX <= (gFileInFlashAddrRead + numByteToRead))
{
return OS_ERROR;
}
DRV_GD25Q40_BufferRead(buffer, gFileInFlashAddrRead, numByteToRead);
gFileInFlashAddrRead += numByteToRead;
}
else if (APP_FILE_FD_APPINROM == fd)
{
if ((APP_FILE_INROM_MAX+APP_FILE_INROM_START) <= (gFileInRomAddrRead + numByteToRead))
{
return OS_ERROR;
}
DRV_FMC_ReadBuffer(gFileInRomAddrRead, numByteToRead, buffer);
gFileInRomAddrRead += numByteToRead;
}
return OS_OK;
}
VOID APP_FILE_Seek(IN U8 fd, IN U32 offset)
{
if (APP_FILE_FD_APPINFLASH == fd)
{
gFileInFlashAddrRead = offset;
}
else if (APP_FILE_FD_APPINROM == fd)
{
gFileInRomAddrRead = offset;
}
}
VOID APP_FILE_SeekStartOfFile(IN U8 fd)
{
if (APP_FILE_FD_APPINFLASH == fd)
{
gFileInFlashAddrRead = 0;
}
else if (APP_FILE_FD_APPINROM == fd)
{
gFileInRomAddrRead = APP_FILE_INROM_START;
}
}
ROM操作,即FMC操作
FMC,闪存控制器,其本质上是FLASH,应有和Flash一样的操作特性,只是因为在芯片内部集成,所以芯片提供了一套全新的操作寄存器,操作方法如下,
VOID DRV_FMC_Erase(IN U32 addrMax, IN U32 addrStart)
{
volatile U32 NbrOfPage = 0x00;
volatile U32 EraseCounter = 0x00;
/* Unlock the Flash Bank1 Program Erase controller */
FMC_Unlock();
/* Define the number of page to be erased */
NbrOfPage = (addrMax) / DRV_FMC_PAGE_SIZE;
/* Clear All pending flags */
FMC_ClearBitState(FMC_FLAG_EOP | FMC_FLAG_WERR | FMC_FLAG_PERR );
/* Erase the FLASH pages */
for(EraseCounter = 0; EraseCounter < NbrOfPage; EraseCounter++)
{
(VOID)FMC_ErasePage(addrStart + (DRV_FMC_PAGE_SIZE * EraseCounter));
FMC_ClearBitState(FMC_FLAG_EOP | FMC_FLAG_WERR | FMC_FLAG_PERR );
}
FMC_Lock();
return;
}
/* len长度不能超过buf空间长度 */
VOID DRV_FMC_ReadBuffer(IN U32 addr, IN U32 len, OUT U8 *buf)
{
U32 i = 0;
for (i = 0; i < len; i++)
{
buf[i] = *(U8 *)(addr+i);
}
return;
}
/* buf空间必须是4的倍数,len长度不能超过buf空间长度 */
VOID DRV_FMC_WriteBuffer(IN U32 addr, IN U8 *buf, IN U32 len)
{
U32 i = 0;
U32 offset = 0;
DrvFmc_u data;
/* Unlock the Flash Bank1 Program Erase controller */
FMC_Unlock();
/* Clear All pending flags */
FMC_ClearBitState(FMC_FLAG_EOP | FMC_FLAG_WERR | FMC_FLAG_PERR );
for (i = 0; i < len; i += 4)
{
memcpy(data.c_data, buf + offset, 4);
(VOID)FMC_ProgramWord(addr + offset, data.i_data);
FMC_ClearBitState(FMC_FLAG_EOP | FMC_FLAG_WERR | FMC_FLAG_PERR );
offset += 4;
}
FMC_Lock();
return;
}
升级是一个把APP文件从片外FLASH,写到片内ROM的一个过程,如下,
VOID APP_UPGRADE_Updating(VOID)
{
U32 i = 0;
U8 Buf[APP_UPGRADE_PAGE] = {0};
APP_FILE_Open(APP_FILE_FD_APPINROM);
for (i = 0; i < APP_UPGRADE_APP_MAX; i++)
{
APP_FILE_Read(APP_FILE_FD_APPINFLASH, APP_UPGRADE_PAGE, Buf);
APP_FILE_Write(APP_FILE_FD_APPINROM, Buf, APP_UPGRADE_PAGE);
}
}
该案例涉及的boot,跟一般嵌入式开发的Uboot界面极为相似,而且使用的知识点基本也把前面的都覆盖了,是一个很好的综合实例,起到承前启后的作用。
https://github.com/YaFood/GD32F103/tree/master/TestBOOT