说明
有些用户使用的非F103系列的单片机做项目,为方便用户移植使用
故写一节关于 STM32F072 DTU实现远程升级的移植教程!
前要
在移植到别的单片机之前,用户需要对自己的单片机有如下的了解:
1.会使用Flash存储数据,是非常的了解Flash的使用!
2.知道怎么跳转程序运行!(这个都是固定的几句程序,找找资料)
3.能用自己的单片机进行TCP访问,并模拟http协议下载数据了
4.会使用定时器.
以上缺一不可!
注:(基础知识不再重复,请用户从第一节开始看!)
因为是使用的DTU,请用户自行配置DTU连接自己的WEB服务器
可以配置成TCP模式,可以配置成我的服务器,先测试下
IP地址: mnif.cn 端口号: 80
配置完成以后发送个指令
GET /1.txt HTTP/1.1rnHost: mnif.cnrnrn
然后会看到返回
开始移植(BootLoader程序制作)
提示:一般BootLoader程序只有保留和服务器通信的程序!
1.把移植文件放到自己的工程
2.建立两个分组,把文件添加到里面,包涵下.h路径
3.编译下工程
修改为自己单片机的头文件包含
4.屏蔽掉stmflash.c文件里面的所有实现程序
5.然后再编译一下,如果出现以下数据类型没有定义
6.最简单的方式是在自己的 stm32fxxxx.h的头文件里面加入
typedef uint32_t u32; typedef uint16_t u16; typedef uint8_t u8;
7.编译一下(替换自己的复位重启程序)
8.关闭自己在BootLoader程序里面使用的所有中断
注:请自行根据自己使用的关闭,我就不写了.
9.修改检测数据某些位置是不是固定的那个数数据
注:我使用的STM32F072 和F103的一样
10.屏蔽掉以前的跳转程序,编写自己的跳转程序
11.把自己的延时函数放到此处调用
注:我就用for循环代替
12.替换自己的请求文件发送函数
注:假设DTU配置好了透传模式.(使用的串口3)
只要往串口发送数据,数据就直接发送给了服务器.
如果自己的程序文件是https访问的,请自行编写https方式发送数据的方式.
13.再次编译一下应该没有任何错误
如果有的话可能是这个地方(关闭 和 打开全局中断),请自行根据自己的单片机修改
13.重定向printf打印(可以没有,最好有!)
注:假设使用的串口1作为日志打印
14.如果自己的串口使用的阻塞方式发送的printf数据,需要屏蔽下面的部分
15.把 IAPTimerOut(); 函数放到1ms定时器中断函数里面
假设使用的定时器3
16.在主程序里面写上以下程序
代码语言:javascript复制IAPInit();
/* ÓиüбêÖ¾ δÁ¬½ÓÉÏWeb·þÎñÆ÷*/
if(IAPStructValue.UpdateFlag && !IAPStructValue.ConnectWebFlag)
{
if(AutoConnectTCP())//Á¬½ÓWeb·þÎñÆ÷,²»Í¬Ä£¿éÇëÌæ»»×Ô¼ºµÄÁ¬½Óº¯Êý(²»¶Ï³¢ÊÔÁ¬½Ó,Ö±ÖÁÁ¬½ÓÉÏ,»ò³ÌÐò³¬Ê±ÖØÆô)
{
IAPStructValue.ConnectWebFlag = 1;
}
}
IAPLoadAPPProgram();//³¢ÊÔ¼ÓÔØÓû§³ÌÐò
IAPDownloadTimeoutFunction();//³ÌÐòÏÂÔس¬Ê±,³¬Ê±ÖØÆô;
IAPMainTimeoutFunction();//ÕûÌåÔËÐг¬Ê±,³¬Ê±ÖØÆô;
IAPGetProgramFile();//·¢ËÍÖ¸Áî»ñÈ¡³ÌÐòÎļþ
IAPWriteData();//°Ñ½ÓÊÕµ½µÄ³ÌÐòÎļþдÈëFlash
17.如果使用的是DTU,DTU已经连接了,所以屏蔽掉需要自己实现连接的部分
18.把 IAPPutDataToLoopList(char Res); 和 IAPHttpHead(char Res);
函数放到和模块通信的串口中断接收里面
19.设置下默认的固件程序下载地址(根据自己的服务器修改)
20.设置工程生成bin文件,然后编译下工程.
21,根据bin文件大小在 stmflash.h中调整下flash分配
STM32_FLASH_SIZE 根据自己的单片机容量调整
如果使用的是128KB Flash的单片机:
FLASH_IAP_SIZE XX 根据BootLoader生成的bin文件大小设置(该值需要大于生成的bin文件大小)
FLASH_UPDATE_SIZE 1 //存储更新相关数据所有FLASH大小,不需要改动.
FLASH_USERDATA_SIZE XX 如果用户存储的数据量比较大,增加该值即可
如果使用的是256KB及其以上 Flash的单片机:
FLASH_IAP_SIZE XX 根据BootLoader生成的bin文件大小设置(该值需要大于生成的bin文件大小,设置为4的倍数)
FLASH_UPDATE_SIZE 4 //存储更新相关数据所有FLASH大小,设置为4
FLASH_USERDATA_SIZE XX 如果用户存储的数据量比较大,增加该值即可(设置为4的倍数)
我使用的单片机是STM32F072RBT6 ,所以设置的 STM32_FLASH_SIZE 是128KB
我刚才的BootLoader程序用到了16KB,所以我设置了18KB
22.最后一个工作是完善Flash里面的函数
读写半字
代码语言:javascript复制//¶ÁÈ¡Ö¸¶¨µØÖ·µÄ°ë×Ö(16λÊý¾Ý)
//faddr:¶ÁµØÖ·(´ËµØÖ·±ØÐëΪ2µÄ±¶Êý!!)
//·µ»ØÖµ:¶ÔÓ¦Êý¾Ý.
u16 STMFLASH_ReadHalfWord(u32 faddr)
{
return *(__IO uint16_t*)faddr;
}
#if STM32_FLASH_WREN //Èç¹ûʹÄÜÁËд
//²»¼ì²éµÄдÈë
//WriteAddr:ÆðʼµØÖ·
//pBuffer:Êý¾ÝÖ¸Õë
//NumToWrite:°ë×Ö(16λ)Êý
void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u16 i;
for(i=0;i<NumToWrite;i )
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
WriteAddr =2;//µØÖ·Ôö¼Ó2.
}
}
指定地址写入指定长度的半字数据
代码语言:javascript复制//´ÓÖ¸¶¨µØÖ·¿ªÊ¼Ð´ÈëÖ¸¶¨³¤¶ÈµÄÊý¾Ý
//WriteAddr:ÆðʼµØÖ·(´ËµØÖ·±ØÐëΪ2µÄ±¶Êý!!)
//pBuffer:Êý¾ÝÖ¸Õë
//NumToWrite:°ë×Ö(16λ)Êý(¾ÍÊÇҪдÈëµÄ16λÊý¾ÝµÄ¸öÊý.)
#if STM32_FLASH_SIZE<256
#define STM_SECTOR_SIZE 1024 //×Ö½Ú
#else
#define STM_SECTOR_SIZE 2048
#endif
u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//×î¶àÊÇ2K×Ö½Ú
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
FLASH_EraseInitTypeDef f;
f.TypeErase = FLASH_TYPEERASE_PAGES;
f.NbPages = 1;
uint32_t PageError = 0;
u32 secpos; //ÉÈÇøµØÖ·
u16 secoff; //ÉÈÇøÄÚÆ«ÒƵØÖ·(16λ×Ö¼ÆËã)
u16 secremain; //ÉÈÇøÄÚÊ£ÓàµØÖ·(16λ×Ö¼ÆËã)
u16 i;
u32 offaddr; //È¥µô0X08000000ºóµÄµØÖ·
if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE 1024*STM32_FLASH_SIZE)))return;//·Ç·¨µØÖ·
HAL_FLASH_Unlock(); //½âËø
offaddr=WriteAddr-STM32_FLASH_BASE; //ʵ¼ÊÆ«ÒƵØÖ·.
secpos=offaddr/STM_SECTOR_SIZE; //ÉÈÇøµØÖ· 0~127 for STM32F103RBT6
secoff=(offaddr%STM_SECTOR_SIZE)/2; //ÔÚÉÈÇøÄÚµÄÆ«ÒÆ(2¸ö×Ö½ÚΪ»ù±¾µ¥Î».)
secremain=STM_SECTOR_SIZE/2-secoff; //ÉÈÇøÊ£Óà¿Õ¼ä´óС
if(NumToWrite<=secremain)secremain=NumToWrite;//²»´óÓÚ¸ÃÉÈÇø·¶Î§
while(1)
{
STMFLASH_Read(secpos*STM_SECTOR_SIZE STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//¶Á³öÕû¸öÉÈÇøµÄÄÚÈÝ
for(i=0;i<secremain;i )//УÑéÊý¾Ý
{
if(STMFLASH_BUF[secoff i]!=0XFFFF)break;//ÐèÒª²Á³ý
}
if(i<secremain)//ÐèÒª²Á³ý
{
f.PageAddress = secpos*STM_SECTOR_SIZE STM32_FLASH_BASE;
HAL_FLASHEx_Erase(&f, &PageError);//²Á³ýÕâ¸öÉÈÇø
for(i=0;i<STM_SECTOR_SIZE;i =2)//ÔÙ´ÎÅжÏÊÇ·ñÓвÁ³ýʧ°Ü
{
if(STMFLASH_ReadHalfWord(secpos*STM_SECTOR_SIZE STM32_FLASH_BASE i)!=0XFFFF)
{
f.PageAddress = secpos*STM_SECTOR_SIZE STM32_FLASH_BASE;
HAL_FLASHEx_Erase(&f, &PageError);//²Á³ýÕâ¸öÉÈÇø
break;
}
}
for(i=0;i<secremain;i )//¸´ÖÆ
{
STMFLASH_BUF[i secoff]=pBuffer[i];
}
STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//дÈëÕû¸öÉÈÇø
}
else
{
STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//дÒѾ²Á³ýÁ˵Ä,Ö±½ÓдÈëÉÈÇøÊ£ÓàÇø¼ä.
}
if(NumToWrite==secremain)break;//дÈë½áÊøÁË
else//дÈëδ½áÊø
{
secpos ; //ÉÈÇøµØÖ·Ôö1
secoff=0; //Æ«ÒÆλÖÃΪ0
pBuffer =secremain; //Ö¸ÕëÆ«ÒÆ
WriteAddr =secremain; //дµØÖ·Æ«ÒÆ
NumToWrite-=secremain; //×Ö½Ú(16λ)ÊýµÝ¼õ
if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//ÏÂÒ»¸öÉÈÇø»¹ÊÇд²»Íê
else secremain=NumToWrite;//ÏÂÒ»¸öÉÈÇø¿ÉÒÔдÍêÁË
}
};
HAL_FLASH_Lock();//ÉÏËø
}
写一个半字,并加入判断写入的是否正确
代码语言:javascript复制char WriteFlashHalfWord(uint32_t WriteAddress,u16 data)
{
HAL_FLASH_Unlock();
FlashStatus = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddress,data);//дÈëÊý¾Ý
if(FlashStatus == HAL_OK)//²Ù×÷Íê³É
{
if(STMFLASH_ReadHalfWord(WriteAddress) == data)//¶Á³öµÄºÍдÈëµÄÒ»ÖÂ
FlashStatus = 0;//¶Á³öºÍдÈëµÄÒ»ÖÂ
else
FlashStatus = 5;
}
HAL_FLASH_Lock();//ÉÏËø
return FlashStatus;
}
指定地址读取指定长度的半字数据
代码语言:javascript复制void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
{
u16 i;
for(i=0;i<NumToRead;i )
{
pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//¶ÁÈ¡2¸ö×Ö½Ú.
ReadAddr =2;//Æ«ÒÆ2¸ö×Ö½Ú.
}
}
擦除页
注意:假设需要擦除20KB,BootLoader会传进来20.
BootLoader里面只是传进来需要擦除的KB数.
如果自己的单片机是以1024字节一页,那么此函数直接控制擦除20页即可.
如果自己的单片机是以2048作为一页,那么此函数就应该擦除10页!
所以源码里面有一句 if(STM_SECTOR_SIZE==2048){PageCnt=PageCnt/2;}
代码语言:javascript复制/**
* @brief ²Á³ýFlash
* @waing
* @param EraseAddress ²Á³ýµÄµØÖ·
* @param PageCnt ²Á³ýÁ¬ÐøµÄ¼¸Ò³(1024¼ÆËã)
* @param None
* @retval 4:³É¹¦
* @example
**/
char FlashErasePage(uint32_t EraseAddress,u16 PageCnt)
{
FLASH_EraseInitTypeDef f;
f.TypeErase = FLASH_TYPEERASE_PAGES;
f.NbPages = 1;
uint32_t PageError = 0;
u32 i=0;
u32 secpos; //ÉÈÇøµØÖ·
if(EraseAddress<STM32_FLASH_BASE||(EraseAddress>=(STM32_FLASH_BASE 1024*STM32_FLASH_SIZE)))return 0;//·Ç·¨µØÖ·
secpos = EraseAddress-STM32_FLASH_BASE;//ʵ¼ÊµØÖ·
secpos = secpos/STM_SECTOR_SIZE;//ÉÈÇøµØÖ·
if(STM_SECTOR_SIZE==2048){PageCnt=PageCnt/2;}
// if(FLASH_GetStatus() == FLASH_COMPLETE)//¿ÉÒÔ²Ù×÷Flash
{
HAL_FLASH_Unlock();
for(i=0;i<PageCnt;i )
{
f.PageAddress = secpos*STM_SECTOR_SIZE STM32_FLASH_BASE;
FlashStatus = HAL_FLASHEx_Erase(&f, &PageError);//²Á³ýÕâ¸öÉÈÇø
if(FlashStatus != HAL_OK) break;
secpos ;
}
HAL_FLASH_Lock();//ÉÏËø
}
return FlashStatus;
}
23.编译下载到单片机,查看下打印的信息
开始移植(APP用户程序制作)
1.复制一份上面的BootLoader程序作为用户程序
2.修改IAP.h文件, #define IAPProgramSelect
3.屏蔽
4.取消屏蔽
5.修改型号和info.txt文件的下载地址(根据自己的情况修改)
6.根据BootLoader先前打印的信息调整用户程序配置
7.此时编译下工程
报错原因:
咱们把48个中断地址强制放在了RAM的 0x200000000的开始地址上了.
软件编译的时候,有些变量也放到了这个地址上,然后软件就报冲突错误.
设置下RAM的偏移,让程序里面的变量从偏移的地址开始存放
7.在主函数里面添加以下函数
IAPInfoPathInit();
IAPUpdateDispose();
注意:根据前面的说明,实际中最好应该确保用户程序执行一段时间没有问题以后
再调用 IAPUpdateDispose();函数
8.增加使用get指令访问info.txt文件命令
连接的服务器的IP地址 IAPStructValue.IP(字符串) 端口号 IAPStructValue.Port
文件路径 IAPStructValue.Path
以上信息是由 IAPInfoPathInit();函数解析而来
如果使用的是DTU,只需要 用到 文件路径 IAPStructValue.Path
获取 info.txt 文件的get指令拼接方式如下:
MainLen = sprintf((char*)MainBuffer,"GET %s HTTP/1.1rnHost: %srnrn",IAPStructValue.Path,IAPStructValue.IP);
然后把这个消息发给服务器即可
9.编写解析获取的文件信息
提示一下info.txt文件内容格式:
{"version":"0.0.1","size":15990,"url":"http://mnif.cn/ota/hardware/STM32ESP8266BK/user_crc.bin","info":"1.解决了部分BUG 2.优化了部分程序"}
注:具体的提取程序,请自行编写,可参照提供的源码.
10.修改下版本
11.编译下工程生成bin文件,打开OTA Tools 软件
选择bin文件,点击带有crc校验的bin文件
11.根据生成的信息修改 info.txt文件信息
12.然后把 info.txt 和 bin文件放到云端即可
注意: info.txt的路径要根据自己设置的地址进行存放
bin文件路径,要根据 info.txt里面的url地址进行存放