实验一用gpio口点亮发光二极管.docx
- 文档编号:28067617
- 上传时间:2023-07-08
- 格式:DOCX
- 页数:29
- 大小:1.34MB
实验一用gpio口点亮发光二极管.docx
《实验一用gpio口点亮发光二极管.docx》由会员分享,可在线阅读,更多相关《实验一用gpio口点亮发光二极管.docx(29页珍藏版)》请在冰豆网上搜索。
实验一用gpio口点亮发光二极管
实验一、使用stm32单片机点亮核心板上的led
硬件电路
图1LED硬件电路:
使用核心板上的PB口6-9引脚分别点亮L1~L4四个发光二极管。
实验需要解决的关键问题:
1、使用KeilMDK软件设计的模板问题;
2、STM32处理器GPIO(通用输入/输出接口)的配置问题;
3、系统及片上外设的时钟配置问题。
解决第一个问题:
使用KeilMDK软件设计的模板及工程配置问题
在新建工程模板之前,首先需要获取到ST库源码,源码可从ST的官方网站下载到。
在STM32-Template文件夹下,新建六个文件夹,分别为libraries、user、output、listing、doc和project。
user用来存放工程文件和用户代码,包括主函数(测试函数)main.c,用户文件及其头文件(*.H)。
libraries中包含两个文件夹:
FWlib、CMSIS。
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\Libraries\CMSIS(需先在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文件,头文件是不需要添加的。
最终效果如下图:
至此,我们的工程已经基本建好,但是还有一些工作需要完成。
下面来配置一下MDK的配置选项,点击工具栏中的魔术棒按钮。
1、在Target选项卡中,把微库的勾打上,这样是为了后面的串口例程可以使用printf这个函数。
2、Output选项卡如下设置,点击SelectFolderforObjects...设置编译后输出文件保存的位置。
同时把DebugInformation、CreateHEXFile和Browseinformation这三个选项框也选上。
3、在Listing这个选项卡中,点击SelectFolderlistings…定位到模板中的Listing文件夹。
4、在C/C++这个选项卡上需要设置的比较多。
在Define里面输入添加STM32F10X_HD,USE_STDPERIPH_DRIVER这两个宏。
添加USE_STDPERIPH_DRIVER是为了屏蔽编译器的默认搜索路径,转而使用我们添加到工程中的ST的库,添加STM32F10X_HD是因为我们用的芯片是大容量的,添加了STM32F10X_HD这个宏之后,库文件里面为大容量定义的寄存器我们就可以用了。
芯片是小或中容量的时候宏要换成STM32F10X_LD或者STM32F10X_MD。
其实不管是什么容量的,我们只要添加上STM32F10X_HD这个宏即可,当你用小或者中容量的芯片时,那些为大容量定义的寄存器我不去访问就是了,反正也访问不了。
在IncludePaths栏里添加库文件的搜索路径,这样就可以屏蔽掉默认的搜索路径。
但当编译器在我们指定的路径下搜索不到的话还是会回到标准目录去搜索,就像有些ANSICC的库文件,如stdin.h、stdio.h。
库文件路径修改成功之后如下所示:
5、修改main.c文件。
至此,我们的工程模板就建成了。
学会新建工程,是学习stm32的第一步。
这个工程默认的是软件仿真,如果开发板要用J-LINK调试的话,还需要在开发环境中做如下修改。
实际上,我们开发程序的时候90%都是在硬件上调试的。
在选项卡Debug\Setting\Flashdownload中我们设置成如下:
到了这里就算是大功告成了。
如果在新建工程中遇到什么问题,先不要急,可先参考提供的建好的工程模板。
第二个问题:
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端口为低电平?
关键之处在于这个代码所包含的头文件
在这个文件下有以下的定义:
这些定义被称为地址映射。
所谓地址映射,就是将芯片上的存储器甚至I/O等资源与地址建立一一对应的关系。
如果某地址对应着某寄存器,我们就可以运用C语言的指针来寻址并修改这个地址上的内容,从而实现修改该寄存器的内容。
正是因为
图2Cortex-M3地址映射
Cortex-M3的地址映射也是类似的。
Cortex-M3有32根地址线,所以它的寻址空间大小为232=4GB。
ARM公司设计时,预先把这4GB的寻址空间大致地分配好了。
它把地址从0x40000000至0x5FFFFFFF(512MB)的地址分配给片上外设。
通过把片上外设的寄存器映射到这个地址区,就可以简单地以访问内存的方式,访问这些外设的寄存器,从而控制外设的工作。
结果,片上外设可以使用C语言来操作。
M3存储器映射见图2。
stm32f10x.h这个文件中重要的内容就是把STM32的所有寄存器进行地址映射。
如同51单片机的
有什么缺点?
不进行这些宏定义的缺点有:
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不同的外设是挂载在不同的总线上的,见图3。 有AHB总线、APB2总线、APB1总线,挂载在这些总线上的外设有特定的地址范围。 图3存储器和总线架构图 其中像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寄存器相对于所在寄存器组的偏移地址,见下图。 同理,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了。 是不是觉得很巧妙? 但这只是库开发的皮毛,而且实际上我们并不是这样使用库的,库为我们提供了更简单的开发方式。 第三个问题: 系统及片上外设的时钟配置问题; 3.1STM32的时钟系统 STM32芯片为了实现低功耗,设计了一个功能完善但却非常复杂的时钟系统。 普通的MCU,一般只要配置好GPIO寄存器,就可以使用了,但STM32还有一个步骤,就是开启外设时钟。 为了正确配置STM32芯片的时钟,首先,从整体上了解STM32的时钟系统(时钟树) 图4时钟树 GPIO外设挂载在APB2总线上,APB2时钟是APB2预分频器的输出,而APB2预分频器的时钟来源是AHB预分频器。 因此,把APB2预分频器设置为不分频(预分频系数为1),就可以得到GPIO外设的时钟也等于HCLK,为72MHz了。 3.2配置系统时钟(目的是为了开启外设时钟为GPIO提供时钟信号) 调用了GPIO_Init()函数之后,对GPIO的初始化也就基本完成了,那还缺少什么呢? 就是在前面强调过的必须要开启外设时钟,在开启外设时钟之前,我们首先要配置好系统时钟SYSCLK,为配置SYSCLK,要设置一系列的时钟来源、倍频、分频等控制参数。 这些工作由SystemInit()库函数完成。 启动文件及SystemInit()函数分析 在startup_stm32f10x_hd.s启动文件中,有如下一段启动
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 实验 gpio 点亮 发光二极管
![提示](https://static.bdocx.com/images/bang_tan.gif)