打印
[活动专区]

【开源活动】-基于国民N32G45x的SD卡IAP升级开发

[复制链接]
560|23
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
lulugl|  楼主 | 2023-3-29 21:44 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 lulugl 于 2023-4-3 14:02 编辑

#申请原创# 【感谢】非常感21ic与国民技术提供这次[size=1em]基于N32G45x的SD卡IAP升级开发活动。感@uant大佬提前把IAP升级的资料放出来。
【艰难的IAP升级之旅】我是第一次使用SD卡进行IAP升级,在接到这个任务后,我查阅了很多资料,同时也购买了TF卡转接卡。回来时刚好接到连续的出差,我是带着macOS电脑出门的,临时安装的虚拟机进行开发。所以遇到了各种想不到的困难。
1、官方提供的例程是SDIO的例程,而我拿的到卡是SPI的转接卡,能查到的资料大多是stm32的资料。由于我的spi转接卡可能是质量有问题,有时读卡有时不读书卡,所以在读卡上面花了很多时间。后面我又重新买了几种卡。这个问题才解决。
2、SD卡读取通信速率的问题。由于初始化时需要提供100K-400K的通迅速率。我用spi的256分频时,时可通迅,时不可通迅,由于我当时出差没有带逻辑分析仪。来来回回调试就是有时可以初始化卡,有时不可以,直到后面我回到家里用示波器与逻辑分析仪查看时钟才发现需要分频到128才满足。
3、当回到家时@uant大佬的作品,我认直学习了几遍,发现他的SD卡读取也是跟我学习的一样,才给了我相信读卡的程序没有问题。
【材料准备】
(1) N32G45XVL-STB V1.1开发板。
(2) TFTSPI转接板。
(3) 16GTFT

【硬件连接】

N32G45VXL
SD转接卡
PB14
CS
PB15
SCK
PB16
MISO
PB17
MOSI
5V
VCC
GND
GND
【开发环境】
(1) MDK5.38
(2) SDKNationstech.N32G45x_Library.2.1.0
(3) 其中logdelay使用bsp目录下的官方例程。
(4) SD移植这里学习了@uant大佬的例子。我在前面做SD卡数据读取时,一直不稳定,后面换了好几片SDTFT卡才稳定。
(5) FatFs

【升级流程图】


【开发环境】
(1) 驱动SD卡:
这里我选用SPI2作为与SD卡的接口,因为开发板的SPI1PA8分配给了LED1使用,如果选用SPI1就会有冲突。具体驱动的程序为SD_Driver.c/h。其中大部分的程序我学习了@uant大佬的程序,但是在while中添加了超时退出的标志位,以免出现意外时卡死。
程序如下:
#include "SD_Driver.h"


uint8_t SD_TYPE=0x00;
uint8_t SD_SPI_Init_Status = 0;

SPI_InitType SD_SPI_Struct;
        
void SD_GPIO_Init(void)
{
        if(SD_SPI_Init_Status)return ;
        SD_SPI_Init_Status = 1;
        
        
        RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO | SD_SPI_PIN_PERIPH, ENABLE);
        if(SD_SPI_PERIPH == RCC_APB2_PERIPH_SPI1){
                RCC_EnableAPB2PeriphClk(SD_SPI_PERIPH, ENABLE);
        }else{
                RCC_EnableAPB1PeriphClk(SD_SPI_PERIPH, ENABLE);
        }
        
        
        GPIO_InitType SD_GPIO_Struct;
        GPIO_InitStruct(&SD_GPIO_Struct);
        
        
        SD_GPIO_Struct.GPIO_Mode                 = GPIO_Mode_AF_PP;
        SD_GPIO_Struct.GPIO_Speed                = GPIO_Speed_50MHz;
        SD_GPIO_Struct.Pin                                        = SD_SPI_PIN_MOSI | SD_SPI_PIN_SCK;                                
        GPIO_InitPeripheral(SD_SPI_PIN_PORT,&SD_GPIO_Struct);
        
        SD_GPIO_Struct.GPIO_Mode                = GPIO_Mode_IN_FLOATING;
        SD_GPIO_Struct.Pin                                        = SD_SPI_PIN_MISO ;
        GPIO_InitPeripheral(SD_SPI_PIN_PORT,&SD_GPIO_Struct);
        
        SD_GPIO_Struct.GPIO_Mode                = GPIO_Mode_Out_PP;
        SD_GPIO_Struct.Pin                                        = SD_SPI_PIN_CS ;
        GPIO_InitPeripheral(SD_SPI_PIN_PORT,&SD_GPIO_Struct);
        SD_CS_HIGH;        
}


