【正点原子STM32连载】 第十三章 跑马灯实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

news2024/11/18 11:23:16

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html

第十三章 跑马灯实验

本章将通过一个经典的跑马灯程序,带大家开启STM32F103之旅。通过本章的学习,我们将了解到STM32F103的IO口作为输出使用的方法。我们将通过代码控制开发板上的LED灯:LED0、LED1交替闪烁,实现类似跑马灯的效果。
本章分为如下4个小节:
13.1 STM32F1 GPIO简介
13.2硬件设计
13.3程序设计
13.4下载验证

13.1 STM32F1 GPIO简介

GPIO是控制或者采集外部器件的信息的外设,即负责输入输出。它按组分配,每组16个IO口,组数视芯片而定。STM32F103ZET6芯片是144脚的芯片,具有GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF和GPIOG七组GPIO口,共有112个IO口可供我们编程使用。这里重点说一下STM32F103的IO电平兼容性问题,STM32F103的绝大部分IO口,都兼容5V,至于到底哪些是兼容5V的,请看STM32F103xE的数据手册(注意是数据手册,不是中文参考手册),见表5 大容量STM32F103xx引脚定义,凡是有FT标志的,都是兼容5V电平的IO口,可以直接接5V的外设(注意:如果引脚设置的是模拟输入模式,则不能接5V!),凡是不带FT标志的,就建议大家不要接5V了,可能烧坏MCU。
13.1.1 GPIO功能模式
GPIO有八种工作模式,分别是:
1、输入浮空
2、输入上拉
3、输入下拉
4、模拟功能
5、开漏输出
6、推挽输出
7、开漏式复用功能
8、推挽式复用功能
13.1.2 GPIO基本结构分析
我们知道了GPIO有八种工作模式,具体这些模式是怎么实现的?下面我们通过GPIO的基本结构图来分别进行详细分析,先看看总的框图,如图13.1.2.1所示。
在这里插入图片描述

图13.1.2.1 GPIO的基本结构图
如上图所示,可以看到右边只有I/O引脚,这个I/O引脚就是我们可以看到的芯片实物的引脚,其他部分都是GPIO的内部结构。
① 保护二极管
保护二极管共有两个,用于保护引脚外部过高或过低的电压输入。当引脚输入电压高于VDD时,上面的二极管导通,当引脚输入电压低于VSS时,下面的二极管导通,从而使输入芯片内部的电压处于比较稳定的值。虽然有二极管的保护,但这样的保护却很有限,大电压大电流的接入很容易烧坏芯片。所以在实际的设计中我们要考虑设计引脚的保护电路。
② 上拉、下拉电阻
它们阻值大概在30~50K欧之间,可以通过上、下两个对应的开关控制,这两个开关由寄存器控制。当引脚外部的器件没有干扰引脚的电压时,即没有外部的上、下拉电压,引脚的电平由引脚内部上、下拉决定,开启内部上拉电阻工作,引脚电平为高,开启内部下拉电阻工作,则引脚电平为低。同样,如果内部上、下拉电阻都不开启,这种情况就是我们所说的浮空模式。浮空模式下,引脚的电平是不可确定的。引脚的电平可以由外部的上、下拉电平决定。需要注意的是,STM32的内部上拉是一种“弱上拉”,这样的上拉电流很弱,如果有要求大电流还是得外部上拉。
③ 施密特触发器
对于标准施密特触发器,当输入电压高于正向阈值电压,输出为高;当输入电压低于负向阈值电压,输出为低;当输入在正负向阈值电压之间,输出不改变,也就是说输出由高电准位翻转为低电准位,或是由低电准位翻转为高电准位对应的阈值电压是不同的。只有当输入电压发生足够的变化时,输出才会变化,因此将这种元件命名为触发器。这种双阈值动作被称为迟滞现象,表明施密特触发器有记忆性。从本质上来说,施密特触发器是一种双稳态多谐振荡器。
施密特触发器可作为波形整形电路,能将模拟信号波形整形为数字电路能够处理的方波波形,而且由于施密特触发器具有滞回特性,所以可用于抗干扰,以及在闭回路正回授/负回授配置中用于实现多谐振荡器。
下面看看比较器跟施密特触发器的作用的比较,就清楚的知道施密特触发器对外部输入信号具有一定抗干扰能力,如图13.1.2.2所示。
在这里插入图片描述

