【单片机】点亮STM32F103ZE的LED
2022-08-05 19:42:07

关键词:GPIO、单片机、C语言

引子

本文是自己的单片机学习记录。我会从硬件开始,一步步研究,点亮STM32F103ZE的LED小灯,并使之闪烁。如果能给您一点启发,再好不过。我选择的是正点原子的STM32F103ZET6精英开发板。

要完成这个任务,你需要:

  • Keil uVision5
  • FlyMcu(或者其他烧录工具)
  • 开发板原理图
  • STM32参考手册
  • 一点数字电路和模拟电路的知识
  • C语言的基础知识

一、硬件连接

LED0和PB5引脚相连,LED1和PE5相连,那么我们就要操纵这两个GPIO引脚的电平,给高电平还是低电平呢?

LED和VCC^13.3V相连,所以我们要在引脚处给予低电平,才能使LED小灯亮起,要让它们闪烁,交替给予高低电平就可以了。

二、GPIO

GPIO[^2]口的基本结构如下:

几个小元件/称谓:

  • TTL肖特基触发器:即由TTL元件构成的施密特触发器/滞回比较器,可以将模拟信号转化为数字信号。
  • 上拉/下拉电阻:同图中VDD[^3]相连为上拉电阻,VSS[^4]相连为下拉电阻。

GPIO口的几种输入模式:

  • 浮空输入
  • 上拉输入
  • 下拉输入
  • 模拟输入

输出模式:

  • 开漏输出
  • 开漏复用
  • 推挽输出
  • 推挽复用

浮空输入

上下拉电阻的开关都不闭合,信号直接通过“IO-触发器-输入数据寄存器”。I/O口悬空的时候,输入端电平高低无法确定。

上拉输入

在浮空输入基础上,如果上拉电阻的开关闭合,就成了上拉输入模式。I/O口悬空时,输入端上拉为高电平。输入低电平时,输出也能为低电平。这里不要错以为输入低电平时,输出由于上拉电阻的影响为高电平!

下拉输入

在浮空输入的基础上,把下拉电阻的开关闭合,就成了下拉输入模式。I/O口悬空时,输入端下拉为低电平。

模拟输入

上下拉电阻的开关都不闭合,信号直接通过“IO-模拟输入”至片上外设模块。如果通过触发器,模拟信号会变成数字信号。

开漏(Open-Drain)和推挽(Push-Pull)

开漏指输出信号只与NMOS管,而与PMOS管无关。输出为低电平,低电平到达NMOS管栅极,NMOS导通,输出拉至GND[^5]低电平。当高电平到达NMOS管时,不导通,输出悬空。

开漏输出的这一特性一个明显的优势就是可以很方便的调节输出的电平,因为输出电平完全由上拉电阻连接的电源电平决定。所以在需要进行电平转换的地方,非常适合使用开漏输出。
开漏输出的这一特性另一个好处在于可以实现”线与”功能,所谓的”线与”指的是多个信号线直接连接在一起,只有当所有信号全部为高电平时,合在一起的总线为高电平;只要有任意一个或者多个信号为低电平,则总线为低电平。而推挽输出就不行,如果高电平和低电平连在一起,会出现电流倒灌,损坏器件。

推挽指输出信号与PMOS、NMOS管都有关系,低电平令NMOS导通,输出拉低至GND,高电平令PMOS导通,输出拉高至VDD。其电平转换快,但是功耗也高。

复用输出

其与另外两种输出的区别就是输出信号的来源不同而已,如图所示。

三、代码

如何在Keil中创建项目就不再赘述了,具体步骤大概为:创建.c文件-创建项目-引入头文件-把.c文件引入项目中。

Step 1 LED的初始化函数

GPIOB,GPIOE分布在APB2总线上,所以使能应该调用APB2的相关函数:

1
2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);

之后,初始化PB5、PE5引脚,设置输入模式、反转速度等等,通过看相关函数的定义,这几句不难写出:

1
2
3
4
5
6
7
8
9
10
11
GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //Output-Push&Pull
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0->PB.5
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //Output-Push&Pull
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1->PE.5
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE,&GPIO_InitStructure);

这里将PB5、PE5设置成50MHz的推挽输出。

然后,我们要设置灯的初态,亮或是灭:

1
2
GPIO_SetBits(GPIOE,GPIO_Pin_5);
GPIO_SetBits(GPIOB,GPIO_Pin_5);

这里,我们将PB5、PE5初始状态设为高电平,也就是让LED小灯先灭掉。

Step2 主函数

对于单片机来讲,主函数一般是这样的结构——“初始化-死循环”。

主函数没什么可讲的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(void)
{
LED_Init();//初始化LED
while(1)
{
//PB5、PE5变成高电平,LED熄灭
GPIO_SetBits(GPIOE,GPIO_Pin_5);
GPIO_SetBits(GPIOB,GPIO_Pin_5);

delay_ms(500);//延时500ms便于观察情况

//PB5、PE5变成低电平,LED亮起
GPIO_ResetBits(GPIOE,GPIO_Pin_5);
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
}
}

喂喂喂,等等!延时函数怎么写哇!正点原子官方给提供了,我会在下一篇文章中研究一下时钟系统。

结语

单纯调库函数属实没多大意思,那些底层的东西,往往才是最有价值的。我写作水平不是很高,诸位凑活着看吧!

参考资料

你彻底弄清GPIO内部结构和各种模式了吗?

[^2]:General Purpose Input Output,通用输入输出
[^3]:设备工作电压
[^4]:地/电源负极,我当作接地来理解
[^5]:GrouND,接地
[^6]:High Speed Internal
[^7]:High Speed External
[^8]:Low Speed Internal
[^9]:Low Speed External
[^10]:Phase Locked Loop,锁相环