STM32F103VET6学习(2)

一.前言

这次主要写的内容是对LED的点亮的程序的解释,以及如何与数据手册结合起来写程序。程序会在下面再次附上。

二.正文

1.基址宏定义
STM32内的各部分的地址都是分配好的,我们直接利用就行。基址宏定义,即如下内容(这个可以从数据手册获得,不需要深究):

/*总线基地址*/
#define PERIPH_BASE ((unsigned int)0x40000000)
/*APB2总线基地址*/
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
/*AHB总线基地址*/
#define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000)
/*GPIO外设基地址*/
#define GPIOB_BASE (APB2PERIPH_BASE + 0X0C00)
/*RCC外设基地址·*/
#define RCC_BASE *(unsigned int*)(AHBPERIPH_BASE + 0X1000)

这在stm32f10x.h文件中是已经定义好的,我们不需要再次定义。
在此基础上需要做的就是,定义我们要用的GPIO寄存器地址并把它强制转换为指针,这样指针就可以指向我们寄存器想要指向的地址,从而实现想要实现的功能。后文不时出现的寄存器GPIOx中的x之所以是B,是因为本次我们要点亮的LED在芯片上连接的都是PBx口,只看LED的话,这里的x可以为0、1和5,对比下图就可以看明白:
芯片原理图部分截图
上图是芯片的部分截图,可以看到LED口连接了PBx口(x=0,1,5)。
LED
这是LED的原理图,可以看到,LED为共阳极接法(3.3V),因此要点亮LED(D3)就要从PBx口输出低电平从而使其导通。图片中的R,G,B分别代表Red,Green和Black,指的是颜色的三原色:红色、绿色和黑色,这三者分别不同的取值组合可以产生任意已知的颜色。由于目前我知识水平有限,一会儿就主要说说点亮绿色和红色的就好了(因为黑色等于没亮,所以就不做单独显示黑色的了)。下来继续说寄存器。
如下代码,是这几个寄存器地址的定义:

#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0X00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0X04)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0X0C)

寄存器的偏移地址都是在GPIO外设基地址的基础上进行偏移的,偏移地址我们可以查阅数据手册获得:
GPIOB_CRL寄存器
由此知道,GPIOB_CRL的偏移地址为0x00,因此有:

#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0X00)

GPIOB_CRH寄存器
由此知道,GPIOB_CRH的偏移地址为0x04,因此有:

#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0X04)

GPIOB_ODR寄存器
由此知道,GPIOB_ODR寄存器的偏移地址为0x0C,因此有:

#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0X0C)

当然,除了要知道这几个寄存器,还得开启GPIO外设时钟,通过RCC_APB2ENR寄存器的配置来完成。如果想要外设工作就必须开启相应的外设时钟。所有的 GPIO 都挂载到 APB2 总线上,具体的时钟由 APB2 外设时钟使能寄存器(RCC_APB2ENR)来控制:
RCC_APB2ENR寄存器
由此知道,RCC_APB2ENR寄存器的偏移地址为0x18,因此有:

#define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0X18)

如上就完成了这几个关键要用的寄存器的定义。
2.点亮LED
(1)开启外设时钟

RCC_APB2ENR |= (1<<3);    //开启时钟

其中的"<<"是左移运算符,表示将1左移3次。什么意思?用二进制来看,1就是1,将1左移3位,就变成了1000,对比下图该寄存器的位配置来看:
寄存器
看图片中的解释也能明白。我们要用的是PB口,因此就需要配置IOPB这一位了,该位高有效,因此给它赋1 。其余位,保持不变。要保持的原因是我们实现我们想要的功能就好了,但是不要更改原来的配置,以避免不必要的麻烦。
保持不变的方法,就是使用“或”运算,有1为1,全0为0.
因此才有了 " |= " , 即:

RCC_APB2ENR = RCC_APB2ENR | (1<<3)

这与刚才的代码的功能是完全一样的,只不过上一个更加简洁。在之后的几句重要代码也采用类似写法,不再作解释。
(2)以点亮LED(D3)为绿色为例,来阐述该步骤。

先把LED的原理图在此附在下面,看着方便。
LED
第一,先知道绿色LED对应的端口是PB0口,不明白就看图。
第二,清空控制PB0的端口位,之后再对它写入,从而实现要让它在这个口该实现的功能。
首先看看这步要用的是哪个寄存器,是这个:GPIOB_CRL
为啥?数据手册中是这样的描述的:
数据手册描述
可以看到要配置的就是端口配置低寄存器,也就是CRL,“低”就体现在了L上面。其他的配置就是辅助的配置,等下说。
寄存器

  • 先看0-31这32个数,它代表的是32位。每个GPIO引脚占用四位,通俗的说,就是PB0占4位,为0~3位;PB1占4位,为4 - 7位;…以此类推。从这里也明白了PB0包含了MODE0和CNF0,PB1包含了MODE1和CNF1,…,同样,以此类推,这个一会儿还有重用。
  • 小字rw表示读写权限为可读可写,如果只有r或w,则表示只读或只可写入。
  • 每个引脚的四位由CNFx[1:0]和MODEx[1:0]这俩组成(x为0,1,2,…,15。8-15是CRH中的)。CNF是用来定义输入的模式的,MODE是定义输出的模式以及输出的速度(这个不用记,用的话查手册就好了),在0~31这32位不同的位CNF和MODE有不同的规定,看下图就明白了:
    CNF和MODE位

由此,我们回到最初的话题,要点亮绿色LED,而绿色连接的是PB0口,而PB0对应的是CNF0[1:0]和MODE0[1:0]这两个位。

