【STM32教程】GPIO输出控制LED灯闪烁

往期内容:

【stm32】00亲爱的勇者,在踏上这个波澜壮阔的大陆之前,厉兵秣马,我们即刻出发

各位勇者你们好啊,我是weib。祝贺你们已经厉兵秣马做好在这个世界中闯出一番天地的准备了,也欢迎来到第一个关卡——GPIO的输出操作,闲言少叙让我们进入正题吧。

0、准备

请各位勇者再次确认自己的装备是否齐全——keil5-mdk及其芯片包、cubemx及其jdk环境以及一颗强大的心脏。加油吧各位勇者,我相信你们都是天选之人,定能在嵌入式这个世界闯出一片天地!

最后请闭上眼睛,轻轻的抚摸你的键盘,她是你最忠实的伙伴,她将陪伴着你攻克道道难题。

1、二极管及其单向导电性

我们今天面对的boss叫做led(light-emitting diode),它的中文名是发光二极管,以下是它权威的介绍:

led

发光二极管简称为LED。由含镓(Ga)、砷(As)、磷(P)、氮(N)等的化合物制成。

当电子与空穴复合时能辐射出可见光,因而可以用来制成发光二极管。在电路及仪器中作为指示灯,或者组成文字或数字显示。砷化镓二极管发红光,磷化镓二极管发绿光,碳化硅二极管发黄光,氮化镓二极管发蓝光。因化学性质又分有机发光二极管OLED和无机发光二极管LED。

LED最初用于仪器仪表的指示性照明,随后扩展到交通信号灯。再到景观照明、车用照明和手机键盘及背光源。后来发展出微型发光二极管(micro-LED)的新技术,其将原本发光二极管的尺寸大幅缩小,用可独立发光的红、蓝、绿微型发光二极管成阵列排列形成显示阵列用于显示技术领域。微型发光二极管具有自发光显示特性,比自发光显示的有机发光二极管(Organic Light Emitting DiodeOLED)效率高、寿命较长、材料不易受到环境影响而相对稳定。

2023年5月,新加坡—麻省理工学院研究与技术联盟的科学家开发了世界上最小的LED(发光二极管)。(引用自百度百科)

看了这个有何感想?不好意思,weib的知识薄弱,我是看不懂。led,又称发光二极管,看到二极管三个字,如果是和我一样是电子类专业的同学肯定脱口而出五个字——单向导电性

那么什么是二极管呢?什么又是单向导电性呢?

按照惯例我还是放出权威解释:

二极管

二极管是用半导体材料(硅、硒、锗等)制成的一种电子器件 。二极管有两个电极,正极,又叫阳极;负极,又叫阴极,给二极管两极间加上正向电压时,二极管导通, 加上反向电压时,二极管截止。 二极管的导通和截止,则相当于开关的接通与断开 。

二极管具有单向导电性能,导通时电流方向是由阳极通过管子流向阴极。

二极管是最早诞生的半导体器件之一,其应用非常广泛。特别是在各种电子电路中,利用二极管和电阻、电容、电感等元器件进行合理的连接,构成不同功能的电路,可以实现对交流电整流、对调制信号检波、限幅和钳位以及对电源电压的稳压等多种功能 。

无论是在常见的收音机电路还是在其他的家用电器产品或工业控制电路中,都可以找到二极管的踪迹 。(引用自百度百科)

单向导电性

PN结加正向电压时,可以有较大的正向扩散电流,即呈现低电阻, 我们称PN结导通; PN结加反向电压时,只有很小的反向漂移电流,呈现高电阻, 我们称PN结截止。 这就是PN结的单向导电性。(引用自百度百科)

推导及结论

不好意思,weib的知识薄弱,上面的两种解释我是看不懂。假设有这么一个王国,那里的人开门只会推门,而不会拉门。现在有这么一种门只能从一侧推开(如我们家中的防盗门),这种门就是我们的二极管。然后,我们想象以下以下几种情况——当门的两边没有人推门或是两边的人用相同的力推门(甚至是无法推开侧的人用的力比可推开侧的人大)的时候,门是在无法推开(关闭)的状态,这就叫做截止状态。而当(可推开侧的人用的力)  > ( (把门推开的力) + (不可推开侧的人用的力) )时这道门就推开了,就呈开的状态了,这就叫做导通状态,今天我们讨论以上两种状态即可。还有一种状态,无法推开侧的人发现推不开门,但是他就不信这个邪,呼朋唤友一起来推,当超过一个临界值时门再也坚持不住了,门说:大哥们,我错了,我开还不行吗?门就往反方向打开了,这种状态叫做击穿状态,击穿分为电压击穿和热击穿。其中,电压击穿可恢复,热击穿不可恢复。

