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

[复制链接]
 楼主| uant 发表于 2023-3-23 09:00 | 显示全部楼层 |阅读模式
本帖最后由 uant 于 2023-3-23 09:20 编辑

#申请原创#    @安小芯 本次项目完成走了不少坑,也更理解了代码实现的原理,整个过程算是一波三折,不过还是顺利的完成了整个功能的测试,至于遇到的坑在最后经验总结中聊聊。    哪么开始之前,我就先把自己理解的原理讲解下:
    要想实现固件的自升级,就是把新的固件按一个固定的模式写入到Flash中,程序运行会有一个入口,这就是入口地址,而ARM的好像都是从0x8000000这个地址开始,至少我目前用到的都是这样,当我们使用某种方式(编程器或串口烧录)成功把固件从这个起始地址写入到Flash中,哪么就正常来说就可以正常运行。但想要程序运行时把新固件从这个地址会写入到Flash会遇到一个问题,因程序本身也是从0x8000000这个地址运行,运行时又要读取Flash中数据,而向这个地址写入数据的话,就会把本身的运行环境破坏,也就是运行时读取到的数据被改变了,导致程序出错卡死。所以如果是自身更新自身的话,肯定是有问题的,最好的办法就是另一个程序来更新,由更新程序来向另一个地址来写入,这样本身数据不会被破坏,也就能正常更新了。
    通过以上的分析,我们就可以做这样的事情,让一个程序先运行,这个程序呢不干别的事情,仅做更新工作,另一个程序专门负责业务逻辑,当需要更新时,我们就可以跳到专门负责更新的程序中,更新完成后就自动跳到业务逻辑程序中。
    为了方便解说,我们就把负责更新的程序叫做Bootloader,负责业务逻辑的程序叫做APP(这里引用了国民技术官方示例的说法,进行统一方便大家在看官方示例时容易理解)。而我们想要实现SD卡升级功能,就需驱动SD卡,而文件存储在SD卡中都是按二进制方式存储的,想要读取文件升级就需要一个文件系统,也就是文件系统移植,所以本文将从以下几点开始讲解:
1、基于SPI的SD卡驱动的移植;
2、FATFS文件系统的移植;
3、IAP升级的实现
4、使用微信小程序实现IAP升级

然后就是开发环境和硬件了:
# 开发环境
系统: windows 11
开发软件: Keil 5.36
DAP-Link: 板载 NS-Link

# 开发板
N32G45XVL-STB v1.1

一、基于SPI的SD卡驱动的移植

1、基础知识及硬件连接
   在讲SD卡移植之前,我们稍稍提下SD卡方面的内容,我们先来了解下 SD卡驱动的两种方式:  SDIO、SPI,以下是他们的异同点:
【SDIO模式】
    CLK:时钟,通信过程需要的东西,没这个东西,数据会不稳定
    CMD:命令,可下达命令,例如读取SD卡的信息,或是写入数据等等
    DAT0、1、2、3:四条数据线
    VCC、VSS:电源和地

【SPI模式】
    CLK:时钟,理由同SDIO里面的CLK
    MOSI:命令或数据输出到SD卡
    MISO:SD卡传输数据到主机,数据线
    CS:片选,选择是否要操作当前的SD卡    VCC、VSS:电源和地

从上面我们可以看到 SDIO模式有四条数据线,而SPI模式下只有一条数据线 MISO,所以说SDIO速度会优于SPI模式。但是SDIO需要硬件支持,而SPI却更灵活,就算没有硬件SPI,也可以使用多余的IO进行模拟,通用性更好。考虑到后期方便移植,所以本次还是以SPI模式方式来移植,因为仅需要改动比较少的代码就可以在不同芯片上运行起来,更方便和快捷。  
关于这方面的知道,我给出了几个参考的文章的地址:
SD卡移植及时序讲解: https://blog.csdn.net/xiaolong1126626497/article/details/127985503

我使用的是SD卡扩展卡,样式如下:
IMG_3046.jpg
   这个上面已经标识清楚了SD卡的引脚信息,比较方便连接,如果是SD卡槽或TF卡槽也不要紧,下面也给出连接方法:
   SD卡和卡座
SD卡.png
SD卡座.png

TF卡和卡座
TF卡.png
TF卡座.png
以上图片来源于:https://blog.csdn.net/jiangfutao/article/details/124466153

接下来就是连接扩展板和开发板了,接线方式如下:
开发板        扩展板
PB12    ----  CS
PB13    ----  SCK
PB14    ----  MISO
PB15    ----  MOSI
5V       ----  VCC
GND    ----  GND  
请注意:这里扩展板的VCC一定要连开发板5V,因为扩展板上用的是AMS1117,这个LDO压降比较大,如果使用3.3V供电的话,输出可能降到2.2V,这样可能导致TF卡无法正常工作。

接下来看下连接完成的样子:
IMG_3047.jpg
好了,开发板连接完成了,下面就可以上电写代码部分了。


2、软件移植
    SD卡驱动编写
  1. #ifndef SD_DRIVER_H_
  2. #define SD_DRIVER_H_

  3. #include "main.h"

  4. // SD SPI初始化定义
  5. #define SD_SPIx                                           SPI2
  6. #define SD_SPI_PERIPH                                RCC_APB1_PERIPH_SPI2
  7. #define SD_SPI_PIN_PORT                        GPIOB
  8. #define SD_SPI_PIN_PERIPH                RCC_APB2_PERIPH_GPIOB
  9. #define SD_SPI_PIN_MISO                        GPIO_PIN_14
  10. #define SD_SPI_PIN_MOSI                        GPIO_PIN_15
  11. #define SD_SPI_PIN_SCK                        GPIO_PIN_13
  12. #define SD_SPI_PIN_CS                                GPIO_PIN_12

  13. //SD卡类型
  14. #define ERR             0x00
  15. #define MMC                                0x01
  16. #define V1                                0x02
  17. #define V2                                0x04
  18. #define V2HC                        0x06

  19. #define DUMMY_BYTE                                 0xFF
  20. #define MSD_BLOCKSIZE                         512

  21. //CMD定义
  22. #define CMD0    0       //卡复位
  23. #define CMD1    1
  24. #define CMD8    8       //命令8 ,SEND_IF_COND
  25. #define CMD9    9       //命令9 ,读CSD数据
  26. #define CMD10   10      //命令10,读CID数据
  27. #define CMD12   12      //命令12,停止数据传输
  28. #define CMD16   16      //命令16,设置扇区大小(SectorSize) 应返回0x00
  29. #define CMD17   17      //命令17,读扇区(sector)
  30. #define CMD18   18      //命令18,读多个扇区(Multi sector)
  31. #define CMD23   23      //命令23,设置多扇区(sector)写入前预先擦除N个block
  32. #define CMD24   24      //命令24,写扇区(sector)
  33. #define CMD25   25      //命令25,写多个扇区(Multi sector)
  34. #define CMD41   41      //命令41,应返回0x00
  35. #define CMD55   55      //命令55,应返回0x01
  36. #define CMD58   58      //命令58,读OCR信息
  37. #define CMD59   59      //命令59,使能/禁止CRC,应返回0x00

  38. //数据写入回应字意义
  39. #define MSD_DATA_OK                0x05
  40. #define MSD_DATA_CRC_ERROR         0x0B
  41. #define MSD_DATA_WRITE_ERROR       0x0D
  42. #define MSD_DATA_OTHER_ERROR       0xFF
  43. //SD卡回应标记字
  44. #define MSD_RESPONSE_NO_ERROR      0x00
  45. #define MSD_IN_IDLE_STATE          0x01
  46. #define MSD_ERASE_RESET            0x02
  47. #define MSD_ILLEGAL_COMMAND        0x04
  48. #define MSD_COM_CRC_ERROR          0x08
  49. #define MSD_ERASE_SEQUENCE_ERROR   0x10
  50. #define MSD_ADDRESS_ERROR          0x20
  51. #define MSD_PARAMETER_ERROR        0x40
  52. #define MSD_RESPONSE_FAILURE       0xFF


  53. #define SD_CS_LOW                        GPIO_ResetBits(SD_SPI_PIN_PORT,SD_SPI_PIN_CS);
  54. #define SD_CS_HIGH                GPIO_SetBits(SD_SPI_PIN_PORT,SD_SPI_PIN_CS);

  55. // SD卡SPI初始化
  56. void SD_SPI_Init(void);
  57. // SD初始化
  58. DSTATUS SD_Init(void);
  59. // 读盘
  60. DRESULT SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt);
  61. // 写盘
  62. DRESULT SD_WriteDisk(uint8_t* buf,uint32_t sector,uint8_t cnt);

  63. #endif



