| 银杏科技有限公司旗下技术文档发布平台 | 
	
		| 技术支持电话 | 0379-69926675-801 | 
	
		| 技术支持邮件 | Gingko@vip.163.com | 
	
		| 版本 | 日期 | 作者 | 修改内容 | 
	
		| V1.0 | 2020-07-10 | gingko | 初次建立 | 
实验二十九:SD_IAP_FPGA实验——更新升级FPGA
一、 实验目的与意义
-  了解FPGA的IAP结构。 
-  了解FPGA的IAP特征。 
-  掌握FPGA的IAP的使用方法。 
-  掌握SD卡的使用方法。 
-  掌握FATFS的使用方法。 
-  掌握STM32 HAL库中FATFS属性的配置方法。 
-  掌握KEIL MDK 集成开发环境使用方法。 
 
二、 实验设备及平台
- 
- 
-  Micro USB线缆。 
-  SD卡。 
-  Keil MDK 开发平台。 
-  STM32CubeMX开发平台。 
-  装有WIN XP(及更高版本)系统的计算机。 
 
三、 实验原理
1、IAP简介
-  IAP是In Application Programming的首字母缩写,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。 
-  通常在用户需要实现IAP功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信管道(如USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在User Flash中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作: - 
-  (1)检查是否需要对第二部分代码进行更新 
-  (2)如果不需要更新则转到4) 
-  (3)执行更新操作 
-  (4)跳转到第二部分代码执行 
 
-  第一部分代码必须通过其它手段,如JTAG或ISP烧入;第二部分代码可以使用第一部分代码IAP功能烧入,也可以和第一部分代码一道烧入,以后需要程序更新是再通过第一部分IAP代码更新。 
-  对于STM32来说,因为它的中断向量表位于程序存储器的最低地址区,为了使第一部分代码能够正确地响应中断,通常会安排第一部分代码处于Flash的开始区域,而第二部分代码紧随其后。 
-  在第二部分代码开始执行时,首先需要把CPU的中断向量表映像到自己的向量表,然后再执行其他的操作。 
-  如果IAP程序被破坏,产品必须返厂才能重新烧写程序,这是很麻烦并且非常耗费时间和金钱的。针对这样的需求,STM32在对Flash区域实行读保护的同时,自动地对用户Flash区的开始4页设置为写保护,这样可以有效地保证IAP程序(第一部分代码)区域不会被意外地破坏。 
 
2、FPGA简介
-  FPGA(Field Programmable Gate Array)是在PAL、GAL等可编程器件的基础上进一步发展的产物。它是作为专用集成电路(ASIC)领域中的一种半定制电路而出现的,既解决了定制电路的不足,又克服了原有可编程器件门电路数有限的缺点。 
-  FPGA 器件属于专用集成电路中的一种半定制电路,是可编程的逻辑列阵,能够有效的解决原有的器件门电路数较少的问题。FPGA 的基本结构包括可编程输入输出单元,可配置逻辑块,数字时钟管理模块,嵌入式块RAM,布线资源,内嵌专用硬核,底层内嵌功能单元。由于FPGA具有布线资源丰富,可重复编程和集成度高,投资较低的特点,在数字电路设计领域得到了广泛的应用。FPGA的设计流程包括算法设计、代码仿真以及设计、板机调试,设计者以及实际需求建立算法架构,利用EDA建立设计方案或HD编写设计代码,通过代码仿真保证设计方案符合实际要求,最后进行板级调试,利用配置电路将相关文件下载至FPGA芯片中,验证实际运行效果。 
-  FPGA采用了逻辑单元阵列LCA(Logic Cell Array)这样一个概念,内部包括可配置逻辑模块CLB(Configurable Logic Block)、输入输出模块IOB(Input Output Block)和内部连线(Interconnect)三个部分。 现场可编程门阵列(FPGA)是可编程器件,与传统逻辑电路和门阵列(如PAL,GAL及CPLD器件)相比,FPGA具有不同的结构。FPGA利用小型查找表(16×1RAM)来实现组合逻辑,每个查找表连接到一个D触发器的输入端,触发器再来驱动其他逻辑电路或驱动I/O,由此构成了既可实现组合逻辑功能又可实现时序逻辑功能的基本逻辑单元模块,这些模块间利用金属连线互相连接或连接到I/O模块。FPGA的逻辑是通过向内部静态存储单元加载编程数据来实现的,存储在存储器单元中的值决定了逻辑单元的逻辑功能以及各模块之间或模块与I/O间的联接方式,并最终决定了FPGA所能实现的功能,FPGA允许无限次的编程。 
 
