STM32学习笔记,const变量存储在哪里

变量 9
STM32学习笔记 从51开始,单片机玩了很长时间了,有51,PIC,AVR等等,早就想跟潮流玩玩ARM,但一直没有开始,原因-----不知道玩了ARM可以做什么(对我自己而言)。
如果为学习而学习,肯定学不好。
然后cortex-m3出来了,据说,这东西可以替代单片机,于是马上开始关注。
也在第一时间开始学习,可惜一开始就有点站错了队,选错了型(仍是对我自己而言)。
我希望这种芯片应该是满大街都是,随便哪里都可以买得到,但我选的第一种显然做不到。
为此,大概浪费了一年多时间吧,现在,回到对我来说是正确的道路上来啦,边学边写点东西。
这里写的是我的学习的过程,显然,很多时候会是不全面的,不系统的,感悟式的,甚至有时会是错误的,有些做法会是不专业的。
那么,为什么我还要写呢?这是一个有趣的问题,它甚至涉及到博客为什么要存在的问题。
显然,博客里面的写的东西,其正确性、权威性大多没法和书比,可为什么博客会存在呢?理由很多,我非专家,只说我的感慨。
我们读武侠小说,总会有一些创出独门功夫的宗师,功夫极高,然后他的弟子则基本上无法超越他。
我在想,这位宗师在创造他自己的独门功夫时,必然会有很多的次的曲折、弯路、甚至失败,会浪费他的很多时间,而他教给弟子时,则已去掉了这些曲折和弯路,当然更不会把失败教给弟子,按理说,效率应该更高,可是没用,弟子大都不如师。
为什么呢?也许知识本身并不是最重要的,获取知识的过程才是最重要的?也许所谓的知识,并不仅仅是一条条的结论,而是附带着很多说不清道不明的东西?如植物的根,一条主根上必带有大量的小小的触须?闲话多了些,就权当前言了。
下面准备开始。

一、条件的准备 我的习惯,第一步是先搭建一个学习的平台。
原来学51,PIC,AVR时,都是想方设法自己做些工具,实验板之类,现在人懒了,直接购买成品了。
硬件电路板:火牛板软件:有keil和iar可供选择。
网上的口水仗不少,我选keil,理由很简单,这个我熟。
目前要学的知识中,软、硬件我都不熟,所以找一个我有点熟的东西就很重要。
在我相当熟练之前,肯定不会用到IAR,如果真的有一天不得不用IAR,相信学起来也很容易,因为这个时候硬件部分我肯定很熟了,再加上有keil的基础,所以应该很容易学会了。
调试工具:JLINKV8。
这个不多说了,价格便宜又好用,就是它了。

二、热身网上选购的,付了款就是等了。
拿到包裹,端详良久,起身。



沐浴,更衣,焚香,,,,,总得先吃晚饭,洗澡,再点个电蚊香什么的吧。
,拆包细细端详,做工精良,尤其那上面的3.2吋屏,越看越喜欢。
接下来就是一阵折腾了,装JLINK软件,给板子通电,先试试JLINK能不能与电脑和板子通信上了。
真顺,一点问题也没有。
于是准备将附带的程序一个一个地写进去试一试。
一检查,大部分例子的HEX文件并没有给出,这要下一步自己生成,但是几个大工程的例子都有HEX文件,如MP3,如UCCGI测试等,写完以后观察程序运行的效果。
因为之前也做过彩屏的东西,知道那玩艺代码量很大,要流畅地显示并不容,当时是用AVR做的,在1.8吋屏上显示一幅画要有一段时间。
现在看起来,用STM32做的驱动显示出来的画面还是很快的,不过这里显示的大部分是自画图,并没有完整地显示一整幅的照片,所以到底快到什么程度还不好说,看来不久以后这可以作为一个学习点的。
一个晚上过去了,下一篇就是要开始keil软件的学习了。
STM32学习笔记
(2) 本想着偷点懒的,没想到竞被加了“精”,没办法啦,只能勤快点啦。


