前言:从51开始,单片机玩了很长时间了,有51,pic,avr等等,早就想跟潮流玩玩arm,但一直没有开始,原因-----不知道玩了arm可以做什么(对我自己而言)。如果为学习而学习,肯定学不好。然后cortex-m3出来了,据说,这东西可以替代单片机,于是马上开始关注。也在第一时间开始学习,可惜一开始就有点站错了队,选错了型(仍是对我自己而言)。我希望这种芯片应该是满大街都是,随便哪里都可以买得到,但我选的第一种显然做不到。为此,大概浪费了一年多时间吧,现在,回到对我来说是正确的道路上来啦,边学边写点东西。
这里写的是我的学习的过程,显然,很多时候会是不全面的,不系统的,感悟式的,甚至有时会是错误的,有些做法会是不专业的。那么,为什么我还要写呢?这是一个有趣的问题,它甚至涉及到博客为什么要存在的问题。显然,博客里面的写的东西,其正确性、权威性大多没法和书比,可为什么博客会存在呢?理由很多,我非专家,只说我的感慨。
我们读武侠小说,总会有一些创出独门功夫的宗师,功夫极高,然后他的弟子则基本上无法超越他。我在想,这位宗师在创造他自己的独门功夫时,必然会有很多的次的曲折、弯路、甚至失败,会浪费他的很多时间,而他教给弟子时,则已去掉了这些曲折和弯路,当然更不会把失败教给弟子,按理说,效率应该更高,可是没用,弟子大都不如师。为什么呢?也许知识本身并不是最重要的,获取知识的过程才是最重要的?也许所谓的知识,并不仅仅是一条条的结论,而是附带着很多说不清道不明的东西?如植物的根,一条主根上必带有大量的小小的触须?
闲话多了些,就权当前言了。下面准备开始。
一、条件的准备
我的习惯,第一步是先搭建一个学习的平台。原来学51,pic,avr时,都是想方设法自己做些工具,实验板之类,现在人懒了,直接购买成品了。
硬件电路板:火牛板
软件:有keil和iar可供选择。网上的口水仗不少,我选keil,理由很简单,这个我熟。目前要学的知识中,软、硬件我都不熟,所以找一个我有点熟的东西就很重要。在我相当熟练之前,肯定不会用到iar,如果真的有一天不得不用iar,相信学起来也很容易,因为这个时候硬件部分我肯定很熟了,再加上有 keil的基础,所以应该很容易学会了。
调试工具:jlink v8。这个不多说了,价格便宜又好用,就是它了。
二、热身
细细端详,做工精良,尤其那上面的3.2吋屏,越看越喜欢。接下来就是一阵折腾了,装jlink软件,给板子通电,先试试jlink能不能与电脑和板子通信上了。真顺,一点问题也没有。于是准备将附带的程序一个一个地写进去试一试。一检查,大部分例子的hex文件并没有给出,这要下一步自己生成,但是几个大工程的例子都有hex文件,如mp3,如uccgi测试等,写完以后观察程序运行的效果。因为之前也做过彩屏的东西,知道那玩艺代码量很大,要流畅地显示并不容,当时是用avr做的,在1.8吋屏上显示一幅画要有一段时间。现在看起来,用stm32做的驱动显示出来的画面还是很快的,不过这里显示的大部分是自画图,并没有完整地显示一整幅的照片,所以到底快到什么程度还不好说,看来不久以后这可以作为一个学习点的。
一个晚上过去了,下一篇就是要开始keil软件的学习了。
三、开始编程
硬件调通后,就要开始编程了。
编程的方法有两种,一种是用st提供的库,另一种是从最底层开始编程,网上关于使用哪种方法编程的讨论很多,据说用库的效率要低一些。但是用库编程非常方便,所以我还是从库开始啦。库是st提供的,怎么说也不会差到哪里,再说了,用32位arm的话,开发的观念也要随之改变一点了。
说说我怎么学的吧。
找个例子,如gpio,可以看到其结构如下:
source(文件夹)
- app(文件夹)
-cmsis(文件夹)
-stm32f10x_stdperiph_driver(文件夹)
lis(文件夹)
obj(文件夹)
其中source中保存的是应用程序,其中又有好多子文件夹,而cmsis文件夹中和stm32f10x_stdperiph_driver文件夹中是st提供的库,这样,如果要做新的工程只要将这个文件夹整个复制过来就行,其中app中保存自己的代码。
因为我们用51单片机时一般比较简单,有时就一个文件,所以通常不设置专门的输出文件夹,而这里做开发,通常会有很多个文件加入一个工程中,编译过程中会产生很多中间文件,因此设置专门的文件夹lis和obj用来保存中间文件。
下面就将设置简单描述一下。
将复到过来的gpio根目录下的所有文件删除,因为我们要学着自己建立工程。
用菜单project--》new uvision porject.。.建立新的工程,选择目标器件为stm32103vc,这个过程与建立51单片机的工程没有什么区别,这里就偷点懒,不上图了。接下来看一看怎么设置。
点那个品字形,打开对话框
这里就给个图了,相信有一定操作基础的人应该会用。顺便提一下,原来用vc或者iar时总觉得它们的一个功能:就是建立一个是debug组和release组,这个功能挺好的,从这个图可在keil里也是一样可以建的。
将刚才那个文件夹图中cmsis中的文件加入cmsis组,一共3个,其中\source\cmsis\core\cm3有两个c语言源程序文件全部加入,另外还有一个在
\source\cmsis\core\cm3\startup\arm文件夹中,这个文件夹中有4个.s文件, 我们选择其中的 startup_stm32f10x_hd.s文件。这是根据项目所用cpu来选择的,我们用的cpu是103vc的,属于高密度的芯片,所以选这个。
至于lib中的文件,就在这儿:\source\stm32f10x_stdperiph_driver\src啦。这里有很多个文件,把什么文件加进去呢?怕麻烦的话,把所有文件全部加进去,这并不会增加编译后的代码量,但会增加很多的编译时间。
接下来设定目标输出文件夹。上面这个图怎么出来的就不说啦,单击“select foler for objects。”,在弹出来的对话框中选择obj文件夹。
同样方法,选择list文件的输出文件夹。
设置好后,如果直接编译是不行的,会出错。还需要提供头文件所在位置。单击c/c++标签页。
第一次进入时include paths 文本框中是空白的,点击其后的按钮打开对话框,增加相应的路径
这样路径就设好了。单击ok,回到上一界面,然后再单击ok,退出设置,即可编译、链接。
下一会要试一试新的3.12版的库效果如何了。
升级库
光盘中所带的例子是3.10的,另外还有一个3.12的,我 试着将3.12的库替代原来的库,还真有问题,下面就简述问题及解决方法。
(1)将库文件解压
库文件名是:stm32f10x_stdperiph_lib.zip,解压后放在任意一个文件夹中。
(2)由于原作者做了很好的规划,每一个项目中都分成三个文件夹,并且在source文件夹中又做了3个文件夹,其中app文件夹是放自己写的文件的,其他的两个是从库中复制过来的,因此,想当然地把3.1.2版本中相同的两个文件夹:
cmsis和stm32f10x_stdperiph_driver直接复制过来,以为一切ok,结果一编译,出来一堆错误。
其中有错误:
source\app\main.c(7): error: #20: identifier “gpio_inittypedef” is undefined
。..。
还有大量的警告:
source\stm32f10x_stdperiph_driver\src\stm32f10x_flash.c(130): warning: #223-d: function “assert_param” declared implicitly
看了看,在app文件夹中还有一些不属于自己的东西:
stm32f10x_conf.h,stm32f10x_it.h,stm32f10x_it.c,打开一看,果然是3.10版本的,没说的,换。。。。,找到stm32f10x_stdperiph_lib_v3.1.2\project\template文件夹,用里面的同样的文件替换掉这几个文件,这回应该万事大吉了吧。
再一看,依然如故,,没办法了,只好细细研究了。通过观察,发现原来可以编译通过的工程,在main.c下面挂满了.h文件,而这个通不过的,则少得很。
这是编译能通过的工程
这是编译通不过的工程
显然,有些文件没有被包含进来。一点一点跟踪,发现大部分的头文件都包含在stm32f10x_conf.h中,而这个文件又出现在stm32f10x.h中,其中有这样的一行:
#ifdef use_stdperiph_driver
#include “stm32f10x_conf.h”
#endif
看来,是这个use_stdperiph_driver没有被定义啊,于是,人为地去掉条件:
//#ifdef use_stdperiph_driver
#include “stm32f10x_conf.h”
//#endif
再次编译,果然就ok了。可是,可是,也不能就这么去掉啊,怎么办呢?万能的网啊,一搜果然就有了。
到设置 c/c++页面
在那个define中加入“use_stdperiph_driver,stm32f10x_hd”
当然,去掉条件编译前面的注释,回到原样。
再次编译,一切顺利。可是,原来的工程例子也没有加这个啊,怎么回事呢?再次打开原来的例子,找到stm32f10x.h,可以看到有这么一行:
而新的stm32f10x.h中则是这样的:
原来那个3.1.0版的stm32f10x.h被人为地修改了一下,所以,不在define中定义也不要紧,而新升级的3.1.2则不行了。
至此,简单的升级搞定。
内存学习
arm中的ro、rw和zi data
一直以来对于arm体系中所描述的ro,rw和zi数据存在似是而非的理解,这段时间对其仔细了解了一番,发现了一些规律,理解了一些以前书本上有的但是不理解的东西,我想应该有不少人也有和我同样的困惑,因此将我的一些关于ro,rw和zi的理解写出来,希望能对大家有所帮助。
要了解ro,rw和zi需要首先了解以下知识:
arm程序的组成
此处所说的“arm程序”是指在arm系统中正在执行的程序,而非保存在rom中的bin映像(image)文件,这一点清注意区别。
一个arm程序包含3部分:ro,rw和zi
ro是程序中的指令和常量
rw是程序中的已初始化变量
zi是程序中的未初始化的变量
由以上3点说明可以理解为:
ro就是readonly,
rw就是read/write,
zi就是zero
arm映像文件的组成
所谓arm映像文件就是指烧录到rom中的bin文件,也称为image文件。以下用image文件来称呼它。
image文件包含了ro和rw数据。
之所以image文件不包含zi数据,是因为zi数据都是0,没必要包含,只要程序运行之前将zi数据所在的区域一律清零即可。包含进去反而浪费存储空间。
q:为什么image中必须包含ro和rw?
a:因为ro中的指令和常量以及rw中初始化过的变量是不能像zi那样“无中生有”的。
arm程序的执行过程
从以上两点可以知道,烧录到rom中的image文件与实际运行时的arm程序之间并不是完全一样的。因此就有必要了解arm程序是如何从rom中的image到达实际运行状态的。
实际上,ro中的指令至少应该有这样的功能:
1. 将rw从rom中搬到ram中,因为rw是变量,变量不能存在rom中。
2. 将zi所在的ram区域全部清零,因为zi区域并不在image中,所以需要程序根据编译器给出的zi地址及大小来将相应得ram区域清零。zi中也是变量,同理:变量不能存在rom中
在程序运行的最初阶段,ro中的指令完成了这两项工作后c程序才能正常访问变量。否则只能运行不含变量的代码。
说了上面的可能还是有些迷糊,ro,rw和zi到底是什么,下面我将给出几个例子,最直观的来说明ro,rw,zi在c中是什么意思。
1; ro
看下面两段程序,他们之间差了一条语句,这条语句就是声明一个字符常量。因此按照我们之前说的,他们之间应该只会在ro数据中相差一个字节(字符常量为1字节)。
prog1:
#include 《stdio.h》
void main(void)
{
;
}
prog2:
#include 《stdio.h》
const char a = 5;
void main(void)
{
;
}
prog1编译出来后的信息如下:
================================================================================
code ro data rw data zi data debug
948 60 0 96 0 grand totals
================================================================================
total ro size(code + ro data) 1008 ( 0.98kb)
total rw size(rw data + zi data) 96 ( 0.09kb)
total rom size(code + ro data + rw data) 1008 ( 0.98kb)
================================================================================
prog2编译出来后的信息如下:
================================================================================
code ro data rw data zi data debug
948 61 0 96 0 grand totals
================================================================================
total ro size(code + ro data) 1009 ( 0.99kb)
total rw size(rw data + zi data) 96 ( 0.09kb)
total rom size(code + ro data + rw data) 1009 ( 0.99kb)
================================================================================
以上两个程序编译出来后的信息可以看出:
prog1和prog2的ro包含了code和ro data两类数据。他们的唯一区别就是prog2的ro data比prog1多了1个字节。这正和之前的推测一致。
如果增加的是一条指令而不是一个常量,则结果应该是code数据大小有差别。
2; rw
同样再看两个程序,他们之间只相差一个“已初始化的变量”,按照之前所讲的,已初始化的变量应该是算在rw中的,所以两个程序之间应该是rw大小有区别。
prog3:
#include 《stdio.h》
void main(void)
{
;
}
prog4:
#include 《stdio.h》
char a = 5;
void main(void)
{
;
}
prog3编译出来后的信息如下:
================================================================================
code ro data rw data zi data debug
948 60 0 96 0 grand totals
================================================================================
total ro size(code + ro data) 1008 ( 0.98kb)
total rw size(rw data + zi data) 96 ( 0.09kb)
total rom size(code + ro data + rw data) 1008 ( 0.98kb)
================================================================================
prog4编译出来后的信息如下:
================================================================================
code ro data rw data zi data debug
948 60 1 96 0 grand totals
================================================================================
total ro size(code + ro data) 1008 ( 0.98kb)
total rw size(rw data + zi data) 97 ( 0.09kb)
total rom size(code + ro data + rw data) 1009 ( 0.99kb)
================================================================================
可以看出prog3和prog4之间确实只有rw data之间相差了1个字节,这个字节正是被初始化过的一个字符型变量“a”所引起的。
3; zi
再看两个程序,他们之间的差别是一个未初始化的变量“a”,从之前的了解中,应该可以推测,这两个程序之间应该只有zi大小有差别。
prog3:
#include 《stdio.h》
void main(void)
{
;
}
prog4:
#include 《stdio.h》
char a;
void main(void)
{
;
}
prog3编译出来后的信息如下:
================================================================================
code ro data rw data zi data debug
948 60 0 96 0 grand totals
================================================================================
total ro size(code + ro data) 1008 ( 0.98kb)
total rw size(rw data + zi data) 96 ( 0.09kb)
total rom size(code + ro data + rw data) 1008 ( 0.98kb)
================================================================================
prog4编译出来后的信息如下:
================================================================================
code ro data rw data zi data debug
948 60 0 97 0 grand totals
================================================================================
total ro size(code + ro data) 1008 ( 0.98kb)
total rw size(rw data + zi data) 97 ( 0.09kb)
total rom size(code + ro data + rw data) 1008 ( 0.98kb)
================================================================================
编译的结果完全符合推测,只有zi数据相差了1个字节。这个字节正是未初始化的一个字符型变量“a”所引起的。
注意:如果一个变量被初始化为0,则该变量的处理方法与未初始化华变量一样放在zi区域。
即:arm c程序中,所有的未初始化变量都会被自动初始化为0。
总结:
1; c中的指令以及常量被编译后是ro类型数据。
2; c中的未被初始化或初始化为0的变量编译后是zi类型数据。
3; c中的已被初始化成非0值的变量编译后市rw类型数据。
附:
程序的编译命令(假定c程序名为tst.c):
armcc -c -o tst.o tst.c
armlink -noremove -elf -nodebug -info totals -info sizes -map -list aa.map -o tst.elf tst.o
编译后的信息就在aa.map文件中。
rom主要指:nand flash,nor flash
ram主要指:psram,sdram,sram,ddram
骁龙845和麒麟980到底谁更厉害
使用运算放大器和555定时器设计锯齿波发生器电路
消防占道摄像机的原理和应用:了解这项技术的必要性和优势
一文解析Linux中ARP学习和老化机制
LED电路的三种接线方式介绍
STM32单片机小Tips(1):充分准备与开始编程
基于ISO9141标准的K线通讯方式实现汽车天窗马达ECU通讯系统的应用
酷博短信发送软件
Linux reset子系统及驱动实例
什么是光感传感器?
云计算死亡倒计时,未来属于分布式的点对点网络
宝安区航城街道领导一行莅临联诚发参观调研
新唐科技N588H120介绍
软硬整合,构筑可靠的数字标牌国产主板解决方案
赫兹是如何证实电磁波存在的?
S2C将FPGA设计原型带入云端:Prodigy完整原型设计平台能处理任何规模的工程
看3D电影应该如何呵护双眼
MAX4789, MAX4790, MAX4791, MAX
企业的移动性可以利用人工智能来改变吗
偏压、控制及电源管理IC简化LNB设计