3、FATFS文件系统简介
4、FATFS的特点
5、FATFS模块的层次结构图
 
-  最顶层是应用层,使用者无需理会FATFS的内部结构和复杂的FAT协议,只需要调用FATFS模块提供给用户的一系列应用接口函数,如f_open,f_read,f_write和f_close等,就可以像在PC上读/写文件那样简单。 
-  中间层FATFS模块,实现了FAT文件读/写协议。FATFS模块提供的是ff.c和ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。 
-  需要我们编写移植代码的是FATFS模块提供的底层接口,它包括存储媒介读/写接口(diskI/O)和供给文件创建修改时间的实时时钟。 
 
6、原理图
四、 实验程序
1、主函数
int main(void)
{
    int i;
    int k;
    unsigned int counter;
    unsigned long int ncounter = 0;
    unsigned char buffer[1024];
    FIL fil;
    FATFS fatfs;
    static FRESULT res;
    /* MCU 配置*/
    /* 重置所有外设, 初始化Flash接口和Systick. */
    HAL_Init();
    /* 系统时钟配置 */
    SystemClock_Config();
    /* 初始化所有已配置的外设 */
    MX_GPIO_Init();
    //如果上电时检测到按键按下,则进入虚拟U盘模式,重新上电则开始升级FPGA
    if(ARM_KEY_STATE == KEY_DOWN){
        MX_USB_DEVICE_Init();
        while(1){
            LED_RED_ON;
            HAL_Delay(300);
            LED_RED_OFF;
            HAL_Delay(300);
        }
    }
    //SD卡初始化
    if(BSP_SD_Init() != MSD_OK){
        //初始化失败,红色LED快闪
        while(1){
            LED_RED_ON;
            for(i = 0;i < 3000000;i ++);
            LED_RED_OFF;
            for(i = 0;i < 3000000;i ++);    
        }
    }
    HAL_Delay(500);
    //判断f_mount是否成功
    res = f_mount(&fatfs,"0",1);
    if(res != RES_OK){
        while(1){
            LED_RED_ON;
            for(i = 0;i < 3000000;i ++);
            LED_RED_OFF;
            for(i = 0;i < 3000000;i ++);    
        }
    }
 
    res = f_open(&fil,"0:/system/sram.rbf",FA_READ);   //只读打开文件
    if(res != RES_OK){
        //打开失败,蓝灯快闪
        while(1){
            LED_BLUE_ON;
            for(i = 0;i < 3000000;i++);
            LED_BLUE_OFF;
            for(i = 0;i < 3000000;i++);
        }
    }   
 
    res = f_lseek(&fil,0);           //将指针移动到第一个位置
    if(res != RES_OK){
        //白色
        LED_RED_ON;
        LED_BLUE_ON;
        LED_GREEN_ON;
        while(1){
        }
    }
    //开始升级FPGA程序
    NCONFIG_OFF;
    DCLK_OFF;
    for(i = 0; i < 5000; i++);
    if(NSTATUS == 1)
    {
        LED_RED_ON;
        return 0;       
    }
    for(i = 0;i < 40;i++);
    NCONFIG_ON;
    for(i = 0; i < 40; i++);
    while(ncounter < fil.fsize)
    {
    res = f_read(&fil,buffer,1024,&counter);     //读取数据  
        if(res != RES_OK){
            //读取失败,红灯慢闪
            while(1){
                LED_RED_ON;
                for(i = 0;i < 10000000;i++);
                LED_RED_OFF;
                for(i = 0;i < 10000000;i++);
            }
        }
        for(k = 0; k < counter; k++)
        {
          for(i = 0; i < 8; i++)
          {
          if(buffer[k]&0x01)DATA0_ON;
                else DATA0_OFF;
                DCLK_ON;
                buffer[k] >>= 1;
              DCLK_OFF;
        }
          ncounter++;
      }
  }   
    if(CONFIG_DONE == 1){
        LED_GREEN_ON;
    }else {
        LED_BLUE_ON;
    }
    for(i = 0; i < 40; i++)
    {
     DCLK_ON;
         for(i = 0; i < 800; i++);          //延时 100us
         DCLK_OFF;
         for(i = 0; i < 800; i++);          //延时 100us
    }   
  while (1)
  {
  }
}
 