void SD_SPI_Init(void)
{
        SD_GPIO_Init();

        SPI_I2S_DeInit(SD_SPIx);
        
        // 初始化SD_SPI
        SPI_InitStruct(&SD_SPI_Struct);
        
        SD_SPI_Struct.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
        SD_SPI_Struct.SpiMode       = SPI_MODE_MASTER;
        SD_SPI_Struct.DataLen       = SPI_DATA_SIZE_8BITS;
        SD_SPI_Struct.CLKPOL        = SPI_CLKPOL_HIGH;
        SD_SPI_Struct.CLKPHA        = SPI_CLKPHA_SECOND_EDGE;
        SD_SPI_Struct.NSS           = SPI_NSS_SOFT;
        SD_SPI_Struct.BaudRatePres  = SPI_BR_PRESCALER_128;
        SD_SPI_Struct.FirstBit      = SPI_FB_MSB;
        SD_SPI_Struct.CRCPoly       = 7;
        SPI_Init(SD_SPIx, &SD_SPI_Struct);
        
        SPI_Enable(SD_SPIx,ENABLE);
        
}

uint8_t SPIx_ReadWriteByte(uint8_t byte)
{
        uint8_t t=0; //超时标志
        /* 等待数据发送寄存器清空 */
        while ((SPI_I2S_GetStatus(SD_SPIx, SPI_I2S_TE_FLAG) == RESET) && t<200)
        {
                t++;
        }
         
        /* 通过SPI发送出去一个字节数据 */
        SPI_I2S_TransmitData(SD_SPIx, byte);
         
        /* 等待接收到一个数据(接收到一个数据就相当于发送一个数据完毕) */
        t = 0;
        while ((SPI_I2S_GetStatus(SD_SPIx, SPI_I2S_RNE_FLAG) == RESET) && t<200)
        {
                t++;
        }
        /* 返回接收到的数据 */
        return SPI_I2S_ReceiveData(SD_SPIx);
}

void SPIx_SetSpeed(u8 SpeedSet)
{
        SD_SPI_Struct.BaudRatePres = SpeedSet ;
        SPI_Init(SD_SPIx, &SD_SPI_Struct);
        SPI_Enable(SD_SPIx,ENABLE);
        
}



///////////////////////////////////////////////////////////////
//发送命令,发完释放
//////////////////////////////////////////////////////////////
int SD_sendcmd(uint8_t cmd,uint32_t arg,uint8_t crc)
{
        DRESULT r1;
  uint8_t retry;
        uint16_t t = 0;
  SD_CS_LOW;
        systick_delay_us(20);
        do{
                t++;
                retry=SPIx_ReadWriteByte(0xFF);
        }while(retry!=0xFF && t<200);

  SPIx_ReadWriteByte(cmd | 0x40);
  SPIx_ReadWriteByte(arg >> 24);
  SPIx_ReadWriteByte(arg >> 16);
  SPIx_ReadWriteByte(arg >> 8);
  SPIx_ReadWriteByte(arg);
  SPIx_ReadWriteByte(crc);
  if(cmd==CMD12)SPIx_ReadWriteByte(0xFF);
        
  do
        {
                r1=SPIx_ReadWriteByte(0xFF);
                t++;
        }while((r1&0X80) && t<=0x0F);
        
        return r1;
}


//读取指定长度数据
DRESULT SD_ReceiveData(uint8_t *data, uint16_t len)
{

        uint8_t r1;
        SD_CS_LOW;
        
        do
        {
                r1 = SPIx_ReadWriteByte(0xFF);        
                systick_delay_us(100);
        }while(r1 != 0xFE);        
        
        while(len--)
        {
                *data = SPIx_ReadWriteByte(0xFF);
                data++;
        }
        
        SPIx_ReadWriteByte(0xFF);
        SPIx_ReadWriteByte(0xFF);                                                                                                   
        return RES_OK;
}
//向sd卡写入一个数据包的内容 512字节
DRESULT SD_SendBlock(uint8_t* buf,uint8_t cmd)
{        
        uint16_t t = 0;        
        uint8_t r1;        
        do{
                r1=SPIx_ReadWriteByte(0xFF);
                t++;
        }while(r1!=0xFF && t<=0x0F);
        
        SPIx_ReadWriteByte(cmd);
        if(cmd!=0XFD)//不是结束指令
        {
                for(t=0;t<512;t++)SPIx_ReadWriteByte(buf[t]);//提高速度,减少函数传参时间
            SPIx_ReadWriteByte(0xFF);//忽略crc
            SPIx_ReadWriteByte(0xFF);
                t=SPIx_ReadWriteByte(0xFF);//接收响应
                if((t&0x1F)!=0x05)return RES_WRPRT;//响应错误                                                                                                                     
        }                                                                                                                                                                       
  return RES_OK;//写入成功
}


