OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 16750|回复: 19

#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)这句话的具体作用是什么啊

[复制链接]

9

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
74
金钱
74
注册时间
2014-5-5
在线时间
0 小时
发表于 2014-5-20 10:11:50 | 显示全部楼层 |阅读模式
5金钱
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)这句话的具体作用是什么啊

最佳答案

查看完整内容[请看2#楼]

GPIO_TypeDef是一个结构体,里面定义了GPIO的常用寄存器,GPIOA_BASE是GPIOA的基地址,#define GPIOA_BASE            (AHB2PERIPH_BASE + 0x00000000),#define AHB2PERIPH_BASE       (PERIPH_BASE + 0x08000000),#define ERIPH_BASE        &n ...
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

0

主题

59

帖子

0

精华

初级会员

Rank: 2

积分
79
金钱
79
注册时间
2014-5-4
在线时间
0 小时
发表于 2014-5-20 10:11:51 | 显示全部楼层
GPIO_TypeDef是一个结构体,里面定义了GPIO的常用寄存器,GPIOA_BASE是GPIOA的基地址,#define GPIOA_BASE            (AHB2PERIPH_BASE + 0x00000000),#define AHB2PERIPH_BASE       (PERIPH_BASE + 0x08000000),#define ERIPH_BASE           ((uint32_t)0x40000000) 。这句话的作用就是把GPIOA定义成一个基地址为GPIOA_BASE 的地址指针,指针类型为GPIO_TypeDef结构体,这样GPIO_TypeDef结构体里的GPIO的常用寄存器也就会一一映射到CM3 存储器外设地址,对GPIOA这个结构体里的寄存器操作就相当于对对应的外设地址操作。不知道这么说楼猪是否明白,建议楼猪狠补一下嵌入式C语言的知识,这种定义结构体类型的地址指针的宏定义是嵌入式驱动程序的基础,非常常见。
回复

使用道具 举报

230

主题

1950

帖子

10

精华

论坛元老

Rank: 8Rank: 8

积分
4562
金钱
4562
注册时间
2010-12-14
在线时间
32 小时
发表于 2014-5-20 10:11:51 | 显示全部楼层
 回复【3楼】 ping1185279685 :
---------------------------------
1. GPIOA    他要的是结构体指针类型,不是结构体类型。所以要加*
2. 第二个,就是c语言基础了:

引用如下:
  
结构(struct)
     结构是由基本数据类型构成的、并用一个标识符来命名的各种变量的组合。 
结构中可以使用不同的数据类型。 
    1. 结构说明和结构变量定义 
    在Turbo C中, 结构也是一种数据类型, 可以使用结构变量, 因此,  象其它 
类型的变量一样, 在使用结构变量时要先对其定义。 
    定义结构变量的一般格式为: 
     struct 结构名 
[mw_shl_code=c,true] struct 结构名 { 类型 变量名; 类型 变量名; ... } 结构变量; [/mw_shl_code]

     } 结构变量; 
    结构名是结构的标识符不是变量名。 
    类型为第二节中所讲述的五种数据类型(整型、浮点型、字符型、指针型和 
无值型)。 
    构成结构的每一个类型变量称为结构成员, 它象数组的元素一样, 但数组中 
元素是以下标来访问的, 而结构是按变量名字来访问成员的。 
    下面举一个例子来说明怎样定义结构变量。 
[mw_shl_code=c,true] struct string { char name[8]; int age; char sex[2]; char depart[20]; float wage1, wage2, wage3, wage4, wage5; } person; [/mw_shl_code]


    这个例子定义了一个结构名为string的结构变量person,   如果省略变量名 
person, 则变成对结构的说明。用已说明的结构名也可定义结构变量。这样定义 
时上例变成: 
[mw_shl_code=c,true] struct string { char name[8]; int age; char sex[2]; char depart[20]; float wage1, wage2, wage3, wage4, wage5; }; struct string person; [/mw_shl_code]


    如果需要定义多个具有相同形式的结构变量时用这种方法比较方便, 它先作 