经过以上描述,不知道各位勇者对二极管及其单向导电性有没有一个认识?接下来把目光我们回到我们今天的boss led身上,led是二极管的一种,它具备单向导电性以及导通、截止、击穿三种状态,今天我们把目光放在导通和截止状态上,因为如果led处在击穿状态下绝对是一件令人痛心的事情。

这是单片机上led的原理图, 图中D2便是led,其中我用红色的笔把1端标识成P端(可被推开的一侧),把2端标识成N端(无法推开的一侧),然后大家还需要知道的一点是在stm32中引脚可以输出高低电平(0和1(二进制0和1,纯洁的0和1哈)),

当输出低电平(0)时:引脚端电位为0V;

当输出高电平(1)时:引脚端电位为3.3V;

所以根据我们初中学的物理电学知识再结合上对刚才提到的二极管单项导电性,是不是可以作出如下猜想:

当输出低电平(0)时,2端电位0V,1端3.3V,led处于导通状态,led点亮;

当输出高电平(1)时,2端电位3.3V,1端3.3V,led处于截止状态,led熄灭;

恭喜你,亲爱的勇者!你的猜想是对的,接下来让我们带着这个结论继续向前吧!

2、基础框架的搭建

亲爱的勇者们,整理好装备了解了这个世界的背景故事之后,我们就要开始进行实操训练了,你们准备好了吗?

0、打开cubemx,这里推荐使用双击左键的方式打开,各位勇者能用更炫酷的方式打开当然是可以的,嘿嘿~

这是打开后的界面

1、双击选择ACCESS TO MCU SELECTOR

  

2、等待读条后,弹出如下界面,然后在搜索框里输入你所使用的芯片型号,并且双击选择所对应的型号

我这里用stm32f103c8t6给大家做示范,各位勇者一定不要局限于某一个型号的芯片,stm32就是我们的武器,如大刀、长剑、匕首各有所长——F代表主流产品、L代表低功耗产品、H代表高性能产品等,经历了一系列的探险之后,各位勇者会发现在cubemx的协助下,只要自身功夫到位stm32系列任何型号的芯片都是可以触类旁通的。

3、等待读条,工程成功创建之后,我们可以看到这样一个界面,然后我们开始基础框架的搭建

好,已经在打瞌睡的勇者可以清醒一下了,接下来的操作很重要,在没有特殊需求的情况下基本上每个工程都要重复以下操作,现在不知道为什么这么做没关系 ,我们的宗旨就是:先学操作、后学原理

0、创建工程

1、调试环境Debug的设置

 如图所示,我们点击展开System Core然后选择SYS最后在弹出的mode界面中Debug选项栏中选择Serial Wire即可。

2、系统时钟源及主频的设置

 如图所示,我们点击展开System Core然后选择RCC最后在弹出的mode界面中Hight Speed Clock(HSE)选项栏中选择Crystal/Ceramic Resonator即可。这样我们就把系统时钟源配置为外部高速时钟啦~ 接下来我们去修改单片机主频

图2.3.2.0

 图2.3.2.1

  图2.3.2.2

   图2.3.2.3

 首先,根据  图2.3.2.0 我们点击界面上方的Clok Configuartion,映入眼帘的界面就是stm32的时钟树,图2.3.2.1所框选的部分就是单片机的主频,可以通过输入不同数值改变单片机的主频(单位为MHZ),细心的勇者可能会发现下方有一行小字"72 MHZ max",这就代表我所使用的这种型号的单片机最大主频为72MHZ(以后有机会教大家如何超频,嘿嘿~),在不考虑功耗的情况下,嘿嘿~肯定要拉满!拉满!拉满!所以我这就输入72,见图2.3.2.1,然后按下回车,就会出现如图2.3.2.2界面,点击欧克即可,cubemx就会自动帮你配置时钟,图2.3.2.3就是配置好的状态啦。