图13.1.2.2 比较器的(A)和施密特触发器(B)作用比较
④ P-MOS管和N-MOS管
这个结构控制GPIO的开漏输出和推挽输出两种模式。开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。推挽输出:这两只对称的MOS管每次只有一只导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载拉电流。推拉式输出既能提高电路的负载能力,又能提高开关速度。
上面我们对GPIO的基本结构图中的关键器件做了介绍,下面分别介绍GPIO八种工作模式对应结构图的工作情况。
1、输入浮空
输入浮空模式:上拉/下拉电阻为断开状态,施密特触发器打开,输出被禁止。输入浮空模式下,IO口的电平完全是由外部电路决定。如果IO引脚没有连接其他的设备,那么检测其输入电平是不确定的。该模式可以用于按键检测等场景。
在这里插入图片描述

图13.1.2.3 输入浮空模式
2、输入上拉
输入上拉模式:上拉电阻导通,施密特触发器打开,输出被禁止。在需要外部上拉电阻的时候,可以使用内部上拉电阻,这样可以节省一个外部电阻,但是内部上拉电阻的阻值较大,所以只是“弱上拉”,不适合做电流型驱动。
在这里插入图片描述

图13.1.2.4 输入上拉模式
3、输入下拉
输入下拉模式:下拉电阻导通,施密特触发器打开,输出被禁止。在需要外部下拉电阻的时候,可以使用内部下拉电阻,这样可以节省一个外部电阻,但是内部下拉电阻的阻值较大,所以不适合做电流型驱动。
在这里插入图片描述

图13.1.2.5 输入下拉模式
4、模拟功能
模拟功能:上下拉电阻断开,施密特触发器关闭,双MOS管也关闭。其他外设可以通过模拟通道输入输出。该模式下需要用到芯片内部的模拟电路单元单元,用于ADC、DAC、MCO这类操作模拟信号的外设。
在这里插入图片描述

图13.1.2.6 模拟功能
5、开漏输出
开漏输出模式:STM32的开漏输出模式是数字电路输出的一种,从结果上看它只能输出低电平Vss或者高阻态,常用于IIC通讯(IIC_SDA)或其它需要进行电平转换的场景。根据《STM32F10xxx参考手册_V10(中文版).pdf》第108页关于“GPIO输出配置”的描述,我们可以知道开漏模式下,IO是这样工作的:
P-MOS被“输出控制”控制在截止状态,因此IO的状态取决于N-MOS的导通状况;
只有N-MOS还受控制于输出寄存器,“输出控制”对输入信号进行了逻辑非的操作;
施密特触发器是工作的,即可以输入,且上下拉电阻都断开了,可以看成浮空输入;
根据参考手册的描述,同时为了方便大家理解,我们在“输出控制”部分做了等效处理,如图13.1.2.7所示。图13.1.2.7中写入输出数据寄存器①的值怎么对应到IO引脚的输出状态②是我们最关心的。
根据参考手册的描述:开漏输出模式下P-MOS一直在截止状态,即不导通,所以P-MOS 管的栅极相当于一直接VDD。如果输出数据寄存器①的值为0,那么IO引脚的输出状态②为低电平,这是我们需要的控制逻辑,怎么做到的呢?是这样的,输出数据寄存器的逻辑0经过“输出控制”的取反操作后,输出逻辑1到N-MOS管的栅极,这时N-MOS管就会导通,使得IO引脚连接到VSS,即输出低电平。如果输出数据寄存器的值为1,经过“输出控制”的取反操作后,输出逻辑0到N-MOS管的栅极,这时N-MOS管就会截止。又因为P-MOS管是一直截止的,使得IO引脚呈现高阻态,即不输出低电平,也不输出高电平。因此要IO 引脚输出高电平就必须接上拉电阻。又由于F1系列的开漏输出模式下,内部的上下拉电阻不可用,所以只能通过接芯片外部上拉电阻的方式,实现开漏输出模式下输出高电平。如果芯片外部不接上拉电阻,那么开漏输出模式下,IO无法输出高电平。
在开漏输出模式下,施密特触发器是工作的,所以IO口引脚的电平状态会被采集到输入数据寄存器中,如果对输入数据寄存器进行读访问可以得到IO口的状态。也就是说开漏输出模式下,我们可以读取IO引脚状态。
在这里插入图片描述