硬件调通后,就要开始编程了。
编程的方法有两种,一种是用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-->NewuVisionPorject...建立新的工程,选择目标器件为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啦。
这里有很多个文件,把什么文件加进去呢?怕麻烦的话,把所有文件全部加进去,这并不会增加编译后的代码量,但会增加很多的编译时间。
接下来设定目标输出文件夹。
上面这个图怎么出来的就不说啦,单击“SelectFolerforObjects...”,在弹出来的对话框中选择OBJ文件夹。
同样方法,选择List文件的输出文件夹。
设置好后,如果直接编译是不行的,会出错。
还需要提供头文件所在位置。
单击c/C++标签页. 第一次进入时IncludePaths文本框中是空白的,点击其后的按钮打开对话框,增加相应的路径 这样路径就设好了。
单击OK,回到上一界面,然后再单击OK,退出设置,即可编译、链接。
下一会要试一试新的3.12版的库效果如何了。
STM32学习笔记
(3) 升级库光盘中所带的例子是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"isundefined....还有大量的警告: Source\STM32F10x_StdPeriph_Driver\src\stm32f10x_flash.c(130):warning:#223-D:function"assert_param"declaredimplicitly看了看,在APP文件夹中还有一些不属于自己的东西:stm32f10x_conf.h,stm32f10x_it.h,stm32f10x_it.c,打开一看,果然是3.10版本的,没说的,换。



