补充:html
你们应该知道咱的单片机程序都是存在flash里面的数组
你们应该知道ISP吧,就是咱用串口下载51单片机,32单片机等等app
你们知道过程不函数
咱用ISP的时候是下载的hex文件测试
知道下载的时候为何须要从新启动单片机不?????spa
其实单片机在启动的时候内部会先执行个程序:检测是否是给我下载程序,若是是,我就接收,而后看看写到哪一个地址里面.net
固然总体就是这样,其实单片机和烧录他的软件通讯的时候,他俩还有具体协议,设计
本身想一想就明白,,,你直接发给他,你不问问写好了没,能够发下一条了不....也就这么回事3d
单片机写好了,而后就是读取运行呗....指针
接着看IAP
净整一些名词,看着就犯迷糊
您直接说:本身写一个程序控制把程序写到flash的哪一个位置,而后告诉单片机从哪里开始读取运行
既然是本身写,简单的就是这样:把程序放到0x8004000(假设位置是这个)
首先明确一点,单片机一开始运行仍是从头开始的,默认就是下载到这个位置
因此咱本身写的那个控制程序(IAP程序),就写到这里,而后用串口或者下载器下载到里面
假设咱如今单片机的串口接受的就是单片机程序,咱(本身写的IAP程序的功能)就是把串口接收的程序从flash的(0x8004000)那个地址开始写入
支持IAP的单片机都有函数支持写Flash的
而后写完以后呢,调用单片机给的API函数就能够了,
好比32的,,,那个appxaddr就是 0x8004000
这样就跳转过去执行去了
说是这样说,不过呢,说说其余问题
1,程序文件呢通常都用BIN文件,其实就是hex文件只保留数据部分,其它的不要....
由于是咱本身规定的写到flash的哪一个位置....要地址就没有用了
咱编写单片机的软件都支持生成bin文件,具体看下面
2,咱的普通的程序文件并非直接写到0x8004000个位置,而后调用跳转就能够运行,其实须要让软件配置一下文件才能够,要想让其运行,必须告诉编写单片机的软件我这个程序是在哪一个位置运行,这样他生成的程序文件才能在在那个位置运行.具体看下面
这个是设置的在0x8006000运行
3,还有一个中断向量表的问题
假设
0x80000004 默认是定时器0的中断函数地址,并且中断的地址往上加
我把程序文件写到了0x8004000了,,若是里面写个定时器0的中断..难道要到0x80000004那里去取中断函数的地址去????
下面的位置是放了咱控制ISP的程序......岂不是有点乱了......
通常程序从哪里开始,那么总体的中断地址就从哪里开始,全部还须要设置一下中断地址,也就是总体偏移一下
其实也就这几个须要了解和注意的地方
不过上面的那个不实用,真正的项目,通常都是设计成两套,而后每次更新的时候切换写入,而后切换运行
转眼间天亮了......
而后就想起了一个朋友QQ的个性签名:年轻人老是要为一些本身认为有意义的事情而废寝忘食,通宵达旦,直至白发方休........
对了这篇文章必定会介绍的很详细,请细嚼慢咽.......嗯,我是这样认为的,,,,,,
上面是昨天写的,应该说是今天写的,,今天发现发现博客又不能复制粘贴图片了!!!!而后就睡了一觉,,麻烦。。。。是否是由于我写的博客有太多的图片而把我屏蔽了。能让人一目了然的就是图片。。。。
说一下本身是如何作的,,,
先说一下实现的功能
IAP程序的功能
再看本身的用户程序--用户程序本身也作了些设置
对了关于我为何拷贝到Flash里面------本身用的单片机的RAM不够用,存不了用户程序,因此本身就定义了一个小点的数组(环形队列),串口一边接收,一边往Flash里面写,环形队列但是帮了大忙了,,,,
把IAP升级程序下进去,之后就直接经过串口发送本身的用户程序就好了...什么都不须要作了,先说一下操做过程吧!最后有本身的源码
IAP程序软件不须要任何配置
波特率太快的话,数据来不及写入Flash,环形队列容易溢出,,太慢的话,程序发送的慢。。。
用户程序软件须要一些配置
8006000告诉编译器个人用户程序打算在这里开始,你帮我设置一下吧,默认是在8000000开始的
0x1A000,就是告诉编译器个人程序空间有这么大。
个人用户程序里面也是设置的6000,这个必定要和程序设置的同样哈
关于这个我后面会说为何这样设置。。。固然也能够百度一下。
其实个人原本是
20000换成十进制就是131072个字节 除以1024 等于128
因为我先把IAP程序下进去了,IAP程序也须要空间来运行,,,我就给了他6000 换成十进制就是24576 除以1024就是24K
个人总共是128K而后去掉IAP暂用的24K就是 128-24 = 104K = 106496个字节 换成16进制就是 1A000
因此我上面写了1A000
对了若是您的板子是大容量的若是您很是明白就本身随意修改把,别忘了修改程序里的那个,,,
若是不是很明白按照上面修改就行,后面会让您明白
这个呢就是让Keil软件帮忙生成bin文件
F:\Keil4&&MDK4.70A\ARM\ARMCC\bin\fromelf.exe --bin -o .\Progect\Progect.bin ..\Progect\output\Progect.axf
F:\Keil4&&MDK4.70A\ARM\ARMCC\bin\fromelf.exe --bin -o 这个是执行的命令,就是生成bin文件,根据本身的安装路径找哈
.\Progect\Progect.bin 就是告诉他把生成的bin文件放在哪一个地方
..\Progect\output\Progect.axf 这个就是本身工程编译的时候产生的.axf文件,根据本身的找到
./当前目录
../上一级目录
../../上上一级目录
关于Bin文件和Hex文件
http://blog.sina.com.cn/s/blog_6b94d5680100lo2h.html
这是个人用户程序的Hex与Bin
我们本身设置好写到哪里了,因此前头的就不须要了,后面的校验也不须要了,,不过呢应该向他那样加上校验,数据对了再写进去!!!!
好生成了bin文件
而后
假设修改了程序了,再升级
您再升级就再升级把!!
再升级
不要总是升级哈!!!玩坏了Flash可就很差玩了
本身用的F103RBT6单片机的RAM只有 5000 也就是20480个字节,,可是本身的程序已经超过了这个字节数
因此本身就不能先定义一个很大的数组而后而后把程序先存在里面了,列如不少都是:
u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));//接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.//把数据固定的存在以0X20001000为起始地址的RAM里面
本身呢就是用的环形队列一边接收,一边写入,,,关于环形队列能够看个人环形队列的文章,,,
http://www.cnblogs.com/yangfengwu/p/6822984.html
就再说一下本身的程序的一些地方
串口接收的
void USART1_IRQHandler(void) //串口1中断服务程序 { u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res =USART_ReceiveData(USART1); //读取接收到的数据 PutData(&Res,1); //把数据存入队列 Usart1RecCnt ++; //数据个数 } }
用的系统定时器中断来检测的串口空闲,判断接没接收到一条完整的数据--方法呢是看到人家的一种方法,感受比本身之前的好,因此直接拿过来用了
关于单片机空闲中断能够看一下本身之前的
http://www.cnblogs.com/yangfengwu/p/6746403.html
/*系统定时器中断*/ void SysTick_Handler(void) { SysTickCnt ++; SysTickCnt1++; SysTickCnt2++; if(SysTickCnt1>=10)//每隔10毫秒检测一次 { SysTickCnt1 = 0; if(Usart1RecCnt)//若是接收到数据了 { if(IdleCnt == Usart1RecCnt)//10ms时间数据没了变化 { Usart1RecCntCopy = Usart1RecCnt;//拷贝数据个数 Usart1RecCnt = 0;//清零数据个数 IdleCnt = 0;//清零 Usart1Flage = 1;//接收到一条数据 // rbDelete(&pRb);测试的时候销毁 // rbCreate(&pRb,ReceBuff,USART_REC_LEN);//建立接收环形队列 } else { IdleCnt = Usart1RecCnt; } } } }
个人IAP的接收的数据往Flash里面写和用户程序的往Flash里面写有一点不一样,其实用户程序的往Flash里面写的程序是后期的改进...
先看个人IAP的
if(rbCanRead(&pRb)>1)//若是环形队列里面的数据个数大于1 { rbRead(&pRb, &ReadDat, 2);//读取两个数据 ReadDat16 = (u16)ReadDat[1]<<8; ReadDat16 = ReadDat16|ReadDat[0]; STMFLASH_Write(addr2,&ReadDat16,1); addr2+=2; }
if(rbCanRead(&pRb)>1)
由于一次性要往Flash里面写16位数据,因此才会判断数据个数大于一个的时候再往里面写.
虽然如今程序没有问题,可是我还在想若是程序的个数是奇数个就完啦!可是好像程序的个数老是偶数个....其实能够在判断接收完成的里面
作一下判断,若是数据还残留一个,那就写进去....
好,那就看一下判断接收完程序
if(Usart1Flage == 1)//数据接收完成 { addr2 = FLASH_APP2_ADDR;//存储数据的地址 Usart1Flage =0;//清零 if(((*(vu32*)(FLASH_APP2_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX. { printf("准备执行APP代码!!\r\n"); UserDataAddr = FLASH_DATA_ADDR;//存储其他的数据地址 ReadDat16 = 0x55;//写入标志告诉IAP程序有可更新的用户程序 STMFLASH_Write(UserDataAddr,&ReadDat16,1); UserDataAddr+=2; printf("写入0x55标志!!\r\n"); ReadDat16 = (u16)((Usart1RecCntCopy>>16)&0xffff);//存储接收到多少数据高位 STMFLASH_Write(UserDataAddr,&ReadDat16,1); UserDataAddr+=2; ReadDat16 = (u16)(Usart1RecCntCopy&0xffff);//存储接收到多少数据低位 STMFLASH_Write(UserDataAddr,&ReadDat16,1); UserDataAddr+=2; Usart1RecCntCopy = 0; printf("开始复位重启!!\r\n"); Reset_MCU(); } else { printf("非FLASH应用程序,没法执行!\r\n"); } // printf("Cnt=%d\r\n",Usart1RecCntCopy); // for(i=0;i<Usart1RecCntCopy/2;i++) // { // STMFLASH_Read(addr1,&ReadDat16,1); // addr1+=2;//偏移2048 16=2*8.因此要乘以2. // if((ReadDat16&0x00ff)<=15) // { // printf("0%x ",ReadDat16&0x00ff); // } // else // { // printf("%x ",ReadDat16&0x00ff); // } // // if((ReadDat16>>8)<=15) // { // printf("0%x ",ReadDat16>>8); // } // else // { // printf("%x ",ReadDat16>>8); // } // } // addr1 = FLASH_APP1_ADDR; // for(i=0;i<40;i++) // { // STMFLASH_Erase(addr1,1024);//擦除FLASH_APP1_ADDR地址以及以上40页 // addr1 +=2048; // } // addr1 = FLASH_APP1_ADDR; }
后边屏蔽的是测试的时候,看一下写入的数据,而后和源数据对比一下,看一下写入的对不对
if(((*(vu32*)(FLASH_APP2_ADDR+4))&0xFF000000)==0x08000000)
这句话
先问一个问题,怎么知道接收过来的是用户程序呢????要是别的数据怎么办???,,必须有一个判断依据才行对吧!!
就必须找到用户程序中永恒不变的变量....
而后呢,我是看别人的程序说,数据的第一个4个字节为栈顶地址,数据的第二个4字节为复位中断向量的入口地址
FLASH_APP2_ADDR+4指针就移动到了IAP升级程序的E9或者说电压电流采集程序的D5上
(*(vu32*)(FLASH_APP2_ADDR+4))而后强制型的转成32位的,而后取出来,就是IAP升级程序的E9 20 00 08
或者说电压电流采集程序的D5 7E 00 08
还有一件事就是STM32是小端模式,,,,所谓小端模式就是低位在低地址,高位在高地址
举个例子
把60000存到STM32的Flash的,60000转换成16进制是EA60 EA是高8位,60是低八位,,存到Flash里面就是60EA这样存的
60存到了低地址,EA存到了高地址,,,,,固然有小端模式就有大端模式,,,大端模式就是低位在高地址,高位在低地址,电脑就是这样...
说到这里就要说一下共用体
typedef union Resolver_I { long Data; char Data_Table[4]; }Resolver_iData; typedef union Resolver_f { float Data; char Data_Table[4]; }Resolver_fData;
Resolver_iData Resolver_7758; //解析7758数据
Resolver_fData Resolver_Usart; //解析串口数据
一个整形数据快速的转换成16进制存到数组里面
Resolver_7758.Data = 60000;
那么Resolver_7758.Data_Table[0] = 0x60;
Resolver_7758.Data_Table[1] = 0xEA;
Resolver_7758.Data_Table[2] = 0x00;
Resolver_7758.Data_Table[3] = 0x00;
一个浮点型的数据转换成16进制存到数组里面--其实也是按照IEEE754规约来计算的
Resolver_Usart.Data = 220.5;
那么Resolver_Usart.Data_Table[0] = 0x00;
Resolver_Usart.Data_Table[1] = 0x80;
Resolver_Usart.Data_Table[2] = 0x5C;
Resolver_Usart.Data_Table[3] = 0x43;
因此说上面的数据取出来就是08 00 20 E9而后&0xFF000000 确定就等于 0x08000000啦
其实这样还有一个缘由是由于32的地址是从08000000开始的,复位中断向量的地址的最高两位确定是08啦!!!!
而后还有个if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法.
这个......难道栈顶地址的最高位就是20......之后等看到相关的资料再说吧
有人这样介绍的--仔细看,细细品味....
http://blog.csdn.net/yx_l128125/article/details/12992773
对了说一下中断向量表
就从个人MSP430的文章中摘抄过来
原文地址
http://www.cnblogs.com/yangfengwu/p/6064129.html
忽然想起来一句话,知识是相通的....
32也有中断向量表,就像上面的430的似的,
只不过呢!32的中断函数的入口地址是能够改变的!!!(F0好像固定,可是看过一篇文章好像也能经过某种方式改改)
咱们写入IAP程序后单片机内部就有了一个中断向量表,这个呢是其内部一开始的固定的,
若是跳转到用户程序,用户程序确定会有本身的中断函数吧!如串口,,定时器,,等等,,,,若是不改变中断向量表的话!!!产生的中断
岂不是跑到了IAP那边去了,IAP那边有本身的中断函数,,,乱了,完全乱了,,,,,因此必须得让中断向量表改变改变,好让本身产生的
中断,执行本身的中断函数......
那就加一句话,或者修改一个地方
再看个人用户程序的一个地方,,感受本身罗嗦了
if(AppFlage == 1)//接收到更新程序 { if(rbCanRead(&pRb)>1) { rbRead(&pRb, &ReadDat, 2);//读取两个数据 ReadDat16 = (u16)ReadDat[1]<<8; ReadDat16 = ReadDat16|ReadDat[0]; STMFLASH_Write(addr2,&ReadDat16,1); addr2+=2; } } else if(AppFlage == 0) { if(rbCanRead(&pRb)==8) { rbRead(&pRb, &TestData, 8);//读取数据 if(TestData[3] == 0x20 && TestData[7] == 0x08)//判断是不是更新程序 { AppFlage = 1;//要更新程序 for(i=0;i<4;i++)//先写入这八位数据 { ReadDat16 = (u16)TestData[(i<<1)+1]<<8; ReadDat16 = ReadDat16|TestData[i<<1]; STMFLASH_Write(addr2,&ReadDat16,1); addr2+=2; } } } }
毕竟是用户程序,串口1可能要参与别的通讯,,,因此本身加了一个判断是不是要更新程序的数据,,,是的话才往Flash里面写
本身的源码
连接:http://pan.baidu.com/s/1bJtc78 密码:nobu
这两天发现了本身程序的Bug
1,若是用户程序主函数加入延时,那么程序就来不及读出而后写到Flash里面,串口却不停的往环形队列里面写,从而形成环形队列溢出....
再者若是写入的时候,设置的串口助手的波特率太快,,,,一样也会形成环形队列溢出(就是往环形队列写的太快了)....
本身把写Flash的程序放在了定时器里面,50Us进入一次的定时器,看着网上说往Flash写一个字节大约16Us,,,,加上其他的程序总体应该不会超过50Us
若是有溢出程序不在往环形队列里面写了
void USART1_IRQHandler(void) //串口1中断服务程序 { u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res =USART_ReceiveData(USART1); //读取接收到的数据 if(Overflow==0) { if(PutData(&Res,1) == -1) { Overflow = 1; } } Usart1RecCnt ++; } }
最后接收完若是判断溢出过,直接复位重启,复位重启更方便直接...
if(Usart1Flage == 1)//串口1接收完成 { addr2 = FLASH_APP2_ADDR; Usart1Flage =0; if(Overflow==1)//若是中途溢出了 { printf("程序中途溢出,准备复位重启!!\r\n"); Reset_MCU();//复位重启CPU }
2,若是写了一些后,忽然由于某些缘由中止了写入,,,,,本想在程序中观察末尾有什么固定的数据没有,,,,或者本身最后加一些标志位
可是这个如今程序好像能判断出来....可是本身一直没有明白程序为何能够判断出来............应该判断不出来的......
后来一想如今反正是本身去更新程序,真不行能够直接烧,,,,就先放一放,,,,
更改后的
连接:http://pan.baidu.com/s/1slnWFVJ 密码:mts7