图13.1.2.7 开漏输出模式
6、推挽输出
推挽输出模式:STM32的推挽输出模式,从结果上看它会输出低电平VSS或者高电平VDD。推挽输出跟开漏输出不同的是,推挽输出模式P-MOS管和N-MOS管都用上。同样地,我们根据参考手册推挽模式的输出描述,可以得到等效原理图,如图13.1.2.8所示。根据手册描述可以把“输出控制”简单地等效为一个非门。
如果输出数据寄存器①的值为0,经过“输出控制”取反操作后,输出逻辑1到P-MOS 管的栅极,这时P-MOS管就会截止,同时也会输出逻辑1到N-MOS管的栅极,这时N-MOS 管就会导通,使得IO引脚接到 VSS,即输出低电平。
如果输出数据寄存器的值为1 ,经过“输出控制”取反操作后,输出逻辑0到N-MOS 管的栅极,这时N-MOS管就会截止,同时也会输出逻辑0到P-MOS管的栅极,这时P-MOS管就会导通,使得IO引脚接到VDD,即输出高电平。
由上述可知,推挽输出模式下,P-MOS管和N-MOS管同一时间只能有一个管是导通的。当IO引脚在做高低电平切换时,两个管子轮流导通,一个负责灌电流,一个负责拉电流,使其负载能力和开关速度都有较大的提高。
另外在推挽输出模式下,施密特触发器也是打开的,我们可以读取 IO 口的电平状态。
由于推挽输出模式下输出高电平时,是直接连接VDD,所以驱动能力较强,可以做电流型驱动,驱动电流最大可达25mA,但是芯片的总电流有限,所以并不建议这样用,最好还是使用芯片外部的电源。
在这里插入图片描述

图13.1.2.8 推挽输出模式
7、开漏式复用功能
开漏式复用功能:一个IO口可以是通用的IO口功能,还可以是其他外设的特殊功能引脚,这就是IO口的复用功能。一个IO口可以是多个外设的功能引脚,我们需要选择作为其中一个外设的功能引脚。当选择复用功能时,引脚的状态是由对应的外设控制,而不是输出数据寄存器。除了复用功能外,其他的结构分析请参考开漏输出模式。
另外在开漏式复用功能模式下,施密特触发器也是打开的,我们可以读取IO口的电平状态,同时外设可以读取IO口的信息。
在这里插入图片描述

图13.1.2.9 开漏式复用功能
8、推挽式复用功能
推挽式复用功能:复用功能介绍请查看开漏式复用功能,结构分析请参考推挽输出模式,这里不再赘述。
在这里插入图片描述

图13.1.2.10 推挽式复用功能
13.1.3 GPIO寄存器介绍
STM32F1每组(这里是A~D)通用GPIO口有7个32位寄存器控制,包括 :
2 个 32 位端口配置寄存器(CRL和CRH)
2 个 32 位端口数据寄存器(IDR 和 ODR)
1 个 32 位端口置位/复位寄存器 (BSRR)
1 个 16 位端口复位寄存器(BRR)
1个32位端口锁定寄存器 (LCKR)
下面我们将带大家理解本章用到的寄存器,没有介绍到的寄存器后面用到会继续介绍。这里主要是带大家学会怎么理解这些寄存器的方法,其他寄存器理解方法是一样的。因为寄存器太多不可能一个个列出来讲,以后基本就是只会把重要的寄存器拿出来讲述,希望大家尽快培养自己学会看手册的能力。下面先看GPIO的2个32位配置寄存器:
 端口配置寄存器(GPIOx_CRL 和 GPIO_x_CRH)
这两个寄存器都是GPIO口配置寄存器,不过CRL控制端口的低八位,CRH控制端口的高八位。寄存器的作用是控制GPIO口的工作模式和工作速度,寄存器描述如图13.1.3.1和图13.1.3.2所示。
在这里插入图片描述

图13.1.3.1 GPIOx_CRL寄存器描述
在这里插入图片描述

图13.1.3.2 GPIOx_CRH寄存器描述
每组GPIO下有16个IO口,一个寄存器共32位,每4个位控制1个IO,所以才需要两个寄存器完成。我们看看这个寄存器的复位值,然后用复位值举例说明一下这样的配置值代表什么意思。比如GPIOA_CRL的复位值是0x44444444,4位为一个单位都是0100,以寄存器低四位说明一下,首先位1:0为00即是设置为PA0为输入模式,位3:2为01即设置为浮空输入模式。所以假如GPIOA_CRL的值是0x44444444,那么PA0~PA7都是设置为输入模式,而且是浮空输入模式。
上面这2个配置寄存器就是用来配置GPIO的相关工作模式和工作速度,它们通过不同的配置组合方法,就决定我们所说的8种工作模式。下面,我们来列表阐述,如表13.1.3.1所示。
表13.1.3.1 配置寄存器4个组合下的8种工作模式
因为本章需要GPIO作为输出口使用,所以我们再来看看端口输出数据寄存器。
 端口输出数据寄存器(ODR)