代码编写:
  1. // SD_Driver.c
  2. #include "SD_Driver.h"
  3. // SD卡类型
  4. uint8_t SD_TYPE=0x00;
  5. // SPI初始化状态,防止反复进行初始化
  6. uint8_t SD_SPI_Init_Status = 0;
  7. SPI_InitType SD_SPI_Struct;
  8. // SD卡SPI初始化
  9. void SD_SPI_Init(void)
  10. {
  11.         if(SD_SPI_Init_Status)return ;
  12.       
  13.         SD_SPI_Init_Status = 1;
  14.       
  15.         // 开启GPIO和复用时钟
  16.         RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO | SD_SPI_PIN_PERIPH, ENABLE);
  17.         // 根据配置的SPI对象开启SPI时钟
  18.         if(SD_SPI_PERIPH == RCC_APB2_PERIPH_SPI1){
  19.                 RCC_EnableAPB2PeriphClk(SD_SPI_PERIPH, ENABLE);
  20.         }else{
  21.                 RCC_EnableAPB1PeriphClk(SD_SPI_PERIPH, ENABLE);
  22.         }
  23.       
  24.         // GPIO初始化
  25.         GPIO_InitType SD_GPIO_Struct;
  26.         GPIO_InitStruct(&SD_GPIO_Struct);
  27.       
  28.         SD_GPIO_Struct.GPIO_Mode                 = GPIO_Mode_AF_PP;
  29.         SD_GPIO_Struct.GPIO_Speed                = GPIO_Speed_50MHz;
  30.         SD_GPIO_Struct.Pin                                = SD_SPI_PIN_MOSI | SD_SPI_PIN_SCK;                              
  31.         GPIO_InitPeripheral(SD_SPI_PIN_PORT,&SD_GPIO_Struct);
  32.       
  33.         SD_GPIO_Struct.GPIO_Mode                = GPIO_Mode_IN_FLOATING;
  34.         SD_GPIO_Struct.Pin                                = SD_SPI_PIN_MISO ;
  35.         GPIO_InitPeripheral(SD_SPI_PIN_PORT,&SD_GPIO_Struct);
  36.       
  37.         SD_GPIO_Struct.GPIO_Mode                = GPIO_Mode_Out_PP;
  38.         SD_GPIO_Struct.Pin                                = SD_SPI_PIN_CS ;
  39.         GPIO_InitPeripheral(SD_SPI_PIN_PORT,&SD_GPIO_Struct);
  40.         SD_CS_HIGH;
  41.       
  42.         // 初始化SPI1
  43.         SPI_InitStruct(&SD_SPI_Struct);
  44.       
  45.         SD_SPI_Struct.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;                // 工作模式:全双工
  46.         SD_SPI_Struct.SpiMode       = SPI_MODE_MASTER;                                        // SPI主从配置:主机模式
  47.         SD_SPI_Struct.DataLen       = SPI_DATA_SIZE_8BITS;                                        // 数据长度:8bit
  48.         SD_SPI_Struct.CLKPOL        = SPI_CLKPOL_HIGH;                                        // 时钟极性:高
  49.         SD_SPI_Struct.CLKPHA        = SPI_CLKPHA_SECOND_EDGE;                                // 触发沿:第二个边沿触发
  50.         SD_SPI_Struct.NSS           = SPI_NSS_SOFT;                                                // NSS控制:软件控制
  51.         SD_SPI_Struct.BaudRatePres  = SPI_BR_PRESCALER_256;                                // 分频系数
  52.         SD_SPI_Struct.FirstBit      = SPI_FB_MSB;                                                        // 数据传输高位优先
  53.         SD_SPI_Struct.CRCPoly       = 7;                                                                        // CRC检验
  54.         SPI_Init(SD_SPIx, &SD_SPI_Struct);
  55.         // 开启SPI
  56.         SPI_Enable(SD_SPIx,ENABLE);
  57.       
  58. }
  59. // SPI数据读写
  60. uint8_t SPIx_ReadWriteByte(uint8_t byte)
  61. {
  62.         /* 等待数据发送寄存器清空 */
  63.         while (SPI_I2S_GetStatus(SD_SPIx, SPI_I2S_TE_FLAG) == RESET);
  64.          
  65.         /* 通过SPI发送出去一个字节数据 */
  66.         SPI_I2S_TransmitData(SD_SPIx, byte);
  67.          
  68.         /* 等待接收到一个数据(接收到一个数据就相当于发送一个数据完毕) */
  69.         while (SPI_I2S_GetStatus(SD_SPIx, SPI_I2S_RNE_FLAG) == RESET);
  70.          
  71.         /* 返回接收到的数据 */
  72.         return SPI_I2S_ReceiveData(SD_SPIx);
  73. }
  74. // SPI速度设置
  75. void SPIx_SetSpeed(u8 SpeedSet)
  76. {
  77.         SD_SPI_Struct.BaudRatePres = SpeedSet ;
  78.         SPI_Init(SD_SPIx, &SD_SPI_Struct);
  79.         SPI_Enable(SD_SPIx,ENABLE);
  80. }
SD 卡初始化


  1. ///////////////////////////////////////////////////////////////
  2. //发送命令,发完释放
  3. //////////////////////////////////////////////////////////////
  4. int SD_sendcmd(uint8_t cmd,uint32_t arg,uint8_t crc)
  5. {
  6.         DRESULT r1;
  7.   uint8_t retry;

  8.   SD_CS_LOW;
  9.         systick_delay_us(20);
  10.   //SD_CS_ON;
  11.         do{
  12.                 retry=SPIx_ReadWriteByte(0xFF);
  13.         }while(retry!=0xFF);

  14.   SPIx_ReadWriteByte(cmd | 0x40);
  15.   SPIx_ReadWriteByte(arg >> 24);
  16.   SPIx_ReadWriteByte(arg >> 16);
  17.   SPIx_ReadWriteByte(arg >> 8);
  18.   SPIx_ReadWriteByte(arg);
  19.   SPIx_ReadWriteByte(crc);
  20.   if(cmd==CMD12)SPIx_ReadWriteByte(0xFF);
  21.   do
  22.         {
  23.                 r1=SPIx_ReadWriteByte(0xFF);
  24.         }while(r1&0X80);
  25.         
  26.         return r1;
  27. }


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

  31.         uint8_t r1;
  32.         SD_CS_LOW;
  33.         
  34.         do
  35.         {
  36.                 r1 = SPIx_ReadWriteByte(0xFF);        
  37.                 systick_delay_us(100);
  38.         }while(r1 != 0xFE);        
  39.         
  40.         while(len--)
  41.         {
  42.                 *data = SPIx_ReadWriteByte(0xFF);
  43.                 data++;
  44.         }
  45.         
  46.         SPIx_ReadWriteByte(0xFF);
  47.         SPIx_ReadWriteByte(0xFF);                                                                                                   
  48.         return RES_OK;
  49. }
  50. //向sd卡写入一个数据包的内容 512字节
  51. DRESULT SD_SendBlock(uint8_t* buf,uint8_t cmd)
  52. {        
  53.         uint16_t t;        
  54.         uint8_t r1;        
  55.         do{
  56.                 r1=SPIx_ReadWriteByte(0xFF);
  57.         }while(r1!=0xFF);
  58.         
  59.         SPIx_ReadWriteByte(cmd);
  60.         if(cmd!=0XFD)//不是结束指令
  61.         {
  62.                 for(t=0;t<512;t++)SPIx_ReadWriteByte(buf[t]);//提高速度,减少函数传参时间
  63.             SPIx_ReadWriteByte(0xFF);//忽略crc
  64.             SPIx_ReadWriteByte(0xFF);
  65.                 t=SPIx_ReadWriteByte(0xFF);//接收响应
  66.                 if((t&0x1F)!=0x05)return RES_WRPRT;//响应错误                                                                                                                     
  67.         }                                                                                                                                                                       
  68.   return RES_OK;//写入成功
  69. }


  70. //获取CID信息
  71. uint8_t SD_GETCID (uint8_t *cid_data)
  72. {
  73.                 uint8_t r1;
  74.           r1=SD_sendcmd(CMD10,0,0x01); //读取CID寄存器
  75.                 if(r1==0x00){
  76.                         r1=SD_ReceiveData(cid_data,16);
  77.                 }
  78.                 SD_CS_HIGH;
  79.                 if(r1)return 1;
  80.                 else return 0;
  81. }
  82. //获取CSD信息
  83. uint8_t SD_GETCSD(uint8_t *csd_data){
  84.                 uint8_t r1;         
  85.     r1=SD_sendcmd(CMD9,0,0x01);//发CMD9命令,读CSD寄存器
  86.     if(r1==0)
  87.         {
  88.             r1=SD_ReceiveData(csd_data, 16);//接收16个字节的数据
  89.     }
  90.         SD_CS_HIGH;//取消片选
  91.         if(r1)return 1;
  92.         else return 0;
  93. }
  94. //获取SD卡的总扇区数
  95. uint32_t SD_GetSectorCount(void)
  96. {
  97.     uint8_t csd[16];
  98.     uint32_t Capacity;  
  99.     uint8_t n;
  100.                 uint16_t csize;                                             
  101.         //取CSD信息,如果期间出错,返回0
  102.     if(SD_GETCSD(csd)!=0) return 0;            
  103.     //如果为SDHC卡,按照下面方式计算
  104.     if((csd[0]&0xC0)==0x40)         //V2.00的卡
  105.     {        
  106.                         csize = csd[9] + ((uint16_t)csd[8] << 8) + 1;
  107.                         Capacity = (uint32_t)csize << 10;//得到扇区数                           
  108.     }
  109.                 else//V1.XX的卡
  110.     {        
  111.                         n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
  112.                         csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
  113.                         Capacity= (uint32_t)csize << (n - 9);//得到扇区数   
  114.     }
  115.     return Capacity;
  116. }
  117. //
  118. u32 GetSDCardSectorCount(void)
  119. {
  120.     u8 csd[16];
  121.     u32 Capacity=0;  
  122.           u16 csize;
  123.                 //获取SD卡的CSD信息,包括容量和速度信息,存放CID的内存,至少16Byte
  124.                 SD_sendcmd(CMD9,0,0x01);//发SDCard_CMD9命令,读CSD
  125.           SD_ReceiveData(csd,16);//接收16个字节的数据
  126.                 SD_CS_HIGH;//取消片选
  127.                 SPIx_ReadWriteByte(0xff);//提供额外的8个时钟
  128.     if((csd[0]&0xC0)==0x40)  //SDHC卡,按照下面方式计算
  129.     {        
  130.                         csize=csd[9]+(csd[8]<<8)+1;
  131.                         Capacity=csize<<10;//得到扇区数                           
  132.     }
  133.     return Capacity;
  134. }

  135. // SD卡初始化
  136. DSTATUS SD_Init(void)
  137. {
  138.         /*1. 初始化底层IO口*/
  139.         SD_SPI_Init();
  140.         
  141.         // 设置SPI速度,初始化时速度尽量低,防止初始化失败,初始化完成后尽量提高速度
  142.         SPIx_SetSpeed(SPI_BR_PRESCALER_256);

  143.         SD_CS_LOW;
  144.         /*2. 发送最少74个脉冲*/
  145.          for(uint8_t i=0;i<10;i++)SPIx_ReadWriteByte(0xFF);
  146.         
  147.         
  148.         /*3. 进入闲置状态*/
  149.         uint8_t r1;
  150.         do{
  151.                 r1 = SD_sendcmd(CMD0 ,0, 0x95);        
  152.         }while(r1!=0x01);
  153.         
  154.         
  155.         uint8_t buff[6] = {0};
  156.         uint16_t retry;
  157.         
  158.         /*4. 鉴别SD卡类型*/
  159.         SD_TYPE = 0;
  160.         if(SD_sendcmd(CMD8,0x1AA,0x87)==0x01)
  161.         {
  162.                 for(uint8_t i=0;i<4;i++)buff[i]=SPIx_ReadWriteByte(0xFF);        //Get trailing return value of R7 resp
  163.                 //printf("R7 Resp:%02X %02X %02X %02X\n",buff[0],buff[1],buff[2],buff[3]);
  164.                 if(buff[2] == 0x01 && buff[3] == 0xAA)
  165.                 {
  166.                         do
  167.                         {
  168.                                 SD_sendcmd(CMD55,0,0x01);                    //发送SDCard_CMD55
  169.                                 r1=SD_sendcmd(CMD41,0x40000000,0x01);//发送SDCard_CMD41
  170.                         }while(r1);

  171.                         if(SD_sendcmd(CMD58,0,0x01)==0)                //鉴别SD2.0卡版本开始
  172.                         {
  173.                                 for(uint8_t i=0;i<4;i++)buff[i]=SPIx_ReadWriteByte(0XFF);//得到OCR值
  174.                                 
  175.                                 if(buff[0]&0x40)
  176.                                 {
  177.                                         SD_TYPE=V2HC;
  178.                                 }
  179.                                 else
  180.                                 {
  181.                                         SD_TYPE=V2;
  182.                                 }
  183.                         }
  184.                 }
  185.                 else
  186.                 {
  187.                         SD_sendcmd(CMD55,0,0X01);                        //发送CMD55
  188.                         r1=SD_sendcmd(CMD41,0,0X01);        //发送CMD41
  189.                         if(r1<=1)
  190.                         {               
  191.                                 SD_TYPE=V1;
  192.                                 retry=0XFFFE;
  193.                                 do //等待退出IDLE模式
  194.                                 {
  195.                                         SD_sendcmd(CMD55,0,0X01);        //发送CMD55
  196.                                         r1=SD_sendcmd(CMD41,0,0X01);//发送CMD41
  197.                                 }while(r1&&retry--);
  198.                         }else//MMC卡不支持CMD55+CMD41识别
  199.                         {
  200.                                 SD_TYPE=MMC;//MMC V3
  201.                                 retry=0XFFFE;
  202.                                 do //等待退出IDLE模式
  203.                                 {                                                                                            
  204.                                         r1=SD_sendcmd(CMD1,0,0X01);//发送CMD1
  205.                                 }while(r1&&retry--);  
  206.                         }
  207.                         if(retry==0||SD_sendcmd(CMD16,512,0X01)!=0)SD_TYPE=ERR;//错误的卡
  208.                 }
  209.         }

  210.         char cardType[10]={0};
  211.         switch(SD_TYPE)
  212.         {
  213.                 case MMC:{ strcpy(cardType,"MMC");break;}
  214.                 case V1:{ strcpy(cardType,"SDV1");break;}
  215.                 case V2:{ strcpy(cardType,"SDV2");break;}
  216.                 case V2HC:{ strcpy(cardType,"SDHC");break;}
  217.                 default:{ strcpy(cardType,"ERROR");break;}
  218.         }
  219.         printf("CartType=%s\n",cardType);
  220.         
  221.         SD_CS_HIGH;                //取消片选
  222.          SPIx_ReadWriteByte(0xff);//提供额外的8个时钟
  223.         SPIx_SetSpeed(SPI_BR_PRESCALER_8);

  224.         if(SD_TYPE)return FR_OK;
  225.         else return FR_INT_ERR;
  226. }