结构说明, 再用结构名来定义变量。 
    例如: 
     struct string Tianyr, Liuqi, ...; 
    如果省略结构名, 则称之为无名结构, 这种情况常常出现在函数内部, 用这 
种结构时前面的例子变成: 
[mw_shl_code=c,true] struct { char name[8]; int age; char sex[2]; char depart[20]; float wage1, wage2, wage3, wage4, wage5; } Tianyr, Liuqi; [/mw_shl_code]




我是开源电子网?网站管理员,对网站有任何问题,请与我联系!QQ:389063473Email:389063473@qq.com
回复

使用道具 举报

0

主题

59

帖子

0

精华

初级会员

Rank: 2

积分
79
金钱
79
注册时间
2014-5-4
在线时间
0 小时
发表于 2014-5-20 10:11:51 | 显示全部楼层
1、#define GPIOA      ((GPIO_TypeDef *) GPIOA_BASE)这句话我给你简单翻译一下:GPIOA首先是一个指向地址GPIOA_BASE的指针,指针类型为一个GPIO_TypeDef类型的结构体,也就是说GPIOA指向的结构体第一个成员为GPIOA的第一个寄存器,而这第一个寄存器的地址偏移一般是0,也就是说第一个寄存器的地址就是GPIOA_BASE,第二个寄存器地址为GPIOA_BASE+4个字节,以此类推。
2、不能把*号去掉,这是嵌入式C语言中定义指向固定地址指针的严格格式,比如我想在SRAM的地址0x2000FFF0处定义一个指针变量,就可以如下:
#define dat   (unsigned char *)0x2000FFF0;(*号不能少)
*dat=0;
3、你所说的ss不是指针。
回复

使用道具 举报

9

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
74
金钱
74
注册时间
2014-5-5
在线时间
0 小时
 楼主| 发表于 2014-5-20 11:11:14 | 显示全部楼层
回复【2楼】cdshkf:
---------------------------------
你说  GPIOA定义成一个基地址为GPIOA_BASE 的地址指针 , 这里的地址指针 我理解为地址的地址....为什么不定义成#define GPIOA               ((GPIO_TypeDef ) GPIOA_BASE)去掉*号啊??另外,c语言中,struct { int a;}ss;这里的ss是指针么???谢谢了
回复

使用道具 举报

230

主题

1950

帖子

10

精华

论坛元老

Rank: 8Rank: 8

积分
4562
金钱
4562
注册时间
2010-12-14
在线时间
32 小时
发表于 2014-5-20 11:29:57 | 显示全部楼层
回复【5楼】cdshkf:
---------------------------------
3.ss是结构体变量,不是指针。
我是开源电子网?网站管理员,对网站有任何问题,请与我联系!QQ:389063473Email:389063473@qq.com
回复

使用道具 举报

0

主题

59

帖子

0

精华

初级会员

Rank: 2

积分
79
金钱
79
注册时间
2014-5-4
在线时间
0 小时
发表于 2014-5-20 11:30:56 | 显示全部楼层
给楼猪看个东西,从阿莫上转过来的,希望会对楼猪有点启发。

我做了个实例,不用ST的库来点LED,解答你的问题

我的 KeilMDK 3.5
我的STM32板子奋斗版是 ,IC 是 STM32F103VET6
调试工具 JTAG V8
LED 接在 B5 ,高电平点亮

既然楼主说一定懂C语言了,那么对于下面我的问题,不查百度,完全靠自己,懂多少?然后查了百度之后又能懂多少?

(一)新建 keil 工程,IC选择 ST 公司的 STM32F103VE,keil提示是否copy 启动文件,选择是。

这里有问题问楼主,
你有没有读过这个启动头文件? 51 也是同样的启动文件,51的那个启动文件有没有读过?你知道
头文件里面做了什么吗? C语言真的从 main 函数开始吗?运行时库是什么?这些资料从
什么地方知道?keil编译器的行为?
(如果你说头文件是汇编的,没有必要看,那我当我没说)