该寄存器用于控制GPIOx的输出高电平或者低电平,寄存器描述如图13.1.3.3所示。
在这里插入图片描述

图13.1.3.3 GPIOx_ODR寄存器描述
该寄存器低16位有效,分别对应每一组GPIO的16个引脚。当CPU写访问该寄存器,如果对应的某位写0(ODRy=0),则表示设置该IO口输出的是低电平,如果写1(ODRy=1),则表示设置该IO口输出的是高电平,y=0~15。
此外,除了ODR寄存器,还有一个寄存器也是用于控制GPIO输出的,它就是BSRR寄存器。
 端口置位/复位寄存器(BSRR)
该寄存器也用于控制GPIOx的输出高电平或者低电平,寄存器描述如图13.1.3.4所示。
在这里插入图片描述

图13.1.3.4 GPIOx_BSRR寄存器描述
为什么有了ODR寄存器,还要这个BSRR寄存器呢?我们先看看BSRR的寄存器描述,首先BSRR是只写权限,而ODR是可读可写权限。BSRR寄存器32位有效,对于低16位(0-15),我们往相应的位写1(BSy=1),那么对应的IO口会输出高电平,往相应的位写0(BSy=0),对IO口没有任何影响,高16位(16-31)作用刚好相反,对相应的位写1(BRy=1)会输出低电平,写0(BRy=0)没有任何影响,y=0~15。
也就是说,对于BSRR寄存器,你写0的话,对IO口电平是没有任何影响的。我们要设置某个IO口电平,只需要相关位设置为1即可。而ODR寄存器,我们要设置某个IO口电平,我们首先需要读出来ODR寄存器的值,然后对整个ODR寄存器重新赋值来达到设置某个或者某些IO口的目的,而BSRR寄存器直接设置即可,这在多任务实时操作系统中作用很大。BSRR寄存器还有一个好处,就是BSRR寄存器改变引脚状态的时候,不会被中断打断,而ODR寄存器有被中断打断的风险。
13.2 硬件设计

  1. 例程功能
    LED灯:DS0和DS1每过500ms一次交替闪烁,实现类似跑马灯的效果。
  2. 硬件资源
    1)LED灯
    DS0 – PB5
    DS1 – PE5
  3. 原理图
    本章用到的硬件用到LED灯:DS0和DS1。电路在开发板上已经连接好了,所以在硬件上不需要动任何东西,直接下载代码就可以测试使用。其连接原理图如图13.2.1所示:
    在这里插入图片描述

图13.2.1 LED与STM32F103连接原理图
13.3 程序设计
了解了GPIO的结构原理和寄存器,还有我们的实验功能,下面开始设计程序。
13.3.1 GPIO的HAL库驱动分析
HAL库中关于GPIO的驱动程序在STM32F1xx_hal_gpio.c文件以及其对应的头文件。

  1. HAL_GPIO_Init函数
    要使用一个外设我们首先要对它进行初始化,所以我们先看外设GPIO的初始化函数。其声明如下:
    void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
    函数描述:
    用于配置GPIO功能模式,还可以设置EXTI功能。
    函数形参:
    形参1是端口号,可以有以下的选择:
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)
这是库里面的选择项,实际上我们的芯片只能从GPIOA~GPIOE,因为我们只有5组IO口。
形参2是GPIO_InitTypeDef类型的结构体变量,其定义如下:
typedef struct
{
  uint32_t Pin;       	/* 引脚号 */
  uint32_t Mode;      	/* 模式设置 */
  uint32_t Pull;      	/* 上拉下拉设置 */
  uint32_t Speed;     	/* 速度设置 */
} GPIO_InitTypeDef;
该结构体很重要,下面对每个成员介绍一下。
成员Pin表示引脚号,范围:GPIO_PIN_0到 GPIO_PIN_15,另外还有GPIO_PIN_All和GPIO_PIN_MASK可选。
成员Mode是GPIO的模式选择,有以下选择项:
#define  GPIO_MODE_INPUT              	(0x00000000U)   /* 输入模式 */
#define  GPIO_MODE_OUTPUT_PP         	(0x00000001U)   /* 推挽输出 */
#define  GPIO_MODE_OUTPUT_OD             	(0x00000011U)   /* 开漏输出 */
#define  GPIO_MODE_AF_PP               	(0x00000002U)   /* 推挽式复用 */
#define  GPIO_MODE_AF_OD              	(0x00000012U)   /* 开漏式复用 */
#define  GPIO_MODE_AF_INPUT				GPIO_MODE_INPUT

