LED简介:
- 实物LED灯有一长一短,长的是正极,短的是负极
- 在点亮LED的简单电路中,正极通常连接vcc,中间在连一个电阻;而负极则与单片机的引脚相连接。
因此我们就可以知晓,只需给对应引脚输出0,就可以点亮这个LED灯了,接下来就开始实操
一、以寄存器的方式点亮LED
以PA0为例:表示属于GPIOA这个模块的0号引脚,现在要给这个引脚输出一个低电平。
- GPIO有GPIOA~GPIOX,相当于对单片机引脚进行的一个分组
基本流程:
如果你学习过51,对于点灯这个流程,肯定非常熟悉,
只需要找到对应的引脚,隶属于哪一个寄存器,
然后给这个寄存器 控制该引脚的 某一位置0,就可以将灯点亮了
但是如果在stm32中,与之有什么差别的呢?
变的更复杂了,换句话来说,应该更具备逻辑性,为什么会这样?
在介绍基本流程过程中,我会回答这个问题。
1.时钟(RCC)配置
- 我们使用PA0,属于GPIOA这个模块,我们需要打开这个模块的时钟,这个模块才能正常运行,所以我们需要找到它。
- 通过下面这张系统架构图我们可以知道ABH系统总线上桥接着APB2和APB1这两根高速外设总线 ,它们需要的系统时钟频率是不同的:
- APB1:36MHz
- APB2:72Mhz
- 我们要使用的GPIOA挂载在APB2这个高速外设总线上,因此我们就确认好了GPIOA的位置,也就可以进行配置。
-
回到一开始的问题:为什么需要时钟?为什么stm32需要配置时钟?为什么51不需要?
- 时钟相当于一个指挥家,芯片内部的运行需要高低电平的变化,且具有节奏,节奏需要上升沿和下降沿来控制,因此引脚在输入输出时,需要配置配置时钟。
- 对于51的看法,我们通常认为是落后过时,结构简单。确实如此,51单片机内部的模块少,只需统一的时钟就可以。在通电以后,内部时钟就自动打开。
- 而32,内部结构复杂,有着许多不同模块。通过上面的系统架构图,我们就可以看到高速外设总线上挂载这许多不同设备模块。不同模块,需要的时钟不一致,有些需要高速,有些需要低速。如果采用统一的时钟,就会资源浪费。所以我们采取的策略就是哪个模块需要,就打开哪个模块的时钟。
2.配置引脚的工作模式
输出模式下,分为四种:
- 通用推挽输出模式
- 通用开漏输出模式
- 复用推挽输出模式
- 复用开漏输出模式
输入模式下,分为四种:
- 模拟输入模式
- 浮空输入模式
- 上/下拉输入模式
我们采用的是通用推挽输出模式 ,即通过单片机的引脚给LED的负极输入低电平。LED理解为外设,单片机理解为内,由内而外,自然是输出。
假如单片机要读取外设的内容,比如读取传感器的内容,然后单片机要进行处理,这种由外而内,就是输入。
对于是输入还是输出,我们要站在单片机的角度去看,如果站在外设角度看,逻辑恰好相反。
对于通用和复用的区别,在后续的案列更新中,会慢慢阐述,这里简单介绍一下开漏和推挽。
- 开漏,相当于有一个默认的输出值,但这个默认值需要我们去设置,我们设置为1。如果要输出0,就需要我们主动控制引脚输出0。
- 推挽,可以理解为,需要我们主动介入。输出1还是0,都需要我们控制对应的引脚,置1还是置0
实际的配置过程中,我们需要配置输入还是输出,
在选择输出的时候,有多个速度选择10MHz,2MHz,还是50Mhz
最后就是具体的输出模式了
3.控制引脚输出低电平
只需要找到对应引脚的寄存器,置0就可以了
实现:
1)配置时钟
参考手册:
- 在这个图里面,我们可以看到RCC这个寄存器表示的范围,从起始地址~末尾地址
- 这里面包含了我们配置各种模块的时钟
- 我们只需要在起始地址的基础上 + 对应模块时钟的偏移地址 = 对应模块配置的区域
点击后面的 存储器映像列表下对应位置 就可以跳转到如下的这张图:
- 但是这张图传递的信息,并不详细,但也比较直观了
在目录中找到RCC寄存器:
- 从这里我们可以知道:0X18就是GPIOA这个模块的偏移地址
- IOPA中的A是端口A,所指的就是GPIOA
c
//1.时钟配置
法一:
//GPIOA位于第二位,我们要给这一位置1,如果采用十进制的方式赋值,那就是赋值4
//由于我们是直接操作地址,32位基地址+8位的起始地址以后,需要进行强转,这是一个指针类型
//所以还需要进行解引用
*(uint32_t*)(0x40021000+0X18)=4;
//我们还可以采用位运算的方式(位与):
*(uint32_t*)(0x40021000+0X18)|=1<<2;
//事实上,直接采用地址的方式可见不便于阅读,在编写的过程中也比较的复杂,
//因此我们对此进行了一个宏定义,库文件已经帮助我们完成了这个工作
只需要包含:
#include "stm32f10x.h"
所以我们可以这样表达:
//RCC 是一个结构体指针,指向 RCC 外设的基地址(也就是我们之前说的 0x40021000)。
//-> 是 C 语言的指针成员访问符,它会自动根据结构体成员的偏移地址,计算出 APB2ENR 寄存器的实际地址(基地址 + 偏移 0x18),和我们手动计算的逻辑完全一致。
//而后面要去配置指定位时,采用_的方式衔接,这种方式可以将我们要配置的位,默认置1
RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;
- 底层文件已经帮助我们找到配置了位,我们只需q要将其 与上 对应的寄存器 就可以了
2)配置工作模式
通过这张图我们可以知道如何配置工作模式:

- 通过MODE多位 可以选择输入输出模式,通过CNF多位 可以选择具体的一种模式
- 具体模式,赋值什么值,一目了然
事实上有了宏定义,我们就不需要关注要配置的位处于哪一个位置,我们只需要知道寄存器的名字,知道赋值一个怎样的值,就可以了。指定位置默认初值是1,对于这个位置的宏定义是:名字+序号
- 除了CRL寄存器,还有CRH,CR---控制寄存器,由于每一个引脚的具体模式都需要四位完成,一个GPIO口有16个引脚,因此就需要64位,我们的stm32是32位的处理器,也就需要两个32位的寄存器存放,一个存放序号高的8个,一个存放序号低的8个。
- 我们使用的是PA0,因此它所属的CHL,我们就要对MODE0和CNF0,进行一个操作

c
//因为我们现在要对引脚模式进行配置
//自然它的逻辑就是:
//通过对应GPIO,比如GPIOA,找到它的成员CRL,现在就找到要进行配置的寄存器
//要在指定位置上赋值,默认值是1
//所有的GPIO的引脚包含的模式都是一样的,因此就不需要去指定是哪个GPIO
//通用推挽输出模式:MODE---11 CNF---00
//这种方式是同时控制 两位
GPIOA->CRL&=~GPIO_CRL_CNF0; (按位取反&,指定位置 置0,其他位置不变)
GPIOA->CRL|=GPIO_CRL_MODE0; (按位或上,指定位置 置1,其他位置不变)
对照上面的那张图,我们可以发现宏定义的位就是我们目标配置的位:

除了可以同时配置两位以外,我们还可以去单独配置一位:
c
GPIOA->CRL&=~GPIO_CRL_CNF0_1; //配置两位中的高位
GPIOA->CRL&=~GPIO_CRL_CNF0_0;//配置两位中的低位
GPIOA->CRL&=~GPIO_CRL_CNF0_1;
GPIOA->CRL&=~GPIO_CRL_CNF0_0;

3)配置引脚输出

-
我们现在要给引脚输出0,自然是使用端口输出数据寄存器ODR,IDR是负责输入的。
-
使用ODR是一种直接控制引脚的方式,置1就是1,置0就是0.
-
如图我还包含了另外一个寄存器:BSRR。这是一种间接控制引脚输出的寄存器。

- 如果给BR0置1,那就相当于输出1
- 如果给BS0置1,那就相当于输出0
c
//通过GPIOA找到要配置的寄存器
//然后通过GPIO找到要配置位的宏定义,通过逻辑运算,输出0
GPIOA->ODR&=~GPIO_ODR_ODR0;
二、以HAL库的方式点亮LED
(1)引脚配置




(2)时钟配置

(3)项目管理



- 我们只需要找到主函数中的死循环,将代码的逻辑写在前面就可以了,一切的初始化工作,在图形化配置时,都帮助我做了,只需要调用HAL库就可以了

- 转义到声明这之后,可以看到还有许多的操作

- 继续看我们刚才使用到的函数,底层的逻辑采用BSSR寄存器,就是那个间接控制引脚上输出值的寄存器

- 我们使用的PA0,对应的就是位0