例如启动文件里面有这么一句,我的问题是 __main 这个标号在哪里实现的,注意,这里肯定不是 main 函数
这里跳到哪里去了?还有个问题 [WEAK] 这里是什么意思?有什么用????

Reset_Handler   ROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                LDR     R0, =__main
                BX      R0


(二)新建一个 main.c 并且写一个 main函数,什么都不做,这和51一样了。

void main(void)
{
        while (1)
        {

        }
}

然后因为我需要调试,则设置jtag调试器,在项目属性里面 Debug 标签,Use JTAG/J-TRACE ,然后到
utilities 标签,同样选择JTAG /J-TRACK ,并且选择 Setting 按钮,里面的 rogramming Algorithm
还是空的,表示keil 不知道目标是什么,我添加一个 STM32F10X High-density Flash ,问题,为什么是
High-desity ?依据是什么???
全部确认返回。

这个时候已经可以编译,开发板上电,已经可以下载仿真的,虽然程序什么都没有写

(三)既然硬件,仿真器,调试都准备好了,接着就开始写程序了。
我一直推荐新手花钱买学习板和仿真器,因为可以排除硬件的问题,让初学者集中精力去写程序,而不用怀疑
硬件有问题,这点很重要。

这阶段主要是看书,了解这个IC 的架构,了解指令集,了解寄存器(别跟我说你找不到这些资料? .....)
Cortex-M3权威指南CnR2(电子书).pdf
STM3210x参考手册.pdf
学习板原理图
博客,论坛等多个帖子,务必要对整个IC有个初步的了解。这个过程有点痛苦,但是值得花这个时间。

(四)开始写 LED
既然我们要操作 IO 口,当然就要看IO口相关的知识。打开 STM3210x参考手册.pdf ,我的目的只是操作 GPIO
所以我只需要将第五章看完就OK了。章节比较多,懒得看,根据一般的经验(楼主,你缺经验了吧?),不说多
就AVR 和 IC 而已。操作IO一般是两个步骤,第一,操作IO控制寄存器,设置IO为输出,第二就是送数据。

那么很明显,只可能是 GPIOx_CRL  GPIOx_CRH , GPIOx_ODR 三个寄存器会有想要
仔细阅读这几个寄存器的介绍后知道,GPIOx_CRL 是控制 IN 0-7 的属性的,GPIOx_CRH 控制PIN 8-15,ODR寄存器
当然就是输出数据了,将数据送到这里就行了。

然后,这几个寄存器的地址是多少?首先看 stm32f103ve.pdf 这个是官方的datasheet、,看第四章, Mmeory Mapping
为什么看这章?会英文都能猜到吧?,看 ORTB 的地址是 0x40010C00 - 0x40010FFF ,这个就是基地址了。基地址
加上偏移量就能找到具体的寄存器。

例如我需要操作 GPIOB_CRL 的偏移为 00H ,(看STM3210x参考手册.pdf) ODR 寄存器的偏移为 0CH
那么很自然得出
GPIOB_CRL = 0x40010C00
GPIOB_ODR = 0x40010C0C

怎么验证我的结论正确?先看 keil 给的头文件 \Keil\ARM\INC\ST\STM32F10x\stm32f10x_map.h
#define ERIPH_BASE           ((u32)0x40000000)
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)

这样怎么算都能算出 0x40010C00 出来吧??ODR 寄存器同理

为了点亮 LED ,我需要将 B5 (也就是 GPIOB5)设置为输出,并且ODR相应的位写入 1 ,看资料得出 MODE5 是
bit 20 21 控制的,CNF5 是bit 22,23
MODE5应该设置 10(0x2) 选择 2MHZ 输出,CNF5 选择00(0x0),通用推挽模式,于是将这个值写入

*(*volatile unsigned long)0x40010C00 = (2<<20) | (0<<22);  // 为简单起见,不管其他位了