//获取CID信息
uint8_t SD_GETCID (uint8_t *cid_data)
{
        if(!SD_TYPE)return 1;
        
        uint8_t r1;
        r1=SD_sendcmd(CMD10,0,0x01); //读取CID寄存器
        if(r1==0x00){
                r1=SD_ReceiveData(cid_data,16);
        }
        SD_CS_HIGH;
        if(r1)return 1;
        else return 0;
}
//获取CSD信息
uint8_t SD_GETCSD(uint8_t *csd_data){
        if(!SD_TYPE)return 1;
        
        uint8_t r1;         
        r1=SD_sendcmd(CMD9,0,0x01);//发CMD9命令,读CSD寄存器
        if(r1==0)
        {
                r1=SD_ReceiveData(csd_data, 16);//接收16个字节的数据
        }
        
        SD_CS_HIGH;//取消片选
        if(r1)return 1;
        else return 0;
}
//获取SD卡的总扇区数
uint32_t SD_GetSectorCount(void)
{
    uint8_t csd[16];
    uint32_t Capacity;  
    uint8_t n;
                uint16_t csize;                                             
        //取CSD信息,如果期间出错,返回0
    if(SD_GETCSD(csd)!=0) return 0;            
    //如果为SDHC卡,按照下面方式计算
    if((csd[0]&0xC0)==0x40)         //V2.00的卡
    {        
                        csize = csd[9] + ((uint16_t)csd[8] << 8) + 1;
                        Capacity = (uint32_t)csize << 10;//得到扇区数                           
    }
                else//V1.XX的卡
    {        
                        n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
                        csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
                        Capacity= (uint32_t)csize << (n - 9);//得到扇区数   
    }
    return Capacity;
}

u32 GetSDCardSectorCount(void)
{
    u8 csd[16];
    u32 Capacity=0;  
          u16 csize;
                //获取SD卡的CSD信息,包括容量和速度信息,存放CID的内存,至少16Byte
                SD_sendcmd(CMD9,0,0x01);//发SDCard_CMD9命令,读CSD
          SD_ReceiveData(csd,16);//接收16个字节的数据
                SD_CS_HIGH;//取消片选
                SPIx_ReadWriteByte(0xff);//提供额外的8个时钟
    if((csd[0]&0xC0)==0x40)  //SDHC卡,按照下面方式计算
    {        
                        csize=csd[9]+(csd[8]<<8)+1;
                        Capacity=csize<<10;//得到扇区数                           
    }
    return Capacity;
}

DSTATUS SD_Init(void)
{
        /*1. 初始化底层IO口*/
        SD_SPI_Init();
        
        // 设置SPI速度,初始化时速度尽量低,防止初始化失败,初始化完成后尽量提高速度
        SPIx_SetSpeed(SPI_BR_PRESCALER_128);

        SD_CS_LOW;
        /*2. 发送最少74个脉冲*/
         for(uint8_t i=0;i<10;i++)SPIx_ReadWriteByte(0xFF);
        
        
        /*3. 进入闲置状态*/
        uint8_t r1;
        uint16_t t = 0;
        do{
                r1 = SD_sendcmd(CMD0 ,0, 0x95);
                t++;
        }while(r1!=0x01 && t<=0xFF);
        
        if(r1!=0x01)
        {
                return FR_INT_ERR;
        }
        
        uint8_t buff[6] = {0};
        uint16_t retry;
        
        /*4. 鉴别SD卡类型*/
        SD_TYPE = 0;
        if(SD_sendcmd(CMD8,0x1AA,0x87)==0x01)
        {
                for(uint8_t i=0;i<4;i++)buff[i]=SPIx_ReadWriteByte(0xFF);        //Get trailing return value of R7 resp
                //printf("R7 Resp:%02X %02X %02X %02X\n",buff[0],buff[1],buff[2],buff[3]);
                if(buff[2] == 0x01 && buff[3] == 0xAA)
                {
                        do
                        {
                                SD_sendcmd(CMD55,0,0x01);                    //发送SDCard_CMD55
                                r1=SD_sendcmd(CMD41,0x40000000,0x01);//发送SDCard_CMD41
                        }while(r1);

                        if(SD_sendcmd(CMD58,0,0x01)==0)                //鉴别SD2.0卡版本开始
                        {
                                for(uint8_t i=0;i<4;i++)buff[i]=SPIx_ReadWriteByte(0XFF);//得到OCR值
                                
                                if(buff[0]&0x40)
                                {
                                        SD_TYPE=V2HC;
                                }
                                else
                                {
                                        SD_TYPE=V2;
                                }
                        }
                }
                else
                {
                        SD_sendcmd(CMD55,0,0X01);                        //发送CMD55
                        r1=SD_sendcmd(CMD41,0,0X01);        //发送CMD41
                        if(r1<=1)
                        {               
                                SD_TYPE=V1;
                                retry=0XFFFE;
                                do //等待退出IDLE模式
                                {
                                        SD_sendcmd(CMD55,0,0X01);        //发送CMD55
                                        r1=SD_sendcmd(CMD41,0,0X01);//发送CMD41
                                }while(r1&&retry--);
                        }else//MMC卡不支持CMD55+CMD41识别
                        {
                                SD_TYPE=MMC;//MMC V3
                                retry=0XFFFE;
                                do //等待退出IDLE模式
                                {                                                                                            
                                        r1=SD_sendcmd(CMD1,0,0X01);//发送CMD1
                                }while(r1&&retry--);  
                        }
                        if(retry==0||SD_sendcmd(CMD16,512,0X01)!=0)SD_TYPE=ERR;//错误的卡
                }
        }

        char cardType[10]={0};
        switch(SD_TYPE)
        {
                case MMC:{ strcpy(cardType,"MMC");break;}
                case V1:{ strcpy(cardType,"SDV1");break;}
                case V2:{ strcpy(cardType,"SDV2");break;}
                case V2HC:{ strcpy(cardType,"SDHC");break;}
                default:{ strcpy(cardType,"ERROR");break;}
        }
        printf("CartType=%s\n",cardType);
        
        SD_CS_HIGH;                //取消片选
         SPIx_ReadWriteByte(0xff);//提供额外的8个时钟
        SPIx_SetSpeed(SPI_BR_PRESCALER_8);
        
        systick_delay_ms(200);
        //SD_CS_LOW;
        printf("GetSDCardSectorCount=%02x\n",GetSDCardSectorCount());
        uint32_t Sector = SD_GetSectorCount();
        printf("SectorCount=%d %.2fMB\n",Sector,(Sector/(1024*1024.0f))*MSD_BLOCKSIZE);
        
        if(SD_TYPE)return FR_OK;
        else return FR_INT_ERR;
}