3、这是一个总结

亲爱的勇者们,怎么样?记住基础框架搭建的步骤了吗?嘿嘿~和把大象装进冰箱一样,我们搭建一个框架也只需要三步:创建工程、设置调试环境、配置系统时钟及主频

啊?你说太复杂了很懵逼并且记不住。weib猛男落泪~

嘿嘿~其实刚开始接触的时候很懵逼、觉得很复杂是正常的,遥想weib刚进入这个世界的时候也是一度很崩溃,甚至怀疑自己不适合成为一个勇者,在嵌入式的世界漫游。但是,如我这样一个菜鸟现在不也好好的吗,所以要相信自己!

现在看不懂、无法理解没关系,照着上述步骤一步一步做,这又不是考试不需要闭卷,每个工程都要这样搭建一遍,相信不久的将来,你闭着眼睛用舌头都能把这基础框架搭建出来,嘿嘿~,再强调一遍我们的宗旨:先学操作、后学原理

接下来我们就要直面boss了,各位勇者准备好了吗?

3、GPIO引脚的配置、keil工程的生成

0、找到你的led引脚

如图所示,这是之前提到的,我的单片机上led部分的原理图,从图中我们可以看到在我的单片机中led的2端串联着一个限流保护电阻然后与PC13脚相连,所以在接下来我会控制PC13脚来控制led的点亮与熄灭。但,值得注意的是,不一定每位勇者手上的stm32的led都连向PC13脚,具体的引脚得参考该单片机的原理图。但是也不用慌张,我们学习的是方法,而并不是一板一眼的按着我的操作去做,我也相信即使所对应的引脚不一样,各位勇者也能依葫芦画瓢的点亮led的。

1、GPIO输出配置(讲解)

各位勇者,请把目光移向你们手中stm32,它上面一个个引出的引脚就是引出来的GPIO,别看它们只是一根根小小的引脚,但是它们的功能却是非常强大的——但是,前提是你得学会运用它的力量,嘿嘿 ~是不是有修仙小说那味了。GPIO有很多种模式:输出模式、输入模式、外部中断模式、复用功能模式等等,前期的学习我们先把目光放到输入和输出模式中。

今天我们所学习的是GPIO的输出模式,那么什么是GPIO的输出模式呢?我相信聪明的勇者们已经想到了刚才我们在讲二极管的时候谈到的纯洁的0和1,莫错——这就是对单片机输出模式的一个概述,嘿嘿~我再把它复制过来。

当输出低电平(0)时:引脚端电位为0V;

当输出高电平(1)时:引脚端电位为3.3V;

我们控制GPIO输出其根本就是根据运用情景设置引脚的高低电平(0或1),以达到我们想要的效果。在stm32的GPIO的输出模式又分为推挽输出(Output Push Pull)和开漏输出(Output Open Drain),那这两者是什么又有什么区别呢?按照惯例,是时候请它出场了:

推挽输出(英语:Push–pull output)是一种使用一对选择性地从相连负载灌电流或者拉电流的器件的电路。它常常使用一对参数相同的功率三极管或MOSFET管,以推挽方式存在于电路中。

开漏输出就是不输出电压,控制输出低电平时引脚接地,控制输出高电平时引脚既不输出高电平,也不输出低电平,为高阻态。如果外接上拉电阻,则在输出高电平时电压会拉到上拉电阻的电源电压。这种方式适合在连接的外设电压比单片机电压低的时候。(引用自百度百科)

我们再来看看推挽输出和开漏输出的区别:

推挽输出更适合单线单向通信(SPI, UART);开漏输出更适合双向单线通信(I2C, One-Wire);

推挽输出没有使用上拉电阻功耗更低;开漏输出由于使用上拉电阻功耗更高;

推挽输在信号的斜率性的能更强;推挽输在信号的斜率性的能更弱。(引用至知乎答主NewLook)

嘿嘿~你们是不是已经猜到我要说啥了,诶~我就不说。我们可以看到推挽输出的上拉电阻功耗更低、斜率性更强、输出高电平的时候不用自己加上拉电阻,或许就有小伙伴要问:那为什么不都用推挽输出啊?

