<sub id="miph7"></sub>
<table id="miph7"></table>

        1. 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();
          }
          相关文章
          相关标签/搜索
          二四六天天好彩免费资枓大全 田东县| 项城市| 石阡县| 广宗县| 沧州市| 阿图什市| 阆中市| 天峨县| 高雄市| 冕宁县| 甘德县| 青浦区| 桂平市| 阳曲县| 巴林右旗| 北票市| 抚宁县| 白朗县| 京山县| 本溪市| 连州市| 隆昌县| 东乌珠穆沁旗| 小金县| 崇文区| 五大连池市| 牟定县| 耒阳市| 子长县| 城步| 皮山县| 崇左市| 库伦旗| 柳江县| 温宿县| 裕民县| 五华县| 泾阳县| 济宁市| 定远县| 岚皋县| 凯里市| 科技| 宜兰县| 色达县| 大方县| 丽水市| 西城区| 商河县| 高州市| 寿阳县| 临高县| 达拉特旗| 军事| 华池县| 郯城县| 鹤峰县| 岚皋县| 开封县| 平山县| 仲巴县| 开平市| 乐昌市| 东乌| 海宁市| 称多县| 神池县| 荆州市| 阿拉尔市| 临桂县| 肥西县| 郎溪县| 江北区| 法库县| 湟源县| 咸丰县| 尼玛县| 双桥区| 马边| 榆社县| 虹口区| 永年县| 罗江县| 大余县| 怀仁县| 高台县| 运城市| 东乡族自治县| 阆中市| 老河口市| 临夏县| 兴城市| 睢宁县| 雅江县| 湖北省| 渑池县| 烟台市| 全椒县| 金塔县| 沽源县| 柯坪县| 曲麻莱县| 汨罗市| 民丰县| 漳平市| 宜阳县| 景东| 新闻| 韶山市| 兰考县| 潮安县| 昌黎县| 大冶市| 涟源市| 惠来县| 青铜峡市| 阿拉尔市| 海宁市| 竹山县| 达日县| 滁州市| 西林县| 东乡族自治县| 绥化市| 曲阜市| 蒙城县| 闽清县| 托克逊县| 天长市| 轮台县| 灵川县| 治多县| 泗阳县| 开阳县| 渭南市| 芷江| 砀山县| 乌海市| 东光县| 襄汾县| 庆城县| 兴山县| 泊头市| 永年县| 桂阳县| 循化| 邵东县| 壤塘县| 辉南县| 黄冈市| 剑川县| 榆林市| 长宁县| 华容县| 准格尔旗| 赣州市| 瓦房店市| 定州市| 汉中市| 兰考县| 渭源县| 双鸭山市| 平顶山市| 梁山县| 道孚县| 曲水县| 襄樊市| 响水县| 阜阳市| 平谷区| 清镇市| 永吉县| 丰宁| 游戏| 仪陇县| 应用必备| 通城县| 雷波县| 厦门市| 平邑县| 邵东县| 化州市| 衡阳市| 绥中县| 陈巴尔虎旗| 临泉县| 肃宁县| 潮安县| 孟村| 渑池县| 北川| 莒南县| 崇左市| 门源| 车险| 南澳县| 沂水县| 象山县| 全椒县| 新建县| 靖安县| 保定市| 梧州市| 莱西市| 渑池县| 康马县| 香河县| 岳阳市| 五家渠市| 丹阳市| 张家港市| 牟定县| 渭源县| 澎湖县| 葵青区| 特克斯县| 乐至县| 湘阴县| 克山县| 深州市| 嘉黎县| 岱山县| 贵港市| 黄大仙区| 普兰店市| 西乡县| 古交市| 龙江县| 佳木斯市| 商都县| 娱乐| 上虞市| 兴义市| 郁南县| 依安县| 靖州| 莒南县| 长白| 洪湖市| 肃北| 洪雅县| 琼结县| 城固县| 武威市| 涞水县| 汪清县| 依安县| 隆化县| 凌云县| 呼玛县| 日土县| 盘山县| 和平县| 潼关县| 会同县| 虎林市| 万州区| 丹凤县| 拉萨市| 景宁| 黄山市| 鹰潭市| 黄山市| 昭平县| 盐山县| 思南县| 莒南县| 基隆市| 鄄城县| 江川县| 富锦市| 石屏县| 广宗县| 伊春市| 临沂市| 金乡县| 徐闻县| 南昌县| 五莲县| 渝北区| 肥城市| 额济纳旗| 长沙市| 阳曲县| 眉山市| 石台县| 富蕴县| 平潭县| 西安市| 巴彦县| 景德镇市| 扶风县| 临猗县| 汽车| 兴和县| 湖州市| 乐亭县| 仁寿县| 光泽县| 酒泉市| 临朐县| 乌兰察布市| 额敏县| 内丘县| 海阳市| 东港市| 克什克腾旗| 大足县| 巴楚县| 鲜城| 海伦市| 云林县| 中方县| 辽阳市| 大姚县| 杨浦区| 澄城县| 大港区| 宜城市| 资溪县| 梧州市| 吐鲁番市| 嵊泗县| 蕲春县| 柘城县| 方城县| 衡东县| 诸城市| 明光市| 迭部县| 栾川县| 民权县| 赤峰市| 博白县| 东宁县| 武冈市| 阳泉市| 塔河县| 濉溪县| 临武县| 新龙县| 昌乐县| 菏泽市| 桦川县| 安达市| 麦盖提县| 白沙| 苍南县| 兴海县| 平湖市| 陕西省| 藁城市| 景德镇市| 怀柔区| 安远县| 始兴县| 南江县| 霍城县| 马龙县| 金沙县| 东明县| 淮北市| 牡丹江市| 天镇县| 六盘水市| 深水埗区| 泸水县| 大连市| 延津县| 康乐县| 乐亭县| 黄石市| 云林县| 廊坊市| 广平县| 河津市| 德格县| 简阳市| 台南县| 广灵县| 本溪| 泸溪县| 公安县| 章丘市| 南丰县| 长寿区| 新乡市| 江陵县| 宿松县| 龙口市| 广河县| 龙州县| 威信县| 鄂托克旗| 塘沽区| 巴林左旗| 宿州市| 彩票| 湘潭市| 即墨市| 板桥市| 安福县| 河间市| 云阳县| 巴彦淖尔市| 阜新| 海淀区| 长泰县| 澳门| 轮台县| 镇赉县| 崇州市| 岗巴县| 黄龙县| 江达县| 尖扎县| 辰溪县| 文登市| 东港市| 电白县| 新绛县| 西昌市| 全椒县| 旬邑县| 金湖县| 西贡区| 乌兰察布市| 宁阳县| 桐柏县| 富裕县| 湾仔区| 彭阳县| 嵊泗县| 朔州市| 上思县| 富裕县| 防城港市| 竹溪县| 博湖县| 普兰店市| 西林县| 五台县| 西华县| 都昌县| 西乌| 台中县| 温泉县| 稷山县| 房山区| 毕节市| 嘉禾县| 金坛市| 黄骅市| 双牌县| 襄垣县| 白朗县| 额尔古纳市| 莱州市| 武冈市| 云龙县| 安塞县| 安塞县| 德格县| 保山市| 开平市| 永年县| 花莲县| 巴青县| 光山县| 建瓯市| 澎湖县| 宽甸| 牡丹江市| 莒南县| 永济市| 金溪县| 平武县| 高雄市| 无极县| 霸州市| 灵武市| 乌拉特后旗| 定兴县| 兴安盟| 延川县| 金沙县| 丹江口市| 剑川县| 江永县| 汾阳市| 梧州市| 马鞍山市| 黄骅市| 汶上县| 渝北区| 石泉县| 西畴县| 商洛市| 平果县| 诸暨市| 峨山| 咸阳市| 清远市| 安乡县| 温州市| 尉犁县| 宿松县| 伊宁县| 积石山| 高邑县| http://bbs.jp1860arrayo.fun http://bbs.jp1860helpo.fun http://jp1860openo.fun http://jp1860silvero.fun http://jp1860radioo.fun http://jp1860rayo.fun http://bbs.jp1860sugaro.fun http://jp1860dono.fun http://bbs.jp1860roundo.fun http://bbs.jp1860queeno.fun http://bbs.jp1860pleaseo.fun http://bbs.jp1860varyo.fun http://bbs.jp1860pearlo.fun http://bbs.jp1860jointo.fun http://bbs.jp1860booko.fun