至此,主要的SPI工作就完成了,我们回到 main.h中来测试下能不能成功驱动起来
// main.h
#include "main.h"
#include "log.h"
#include "SD_Driver.h"

int main(void)
{
        log_init();
  SD_Init();

  // 获取扇区数和大小
  uint32_t Sector = SD_GetSectorCount();
  printf("SectorCount=%d Size=%.2fMB\n",Sector,(Sector/(1024*1024.0f))*MSD_BLOCKSIZE);

  while(1)
  {

  }

}

我的是32G的TF卡,实际打印消息如下:
截屏2023-03-13 17.14.21.png

如果出现这样的信息,就说明我们SD卡已经初始化成功了,接下来就可以愉快的进行文件系统的移植了!

2、FatFS文件系统的移植
1)配置说明
    因为最终我们是希望实现在SD卡中拷入固件文件方式进行升级,而我们之前配置的并不能获取到文件系统的信息,就是无法直接通过文件名来访问到文件,所以接下来我们就要移植下文件系统,这里使用的是目前嵌入式系统广泛使用的FatFS文件系统。
    开始移植前可能是一头雾水,不知道从哪开始,要做些什么!其实,如果能明白FatFS移植要处理些什么,哪移植起来就非常的简单,如果不明白,哪就是非常的艰难,我因为没有找到一篇非常清楚讲解移植要做些什么,所以花了差不多三天时间才算是搞定,因为从各种各样的资料中获取到自己所需的信息,花费了太多查找资料的时间。
    请看下图:
    FatFS移植.png
    从上图中,我们可以看到,移植就是需要处理状态获取、进**初始化、然后实现读与写,移植过程其实就是需要实现图上的几个接口的功能就可以了,剩下的工作就交给FatFS。所以说会者不难,难者不会,就是这个道理。
  2)开始配置
    现在我们就开始移植吧,首先我们访问 fatfs 的官方网站: http://elm-chan.org/fsw/ff/00index_e.html,将页面滚动到最后,找到箭头所指的位置,点击后可以跳到所有发布的下载地址页面:
QQ20230313-183307.png
我这里显示的最新版本是 FatFs R0.15 (跳到这个页面的原理就是怕之后版本会有变化,而这里会有所有版本,以免版本不一致导致报错等情况的出现)
QQ20230313-183342.png
   
下载后解压,可以看到 documents(文档目录)和source(源码目录),而source下就是我们需要的文件内容

截屏2023-03-13 18.37.02.png
截屏2023-03-13 18.36.50.png
在我们工程目录下新建一个文件夹 FATFS,将 source 目录下所有的.h .c文件复制过去,然后在Keil5中将新建一个组 FATFS,将所有的 .c 加进去,并且在配置里将头文件路径中将FATFS加入,以下是配置细节:

QQ20230313-184503.png

QQ20230313-184556.png

QQ20230313-184848.png

最后点 Ok,软件部分就配置完了。

  3)移植及配置
  1. // ffconf.h 文件
  2. // 文件系统配置

  3. // 是否启用只读  0不启用
  4. #define FF_FS_READONLY        0

  5. // 是否启用 格式化 改为1 启用
  6. #define FF_USE_MKFS                1

  7. // 是否使用启用 seek 改为1 启用
  8. #define FF_USE_FASTSEEK        1

  9. // 字符编码 包含中文所以使用936
  10. #define FF_CODE_PAGE        936

  11. // 是否开始长文件名 ,根据自己需要,启用后固件会成倍增加
  12. #define FF_USE_LFN                0

  13. // 驱动器号 我们这里使用SD卡,所以改为2
  14. #define FF_VOLUMES                2

  15. // 无RTC,本次没有配置RTC,所以改为1
  16. #define FF_FS_NORTC                1

  17. // 是否启用 exFat 支持,启用后必须开启 FF_USE_LFN
  18. #define FF_FS_EXFAT                0


根据前面思维导图中说明,我们需要实现一些初始化函数,而有些我们已经处理了,接着就要驱动文件中添加以下方法及实现了:
// SD_Driver.h
  1. // SD_Driver.h 添加以下声明


  2. // FatFs文件系统初始化
  3. DSTATUS SD_disk_initialize(BYTE pdrv);
  4. // FatFs文件系统状态
  5. DSTATUS SD_disk_status(BYTE pdrv);
  6. // FatFs文件系统ioctl
  7. DRESULT SD_disk_ioctl(BYTE pdrv,BYTE cmd,void *buff);


// SD_Driver.c

  1. // SD_Driver.c 添加以下内容


  2. DSTATUS SD_disk_status(BYTE pdrv)
  3. {
  4. DSTATUS stat;

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

  10. return stat;
  11. //return STA_NOINIT;/* Drive not initialized */
  12. }


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

  23. return stat;

  24. }


  25. DRESULT SD_disk_ioctl (
  26. BYTE pdrv, /* Physical drive nmuber (0..) */
  27. BYTE cmd, /* Control code */
  28. void *buff /* Buffer to send/receive control data */
  29. )
  30. {
  31. DRESULT res;
  32. // if(pdrv == DEV_SD)
  33. // {
  34. switch (cmd)
  35. {
  36. case CTRL_SYNC:
  37. res = RES_OK;
  38. break;
  39. case GET_SECTOR_SIZE:
  40. *(DWORD*)buff = 512; //每个扇区的大小为512字节
  41. res = RES_OK;
  42. break;
  43. case GET_BLOCK_SIZE:
  44. *(WORD*)buff = 8;
  45. res = RES_OK;
  46. break;
  47. case GET_SECTOR_COUNT:
  48. *(DWORD*)buff = SD_GetSectorCount();//调用获取SD卡扇区个数的函数接口
  49. res = RES_OK;
  50. break;
  51. default:
  52. res = RES_PARERR;
  53. break;
  54. }
  55. //}
  56. if(res)
  57. return RES_PARERR;
  58. return res;
  59. }