有小聪明可能已经猜到了我的回答:不是这样的,推挽和开漏两种输出模式各有各的优势……

嘿嘿~可惜weib我岂是按常理出牌的人,我的回答是:莫错~,在绝大多数情况下我们都是用的推挽输出,只有极少数情况下才会使用开漏输出模式,所以刚入门的勇者们记住在使用GPIO输出模式是配置成推挽输出(Output Push Pull)就行啦。

接下来我们就要进行实操了,各位勇者准备好了吗?

2、GPIO输出配置(实操)

图3.2.0

图3.2.1

 图3.2.2

 首先,我们点击上方的Pinout & Configuration回到功能设置界面,然后找到led连接的引脚左键点击就会出现一个列表我们选择GPIO_Output把该引脚配置为输出模式,如图3.2.0所示。

配置完成之后我我们可以看到屏幕左边弹出了一个名为GPIO Mode and Configurationd的窗口如图3.2.1所示,同时我们也可以在左侧通过System Core -> GPIO打开该窗口。

接着我们点击对应引脚就可以进入该引脚详情配置界面,如图3.2.2标注,这就不做过多累述。在今天的使用中我们全保持默认配置即可。

3、keil工程文件的生成

 完成GPIO的配置之后,我们点击上方的Project Manager进入工程配置界面,在这个界面中我们需要进行三项操作:

1、工程名字的设置

2、工程路径的选择

3、编译器及其版本的选择,因为我们用的是keil_mdk,所以我们编译器选择为MDK-ARM,版本选择保持默认或根据自身情况进行选择

weib特别提醒各位勇者,一定要注意的是1、2点中工程名字的设置与工程路径的选择一定不要出现中文字符!

 完成上面三步后,如图所示选择Code Generator进行工程文件的设置,如图2、3步进行选择,以上所有流程检查无误以后,激动人心的一刻到来了,如图步骤4所示点击GENERATE CODE生成工程文件。

 读条完毕后,如果出现这个弹窗,那么就代表工程生成成功啦~这不为自己鼓个掌?接着我们点击Open Project就可以进入keil进行编程啦!

各位勇者,我们马上就要和今天的boss决一死战了,你和你的键盘娘准备好了吗?

4、程序的编写

0、下载配置选项

 进入keil之后,单击那根神奇的魔法棒,然后根据自己的下载器选择下载器,weib这用的是st-link,所以也就选了st-link,然后点击setting进入设置选项

 如图所示,如果SWDIO正常显示没有error就表示检测到下载器了,大概率可以下载了(因为这玩意儿太多玄学了,猛男落泪~),接着我们点击Flash Download

 推荐把Reset and Run勾选上,但是有些单片机即使勾选上了也要手动复位(摊手~),如图所示,如果框住那栏是空的,就需要手动点击add进行添加,如果没有匹配的选项,大概率是因为有小傻瓜没把对应的keil芯片包安装成功。

最后,点击确定完成配置。

1、决战前最后一次准备工作

勇者们,打起精神来!最后一次检查以下自己的装备是否完整!

如图所示:

1、编译工程,正常情况下是0 Error 0 Warning 的,如果有小倒霉蛋出现了错误,优先检查 :点击魔术棒->选择上方的Target->  查看ARM Compiler   是否为version 5,如果不是的话选择version 5点击ok然后再次编译。如果不是这个错误,返回cubemx点击GENERATE CODE重新生成工程,再次尝试编译。如果还是不行,只能求助于万能的互联网了;

2、编译完成后,打开main.c文件;

3、在main.c中找到main函数,然后再次抚摸键盘娘——战斗要打响咯!

2、点亮、熄灭led

经历了重重关卡,这一刻终于到来了, 闲话少说,我们打足精神开始吧!

0、关于BEGIN和END

 细心的小聪明应该发现了,在cubemx为我们生成的文件中有很多xxx BEGIN和xxx END,我要提醒各位小伙伴的是:在cubemx提供的框架编写代码时一定要写在BEGIN和END之间!!!