DSTATUS SD_disk_status(BYTE pdrv)
{
        DSTATUS stat;

        uint8_t ciddata[16]= {0};
        if(SD_GETCID(ciddata))        //调用SD卡的获取设备状态函数接口        
                stat = RES_ERROR;
        else
                stat = RES_OK;

        return stat;
        //return STA_NOINIT;/* Drive not initialized */
}


/*@pdrv Physical drive nmuber to identify the drive */
DSTATUS SD_disk_initialize (BYTE pdrv)
{
        DSTATUS stat;
        uint8_t ciddata[16]= {0};
        SD_Init();//初始化SD卡
        if(SD_GETCID(ciddata))        //重新获取一下SD卡的ID,看是否初始化成功        
                stat = RES_ERROR;
        else
                stat = RES_OK;
        
        return stat;

        //return STA_NOINIT;/* Drive not initialized */
}

//读SD卡
//buf:数据缓存区
//sector:扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
DRESULT SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{
        DRESULT r1;
        if(SD_TYPE!=V2HC)sector <<= 9;//转换为字节地址
        if(cnt==1)
        {
                r1=SD_sendcmd(CMD17,sector,0X01);//读命令
                if(r1==0)//指令发送成功
                {
                        r1=SD_ReceiveData(buf,512);//接收512个字节        
                }
        }
        else
        {
                r1=SD_sendcmd(CMD18,sector,0X01);//连续读命令
                do
                {
                        r1=SD_ReceiveData(buf,512);//接收512个字节         
                        buf+=512;  
                }while(--cnt && r1==0);         
                SD_sendcmd(CMD12,0,0X01);        //发送停止命令
        }   
        SD_CS_HIGH;//取消片选
        return r1;//
}



//写SD卡
//buf:数据缓存区
//sector:起始扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
DRESULT SD_WriteDisk(uint8_t* buf,uint32_t sector,uint8_t cnt)
{
        DRESULT r1;
        if(SD_TYPE!=V2HC)sector *= 512;//转换为字节地址
        if(cnt==1)
        {
                r1=SD_sendcmd(CMD24,sector,0X01);//读命令
                if(r1==0)//指令发送成功
                {
                        r1=SD_SendBlock(buf,0xFE);//写512个字节           
                }
        }else
        {
                if(SD_TYPE!=MMC)
                {
                        SD_sendcmd(CMD55,0,0X01);        
                        SD_sendcmd(CMD23,cnt,0X01);//发送指令        
                }
                 r1=SD_sendcmd(CMD25,sector,0X01);//连续读命令
                if(r1==0)
                {
                        do
                        {
                                r1=SD_SendBlock(buf,0xFC);//接收512个字节         
                                buf+=512;  
                        }while(--cnt && r1==0);
                        r1=SD_SendBlock(0,0xFD);//接收512个字节
                }
        }   
        SD_CS_HIGH;//取消片选
        return r1;//
}        

DRESULT SD_disk_ioctl (
        BYTE pdrv,                /* Physical drive nmuber (0..) */
        BYTE cmd,                /* Control code */
        void *buff                /* Buffer to send/receive control data */
)
{
        DRESULT res;
//        if(pdrv == DEV_SD)
//        {
                switch (cmd)
                {
                        case CTRL_SYNC:
                                res = RES_OK;
                                break;         
                        case GET_SECTOR_SIZE:
                                *(DWORD*)buff = 512; //每个扇区的大小为512字节
                                res = RES_OK;
                                break;         
                        case GET_BLOCK_SIZE:
                                *(WORD*)buff = 8;
                                res = RES_OK;
                                break;         
                        case GET_SECTOR_COUNT:
                                *(DWORD*)buff = SD_GetSectorCount();//调用获取SD卡扇区个数的函数接口
                                res = RES_OK;
                                break;
                        default:
                                res = RES_PARERR;
                                break;
                }
        //}
        if(res)
                return RES_PARERR;
        return res;
}