楼主你是否能看懂这句C语言??volatile 什么意思什么用?指针的本质是什么?为什么能这样用?2<<20 是什么
意思,为什么能这样用?楼主我真的不是为难你,嵌入式都这么写的,ST的头文件也是这么定义

同理,设置 ODR 寄存器
*(volatile unsigned long *)0x40010C0C = 1<<5;
*(volatile unsigned long *)0x40010C0C = 0;

STM32 没有SFR ,没有bit,没有sbit 的概念的了。是不是就不如 51 了?


下载运行,还不行,因为GPIOB 的CLK 没有使能,这时其实 GPIOB 是不能工作的,这是 STM32 特殊的地方,上电
默认外设的时钟都是关的,初学者没有注意这里,是可以原谅的,多看看书,多实践,多问问就是了。

找到问题的原因,则再 RCC_APB2ENR 设置,其中 BIT 3 就是 IOPBEN 是时钟使能位,同上,先找到 RCC_APB2ENR
的地址
#define ERIPH_BASE           ((u32)0x40000000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
#define RCC_BASE              (AHBPERIPH_BASE + 0x1000)
RCC_APB2ENR 的偏移是 18H ,所以最终得到地址为 0x40021018,操作方法同上

*(volatile unsigned long *)0x40021018 |= 1<<3;


最终的点LED的程序就完成了。
void main(void)
{
        *(volatile unsigned long *)0x40021018 |= 1<<3;
        *(volatile unsigned long *)0x40010C00 = (2<<20) | (0<<22);
        *(volatile unsigned long *)0x40010C0C = 1<<5;
        while (1)
        {

        }
}
如果将寄存器做一个定义,则程序变成如下

#define RCC_APB2ENR *(volatile unsigned long *)0x40021018
#define GPIOB_CRL  *(volatile unsigned long *)0x40010C00
#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C

void main(void)
{
        RCC_APB2ENR |= 1<<3;
        GPIOB_CRL = (2<<20) | (0<<22);
        GPIOB_ODR = 1<<5;
        while (1)
        {

        }
}

RCC_APB2ENR  RCC 是时钟寄存器 , APB2 是外设2 ,ENR ,可以理解为 enable
GPIOB_CRL  GPIO B control 控制寄存器
GPIOB_ODR  GPIO(general purpose input output) B output data register 输出数据寄存器

都是有意义的名字,哪里难记了??而且名字都来自 ST 的官方 datasheet、这个程序跟你用 51 写的程序我还真的
没看出差别有很大 .....

加入刚才的 GPIOB 寄存器,看看 ST 的官方库是怎么定义的,
\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\stm32f10x.h
用 UltraEdit 打开,搜索 GPIOB

#define ERIPH_BASE           ((uint32_t)0x40000000)
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)

没错,和keil 里面是一模一样的。


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  的定义在 \Libraries\CMSIS\CM3\CoreSupport\core_cm3.h 为什么我知道在这个文件里面,因为我会
用 source insight ...

#define     __IO    volatile

__IO uint32_t CRL 其实就是 volatile uint32_t CRL

为什么用结构体?因为结构体的成员的地址分配(RAM中)是连续(不知道楼主是否懂得,这还是C语言的问题),
而 STM32 的一个模块的功能寄存器都是连续的,每个寄存器都是相当于 基地址加偏移,跟上面的理论一致

于是就有了结构体指针的用法
跟踪库函数的源代码,例如 GPIO 的 初始化函数
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)

以结构体指针的形式传递 IO 口 GPIO_TypeDef* GPIOx

访问 CRL 寄存器则用成员的形式 GPIOx->CRL;

不需要担心这样做的效率,因为都是地址,也就是指针,最终的效率是直接寄存器操作,效率是非常高的。

看不懂库函数,归根究底就是C语言功底不行。不要以为写过几行51就懂C语言了,远的很呢。

还有,STM 的库下载的时候包含了很多很多例子,库函数怎么使用在例子里面有很详细的介绍,不用写几行代码,
都是复制例子做实验,也很很容易的。
回复

使用道具 举报

34

主题

805

帖子