// 配置 diskio.c

  1. // 配置 diskio.c


  2. #include "ff.h"                        /* Obtains integer types */
  3. #include "diskio.h"                /* Declarations of disk functions */
  4. #include "SD_Driver.h"


  5. /* Definitions of physical drive number for each drive */
  6. #define DEV_RAM                0        /* Example: Map Ramdisk to physical drive 0 */
  7. #define DEV_MMC                1        /* Example: Map MMC/SD card to physical drive 1 */
  8. #define DEV_USB                2        /* Example: Map USB MSD to physical drive 2 */


  9. /*-----------------------------------------------------------------------*/
  10. /* Get Drive Status                                                      */
  11. /*-----------------------------------------------------------------------*/

  12. DSTATUS disk_status (
  13.         BYTE pdrv                /* Physical drive nmuber to identify the drive */
  14. )
  15. {
  16.         DSTATUS stat;
  17.         int result;

  18.         switch (pdrv) {
  19.         case DEV_RAM :
  20.                 //result = RAM_disk_status();

  21.                 // translate the reslut code here

  22.                 return stat;

  23.         case DEV_MMC :
  24.                 result = SD_disk_status(pdrv);

  25.                 // translate the reslut code here

  26.                 return result;

  27.         case DEV_USB :
  28.                 //result = USB_disk_status();

  29.                 // translate the reslut code here

  30.                 return stat;
  31.         }
  32.         return STA_NOINIT;
  33. }



  34. /*-----------------------------------------------------------------------*/
  35. /* Inidialize a Drive                                                    */
  36. /*-----------------------------------------------------------------------*/

  37. DSTATUS disk_initialize (
  38.         BYTE pdrv                                /* Physical drive nmuber to identify the drive */
  39. )
  40. {
  41.         DSTATUS stat;
  42.         int result;

  43.         switch (pdrv) {
  44.         case DEV_RAM :
  45.                 //result = RAM_disk_initialize();

  46.                 // translate the reslut code here

  47.                 return stat;

  48.         case DEV_MMC :
  49.                 result = SD_disk_initialize(pdrv);

  50.                 // translate the reslut code here

  51.                 return result;

  52.         case DEV_USB :
  53.                 //result = USB_disk_initialize();

  54.                 // translate the reslut code here

  55.                 return stat;
  56.         }
  57.         return STA_NOINIT;
  58. }



  59. /*-----------------------------------------------------------------------*/
  60. /* Read Sector(s)                                                        */
  61. /*-----------------------------------------------------------------------*/

  62. DRESULT disk_read (
  63.         BYTE pdrv,                /* Physical drive nmuber to identify the drive */
  64.         BYTE *buff,                /* Data buffer to store read data */
  65.         LBA_t sector,        /* Start sector in LBA */
  66.         UINT count                /* Number of sectors to read */
  67. )
  68. {
  69.         DRESULT res;
  70.         int result;

  71.         switch (pdrv) {
  72.         case DEV_RAM :
  73.                 // translate the arguments here

  74.                 //result = RAM_disk_read(buff, sector, count);

  75.                 // translate the reslut code here

  76.                 return res;

  77.         case DEV_MMC :
  78.                 // translate the arguments here

  79.                 result = SD_ReadDisk(buff, sector, count);

  80.                 // translate the reslut code here

  81.                 return result;

  82.         case DEV_USB :
  83.                 // translate the arguments here

  84.                 //result = USB_disk_read(buff, sector, count);

  85.                 // translate the reslut code here

  86.                 return res;
  87.         }

  88.         return RES_PARERR;
  89. }



  90. /*-----------------------------------------------------------------------*/
  91. /* Write Sector(s)                                                       */
  92. /*-----------------------------------------------------------------------*/

  93. #if FF_FS_READONLY == 0

  94. DRESULT disk_write (
  95.         BYTE pdrv,                        /* Physical drive nmuber to identify the drive */
  96.         const BYTE *buff,        /* Data to be written */
  97.         LBA_t sector,                /* Start sector in LBA */
  98.         UINT count                        /* Number of sectors to write */
  99. )
  100. {
  101.         DRESULT res;
  102.         int result;

  103.         switch (pdrv) {
  104.         case DEV_RAM :
  105.                 // translate the arguments here

  106.                 //result = RAM_disk_write(buff, sector, count);

  107.                 // translate the reslut code here

  108.                 return res;

  109.         case DEV_MMC :
  110.                 // translate the arguments here

  111.                 result = SD_WriteDisk((uint8_t *)buff, sector, count);

  112.                 // translate the reslut code here

  113.                 return result;

  114.         case DEV_USB :
  115.                 // translate the arguments here

  116.                 //result = USB_disk_write(buff, sector, count);

  117.                 // translate the reslut code here

  118.                 return res;
  119.         }

  120.         return RES_PARERR;
  121. }

  122. #endif


  123. /*-----------------------------------------------------------------------*/
  124. /* Miscellaneous Functions                                               */
  125. /*-----------------------------------------------------------------------*/

  126. DRESULT disk_ioctl (
  127.         BYTE pdrv,                /* Physical drive nmuber (0..) */
  128.         BYTE cmd,                /* Control code */
  129.         void *buff                /* Buffer to send/receive control data */
  130. )
  131. {
  132.         DRESULT res;
  133.         int result;

  134.         switch (pdrv) {
  135.         case DEV_RAM :

  136.                 // Process of the command for the RAM drive

  137.                 return res;

  138.         case DEV_MMC :

  139.                 // Process of the command for the MMC/SD card

  140.                 return res;

  141.         case DEV_USB :

  142.                 // Process of the command the USB drive

  143.                 return res;
  144.         }

  145.         return RES_PARERR;
  146. }

我们来测试下,回到 main.h
  1. #include "main.h"
  2. #include "log.h"
  3. #include "SD_Driver.h"

  4. int main(void)
  5. {
  6.         log_init();
  7.   
  8.         FATFS *fs;
  9.         fs = malloc(sizeof(FATFS));
  10.         FRESULT fr = f_mount(
  11.                                                 fs,                        // FATFS对象
  12.                                                 "1:",                // 挂载的盘符 相当于win系统下的 C:
  13.                                                 1                                // 挂载选项 0 延迟挂载 1立即挂载
  14.                                         );        
  15.         if(fr == FR_OK)
  16.         {
  17.                 printf("挂载SD成功.\n");
  18.         }else if(fr == FR_NO_FILESYSTEM)
  19.         {
  20.                 // 没有文件系统
  21.                 printf("没有文件系统,开始格式化 ...\n");
  22.                
  23.                 BYTE work[FF_MAX_SS]; /* Work area (larger is better for processing time) */
  24.                 fr = f_mkfs("1:", 0, work, sizeof work);
  25.                 printf("格式化结果: %s %d\r\n", fr==FR_OK?"成功":"失败",fr);        
  26.         }
  27.         
  28.         // 开始读写文件测试
  29.         char path[] = "1:test.txt";                        // 同样1:是盘符
  30.         char data[] = "Write Data Content.";
  31.         
  32.         // 写入文件测试
  33.         FIL fil;
  34.         fr = f_open(&fil,path,FA_CREATE_ALWAYS | FA_WRITE);
  35.         if(fr == FR_OK)
  36.         {
  37.                 UINT br = 0;
  38.                 f_write(&fil,data,sizeof(data),&br);
  39.                 f_close(&fil);
  40.                
  41.                 printf("创建文件成功,写入字节数: %d\n",br);
  42.         }else{
  43.                 printf("创建文件失败\n");
  44.         }
  45.         
  46.         // 读取文件测试,读取刚写入的文件内容
  47.         fr = f_open(&fil,path,FA_READ);
  48.         if(fr == FR_OK)
  49.         {
  50.                 UINT br = 0;
  51.                 char tmp[100]={0};
  52.                
  53.                 f_read(&fil,tmp,100,&br);
  54.                 f_close(&fil);
  55.                
  56.                 printf("读取文件成功,文件内容: %s\n",tmp);
  57.         }else{
  58.                 printf("打开文件失败\n");
  59.         }
  60.         
  61.         // 删除文件
  62.         fr = f_unlink(path);
  63.         printf("删除文件结果: %s\n",fr == FR_OK?"成功":"失败");
  64.         
  65.   while(1)
  66.   {

  67.   }

  68. }



实际执行结果:
截屏2023-03-13 21.00.45.png


三、IAP升级实现
    之前我们也分析了下,想要实现IAP,就需要两套程序,一套专门负责升级的叫Bootloader,一套负责业务逻辑的叫APP。而即然是两套程序,都写入到MCU的Flash中,地址肯定要不一样,不然不就后面一个把前面的内容给覆盖了,你说是不是。所以这里就需要对空间进行分配,并且可以方便的进行程序间隔离,相互不影响。
    这里用到的开发板是 N32G45XVL-STB v1.1,芯片型号是 N32G457VEL7,Flash 512K (16进制表示 0x80000 ),内存144K (16进制表示 0x24000 )。给Bootloader 划分 16K的存储空间和16K的运行内存,剩余的空间和内存分配给APP。因分配给Bootloader空间有限,所以就没有办法实现非常复杂的功能,这里编写时需要注意。
    IAP升级.png


    下面就要编写相应的程序了,为了便于观察,我们使用了板载灯和串口打印信息做为提示,当板载PB5亮起时,表示正在升级程序,当板载PA8闪烁时,表示成功升级并跳到了APP中。


1)Bootloader编写
    IROM1(Flash 存储空间) 起始地址是 0x8000000,Bootloader是需要先启动,所以他的起始地址就是 0x8000000 , 16K 就是 0x4000,Size 填入 0x4000。RAM(内存) 起始地址是 0x20000000,16K 是 0x4000,Size 填入 0x4000,Bootloader在Keil5中配置如下:

Bootloader keil5配置
QQ20230314-091903.png

    我们先来编写IAP程序,处理Flash写入、跳转等工作:
iap.h
  1. #ifndef IAP_H_
  2. #define IAP_H_

  3. #include "main.h"

  4. #define FLASH_APP_BASE_ADDR                 0x08004000                                // Bootloader 预留16K空间,APP程序从0x08004000开始                                                   
  5. #define FLASH_START_ADDR        FLASH_APP_BASE_ADDR
  6. #define app_update_flag_addr    0x08040000-4              // APP更新标志,存在BOOT,注意不能覆盖BOOT代码

  7. typedef  void (*iapfun)(void);                                                                        // 定义一个函数类型的参数.           
  8. void iap_load_app(u32 appxaddr);                                                                // 跳转到APP程序执行
  9. void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 applen);                // 在指定地址开始,写入bin
  10. void IAP_UPDATE_APP(void);
  11. int32_t app_flag_write(uint32_t data ,uint32_t start_add);


  12. #endif


iap.c

  1. #include "iap.h"

  2. #include "string.h"

  3. iapfun jump2app;  
  4. uint8_t uart_receiveBIN_ok;

  5. uint8_t pages_number = 0;
  6. uint32_t ready_write_addr = 0;

  7. uint8_t flash_buf[];
  8. uint8_t receive_app_done;

  9. __asm void MSR_MSP(u32 addr)
  10. {
  11.     MSR MSP, r0                         //set Main Stack value
  12.     BX r14
  13. }

  14. /**================================================================
  15.                 APP 跳转
  16.                 appxaddr:用户代码起始地址.
  17. ================================================================*/
  18. void iap_load_app(u32 appxaddr)
  19. {
  20.         if(((*(vu32*)appxaddr)&0x0FFFFFFF) < 1024*512)                // 检查栈顶地址是否合法.
  21.         {
  22.                 jump2app = (iapfun)*(vu32*)(appxaddr+4);                                
  23.                 MSR_MSP(*(vu32*)appxaddr);                                                // 初始化堆栈指针
  24.                 jump2app();                                                                                // 跳转到APP.
  25.         }
  26. }               
  27. /**================================================================
  28. ================================================================*/
  29. int32_t app_flag_write(uint32_t data ,uint32_t start_add)
  30. {
  31.         FLASH_Unlock();
  32.         //
  33.         FLASH_EraseOnePage(start_add);                        //写之前先擦一遍,每次擦2K
  34.         if (FLASH_COMPL != FLASH_ProgramWord(start_add, data))                //写
  35.         {
  36.                 FLASH_Lock();
  37.                 //printf("flash write fail! \r\n");
  38.                 return 1;
  39.         }
  40.         FLASH_Lock();
  41.     return 0;
  42. }
  43. /**================================================================
  44. ================================================================*/
  45. #define FLASH_PAGE_SIZE                 2048                                 

  46. /**
  47. * [url=home.php?mod=space&uid=247401]@brief[/url]
  48. * @param void
  49. * @return
  50. * - `SUCCESS: 表示操作成功
  51. * - 其它值表示出错
  52. */
  53. int32_t app_flash_write(uint32_t *data ,uint32_t Flash_address)
  54. {
  55.     uint32_t i;
  56.         uint32_t start_add;
  57.         start_add = Flash_address;
  58.                
  59.         FLASH_Unlock();
  60.         //
  61.         for(i = 0;i<FLASH_PAGE_SIZE/FLASH_PAGE_SIZE;i++)
  62.         {
  63.                 FLASH_EraseOnePage(start_add+i*FLASH_PAGE_SIZE);                        //写之前先擦一遍,每次擦2K
  64.         }
  65.         //
  66.         for(i=0;i<FLASH_PAGE_SIZE/4 ;i++)
  67.         {
  68.                 if (FLASH_COMPL != FLASH_ProgramWord(start_add+i*4, data[i]))                //写
  69.                 {
  70.                         FLASH_Lock();
  71.                         //printf("flash write fail! \r\n");
  72.                         receive_app_done = 0;
  73.                         return 1;
  74.                 }
  75.         }
  76.         FLASH_Lock();
  77.     return 0;
  78. }
  79. /**================================================================
  80.                 //升级APP
  81. ================================================================*/
  82. void IAP_UPDATE_APP(void)
  83. {
  84.         ready_write_addr = FLASH_APP_BASE_ADDR + pages_number*2048;
  85.         //
  86.         while(app_flash_write((uint32_t *)flash_buf , ready_write_addr));                //IAP每次升级2K
  87.         //
  88.         memset(flash_buf,0x00,2048);
  89.         pages_number++;

  90. }


    编写完成后,我们回到 main.c 中完成逻辑部分:
main.c
  1. #include "main.h"
  2. #include "SD_Driver.h"
  3. #include "iap.h"


  4. // 文件盘符
  5. #define DRIVE_LETTER        "1:"
  6. #define FLASH_PAGE_SIZE                2048

  7. __IO uint32_t count_time = 0;

  8. void IAP_Upgrade(char *path);
  9. extern int32_t app_flash_write(uint32_t *data ,uint32_t Flash_address);

  10. /**================================================================
  11.                 读取Flash
  12. ================================================================*/
  13. uint32_t FLASH_ReadWord(uint32_t address)
  14. {
  15.     return *(__IO uint32_t*)address;
  16. }

  17. // 初始化PB5做为升级指示
  18. void LED_Init(void);
  19. #define LED_ON        GPIO_SetBits(GPIOB,GPIO_PIN_5)
  20. #define LED_OFF        GPIO_ResetBits(GPIOB,GPIO_PIN_5)

  21. int main(void)
  22. {
  23.   // 检查标志位,判断有没有app,如果有,直接跳转到app中
  24.   if(FLASH_ReadWord(app_update_flag_addr) == 0x12345678)
  25.   {
  26.     // 跳转前设置中断向量表
  27.     SCB->VTOR = FLASH_START_ADDR;
  28.     // 跳转到APP起始地址,期间不能被其他中断打断,否则会跳转失败
  29.     iap_load_app(FLASH_START_ADDR);        
  30.                
  31.     return 0;
  32.   }
  33.         
  34.   // 没有app 则进行升级,升级请将固件拷到SD卡中,插入到卡槽中
  35.         
  36.   log_init();
  37.   LED_Init();
  38.   printf("\n");
  39.   printf("Start running Bootloader.\n");
  40.                
  41.         
  42.   // 传入要升级的文件名称
  43.   IAP_Upgrade("project.bin");

  44.         
  45.         
  46.   while(1)
  47.   {
  48.     // 升级失败,PB5快闪
  49.     systick_delay_ms(100);
  50.     systick_delay_ms(100);
  51.     if(GPIO_ReadOutputDataBit(GPIOB,GPIO_PIN_5) == RESET)
  52.     {
  53.       LED_ON;
  54.     }else{
  55.       LED_OFF;
  56.     }
  57.   }
  58. }

  59. void LED_Init(void)
  60. {
  61.   RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB,ENABLE);
  62.         
  63.   GPIO_InitType GPIO_InitStrucetre;
  64.   GPIO_InitStruct(&GPIO_InitStrucetre);
  65.         
  66.   GPIO_InitStrucetre.GPIO_Mode                = GPIO_Mode_Out_PP;
  67.   GPIO_InitStrucetre.Pin                                        = GPIO_PIN_5;
  68.   GPIO_InitPeripheral(GPIOB,&GPIO_InitStrucetre);

  69. }

  70. void IAP_Upgrade(char *path)
  71. {
  72.   char _path[50]={0};
  73.   // 判断是否有盘符,没有则加上盘符
  74.   if(strncmp(path,DRIVE_LETTER,strlen(DRIVE_LETTER)) != 0)
  75.   {
  76.     sprintf(_path,"%s%s",DRIVE_LETTER,path);
  77.   }else{
  78.     strcpy(_path,path);
  79.   }
  80.         
  81.         
  82.   FATFS *fs;
  83.   fs = malloc(sizeof (FATFS));           /* Get work area for the volume */
  84.   FRESULT res=f_mount(fs,DRIVE_LETTER,1);                //挂载
  85.         
  86.   if(res == FR_OK)
  87.   {
  88.     printf("SD Mount Ok.\n");
  89.                
  90.     // 开始检查是否存在指定的文件
  91.     FILINFO fno;
  92.     FRESULT fr = f_stat(_path,&fno);
  93.     if(fr != FR_OK)
  94.     {
  95.       printf("Upgrade file not found\n");
  96.       return;
  97.     }
  98.         
  99.   }else{
  100.     printf("SD Not Mount.\n");
  101.     return;
  102.   }
  103.         
  104.   printf("Upgrade Start ... \n");
  105.         
  106.   LED_ON;
  107.         
  108.   // 写入Flash页码
  109.   uint16_t pages_number = 0;
  110.   // 读写Flash地址
  111.   uint32_t ready_write_addr = 0;
  112.   char buff[FLASH_PAGE_SIZE] ;
  113.   UINT br = 0;
  114.         
  115.   FIL fil;  
  116.   FRESULT fr = f_open(&fil, _path, FA_READ);
  117.   if(fr == FR_OK)
  118.   {
  119.     do{
  120.       memset(buff,0,FLASH_PAGE_SIZE);
  121.       f_read(&fil,buff,FLASH_PAGE_SIZE,&br);
  122.       if(br > 0)
  123.       {
  124.         ready_write_addr = FLASH_APP_BASE_ADDR + pages_number*FLASH_PAGE_SIZE;
  125.         while(app_flash_write((uint32_t *)buff , ready_write_addr));                //IAP每次升级2K
  126.         pages_number++;
  127.       }
  128.     } while(br);
  129.                
  130.     f_close(&fil);
  131.                
  132.     if(pages_number > 0)
  133.     {
  134.       printf("Upgrade completed, start jumping to app.\n\n\n");
  135.                         
  136.       // 升级完成操作 释放GPIOB
  137.       GPIO_DeInit(GPIOB);
  138.      // 写IAP升级标志
  139.      app_flag_write(0x12345678 ,app_update_flag_addr);
  140.                         
  141.      //NVIC_SystemReset(); // 复位
  142.     // 跳转前设置中断向量表
  143.     SCB->VTOR = FLASH_START_ADDR;        
  144.     // 跳转到APP起始地址,期间不能被其他中断打断,否则会跳转失败                        
  145.     iap_load_app(FLASH_START_ADDR);                                
  146.    }
  147.   }else{
  148.     printf("File opening failed\n");
  149.   }
  150. }



   
    以上程序编写完成后可以直接烧入开发板中,因为此时没有拷入升级文件,所以PB5灯应该会快闪才对,因为没升级文件无法升级。


2)APP(业务逻辑)程序编写

    Flash总大小 0x80000 被Bootloader占用了16K 就剩下了 0x7C000,内存总大小 0x24000,剩余就是0x20000,并且之前空间已经被占用,所以新的超始地址就需要向后偏移相应的大小,以下就是App的配置:

app keil5配置
QQ20230314-092038.png
    我们可以看到IROM1的起始地址变成了 0x8004000,就是0x8000000+0x4000(Bootloader的大小),大小要减去16K变成了 0x7C000,就是0x80000-0x4000,IRAM1也是一样的道理。

    写app程序之前,我们要先了解一个知识点:中断向量表。

    什么是中断向量表?
      中断向量表就是中断向量的列表。
      中断向量表在内存中保存,其中存放着中断源(中断向量号或者中断类型号)所对应的中断处理程序的入口地址。
      一个中断源对应一个中断处理程序,这种关系索引表,就是中断向量表。


    提到这个的原因就是因为我们app开始时的中断向量表地址是  0x08000000,而我们app程序整体向后移了16K,如果我们app程序中有用到中断功能,哪么如果还是指向之前的中断向量表的地址,就会无法执行或直接卡死,因为这个地址已经变了,但我们没有去更新。在写Bootloader程序时,大家应该也注意到了跳转app前设置中断向量表,虽然这里设置了,但只能影响跳转时的,在进入APP又会变成无效了,这是为什么呢?
    因为啊,在进入程序前,会有个 SystemInit() 初始化系统时钟,而这里又设置了中断向量表,导致我们跳转前设置的又变成了无效。不信的话,大家可以编写一个简单的定时器程序,做一个更新中断,在初始化定时前和之后都打印一条消息,我们会发现,一定初始化定时器程序后,立马就卡死了,就是因为此地址不对。
    初始化时钟的程序在 system_n32g45x.c 文件中,在此文件里找到 void SystemInit(void) 函数,在函数末尾我们可以看到这样一个配置(红框中):
QQ20230319-151629@2x.png
    可以看到这里又重置了中断向量表的地址,FLASH_BASE 是Flash的起始地址,也就是 0x08000000 再与上了一个偏移地址 VECT_TAB_OFFSET ,我们点右键跳转到他的定义,就会发现 VECT_TAB_OFFSET 配置的地址是 0x00,所以才会出现这样的原因,此时我只需要把 VECT_TAB_OFFSET 的值改为 0x4000,就可以了,就像这样(注意红框中值变化):
QQ20230319-152121@2x.png

    需要注意的地方已经讲完了,剩余的就是正常编写程序就可以了,我这里为了方便演示,我就做了一个点灯和定时器,正常运行APP后,我们点亮板载的PA8,并闪烁,初始化CountTime 1ms更新一次的定时器,并且每隔500ms打印一条消息。APP部分也是基于之前的SD卡程序演示文件的读写并打印消息,方便观察运行情况。为了在进入app后也能升级,我们添加了一个按键,使用的就是 开发板上的WAKEUP按键,当我们将有固件的TF卡插入后,我们点击这个按键就会开始升级。
    注意:这里为了简单点,把固件名称固定为 “ Project.bin ”,Bootloader就是读取这个文件就可以升级。升级现象就是:当按下WAKEUP按键后,PB5灯先亮起,开始升级,升级完成后会熄灭,之后跳转到APP中,PA8 亮起并闪烁,整个升级过程完成。
    好了,打开main.c 进行开始编写代码:

main.c
  1. #include "main.h"
  2. #include "SD_Driver.h"
  3. #include "iap.h"

  4. extern int32_t app_flash_write(uint32_t *data ,uint32_t Flash_address);

  5. void SD_Test(void);

  6. // 按键软件防抖配置
  7. #define KEY_LOCK_TIME                50
  8. uint32_t KeyPressTime = 0;
  9. // 按键初始化
  10. void Key_Init(void);

  11. // LED初始化
  12. void LED_Init(void);
  13. #define LED_ON        GPIO_SetBits(GPIOA,GPIO_PIN_8)
  14. #define LED_OFF        GPIO_ResetBits(GPIOA,GPIO_PIN_8)

  15. int main(void)
  16. {
  17.   log_init();
  18.   LED_Init();
  19.   CountDown_Init();
  20.   Key_Init();
  21.         
  22.   printf("App Init Ok.\n");
  23.         
  24.   LED_ON;
  25.   
  26.   // SD卡测试
  27.   SD_Test();
  28.         
  29.   uint32_t t = count_time;
  30.   while(1)
  31.   {
  32.     if(count_time - t>= 500)
  33.     {
  34.       t = count_time;
  35.                         
  36.       printf("Test Msg, CountTime=%d\n",count_time);
  37.                         
  38.       if(GPIO_ReadOutputDataBit(GPIOA,GPIO_PIN_8) == RESET)
  39.       {
  40.         LED_ON;
  41.       }else{
  42.         LED_OFF;
  43.       }
  44.     }
  45.                
  46.     // USART接收数据打印
  47.     if(USART_Recv_Flag)
  48.     {
  49.       printf("Recv: %s\n",USART_RecvBuff);
  50.                         
  51.       USART_Recv_Flag = 0;
  52.       USART_Recv_Size = 0;
  53.       memset(USART_RecvBuff,0,USART_MAX_SIZE);
  54.     }
  55.   }

  56. }

  57. void SD_Test(void)
  58. {
  59.         
  60.     FATFS *fs;
  61.     fs = malloc(sizeof(FATFS));
  62.     FRESULT fr = f_mount(
  63.             fs,                        // FATFS对象
  64.             "1:",                // 挂载的盘符 相当于win系统下的 C:
  65.             1                                // 挂载选项 0 延迟挂载 1立即挂载
  66.         );        
  67.     if(fr == FR_OK)
  68.     {
  69.       printf("挂载SD成功.\n");
  70.     }else if(fr == FR_NO_FILESYSTEM)
  71.     {
  72.       // 没有文件系统
  73.       printf("没有文件系统,开始格式化 ...\n");
  74.                
  75.       BYTE work[FF_MAX_SS]; /* Work area (larger is better for processing time) */
  76.       fr = f_mkfs("1:", 0, work, sizeof work);
  77.       printf("格式化结果: %s %d\r\n", fr==FR_OK?"成功":"失败",fr);        
  78.     }else{
  79.        printf("SD卡挂载失败.\n");
  80.        return;
  81.     }
  82.         
  83.     // 开始读写文件测试
  84.     char path[] = "1:test.txt";                        // 同样1:是盘符
  85.     char data[] = "Write Data Content.";
  86.         
  87.     // 写入文件测试
  88.     FIL fil;
  89.     fr = f_open(&fil,path,FA_CREATE_ALWAYS | FA_WRITE);
  90.     if(fr == FR_OK)
  91.     {
  92.       UINT br = 0;
  93.       f_write(&fil,data,sizeof(data),&br);
  94.       f_close(&fil);
  95.                
  96.       printf("创建文件成功,写入字节数: %d\n",br);
  97.     }else{
  98.       printf("创建文件失败\n");
  99.     }
  100.         
  101.     // 读取文件测试,读取刚写入的文件内容
  102.     fr = f_open(&fil,path,FA_READ);
  103.     if(fr == FR_OK)
  104.     {
  105.       UINT br = 0;
  106.       char tmp[100]={0};
  107.                
  108.       f_read(&fil,tmp,100,&br);
  109.       f_close(&fil);
  110.                
  111.       printf("读取文件成功,文件内容: %s\n",tmp);
  112.     }else{
  113.       printf("打开文件失败\n");
  114.     }
  115.         
  116.     // 删除文件
  117.     fr = f_unlink(path);
  118.     printf("删除文件结果: %s\n",fr == FR_OK?"成功":"失败");
  119.         
  120. }


  121. // 按键初始化
  122. void Key_Init(void)
  123. {
  124.         RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA,ENABLE);
  125.         
  126.         GPIO_InitType  KeyGpio_InitStruct;
  127.         GPIO_InitStruct(&KeyGpio_InitStruct);
  128.         
  129.         KeyGpio_InitStruct.GPIO_Mode        = GPIO_Mode_IN_FLOATING;
  130.         KeyGpio_InitStruct.Pin                                = GPIO_PIN_0;
  131.         GPIO_InitPeripheral(GPIOA,&KeyGpio_InitStruct);
  132.         
  133.         // 中断线配置
  134.         GPIO_ConfigEXTILine(GPIOA_PORT_SOURCE,GPIO_PIN_SOURCE0);
  135.         
  136.         // 按键中断触发方式
  137.         EXTI_InitType   Exti_InitStruct;
  138.         Exti_InitStruct.EXTI_Line                        = EXTI_LINE0;
  139.         Exti_InitStruct.EXTI_LineCmd        = ENABLE;
  140.         Exti_InitStruct.EXTI_Mode                        = EXTI_Mode_Interrupt;
  141.         Exti_InitStruct.EXTI_Trigger        = EXTI_Trigger_Falling;
  142.         EXTI_InitPeripheral(&Exti_InitStruct);
  143.         
  144.         // 配置中断
  145.         NVIC_InitType NVIC_InitStructure;

  146.         NVIC_InitStructure.NVIC_IRQChannel                   = EXTI0_IRQn;
  147.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x05;
  148.         NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0x0F;
  149.         NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
  150.         NVIC_Init(&NVIC_InitStructure);
  151.         
  152. }


  153. void EXTI0_IRQHandler(void)
  154. {
  155.   if(EXTI_GetITStatus(EXTI_LINE0) != RESET)
  156.   {
  157.     EXTI_ClrITPendBit(EXTI_LINE0);
  158.                
  159.     // 软件防抖
  160.     if( GPIO_ReadInputDataBit(GPIOA,GPIO_PIN_0) != RESET || count_time - KeyPressTime <= KEY_LOCK_TIME)return;
  161.     KeyPressTime = count_time;
  162.                
  163.     printf("Key Press\n");
  164.                
  165.     // 跳转前将中断向量表改回去
  166.     SCB->VTOR = FLASH_BASE;
  167.     // 清除升级标志
  168.     app_flash_write(0x00,app_update_flag_addr);
  169.     // 复位,开始升级
  170.     NVIC_SystemReset();
  171.   }
  172. }


  173. void LED_Init(void)
  174. {
  175.     RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA,ENABLE);
  176.         
  177.     GPIO_InitType GPIO_InitStrucetre;
  178.     GPIO_InitStruct(&GPIO_InitStrucetre);
  179.         
  180.     GPIO_InitStrucetre.GPIO_Mode                = GPIO_Mode_Out_PP;
  181.     GPIO_InitStrucetre.Pin                                        = GPIO_PIN_8;
  182.     GPIO_InitPeripheral(GPIOA,&GPIO_InitStrucetre);
  183.         
  184. }


    至此,本次项目已经全部完成了,并且附上完整实现代码,希望能给大家带来帮助。
    最后一部分我也准备了真实应用实例,通过微信小程序来实现升级,有兴趣可以继续阅读下一部分内容。

四、使用微信小程序实现IAP升级

    前言:微信是目前使用最广泛的通讯方式了,其推出的小程序也简单易用,如果升级能结合微信小程序来实现,则能极快的减少升级成本,仅需要把固件通过微信下发,客户就可以直接使用手机进行升级,这将是一个非常快捷且节约成本的升级方案了,再也不用千里迢迢跑到客户哪边去升级了。
    所以,接下来做的就是实现微信小程序的升级功能,本次实例的方案使用的是 AT固件的蓝牙模块,这样移植起来非常简便,不过要求支持透传功能,下面介绍下使用到的蓝牙模块:
蓝牙模块:集芯微 G75-C2G4A12S3a
类型:AT固件,支持透传
价格:不到4元
优势:价格便宜,使用简单,容易购买到
缺点:AT固件只能发送字符型,无法发送二进制数据,因为遇到\0就会截断,该模块单包数据有限制,实测 96字节可以发,128字节模块直接死掉,应该是溢出了

说了模块,就是连接方式了:
模块           开发板
VDD   ----    3V3
GND   ----    GND
RXD   ----    PB10
TXD   ----    PB11

模块用到了几个AT命令
    1、修改蓝牙名称:
       为了便于标识,我们将蓝牙名称修改为 " IAP_N32G457 ",命令描述如下:
截屏2023-03-22 16.44.12.png

    2、数据透传开启命令:
        这里数据主要是乃至数据透传,这样可以方便进行数据的交互。开启透传后,连接上蓝牙的设备发送过来的数据会直接通过串口发给MCU,同样MCU通过串口发给蓝牙模块的数据会直接发送到连接上来的设备端。命令描述如下:
截屏2023-03-22 16.44.40.png
  
    3、配置保存命令,用于将设置保存下来:
截屏2023-03-22 16.44.52.png

    以上直接截图原文档中内容,文末我也会将此文档上传上来。

    说完了模块的几条命令,下面就来配置一下与模块通讯的串口了,我们新建一个ble.h ble.c 来处理蓝牙相关的内容。