(1) 移植FatFs文件系统
这里我只使用了ff.cdiskio.c这两个文件。
其中我们需要注意在移植过程中在加载sd卡时要在diskio.c的第42行时匹配SD_disk_status(pdrv)这个函数。既在SD_Driver.c中的DSTATUS SD_disk_status(BYTE pdrv)。这个函数执行了SD的初始化等功能。
(2)iap.c主要是对flash进行写入,以及APP跳转的汇编代码,详细代代码如下:
#include "iap.h"
#include "string.h"

iapfun jump2app;  

uint8_t pages_number = 0;
uint32_t ready_write_addr = 0;

uint8_t flash_buf[FLASH_PAGE_SIZE] = {0};

__asm void MSR_MSP(u32 addr)
{
    MSR MSP, r0                         //set Main Stack value
    BX r14
}

/**================================================================
                APP 跳转
                appxaddr:用户代码起始地址.
================================================================*/
void iap_load_app(u32 appxaddr)
{
        if(((*(vu32*)appxaddr)&0x0FFFFFFF) < 1024*512)                // 检查栈顶地址是否合法.
        {
                jump2app = (iapfun)*(vu32*)(appxaddr+4);                                
                MSR_MSP(*(vu32*)appxaddr);                                                // 初始化堆栈指针
                jump2app();                                                                                // 跳转到APP.
        }
}               

                        

/**
* [url=home.php?mod=space&uid=247401]@brief[/url]
* @param void
* @return
* - `SUCCESS: 表示操作成功
* - 其它值表示出错
*/
uint8_t app_flash_write(uint32_t *data ,uint32_t Flash_address)
{
    uint32_t i;
        uint32_t start_add;
        start_add = Flash_address;
               
        FLASH_Unlock();
        //
        for(i = 0;i<FLASH_PAGE_SIZE/FLASH_PAGE_SIZE;i++)
        {
                FLASH_EraseOnePage(start_add+i*FLASH_PAGE_SIZE);                        //写之前先擦一遍,每次擦2K
        }
        //
        for(i=0;i<FLASH_PAGE_SIZE/4 ;i++)
        {
                if (FLASH_COMPL != FLASH_ProgramWord(start_add+i*4, data[i]))                //写
                {
                        FLASH_Lock();
                        //printf("flash write fail! \r\n");
                        return 1;
                }
        }
        FLASH_Lock();
  return 0;
}


(3)main.c主函数,主要是初始化串口、LED、KEY。按照流程图的设计思路,如果是正常的开机,如果没有检没到KEY1按下,就是直接跳转到APP。如果检测到了KEY有按下,则进行计数,如果达到200次,就进入升级程序。对应程序设计,如果在升级过程中遇到意外,将返回错误代码,在main的while(1)中对应点亮相应的LED灯来给用户提示错误信息。当然如果有接上串口,也可以同步看到串口的打印信息。详见程序如下:
#include "main.h"

// 文件盘符
#define DRIVE_LETTER        "1:"

#define RETRY_TIMES       200     //按键按下检测次数
__IO uint32_t count_time = 0;

uint8_t IAP_Upgrade(char *path);

void LED_Init(void);
void Key_Init(void);
static uint8_t Get_Key(void);


int main(void)
{
        uint8_t res; //升级状态
        LED_Init();
        Key_Init();
        log_init();

        //判断 按键是否按下
        while (Get_Key())
        {
                if(count_time <RETRY_TIMES)  //连续采集200次即2秒钟
                {
                        count_time ++;
                        systick_delay_ms(10);//消抖
                }
                else
                {
                        break;
                }
        }
        //如果按时下间少于设定时间
        if(count_time<RETRY_TIMES)
        {
                        log_debug("跳转到APP\r\n");
                        // 跳转前设置中断向量表
                        SCB->VTOR = FLASH_START_ADDR;
                        // 跳转到APP起始地址,期间不能被其他中断打断,否则会跳转失败
                        iap_load_app(FLASH_START_ADDR);        
                        //如果跳转成功,不会执行到这里
                        return 0;
        }
        else
        {
                log_debug("准备升级固件......\r\n");
        // 传入要升级的文件名称
                res = IAP_Upgrade("project.bin");
        }
        
        
        while(1)
        {
                // 升级失败 根据错误提示闪灯
                if(res == 2) //初始化SD卡失败
                {
                        LED1_ON;
                }
                else if(res == 1 ) //未找到固件
                {
                        LED2_ON;
                        systick_delay_ms(100);
                        LED2_OFF;
                        systick_delay_ms(100);
                }
                else if(res == 3 ) //写入FLASH失败
                {
                        LED2_ON;
                        systick_delay_ms(500);
                        LED2_OFF;
                        systick_delay_ms(500);
                }
                else
                {
                        LED3_ON;//其他错误
                }
        }
}