,找到STM32F10x_StdPeriph_Lib_V3.1.2\Proje ct\Template文件夹,用里面的同样的文件替换掉这几个文件,这回应该万事大吉了吧。
再一看,依然如故,,没办法了,只好细细研究了。
通过观察,发现原来可以编译通过的工程,在main.c下面挂满了.h文件,而这个通不过的,则少得很。
这是编译能通过的工程 这是编译通不过的工程显然,有些文件没有被包含进来。
一点一点跟踪,发现大部分的头文件都包含在stm32f10x_conf.h中,而这个文件又出现在stm32f10x.h中,其中有这样的一行:#ifdefUSE_STDPERIPH_DRIVER #include"stm32f10x_conf.h"#endif看来,是这个USE_STDPERIPH_DRIVER没有被定义啊,于是,人为地去掉条件://#ifdefUSE_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和ZIDATA一直以来对于ARM体系中所描述的RO,RW和ZI数据存在似是而非的理解,这段时间对其仔细了解了一番,发现了一些规律,理解了一些以前书本上有的但是不理解的东西,我想应该有不少人也有和我同样的困惑,因此将我的一些关于RO,RW和ZI的理解写出来,希望能对大家有所帮助。
要了解RO,RW和ZI需要首先了解以下知识:ARM程序的组成此处所说的“ARM程序”是指在ARM系统中正在执行的程序,而非保存在ROM 中的bin映像(image)文件,这一点清注意区别。
一个ARM程序包含3部分:RO,RW和ZIRO是程序中的指令和常量RW是程序中的已初始化变量ZI是程序中的未初始化的变量由以上3点说明可以理解为:RO就是readonly,RW就是read/write,ZI就是zeroARM映像文件的组成所谓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 voidmain(void) { ; }Prog2: #includeconstchara=5; voidmain(void) { ; }Prog1编译出来后的信息如下: =========================================================== ===================== Code
RODataRWDataZIDataDebug 948 60
0 96
0 GrandTotals =========================================================== ===================== Total
ROSize(Code+ROData) 1008(0.98kB) TotalRWSize(RWData+ZIData) 96(0.09kB) TotalROMSize(Code+ROData+RWData) 1008(0.98kB) =========================================================== =====================
Prog2编译出来后的信息如下: =========================================================== ===================== Code
RODataRWData ZIDataDebug 948 61
0 96
0 GrandTotals =========================================================== ===================== Total
ROSize(Code+ROData) 1009(0.99kB) TotalRWSize(RWData+ZIData) 96(0.09kB) TotalROMSize(Code+ROData+RWData)1009(0.99kB) =========================================================== =====================
以上两个程序编译出来后的信息可以看出: Prog1和Prog2的RO包含了Code和ROData两类数据。
他们的唯一区别就是Prog2的ROData比Prog1多了1个字节。
这正和之前的推测一致。
如果增加的是一条指令而不是一个常量,则结果应该是Code数据大小有差别。
2;RW同样再看两个程序,他们之间只相差一个“已初始化的变量”,按照之前所讲的,已初始化的变量应该是算在RW中的,所以两个程序之间应该是RW大小有区别。
Prog3: #include voidmain(void) { ; }Prog4: #includechara=5; voidmain(void) { ; }Prog3编译出来后的信息如下: =========================================================== ===================== Code
RODataRWData ZIDataDebug 948 60
0 96
0 GrandTotals =========================================================== ===================== Total
ROSize(Code+ROData) 1008(0.98kB) TotalRWSize(RWData+ZIData) 96(0.09kB) TotalROMSize(Code+ROData+RWData)1008(0.98kB) =========================================================== =====================
Prog4编译出来后的信息如下: =========================================================== ===================== Code
ROData RWDataZIData Debug 948 60
1 96
0 Grand Totals =========================================================== ===================== Total
ROSize(Code+ROData) 1008(0.98kB) TotalRWSize(RWData+ZIData) 97(0.09kB) TotalROMSize(Code+ROData+RWData) 1009(0.99kB) =========================================================== =====================
可以看出Prog3和Prog4之间确实只有RWData之间相差了1个字节,这个字节正是被初始化过的一个字符型变量“a”所引起的。
3;ZI再看两个程序,他们之间的差别是一个未初始化的变量“a”,从之前的了解中,应该可以推测,这两个程序之间应该只有ZI大小有差别。
Prog3: #include voidmain(void) { ; }Prog4: #includechara; voidmain(void) { ; }Prog3编译出来后的信息如下: =========================================================== ===================== Code
RODataRWData ZIData Debug 948 60
0 96
0 Grand Totals =========================================================== ===================== Total
ROSize(Code+ROData) 1008(0.98kB) TotalRWSize(RWData+ZIData) 96(0.09kB) TotalROMSize(Code+ROData+RWData) 1008(0.98kB) =========================================================== =====================
Prog4编译出来后的信息如下: =========================================================== ===================== Code RO
Data RWData ZIData Debug 948 60
0 97
0 Grand Totals =========================================================== ===================== Total
ROSize(Code+ROData) 1008(0.98kB) TotalRWSize(RWData+ZIData) 97(0.09kB) TotalROMSize(Code+ROData+RWData) 1008(0.98kB) =========================================================== =====================
编译的结果完全符合推测,只有ZI数据相差了1个字节。
这个字节正是未初始 化的一个字符型变量“a”所引起的。
注意:如果一个变量被初始化为
0,则该变量的处理方法与未初始化华变量一样 放在ZI区域。
即:ARMC程序中,所有的未初始化变量都会被自动初始化为
0。
总结: 1;C中的指令以及常量被编译后是RO类型数据。
2;C中的未被初始化或初始化为0的变量编译后是ZI类型数据。
3;C中的已被初始化成非0值的变量编译后市RW类型数据。
附:程序的编译命令(假定C程序名为tst.c):-c-otst.otst.carmlink-noremove-elf-nodebug-infototals-infosizes-map-listaa.map-otst.elftst.o编译后的信息就在aa.map文件中。
ROM主要指:NANDFlash,NorFlashRAM主要指:PSRAM,SDRAM,SRAM,DDRAM继续学习中,先把开发板自带一个例子做了些精简,以免看得吓人。



