<thead id="dlkbl"></thead>
    <sub id="dlkbl"><del id="dlkbl"></del></sub>

  • <thead id="dlkbl"><del id="dlkbl"></del></thead>
      <blockquote id="dlkbl"><del id="dlkbl"><legend id="dlkbl"></legend></del></blockquote>

      STM32 IAP在线升级在项目中的应用

      • IAP即在线应用编程,平时我们写好的程序都是通过下载器去下载的,但是对于组装好的产品在想更新底层硬件代码是很麻烦的事情,如果在公司情况还没那么糟糕,要是发出去的产品出现bug,你不可能要用户给你下载程序的。IAP这种技术,我们就可以像软件一样,可以实现远程更新了。我们需要做的就是,写FLASH读写接口,程序可以通过串口,网口等进行下发,然后内部调用FLASH写函数,把代码写到对于区域即可。
      • 当然这只是一个大概思路,具体实现还是要注意很多细节的东西。网上也有好多关于这方面的教程,但是能用到项目中的却很少,我写这边文章就是想和大家分享我在项目中实际应用。
      • 想了想,就以我实际开发过程来写吧,这里对新人来说也可以当作一篇教程来学习。

      一、FLASH读写接口的实现

      • 这里大家可以参考原子哥的FLASH模拟EEPROM实验来写。因为我们做写的是程序,数据流很大,需要做一些改动,这样写入速度会快很多。
      • 首先我们来了解一下STM32F1的FLASH,如下图,我们要看的只有主存储区,可以看到单片机内部FLASH是按2K一页来区分的,而且对其读写是有如下几点要求:
      1. 每次写入必须为2个字节。
      2. 写入地址为2的倍数。
      3. 写入之前必须是被擦除的(即其值为0xFFFF),也可以理解为,写入数据只能把位写0,不能置1。
      4. 写入速度≤24MHz。
      5. 擦除方式:页擦除和正片擦除(这个要注意,如果你是做数据保存,就必须先把这一页的数据读取到缓存中,然后修改缓存里的值,再整页写入)。

      分享图片

      • FLASH写入过程如下:
      1. 解锁
      2. 读页数据到缓存
      3. 页擦除
      4. 修改缓存数据
      5. 把缓存数据页写入
      6. 上锁
      • 首先我们都有一下基本的读写函数,写函数官方库已经为我们提供,我们要写的就是读函数,代码如下:
      //读1个字节
      uint8_t FLASH_ReadByte(uint32_t Addr)
      {
          return *(vu8 *)Addr;
      }
      //读2个字节
      uint16_t FLASH_ReadHalfWord(uint32_t Addr)
      {
          return *(vu16 *)Addr;
      }
      //读N个字节
      void FLASH_ReadNByte(uint32_t Addr,uint8_t *pBuff,uint32_t Len)
      {
          uint32_t i;
          
          for(i = 0;i < Len;i++)
          {
              pBuff[i] = FLASH_ReadByte(Addr);
              Addr += 1;
          }
      }
      • 然后就是在基本函数的基础上面扩展我们需要的函数,因为升级过程中,我们需要保存一些标志,需要用到读某一页的函数。
      #define STM32_SECTOR_SIZE   2048    //页大小
      #define STM32_SECTOR_NUM    255     //页数
      
      //STM32 FLASH的起始地址
      #define STM32_FLASH_BASE 0x08000000
      
      void FLASH_ReadPage(uint8_t Page_Num,uint8_t *pBuff)
      {
          uint16_t i;
          uint32_t Buff;
          uint32_t Addr;
          
          //是否超出范围
          if(Page_Num > STM32_SECTOR_NUM)
              return;
          //先计算页首地址
          Addr = Page_Num * STM32_SECTOR_SIZE + STM32_FLASH_BASE;
          
          for(i = 0;i < STM32_SECTOR_SIZE;i += 4)
          {
              Buff = FLASH_ReadWord(Addr);
              
              pBuff[i]   = Buff;
              pBuff[i+1] = Buff >> 8;
              pBuff[i+2] = Buff >> 16;
              pBuff[i+3] = Buff >> 24;
              
              Addr += 4;
          }
      }
      • 需要读写就需要写页,再来写一个写页函数,由于一次只能写2字节,所有我们调用的是官方库函数FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)。
      void FLASH_WritePage(uint8_t Page_Num,uint8_t *pBuff)
      {
          uint16_t i;
          uint16_t Buff;
          uint32_t Addr;
          
          //是否超出范围
          if(Page_Num > STM32_SECTOR_NUM)
              return;
          //解锁
          FLASH_Unlock();
          //先计算页首地址
          Addr = Page_Num * STM32_SECTOR_SIZE + STM32_FLASH_BASE;
          
          for(i = 0;i < STM32_SECTOR_SIZE ;i += 2)
          {
              Buff = ((uint16_t)pBuff[i+1] << 8) | pBuff[i];
              FLASH_ProgramHalfWord(Addr,Buff);
              Addr += 2;
          }
          //上锁
          FLASH_Lock();
      }
      • 然后我们还要写两个重要的函数,他们都是写N字节函数,区别是一个要先把页数据读到缓存中,再写入,这个函数用来保存一些标志等等,另一个函数我们不负责扇区数据擦除保存等处理,我们只管往某个地址写入数据,这个函数用来做升级用,这样速度会快一些。下来就来实现这两个函数。
      void FLASH_WriteNData(uint32_t Addr,uint8_t *pBuff,uint32_t Len)
      {
          uint32_t Offset;
          uint8_t  Page_Num;
          uint16_t Page_Offset;
          uint16_t Free_Space;
          uint16_t i;
          
          if((Addr < STM32_FLASH_BASE) || (Addr > STM32_FLASH_END))
              return;
          
          Offset = Addr - STM32_FLASH_BASE;//偏移地址
          Page_Num = Offset / STM32_SECTOR_SIZE;//得到地址所在页
          Page_Offset = Offset % STM32_SECTOR_SIZE;//在页内的偏移地址
          Free_Space = STM32_SECTOR_SIZE -  Page_Offset;//页区剩余空间
          //要写入的数据是否大于剩余空间
          if(Len <= Free_Space)
              Free_Space = Len;
          
          FLASH_Unlock();//解锁
          
          while(1)
          {
              FLASH_ReadPage(Page_Num,STM32_FLASH_BUFF);//先把数据读到缓存中
              FLASH_ErasePage(Page_Num * STM32_SECTOR_SIZE + STM32_FLASH_BASE);//页擦除
              //修改缓存数据
              for(i = 0;i < Free_Space;i++)
              {
                  STM32_FLASH_BUFF[i+Page_Offset] = pBuff[i];
              }
              FLASH_WritePage(Page_Num,STM32_FLASH_BUFF);//把缓存数据写入
              //判断是否超出当前页,超出进入下一页
              if(Len == Free_Space)
                  break;
              else
              {
                  Page_Num++;//下一页
                  Page_Offset = 0;
                  pBuff += Free_Space;
                  
                  Len -= Free_Space;
                  if(Len > STM32_SECTOR_SIZE)
                      Free_Space = STM32_SECTOR_SIZE;
                  else
                      Free_Space = Len;
              }
          }
          FLASH_Lock();
      }
      void FLASH_WriteNByte(uint32_t Addr,uint8_t *pBuff,uint32_t Len)
      {
          uint16_t i;
          uint16_t temp = 0;
          
          if((Addr < STM32_FLASH_BASE) || (Addr > STM32_FLASH_END))
              return;
          
          FLASH_Unlock();//解锁
          
          for(i = 0;i < Len;i += 2)
          {
              temp = pBuff[i];
              temp |= (uint16_t)pBuff[i+1] << 8;
              
              FLASH_ProgramHalfWord(Addr,temp);
              Addr += 2;
              if(Addr > STM32_FLASH_END)
              {
                  FLASH_Lock();
                  return;
              }
          }
          FLASH_Lock();
      }
      • 因为我们程序可能会占用多页,所以我们需要写一个擦除指定页的函数,代码如下。
      void Flash_EraseSector(uint8_t Start_Page,uint8_t End_Page)
      {
          uint8_t i;
          uint8_t num = 0;
          
          if(Start_Page > End_Page)
              return;
          
          FLASH_Unlock();//解锁
          
          num = End_Page - Start_Page;//擦除页数
          
          for(i = 0;i <= num;i++)
          {
              FLASH_ErasePage((Start_Page + i) * STM32_SECTOR_SIZE + STM32_FLASH_BASE);//页擦除
          }
          
          FLASH_Lock();
      }
      • 我们写了几个接口,我们要测试一下是否好用,开发就是要稳扎稳打,保证每个功能稳定。测试嘛,给它们搭一个小舞台,让它们上去表演一下,哈哈。我们要的就是往某页写入数据,再读出来,看看是否相同,注意你程序的大小不要把当前运行的代码覆盖咯。
      void Test_Flash_WR(uint8_t Page_Num)
      {
          uint16_t i = 0;
          uint8_t j = 0;
          
          //是否超出范围
          if(Page_Num > STM32_SECTOR_NUM)
              return;
          
          for(i = 0;i < STM32_SECTOR_SIZE;i++)
          {
              buff[i] = j++;
          }
          //页擦除
      //  Flash_EraseSector(Page_Num,Page_Num);
          //写入
      //  FLASH_WritePage(Page_Num,buff);
          //写入
      //  FLASH_WriteNByte(Page_Num * STM32_SECTOR_SIZE + STM32_FLASH_BASE,buff,STM32_SECTOR_SIZE);
          //写入
          FLASH_WriteNData(Page_Num * STM32_SECTOR_SIZE + STM32_FLASH_BASE + 4,buff,10);
          //清零
          memset(buff,0,STM32_SECTOR_SIZE);
          //读出
          FLASH_ReadPage(Page_Num,buff);
          
          for(i = 0;i < STM32_SECTOR_SIZE;i++)
          {
              printf("%02X ",buff[i]);
          }
          printf("\r\n");
      }

      二、分区规划

      • 写完FLASH接口函数,下来就是进行对我们的FLASH进行分区了,这样才知道我们的数据到底应该写到哪里。下面是我自己使用的分区方式。
      • 首先是Bootloader分区,放置我们的引导程序,主要负责判断标志来决定是跳转到app分区运行,还是进行程序更新,又或者是需要恢复出厂程序。
      • 其次是APP分区,这里存放的是我们的主程序。
      • 下来是Download分区,负责存储我们下发的更新代码,这样做是保证代码完整再进行更新,保证更新成功率。
      • 最后是Flag分区,存放一些标志性数据。
      分区 大小 扇区 备注
      Bootloader 12K 0 - 5 引导程序
      APP 100K 6 - 55 存储App
      Download 100K 56 - 105 下载缓存
      Flag 2K 255 升级标志

      三、Bootloader程序实现

      • 说一下Bootloader程序设计思路吧,单片机上电进入Bootloader程序,先判断升级标志是否需要升级固件,需要就把Download分区拷贝到app分区,然后清空升级标志;下来判断是否右APP分区中断向量表是否正确,正确说明有app可以跑,直接跳转到app运行;如果没有在bootloader里循环等待接收app固件。下面是我程序的整体框架:
      #define FLASH_APP_ADDR                STM32_SECTOR6_ADDR
      #define FLASH_DOWNLOAD_ADDR     STM32_SECTOR56_ADDR
      #define FLASH_APP_FLAG                 STM32_SECTOR255_ADDR
      #define FLASH_UPDATA_FLAG           FLASH_APP_FLAG + 2
      
      int main(void)
      {
          SystemInit();
          NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
          
          Delay_Init();
          
          LED_Init();
          KEY_Init();
          USART1_Init(115200);
          
          //判断是否需要升级固件
          if(FLASH_ReadHalfWord(FLASH_UPDATA_FLAG) == 0xAA55)
          {
              printf("Updata App...\r\n");
              
              IAP_Copy_App();//拷贝到app分区
              
              printf("Updata App Succeed...\r\n");
          }
          //判断是否有APP程序
          //中断向量表判断
          if(((*(vu32*)(FLASH_APP_ADDR + 4))&0xFF000000) == 0x08000000)
          {
              printf("Run App...\r\n\r\n");
              
              Delay_ms(10);
              IAP_Load_App(FLASH_APP_ADDR);//转到app
          }
          
          printf("No App\r\n");
          
          TIM3_Init(1000,72);//定时0.001s
          
          while(1)
          {
              Task_Process();
              
              if(USART1_RX_CNT > 0)
              {
                  IAP_WriteBin(FLASH_DOWNLOAD_ADDR,USART1_RxBuff,USART1_RX_CNT);
                  USART1_RX_CNT = 0;
              }
          }
      }
      • 这里我们需要实现的函数有IAP_Copy_App()和IAP_Load_App(),代码如下:
      typedef void (*IAP_Fun)(void);
      IAP_Fun JumpApp;
      
      uint8_t STM32_FLASH_BUFF[STM32_SECTOR_SIZE] = {0};
      
      void IAP_Copy_App(void)
      {
          uint8_t i;
          uint8_t buf[2] = {0x00,0x00};
          //擦除App扇区
          Flash_EraseSector(6,55);
          
          for(i = 0;i < 50;i++)
          {
              FLASH_ReadPage(56 + i,STM32_FLASH_BUFF);
              FLASH_WritePage(6 + i,STM32_FLASH_BUFF);
              LED3 = !LED3;
          }
          
          FLASH_WriteNData(FLASH_UPDATA_FLAG,buf,2);
      }
      
      void IAP_Load_App(uint32_t Addr)
      {
          //检查栈顶地址是否合法
          if(((*(vu32*)Addr) & 0x2FFE0000) == 0x20000000)
          {
              __disable_irq();
              JumpApp = (IAP_Fun)*(vu32 *)(Addr + 4);
              MSR_MSP(*(vu32 *)Addr);
              JumpApp();
          }
      }
      • 然后我们还要写一个关于下载程序的函数IAP_WriteBin(),一般我们数据会通过串口或网口下发过来,下发的数据要保存到下载分区,所以需要一个写数据到下载分区的函数。
      void IAP_WriteBin(uint32_t Addr,uint8_t *pBuff,uint32_t Len)
      {
          uint8_t buf[2] = {0xAA,0x55};
          //擦除App扇区
          Flash_EraseSector(6,55);
          //写入程序
          FLASH_WriteNByte(Addr,pBuff,Len);
          //更新标记
          FLASH_WriteNData(FLASH_UPDATA_FLAG,buf,2);
          //复位单片机
          NVIC_SystemReset();
      }
      相关文章
      相关标签/搜索
      4887王中王鉄算盘开奖结 栖霞市| 赤城县| 永宁县| 宣城市| 阿勒泰市| 聂荣县| 龙门县| 吕梁市| 增城市| 昌宁县| 墨竹工卡县| 枣阳市| 安达市| 鹰潭市| 晋宁县| 安徽省| 岑溪市| 渭源县| 阿巴嘎旗| 承德县| 治县。| 榆林市| 永康市| 栾川县| 金山区| 广平县| 府谷县| 天峨县| 确山县| 肥西县| 西华县| 温泉县| 和林格尔县| 获嘉县| 绥江县| 深圳市| 安乡县| 东乌珠穆沁旗| 常山县| 忻州市| 永寿县| 罗山县| 班戈县| 临猗县| 绥芬河市| 余江县| 兴城市| 巴彦淖尔市| 仙桃市| 巴中市| 砚山县| 邵阳县| 秦皇岛市| 东明县| 富宁县| 原平市| 乌海市| 青铜峡市| 临沂市| 通道| 丹巴县| 临西县| 富裕县| 子长县| 安庆市| 浦北县| 万山特区| 兴安县| 凤凰县| 佳木斯市| 德清县| 济宁市| 乌海市| 合阳县| 千阳县| 崇礼县| 万盛区| 南通市| 鹤壁市| 凤庆县| 龙山县| 泗水县| 台州市| 竹北市| 平凉市| 横山县| 蛟河市| 苗栗县| 梁平县| 克什克腾旗| 泾源县| 石家庄市| 凭祥市| 新乐市| 崇明县| 姜堰市| 龙口市| 百色市| 乌拉特中旗| 永新县| 丰城市| 皋兰县| 沾化县| 张掖市| 大庆市| 沙坪坝区| 精河县| 博湖县| 盐源县| 会宁县| 鄂州市| 桂平市| 小金县| 洪雅县| 横峰县| 耿马| 刚察县| 株洲市| 麻栗坡县| 开原市| 齐齐哈尔市| 绵竹市| 石城县| 曲周县| 湘西| 寿光市| 阿克| 柘城县| 酒泉市| 如东县| 筠连县| 江华| 建宁县| 柘荣县| 古蔺县| 田东县| 乐昌市| 枞阳县| 东阳市| 财经| 洛南县| 威海市| 新竹市| 宿迁市| 新安县| 昭觉县| 彰化县| 林西县| 龙海市| 东方市| 阳朔县| 抚宁县| 林芝县| 萨迦县| 什邡市| 婺源县| 东乌| 台南市| 中牟县| 乃东县| 龙江县| 南宁市| 台北市| 阿巴嘎旗| 仙桃市| 固始县| 东乡县| 潜江市| 南通市| 崇信县| 灯塔市| 南投市| 平顶山市| 鞍山市| 望城县| 连山| 朔州市| 上栗县| 凌云县| 陆良县| 丹巴县| 澄城县| 扶绥县| 西华县| 东海县| 伊吾县| 肥东县| 张掖市| 镶黄旗| 南召县| 康定县| 景东| 新干县| 南昌市| 抚远县| 吉安县| 绍兴县| 航空| 汉阴县| 连州市| 聊城市| 临猗县| 普宁市| 扶余县| 新疆| 海兴县| 平舆县| 界首市| 海口市| 柞水县| 米脂县| 申扎县| 子长县| 钟山县| 威海市| 万年县| 福鼎市| 漠河县| 南和县| 贵南县| 海宁市| 凤庆县| 肥西县| 镇远县| 任丘市| 济源市| 新田县| 扶绥县| 开阳县| 元江| 双江| 沧州市| 图木舒克市| 牙克石市| 盱眙县| 宁强县| 伊春市| 象州县| 永寿县| 泗阳县| 武义县| 贵定县| 西乌| 育儿| 余姚市| 中阳县| 红河县| 泌阳县| 闵行区| 故城县| 铜梁县| 武邑县| 宝丰县| 称多县| 晋城| 聂拉木县| 桑植县| 宜君县| 淳化县| 额尔古纳市| 临沂市| 溧阳市| 呼伦贝尔市| 灌云县| 灵丘县| 旬邑县| 湘乡市| 嘉义县| 龙泉市| 阿勒泰市| 宝鸡市| 邢台市| 遂昌县| 修武县| 庐江县| 湖南省| 潮安县| 河源市| 新蔡县| 闽侯县| 寿宁县| 突泉县| 攀枝花市| 巴青县| 望奎县| 剑川县| 秦皇岛市| 大化| 宜丰县| 静宁县| 宁河县| 孟连| 武汉市| 津市市| 封丘县| 杭州市| 余姚市| 搜索| 曲沃县| 敦煌市| 武鸣县| 肇源县| 丹棱县| 巴林右旗| 崇文区| 图们市| 凤凰县| 河池市| 舞钢市| 秭归县| 汝阳县| 大余县| 五常市| 出国| 通州区| 应城市| 山阴县| 云龙县| 延边| 惠水县| 高清| 勐海县| 门头沟区| 甘谷县| 洛扎县| 拜城县| 黄冈市| 建宁县| 凤台县| 襄垣县| 景洪市| 横峰县| 三江| 泊头市| 广宁县| 黑水县| 武清区| 苍南县| 惠州市| 定日县| 乌什县| 双江| 南城县| 肥城市| 永新县| 兰西县| 伊通| 海淀区| 长顺县| 麦盖提县| 尚志市| 云南省| 邯郸县| 台前县| 南澳县| 柞水县| 澄城县| 措勤县| 木里| 永州市| 云梦县| 潜江市| 法库县| 紫阳县| 咸宁市| 滁州市| 普定县| 大港区| 东海县| 三明市| 光泽县| 汝州市| 万载县| 澄迈县| 定边县| 黄平县| 平乐县| 德阳市| 长海县| 棋牌| 晋城| 来凤县| 深州市| 隆安县| 凌源市| 贵阳市| 长顺县| 乌审旗| 神池县| 宣恩县| 临颍县| 茶陵县| 昂仁县| 渭源县| 宽城| 汉寿县| 辽源市| 安多县| 龙南县| 贵南县| 交城县| 临夏县| 泊头市| 隆昌县| 噶尔县| 扎鲁特旗| 齐齐哈尔市| 贡嘎县| 南投市| 牙克石市| 喀什市| 华亭县| 开江县| 遂川县| 沧州市| 甘谷县| 闵行区| 镇坪县| 巢湖市| 广元市| 镇坪县| 吉林省| 丽水市| 巢湖市| 建阳市| 宝兴县| 乐至县| 六枝特区| 新安县| 七台河市| 富民县| 淮滨县| 白城市| 安顺市| 新郑市| 太谷县| 吉水县| 吴忠市| 黄骅市| 黑龙江省| 隆化县| 乐清市| 闵行区| 漳浦县| 浦江县| 葫芦岛市| 临汾市| 务川| 宁德市| 新绛县| 雷波县| 勃利县| 盐源县| 胶州市| 永寿县| 巴林右旗| 沛县| 安庆市| 芜湖市| 东安县| 泌阳县| 格尔木市| 报价| 万盛区| 商丘市| 宿州市| 马边| 威海市| 本溪| 江山市| 乐山市| 新民市| 新泰市| 民县| 延长县| 无为县| 崇明县| 上饶县| 铜陵市| 长岛县| 南乐县| 莲花县| 南丰县| 原平市| 兰州市| 营山县| 凤城市| 平原县| 新巴尔虎右旗| 西安市| 繁昌县| 阳东县| 和政县| 永春县| 明光市| 潮安县| 漠河县| 吉林省| 蒲城县| 民权县| 永春县| 东城区| 白水县| 抚远县| 聊城市| 久治县| 六安市| 灵宝市| 玛曲县| 迁西县| http://m.jp1860callo.fun http://3g.jp1860flyo.fun http://jp1860hato.fun http://www.jp1860judgeo.fun http://m.jp1860booko.fun http://m.jp1860advanceo.fun http://m.jp1860cocko.fun http://m.jp1860surveyo.fun http://wap.jp1860certificateo.fun http://bbs.jp1860cottono.fun http://jp1860catcho.fun http://bbs.jp1860stableo.fun http://jp1860classo.fun http://bbs.jp1860outputo.fun http://bbs.jp1860judgeo.fun