/* *功  能: 按键检测函数
         *输入参数: 无
         *输出参数: 1 为按下状态
              0 为非按下状态
*/
static uint8_t Get_Key(void)
{
        
        if(GPIO_ReadInputDataBit(KEY3_PORT,KEY3_PIN) == Bit_RESET)
        {
                systick_delay_ms(10);
                if(GPIO_ReadInputDataBit(KEY3_PORT,KEY3_PIN) == Bit_RESET)
                {
                        return 1;
                }
        }
        return 0;
}

/* LED初始化 */
void LED_Init(void)
{
        RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA | RCC_APB2_PERIPH_GPIOB,ENABLE);
        //以下3行是解决LED2显示问题
        //RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO,ENABLE);//使能AFIO复用功能时钟
        RCC->APB2PCLKEN|=1<<0;     //使能AFIO复用功能时钟      
        AFIO->RMP_CFG&=0XF8FFFFFF; //清除AFIO_RMP_CFG的[26:24]
        AFIO->RMP_CFG|=0X02000000; //关闭jtag,启用SWD([26:24]位设置为010

        GPIO_InitType GPIO_InitStrucetre;
        GPIO_InitStruct(&GPIO_InitStrucetre);
        
        GPIO_InitStrucetre.GPIO_Mode                = GPIO_Mode_Out_PP;
        GPIO_InitStrucetre.Pin                                        = LED1_PIN;
        GPIO_InitPeripheral(LED1_PORT,&GPIO_InitStrucetre);
        
        GPIO_InitStrucetre.Pin                                        = LED2_PIN;
        GPIO_InitPeripheral(LED2_PORT,&GPIO_InitStrucetre);
        
        GPIO_InitStrucetre.Pin                                        = LED3_PIN;
        GPIO_InitPeripheral(LED3_PORT,&GPIO_InitStrucetre);
        
        //初始化3个灯为熄灭状态
        LED1_OFF;
        LED2_OFF;
        LED3_OFF;
        
}
// 按键初始化
void Key_Init(void)
{
        GPIO_InitType  KeyGpio_InitStruct;
        RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA,ENABLE);
        
        
        KeyGpio_InitStruct.GPIO_Mode                = GPIO_Mode_IPU;
        KeyGpio_InitStruct.Pin                                        = KEY3_PIN;
        GPIO_InitPeripheral(KEY3_PORT,&KeyGpio_InitStruct);

}

/* *功  能: IAP升级
         *输入参数: 文件路径
         *输出参数: 1 固件文件未找到
              2 SD卡加载错误
                                                        3 固件文件打开出错!
                                                        99 其他错误
   *备注:如果升级成功,会跳转到APP,就不会存在返回值
*/
uint8_t IAP_Upgrade(char *path)
{
        char _path[50]={0};
        // 判断是否有盘符,没有则加上盘符
        if(strncmp(path,DRIVE_LETTER,strlen(DRIVE_LETTER)) != 0)
        {
                sprintf(_path,"%s%s",DRIVE_LETTER,path);
        }else{
                strcpy(_path,path);
        }
        
        
        FATFS *fs;
        fs = malloc(sizeof (FATFS));           /* 申请内存 */
        FRESULT res=f_mount(fs,DRIVE_LETTER,1);                //挂载
        
        if(res == FR_OK)
        {
                log_debug("SD卡加载成功!\n");
               
                // 开始检查是否存在指定的文件
                FILINFO fno;
               
                FRESULT fr = f_stat(_path,&fno);
                if(fr != FR_OK)
                {
                        log_error("固件文件:%s未找到\n", _path);
                        return 1;
                }
        
        }else{
                log_error("SD卡加载错误!\n");
                return 2;
        }
        
        log_debug("开始升级固件 ... \n");
        
        // 写入Flash页码
        uint16_t pages_number = 0;
        // 读写Flash地址
        uint32_t ready_write_addr = 0;
        char buff[FLASH_PAGE_SIZE] ;
        UINT br = 0;
        
        FIL fil;  
        FRESULT fr = f_open(&fil, _path, FA_READ);
        if(fr == FR_OK)
        {
                do{
                        memset(buff,0,FLASH_PAGE_SIZE);
                        f_read(&fil,buff,FLASH_PAGE_SIZE,&br);
                        if(br > 0)
                        {
                                ready_write_addr = FLASH_APP_BASE_ADDR + pages_number*FLASH_PAGE_SIZE;
                                while(app_flash_write((uint32_t *)buff , ready_write_addr) == 1);               
                                pages_number++;
                        }
                } while(br);
               
                f_close(&fil);
               
                if(pages_number > 0)
                {
                        log_debug("升级完成,正在跳转到APP.\n\n\n");        
                        // 跳转前设置中断向量表
                        SCB->VTOR = FLASH_START_ADDR;        
                        // 跳转到APP起始地址,期间不能被其他中断打断,否则会跳转失败                        
                        iap_load_app(FLASH_START_ADDR);                                
                }
                return 99;
        }else{
                log_error("固件文件打开出错!!\n");
                return 3;
        }
        return 99;
}

(4)APP程序,APP程序不是这次重点的设计,其功能为进入后,打印"App Init Ok",LED进行流水灯以示APP运转正常。代码如下:
#include "main.h"