4

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
1865
金钱
1865
注册时间
2011-3-29
在线时间
140 小时
发表于 2014-5-20 11:34:19 | 显示全部楼层
最好改一下标题以便于其他人搜索。
业余程序玩家。
回复

使用道具 举报

9

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
74
金钱
74
注册时间
2014-5-5
在线时间
0 小时
 楼主| 发表于 2014-5-20 12:30:31 | 显示全部楼层
回复【5楼】cdshkf:
---------------------------------
我想我应该明白了! #define GPIOA      ((GPIO_TypeDef *) GPIOA_BASE)   我理解成:(GPIO_TypeDef *) 是强制转换,把GPIOA_BASE转换成GPIO_TypeDef*类型,也就是说GPIOA的值就是GPIOA_BASE,同时又是GPIO_TypeDef 结构体指针,指向GPIO_TypeDef 结构体的第一个元素,也就是地址为GPIOA_BASE的元素(这里为寄存器)...应该是这样的吧
回复

使用道具 举报

0

主题

59

帖子

0

精华

初级会员

Rank: 2

积分
79
金钱
79
注册时间
2014-5-4
在线时间
0 小时
发表于 2014-5-20 12:38:36 | 显示全部楼层
回复【9楼】ping1185279685:
---------------------------------
孺子可教!
回复

使用道具 举报

9

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
74
金钱
74
注册时间
2014-5-5
在线时间
0 小时
 楼主| 发表于 2014-5-20 12:58:49 | 显示全部楼层
回复【10楼】cdshkf:
---------------------------------
但是我还有一点不明白!!GPIOA->CRL=0x00000001 这句话是怎么把数据送入寄存器的,是从GPIOA基地址加上CRL偏移地址 开始一位一位连续的放入数据么,如果是的话,怎么感觉是给地址赋值啊
回复

使用道具 举报

0

主题

59

帖子

0

精华

初级会员

Rank: 2

积分
79
金钱
79
注册时间
2014-5-4
在线时间
0 小时
发表于 2014-5-20 13:08:50 | 显示全部楼层
回复【11楼】ping1185279685:
---------------------------------
这就是嵌入式驱动的精髓所在了,GPIOA->CRL=0x00000001这句话确实是把数据送入一个地址,实际上IC设计厂家在设计MCU的时候已经在硬件上把所有的外设寄存器的地址都已经映射到了cortex-M3 的相应存储器地址,也就是说你对某个外设寄存器地址写入某个数据就相当于实现芯片手册中寄存器的相应位操作的功能。如果你硬要深究芯片厂家是怎么将外设寄存器的地址(比如GPIOA的CRL寄存器)映射到Cortex-M3的存储器单元中,这就牵扯到数字设计的问题了。你现在需要知道的是,首先看数据手册,哪个外设的哪个寄存器的哪个位清零还是置1实现什么功能,然后就在相应的地址处置1或者清0就行了,仅此而已。
回复

使用道具 举报

9

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
74
金钱
74
注册时间
2014-5-5
在线时间
0 小时
 楼主| 发表于 2014-5-20 15:50:28 | 显示全部楼层
回复【12楼】cdshkf:
---------------------------------
有一点奇怪的是:GPIOA->CRL=0x00000001是给地址赋值,但是struct   stud{int a; char b;}; struct stud * ss; ss->a=1;却是给变量赋值,一个给地址赋值一个给变量赋值...好奇怪啊
回复

使用道具 举报

7

主题

34

帖子

0

精华

初级会员

Rank: 2

积分
126
金钱
126
注册时间
2015-9-13
在线时间
9 小时
发表于 2015-10-12 09:55:21 | 显示全部楼层
MARK一下,要好好看看~
0 error 0 warning
回复

使用道具 举报

1

主题

4

帖子

0

精华

新手上路

积分
42
金钱
42
注册时间
2016-3-29
在线时间
5 小时
发表于 2016-4-4 19:56:57 | 显示全部楼层
mark一下
回复

使用道具 举报

0

主题

15

帖子

