关于STM32编码方式、优化等级、Debug、代码内存、变量内存、内部Flash、RAM说明
一、关于STM32编码方式
1、第一次安装好一个KEIL软件后,可以首先将编码方式设置成UTF-8,如下第3标记点,这样以后复制到其他UTF-8编码的项目就不会出现乱码情况。STM32默认编码方式为:,这种编码方式英文看起来比较合理,但是中文占两个字节,所以每次删除中文的时候都要删两次,而且当我们想要把代码移植到其他不是这种编码方式的工程时就会乱码。此时我们只能将编码方式改为GB2312复制过去,再改为ANSI才不会乱码,如果改成UTF-8的时候点了保存再改回来的话原本的工程注释也会变成乱码,如下第4标记点。此时只能退出工程,重新打开后才能恢复,退出时有没有保存的代码可以保存。
二、关于STM32优化等级
0等级——特点:最少的优化,可以最大程度上配合产生代码调试信息,可以在任何代码行打断点,特别是死代码处。
1等级——特点:删除未使用的内联函数(inline)和未使用的静态函数(static)。在这个优化级别,编译器还应用自动优化,例如删除冗余代码和重新排序指令以避免互锁情况。生成的代码经过合理优化,具有良好的调试视图。
2等级——特点:在此级别应用的优化利用了ARM对处理器体系结构的深入了解,利用给定目标的特定于处理器的行为。有可能会修改代码和函数调用执行流程,自动对函数进行内联等。调试有限。
3等级——特点:最大程度优化,产生极少量的调试信息。会进行更多代码优化,例如循环展开,更激进的函数内联等。
注意:1、在实际项目开发中,常用优化等级1,此时编译出来的hex文件和bin文件相对0级优化较小,但笔者遇到1级优化韦根输出数据错误,0级优化就没有问题的情况(把中断里的变量修饰成volatile也没用,可能是代码结构的问题)。
2、因此是否要开优化取决于代码量,如果代码太多编译不能通过也可以开1级优化,或者你的代码写的很好,否则会有疑难问题。
三、关于Debug
编译一下工程。然后点击:(开始/停止仿真按钮),开始仿真(如果开发板的代码没被更新过,则会先更新代码(即下载代码),再仿真,你也可以通过按 ,只下载代码,而不进入仿真。特别注意:开发板上的 B0和 B1 都要设置到 GND,否则代码下载后不会自动运行的!),如图
因为我们之前勾选了 Run to main()选项,所以,程序直接就运行到了 main 函数的入口处。另外,此时 MDK 多出了一个工具条, 这就是 Debug 工具条,这个工具条在我们仿真的时候是非常有用的,下面简单介绍一下 Debug 工具条相关按钮的功能。 Debug 工具条部分按钮的功能,如图:
执行出去:该按钮是在进入了函数单步调试的时候,有时候你可能不必再执行该函数的剩余部分了, 通过该按钮就直接一步执行完函数余下的部分,并跳出函数,回到函数被调用的位置。
执行到光标处:该按钮可以迅速的使程序运行到光标处,其实是挺像执行到断点处按钮功能,但是两者是 有区别的,断点可以有多个,但是光标所在处只有一个。
汇编窗口: 通过该按钮,就可以查看汇编代码,这对分析程序很有用。
堆栈局部变量窗口:通过该按钮,显示 Call Stack+Locals 窗口,显示当前函数局部变量及其值,方便查看。
观察窗口: MDK5 提供 2 个观察窗口(下拉选择),该按钮按下,会弹出一个显示变量的窗口, 输入你 所想要观察的变量/表达式,即可查看其值, 是很常用的一个调试窗口。
内存查看窗口: MDK5 提供 4 个内存查看窗口(下拉选择), 该按钮按下,会弹出一个内存查看窗口, 可以在里面输入你要查看的内存地址,然后观察这一片内存的变化情况。是很常用的一个 调试窗口
串口打印窗口: MDK5 提供 4 个串口打印窗口(下拉选择), 该按钮按下,会弹出一个类似串口调试助手界面的窗口,用来显示从串口打印出来的内容。
逻辑分析窗口: 该图标下面有 3 个选项(下拉选择),我们一般用第一个,也就是逻辑分析窗口(Logic Analyzer),点击即可调出该窗口, 通过 SETUP 按钮新建一些 IO 口,就可以观察这些 IO 口的电平变化情况,以多种形式显示出来,比较直观。
系统查看窗口: 该按钮可以提供各种外设寄存器的查看窗口(通过下拉选择),选择对应外设,即可调出 该外设的相关寄存器表,并显示这些寄存器的值,方便查看设置的是否正确。
特别注意: 串口打印窗口和逻辑分析窗口仅在软件仿真的时候可用,而 MDK5 对 STM32F4的软件仿真,基本上不支持(故本教程直接没有对软件仿真进行介绍了),所以,基本上这两 个窗口用不着。 但是对 STM32F1 的软件仿真, MDK5 是支持的,在 F1 开发的时候,可以用到。
四、关于程序、变量存放地址
1、首先我们从代码里编译出一个.HEX文件,这个文件是我们烧录到单片机里的可执行文件。
2、从这里我们可以看到代码所存放的是一段ROM空间,起始地址为0x8000000,大小为0x10000 = 65536。
3、去工程目录里找到这个hex文件,可以通过 Notepad++软件打开。
上图一行为16字节,因此代码总大小为:0x3CC0 = 15552,使用电脑计算器的程序员模式可以换算。
我们编译的时候如果没有报错底部也会显示代码大小,6660+8832+60 = 15552,跟HEX文件里的大小一致,
Code:编译生成的机器指令,也就是程序的执行代码,存放到 ROM 区。
RO-data: 只读数据(Read Only Data),包含程序中所定义的全局常量、字符串、const关键字修饰的变量数据,存放到 ROM 区。
RW-data: 已初始化的可读写数据(Read And Write Data),程序中定义并且初始化(非0值)的全局变量和静态变量,存放在 RAM 区。
ZI-data: 定义了但未初始化或初始化为0的可读写数据,也包含了堆栈大小。keil编译器默认是把你没有初始化的变量都赋值一个0,这些变量在程序运行时时保存在 RAM 中。
我们也可以分析一下生成的.map文件,路径为生成的工程中的Listings文件夹下,拉到文件底部我们可以看到如下图。
总结:在拿到一款单片机的时候,一定要明确单片机 ROM 大小和 RAM 大小。
RAM的空间要大于:Total Rw Size(RW Data + ZI Data)的总和。即上图中的1144字节 = 1.12kB,这是存入单片机RAM中的运行内存。
ROM的空间要大于:Total ROM Size(Code + RO Data + RW Data)的总和。即上图中的15552字节 = 15.19kB,这是烧录到单片机Flash中的程序大小。
会发现这里调试的值跟我们Notepad++打开的.hex文件的值一样。如果上述不直观我们可以拖动界面使其跟我们Notepad++打开的宽度一样。我们拖到代码总大小位置,发现后面都是FF FF,这是因为我们的代码只有这么大,没有存满这部分空间,通过计算好并且合理利用,这部分其实是可以用来存变量的。就像我们外接一个Flash一样,将变量存在这个地址掉电不丢失。STM32有相应的库函数可以直接调用。
4、变量地址,工作中我们常常使用Debug模式将断点打到变量后面的语句去查看变量的值,但是有没有发现值前面就是变量存放的地址,如下图二,我们也可以直接查看变量Memory地址。如图可知,变量的地址跟代码的地址并不是同一个空间。
五、STM32之bootloader
1、通过第四、我们知道编译出来的.hex文件在单片机ROM空间里是如何存放的,注:编译出的.hex文件前的地址并不是绝对地址,需要我们根据实际使用去计算,如下(当.hex文件太大在满了FFFF后又从0000开始加了,因此我们看地址时要实际看.hex文件加了几次0000到FFFF):
2、下图是有bootloader的情况,图一是bootloader代码存放地址,图二是APP应用程序存放地址,我们看到图二是从0x8010000地址开始的,也就是说前64k的大小给了bootloader,通过keil查看内存地址时也可以和bootloader编译出来的.hex文件以及app程序编译出来的.hex文件里的数据对应上。
3、升级流程如下,首先在APP应用程序里通过网络接口下载升级文件,下载完后校验好存入外部 Flash,并且把升级的标志位置1,也存入结构体中,方便bootload读取是否更新APP。
4、bootload功能实现如下(如下方式有问题,即升级成功重启时立马断电,此时更新APP的标志位已经清空,但是ST库函数还没有完全从外部Flash读到内部Flash,此时app程序就会运行不了,要优化可以在Upgrade函数头部再增加一个配置写到外部Flash并置0,如果成功写入内部Flash则置1,如果数据还没写完就不置1并且执行复位操作。此时就会重新去更新APP程序):
跳转到APP应用程序的过程中要先关不所有中断
六、Keil编译报超出内存的原因
有时我们编译会报错:error: L6050U: The code size of this image exceeds the maximum allowed for this versio
我们会以为是代码量大的原因,通过上面我们看到ROM空间为65536,此时有可能是Keil没有破解或之前的破解已经失效的原因,没有破解时keil本身所带字节很小,只能编译代码量很小的代码。需要使用注册机进行破解。
破解方法:
1、以管理员方式运行Keil软件。
2、选中Keil软件菜单栏的File栏下的License Management。
3、复制CID栏的数据到注册机里,注意:注册机模式是C51格式,要改为ARM,因为我们的单片机是ARM内核。然后点生成,将生成的数据复制到下图,点击Add LIC,此时就注册完成了。
七、STM32内部Flash运用
通过第三大点可知,当我们代码量不大时,可以从ROM里分配一块空间用来存变量(掉电不丢失)。就像我们外接一个AT24C02一样,STM32有相应的库函数可以直接调用(stm32f10x_flash.c)。一般我们将变量都放到一个结构体里,那么我们应该从哪里去开始写如数据呢?以上面的代码为例,我们看到代码结束的内存为0x8003CC0共15552字节,而根据第三点的2可知,ROM的size分配0x10000=65536,我们就可以使用0x80040000来作为存变量的开始地址,但实际开发中需要考虑增加新的代码,或则从最后65536往前去计算合适的大小。
案例分析:
1、如下,在flash.c里定义了变量从哪里开始存。但是flash.h里又定义了一个大小,因此最终为.C里的基地址+上.h里定义的起始地址。
2、我们接上设备后打开Debug调试模式,如果我们之前已经向该地址存过变量的值,如下,图片二在Memory下输入0x800F100,此时我们就看到了我们存在ROM下的变量的值,低位在前。
代码如下(作为参考,如需使用可在上面修改):
//flash.c
1 #include "stm32f10x.h" 2 #include "stm32_it.h" 3 #include "platform_config.h" 4 #include "flash.h" 5 6 #define DATA_FLASH_BASE (0x800ef00) 7 #define SECTOR_SIZE 1024 8 #define DATA_FLASH_SIZE (4 * SECTOR_SIZE)
9 #define FLASH_TOP_ADDR (0x8030000)
10 unsigned short FLASH_ReadHalfWord(unsigned int address)
11 {
12 return*(__IO unsigned short*)address;
13 }
14
15 void FLASH_read_buffer(unsigned int address, unsigned short *read_buf, unsigned short count)
16 {
17 unsigned short data_index;
18 u16 len;
19 FLASH_Unlock();
20 len = count/2;
21 //len += count%2;
22 for (data_index = 0; data_index < len; data_index++)
23 {
24 if((count%2) && (data_index == (len-1)))
25 break;
26 read_buf[data_index] = FLASH_ReadHalfWord((DATA_FLASH_BASE + address) + data_index * 2);
27 }
28 FLASH_Lock();
29 }
30
31 void FLASH_write_buffer(unsigned int startAddress, unsigned short *writeData, unsigned short countToWrite)
32 {
33
34 unsigned int WRITE_ADDR = 0;
35 u8 SEC_CON = 0;
36 u16 len,i;
37 len = countToWrite/2;
38 //len += countToWrite%2;
39 if((((DATA_FLASH_BASE + startAddress + countToWrite)) >= (FLASH_TOP_ADDR )))
40 {
41 return;
42 }
43 WRITE_ADDR = startAddress + DATA_FLASH_BASE;
44 SEC_CON = countToWrite /SECTOR_SIZE;
45 if(!SEC_CON)
46 SEC_CON++;
47
48 FLASH_Unlock();
49 for(i = 0;i<SEC_CON;i++)
50 {
51 FLASH_ErasePage(WRITE_ADDR+i*SECTOR_SIZE);
52 }
53 for(i = 0;i < len;i++)
54 {
55 FLASH_ProgramHalfWord(WRITE_ADDR + i * 2, writeData[i]);
56 }
57
58 FLASH_Lock();
59 }
//flash.h
#ifndef __FLASH_H #define __FLASH_H #include "stm32f10x.h" #include <string.h> #define DEBUG 10// #if DEBUG != 0 #warning Çë¹Ø±Õµ÷ÊÔģʽ #endif #define USE_CON_ADDR 512 #define SYS_CON_ADDR (USE_CON_ADDR+512)
extern USERCONFIG con;
extern SYSTEMCONFIG SysCon;
void FLASH_read_buffer(unsigned int address, unsigned short *read_buf, unsigned short count);
void FLASH_write_buffer(unsigned int startAddress, unsigned short *writeData, unsigned short countToWrite);
#endif