就是这个,让PORTD上接的4个LED分别点亮。
开始研究代码intmain(void){Init_All_Periph();......看到这一行,开始跟踪,于是又看到了下面的内容voidInit_All_Periph(void){RCC_Configuration();......继续跟踪voidRCC_Configuration(void){SystemInit();......这行代码在system_stm32f10x.c中找到了。
voidSystemInit(void){ /*ResettheRCCclockconfigurationtothedefaultresetstate(fordebugpurpose)*//*SetHSIONbit*/RCC->CR|=(uint32_t)0x00000001;/*ResetSW,HPRE,PPRE1,PPRE2,ADCPREandMCObits*/#ifndefSTM32F10X_CLRCC->CFGR&=(uint32_t)0xF8FF0000;#elseRCC->CFGR&=(uint32_t)0xF0FF0000;#endif/*STM32F10X_CL*/ /*ResetHSEON,CSSONandPLLONbits*/RCC->CR&=(uint32_t)0xFEF6FFFF;/*ResetHSEBYPbit*/RCC->CR&=(uint32_t)0xFFFBFFFF;/*ResetPLLSRC,PLLXTPRE,PLLMULandUSBPRE/OTGFSPREbits*/RCC->CFGR&=(uint32_t)0xFF80FFFF;#ifndefSTM32F10X_CL/*Disableallinterruptsandclearpendingbits*/ RCC->CIR=0x009F0000;#else /*ResetPLL2ONandPLL3ONbits*/RCC->CR&=(uint32_t)0xEBFFFFFF;/*Disableallinterruptsandclearpendingbits*/RCC->CIR=0x00FF0000;/*ResetCFGR2register*/RCC->CFGR2=0x00000000;#endif/*STM32F10X_CL*/ /*ConfiguretheSystemclockfrequency,HCLK,PCLK2andPCLK1prescalers*/ /*ConfiguretheFlashLatencycyclesandenableprefetchbuffer*/SetSysClock();} 这一长串的又是什么,如何来用呢?看来,偷懒是不成的了,只能回过头去研究STM32的时钟构成了。
相当的复杂。
系统的时钟可以有3个来源:内部时钟HSI,外部时钟HSE,或者PLL(锁相环模块)的输出。
它们由RCC_CFGR寄存器中的SW来选择。
SW(1:0):系统时钟切换由软件置’1’或清’0’来选择系统时钟源。
在从停止或待机模式中返回时或直接或间接作为系统时钟的HSE出现故障时,由硬件强制选择HSI作为系统时钟(如果时钟安全系统已经启动)00:HSI作为系统时钟;01:HSE作为系统时钟;10:PLL输出作为系统时钟;11:不可用。
////////////////////////////////////////////////////////////////////PLL的输出直接送到USB模块,经过适当的分频后得到48M的频率供USB模块使用。
系统时钟的一路被直接送到I2S模块;另一路经过AHB分频后送出,送往各个系统,其中直接送往SDI,FMSC,AHB总线;8分频后作为系统定时器时钟;经过APB1分频分别控制PLK1、定时器TIM2~TIM7;经过APB2分频分别控制PLK2、定时器TIM1~TIM8、再经分频控制ADC;由此可知,STM32F10x芯片的时钟比之于51、AVR、PIC等8位机要复杂复多,因此,我们立足于对着芯片手册来解读程序,力求知道这些程序代码如何使用, 为何这么样使用,如果自己要改,可以修改哪些部分,以便自己使用时可以得心应手。
单步执行,看一看哪些代码被执行了。
/*ResettheRCCclockconfigurationtothedefaultresetstate(fordebugpurpose)*/ /*SetHSIONbit*/RCC->CR|=(uint32_t)0x00000001; 这是RCC_CR寄存器,由图可见,HSION是其bit0位。
HSION:内部高速时钟使能由软件置’1’或清零。
当从待机和停止模式返回或用作系统时钟的外部4-25MHz时钟发生故障时, 该位由硬件置’1’来启动内部8MHz的RC振荡器。
当内部8MHz时钟被直接或间接地用作或被选择将要作为系统时钟时,该位不能被清零。
0:内部8MHz时钟关闭;1:内部8MHz时钟开启。
////////////////////////////////////////////////////////////////////////*ResetSW,HPRE,PPRE1,PPRE2,ADCPREandMCObits*/#ifndefSTM32F10X_CLRCC->CFGR&=(uint32_t)0xF8FF0000; 这是RCC_CFGR寄存器该行程序清零了MC0[2:0]这三位,和ADCPRE[1:0],ppre2[2:0],PPRE1[2:0],HPRE[3:0],SWS[1:0]和SW[1:0]这16位。
/*MCO:微控制器时钟输出,由软件置’1’或清零。
0xx:没有时钟输出;100:系统时钟(SYSCLK)输出;101:内部8MHz的RC振荡器时钟输出;110:外部4-25MHz振荡器时钟输出;111:PLL时钟2分频后输出。
*//*ResetHSEON,CSSONandPLLONbits*/ RCC->CR&=(uint32_t)0xFEF6FFFF;清零了PLLON,HSEBYP,HSERDY这3位。
/*ResetHSEBYPbit*/ RCC->CR&=(uint32_t)0xFFFBFFFF;清零了HSEBYP位///???为什么不一次写?? HSEBYP:外部高速时钟旁路,在调试模式下由软件置’1’或清零来旁路外部晶体振荡器。
只有在外部4-25MHz振荡器关闭的情况下,才能写入该位。
0:外部4-25MHz振荡器没有旁路;1:外部4-25MHz外部晶体振荡器被旁路。
所以要先清HSEON位,再清该位。
/*ResetPLLSRC,PLLXTPRE,PLLMULandUSBPRE/OTGFSPREbits*/RCC->CFGR&=(uint32_t)0xFF80FFFF; 清零了:USBPRE,PLLMUL,PLLXTPR,PLLSRC共7位/*Disableallinterruptsandclearpendingbits*/RCC->CIR=0x009F0000;////这个暂不解读SetSysClock();跟踪进入该函数,可见一连串的条件编译: 单步运行,执行的是:#elifdefinedSYSCLK_FREQ_72MHz SetSysClockTo72();为何执行该行呢,找到SYSCLK_PREQ_**的相关定义,如下图所示。
这样就得到了我们所要的一个结论:如果要更改系统工作频率,只需要在这里更改就可以了。
可以继续跟踪进入这个函数来观察如何将工作频率设定为72MHz的。
staticvoidSetSysClockTo72(void){ __IOuint32_tStartUpCounter=
0,HSEStatus=0; /*SYSCLK,HCLK,PCLK2andPCLK1configuration--------------------------*/ /*EnableHSE*/RCC->CR|=((uint32_t)RCC_CR_HSEON);//开启HSE/*WaittillHSEisreadyandifTimeoutisreachedexit*/do{ HSEStatus=RCC->CR&RCC_CR_HSERDY;StartUpCounter++;}while((HSEStatus==0)&&(StartUpCounter!
=HSEStartUp_TimeOut));//等待HSE确实可用,这有个标志,即RCC_CR寄存器中的HSERDY位(bit17),这个等待不会无限长,有个超时策略,即每循环一次计数器加
1,如果计数的次数超过HSEStartUp_TimeOut,就退出循环,而这个HSEStartUp_TimeOut在stm32f10x.h中定义,#defineHSEStartUp_TimeOut((uint16_t)0x0500)/*!
CR&RCC_CR_HSERDY)!
=RESET) {HSEStatus=(uint32_t)0x01; }else{ HSEStatus=(uint32_t)0x00;}///再次判断HSERDY标志位,并据此给HSEStatus变量赋值。
if(HSEStatus==(uint32_t)0x01){ /*EnablePrefetchBuffer*/FLASH->ACR|=FLASH_ACR_PRFTBE; /*Flash2waitstate*/FLASH->ACR&=(uint32_t)((uint32_t)~FLASH_ACR_LATENCY);FLASH->ACR|=(uint32_t)FLASH_ACR_LATENCY_2; /*HCLK=SYSCLK*/ RCC->CFGR|=(uint32_t)RCC_CFGR_HPRE_DIV1; //找到定义:#defineRCC_CFGR_HPRE_DIV1 _t)0x00000000) /*!
CFGR|=(uint32_t)RCC_CFGR_PPRE2_DIV1; //找到定义:#defineRCC_CFGR_PPRE2_DIV1 0x00000000) /*!
CFGR|=(uint32_t)RCC_CFGR_PPRE1_DIV2; //找到定义:#defineRCC_CFGR_PPRE1_DIV2 x00000400) /*!
0 #ifdefSTM32F10X_CL…… #else/*PLLconfiguration:PLLCLK=HSE*9=72MHz*/ RCC->CFGR&=(uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC|RCC_CFGR_PLLXTPRE| RCC_CFGR_PLLMULL));RCC->CFGR|=(uint32_t)(RCC_CFGR_PLLSRC_HSE|RCC_CFGR_PLLMULL9);#endif/*STM32F10X_CL*///以上是设定PLL的倍频系数为
9,也就是说,这个72M是在外部晶振为8M时得到的。
/*EnablePLL*/RCC->CR|=RCC_CR_PLLON; /*WaittillPLLisready*/while((RCC->CR&RCC_CR_PLLRDY)==0){} /*SelectPLLassystemclocksource*/RCC->CFGR&=(uint32_t)((uint32_t)~(RCC_CFGR_SW));RCC->CFGR|=(uint32_t)RCC_CFGR_SW_PLL;/*WaittillPLLisusedassystemclocksource*/while((RCC->CFGR&(uint32_t)RCC_CFGR_SWS)!
=(uint32_t)0x08){}}else{/*IfHSEfailstostart-up,theapplicationwillhavewrongclock configuration.Usercanaddheresomecodetodealwiththiserror*/ /*Gotoinfiniteloop*/while
(1){}}}至此,我们可以归纳几条:
(1)时钟源有3个
(2)开机时默认是HSI起作用,可以配置为所要求的任意一个时钟
(3)配置时必须按一定的顺序来打开或都关闭一些位,并且各时钟起作用有 一定的时间,因此要利用芯片内部的标志位来判断是否可以执行下一步。