#define  GPIO_MODE_ANALOG           		(0x00000003U)   /* 模拟模式 */

#define  GPIO_MODE_IT_RISING           	(0x11110000U)	/* 外部中断,上升沿触发检测 */
#define  GPIO_MODE_IT_FALLING          	(0x11210000U)	/* 外部中断,下降沿触发检测 */
/* 外部中断,上升和下降双沿触发检测 */
#define  GPIO_MODE_IT_RISING_FALLING 	(0x11310000U)	

#define  GPIO_MODE_EVT_RISING      		(0x11120000U)	/* 外部事件,上升沿触发检测 */
#define  GPIO_MODE_EVT_FALLING        	(0x11220000U) 	/* 外部事件,下降沿触发检测 */
/* 外部事件,上升和下降双沿触发检测 */
#define  GPIO_MODE_EVT_RISING_FALLING 	(0x11320000U)
成员Pull用于配置上下拉电阻,有以下选择项:
#define  GPIO_NOPULL        			(0x00000000U)   /* 无上下拉 */
#define  GPIO_PULLUP        			(0x00000001U)   /* 上拉 */
#define  GPIO_PULLDOWN      			(0x00000002U)   /* 下拉 */
成员Speed用于配置GPIO的速度,有以下选择项:
#define  GPIO_SPEED_FREQ_LOW       	(0x00000002U)   	/* 低速 */
#define  GPIO_SPEED_FREQ_MEDIUM     	(0x00000001U)   /* 中速 */
#define  GPIO_SPEED_FREQ_HIGH     	(0x00000003U)  	/* 高速 */

函数返回值:

注意事项:
HAL库的EXTI外部中断的设置功能整合到此函数里面,而不是单独独立一个文件。这个我们到外部中断实验再细讲。
2. HAL_GPIO_WritePin函数
HAL_GPIO_WritePin函数是GPIO口的写引脚函数。其声明如下:
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx,
uint16_t GPIO_Pin, GPIO_PinState PinState);
函数描述:
用于设置引脚输出高电平或者低电平,通过BSRR寄存器复位或者置位操作。
函数形参:
形参1是端口号,可以选择范围:GPIOA~GPIOG。
形参2是引脚号,可以选择范围:GPIO_PIN_0到 GPIO_PIN_15。
形参3是要设置输出的状态,是枚举型有两个选择:GPIO_PIN_SET 表示高电平,GPIO_PIN_RESET表示低电平。
函数返回值:

3. HAL_GPIO_TogglePin函数
HAL_GPIO_TogglePin函数是GPIO口的电平翻转函数。其声明如下:
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
函数描述:
用于设置引脚的电平翻转,也是通过BSRR寄存器复位或者置位操作。
函数形参:
形参1是端口号,可以选择范围:GPIOA~GPIOG。
形参2是引脚号,可以选择范围:GPIO_PIN_0到 GPIO_PIN_15。
函数返回值:

本实验我们用到上面三个函数,其他的API函数后面用到再进行讲解。
GPIO输出配置步骤
1)使能对应GPIO时钟
STM32在使用任何外设之前,我们都要先使能其时钟(下同)。本实验用到PB5和PE5两个IO口,因此需要先使能GPIOB和GPIOE的时钟,代码如下:
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
2)设置对应GPIO工作模式(推挽输出)
本实验GPIO使用推挽输出模式,控制LED亮灭,通过函数HAL_GPIO_Init设置实现。
3)控制GPIO引脚输出高低电平
在配置好GPIO工作模式后,我们就可以通过HAL_GPIO_WritePin函数控制GPIO引脚输出高低电平,从而控制LED的亮灭了。
13.3.2 程序流程图
程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。本实验的程序流程图如下:
在这里插入图片描述