刚刚也说过了,要清空这四位(现在应该明白为啥是这4位了),那就让这四位为0,同样,保留其他位的原先配置不变。在这里因为要强行将PB0的四位清零,所以就不能用 或运算符了,不然如果原来是1,就没法清零了。
先取值为1111(二进制),化成十六进制就是0x0F,然后我们让他左移(4*0)位,然后再进行取反,之后再与原来的GPIOB_CRL进行与运算。下来解释这样做的原因:
取值为1111,之后进行的取反运算会把它变成0000,再进行按位的与运算时不就把这四位强制清零了么?然后再说这个左移(4*0)位。这里的4是因为每个PBx口有四位,0表示在当前的位置,也就是相当于没有左移,那为啥还要这样写呢?在这儿我顺便也就把程序的改写说了。我目前控制的是PB0口,如果我要控制为红色LED,且与查原理图可以知道对应的是PB5口。有了(4*0)这个基础,
我只需要把0改成5,也就是(4x5),相当于把这4位总体左移了5下,每下是4位,也就是移动了20位,看下面的二进制解释:

左移前是PB0:
0000 0000 0000 0000 0000 1111
左移后得到的PB5:
1111 0000 0000 0000 0000 0000

可以很明白的看到,1111的每一位被左移了20位。
这样如果我们保留与PB0相同的配置(输入模式、速度等),只需要更改这个左移的次数就可以换到对应的LED上了,很方便。
因此清空PB0端口位的代码如下:

GPIOB_CRL &= ~( 0X0000F << (4*0));   //清空PB0口的端口位

明白了这个,下来就继续说对MODE0[1:0]和CNF0[1:0]配置的值所体现的功能。

在此,要操控的是1:0口(>00),从上表我们就知道,如果要用通用推挽输出模式,则需要CNF0[1:0]=00;设置输出模式的速度为10MHz,则MODE0[1:0]=01。因此配置该寄存器的值就是00 01。
综合考虑的因素,我们就可以写出GPIOB_CRL的配置的代码:

GPIOB_CRL |= (1<<4*0);     //配置PB0口,推挽,10MHz

再做个解释:0001和1是同样的值,都是1; * (乘法的星号)运算符的优先级高于左移运算符<<,所以这里没有给4x0带上括号。配置时,要保留原来的配置,而且刚才已经把PB0的端口位清空为0了,所以可以使用或运算符来配置。4x0的原因和之前相同,不再赘述。

3. 配置输出低电平
为何输出低电平,开始就说过了,是因为LED共阳接法,所以阴极接的PB0要输出低电平,这样这个发光二极管才可以导通。
这一步要用的寄存器当然就是数据输出寄存器了,Output Data Register,也就是ODR,即GPIOB_ODR。下面看看它的配置规则。
ODR
可以看到,ODR是低16位有效,每一位对应一个GPIO引脚。我们要配置I/O0,也就是ODR0这一位,在这里我没有先去清空,原因就是我要写入的是低电平0,使用与运算符(有0为0,全1为1),这样就可以保留原来的配置并且也写入了0,和之前说的没有矛盾哦,看代码:

GPIOB_ODR &= ~(1<<0);   //PB0输出低电平

解释:配置的ODR0在第一位(也是最低位),所以直接给它赋值1,左移0位,然后取反,得到的就是0了,而其他位全部都是1,这样进行与运算时,其他位的配置就可以完全保留了。这里的左移0位同样也是有原因的,而且与之前的(4*0)是相同的道理,如果我要点亮PB5口的红色的LED,只需要将0改成5,也就是:

左移前:1
取反后:0(其他位都为1)

左移5次后:10000
按位取反后:01111

这样就很明了了,进行与运算之后同样可以把其他端口原来的配置保留了,不与之前说的原则相矛盾。
最后再加上一句代码让程序处在死循环中,这样灯就会一直点亮:

while(1);

4. 全部代码
注释我就不再写了,前面解释的很清楚,读者看着也可以给自己解释

#include"stm32f10x.h"
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0X00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0X04)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0X0C)
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0X18)

int main(void)
{
		RCC_APB2ENR |= (1<<3); 
		
		/*点亮绿色的LED*/
		GPIOB_CRL &= ~( 0X0000F << (4*0)); 
		GPIOB_CRL |= (1<<4*0);  
		GPIOB_ODR &= ~(1<<0);     
/*
		//这段是点亮红色LED的代码
		GPIOB_CRL &= ~(0x0F<<(4*5));   
		GPIOB_CRL |= (1<<4*5);     
		GPIOB_ODR &= ~(1<<5);    
*/
	while(1);
}
void SystemInit(void)
{
	/*由于在汇编语言里有这个函数,为了避免调用时找不到而报错,
	就在这里象征性的写这个函数来骗过编译器不报错*/
}

5 .LED效果图
(1)绿色LED:
效果图
(2)红色LED:
效果图

三.总结

首先,这次的内容我觉得自己还是吃的很透的,虽说做得很简单,就是一个简单的点亮发光二极管,但是其中包含了我对寄存器的认识与应用,以及寄存器的位的配置还有“移位”思想、配置读取与保留等等,具体的我都在上面的内容中有反映出来。这次觉得很充实,高兴!
打字过程中难免会有错别字,加上可能有的其他理论问题,欢迎大家指出,共同进步,谢谢!至此,本次内容,完结!

发布了11 篇原创文章 · 获赞 12 · 访问量 2385

猜你喜欢

转载自blog.csdn.net/J_GMson/article/details/104055088