// LED初始化
void LED_Init(void);
void LED_Flash(void);

int main(void)
{
        log_init();
        LED_Init();
        
        systick_delay_ms(500);
        printf("\nApp Init Ok.\n");
        

  while(1)
  {
                LED_Flash();
               
        }
}


/* LED初始化 */
void LED_Init(void)
{
        RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA | RCC_APB2_PERIPH_GPIOB,ENABLE);
        //以下3行是解决LED2显示问题
        //RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO,ENABLE);//使能AFIO复用功能时钟
        RCC->APB2PCLKEN|=1<<0;     //使能AFIO复用功能时钟      
        AFIO->RMP_CFG&=0XF8FFFFFF; //清除AFIO_RMP_CFG的[26:24]
        AFIO->RMP_CFG|=0X02000000; //关闭jtag,启用SWD([26:24]位设置为010

        GPIO_InitType GPIO_InitStrucetre;
        GPIO_InitStruct(&GPIO_InitStrucetre);
        
        GPIO_InitStrucetre.GPIO_Mode                = GPIO_Mode_Out_PP;
        GPIO_InitStrucetre.Pin                                        = LED1_PIN;
        GPIO_InitPeripheral(LED1_PORT,&GPIO_InitStrucetre);
        
        GPIO_InitStrucetre.Pin                                        = LED2_PIN;
        GPIO_InitPeripheral(LED2_PORT,&GPIO_InitStrucetre);
        
        GPIO_InitStrucetre.Pin                                        = LED3_PIN;
        GPIO_InitPeripheral(LED3_PORT,&GPIO_InitStrucetre);
        
        //初始化3个灯为熄灭状态
        LED1_OFF;
        LED2_OFF;
        LED3_OFF;
        
}

void LED_Flash(void)
{
        LED1_ON;
        LED2_OFF;
        LED3_OFF;
        systick_delay_ms(5000);
        LED1_OFF;
        LED2_ON;
        LED3_OFF;
        systick_delay_ms(5000);
        LED1_OFF;
        LED3_ON;
        LED2_OFF;
        systick_delay_ms(5000);
}
在system_n32g45x.c中,定义VECT_TAB_SRAM偏移量:
/* #define VECT_TAB_SRAM */
#define VECT_TAB_OFFSET 0x4000 /*!< Vector Table base offset field. This value must be a multiple of 0x200. */


【内置Flash分配】
1、IAP升级方案中最重要的是内置Flash分配。BootLoader我经过编译后,总占用ROM为15.8K。本着有预留余地的原则(如果flash紧张,可以考虑把串口打印去如掉),我给BootLoader分配16K也就是0x000-0x3FFF的大小。所以总的Flash分配如下图。

2、在MDK工程中BootLoadr的工程ROM以及RAM的设置如下图:

按照这样的配置,编译后用dap_link直接下载到开发板。
3、在MDK工程中APP的工程ROM以及RAM的设置如下图:

APP编译后把bin命名为Project.bin,并把bin文件拷贝到SD卡的根目录下面以备用。
【升级操作】
1、把已经拷好Project.bin的SD卡插入到读书器上。
2、按下KEY1键不放,再按一下复位键,等到2秒钟后,会自动进入固件下载。如果接着串口如会看到如下提示信息:
[20:16:25.683]收←◆准备升级固件......
CartType=SDHC

[20:16:25.784]收←◆GetSDCardSectorCount=1dacc00
SectorCount=31116288 15193.50MB
SD卡加载成功!
开始升级固件 ...

[20:16:25.944]收←◆升级完成,正在跳转到APP.

\0
[20:16:25.980]收←◆?
App Init Ok.
同时三个LED灯会以流水灯的形式运行。
3、升级错误信息:
  • 如果按下了KEY达到2秒以上,但是没有检测到SD卡,,在串口会提示SD卡加载错误,同时LED1常亮。
[21:27:08.831]收←◆准备升级固件......

[21:27:09.004]收←◆SD卡加载错误!
  • 如果SD卡插上了,但是没有对应的bin,会提示借误,固件文件:1:project.bin未找到,LED2快速闪。错误信息如下:
[21:29:12.384]收←◆准备升级固件......

[21:29:12.562]收←◆CartType=SDHC

[21:29:12.646]收←◆GetSDCardSectorCount=1dacc00
SectorCount=31116288 15193.50MB
SD卡加载成功!
固件文件:1:project.bin未找到
  • 如果是因为写flash的中途出错,LED2会慢闪提示,同时在串口提示相应的错误信息。
  • 如果是跳转或其他的错误LED3会常亮。
4、错误处理:
  • 如果没有检测到SD卡,检查一下插卡是否到位,或者更换一张卡,SD卡可格式化为FAT32格式。
  • 如果提示bin文件名错误,请检查是否拷对了固件,固件名称是否正确。(指定固定的固件名,目的是防止误烧固件)
  • 如果是烧写中错误,请检查SD卡接触是否正常。然后重新操作。
  • 如果不进入升级,请确定按键是否正常,按时间需要2秒错以上。