图13.3.2.1 跑马灯实验程序流程图
13.3.3 程序解析

  1. led驱动代码
    这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。LED驱动源码包括两个文件:led.c和led.h(正点原子编写的外设驱动基本都是包含一个.c文件和一个.h文件,下同)。
    下面我们先解析led.h的程序,我们把它分两部分功能进行讲解。
    LED灯引脚宏定义
    由硬件设计小节,我们知道LED灯在硬件上分别连接到PB5和PE5,再结合HAL库,我们做了下面的引脚定义。
    /* LED0 引脚定义 */
#define LED0_GPIO_PORT          		GPIOB
#define LED0_GPIO_PIN             	GPIO_PIN_5 
#define LED0_GPIO_CLK_ENABLE()    	do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)
/* LED1引脚定义 */
#define LED1_GPIO_PORT          		GPIOE
#define LED1_GPIO_PIN             	GPIO_PIN_5 
#define LED1_GPIO_CLK_ENABLE()    	do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   

这样的好处是进一步隔离底层函数操作,移植更加方便,函数命名更亲近实际的开发板。比如:当我们看到LED0_GPIO_PORT这个宏定义,我们就知道这是灯LED0的端口号;看到LED0_GPIO_PIN这个宏定义,就知道这是灯LED0的引脚号;看到LED0_GPIO_CLK_ENABLE这个宏定义,就知道这是灯LED0的时钟使能函数。大家后面学习时间长了就会慢慢熟悉这样的命名方式。
特别注意:这里的时钟使能函数宏定义,使用了do{ }while(0)结构,是为了避免在某些使用场景出错的问题(下同),详见《嵌入式单片机 C代码规范与风格》第六章第2点。

__HAL_RCC_GPIOx_CLK_ENABLE函数是HAL库的IO口时钟使能函数,x=A到G。
LED灯操作函数宏定义
为了后续对LED灯进行便捷的操作,我们为LED灯操作函数做了下面的定义。
/* LED端口操作定义 */
#define LED0(x)   do{ x ? \
                 HAL_GPIO_WritePin(LED0_GPIO_PORT,LED0_GPIO_PIN, GPIO_PIN_SET) : \
                 HAL_GPIO_WritePin(LED0_GPIO_PORT,LED0_GPIO_PIN, GPIO_PIN_RESET);\
                     }while(0)       /* LED0翻转 */
#define LED1(x)   do{ x ? \
                HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
                HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET);\
                     }while(0)       /* LED1翻转 */
/* LED电平翻转定义 */
#define LED0_TOGGLE()    do{ HAL_GPIO_TogglePin(LED0_GPIO_PORT, 
LED0_GPIO_PIN); }while(0)   /* LED0 = !LED0 */
#define LED1_TOGGLE()    do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, 
LED1_GPIO_PIN); }while(0)   /* LED1 = !LED1 */

LED0和LED1这两个宏定义,分别是控制LED0和LED1的亮灭。例如:对于宏定义标识符LED0(x),它的值是通过条件运算符来确定:
当x=0时,宏定义的值为HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET),也就是设置LED0_GPIO_PORT(PB5)输出低电平;
当!=0时,宏定义的值为HAL_GPIO_WritePin (LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET),也就是设置LED0_GPIO_PORT(PB5)输出高电平。
根据前述定义,如果要设置LED0输出低电平,那么调用宏定义LED0(0)即可,如果要设置LED0输出高电平,调用宏定义LED0(1)即可。宏定义LED1(x)同理。
LED0_TOGGLE和LED1_TOGGLE这三个宏定义,分别是控制LED0和LED1的翻转。这里利用HAL_GPIO_TogglePin函数实现IO口输出电平翻转操作。
下面我们再解析led.c的程序,这里只有一个函数led_init,这是LED灯的初始化函数,其定义如下:

/**
 * @brief       初始化LED相关IO口, 并使能时钟
 * @param       无
 * @retval      无
 */
void led_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    LED0_GPIO_CLK_ENABLE();                                 	/* LED0时钟使能 */
    LED1_GPIO_CLK_ENABLE();                                 	/* LED1时钟使能 */
    gpio_init_struct.Pin = LED0_GPIO_PIN;                	/* LED0引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;        	/* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                  /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      /* 高速 */
HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct);  	/* 初始化LED0引脚 */

    gpio_init_struct.Pin = LED1_GPIO_PIN;                 /* LED1引脚 */
    HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);  	/* 初始化LED1引脚 */

    LED0(1);                                                	/* 关闭 LED0 */
    LED1(1);                                                	/* 关闭 LED1 */
}

对LED灯的两个引脚都设置为中速上拉的推挽输出。最后关闭LED灯的输出,防止没有操作就亮了。
2. main.c代码
在main.c里面编写如下代码:

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"

