stm32笔记 -

更新时间:2023-10-31 08:28:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

? 次元立方 IT资讯 | 编程 | 数据库 | 安全 | 系统 | 服务器 | 嵌入式 | 平面设计 | 网页设计 | 基础 | 组网 | QQ | 下载 | JS特效 | 嵌入式 首页 > 嵌入式 > 单片机 > 正文 嵌入式 http://www.it165.net/embed 零死角玩转stm32-初级篇之流水灯的前后今生 作者:tyzhgf 发布日期:2013-07-08 13:20:38 我来说两句(0)收藏本文 ? 作为大家的第一个STM32例程,野火认为很有必要进行足够深入的分析,才能从根本上扫清读者对使用库函数的困惑。而且,只要读者利用这个LED例程,真正领会了库开发的流程以及原理,再进行其它外设的开发就变得相当简单了。 所以本章的任务是: 从STM32库的实现原理上解答库到底是什么、为什么要用库、用库与直接配置寄存器的区别等问题。 让读者了解具体利用库的开发流程,熟悉库函数的结构,达到举一反三的效果,这次可就不是喝稀粥了,保证有吃干饭,所学就是所用的效果。 5.1 STM32的GPIO 想要控制LED灯,当然是通过控制STM32芯片的I/O引脚电平的高低来实现。在STM32芯片上,I/O引脚可以被软件设置成各种不同的功能,如输入或输出,所以被称为GPIO (General-purpose I/O)。而GPIO引脚又被分为GPIOA、GPIOB……GPIOG不同的组,每组端口分为0~15,共16个不同的引脚,对于不同型号的芯片,端口的组和引脚的数量不同,具体请参考相应芯片型号的datasheet。 www.it165.net

于是,控制LED的步骤就自然整理出来了: GPIO端口引脚多 -->就要选定需要控制的特定引脚 GPIO功能如此丰富 -->配置需要的特定功能 控制LED的亮和灭 -->设置GPIO输出电压的高低

继续思考,要控制GPIO端口,就要涉及到控制相关的寄存器。这时我们就要查一查与GPIO相关的寄存器了,可以通过《STM32参考手册》来查看,见图 5-1

图 5-1

图中的7个寄存器,相应的功能在文档上有详细的说明。可以分为以下4类,其功能简要概括如下:

配置寄存器:选定GPIO的特定功能,最基本的如:选择作为输入还是输出端口。 数据寄存器:保存了GPIO的输入电平或将要输出的电平。 位控制寄存器:设置某引脚的数据为1或0,控制输出的电平。 锁定寄存器:设置某锁定引脚后,就不能修改其配置。

注:要想知道其功能严谨、详细的描述,请读者养成习惯在正式使用时,要以官方的datasheet为准,在这里只是简单地概括其功能进行说明。

关于寄存器名称上标号x 的意义,如:GPIOx_CRL、GPIOx_CRH ,这个x的取值可以为图中括号内的值(A……E),表示这些寄存器也跟GPIO一样,也是分组的。也就是说,对于端口GPIOA和GPIOB,它们都有互不相干的一组寄存器,如控制GPIOA的寄存器名

为GPIOA_CRL、GPIOA_CRH等,而控制GPIOB的则是不同的、被命名为GPIOB_CRL、GPIOB_CRH等寄存器。

我们的程序代码以野火STM32第二代开发板为例,根据其硬件连接图来分析,见图 5-2及图 5-3错误!未找到引用源。

图 5-2 图 5-3

从这个图我们可以知道STM32的功能,实际上也是通过配置寄存器来实现的。配置寄存器的具体参数,需要参考《STM32参考手册》的寄存器说明。见图 5-4。

图 5-4

如图,对于GPIO端口,每个端口有16个引脚,每个引脚的模式由寄存器的4个位控制,每四位又分为两位控制引脚配置(CNFy[1:0]),两位控制引脚的模式及最高速度(MODEy[1:0]),其中y表示第y个引脚。这个图是GPIOx_CRH寄存器的说明,配置GPIO引脚模式的一共有两个寄存器,CRH是高寄存器,用来配置高8位引脚:pin8~pin15。还有一个称为CRL寄存器,如果我们要配置pin0~pin7引脚,则要在寄存器CRL中进行配置。

举例说明对CRH的寄存器的配置:当给GPIOx_CRH寄存器的第28至29位设置为参数“11”,并在第30至31位设置为参数“00”,则把x端口第15个引脚的模式配置成了“输出的最大速度为50MHz的通用推挽输出模式、”,其它引脚可通过其GPIOx_CRH或GPIOx_CRL的其它寄存器位来配置。至于x端口的x是指端口GPIOA还是GPIOB还要具体到不同的寄存器基址,这将在后面分析。

接下来分析要控制引脚电平高低,需要对寄存器进行什么具体的操作。见图 5-5。