// ble.h
  1. #ifndef BLE_H_
  2. #define BLE_H_

  3. #include "main.h"

  4. // AT固件蓝牙串口配置
  5. #define BLE_USART_RCC                                RCC_APB1_PERIPH_USART3
  6. #define BLE_USARTx                                        USART3

  7. #define BLE_GPIO_RCC                                RCC_APB2_PERIPH_GPIOB
  8. #define BLE_GPIO_PORT                                GPIOB
  9. #define BLE_PIN_TX                                        GPIO_PIN_10
  10. #define BLE_PIN_RX                                        GPIO_PIN_11

  11. #define BLE_IRQn                                                USART3_IRQn
  12. #define BLE_IRQHandle                                USART3_IRQHandler


  13. // 定义蓝牙收到消息状态
  14. typedef struct {
  15.         char cmd[18];
  16.         char content[100];
  17.         char status[10];
  18. } BleResultStatus;


  19. // 蓝牙名称
  20. #define BLE_NAME                "IAP_N32G457"

  21. // 蓝牙每次最大接收数
  22. #define BLE_MAX_RECV_SIZE                1024
  23. // 蓝牙接收缓存区
  24. extern char BLE_RecvBuff[BLE_MAX_RECV_SIZE];
  25. // 蓝牙接收数据大小
  26. extern uint16_t BLE_Recv_Size;
  27. // 蓝牙接收完成标志
  28. extern uint8_t BLE_Recv_Flag;
  29. // 连接状态
  30. extern uint8_t BLE_Connected ;

  31. void BLE_Init(void);
  32. // 发送数据
  33. void BLE_SendData(const char *data,uint16_t size);
  34. uint8_t BleSendCommand(char *cmd, BleResultStatus *result);
  35. uint8_t BleRestoreStatus(char *data, BleResultStatus *result);
  36. void BLE_Clear_Flag(void);

  37. #endif


// ble.c
  1. #include "ble.h"

  2. // 蓝牙接收缓存区
  3. char BLE_RecvBuff[BLE_MAX_RECV_SIZE] = {0};
  4. // 蓝牙接收数据大小
  5. uint16_t BLE_Recv_Size = 0;
  6. // 蓝牙接收完成标志
  7. uint8_t BLE_Recv_Flag = 0;
  8. // 连接状态
  9. uint8_t BLE_Connected = 0;


  10. void BLE_Init(void)
  11. {
  12.         
  13.         // 配置中断
  14.         NVIC_InitType NVIC_InitStructure;

  15.         /* Enable the USARTz Interrupt */
  16.         NVIC_InitStructure.NVIC_IRQChannel                   = BLE_IRQn;
  17.         //NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  18.         NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 1;
  19.         NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
  20.         NVIC_Init(&NVIC_InitStructure);
  21.         
  22.         
  23.         if(BLE_USART_RCC != RCC_APB2_PERIPH_USART1 && BLE_USART_RCC != RCC_APB2_PERIPH_UART6 && BLE_USART_RCC != RCC_APB2_PERIPH_UART6 )
  24.         {
  25.                 RCC_EnableAPB1PeriphClk(BLE_USART_RCC,ENABLE);
  26.         }else{
  27.                 RCC_EnableAPB2PeriphClk(BLE_USART_RCC | RCC_APB2_PERIPH_AFIO,ENABLE);
  28.         }
  29.         
  30.         RCC_EnableAPB2PeriphClk(BLE_GPIO_RCC,ENABLE);
  31.         
  32.         
  33.         GPIO_InitType GPIO_InitStructure;
  34.   USART_InitType USART_InitStructure;
  35.         GPIO_InitStructure.Pin        = BLE_PIN_TX;
  36.         GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
  37.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  38.         GPIO_InitPeripheral(BLE_GPIO_PORT, &GPIO_InitStructure);

  39.         GPIO_InitStructure.Pin       = BLE_PIN_RX;
  40.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  41.         GPIO_InitPeripheral(BLE_GPIO_PORT, &GPIO_InitStructure);

  42.         USART_InitStructure.BaudRate            = 115200;
  43.         USART_InitStructure.WordLength          = USART_WL_8B;
  44.         USART_InitStructure.StopBits            = USART_STPB_1;
  45.         USART_InitStructure.Parity              = USART_PE_NO;
  46.         USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
  47.         USART_InitStructure.Mode                = USART_MODE_TX | USART_MODE_RX;

  48.         // init uart
  49.         USART_Init(BLE_USARTx, &USART_InitStructure);
  50.         
  51.         // 配置接收中断
  52.         USART_ConfigInt(BLE_USARTx,USART_INT_RXDNE,ENABLE);
  53.         // 配置空闲中断
  54.         USART_ConfigInt(BLE_USARTx,USART_INT_IDLEF,ENABLE);

  55.         // enable uart
  56.         USART_Enable(BLE_USARTx, ENABLE);

  57.         
  58.         BleResultStatus result;
  59.         // 取消透传
  60.         BleSendCommand("AT+BT_TRANS=0\n\r",&result);
  61.         systick_delay_ms(10);
  62.         // 获取蓝牙名称
  63.         BleSendCommand("AT+BT_NAME?\n\r",&result);
  64.         if(strcmp(result.content,BLE_NAME)==0)
  65.         {
  66.                 printf("蓝牙名称正确无需修改\n");
  67.         }else{
  68.                 printf("蓝牙名称不正确,当前蓝牙名称: %s\n",result.content);
  69.                 // 开始修改蓝牙名称
  70.                 char buff[32]={0};
  71.                 sprintf(buff,"AT+BT_NAME=%s\n\r",BLE_NAME);
  72.                 BleSendCommand(buff,&result);
  73.                 printf("蓝牙名称修改结果: %s\n",result.status);
  74.                 if(strcmp(toupper(result.status),"OK") == 0)
  75.                 {
  76.                         // 保存修改结果
  77.                         BleSendCommand("AT+UT_CFGSV\r\n",&result);
  78.                         printf("蓝牙保存结果: %s\n",result.status);
  79.                 }
  80.                
  81.         }
  82.         systick_delay_ms(10);
  83.         // 开启透传
  84.         BleSendCommand("AT+BT_TRANS=1\n\r",&result);
  85. }



  86. void BLE_IRQHandle(void)
  87. {
  88.         if (USART_GetIntStatus(BLE_USARTx, USART_INT_RXDNE) != RESET)
  89.         {
  90.                 BLE_RecvBuff[BLE_Recv_Size++] = USART_ReceiveData(BLE_USARTx);
  91.         }
  92.         
  93.         if (USART_GetIntStatus(BLE_USARTx, USART_INT_IDLEF) != RESET)
  94.         {
  95.                 USART_ReceiveData(BLE_USARTx);
  96.                 BLE_RecvBuff[BLE_Recv_Size++] = '\0';
  97.                
  98.                 BLE_Recv_Flag = 1;
  99.                
  100.                 // 检查是否连接消息,如果是则开启透传模式
  101.                 if(strstr(BLE_RecvBuff,"+BT_CONN"))
  102.                 {
  103.                         BLE_Clear_Flag();
  104.                         BleSendCommand("AT+BT_TRANS=1\n\r",NULL);
  105.                         BLE_Connected = 1;
  106.                 }else if(strstr(BLE_RecvBuff,"+DISCONN")){
  107.                         BLE_Connected = 0;
  108.                 }
  109.         }
  110. }


  111. // 发送数据
  112. void BLE_SendData(const char *data,uint16_t size)
  113. {
  114.         for(int i=0;i<size;i++)
  115.         {
  116.                 USART_SendData(BLE_USARTx, (uint8_t)data[i]);
  117.     while(USART_GetFlagStatus(BLE_USARTx, USART_FLAG_TXDE) == RESET);
  118.         }
  119. }

  120. void BLE_Clear_Flag(void)
  121. {
  122.         BLE_Recv_Flag = 0;
  123.         BLE_Recv_Size = 0;
  124.         memset(BLE_RecvBuff,0,BLE_MAX_RECV_SIZE);
  125. }

  126. // 发送配置命令
  127. uint8_t BleSendCommand(char *cmd, BleResultStatus *result)
  128. {
  129.         if(result != NULL)memset(result,0,sizeof(BleResultStatus));
  130.         
  131.         char _command[30]={0};
  132.         if(strstr(cmd,"\r\n")){
  133.                 strcpy(_command,cmd);
  134.         }else{
  135.                 strcpy(_command,cmd);
  136.                 strcat(_command,"\r\n");
  137.         }
  138.         
  139.         // 发送命令
  140.         BLE_SendData(_command,strlen(_command));
  141.         if(result == NULL)return 1;
  142.         
  143.         // 等待获取返回信息
  144.         uint32_t consume_time = count_time;
  145.         while(!BLE_Recv_Flag && count_time-consume_time<500);
  146.         if(!BLE_Recv_Flag)return 0;
  147.         
  148.         
  149.         // 解析返回值信息
  150.         uint8_t status=BleRestoreStatus(BLE_RecvBuff,result);
  151.         // 清除接收状态
  152.         BLE_Clear_Flag();
  153.         return status;
  154. }


  155. // 解析命令状态
  156. uint8_t BleRestoreStatus(char *data, BleResultStatus *result)
  157. {
  158.         if(data[0] == 0x0a){
  159.                 //printf("有换行符\n");
  160.                 strcpy(data ,data+1);
  161.         }
  162.         
  163.         
  164.         char seq[3] = "\r\n";
  165.         char *token = strtok(data+1,seq);
  166.         if(token == NULL)return 0;
  167.         
  168.         char tmp[60]={0};
  169.         while(token != NULL)
  170.         {
  171.                 if(strstr(token,"+")){
  172.                         strcpy(tmp,token);
  173.                 }else if(strlen(token)>1){
  174.                         strcpy(result->status,token);
  175.                 }
  176.                 token = strtok(NULL, seq);
  177.         }
  178.         
  179.         memset(seq,0,3);
  180.         strcpy(seq,":");
  181.         
  182.         token = strtok(tmp,seq);
  183.         while(token != NULL){
  184.                 if(strstr(token,"+")){
  185.                         strcpy(result->cmd,token);
  186.                 }else if(strlen(token)>0){
  187.                         strcpy(result->content,token);
  188.                 }
  189.                 token = strtok(NULL, seq);
  190.         }
  191.         
  192.         return 1;
  193. }


    接下来就是处理蓝牙消息了

  1. #define PACK_SUB_SIZE 512
  2. // 包临时数据缓存区
  3. char Pack_Buff[1024];
  4. // 固件分包块数量
  5. uint16_t Bin_Sector = 0;
  6. // 分包接收到的固件大小
  7. uint16_t Bin_CurrSize = 0;
  8. // 固件大小
  9. uint16_t Bin_Size = 0;
  10. // 包大小
  11. uint16_t Pack_Size = 0;
  12. uint16_t Pack_Recv_Size = 0;
  13. // 分包接收状态 0 等待 1 正在接收
  14. uint8_t Pack_Recv_Flag = 0;
  15. // 固件名称
  16. #define BIN_NAME        "1:Project.bin"
  17. // 固件缓存名称
  18. #define BIN_CACHE_NAME        "1:tmp.bin"
  19. FIL bin_fil;

  20. // 蓝牙数据处理
  21. void BLE_DataDisponse(void)
  22. {
  23.         if(!BLE_Recv_Flag)return;
  24.         
  25.         char buff[30]={0};
  26.         
  27.         if(strstr(BLE_RecvBuff,"UPGRADE")){
  28.                 // 升级请求
  29.                 if(mountSD() != FR_OK)
  30.                 {
  31.                         printf("挂载SD失败\n");
  32.                         strcpy(buff,"UPGRADE_ERROR");
  33.                         BLE_SendData(buff,strlen(buff));
  34.                         goto end_label;
  35.                 }
  36.                
  37.                 //
  38.                 f_unlink(BIN_CACHE_NAME);
  39.                 // 以新建方式打开文件
  40.                 FRESULT fr = f_open(&bin_fil,BIN_CACHE_NAME,FA_CREATE_ALWAYS | FA_WRITE);
  41.                 if(fr != FR_OK)
  42.                 {
  43.                         printf("创建文件失败\n");
  44.                         strcpy(buff,"UPGRADE_ERROR");
  45.                         BLE_SendData(buff,strlen(buff));
  46.                         
  47.                         
  48.                         goto end_label;
  49.                 }
  50.                
  51.                 strcpy(buff,"READY");
  52.                 BLE_SendData(buff,strlen(buff));
  53.                 printf("UPGRADE READY\n");
  54.                
  55.         }else if(strstr(BLE_RecvBuff,"LENTEST:")){
  56.                
  57.                 // 确定每包大小
  58.                 sprintf(buff,"LEN:%d",strlen(BLE_RecvBuff));
  59.                 BLE_SendData(buff,strlen(buff));
  60.                
  61.         }else if(strstr(BLE_RecvBuff,"BIN:")){
  62.                
  63.                 // 获取bin文件大小
  64.                 Bin_Size = atoi((strchr(BLE_RecvBuff,':')+1));
  65.                 printf("Bin Size=%d\n",Bin_Size);
  66.                
  67.         }else if(strstr(BLE_RecvBuff,"PACK:")){
  68.                
  69.                 // 获取包大小
  70.                 Pack_Size = atoi((strchr(BLE_RecvBuff,':')+1));
  71.                 memset(Pack_Buff,0,sizeof(Pack_Buff));
  72.                 Pack_Recv_Size = 0;
  73.                 Pack_Recv_Flag =1;
  74.                 printf("Pack Size=%d\n",Pack_Size);
  75.                
  76.         }else if(strstr(BLE_RecvBuff,"BIN_END:")){
  77.                 f_close(&bin_fil);
  78.                 strcpy(buff,"START_UPGRADE");
  79.                 BLE_SendData(buff,strlen(buff));
  80.                
  81.                 // 等待100ms后开始升级
  82.                 systick_delay_ms(100);
  83.                 printf("固件接收完成,开始升级\n");
  84.                
  85.                 // 删除原固件文件
  86.                 f_unlink(BIN_NAME);
  87.                 // 将缓存重命名
  88.                 f_rename(BIN_CACHE_NAME,BIN_NAME);
  89.                
  90.                 systick_delay_ms(100);
  91.                
  92.                 USART_DeInit(BLE_USARTx);
  93.                 USART_ClrIntPendingBit(BLE_USARTx,USART_INT_RXDNE);
  94.                 USART_ClrIntPendingBit(BLE_USARTx,USART_INT_IDLEF);
  95.                
  96.         // 跳转前将中断向量表改回去
  97.                 SCB->VTOR = FLASH_BASE;
  98.                 // 清除升级标志
  99.                 app_flash_write(0x00,app_update_flag_addr);
  100.                
  101.                  //关闭所有中断,防止重启事件被打断
  102.                 __set_FAULTMASK(1);
  103.                 // 复位,开始升级
  104.                 NVIC_SystemReset();
  105.                
  106.         }else if(Pack_Recv_Flag){
  107.                 // 开始接收数据
  108.                 strcat(Pack_Buff,BLE_RecvBuff);
  109.                 if(strlen(Pack_Buff) >= Pack_Size)
  110.                 {
  111.                         // 本组包接收完成,开始解包
  112.                         char pack[1024] = {0};
  113.                         uint16_t len = base64_decode(Pack_Buff,strlen(Pack_Buff),pack);
  114.                         printf("Recv Packlen=%d BinLen:%d\n",strlen(Pack_Buff),len);
  115.                         // 打印接收解码后的数据
  116. //                        printf("Pack: \n");
  117. //                        for(uint16_t i=0;i<512;i++)
  118. //                        {
  119. //                                printf("%02X ",pack[i]);
  120. //                                if((i+1)%32 == 0)printf("\n");
  121. //                        }
  122. //                        printf("\n");
  123.                         
  124.                         if(len>512)len=512;
  125.                         // 写入到SD卡虽
  126.                         UINT br=0;
  127.                         f_write(&bin_fil,pack,len,&br);
  128.                         Bin_Sector++;
  129.                         Pack_Recv_Size+=len;
  130.                         
  131.                         // 一组分包处理完成,开始重置状态
  132.                         Pack_Recv_Flag = 0;
  133.                         Pack_Size = 0;
  134.                         memset(Pack_Buff,0,sizeof(Pack_Buff));
  135.                         //printf("分包接收完成\n");
  136.                 }
  137.                 //
  138.                 BLE_Clear_Flag();
  139.                 // 回复本次接收的长度
  140.                 sprintf(buff,"RECV:%d",strlen(BLE_RecvBuff));
  141.                 BLE_SendData(buff,strlen(buff));
  142.                 return;
  143.                
  144.         }
  145.         
  146. end_label:
  147.         printf("BLE Recv: %s\n",BLE_RecvBuff);
  148.         BLE_Clear_Flag();
  149. }