int main(void)
{
    HAL_Init();                           	/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);	/* 设置时钟, 72Mhz */
    delay_init(72);                  		/* 延时初始化 */
    led_init();                        		/* 初始化LED */

    while (1)
    {
        LED0(0);                         	/* LED0 灭 */
        LED1(1);                         	/* LED1 亮 */
        delay_ms(500);
        LED1(1);                         	/* LED0 灭 */
        LED0(0);                         	/* LED1 亮 */
        delay_ms(500);
    }
}

首先是调用系统级别的初始化:初始化 HAL库、系统时钟和延时函数。接下来,调用led_init来初始化LED灯。最后在无限循环里面实现LED0和LED1间隔500ms交替闪烁一次。
13.4 下载验证
我们先来看看编译结果,如图13.4.1所示。
在这里插入图片描述

图13.4.1 编译结果
可以看到没有0错误,0警告。从编译信息可以看出,我们的代码占用FLASH大小为:5804字节(5442+362+28),所用的SRAM大小为:1928个字节(28+1900)。这里我们解释一下,编译结果里面的几个数据的意义:
Code:表示程序所占用FLASH的大小(FLASH)。
RO-data:即Read Only-data,表示程序定义的常量(FLASH)。
RW-data:即Read Write-data,表示已被初始化的变量(FLASH + RAM)
ZI-data:即Zero Init-data,表示未被初始化的变量(RAM)
有了这个就可以知道你当前使用的flash和ram大小了,所以,一定要注意的是程序的大小不是.hex文件的大小,而是编译后的Code和RO-data之和。接下来,大家就可以下载验证了。这里我们使用DAP仿真器(也可以使用其他调试器)下载。
下载完之后,运行结果如图13.4.2所示,可以看到LED灯的LED0和LED1交替亮。
在这里插入图片描述

图13.4.2 程序运行结果
至此,我们的跑马灯实验的学习就结束了,本章介绍了STM32F103的IO口的使用及注意事项,是后面学习的基础,希望大家好好理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/548241.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能Python-python3_9_7怎么用

介绍Python3.9.7及其用途 Python是一种高级编程语言,已成为Web开发、数据科学、机器学习等领域中最广泛使用的语言之一。Python3.9.7是Python的最新版本,于2021年9月6日发布。它包括各种新的特性、改进和安全性实现,提高了Python应用程序的稳…

计算机图形学-GAMES101-13

Ray Tracing (1)为什么使用光线追踪 Ray Tracing 和 Rasterization 是两种不同的成像方式。Rasterization最大的问题:不利于表达全局效果。整体来说光栅化做阴影是比较困难的。Glossy reflection:一种不那么光滑的反射镜面。Ind…

在 Linux 上使用 yuzu 模拟 Nintendo Switch 试玩王国之泪

王国之泪5月12日发售,DLC 玩家已经造出各种脑洞大开的东西了,但是买的卡带迟迟没有收到,因此,打算使用 yuzu 模拟器先体验一下 yuzu 是一款开源的 Ninetendo Switch 模拟器,支持在 Linux 或者 Windows 平台运行&#…

GoLand 2023 Crack函数的支持

GoLand 2023 Crack函数的支持 增加了对“MIN_BY”和“MAX_BY”函数的支持。 更新了Prisma插件previewFeatures以包含jsonProtocol。 改进了与角度相关的符号的文档-添加了更多关于管道、特性和指令的文档。当您将鼠标悬停在符号上或调用显示文档完成时(F1/CtrlQ),您…

linux工作目录切换命令文件查看及管理命令

1、查看用户工作目录 2、切换工作目录 这里使用cd命令即可,输入对应的路径就可切换。 如果要返回上一次所处的目录输入 cd - 如果要返回上层目录使用 cd … 返回用户家目录使用 cd ~ 3、查看目录中文件信息命令 使用ls命令可以查看目录中文件信息-a 参数可以…

Windows 和 Linux 环境下 ProtoBuf 的安装

文章目录 一、ProtoBuf 在 Windows 环境中的安装二、ProtoBuf 在 Linux 环境中的安装 ProtoBuf在GitHub上的下载地址 一、ProtoBuf 在 Windows 环境中的安装 首先选择自己要下载的版本,我选择的是v21.11: 点进去在最下面选择Windows的版本&#xff0…

New:dbForge Edge 2023 4in1 Enterprise Edition Crack