这都是血与泪的教训,因为如果在cubemx配置了新的东西重新生成工程文件时,cubemx只会保存BEGIN和END之间的内容,weib见过太多小倒霉蛋因为忘记了这一点,辛辛苦苦敲了很久的代码烟消云散时欲哭无泪、双目无神的样子,在最开始的时候weib本人也中过招,希望各位小聪明能够记住这一点,别再掉进坑里。

1、熄灭一个led

在点灯之前,大家可以按下这个双箭头,把程序下载进单片机里试试。

如果情况正常的话,你会发现本该秀一波操作,才服服帖帖被点亮的led灯已经被点亮了。你这是应该很生气,骂骂咧咧说weib捉弄了你这么长的时间,然后点个踩退出这篇文章……

但是,桥豆麻袋 !还记得我们在配置GPIO的时候,GPIO的初始化电平是什么吗?

然后我们再来康康这个:

当输出低电平(0)时,2端电位0V,1端3.3V,led处于导通状态,led点亮;

当输出高电平(1)时,2端电位3.3V,1端3.3V,led处于截止状态,led熄灭;

相信聪明的你已经发现什么了吧,嘿嘿~这是weib埋下的伏笔呢,傲娇脸~

那既然它已经被点亮了,那么我们就想办法让他熄灭吧!

首先我们来学习一条秘籍(函数)

/**
  * @brief  Sets or clears the selected data port bit.
  *
  * @note   This function uses GPIOx_BSRR register to allow atomic read/modify
  *         accesses. In this way, there is no risk of an IRQ occurring between
  *         the read and the modify access.
  *
  * @param  GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
  * @param  GPIO_Pin: specifies the port bit to be written.
  *          This parameter can be one of GPIO_PIN_x where x can be (0..15).
  * @param  PinState: specifies the value to be written to the selected bit.
  *          This parameter can be one of the GPIO_PinState enum values:
  *            @arg GPIO_PIN_RESET: to clear the port pin
  *            @arg GPIO_PIN_SET: to set the port pin
  * @retval None
  */
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  assert_param(IS_GPIO_PIN_ACTION(PinState));

  if (PinState != GPIO_PIN_RESET)
  {
    GPIOx->BSRR = GPIO_Pin;
  }
  else
  {
    GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;
  }
}

void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);

这是hal库提供给我们的,用来设置对应引脚电平的函数

它的第一个参数是GPIO_TypeDef *GPIOx,它表示对应端口编号,如GPIOA、GPIOB等,而我要设置的PC13脚,所以我要写入应该是GPIOC

它的第二个参数uint16_t GPIO_Pin代表引脚编号,所以PC13对应的引脚编号是GPIO_PIN_13

它的第三个参数GPIO_PinState PinState是HAL库帮我们封装好的枚举变量,它有GPIO_PIN_RESET( 低电平0)和GPIO_PIN_SET(高电平1)两种状态,如果我们要让灯熄灭,根据weib上面提到的内容,是不是应该设置为高电平,所以我该写入的是GPIO_PIN_SET

好啦~这条秘籍已经成型,不知道各位勇者学会了吗?

HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);

特别要注意的是第一个和第二个参数是根据自己板子的led连接的GPIO决定的哦!!!

一定要值得当心的是——别把自己当作敲代码的工具,别是跟着weib敲一遍,诶~功能实现了,就代表自己会了。要有思考、要举一反三,weib相信你们都是艺术家,你们以键盘为笔,能绘制出一幅幅让人眼前一亮的画作。

好嘞~回到正题,现在我们学会了这条秘籍,那我们该在什么时候使用这条秘籍呢?

我们把目光返回到main函数上,有小聪明可能已经发现cubemx已经帮我们在main函数中填充了一些东西了

 如图,就是其各内容的功能。

这时可能又有小伙伴会说,weib你是不是在捉弄我们啊,学C语言的老师一再强调要避免死循环的出现,可为什么在这个工程里while(1)大摇大摆的出现在main函数里面?

嘿嘿~我们来康康chatAI怎么说:

单片机中的while(1)是一种常见的循环结构,也被称为无限循环或死循环。它表示无论条件是什么,都会一直执行循环内的代码。在单片机中使用while(1)一般用于实现主循环,即主程序的核心部分。

