1实验一用gpio口点亮发光二极管.docx
《1实验一用gpio口点亮发光二极管.docx》由会员分享,可在线阅读,更多相关《1实验一用gpio口点亮发光二极管.docx(25页珍藏版)》请在冰豆网上搜索。
1实验一用gpio口点亮发光二极管
实验一、使用stm32f10x单片机点亮核心板上的led
电路原理
电路的基本原理:
使用核心板上的PB口6-9引脚分别点亮L1~L4四个发光二极管。
实验需要解决的关键问题:
1、使用KeilMDK软件设计的模板问题;
2、STM32处理器GPIO(通用输入/输出接口)的配置问题;
3、系统及片上外设的时钟配置问题。
解决第一个问题:
使用KeilMDK软件设计的模板问题;
在新建工程模板之前,首先需要获取到st库的源码,源码可从st的官方网站下载到。
在STM32-Template文件夹下,我们新建六个文件夹,分别为Libraries、CMSIS、Output、Listing、Doc和Project。
USER用来存放工程文件和用户代码,包括主函数main.c,用户文件及其头文件(*.H)。
Libraries中包含两个folder,oneisFWlibandtheotherisCMSIS.FWlib用来存放STM32库里面的inc和src这两个文件,这两个文件包含了芯片上的所有驱动。
CMSIS用来存放库为我们自带的启动文件和一些M3系列通用的文件。
CMSIS里面存放的文件适合任何M3内核的单片机。
CMSIS的缩写为:
CortexMicrocontrollerSoftwareInterfaceStandard,是ARMCortex微控制器软件接口标准,是ARM公司为芯片厂商提供的一套通用的且独立于芯片厂商的处理器软件接口。
Output用来保存软件编译后输出的文件,Listing用来存放一些编译过程中产生的文件,具体可不用了解。
Project用来存放MDK工程文件。
Doc用来保存该文件的说明文档。
FWlib:
\3.5.0\3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver的inc跟src这两个文件夹拷贝到STM32-Template\FWlib文件夹中。
USER:
\3.5.0\3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template下的main.c(主函数或测试函数)、stm32f10x_conf.h、stm32f10x_it.h、stm32f10x_it.c、system_stm32f10x.c拷贝到STM32-Template\USER目录下。
stm32f10x_it.h、和stm32f10x_it.c这两个文件里面是中断函数,里面为空,并没有写任何的中断服务程序。
stm32f10x_conf.h是用户需要配置的头文件,当我们需要用到芯片中的某部分外设的驱动时,我们只需要在该文件下将该驱动的头文件包含进来即可,片上外设的驱动在src文件夹中,inc文件夹里面是它们的头文件。
这三个文件是用户在编程时需要修改的文件,其他库文件一般不需要修改。
system_stm32f10x.c:
ARM提供的符合CMSIS标准的库文件,主要用来设置系统时钟和总线时钟。
系统时钟设置还需要用到锁相环PLL,这就要用到操作寄存器,而寄存器都是以存储器映射的方式访问,所以在system_stm32f10x.c文件中应包含stm32f10x.h文件。
\3.5.0\3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm的全部文件拷贝到STM32-Template\CMSIS\startup(需先在CMSIS新建好startup文件夹)文件夹下。
这些是用汇编写的启动文件。
\3.5.0\3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport的core_cm3.c和core_cm3.h也拷贝到STM32-Template\CMSIS文件夹下。
\3.5.0\3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x的stm32f10x.h、system_stm32f10x.c、system_stm32f10x.h拷贝到STM32-Template\CMSIS文件夹下。
此时我们新进的工程目录如下所示
在STM32-DEMO(或工程名)上右键选中AddGroup…选项,新建四个组,分别命名为STARTUP、USER、FWlib、CMSIS和DOC。
STARTUP从名字就可以看得出我们是用它来放我们的启动代码的,USER用来存放用户自定义的应用程序,FWlib用来存放库文件,CMSIS用来存放M3系列单片机通用的文件,DOC用来存放程序的相关说明文档。
接下来我们往我们这些新建的组中添加文件,双击哪个组就可以往哪个组里面添加文件。
我们在STARTUP里面添加startup_stm32f10x_hd.s,在USER组里面添加main.c和stm32f10x_it.c这两个文件,在FWlib组里面添加src里面的全部驱动文件,当然,src里面的驱动文件也可以需要哪个就添加哪个。
这里将它们全部添加进去是为了后续开发的方便,况且我们可以通过配置stm32f10x_conf.h这个头文件来选择性添加,只有在stm32f10x_conf.h文件中配置的文件才会被编译。
在CMSIS里面添加core_cm3.c和system_stm32f10x.c文件。
注意,这些组里面添加的都是汇编文件跟C文件,头文件是不需要添加的。
最终效果如下图:
解决第二个问题:
STM32处理器GPIO(通用输入/输出接口)的配置问题;
作为大家的第一个STM32例程,很有必要进行足够深入的分析,才能从根本上扫清对使用库函数的困惑。
而且,只要利用这个LED例程,真正领会了库开发的流程以及原理,再进行其它外设的开发就变得简单一些了。
所以,本部分的任务是:
●从STM32库的实现原理上解答:
库到底是什么?
、为什么要用库?
、用库与直接配置寄存器的区别等问题。
●让我们具体了解利用库的开发流程,熟悉库函数的结构,达到举一反三的效果。
2.1STM32的GPIO
想要控制LED灯,当然是通过控制STM32芯片的I/O引脚电平的高低来实现。
在STM32芯片上,I/O引脚可以被软件设置成各种不同的功能(具体说明见下节Polling按键,这节课),如输入或输出,所以被称为GPIO(General-purposeI/O)。
而GPIO引脚又被分为GPIOA、GPIOB,…,GPIOG不同的组,每组端口分为0~15,共16个不同的引脚,对于不同型号的芯片,端口的组和引脚的数量不同,具体请参考相应芯片型号的datasheet。
于是,控制LED的步骤就自然整理出来了:
1.GPIO端口引脚多-->就要选定需要控制的特定引脚;
2.GPIO功能如此丰富-->配置需要的特定功能;
3.控制LED的亮和灭-->设置GPIO输出电平的高低。
继续思考,要控制GPIO端口,就要涉及到控制相关的寄存器。
这时我们就要查一查与GPIO相关的寄存器了,可以通过《STM32F103XXX参考手册》来查看,见下图
图中的7个寄存器,相应的功能在文档上有详细的说明。
可以分为以下4类,其功能简要概括如下:
1.配置寄存器:
选定GPIO的特定功能,最基本的如:
选择作为输入还是输出端口。
2.数据寄存器:
保存了GPIO的输入电平或将要输出的电平。
3.位控制寄存器:
设置某引脚的数据为1或0,控制输出的电平。
4.锁定寄存器:
设置某锁定引脚后,就不能修改其配置。
注:
要想知道其功能严谨、详细的描述,请养成习惯在正式使用时,以官方的datasheet为准,在这里只是简单地概括其功能进行说明。
关于寄存器名称上标号x的意义,如:
GPIOx_CRL、GPIOx_CRH,这个x的取值可以为图中括号内的值(A,…,G),表示这些寄存器也跟GPIO一样,也是分组的。
也就是说,对于端口GPIOA和GPIOB,它们都有互不相干的一组寄存器,如控制GPIOA的寄存器名为GPIOA_CRL、GPIOA_CRH等,而控制GPIOB的则是不同的、被命名为GPIOB_CRL、GPIOB_CRH等寄存器。
从这个图我们可以知道STM32的功能,实际上也是通过配置寄存器来实现的(但又和直接配置寄存器有区别,一个是直接访问,一个为使用库函数访问)。
2.2STM32的地址映射
温故而知新——stm32f10x.h文件。
首先请大家回顾一下在51单片机上点亮LED是怎样实现的。
这太简单了,几行代码就搞定。
###########################################
1.#include
2.intmain(void)
3.{
4.P0=0;
5.while
(1);
6.}
#########################################
以上代码就可以点亮P0端口与LED阴极相连的LED灯了,当然,这里省略了启动代码。
为什么这个P0=0;句子就能控制P0端口为低电平?
很多刚入门51单片机的同学还真解释不来,关键之处在于这个代码所包含的头文件。
在这个文件下有以下的定义:
这些定义被称为地址映射。
所谓地址映射,就是将芯片上的存储器甚至I/O等资源与地址建立一一对应的关系。
如果某地址对应着某寄存器,我们就可以运用C语言的指针来寻址并修改这个地址上的内容,从而实现修改该寄存器的内容。
正是因为头文件中有了对于各种寄存器和I/O端口的地址映射,我们才可以在51单片机程序中方便地使用P0=0xFF;TMOD=0xFF等赋值句子对寄存器进行配置,从而控制单片机。
Cortex-M3的地址映射也是类似的。
Cortex-M3有32根地址线,所以它的寻址空间大小为2^32bit=4GB。
ARM公司设计时,预先把这4GB的寻址空间大致地分配好了。
它把地址从0x40000000至0x5FFFFFFF(512MB)的地址分配给片上外设。
通过把片上外设的寄存器映射到这个地址区,就可以简单地以访问内存的方式,访问这些外设的寄存器,从而控制外设的工作。
结果,片上外设可以使用C语言来操作。
M3存储器映射见下图。
stm32f10x.h这个文件中重要的内容就是把STM32的所有寄存器进行地址映射。
如同51单片机的头文件一样,stm32f10x.h像一个大表格,我们在使用的时候就是通过宏定义进行类似查表的操作,大家想象一下没有这个文件的话,我们要怎样访问STM32的寄存器?
有什么缺点?
不进行这些宏定义的缺点有:
1、地址容易写错;
2、我们需要查大量的手册来确定哪个地址对应哪个寄存器;
3、看起来还不好看,且容易造成编程的错误,效率低,影响开发进度。
当然,这些工作都是由ST的固件工程师来完成的,只有设计M3的人才是最了解M3的,才能写出完美的库。
在这里我们以外接了LED灯的外设GPIOB为例,在这个文件中有这样的一系列宏定义:
#############################################
#defineGPIOB_BASE(APB2PERIPH_BASE+0x0C00)
#defineGPIOC_BASE(APB2PERIPH_BASE+0x1000)
#defineAPB1PERIPH_BASEPERIPH_BASE
#defineAPB2PERIPH_BASE(PERIPH_BASE+0x10000)
#defineAHBPERIPH_BASE(PERIPH_BASE+0x20000)
#defineFLASH_BASE((uint32_t)0x08000000)/*!
#defineSRAM_BASE((uint32_t)0x20000000)/*!
#definePERIPH_BASE((uint32_t)0x40000000)/*!
############################################################
这几个宏定义是从文件中的几个部分抽离出来的,具体的读者可参考stm32f10x.h源码。
外设基地址
首先看到PERIPH_BASE这个宏,宏展开为0x40000000,并把它强制转换为uint32_t的32位类型数据,这是因为地STM32的地址是32位的,是不是觉得0x40000000这个地址很熟?
是的,这个是Cortex-M3核分配给片上外设的从0x40000000至0x5FFFFFFF的512MB寻址空间中的第一个地址,我们把0x40000000称为外设基地址。
总线基地址
接下来是宏APB2PERIPH_BASE,宏展开为PERIPH_BASE(外设基地址)加上偏移地址0x10000,即指向的地址为0x40010000。
这个APB2PERIPH_BASE宏是什么地址呢?
STM32不同的外设是挂载在不同的总线上的,见图5-8。
有AHB总线、APB2总线、APB1总线,挂载在这些总线上的外设有特定的地址范围。
其中像GPIO、串口1、ADC及部分定时器是挂载这个被称为APB2的总线上,挂载到APB2总线上的外设地址空间是从0x40010000至地址0x40013FFF。
这里的第一个地址,也就是0x40010000,被称为APB2PERIPH_BASE(APB2总线外设的基地址)。
而APB2总线基地址相对于外设基地址的偏移量为0x10000个地址,即为APB2相对外设基地址的偏移地址。
见表。
地址范围
总线
总线基地址
总线基地址相对外设基地址
(0x4000000)的偏移量
0x40018000-0x5003FFFF
AHB
0x40018000
0x18000
0x40010000-0x40017FFF
APB2
0x40010000
0x10000
0x40000000-0x4000FFFF
APB1
0x40000000
0x00000
由这个表我们可以知道,stm32f10x.h这个文件中必然还有以下的宏:
###########################
#defineAPB1PERIPH_BASEPERIPH_BASE
因为偏移量为零,所以APB1的地址直接就等于外设基地址。
寄存器组基地址
最后到了宏GPIOC_BASE,宏展开为APB2PERIPH_BASE(APB2总线外设的基地址)加上相对APB2总线基地址的偏移量0x1000得到了GPIOC端口的寄存器组的基地址。
这个所谓的寄存器组又是什么呢?
它包括什么寄存器?
细看stm32f10x.h文件,我们还可以发现以下类似的宏:
#######################################
#defineGPIOA_BASE(APB2PERIPH_BASE+0x0800)
#defineGPIOB_BASE(APB2PERIPH_BASE+0x0C00)
#defineGPIOC_BASE(APB2PERIPH_BASE+0x1000)
#defineGPIOD_BASE(APB2PERIPH_BASE+0x1400)
#defineGPIOE_BASE(APB2PERIPH_BASE+0x1800)
#defineGPIOF_BASE(APB2PERIPH_BASE+0x1C00)
#defineGPIOG_BASE(APB2PERIPH_BASE+0x2000)
#######################################
2.3STM32库对寄存器的封装
一个陌生的类型GPIO_TypeDef,追踪它的定义,可以在stm32f10x.h文件中找到如下代码:
typedefstruct
{
__IOuint32_tCRL;
__IOuint32_tCRH;
__IOuint32_tIDR;
__IOuint32_tODR;
__IOuint32_tBSRR;
__IOuint32_tBRR;
__IOuint32_tLCKR;
}GPIO_TypeDef;
其中__IO也是一个ST库定义的宏,宏定义如下:
1.#define__Ovolatile/*!
2.#define__IOvolatile/*!
volatitle是C语言的一个关键字,有关volatitle的用法可查阅相关的C语言书籍。
回到GPIO_TypeDef这段代码,这个代码用typedef关键字声明了名为GPIO_TypeDef的结构体类型,结构体内又定义了7个__IOuint32_t类型的变量。
这些变量每个都为32位,也就是每个变量占内存空间4个字节。
在c语言中,结构体内变量的存储空间是连续的,也就是说假如我们定义了一个GPIO_TypeDef,这个结构体的首地址(变量CRL的地址)若为0x40011000,那么结构体中第二个变量(CRH)的地址即为0x40011000+0x04,加上的这个0x04,正是代表4个字节地址的偏移量。
细心的读者会发现,这个0x04偏移量,正是GPIOx_CRH寄存器相对于所在寄存器组的偏移地址,见图5-9。
同理,GPIO_TypeDef结构体内其它变量的偏移量,也和相应的寄存器偏移地址相符。
于是,只要我们匹配了结构体的首地址,就可以确定各寄存器的具体地址了。
有了这些准备,就可以分析本小节的第一段代码了:
4.#defineGPIOA((GPIO_TypeDef*)GPIOA_BASE)
5.#defineGPIOB((GPIO_TypeDef*)GPIOB_BASE)
6.#defineGPIOC((GPIO_TypeDef*)GPIOC_BASE)
GPIOA_BASE在上一小节已解析,是一个代表GPIOA组寄存器的基地址。
(GPIO_TypeDef*)在这里的作用则是把GPIOA_BASE地址转换为GPIO_TypeDef结构体指针类型。
有了这样的宏,以后我们写代码的时候,如果要修改GPIO的寄存器,就可以用以下的方式来实现。
代码分析见注释。
1.GPIO_TypeDef*GPIOx;//定义一个GPIO_TypeDef型结构体指针GPIOx
2.GPIOx=GPIOA;//把指针地址设置为宏GPIOA地址
3.GPIOx->CRL=0xffffffff;//通过指针访问并修改GPIOA_CRL寄存器
通过类似的方式,我们就可以给具体的寄存器写上适当的参数,控制STM32了。
是不是觉得很巧妙?
但这只是库开发的皮毛,而且实际上我们并不是这样使用库的,库为我们提供了更简单的开发方式。
M3的库可谓尽情绽放了c的魅力,如果你是单片机初学者,c语言初学者,那么请你不要放弃与M3库邂逅的机会。
是否选择库,就差你一个闪亮的回眸。
解决第三个问题:
系统及片上外设的时钟配置问题;
STM32的时钟系统
STM32芯片为了实现低功耗,设计了一个功能完善但却非常复杂的时钟系统。
普通的MCU,一般只要配置好GPIO的寄存器,就可以使用了,但STM32还有一个步骤,就是开启外设时钟。
为了正确配置STM32芯片的时钟,首先,从整体上了解STM32的时钟系统(时钟树)
图1时钟树
GPIO外设是挂载在APB2总线上的,APB2的时钟是APB2预分频器的输出,而APB2预分频器的时钟来源是AHB预分频器。
因此,把APB2预分频器设置为不分频,那么我们就可以得到GPIO外设的时钟也等于HCLK,为72MHz了。
配置系统时钟(目的是为了开启外设时钟为GPIO提供时钟信号)
调用了GPIO_Init()函数之后,对GPIO的初始化也就基本完成了,那还缺少什么呢?
就是在前面强调过的必须要开启外设时钟,在开启外设时钟之前,我们首先要配置好系统时钟SYSCLK,0小节提到,为配置SYSCLK,要设置一系列的时钟来源、倍频、分频等控制参数。
这些工作由SystemInit()库函数完成。
启动文件及SystemInit()函数分析
在startup_stm32f10x_hd.s启动文件中,有如下一段启动代码:
************************************
Reset_HandlerPROC
EXPORTReset_Handler[WEAK]
IMPORT__main
IMPORTSystemInit
LDRR0,=SystemInit
BLXR0
LDRR0,=__main
BXR0
ENDP
************************************
当芯片被复位(包括上电复位)的时候,将开始运行这一段代码,运行过程为先调用了SystemInit()函数,再进入c语言中的main函数执行。
进入main函数之前调用了一个名为SystemInit()的函数。
这个函数的定义在system_stm32f10x.c文件之中。
它的作用是设置系统时钟SYSCLK。
函数的执行流程是先将与配置时钟相关的寄存器都复位为默认值,复位寄存器后,调用了另外一个函数SetSysClock()。
******************************************************
voidSystemInit(void)
{
/*ResettheRCCclockconfigurationtothedefaultresetstate(fordebugpurpose)*/
/*SetHSIONbit*/
RCC->CR|=(uint32_t)0x00000001;
…………
SetSysClock();
…………
}
******************************************************
SetSysClock()代码如下:
********************************************
staticvoidSetSysClock(void)
{
#ifdefSYSCLK_FREQ_HSE
SetSysClockToHSE();