(4)如果外部时钟、PLL输出失效,系统可以自动回复到HSI(开启时钟安全系统)
(5)HSI的频率准确度可以达到+/-1%,如果有必要时,还可以用程序来调整这个频率,可调的范围大致在200KHz左右。
最后让我们来感受一下劳动的果实吧--试着改改频率看有何反应。
为查看更改后的效果,先记录更改前的数据。
将调试切换到仿真,在第一条:Delay(0xAFFFF);指令执行前后,分别记录下Status和SecStatus:25073606995Sec:0.000227490.05028982将振荡频率更改为36MHz,即...#defineSYSCLK_FREQ_36MHz36000000//去掉该行的注释 /*#defineSYSCLK_FREQ_48MHz48000000*//*#defineSYSCLK_FREQ_56MHz56000000*//*#defineSYSCLK_FREQ_72MHz72000000*///将该行加上注释再次运行,结果如下:Status:25063606994Sec:0.000084780.10036276基本上是延时时间长了一倍。
改成硬件仿真,将代码写入板子,可以看到LED闪烁的频率明显变慢了。
STM32学习笔记
(6)-I/O的简单研究 前面的例子研究了时钟,接下来就来了解一下引脚的情况Main.c中,有关I/O口的配置代码如下:voidGPIO_Configuration(void){ GPIO_InitTypeDefGPIO_InitStructure; /*ConfigureIOconnectedtoLD1,LD2,LD3andLD4leds*********************/ GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOD,&GPIO_InitStructure); 这几行代码是将GPIOD的第8,9,10和11引脚配置成输出,并且还可以设定输出引脚的速度(驱动能力?),这里设定为50MHz,这应该是常用的,还有 可以设置为2MHz的。
那么如何将引脚设置成输入呢?查看电路原理图,GPIOD.0~GPIO.4是接一个摇杆的5个按钮的,因此,下面尝试着将它们设置成为输入端。
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOD,&GPIO_InitStructure);第1行和第3行完全是照抄,第2行那个GPIO_Mode_IN_FLOATING是在stm32f10x_gpio.h中找到的。
当然是因为这里还有GPIO_Mode_Out_PP,所以猜测应该是它了。
至于还有其他那么多的符号就不管了。
定义完成,编译完全通过,那就接下来准备完成下面的代码了。
intmain(void){ Init_All_Periph();while
(1){if(GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_0))//1 {GPIO_ResetBits(GPIOD,GPIO_Pin_8);}else{/*TurnonLD1*/ GPIO_SetBits(GPIOD,GPIO_Pin_8);/*Insertdelay*/}......标号为1的行显然其作用是判断GPIOD.0引脚是0还是
1。
这个函数是在stm32f10x_gpio.c中找到的。
uint8_tGPIO_ReadInputDataBit(GPIO_TypeDef*GPIOx,uint16_tGPIO_Pin) {uint8_tbitstatus=0x00; /*Checktheparameters*/assert_param(IS_GPIO_ALL_PERIPH(GPIOx));assert_param(IS_GET_GPIO_PIN(GPIO_Pin)); if((GPIOx->IDR&GPIO_Pin)!
=(uint32_t)Bit_RESET){ bitstatus=(uint8_t)Bit_SET;}else{ bitstatus=(uint8_t)Bit_RESET;}returnbitstatus;}虽然程序还有很多符号看不懂(没有去查),但凭感觉它应该是对某一个引脚的状态进行判断,因为这个函数的类型是uint8_t,估计stm32没有bit型函数(需要验证),所以就用了uint8_t型了),如果是读的端口的值,应该用uint16_t型。
这一点在下面也可以得到部分的验证:uint16_tGPIO_ReadInputData(GPIO_TypeDef*GPIOx)uint16_tGPIO_ReadOutputData(GPIO_TypeDef*GPIOx)这些函数是读引脚及输出寄存器的数据的。
再次编译,也是顺利通过,依法炮制,将其他三个引脚输入控制LED的代码也写上,为保险起见,先用软件仿真,免得反复擦写FLASH(顺便说一句,目前还没有搞定将代码写入RAM及从RAM中执行,惭愧) 进入仿真后打开外围部件接口,单步执行,果然如同设想那样运作了,单击Pins0后面的勾,再次运行,果然PIN8后面的勾没了。
做到这里,就感觉到用keil的好处了,这块熟啊,几乎没有花时间在上面,一用就成了。
至此,按我的习惯,要翻开STM32F的数据手册,研究一下其IO端口了。
下面是数据手册中的一段话: ------------------------------------每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR,GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。
根据数据手册中列出的每个I/O端口的特定硬件特征,GPIO端口的每个位可以由软件分别配置成多种模式。
─输入浮空─输入上拉─输入下拉─模拟输入─开漏输出─推挽式输出─推挽式复用功能─开漏复用功能---------------------------------------------------当然,数据手册上关于IO端口的描述是很多很多的,我也只是大概地了解了一下,真正要设计产品时,肯定还要细看。
但至少,知道了IO端口复位后处于浮空状态,也就是其电平状态由外围电路决定,这很重要,如果设计工业品的话,这是必须要确定的;知道了IO引脚可以兼容5V电源;知道了在什么地方可以找到这些引脚在库中的定义而不必看着数据手册去控制那些位;也知道了这 些引脚的一些基本操作函数(连猜带蒙带测试应该可以搞定大部分功能),那么我心里基本就有底啦。

标签: #培训学校 #自动保存 #聊天记录 #单位 #改名字 #安全员 #车牌 #挂在