0

精华

初级会员

Rank: 2

积分
114
金钱
114
注册时间
2016-9-2
在线时间
28 小时
发表于 2016-9-3 09:46:43 | 显示全部楼层
cdshkf 发表于 2014-5-20 11:30
给楼猪看个东西,从阿莫上转过来的,希望会对楼猪有点启发。

我做了个实例,不用ST的库来点LED,解答你的 ...

解释得很详细,刚踏入stm32这块领域,还要多多学习c才行
回复

使用道具 举报

5

主题

54

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
440
金钱
440
注册时间
2016-11-7
在线时间
63 小时
发表于 2016-11-26 08:05:56 来自手机 | 显示全部楼层
cdshkf 发表于 2014-5-20 10:11
1、#define&nbsp;GPIOA&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;((GPIO_TypeDef&nbsp;*)&nbsp;GPIOA_BASE)这句 ...

为什么是4个字节?其他都明白
回复

使用道具 举报

5

主题

54

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
440
金钱
440
注册时间
2016-11-7
在线时间
63 小时
发表于 2016-11-26 10:39:24 | 显示全部楼层
lihilong 发表于 2016-11-26 08:05
为什么是4个字节?其他都明白

开始没转过来 每个寄存器是32位 懂了
回复

使用道具 举报

8

主题

21

帖子

0

精华

新手上路