图 5-5

由寄存器说明图可知,一个引脚y的输出数据由GPIOx_BSRR寄存器位的2个位来控制分别为BRy (Bit Reset y)和BSy (Bit Set y),BRy位用于写1清零,使引脚输出低电平,BSy位用来写1置1,使引脚输出高电平。而对这两个位进行写零都是无效的。(还可以通过设置寄存器ODR来控制引脚的输出。)

例如:对x端口的寄存器GPIOx_BSRR的第0位(BS0) 进行写1,则x端口的第0引脚被设置为1,输出高电平,若要令第0引脚再输出低电平,则需要向GPIOx_BSRR的第16位(BR0) 写1。

5.2 STM32的地址映射

温故而知新——stm32f10x.h文件

首先请大家回顾一下在51单片机上点亮LED是怎样实现的。这太简单了,几行代码就搞定。

#include int main (void) { P0=0; while(1); }

以上代码就可以点亮P0端口与LED阴极相连的LED灯了,当然,这里省略了启动代码。为什么这个P0 =0; 句子就能控制P0端口为低电平?很多刚入门51单片机的同学还真解释不来,关键之处在于这个代码所包含的头文件

在这个文件下有以下的定义:

view sourceprint?

01./* BYTE Registers */ 02.

03.sfr P0 = 0x80; 04.

05.sfr P1 = 0x90; 06.

07.sfr P2 = 0xA0; 08.

09.sfr P3 = 0xB0; 10.

11.sfr PSW = 0xD0; 12.

13.sfr ACC = 0xE0; 14.

15.sfr B = 0xF0; 16.

17.sfr SP = 0x81; 18.

19.sfr DPL = 0x82; 20.

21.sfr DPH = 0x83; 22.

23.sfr PCON = 0x87; 24.

25.sfr TCON = 0x88; 26.

27.sfr TMOD = 0x89; 28.

29.sfr TL0 = 0x8A; 30.

31.sfr TL1 = 0x8B; 32.

33.sfr TH0 = 0x8C; 34.

35.sfr TH1 = 0x8D; 36.

37.sfr IE = 0xA8; 38.

39.sfr IP = 0xB8; 40.

41.sfr SCON = 0x98; 42.

43.sfr SBUF = 0x99;

这些定义被称为地址映射。

所谓地址映射,就是将芯片上的存储器甚至I/O等资源与地址建立一一对应的关系。如果某地址对应着某寄存器,我们就可以运用c语言的指针来寻址并修改这个地址上的内容,从而实现修改该寄存器的内容。

正是因为头文件中有了对于各种寄存器和I/O端口的地址映射,我们才可以在51单片机程序中方便地使用P0 =0xFF; TMOD =0xFF等赋值句子对寄存器进行配置,从而控制单片机。

Cortex-M3的地址映射也是类似的。Cortex-M3有32根地址线,所以它的寻址空间大小为2^32 bit=4GB。ARM公司设计时,预先把这4GB的寻址空间大致地分配好了。它把地址从0x4000 0000至0x5FFF FFFF( 512MB )的地址分配给片上外设。通过把片上外设的寄存器映射到这个地址区,就可以简单地以访问内存的方式,访问这些外设的寄存器,从而控制外设的工作。结果,片上外设可以使用 C 语言来操作。M3存储器映射见图 5-7

图 5-7

stm32f10x.h这个文件中重要的内容就是把STM32的所有寄存器进行地址映射。如同51单片机的头文件一样,stm32f10x.h像一个大表格,我们在使用的时候就是通过宏定义进行类似查表的操作,大家想像一下没有这个文件的话,我们要怎样访问STM32的寄存器?有什么缺点?

不进行这些宏定义的缺点有: 1、地址容易写错

2、我们需要查大量的手册来确定哪个地址对应哪个寄存器

3、看起来还不好看,且容易造成编程的错误,效率低,影响开发进度。

当然,这些工作都是由ST的固件工程师来完成的,只有设计M3的人才是最了解M3的,才能写出完美的库。

在这里我们以外接了LED灯的外设GPIOC为例,在这个文件中有这样的一系列宏定义:

#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000) #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) #define PERIPH_BASE ((uint32_t)0x40000000)

这几个宏定义是从文件中的几个部分抽离出来的,具体的读者可参考stm32f10x.h源码。

外设基地址

首先看到PERIPH_BASE这个宏,宏展开为0x4000 0000,并把它强制转换为uint32_t的32位类型数据,这是因为地STM32的地址是32位的,是不是觉得0x4000 0000这个地址很熟?是的,这个是Cortex-M3核分配给片上外设的从0x4000 0000至0x5FFF FFFF的512MB寻址空间中的第一个地址,我们把0x4000 0000称为外设基地址。

总线基地址

