GD32实战20__Boot综合实验

知识点

设计并实现一个boot,需要用到如下知识点,

1. 体会boot的作用

2. 新增闪存控制器学习

3. 串口知识复习

4. FLASH&SPI知识复习

5. 状态机知识点复习

6. 初识上位机软件

7. xmodem通信协议学习

8. 交互界面设计

设计细节

UI界面设计

启动引导界面
**************************
  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...
获取APP文件界面
**************************
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...
从Flash中dump文件界面
**************************
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.
从Rom中dump文件界面
**************************
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.

状态机设计

如下图,通过用户输入指令,分别进入不同功能,显示不同引导界面,并在该状态下完成相应子功能。

GD32实战20__Boot综合实验_第1张图片

各子功能设计

引导进入APP功能设计(涉及CPU程序运行原理)

所谓引导至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协议)

文件传输采用Xmodem协议,该协议非常简单,如下,

报文格式:

在这里插入图片描述

  1. 串口配置为异步,8位数据,no校验,no停止位
  2. 报文格式说明
    1. Byte1,Start Of Hearder, 如下,
      1. SOH(01H) – Xmodem数据头
      2. EOT(04H) – 发送结束
      3. ACK(06H) – 认可响应
      4. NAK(15H) – 不认可响应
      5. CAN(18H) – 撤销传送
    2. Byte2,Packet Number ,报文序列码,从1开始,每包递加,FF后循环
    3. Byte3,~(Packet Number),报文序列码的补码
    4. Byte4-131,Packet Data,报文数据,每个报文都是128个字节,如果不足128,则用1AH补充
    5. Byte132-133,16bit CRC,Byte132-CRC高位,Byte133-CRC地位,只对128个数据做CRC16运行,多项式为X16+X12+X^5+1。
  3. 交互流程
    1. Tx方发生SOH数据报文
    2. Rx方收到且数正确,回ACK,不正确会NAK
    3. 文件传输完毕,Tx发EOT通知Rx,Rx回ACK
    4. 任何时候,收到CAN,则强制停止
  4. 代码如下,
#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;
}
本地文件读写功能设计(涉及FMC和FLASH操作)

本地文件分两种,

​ 一种是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;
}
升级功能设计(涉及FLASH和FMC操作)

升级是一个把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

你可能感兴趣的:(ARM)