STM32中使用strtok,atol,atoi,memset,strlen;后使用atoi系统卡死

修改使用 heap_size  增大

其实就是堆栈的不足。

好好分析下程序堆栈,如下:

函数的局部变量,都是存放在"栈"里面,栈的英文是:STACK.STACK的大小,我们可以在stm32的启动文件里面设置,以战舰stm32开发板为例,在startup_stm32f10x_hd.s里面,开头就有:
Stack_Size  EQU  0x00000800
表示栈大小是0X800,也就是2048字节.这样,CPU处理任务的时候,函数局部变量做多可占用的大小就是:2048字节,注意:是所有在处理的函数,包括函数嵌套,递归,等等,都是从这个"栈"里面,来分配的.
所以,如果一个函数的局部变量过多,比如在函数里面定义一个u8 buf[512],这一下就占了1/4的栈大小了,再在其他函数里面来搞两下,程序崩溃是很容易的事情,这时候,一般你会进入到hardfault....
这是初学者非常容易犯的一个错误.切记不要在函数里面放N多局部变量,尤其有大数组的时候!

对于栈区,一般栈顶,也就是MSP,在程序刚运行的时候,指向程序所占用内存的最高地址.比如附件里面的这个程序序,内存占用如下图:

图中,我们可以看到,程序总共占用内存:20+2348字节=2368=0X940
那么程序刚开始运行的时候:MSP=0X2000 0000+0X940=0X2000 0940.
事实上,也是如此,如图:

图中,MSP就是:0X2000 0940.
程序运行后,MSP就是从这个地址开始,往下给函数的局部变量分配地址.
再说说栈的增长方向,我们可以用如下代码测试:
//保存栈增长方向
//0,向下增长;1,向上增长.
static u8 stack_dir;
//查找栈增长方向,结果保存在stack_dir里面.
void find_stack_direction(void)
{
static u8 *addr=NULL; //用于存放第一个dummy的地址。
u8 dummy;  //用于获取栈地址
if(addr==NULL)  //第一次进入
{
addr=&dummy;  //保存dummy的地址
find_stack_direction ();  //递归
}else  //第二次进入
{
if(&dummy>addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
else stack_dir=0;  //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的.
}
}
这个代码不是我写的,网上抄来的,思路很巧妙,利用递归,判断两次分配给dummy的地址,来比较栈是向下生长,还是向上生长.
如果你在STM32测试这个函数,你会发现,STM32的栈,是向下生长的.事实上,一般CPU的栈增长方向,都是向下的.
2,再来说说,堆(HEAP)的问题.
全局变量,静态变量,以及内存管理所用的内存,都是属于"堆"区,英文名:"HEAP"
与栈区不同,堆区,则从内存区域的起始地址,开始分配给各个全局变量和静态变量.
堆的生长方向,都是向上的.在程序里面,所有的内存分为:堆+栈. 只是他们各自的起始地址和增长方向不同,他们没有一个固定的界限,所以一旦堆栈冲突,系统就到了崩溃的时候了.
同样,我们用附件里面的例程测试:

stack_dir的地址是0X20000004,也就是STM32的内存起始端的地址.
这里本来应该是从0X2000 0000开始分配的,但是,我仿真发现0X2000 0000总是存放:0X2000 0398,这个值,貌似是MSP,但是又不变化,还请高手帮忙解释下.
其他的,全局变量,则依次递增,地址肯定大于0X20000004,比如cpu_endian的地址就是0X20000005.
这就是STM32内部堆的分配规则.
3,再说说,大小端的问题.
大端模式:低位字节存在高地址上,高位字节存在低地址上
小端模式:高位字节存在高地址上,低位字节存在低地址上
STM32属于小端模式,简单的说,比如u32 temp=0X12345678;
假设temp地址在0X2000 0010.
那么在内存里面,存放就变成了:
地址  |  HEX  |
0X2000 0010  |  78  56  43  12  |
CPU到底是大端还是小端,可以通过如下代码测试:
//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;
//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)
{
int x=1;
if(*(char*)&x==1)cpu_endian=0; //小端模式
else cpu_endian=1;  //大端模式
}
以上测试,在STM32上,你会得到cpu_endian=0,也就是小端模式.
3,最后说说,STM32内存的问题.
还是以附件工程为例,在前面第一个图,程序总共占用内存:20+2348字节,这么多内存,到底是怎么得来的呢?
我们可以双击Project侧边栏的:Targt1,会弹出test.map,在这个里面,我们就可以清楚的知道这些内存到底是怎么来的了.在这个test.map最后,Image 部分有:
==============================================================================

Image component sizes

Code (inc. data)  RO Data  RW Data  ZI Data  Debug  Object Name

172  10  0  4  0  995  delay.o//delay.c里面,fac_us和fac_ms,共占用4字节
112  12  0  0  0  427  led.o
72  26  304  0  2048  828  startup_stm32f10x_hd.o  //启动文件,里面定义了Stack_Size为0X800,所以这里是2048.
712  52  0  0  0  2715  sys.o
348  154  0  6  0  208720  test.o//test.c里面,stack_dir和cpu_endian 以及*addr ,占用6字节.
384  24  0  8  200  3050  usart.o//usart.c定义了一个串口接收数组buffer,占用200字节.

----------------------------------------------------------------------
1800  278  336  20  2248  216735  Object Totals //总共2248+20字节
0  0  32  0  0  0  (incl. Generated)
0  0  0  2  0  0  (incl. Padding)//2字节用于对其

----------------------------------------------------------------------

Code (inc. data)  RO Data  RW Data  ZI Data  Debug  Library Member Name

8  0  0  0  0  68  __main.o
104  0  0  0  0  84  __printf.o
52  8  0  0  0  0  __scatter.o
26  0  0  0  0  0  __scatter_copy.o
28  0  0  0  0  0  __scatter_zi.o
48  6  0  0  0  96  _printf_char_common.o
36  4  0  0  0  80  _printf_char_file.o
92  4  40  0  0  88  _printf_hex_int.o
184  0  0  0  0  88  _printf_intcommon.o
0  0  0  0  0  0  _printf_percent.o
4  0  0  0  0  0  _printf_percent_end.o
6  0  0  0  0  0  _printf_x.o
12  0  0  0  0  72  exit.o
8  0  0  0  0  68  ferror.o
6  0  0  0  0  152  heapauxi.o
2  0  0  0  0  0  libinit.o
2  0  0  0  0  0  libinit2.o
2  0  0  0  0  0  libshutdown.o
2  0  0  0  0  0  libshutdown2.o
8  4  0  0  96  68  libspace.o  //库文件(printf使用),占用了96字节
24  4  0  0  0  84  noretval__2printf.o
0  0  0  0  0  0  rtentry.o
12  0  0  0  0  0  rtentry2.o
6  0  0  0  0  0  rtentry4.o
2  0  0  0  0  0  rtexit.o
10  0  0  0  0  0  rtexit2.o
74  0  0  0  0  80  sys_stackheap_outer.o
2  0  0  0  0  68  use_no_semi.o
2  0  0  0  0  68  use_no_semi_2.o
450  8  0  0  0  236  faddsub_clz.o
388  76  0  0  0  96  fdiv.o
62  4  0  0  0  84  ffixu.o
38  0  0  0  0  68  fflt_clz.o
258  4  0  0  0  84  fmul.o
140  4  0  0  0  84  fnaninf.o
10  0  0  0  0  68  fretinf.o
0  0  0  0  0  0  usenofp.o

----------------------------------------------------------------------
2118  126  42  0  100  1884  Library Totals  //调用的库用了100字节.
10  0  2  0  4  0  (incl. Padding)  //用于对其多占用了4个字节

----------------------------------------------------------------------

Code (inc. data)  RO Data  RW Data  ZI Data  Debug  Library Name

762  30  40  0  96  1164  c_w.l
1346  96  0  0  0  720  fz_ws.l

----------------------------------------------------------------------
2118  126  42  0  100  1884  Library Totals

----------------------------------------------------------------------

==============================================================================

Code (inc. data)  RO Data  RW Data  ZI Data  Debug

3918  404  378  20  2348  217111  Grand Totals
3918  404  378  20  2348  217111  ELF Image Totals
3918  404  378  20  0  0  ROM Totals

==============================================================================

Total RO  Size (Code + RO Data)  4296 (  4.20kB)
Total RW  Size (RW Data + ZI Data)  2368 (  2.31kB)  //总共占用:2248+20+100=2368.
Total ROM Size (Code + RO Data + RW Data)  4316 (  4.21kB)

==============================================================================
通过这个文件,我们就可以分析整个内存,是怎么被占用的,具体到每个文件,占用多少.一目了然了.
4,最后,看看整个测试代码:
main.c代码如下,工程见附件.
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"
//ALIENTEK战舰STM32开发板堆栈增长方向以及CPU大小端测试

//保存栈增长方向
//0,向下增长;1,向上增长.
static u8 stack_dir;

//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;

//查找栈增长方向,结果保存在stack_dir里面.
void find_stack_direction(void)
{
static u8 *addr=NULL; //用于存放第一个dummy的地址。
u8 dummy;  //用于获取栈地址
if(addr==NULL)  //第一次进入
{
addr=&dummy;  //保存dummy的地址
find_stack_direction ();  //递归
}else  //第二次进入
{
if(&dummy>addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
else stack_dir=0;  //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的.
}
}
//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)
{
int x=1;
if(*(char*)&x==1)cpu_endian=0; //小端模式
else cpu_endian=1;  //大端模式
}
int main(void)
{
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600);  //串口初始化为9600
delay_init(72);  //延时初始化
LED_Init();  //初始化与LED连接的硬件接口
printf("stack_dir:%x\r\n",&stack_dir);
printf("cpu_endian:%x\r\n",&cpu_endian);
find_stack_direction(); //获取栈增长方式
find_cpu_endian();  //获取CPU大小端模式
while(1)
{
if(stack_dir)printf("STACK DIRCTION:向上生长\r\n\r\n");
else printf("STACK DIRCTION:向下生长\r\n\r\n");
if(cpu_endian)printf("CPU ENDIAN:大端模式\r\n\r\n");
else printf("CPU ENDIAN:小端模式\r\n\r\n");
delay_ms(500);
LED0=!LED0;
}
}

最后切记程序函数变量数组占用,切记分配内存,函数指针调用等。

原文地址:https://www.cnblogs.com/xiumusheng/p/11576730.html

时间: 2024-11-05 19:28:37

STM32中使用strtok,atol,atoi,memset,strlen;后使用atoi系统卡死的相关文章

STM32中的几个时钟SysTick、FCLK、SYSCLK、HCLK

用时钟源来产生时钟! 在STM32中,有五个时钟源,为HSI.HSE.LSI.LSE.PLL.①.HSI是高速内部时钟,RC振荡器,频率为8MHz.②.HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz.③.LSI是低速内部时钟,RC振荡器,频率为40kHz.④.LSE是低速外部时钟,接频率为32.768kHz的石英晶体.⑤.PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2.HSE或者HSE/2.倍频可选择为2~16倍,但是其输出频率最大不得超过7

STM32中IO口的8中工作模式

该文摘自:http://blog.csdn.net/kevinhg/article/details/17490273 一.推挽输出:可以输出高.低电平,连接数字器件:推挽结构一般是指两个三极管分别受两个互补信号的控制,总是在一个三极管导通的时候另一个截止.高低电平由IC的电源决定.         推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务,电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小.效率高.输出既可以向负载灌电流,也

STM32中的位带(bit-band)操作(转)

源:STM32中的位带(bit-band)操作 支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写.在 CM3 中,有两个区中实现了位带.其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB范围.这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字.当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的.       位带操作的概念其实 30 年前就有了,那还是805

给定二叉树的先序遍历和中序遍历,输出它的后序遍历序列

这里没再用到先申请大Node数组的方法了,想练练写动态内存分配和释放的,一次OK了,也没怎么出错啊,开心~ 方法二 - Code: //给出一个二叉树的先序遍历和中序遍历,输出它的后序遍历 //直接构造的方法白书已给出.这里是先递归构造二叉树,然后进行后序遍历. #include<stdio.h> #include<string.h> #include<stdlib.h> #define MAXN 1000 typedef struct node { char data

安装完Linux Mint后,发现系统中竟没有中文输入法

安装完Linux Mint后,发现系统中竟没有中文输入法,语言支持之后自动更新过程中有些安装包下载失败. 可以采取下面的方法安装上中文输入法. 1. 安装iBus: sudo add-apt-repository ppa:shawn-p-huang/ppasudo apt-get update sudo apt-get install ibus-gtk ibus-pinyin ibus-pinyin-db-open-phrase 2. 打开语言支持,选择更新,这时它会下载一系列相关安装包 3.

根据二叉树的先序序列和中序序列还原二叉树并打印后序序列

#include<stdio.h> #include<stdlib.h> #include<iostream> using namespace std; struct Node { int value; Node *left; Node *right; Node(int value) { this->value = value; left = right = NULL; } }; bool bNotTree = false; Node* RebuildTree(i

删除数组中某一项并输出删除后的数组

方法一: package delete;import java.util.Scanner;public class delete { public static void main(String[] args){ Scanner input = new Scanner(System.in); int[] num = new int[]{3,6,9,13}; System.out.print("请输入要删除的数:"); int number = input.nextInt(); bool

10.4 有n个整数,使前面各数顺序向后移m个位置,最后m个数变成前面m个数,见图。写一函数:实现以上功能,在主函数中输入n个数和输出调整后的n个数。

有n个整数,使前面各数顺序向后移m个位置,最后m个数变成前面m个数,见图.写一函数:实现以上功能,在主函数中输入n个数和输出调整后的n个数. 看了下上机指导上的答案,感觉自己写的这段代码有点low. //一个数组一个指针 #include <stdio.h> #define N 10 #define M 3 int b[N]; int * p=b; int main(){ void change(int a[N],int m); int a[N]={1,2,3,4,5,6,7,8,9,0};

在Fragment中嵌套使用viewpagerindicator切换Fragment返回后出现空白页与FragmentPagerAdapter页面预加载的解决方案

1. 在Fragment中嵌套使用viewpagerindicator切换Fragment返回后出现空白页 先上代码: 1 mAdapter = new OrderAdapter(getChildFragmentManager()); 2 ViewPager pager = (ViewPager) v.findViewById(R.id.pager); 3 pager.setAdapter(mAdapter); 4 5 TabPageIndicator indicator = (TabPageI