积分
30
金钱
30
注册时间
2017-5-25
在线时间
20 小时
发表于 2017-5-25 10:01:18 | 显示全部楼层
一、 GPIO_Init函数解析 1
1、参数GPIO_TypeDef 1
2、参数GPIO_InitStruct 2
3、函数代码详解 4
4、备注 6
一、GPIO_Init函数解析
首先来看一下GPIO_Init函数的原型void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)。这个函数的实现是在Stm32f10x_gpio.c文件中,若要使用该函数在相应的应用程序的前面包含Stm32f10x_gpio.h头文件。
1、参数GPIO_TypeDef
该函数的第一个参数为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;
在这个结构体类型当中有7个32(8字节)位的变量,这些变量在存储空间的地址是相邻的。打开STM32数据手册不难看出,每个端口对应有16的引脚,由7个寄存器控制GPIO行为,并且这7个寄存器的顺序也是连续的。各个端口都有相同的结构。STM32的固件库就将这种结构抽象出一个类型GPIO_TypeDef。在操作寄存器之前你一定要有一个寄存器映射的操作,否则无法访问指定的寄存器,在这里我们只需要映射一次而不需要映射7此。这样做是不是很方便,也提高了代码的可读性,使代码规范化。
既然GPIO_Init的第一个参数GPIO_TypeDef的指针变量,这个指针变量存放的就是某一个端口的首地址。某一个程序的调用语句是这样的GPIO_Init(GPIOD,&GPIO_InitStructure); //初始化GPIOD
GPID是固件库中定义的一个宏,在编译的时候会宏展开,先列出与GPIOD端口地址映射有关的宏定义如下:
#define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOD_BASE            (APB2PERIPH_BASE + 0x1400)
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define PERIPH_BASE           ((uint32_t)0x40000000)
看到了0x4000 0000这个数字是不是非常熟悉,它是外设的首地址。在STM32芯片的内部STM32有两个,一个叫APB1,一个叫APB2。每一个APB桥都会管理很多外设。STM32F10x把这两个APB的外设寄存器访问地址放在了不同的存储空间。0x10000就是APB2外设的存储空间首地址相对于整个外设的偏移。而0x1400是GPIOD端口外设首地址相对于APB2外设的存储空间首地址的偏移。这样就找到了GPIOD外设的基地址了!而((GPIO_TypeDef *) GPIOD_BASE)可以同时实现所有控制GPIOD端口的7个寄存器的映射。若访问某一个寄存器只需要通过指向GPIO_TypeDef 变量的指针。
2、参数GPIO_InitStruct
第二个参数的为GPIO_InitTypeDef* GPIO_InitStruct。就是一个指向GPIO _InitTypeDef的地址。第一个参数只找到配置的目标寄存器,第二个参数就是对相应端口如何配置的数据参数。这些参数存储在指向GPIO_InitTypeDef变量的首地址处。先列处该参数由来的一断代码
GPIO_InitTypeDef  GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitTypeDef 是一个结构体的变量,该变量在Stm32f10x_gpio.h头文件中被定义,定义的原型如下:
typedef struct
{
  uint16_t GPIO_Pin;
  GPIOSpeed_TypeDef  GPIO_Speed;  
  GPIOMode_TypeDef  GPIO_Mode;  
}GPIO_InitTypeDef;
GPIO_InitTypeDef的第一个变量为GPIO_Pin是一个16为的无符号数,该数只有16位,每一位代表一个引脚,若要配置某一个端口的某一个引脚只需要把相应的位设置为1就可以了。在STM32的固件库中有如下引脚号定义:
#define GPIO_Pin_0                 ((uint16_t)0x0001)  /*!< Pin 0 selected */
#define GPIO_Pin_1                 ((uint16_t)0x0002)  /*!< Pin 1 selected */
#define GPIO_Pin_2                 ((uint16_t)0x0004)  /*!< Pin 2 selected */
#define GPIO_Pin_3                 ((uint16_t)0x0008)  /*!< Pin 3 selected */
#define GPIO_Pin_4                 ((uint16_t)0x0010)  /*!< Pin 4 selected */
#define GPIO_Pin_5                 ((uint16_t)0x0020)  /*!< Pin 5 selected */
#define GPIO_Pin_6                 ((uint16_t)0x0040)  /*!< Pin 6 selected */
#define GPIO_Pin_7                 ((uint16_t)0x0080)  /*!< Pin 7 selected */
#define GPIO_Pin_8                 ((uint16_t)0x0100)  /*!< Pin 8 selected */
#define GPIO_Pin_9                 ((uint16_t)0x0200)  /*!< Pin 9 selected */
#define GPIO_Pin_10                ((uint16_t)0x0400)  /*!< Pin 10 selected */
#define GPIO_Pin_11                ((uint16_t)0x0800)  /*!< Pin 11 selected */
#define GPIO_Pin_12                ((uint16_t)0x1000)  /*!< Pin 12 selected */
#define GPIO_Pin_13                ((uint16_t)0x2000)  /*!< Pin 13 selected */
#define GPIO_Pin_14                ((uint16_t)0x4000)  /*!< Pin 14 selected */
#define GPIO_Pin_15                ((uint16_t)0x8000)  /*!< Pin 15 selected */
#define GPIO_Pin_All               ((uint16_t)0xFFFF)  /*!< All pins selected */
使用这些定义好的宏就方便多了,要配置某几个引脚只需要把相应的引脚相或就可以了。若你要多某一个端口的所有为进行配置,那么只需要使用一个宏GPIO_Pin_All 。简单吧!哈哈!
GPIOSpeed_TypeDef是一个枚举变量,它用于存储GPIO速度的参数,它的定义如下:
typedef enum
{
  GPIO_Speed_10MHz = 1,
  GPIO_Speed_2MHz,
  GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
通过定义可以知道,GPIOSpeed_TypeDef的变量有三种取值,那么GPIO的速度有三种,
枚举变量的值
对应的速度
1
10MHZ
2
2MHZ
3
50MHZ
GPIOMode_TypeDef也是一个枚举变量,它用于存储GPIO工作的模式,它的定义如下:
typedef enum
{ GPIO_Mode_AIN = 0x0,
  GPIO_Mode_IN_FLOATING = 0x04,
  GPIO_Mode_IPD = 0x28,
  GPIO_Mode_IPU = 0x48,
  GPIO_Mode_Out_OD = 0x14,
  GPIO_Mode_Out_PP = 0x10,
  GPIO_Mode_AF_OD = 0x1C,
  GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
设计这个枚举变量的可取值有一定的意义。在第四位当中只用到了其中的高两位,这两位数据用来存储到某一个引脚的模式控制位MODEx[1:0] ,而高四位用来标志某一些标志。
高四位的取值
意义
0
输入模式
1
输出模式
2
下拉输入
4
上拉输入
3、函数代码详解
上面是GPIO_Init函数参数的解释。我在我们就可以进入GPIO_Init函数的内部看看了。
先把函数的代码列出,对代码的解释都放在了注释当中 ,如下:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
  uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
  uint32_t tmpreg = 0x00, pinmask = 0x00;
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));  
  