接下来是宏APB2PERIPH_BASE,宏展开为PERIPH_BASE(外设基地址)加上偏移地址0x1 0000,即指向的地址为0x4001 0000。这个APB2PERIPH_BASE宏是什么地址呢?STM32不同的外设是挂载在不同的总线上的,见图 5-8。有AHB总线、APB2总线、APB1总线,挂载在这些总线上的外设有特定的地址范围。

图 5-8

其中像GPIO、串口1、ADC及部分定时器是挂载这个被称为APB2的总线上,挂载到APB2总线上的外设地址空间是从0x4001 0000至地址0x4001 3FFF。这里的第一个地址,也就是0x4001 0000,被称为APB2PERIPH_BASE (APB2总线外设的基地址)。

而APB2总线基地址相对于外设基地址的偏移量为0x1 0000个地址,即为APB2相对外设基地址的偏移地址。

见表:

由这个表我们可以知道,stm32f10x.h这个文件中必然还有以下的宏: #define APB1PERIPH_BASE PERIPH_BASE 因为偏移量为零,所以APB1的地址直接就等于外设基地址 寄存器组基地址

最后到了宏GPIOC_BASE,宏展开为APB2PERIPH_BASE (APB2总线外设的基地址)加上相对APB2总线基地址的偏移量0x1000得到了GPIOC端口的寄存器组的基地址。这个所谓的寄存器组又是什么呢?它包括什么寄存器?

细看stm32f10x.h文件,我们还可以发现以下类似的宏: #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00) #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000) #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)

除了GPIOC寄存器组的地址,还有GPIOA、GPIOB、GPIOD的地址,并且这些地址是不一样的。

前面提到,每组GPIO都对应着独立的一组寄存器,查看stm32的datasheet,看到寄存器说明如下图:

图 5-9

注意到这个说明中有一个偏移地址:0x04,这里的偏移地址的是相对哪个地址的偏移呢?下面进行举例说明。

对于GPIOC组的寄存器,GPIOC含有的端口配置高寄存器(GPIOC_CRH) 寄存器地址为:GPIOC_BASE +0x04。

假如是GPIOA组的寄存器,则GPIOA含有的端口配置高寄存器(GPIOA_CRH)寄存器地址为:GPIOA_BASE+0x04。

也就是说,这个偏移地址,就是该寄存器相对所在寄存器组基地址的偏移量。 于是,读者可能会想,大概这个文件含有一个类似如下的宏( 当初野火也是这么想的 ): #define GPIOC_CRH (GPIOC_BASE + 0x04)

这个宏,定义了GPIOC_CRH寄存器的具体地址,然而,在stm32f10x.h文件中并没有这样的宏。ST公司的工程师采用了更巧妙的方式来确定这些地址,请看下一小节——STM32库对寄存器的封装。

5.3 STM32库对寄存器的封装

ST的工程师用结构体的形式,封装了寄存器组,c语言结构体学的不好的同学,可以在这里补补课了。在stm32f10x.h文件中,有以下代码:

#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE) #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)

有了这些宏,我们就可以定位到具体的寄存器地址,在这里发现了一个陌生的类型GPIO_TypeDef ,追踪它的定义,可以在stm32f10x.h 文件中找到如下代码:

typedef struct {

__IO uint32_t CRL;

__IO uint32_t CRH; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t BRR; __IO uint32_t LCKR; } GPIO_TypeDef;

其中 __IO 也是一个ST库定义的宏,宏定义如下:

#define __O volatile /*!< defines 'write only' permissions */ #define __IO volatile /*!< defines 'read / write' permissions */

volatitle 是c语言的一个关键字,有关volatitle的用法可查阅相关的C语言书籍。 回到GPIO_TypeDef 这段代码,这个代码用typedef 关键字声明了名为

GPIO_TypeDef的结构体类型,结构体内又定义了7个 __IO uint32_t 类型的变量。这些变量每个都为32位,也就是每个变量占内存空间4个字节。在c语言中,结构体内变量的存储空间是连续的,也就是说假如我们定义了一个GPIO_TypeDef ,这个结构体的首地址(变量CRL的地址)若为0x4001 1000,那么结构体中第二个变量(CRH)的地址即为0x4001 1000 +0x04 ,加上的这个0x04 ,正是代表4个字节地址的偏移量。

细心的读者会发现,这个0x04偏移量,正是GPIOx_CRH寄存器相对于所在寄存器组的偏移地址,见图 5-9。同理,GPIO_TypeDef 结构体内其它变量的偏移量,也和相应的寄存器偏移地址相符。于是,只要我们匹配了结构体的首地址,就可以确定各寄存器的具体地址了。

有了这些准备,就可以分析本小节的第一段代码了: #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)

本文来源:https://www.bwwdw.com/article/s6h2.html

Top