微信小程序蓝牙升级流程:
1、微信小程序连上设备后,发送 UPGRADE 命令,请求升级
2、设备端收到 UPGRADE 命令开始挂载 SD 卡,并做升级前检查,准备就绪后回复命令 READY ,表示准备就绪
3、小程序收到 READY 命令后,开始构建一个长数据包(通常为128位),并以 LENTEST: 开头,测试单包最大支持的发送长度
4、设备端收到 LENTEST: 命令后,回复收到长度,格式 LEN:x,x为实际收到的长度
5、小程序端收到 LEN:x 后确认单包最大可以发送的数据包大小,开始读取固件并按512字节每块进行读取,数据使用base64编码成文本格式数据,
先发送bin文件总大小和每块数据发送前都会发送组包文件大小,格式为:
   1)发送bin文件大小格式为 BIN:x,x为bin大小
   2)每块数据编码完成后发送分包大小命令 PACK:x, x为本次分包编码后文件大小
6、设备端收到 BIN:x 后开始清空缓存区,准备接收数据
7、小程序开始发送数据,设备端每次接收完每块拆分数据后会回复本次分包大小,格式为 RECV:x ,x为接收到的长度,并将收到的数据存到缓存中
8、设备端每完整接收完一块编码数据(即每块 512字节编码后的文本内容)后,会解码成二进制数据存入到SD卡中,直到接收完成为止
9、小程序端在固件全部发送完成后,会发送命令 BIN_END:CRC32 命令 (CRC32内容目前为空),表示本次发送结束。设备端接收到完成命令后,检验完成后开始设备升级。

    最后附上小程序码:
gh_c740ba44072c_344.jpg

    小程序使用说明:因为每个蓝牙的服务ID都是不一样的,而使用到的模块是能用的模块,不可能仅我使用,所我的这个小程序是通过蓝牙的服务ID和蓝牙名称双重编定确认为可管理的设备的是,蓝牙的服务ID为 6E400001-B5A3-F393-E0A9-E50E24DCCA9E ,蓝牙名称需要以 IAP_ 开始,如果大家能修改自己的服务ID和名称可以尝试下。

    整个升级流程完成,此部分内容截图不太好展示,所以后面将以视频方式呈现。

五、总结分享:
    终于完成了整个项目,从开始到完成还是花了大力气的,中间也遇到了许多困难,也得到了一些热心群友的帮助,在此非常感谢。    说下遇到的问题:
     1、最开始遇到了就是SD卡驱动和FATFS文件系统方面,因为完全不知道从哪开始,不知道要做些什么,还有就是开始选型,SD卡驱动到底是用SPI模式还是SDIO,毕竟N32G457是支持SDIO的,但最终选择SPI,主要是考虑到后面我在用其它款MCU时不用再费劲了,只要改下SPI驱动部分就可以很愉快的移植了,速度虽然会慢一些,但暂时应该不会做视频存储方面,应该能达到使用需求了,其实说到底还是想偷下懒了
    2、就是中断向量表的问题,就是跳到APP中时已经设置了,但进入程序后一旦开启中断,就会卡死的问题,因为升级部分也是参考官方的示例,连官方示例都没有修改APP中中断向量值,所以就一直以为跳转时设置了就可以了,没想到在进入APP后,在 SystemInit() 函数中又把中断向量表改回去了,这个后面得到群友的提醒才明白,原来不是我设置的不对,而是又被改了。不过在官方示例中是有说到,但在实例中Bootloader中有做,但APP中去没有处理,走了不少弯路。





  




IAP_SD卡升级.zip (3.61 MB, 下载次数: 66)


微信小程序实现IAP升级.zip (3.61 MB, 下载次数: 42)


G75蓝牙模块-AT指令集-v1.0.0.pdf (423.09 KB, 下载次数: 30)



  
jobszheng 发表于 2023-3-23 10:07 | 显示全部楼层
楼主 厉害了

您这帖子是不是预定了一等奖了。
我只有争取第二名的机会了 555..
 楼主| uant 发表于 2023-3-23 12:51 | 显示全部楼层
jobszheng 发表于 2023-3-23 10:07
楼主 厉害了

您这帖子是不是预定了一等奖了。

过奖了,不过这个好像没有设置奖项,都一样
gyh974 发表于 2023-3-23 13:54 | 显示全部楼层
小程序是怎么做的?
sy12138 发表于 2023-3-23 15:37 | 显示全部楼层
太强了
 楼主| uant 发表于 2023-3-24 13:29 | 显示全部楼层
gyh974 发表于 2023-3-23 13:54
小程序是怎么做的?

不难的,可以找些资料学习下
安小芯 发表于 2023-3-24 15:11 | 显示全部楼层
jobszheng 发表于 2023-3-23 10:07
楼主 厉害了

您这帖子是不是预定了一等奖了。

郑工,你要加油,完成后发布出来。
 楼主| uant 发表于 2023-4-6 09:38 | 显示全部楼层
fcgao 发表于 2023-6-29 16:10 | 显示全部楼层
这个fatfs移植后  每次 f_write只能512字节, 如果超过会报错,错误码为9(FR_INVALID_OBJECT),楼主有碰到过吗?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

4

主题

19

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部

4

主题

19

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部