dbForge Edge 2023 4in1 Enterprise Edition 赋予自己开发和管理 SQL Server、MySQL、Oracle 和 PostgreSQL 数据库的广泛能力 dbForge Edge:您的终极多数据库解决方案 让我们来看看。您需要处理多个数据库管理系统。同时,您希望能够灵活有效地处理范围广…

jface

JFace 是建立在 SWT 之上的 UI 部件,它是 SWT 的扩展并能和SWT交互。 ApplicationWindow和Action org.eclipse.jface.window.ApplicationWindow; JFace为了简化窗口的设计特别设计了类,比如ApplicationWindow这一个类,它里面包含了六个默认…

5年测试12月被裁,准备3个月终上岸阿里25K,面试时差点被问哭···

我的个人背景非常简单,也可以说丝毫没有亮点。 学历普通,计算机专业二本毕业,毕业后出来就一直在一家小公司,岁月如梭细,算了下至今从事软件测试已经5年了,也点点点了五年,每天都是重复的工作&…

不用再找了,你要的国内好用的ChatGPT网站都在这里

💡 大家好,我是可夫小子,关注AIGC、读书和自媒体。解锁更多ChatGPT、AI绘画玩法。加:keeepdance,备注:chatgpt,拉你进群。 目录 ChatGPT是什么 OpenAI与ChatGPT的发展历程 AI对话聊天 AI文档…

GWAS分析中的GO和KEGG富集分析

上一次,我们介绍如何根据显著性snp,使用bedtools根据上下游距离,根据gff文件注释基因。 这一次,介绍一下如何根据注释的基因,进行富集分析,主要是看一下GWAS定位的基因有没有某一个趋势,也算是…

Python泰裤辣丨520写一个自动换壁纸软件,将女友照骗放进去送给她

Python泰裤辣!520写一个自动换壁纸软件,将女友照骗放进去送给她! 准备工作1、环境2、使用的模块3、如何配置pycharm里面的python解释器?4、pycharm如何安装插件? 代码实战1、获取壁纸 自动更换壁纸程序最后 话说兄弟们,今天520你们都送给女朋友啥礼物了…

文件上传之,waf绕过(24)

上传参数名解析:明确哪些东西可以修改 content-disposition:一般可更改 表单的数据 name:表单参数值,不能更改 表单提交的值 filename:文件名,可以修改 上传的文件名 content-type:文件mime,…

算法学习(数组和字符串) 之数组简介

集合、列表和数组 1.集合: 集合一般被定义为由一个或多个确定的元素所构成的整体。通俗来讲,集合就是将一组事物组合在一起。你可以将商店里的所有物品当成一个集合。 特点: 集合里的元素没有顺序集合里的元素类型不一定相同 2.列表&…

【Python入门篇】——Python中循环语句(for循环的基础语法)

作者简介: 辭七七,目前大一,正在学习C/C,Java,Python等 作者主页: 七七的个人主页 文章收录专栏: Python入门,本专栏主要内容为Python的基础语法,Python中的选择循环语句…

Foley Sound——DECASE项目——项目复现

文章目录 概述项目复现配置环境下载并配置文件运行代码第一阶段,训练提取DTFR特征的模型资料搜集 train_vqvae.py 第二阶段,使用训练好的模型提取声音的DTFR特征torch.cuda.OutOfMemoryError: CUDA out of memory. 第三阶段,基于特征训练合成…

学习Netty BootStrap的核心知识,成为网络编程高手!

0 定义 深入 ChannelPipeline、ChannelHandler 和 EventLoop 后,如何将这些部分组织起来,成为可运行的应用程序? 引导(Bootstrapping)!引导一个应用程序是指对它进行配置,并使它运行起来的过程…

AcWing算法提高课-1.3.8多重背包问题 III

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 本题链接&#xff08;AcWing&#xff09; 点这里 题目描述 有 N N N 种物品和一个容量是 V V V 的背包。 第 i i i 种物品最多有 s i s_i si​ 件&#xff0c;每件体积是 v i v_i vi​…

我们到底该怎么入门网络安全?一般人还真不行

我之前写了不少网络安全技术相关的故事文章&#xff0c;不少读者朋友知道我是从事网络安全相关的工作&#xff0c;于是经常有人在微信里问我&#xff1a;&#xff08;文末入门福利&#xff09; 我刚入门网络安全&#xff0c;该怎么学&#xff1f; 要学哪些东西&#xff1f; …