/*---------------------------- GPIO Mode Configuration -----------------------*/
  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)//若为输出上拉就会配置GPIO的速度
  {
    /* Check the parameters */
    assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
    /* Output mode */
    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
  }
/*---------------------------- GPIO CRL Configuration ------------------------*/
  /* Configure the eight low port pins */
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)//若对第八个引脚进行配置,GPIO_Pin的值某一位为1就会对该引脚配置
  {
    tmpreg = GPIOx->CRL;//暂存GPIO控制寄存器原来的值
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)//扫描8次决定,查看哪一引脚需要配置,若 //需要配置则进行配置
    {
      pos = ((uint32_t)0x01) << pinpos;//获得要查看的某一个引脚所对应的位为1的值
      /* Get the port pins position */
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;//currentpin 的值为0或者为pos
      if (currentpin == pos)//若为pos说明该位需要配置
      {
        pos = pinpos << 2;//pinpos 的值乘以4得到某一引脚配置位的最低位号:0,4,8......28
        /* Clear the corresponding low control register bits *///用于屏蔽某一个引脚的配置位, 使这4位为0
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);//因为模式所对应的数都存放在第四位,所以需要向左移位到某一个引脚对应的配置位的最低位出,然后对存储到tmpreg 中
        /* Reset the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)//若为输入下拉,需要打开相 应的开关
        {
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }
        else
        {
          /* Set the corresponding ODR bit */
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)//若为输入下拉,需要打开 相应的开关
          {
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;//对低8个引脚配置寄存器赋值
  }
/*---------------------------- GPIO CRH Configuration ------------------------*/
  /* Configure the eight high port pins */
  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
  {
    tmpreg = GPIOx->CRH;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = (((uint32_t)0x01) << (pinpos + 0x08));
      /* Get the port pins position */
      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
      if (currentpin == pos)
      {
        pos = pinpos << 2;
        /* Clear the corresponding high control register bits */
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);
        /* Reset the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
          GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
        /* Set the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
        {
          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
      }
    }
    GPIOx->CRH = tmpreg;
  }
}
4、备注
assert_param函数是对参数的检测。参数要么是逻辑0或者1。IS_GPIO_ALL_PERIPH也是一个宏,宏定义为:
#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \
                                    ((PERIPH) == GPIOB) || \
                                    ((PERIPH) == GPIOC) || \
                                    ((PERIPH) == GPIOD) || \
                                    ((PERIPH) == GPIOE) || \
                                    ((PERIPH) == GPIOF) || \
                                    ((PERIPH) == GPIOG))
其他的参数检测函数当中使用的宏都是相似的,具体可以查看相应的宏定义,在此不一一列出。
对低8位的配置和对高8位的配置原理是一样的。所以在此只对低8引脚配置进行说明。
回复

使用道具 举报

33

主题

218

帖子

0

精华

高级会员

Rank: 4

积分
568
金钱
568
注册时间
2015-1-12
在线时间
75 小时
发表于 2020-2-19 11:15:35 | 显示全部楼层
cdshkf 发表于 2014-5-20 10:11
GPIO_TypeDef是一个结构体,里面定义了GPIO的常用寄存器,GPIOA_BASE是GPIOA的基地址,#define&nbsp;GPIOA_ ...

理解难点在于 ((GPIO_TypeDef *) GPIOA_BASE)  ,GPIOA_BASE是个常量,这种转化的依据是什么啊?不理解
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-5-20 15:43

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表