【总结】
对于这次IAP升级来说,虽然经历了很多挫折,但是最终还是圆满的完成了任务。再次感论坛,感谢国民技术,感@uant提前把作品提交,让我学习到他这么优秀的作品。
APP_N32G457.zip (1.12 MB) BootLoader_N32G457.zip (6.38 MB)


  

使用特权

评论回复
沙发
七毛钱| | 2023-4-6 15:03 | 只看该作者
功夫不负有心人,支持一下

使用特权

评论回复
板凳
lulugl|  楼主 | 2023-4-6 16:43 | 只看该作者
七毛钱 发表于 2023-4-6 15:03
功夫不负有心人,支持一下

多谢大佬的支持与鼓励!

使用特权

评论回复
地板
mickit| | 2023-4-8 22:08 | 只看该作者
SD卡升级功能确实非常棒呢。              

使用特权

评论回复
5
jtracy3| | 2023-4-8 22:17 | 只看该作者
这个是支持热插拔的吗              

使用特权

评论回复
6
lulugl|  楼主 | 2023-4-8 22:27 | 只看该作者
jtracy3 发表于 2023-4-8 22:17
这个是支持热插拔的吗

这个没有设计呀,因为IAP是需要非常要小心的,如果没有备份,升级到一半挂了,不好处理。如果想要热拨插,那就得在APP里面做设计了,可以开一个任务,专门来检测SD卡的状态。

使用特权

评论回复
7
uptown| | 2023-4-9 14:42 | 只看该作者
N32G45x的BootLoader在哪里下载的

使用特权

评论回复
8
biechedan| | 2023-4-9 14:48 | 只看该作者
这个需要修改BootLoader吗

使用特权

评论回复
9
usysm| | 2023-4-9 14:57 | 只看该作者
怎么验证写入的程序是正确的呢?              

使用特权

评论回复
10
sdCAD| | 2023-4-9 15:23 | 只看该作者
可以实现远程代码升级吗?              

使用特权

评论回复
11
elsaflower| | 2023-4-9 15:33 | 只看该作者
有spi驱动SD卡的例程吗              

使用特权

评论回复
12
macpherson| | 2023-4-9 15:53 | 只看该作者
SDIO读写的稳定性怎么样              

使用特权

评论回复
13
everyrobin| | 2023-4-9 16:10 | 只看该作者
IAP升级需要注意什么呢              

使用特权

评论回复
14
plsbackup| | 2023-4-9 16:14 | 只看该作者
IAP的升级方式真是多呢。              

使用特权

评论回复
15
lulugl|  楼主 | 2023-4-9 17:57 | 只看该作者
uptown 发表于 2023-4-9 14:42
N32G45x的BootLoader在哪里下载的

BootLoader的话是自己写的,就是帖子里的程序。

使用特权

评论回复
16
lulugl|  楼主 | 2023-4-9 17:58 | 只看该作者
biechedan 发表于 2023-4-9 14:48
这个需要修改BootLoader吗

BootLoader,主要管理APP的固件升级,产品上线后,非必要不会改写,当然也可以在APP上修改,但是风险非常大,非常很严重的BUG,要不不会这样做的。

使用特权

评论回复
17
lulugl|  楼主 | 2023-4-9 18:00 | 只看该作者
uptown 发表于 2023-4-9 14:42
N32G45x的BootLoader在哪里下载的

N32G45x的BootLoader,这个也是编写的,这次活动,大家都有写的BootLoaer上传,你可以下载下来看看,我的注释写得非常详细的。

使用特权

评论回复
18
lulugl|  楼主 | 2023-4-9 18:01 | 只看该作者
biechedan 发表于 2023-4-9 14:48
这个需要修改BootLoader吗

BootLoader也是一个程序,按自己的设计就行,BootLoadr上线后,基本上不会动了的。

使用特权

评论回复
19
lulugl|  楼主 | 2023-4-9 18:05 | 只看该作者
usysm 发表于 2023-4-9 14:57
怎么验证写入的程序是正确的呢?

验证写入的程序是否正确,这是在写APP时就会验证。但是有些BUG可能设计时没有考虑到,其实IAP也就为了修正BUG或者是用户有新需要来设计的。通过简单的步聚就可以升级原先的APP。
还有就是验证固件是否完整,这需要做较验,比如说做完整性等较验,如果是上线的产品,这个步骤是必要的,还有一些情况下,会先备份原来的固件,如果下载的APP有问题,还可以恢复以前的固件,但是这样,投入成本就比较高。常见的就是电脑主板上的BIOS,有些高端主板有双BIOS,可以一键恢复等等。

使用特权

评论回复
20
lulugl|  楼主 | 2023-4-9 18:07 | 只看该作者
sdCAD 发表于 2023-4-9 15:23
可以实现远程代码升级吗?

实现远程升级也是可以的,但是要有连联网的条件,要有一系列的固件管理系统。还要考虑安全性==,看应用环境吧。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

137

主题

669

帖子

6

粉丝