嘿嘿~虽然weib知识薄弱,但这个还是懂一点点。我们常常用单片机来做一下重复性的操作,如adc(电压)的采集滤波、温湿度的持续检测、串口数据的定时发送等等,我们可以发现它们有几点共通之处:

1、要实现它们的功能会出现需要重复执行的代码

2、需要单片机长时间的工作,如果没有while(1),单片机顺序执行完代码后直接退出程序,停止工作

让我们回到上面的问题,HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);该放在哪里呢?cubemx是我们的大哥,没有它我们现在几乎寸步难行,大哥生成的函数我们怎敢僭越?所以我们要把我们的函数放在cubemx为我们生成的初始化函数后面。那有小可爱说既然啊要尊重大哥就要尊重到底,while(1)也是大哥生成的,那我把这条秘籍放while(1)后面——但是,在不出意外的情况下while(1)循环后面的语句还会执行吗?既然都叫死循环了,这个程序还有可能“活”着见到循环体后面的东西吗?    又有小可爱问,那么放在while(1)里面可以吗?呃…可以是可以,但是你想啊,每一个机器周期我们都要设置一次电平,这有必要吗?这不纯纯大冤种吗?摊手~

所以啊,一般来说,我们应该把只执行一次的、对引脚状态的配置、对复用功能的使能的代码放在cubemx生成的初始化函数下方、while(1)循环上方

 如图所示,然后我们编译、下载、复位,led正常情况下应该是熄灭了

那么如何用函数再点亮led呢?这就留给各位小伙伴去思考咯~

3、让led为你闪烁

写到这里已经有9k+字了,有小伙伴肯定会说,weib你这个标题党,说好的让32为我闪烁呢?扯东扯西都在扯些什么?

嘿嘿~别急,心急吃不了weib画的大饼,马上就来了!

首先,我们又要学习两条秘籍:

/**
  * @brief  Toggles the specified GPIO pin
  * @param  GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
  * @param  GPIO_Pin: Specifies the pins to be toggled.
  * @retval None
  */
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
  uint32_t odr;

  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  /* get current Output Data Register value */
  odr = GPIOx->ODR;

  /* Set selected pins that were at low level, and reset ones that were high */
  GPIOx->BSRR = ((odr & GPIO_Pin) << GPIO_NUMBER) | (~odr & GPIO_Pin);
}

void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);

这条秘籍的效果是让指定引脚的GPIO的电平翻转(0变1、1变0)

我们给可以看到它定义的形参GPIO_TypeDef *GPIOx、uint16_t GPIO_Pin是不是和眼熟,是不是和我们才学的设置电平的秘籍前两个形参命名一样——HAL库是st官方提供的规范、强大的库,所以如形参名这些都是有迹可循、符合规范的。所以我想要翻转PC13脚的电平,我要写进函数的应该是GPIOC、GPIO_PIN_13。

这条秘籍完整的形态如下:

HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);

我们继续往下看

/**
  * @brief This function provides minimum delay (in milliseconds) based
  *        on variable incremented.
  * @note In the default implementation , SysTick timer is the source of time base.
  *       It is used to generate interrupts at regular time intervals where uwTick
  *       is incremented.
  * @note This function is declared as __weak to be overwritten in case of other
  *       implementations in user file.
  * @param Delay specifies the delay time length, in milliseconds.
  * @retval None
  */