2、USB DEVICE初始化
void MX_USB_DEVICE_Init(void)
{
  /* 初始化设备库,添加支持的类并启动该库*/
  USBD_Init(&hUsbDeviceHS, &HS_Desc, DEVICE_HS);
  USBD_RegisterClass(&hUsbDeviceHS, &USBD_MSC);
  USBD_MSC_RegisterStorage(&hUsbDeviceHS, &USBD_Storage_Interface_fops_HS);
 
 
3、SD卡初始化
uint8_t BSP_SD_Init(void)
{ 
  /* 默认情况下,初始化SDMMC1 */
  return BSP_SD_InitEx(SD_CARD1);
}
/**
  * @brief  初始化SD卡设备。
  * @param  SdCard:要使用的SD卡,应该为SD_CARD1或SD_CARD2 
 * @retval SD status
  */
uint8_t BSP_SD_InitEx(uint32_t SdCard)
{ 
  uint8_t sd_state = MSD_OK;
  /* uSD设备接口配置 */
  if(SdCard == SD_CARD1)
  {  
    uSdHandle.Instance = SDMMC1;
    uSdHandle.Init.ClockEdge           = SDMMC_CLOCK_EDGE_RISING;
    uSdHandle.Init.ClockBypass         = SDMMC_CLOCK_BYPASS_DISABLE;
    uSdHandle.Init.ClockPowerSave      = SDMMC_CLOCK_POWER_SAVE_DISABLE;
    uSdHandle.Init.BusWide             = SDMMC_BUS_WIDE_1B;
    uSdHandle.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_DISABLE;
    uSdHandle.Init.ClockDiv            = SDMMC_INIT_CLK_DIV;
  }
  else
  {
    uSdHandle2.Instance = SDMMC2;
    uSdHandle2.Init.ClockEdge           = SDMMC_CLOCK_EDGE_RISING;
    uSdHandle2.Init.ClockBypass         = SDMMC_CLOCK_BYPASS_DISABLE;
    uSdHandle2.Init.ClockPowerSave      = SDMMC_CLOCK_POWER_SAVE_DISABLE;
    uSdHandle2.Init.BusWide             = SDMMC_BUS_WIDE_1B;
    uSdHandle2.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_DISABLE;
    uSdHandle2.Init.ClockDiv            = SDMMC_TRANSFER_CLK_DIV;   
  }
  /*检查SD卡是否插入插槽 */
  if(SdCard == SD_CARD1)
  {
    /* Msp SD初始化 */
    BSP_SD_MspInit(&uSdHandle, NULL);
     /* HAL SD初始化 */
    if(HAL_SD_Init(&uSdHandle) != HAL_OK)
    {
      sd_state = MSD_ERROR;
    }  
  }
  else
  {
    /* Msp SD初始化 */
    BSP_SD_MspInit(&uSdHandle2, NULL);
    /* HAL SD初始化 */
    if(HAL_SD_Init(&uSdHandle2) != HAL_OK)
    {
      sd_state = MSD_ERROR;
    }    
  }
  /* 配置SD总线宽度 */
  if(sd_state == MSD_OK)
  {
    if(SdCard == SD_CARD1)
    {    
      /* 启用宽度操作 */
      sd_state = HAL_SD_ConfigWideBusOperation(&uSdHandle, SDMMC_BUS_WIDE_4B); 
    }
    else
    {
      /* 启用宽度操作 */    
      sd_state = HAL_SD_ConfigWideBusOperation(&uSdHandle2, SDMMC_BUS_WIDE_4B);
    }
    if(sd_state != HAL_OK)
    {
      sd_state = MSD_ERROR;
    }
    else
    {
      sd_state = MSD_OK;
    }
  }
  return  sd_state;
}
 
 
 
五、 实验步骤
-  将升级文件拷贝到SD卡system文件夹下; 
-  把仿真器与iCore4的SWD调试口相连(直接相连或者通过转接器相连); 
-  将跳线冒插在USB OTG; 
-  把iCore4(USB OTG)通过Micro USB线与计算机相连,为iCore4供电; 
-  打开Keil MDK 开发环境,并打开本实验工程; 
-  烧写程序到iCore4上; 
-  也可以进入Debug模式,单步运行或设置断点验证程序逻辑。 
 
六、 实验现象
-  烧写程序成功,绿色ARM·LED灯点亮,三色FPGA·LED灯循环点亮,烧写失败,如果挂载SD卡失败,红灯快闪,如果打开文件失败,蓝灯快闪,读取文件指针移动失败,白灯点亮,升级失败,红灯慢闪。 
-  上电时按着ARM·KEY,进入虚拟U盘模式,计算机将出现一个磁盘,可将升级文件拷入SD卡。: