STM32的stm32f4 软件复位函数数在哪个库文件里

查看: 2113|回复: 0
关于STM32的固件库
为什么会有固件库
对于51核的8位单片机我们在进行开发时,我们是直接进行配置单片机的寄存器,控制芯片的工作方式。只要我们明白寄存器各位的功能和设置方法,直接进行赋值就可以了。不存在固件库的问题。那么使用ARM 32位 CORTEX -M3核心的STM32芯片为什么有固件库呢?当然在使用STM32时也可以像51那样脱离固件库,自己进行配置开发。对于SMT32因为其外设资源非常的丰富,必然带来寄存器的数量十分庞大并且比较复杂,这时直接配置寄存器方式的就会带来:开发速度慢程序可读性差这两个缺陷会直接影响开发效率,程序的维护和交流。而库开发方式能很好的弥补这两个缺陷。两种思路的对比:相对于库开发的方式,直接配置寄存器的方式生成的代码量的确会少一点,但是SMT32有充足的资源,权衡库的优势与不足,绝大部分时候,我们愿意牺牲一点资料,选择库开发。一般只在对代码运行时间要求极其苛刻的地方,才使用直接配置寄存器的方式替代。如频繁调用中断服务程序。STM32结构及库层次关系基于CORTEX的某系列芯片采用的内核都是相同的,区别是核外的片上外设的差异,这些差异导致软件在同内核,不同外设的芯片上移植的困难。为解决不同芯片厂商生产的CORTEX微控制器软件的兼容性问题,ARM与芯片厂商建立了CMSIS标准。即Cortex MicroController software Interface Standard ;CORTEX 微控制器软件接口标准CMSIS包括了:内核函数层:其中包含用于访问内核寄存器的名称、地址定义,主要由ARM公司提供。设备外设访问层:提供了片上的核外外设的地址和中断定义,主要由芯片生产商提供。CMSIS层位于硬件层与操作系统或用户层之间,提供了芯片生产商无关的硬件抽象层,可以为借口外设、实时操作系统提供简单的处理器软件接口,屏蔽了硬件差异,这对软件的移植是有极大的好处的。STM32的库,就是按照CMSIS标准建立的。 固件库主要文件说明:
-htmresc: LOGOLibraries :& &驱动库的源代码及启动文件Project :用驱动库写的例子个一个工程模板Utilities:基于STM官方开发板的例子Release_Notes.html:库版本更新说明stm32f10x_stdperiph_lib_um.chm:库帮助文件 Core_cm3.c和core_cm3.h作用:为采用CORTEX—M3核的芯片商设计的芯片外设提供一个进入M3内核的接口。& &&&只要是采用了M3的核,不同公司的芯片的这两个文件是相同。这两个文件在我们的工程文件是不可缺少的。System_stm32f10x.c文件是由ST公司提供的。功能是设置系统时钟和总线时钟。M3比51单片机复杂的多,它不像51直接接一个8M的晶振就可以工作了,我们还要通过M3核的核内寄存器对8M的时钟进行倍频、分频,或者使用芯片内部的时钟。所有的外设都与时钟的频率相关,所以这个文件的时钟配置是很关键的。在实现系统时钟的时候要用到PLL(锁相环),这就需要操作寄存器,寄存器都是以存储器映射的方式来进行访问的,所以该文件中包含了stm32f10x.h这个头文件。Stm32f10x.h文件是一个很重要很底层的文件所有的处理器厂商都会将对内存的操作封装成一个宏,即我们通常说的寄存器,并且把这些实现封装成一个系统文件,包含在相应的开发环境中。这样在开发自己的应用程序时只要将这个文件包含进来就可以了。启动文件Libraries/CMSIS/core/CM3/startup/arm文件夹下由汇编编写的系统启动文件,不同的文件对应不同的芯片型号。 文件名的英文缩写的意义如下:Cl:互联型产片,STM32F105/107系列Vl: 超值型产品,STM32F100系列Xl: 超高容量产品,STM32F101/103系列Ld: 低密度产品,FLASH小于64KMd: 中密度产品,FLASH=64 or 128KHd: 高密度产品,FLASH大于128K 启动文件是任何处理器在上电复位后最先运行的一段汇编程序。在我们编写的C代码开始运行之前,需要由汇编语言为C语言的运行建立一个合适的环境,接下来才能运行我们的程序。所以要把启动文件添加进我们的工程中去。 启动文件的作用:1、初始化堆栈指针SP;2、初始化程序计数器指针PC;3、设置堆、栈的大小4、设置异常向量表的入口地址5、配置外部SRAM作为数据存储器6、设置C库的分支入口___main7、在3.5版本的启动文件中还调用了在system_stm32f10x.c文件中的SystemIni()函数配置系统时钟,在旧的版本中需要用户进入main函数自己调用这个函数。 Inc和src位于libraries/STM32F10X_StdPeriph_Driver文件夹下,inc即include的缩写,src即source的缩写。属于CMSIS的设备外设函数部分。这些外设是芯片制造商在M3核外加进去的。在src和inc文件夹中是ST公司针对每个STM32外设编写的库函数文件,每个外设对应一个.c和.h文件。在进行开发时,可以用到那个模块就将那两个文件包含到工程中。还有一个很特别的misc.c文件,这个文件提供了外设对内核的NVIC(中断向量控制器)的访问函数 ,在配置中断时,必须把这个文件添加到工程中。 Stm32f10x_it.c 和 stm32f10x_conf.h文件Stm32f10x_it.c ,专门用来编写中断服务函数的,在我们修改前,这个文件已经定义了一些系统异常的接口,其他的普通中断服务函数由我们自己添加。这些中断函数的入口不是我们随意可以定的,可以去汇编启动文件中找到。stm32f10x_conf.h,这个文件被包含进stm32f10x.h文件。用来配置使用了什么外设的头文件,用这个头文件可以很方便的增加或删除外设驱动函数库。库文件之间的关系库文件直接包含进工程即可,有些文件则需要我们根据使用到的功能进行配置。各个文件在库工程中的层次或关系 stm32f10x_stdperiph_lib_um.chm:库帮助文件库函数就是STM32的库文件中为我们编写好的函数接口,我们只要调用这些库函数,就能对STM32进行配置,达到控制的目的。调用函数式必须知道:函数的功能、可传入的参数及其意义和函数的返回值。库函数提供的函数的数量也是极其庞大的,这时就需要好好的利用这个库帮助文件,进行查找。同时由于每个函数和数据类型都是按照“见名知义”的原则编写的,这样名称比较长,在进行输入时很容易出错,也可以在开发软件时,在用到库函数的时候,直接把帮助文件中的函数复制到工程文件中,避免出错。 以上内容节选至《野火》,感谢分享。 看了STM32的库才发现,原来嵌入式C语言程序应该这样来编写,可以如此的简洁流畅与优美。对一个系统架构的构思,对每个细节表达,对指针,变量,函数,纯熟的应用。对库的编写者深深折服,看到了大师的风范。相见恨晚。
Powered bySTM32IAP升级-----编写IAP升级遇到的问题总结
IAP的源码等资料我上传了,压缩包内有12个文件,,(要积分的辛苦收集的你们就给点积分吧)
还有另一篇博客总结的IAP:
一,串口通信问题
1,串口通信两端的TTL电平要一致,看选用的芯片,要么都是3.3V要么都是5V。。当两端电平不一致时通常是收不到数据的。。当检测程序等都没有问题但是依然收不到数据时,考虑检测一下两端的电平是否一致。。可用示波器查看收发数据时的电平
2,在写IAP是使用的官方的库函数,结果串口一直收不到正确的数据。不管我发什么,收到的都是0x58或者0xF6,而且我发5个字节只能收到2个。。确定自己的程序逻辑没问题,然后怀疑是使用的库函数有问题。使用beyond compare挨着对照了一下以前写过的正确的程序。。发现系统初始化时外部晶振没有改。。在SystemInit();时就是为了/* 配置系统时钟为168M 使用外部8M晶体+PLL*/ 在函数内有一个宏定义#define PLL_M & & &25.。。。库函数默认的是25但是我们必须要使用8M的晶振。。所以要把25改成8.。。。。。。改完测试通过。。。。。
3,在问题2的时候自己犯了个错误。当时我把另一个程序的库文件都拷过来了替换了原来的库文件,结果直接进不了中断了。有中断程序就死了。。。如图所示
有中断时汇编显示就跳到了黄色的那句话。然后程序就死了。。其实进不了中断首先想到的是中断向量表的问题。当时一着急没想到这个问题。。复制过来的库文件是我写的IAP的APP程序,我把其中的中断向量表改了,我不是在main函数中改的,我是直接改的库函数中的偏移量,然后就把这个问题给忘了,导致找了半天终于意识到是中断向量表出了问题。。。
4,串口一上电未初始化时就开始疯狂的发乱码,等初始化完成之后就不发了。如图
解决方法:我是看的官方的例程,如果需要更新再初始化串口,如果不需要更新的话就不初始化串口。所以把串口初始化部分写在了后面的判断中。。。这样操作会出现上述问题。把串口的初始化部分写到main函数的前端就不会出现这个问题了。。至于具体为什么会这样的原因还没搞明白。。。
经过询问大神们说不初始化的时候会有浮空电平,有时出现乱发一些数据也正常。。。。
5,我在写iap升级的时候,使用的是一次性全部接收完70K的bin文件,再去升级。。后期程序越写越多,最后编译完的bin文件大于了70K导致升级不能成功。前面的$HEAD命令能接收,发完bin文件后$TAIL命令接收不到。。。。。这是由于bin文件的大小超过了70k大小的buff后面的内容无法接收了。。。。。。。。。。。。。正规的升级应该接收10k或者多少就编程一次。。。。。。。。
二,FLASH问题
1,flash的擦除
实际上是把flash的内容全部写1(擦除完再读的话读出来的全是0XFF),flash的编程要按字(32位)或半字(16位)编程,当接收的数据是奇数位是要补上0,凑够半字来编程。
2,写进flash跟读出来的不一样
注意编程flash时变量的存储接收等要用unsigned类型的。存储接收的变量类型要一致。以免造成越界问题,导致看到的数不一致
3,flash做存储用时
当时写了个程序,用flash来存数据,然后再读出来,遇到的问题是设备不断电时可以完整的读出来,设备断电后读不出了。要读多少个数我是把个数也存储在flash中的。最终发现问题是程序中一个标志位的操作有问题,每次断电后再上电会自动把这个个数写为0,导致每次都读不出来数
三,数组越界问题
1,嵌入式程序通常要求少占内存,通常变量能定义8位不定义16位。。一定要注意变量的最大值。(细心一点)。。在越界问题上,吃亏了好几次了。。。。
eg:①,我定义了一个int16_t的变量来接收flash中的一个变量值0xABCD。。结果很显然越界了。。换成uint16_t即可
②,定义了一个串口接收数据计数器uint16_t 的变量,要接收60k的APP程序,自认为足够用了,但是串口发送60K大小的文件发送的字节数大于了65535,,,又耽误了好久时间。。。
谨记:要细心
四,关于APP与IAP互跳之间的中断处理问题
跳转时中断问题还是一个比较棘手的问题。。经常跳转之后无法进入中断,然后百度了一下,自己理解大概是,跳转时只是强制改变了PC指正的位置,但是里面的中断寄存器什么的都没有变,这样中断存在,但是中断函数什么的都没有了,造成程序死掉。。我在写的过程中也遇到了问题,第一次从iap跳到app正常,但是从app跳回iap的时候由于残留的中断太多,在iap中程序死了。我的处理方式是把app中的跳转命令换成了系统复位NVIC_SystemReset();(不同的固件库可能函数名不同)其他的处理理的方式据我所知还有有①跳转之前复位或者关闭所有打开的中断②跳转后在初始化时加入
五,总结一下在IAP升级中APP程序的中断向量表的偏移
1.&&&&&&关于APP程序的中断向量表地址偏移(三种方法,stm32F2与F4系列通用。三种方法本质一样只是看到网上的各种例程的表现形式不一样)
①&直接操作寄存器
在APP程序的main函数的开头设置中断向量表偏移
SCB-&VTOR = FLASH_BASE | 0x10000;
其中0x10000是偏移量。。也就是前面的IAP程序所占用的空间大小,要是你的main函数中有SystemInit();的话要在SystemInit();之后添加。
因为SystemInit();中有中断向量表的偏移操作
在void SystemInit (void)系统初始化函数中有初始化中断向量表的语句
#ifdef VECT_TAB_SRAM
&SCB-&VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 使用内部SRAM启动设置这一句. */
&SCB-&VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 使用内部FLASH启动设置这句*/
&可以直接修改VECT_TAB_OFFSE的值,这个值代表偏移量。不建议这么改,不建议修改库文件,应为后面其他程序用的话经常忘了这里动过中断向量表,导致中断不能正常运行(我就因为这个浪费了快一天时间,串口就是进不了中断)
#define FLASH_BASE&&&&&&&&&&& ((uint32_t)0x) /*!&FLASH base address in the alias region */
#define SRAM_BASE&&&&&&&&& &&&((uint32_t)0x) /*!& SRAM baseaddress in the alias region */
对应keil设置中的(这是一般程序默认的,IAP升级中APP程序的这个地方还得根据中断偏移量改)
② 使用库函数设置偏移量
在库文件中有专门的一个函数
在APP程序初始化时调用函数NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x10000);
其中/* Vector Table Base----------------------------------*/
#define NVIC_VectTab_RAM&&&&&& &&&&&&((u32)0x)
#define NVIC_VectTab_FLASH&&&&&&&&&& ((u32)0x)
/***********************************************************************
Function Name& : NVIC_SetVectorTable
* Description&&& : Sets the vector table location andOffset.
* Input&&&&&&&&& : - NVIC_VectTab: specifies if thevector table is in RAM or
*&&&&&&&&&&&&&&&&&&& FLASH memory.
**********************************************************************/
void NVIC_SetVectorTable&(u32NVIC_VectTab, u32 Offset)
&/* Check the parameters */
&assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
&assert_param(IS_NVIC_OFFSET(Offset));&
&SCB-&VTOR = NVIC_VectTab | (Offset & (u32)0x1FFFFF80);
③修改库文件(不建议使用)
直接修改固件库里面的数值。在void SystemInit(void)下的
& /* Configure the Vector Table location add offsetaddress ------------------*/
#ifdefVECT_TAB_SRAM
& SCB-&VTOR = SRAM_BASE | VECT_TAB_OFFSET;/* Internal SRAM */
& SCB-&VTOR = FLASH_BASE | VECT_TAB_OFFSET;/* Internal FLASH */
#define VECT_TAB_OFFSET& 0x00 /*!& Vector Table base offset field.
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& This valuemust be a multiple of 0x200. */
#define VECT_TAB_OFFSET& 0x10000 /*!& Vector Table base offsetfield.
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& This valuemust be a multiple of 0x200. */
2.关于IAP程序与APP程序keil中的设置
Stm32的flash都是从0x8000000开始的,结束地址看片子的flash大小
Stm32的sram都是从0x2000000开始的,结束地址看片子的sram大小
IAP程序基本默认就行,跟普通程序一样
在APP程序中需要设置一下偏移量
默认的条件下,图中IROM1的起始地址(Start)一般为0X,大小(Size)为0X100000,即从0X开始的1M空间为我们的程序存储(因为我们的STM32F4的FLASH大小是1M)。而图中,我们设置起始地址(Start)为0X,即偏移量为0X10000(64K字节),因而,留给APP用的FLASH空间(Size)只有0XX00(960K字节)大小了。设置好Start和Szie,就完成APP程序的起始地址设置。
&&&&&&&这里的64K字节,需要大家根据Bootloader程序大小进行选择,比如我们本章的Bootloader程序为22K左右,理论上我们只需要确保APP起始地址在Bootloader之后,并且偏移量为0X200的倍数即可(相关知识,请参考:)。这里我们选择64K(0X10000)字节,留了一些余量,方便Bootloader以后的升级修改。
注意:设置的起始地址要与程序中设置的中断向量表的偏移量对应起来(如果给IAP程序64k的空间则APPkeil中起始地址为0x8010000相应的程序中中断向量偏移0x10000)我用的1M &flash大小的片子。。具体的大小设置,看自己的片子。。
本分类共有文章51篇,更多信息详见
& 2012 - 2016 &
&All Rights Reserved. &
/*爱悠闲图+*/
var cpro_id = "u1888441";您当前的位置:&>&&>&
STM32软件复位(基于库文件V3.5)
&STM32软件复位(基于库文件V3.5)
void SoftReset(void)
__set_FAULTMASK(1); // 关闭所有中端
NVIC_SystemReset();// 复位
在官方软件库的 core_cm3.h 文件里 直接提供了 系统复位的函数
static __INLINE void NVIC_SystemReset(void)
SCB-&AIRCR = ((0x5FA && SCB_AIRCR_VECTKEY_Pos) |
(SCB-&AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
SCB_AIRCR_SYSRESETREQ_Msk); /* Keep priority group unchanged */
__DSB(); /* Ensure completion of memory access */
while(1); /* wait until reset */
但是不是直接调用这个函数就OK了?
在Cortex-M3权威指南中有这么一句话
这里有一个要注意的问题:从SYSRESETREQ 被置为有效,到复位发生器执行复位命令,
往往会有一个延时。在此延时期间,处理器仍然可以响应中断请求。但我们的本意往往是要
让此次执行到此为止,不要再做任何其它事情了。所以,最好在发出复位请求前,先把
FAULTMASK 置位。
所以最好在将FAULTMASK 置位才万无一失。
同样官方 core_cm3.h 文件里也直接提供了该函数
static __INLINE void __set_FAULTMASK(uint32_t faultMask)
register uint32_t __regFaultMask __ASM(&faultmask&);
__regFaultMask = (faultMask & 1);
经过反复的比较和遴选,《今日电子》和21ic中国电子网举办的2013年度产品奖正式揭晓…
() () () () () () () () ()STM32软件复位(基于库文件V3.5)
> STM32软件复位(基于库文件V3.5)
STM32软件复位(基于库文件V3.5)
(库)本文引用地址:void SoftReset(void){__set_FAULTMASK(1); // 关闭所有中端NVIC_SystemReset();// }在官方库的 core_cm3.h 里 直接提供了 系统的函数static __INLINE void NVIC_SystemReset(void){SCB-&AIRCR = ((0x5FA
SCB_AIRCR_VECTKEY_Pos) |(SCB-&AIRCR
SCB_AIRCR_PRIGROUP_Msk) |SCB_AIRCR_SYSRESETREQ_Msk); /* Keep priority group unchanged */__DSB(); /* Ensure completion of memory access */while(1); /* wait until reset */}但是不是直接调用这个函数就OK了?在Cortex-M3权威指南中有这么一句话这里有一个要注意的问题:从SYSRESETREQ 被置为有效,到复位发生器执行复位命令,往往会有一个延时。在此延时期间,处理器仍然可以响应中断请求。但我们的本意往往是要让此次执行到此为止,不要再做任何其它事情了。所以,最好在发出复位请求前,先把FAULTMASK 置位。所以最好在将FAULTMASK 置位才万无一失。同样官方 core_cm3.h 里也直接提供了该函数static __INLINE void __set_FAULTMASK(uint32_t faultMask){register uint32_t __regFaultMask __ASM(faultmask);__regFaultMask = (faultMask
分享给小伙伴们:
我来说两句……
最新技术贴
微信公众号二
微信公众号一教你如何找到导致程序跑飞的指令 - STM32 - 意法半导体STM32/STM8技术社区
后使用快捷导航没有帐号?
查看: 11719|回复: 0
教你如何找到导致程序跑飞的指令
在线时间1 小时
主题帖子精华
新手上路, 积分 31, 距离下一级还需 19 积分
新手上路, 积分 31, 距离下一级还需 19 积分
教你如何找到导致程序跑飞的指令
更多资料请访问我的博客.cn/ifreecoding
调试嵌入式程序时,你是否遇到过程序跑飞最终导致硬件异常中断的问题?遇到这种问题是否感觉比较难定位?不知道问题出在哪里,没有办法跟踪?尤其是当别人的程序踩了自己的内存,那就只能哭了
今天在论坛上看有同学求助这种问题,正好我还算有一点办法,就和大家分享一下。
解决办法非常非常简单,本文将以Aduc7026(ARM7内核)和LM3S8962(cortex内核,STM32也是cortex内核,同理)为例,讲讲解如何定位此种问题。
先说ARM7内核,cortex内核稍微有一点复杂,后面再说。
ARM7内核有多种工作模式,每种模式下有R0~R15以及CPSR共17个寄存器可以使用,有关这些寄存器的细节我就不详细介绍了,详细的介绍请参考“底层工作者手册之嵌入式操作系统内核”中的2.2~2.3节,这里只介绍与本文相关的寄存器。
其中R14又叫做LR寄存器,它被用来保存函数、中断调用时的返回地址,看到了吧,它保存了“返回地址”!这不就是我们需要的么?就这么简单,发生异常中断时,LR寄存器中保存的地址附近就会有导致异常的指令。
接下来我们再先了解一下相关的知识,然后再通过一个例子构造一个指令异常,然后再反推找到产生异常的这条指令,做一个实例演练!
当程序跑飞时,绝大部分情况都会触发硬件异常中断,硬件异常中断的中断服务函数在中断向量表中有定义,我们来看看ARM7的中断向量表,在keil开发环境里(以下例子是在keil环境下介绍的),这个文件一般叫startup.s,如下:
Vectors:& && &&&LDR& &&&PC, Reset_Addr
& && && && && & LDR& &&&PC, Undef_Addr
& && && && && & LDR& &&&PC, SWI_Addr
& && && && && & LDR& &&&PC, PAbt_Addr
& && && && && & LDR& &&&PC, DAbt_Addr
& && && && && & NOP& && && && && && && && && & /* Reserved Vector */
& && && && && & LDR& &&&PC, IRQ_Addr
& && && && && & LDR& &&&PC, FIQ_Addr
Reset_Addr:& &&&.word& &Reset_Handler
Undef_Addr:& &&&.word& &ADI_UNDEF_Interrupt_Setup
SWI_Addr:& && & .word& &ADI_SWI_Interrupt_Setup
PAbt_Addr:& && &.word& &ADI_PABORT_Interrupt_Setup
DAbt_Addr:& && &.word& &ADI_DABORT_Interrupt_Setup
IRQ_Addr:& && & .word& &ADI_IRQ_Interrupt_Setup
FIQ_Addr:& && & .word& &ADI_FIQ_Interrupt_Setup
ARM7的中断向量表比较简单,只有7种中断,它把所有正常的中断都放到了SWI、IRQ和FIQ中了,那么本文所介绍的异常情况将会触发Undef、PAbt或者DAbt异常中断,至于是哪种就需要看具体的原因了。
指令A& & //触发异常
比如说当指令A无法执行时,它就会触发异常中断,硬件就会自动将这条指令后面的指令的所在地址,也就是指令B的地址保存到LR寄存器中,然后就跳转到与这种异常相关的中断向量表中,假如指令A触发了Undef异常中断,那么硬件就会跳转到中断向量表的第二个中断向量Undef_Addr,从中断向量表可知,这个中断向量对应的中断服务函数就是ADI_UNDEF_Interrupt_Setup,这个函数一般是一个死循环,这样单板就死了,当我们停下程序时,就会发现程序停在了这个函数里面。
我们来看下面这个实例,我把定位过程的每一步都记录下来,一起来看下:
14&&S32 main(void)
16& && &U8* pucA
18& && &/* 初始化硬件 */
19& && &DEV_HardwareInit();
21& && &/* 创建任务 */
22& && &WLX_TaskInit(1, TEST_TestTask1, TEST_GetTaskInitSp(1));
23& && &WLX_TaskInit(2, TEST_TestTask2, TEST_GetTaskInitSp(2));
25& && &/**********此指令会触发异常中断**********/
26& && &pucAddr = (U8*)0;
27& && &*pucAddr = 0;
28& && &/****************************************/
31& && &/* 开始任务调度 */
32& && &WLX_TaskStart();
34& && &return 0;
上面这段测试代码是我在我写的一个小型嵌入式操作系统上改的(有兴趣的话可以访问我的博客O(∩_∩)O),只需要关注26和27行即可,其余的只是陪衬,以使这段程序看起来稍微复杂一些。这两行指令将0地址清0,0地址是中断向量表,向这个地址写数据会导致异常的,但——这正是我们所需要的。
然后,为了方便,我们在中断向量表里把上面的3个异常中断向量都修改一下,如下:
Vectors:& && &&&LDR& &&&PC, Reset_Addr
& && && && && & LDR& &&&PC, FaultIsr
& && && && && & LDR& &&&PC, SWI_Addr
& && && && && & LDR& &&&PC, FaultIsr
& && && && && & LDR& &&&PC, FaultIsr
& && && && && & NOP& && && && && && && && && & /* Reserved Vector */
& && && && && & LDR& &&&PC, IRQ_Addr
& && && && && & LDR& &&&PC, FIQ_Addr
这样,只要发生异常中断就都会进入FaultIsr函数,FaultIsr函数如下:
void FaultIsr()
& & while(1)
可以看到FaultIsr函数是个死循环,所以当程序发生异常跑飞时就会死在这里了。
准备工作完成,准备实战演练!在这之前还有一点需要注意,那就是最好将编译选项设置为不优化,这样方便我们定位问题。当然,实际情况也许不允许我们这么做,这样的话就需要你有比较高的汇编语言水平了,这不在本文讨论之内,先不管了。我们在这个例子里将编译选项设置为不优化。
我们将上面改动后的代码重新编译,然后加载到单板里,进入仿真状态,然后全速运行,然后再停止运行,我们就可以发现程序死在FaultIsr函数里了,如下图所示:
从图1可以看到程序停在了42行,这与我们的设计是一致的。在图1的左侧显示了此时各个寄存器内的数值,注意到LR寄存器了吧,这里保存的就是返回地址,出错的指令就在这附近。但,还有一点需要注意,FaultIsr函数是C语言函数,它运行时可能会修改LR寄存器,如果是这样的话,那么此时LR寄存器内的数值就不是发生异常时的值了,为解决此问题,我们可以找到FaultIsr函数的起始地址,将断点打在FaultIsr函数的起始地址,这样当异常发生时就会停在断点的地方,也就是FaultIsr函数的起始地址,这样就可以保证LR寄存器的值就是发生异常时的值了。
如果你的汇编语言足够好,那么你可以在图1右上角的汇编窗口里向上找,找到FaultIsr函数的起始地址。另外,我们还可以通过一个简单的方法找到FaultIsr函数的起始地址。我们在keil的选项中选择生成map文件,代码编译后就会生成一个map文件,我们可以从这个文件里找到FaultIsr函数的地址。
使用一个文本编辑器打开这个map文件,然后搜索“FaultIsr”,如下图,我们就找到了FaultIsr函数的起始地址:0x80608。
在汇编窗口找到0x80608的地址,打上断点,如下图所示:
复位程序,再重新全速跑一遍,我们就会发现程序停在了断点上,这时LR里面的数值就是程序异常时存入的返回地址,通过这个地址差不多就可以找到出错的指令了。
如图3所示,LR的值为0x805ec,我们在汇编窗口里跳到这个地址,如下图所示:
ARM7内核有2级流水线,存入LR的地址一般会多+8个字节,因此0x805ec-8=0x805e4,如图4所示,0x805e4地址是一条STRB R2,[R3]指令,这条指令的意思是将R2寄存器里的数值保存到R3寄存器所指向的地址(一个字节)内。从图3左侧可以看到R2寄存器的数值为0,R3寄存器的数值也为0,那么这条指令的意思就是将0这个数值写入0地址这个字节内,这不是正好对应上述main函数中27行的C指令么?
看到这里我们就应该明白了,向0地址写0,这条C指令有问题,那么这个跑飞的问题也就找到原因了,是不是很简单?
当然,实际情况可能要比上述介绍的情况复杂的多。实际使用的程序几乎都是经过优化的,这样从汇编指令找到C指令就会比较麻烦。还有可能FaultIsr函数的指令或者堆栈被破坏了,那么FaultIsr函数运行都会出问题。还有可能出错的指令不会象27行这么明显,可能是经过了前面很多步骤的积累才在这里触发异常的,最典型的就是别人的程序踩了你的内存,结果错误在你的程序里表现出来了,如果遇到这种情况你就先哭一顿吧。对于这种踩内存的情况也是可以通过这种方法定位的,但这相当复杂,需要从出错点开始到触发异常点为止,这之间所有的堆栈信息,然后从最后的堆栈开始,结合反汇编的代码,从最后一条指令向前推,直到发现问题的根源。这种方法相当于是我们用我们的大脑模拟CPU的反向运行过程,如果程序是经过优化的,那么这个过程就更麻烦了。我准备在“底层工作者手册之嵌入式操作系统内核”6.1节实例讲解一个这种情况(现在是,手册暂时只写到了5.4节)。
好了,先不说这么复杂的了,接着上面的继续说。
有时候出现问题的单板并不在我们手边,问题也许不能复现,那么我们就可以预先在FaultIsr函数里做一个打印功能——将出现异常时的寄存器、堆栈、软件版本号等信息打印出来,编写这样的FaultIsr函数需要注意,FaultIsr函数开始的代码一定要用汇编语言来写,以防止调用FaultIsr函数时的寄存器、堆栈信息被C语言破坏。
如果我们的单板有这样的功能,那么当单板跑死时,一般情况都会向外打印信息,比如上面的例子,就会打印出LR的值为0x805ec。但我们似乎又遇到了一个问题,我们如何知道0x805ec这个地址是哪个函数的?别忘了,我们在一个版本发布时会将软件所有的信息归档(什么?没归档!这样的公司我劝你还是走了吧),根据软件版本号找到出问题的软件的归档文件,取出map文件,利用上面讲述的方法通过map文件我们就可以找到出问题的函数了。再通过软件版本从归档文件中找到这个函数最终编译链接生成的目标文件,一般为.o、.axf、.elf等文件(必须是静态链接的文件,需要有各种段信息的),不能是bin、hex等文件,windows、linux等动态链接的文件已经超出了我目前的知识范围,也不再其中。
然后使用objdump程序进行反汇编,将目标文件与objdump程序放到同一个目录,在cmd窗口下进到这个目录,执行下面命令:
objdump -d wanlix.elf && uncode.txt
这行命令的意思是将wanlix.elf目标程序进行反汇编,反汇编的结果以文本格式存入uncode.txt文本文件。
我们用文本编辑器打开uncode.txt文件,找到0x805ec地址,如下图所示:
如图5所示,我们可以看到0x805ec这个地址位于main函数内,我们再对比一下图5和图4中的指令,可以发现它们是相同的,可能写法上会有一些差异,但功能是相同的。
好了,ARM7内核的介绍到此结束,下面介绍cortex内核的,使用ST的STM32、TI的LM3S系列的同学们注意了,它们都是cortex内核的,下面的介绍你也许用得上。
Cortex内核与ARM7内核定位此种问题的思路完全是一样的,cortex内核的详细介绍请参考“底层工作者手册之嵌入式操作系统内核”中的5.1节。cortex内核有一些特殊,它在产生中断时会先将R0~R3、R12、LR、PC以及XPSR这8个寄存器压入当前的堆栈,然后才跳转到中断向量表执行中断服务程序,此时LR中保存的不是返回地址,而是返回时所使用的芯片模式和堆栈寄存器的标示,只能是0xFFFFFFF1、0xFFFFFFF9或者是0xFFFFFFFD这3个值中的一个,如果你还认为LR中保存的是返回地址,并且是这么奇特的地址,估计你一定会晕了。
要找cortex内核芯片的返回地址就需要到栈中去找,前面不是说了么,进入中断前硬件会自动向当前栈压入8个寄存器,如下图所示:
http://www.stmcu.org/file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ksohtml/wps_clip_image-4754.pnghttp://www.stmcu.org/file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ksohtml/wps_clip_image-24291.png
如果你看了2.3节和5.1节就应该知道cortex和ARM7内核都是一种递减满栈,意思是说压栈时栈指针向低地址移动,栈指针指向最后压入的数据。SP(R13)寄存器就是栈寄存器,它里面保存的就是当前的栈指针,因此当cortex内核发生中断时,我们就可以根据SP指针来找到压入上述8个寄存器的地址,然后找到LR的位置,再从LR中找到返回地址,下面的这个例子是“底层工作者手册之嵌入式操作系统内核”中的6.1节的一个例子,
void TEST_TestTask1(void)
& & while(1)
& && &&&DEV_PutStrToMem((U8*)&\r\nTask1 is running! Tick is: %d&,
& && && && && && && && &MDS_SystemTickGet());
& && &&&DEV_DelayMs(1000);
& && &&&MDS_TaskDelay(250);
& && &&&if(MDS_SystemTickGet() &= 2000)
& && && && &ADDRVAL(0xFFFFFFFF) = 0;
红色字体部分会触发一个异常,它会向0xFFFFFFFF这个地址写入0,也会触发一个异常中断,触发的异常会进入MDS_FaultIsrContext异常中断服务函数,在MDS_FaultIsrContext函数的入口地址打上断点,运行此程序,触发异常后如下图:
从图7左上侧窗口可以看到SP的值为0x,那么我们在右下角的窗口找到0x这块内存的地址,从0x开始,每4个字节对应一个寄存器,依次为R0、R1、R2、R3、R12、LR、PC、XPSR,其中红框的位置就对应着LR,从图中可以看到LR的值为0x1669,我们找到这个版本编译后的目标文件,使用objdump软件反汇编,如下图所示:
可以看到0x1669这个地址位于TEST_TestTask1函数里,与我们设计的一致。
这段代码是经过O2优化的,汇编指令对照到C指令上会有些费事,这里就不再讲解了,知道方法就好,剩下的自己研究。
这里面有2点说明一下,一是cortex内核支持双堆栈,如果使用双堆栈的话会复杂一点,这里为了简单的说明问题,我们只使用了其中的一个MSP,另外一个PSP没有使用,在这个例子里你只需要认为只有一个SP就可以了。另外一点是0x1669这个地址其实就是0x1668,因为cortex内核采用的是Thumb2指令集,该指令集要求指令的最后一个bit为1,因此0x1668就变成了0x1669。
上面介绍ARM7内核的时候我不是说过如果在FaultIsr函数里做一个打印功能就可以通过打印信息来定位这种问题么,其实在介绍cortex内核的这个例子中我就做了这个功能,具体的实现就先不介绍了,有兴趣的同学可以看我6.1节的介绍(,目前book还没写到6.1节),下面是出现异常时打印的一小段信息,从这段信息里我们可以看到SP(R13)的数值为0x,与图7的情况一样,那么在栈中从0x这个地址向上找,找到栈中保存LR的位置,它的数值就是0x1669,与图7中的分析是一致的。
注意一点蓝色字体的R14是我这段打印程序还原过的,因此它与内存中的数值是一样的。
R15 = 0x = 0x = 0x = 0x
R11 = 0x = 0x&&= 0x&&= 0x
R7&&= 0x&&= 0x R5&&= 0x R4&&= 0x
R3&&= 0x0000008C R2&&= 0x&&= 0xE000ED04 R0&&= 0x
0x2000126C: 0x
0xx0000008C
0x2000125C: 0xE000ED04
更多信息,欢迎访问我的博客.cn/ifreecoding
18:39 上传
点击文件名下载附件
276.96 KB, 下载次数: 165
站长推荐 /1
STM32粉丝狂欢节还剩最后一个月,最高单人可得5块开发板!11月中奖名单即将公布,请收到开发板的朋友好好学习!好好利用!
Tel: 3-8056
备案号: 苏ICP备号-2
Powered by}

我要回帖

更多关于 stm32 复位电路 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信