__weak void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;

  /* Add a freq to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }

  while ((HAL_GetTick() - tickstart) < wait)
  {
  }
}

void HAL_Delay(uint32_t Delay);

这是一条延时函数,其作用是让单片机停下来等待一段时间后再继续往下执行语句,其传进去的值为延时时间,单位为ms。

如果我要得到1s的延时,我就该这样写:HAL_Delay(1000);

欧克,恭喜各位勇者,你们已经习得了三条秘籍了,但是不能把这些秘籍一个个分开来看,要学会它们的组合使用,众所周知组合技打出的伤害更高,对吧~

现在,我们思考一个问题,如何使用电平翻转函数与延时函数实现led的闪烁?

想必有小聪明已经想到了:延时->翻转电平->延时->翻转电平->延时->翻转电平->延时->翻转电平->延时->翻转电平->延时->翻转电平->……就能实现led的闪烁了

肯定又有小聪明发现,我们一直在重复 延时->翻转电平 的操作,在学c语言的时候老师是不是有提到连续的、重复的代码块可以用循环来代替?嗯~循环……小聪明们是不是已经想到了什么东西——对!就是while(1)!!! 我们只需把延时->翻转电平放入while(1)中即可!闲话少说,上链接!!呸,上图!!!

 程序编写完成后,编译、下载、复位,不出意外的话你的led就在不灵不灵闪烁啦,再把屋里的灯关上,这不就是整个世界为你闪烁吗?嘿嘿~。你还可以试试,调节HAL_Delay(1000);参数值的大小来改变led闪烁的速度。

恭喜各位勇者,成功的击败了boss完成了这次的挑战,希望在未来的道路上我们仍能并肩同行、越走越远!

没有实现效果的小伙伴也不要丧气,可以移步第6点。

5、多学一招,引脚名称的配置

 

如图所示,在cubemx的配置界面中,我们可以右键单击我们想要命名的引脚,选择Enter User Label然后输入自己想要的命名即可。

 

 也可以直接在GPIO配置框中,User Label栏中直接输入

接着我们生成工程框架、编译

我们打开main.h文件,会发现cubemx帮我们配置了如下两个宏

 

 现在回到我们刚才写的程序,根据C语言中宏定义的知识,我们可以把传入函数的参数中

GPIOC 换成 LED_GPIO_Port

GPIO_PIN_13 换成 LED_Pin 

 编译、下载、复位以后,我们会惊奇的发现——效果是一样的,嘿嘿~

有小可爱可能会说,weib你又在这浪费我们时间,既然效果一样,那么为什么要如此多此一举呢?

Nooooo(故作高深脸),这样做让我们的代码变得更规范、增强了代码的可读性

又有小可爱可能会说,我自己写宏定义就行了,不必在cubemx上配置了又要等待重新生成、编译……

Nooooo(欠打脸),因为我们现在处理的引脚只有一个,量级很小,所以可能会有小可爱觉得不如自己写宏定义,但是在以后的学习或项目中我们会面对大量的GPIO的操作,比起点击引脚输入名字一个个去自己写宏定义也就太难受了吧。cubemx是一个强大的工具,各位勇者要学会驾驭它!

6、最后的碎碎念

根据过往线下教学的经验,weib清楚一定有小伙伴还没有实现功能、一定有小伙伴还是一头雾水。但是,weib想对这些小伙伴说的是,你要坚信你没学会不是你的问题,是weib教得有问题。越是艰难的时候越是要对自己有信心!并且,这玩意是存在一个过程的,只要把懵逼的阶段挺过了,可能突然一瞬间就恍然大悟了,各位勇者加油吧!

可能有小伙伴说,weib我完全能明白你说的每一步、也是按照着一步一步的来的,但是功能就是没有实现。weib想说的是,既然选择了走这条软硬件结合的道路,就要做好接受其bug的不可预判性(我们常常称为玄学),在学习的过程中weib也遇到了各种各样的玄学,甚至有段时间敲代码之前要看看黄历(切勿模仿,这是不可取的哈),其实学在后面你会发现各种模块的应用其实是比较容易上手的,而需要培养的是调试程序的能力以及面对bug临危不乱的心态。

然后,对于无法实现功能的小伙伴,weib只能提出以下建议:

软件上,检查cubemx的配置——引脚有没有配错、引脚模式有没有配错等等,检查keil里面代码位置是否正确

硬件上,检查GPIO是否能够正常输出高低电平、检查led是否被击穿、检查pcb连线是否出现断路等等

最后,说说后续的安排吧,下一篇准备写gpio的输入(按键检测),后续会讲中断、串口、定时器、adc等等,有精力的话可能会穿插一些小项目。当然,这也取决于这篇文章大家的认可度,点灯点了1w+字我也是没想到,还是费了不少的精力,还需要大家多多支持!

好啦,与其去聊那些眼前的苟且,不如把目光放在远处的诗和远方的田野上。今晚,宇宙为你而闪烁;明天,你将控制宇宙的闪烁!我是weib,我们下期再见吧!

物联沃分享整理
物联沃-IOTWORD物联网 » 【STM32教程】GPIO输出控制LED灯闪烁

发表评论