网站首页 > 技术文章 正文
GPIO输出驱动指示灯
实验目的
- 掌握STM32 GPIO输出驱动原理。
- 掌握LED驱动电路的设计:控制方式、限流电阻的计算和需要考虑的因素。
- 掌握对单个和多个指示灯的驱动程序设计及程序控制方式。
实验内容
- 编写程序控制单个指示灯闪烁。
- 编写具有通用性的驱动用户指示灯的函数。
- 编写程序实现流水灯功能。
硬件电路设计
开发板指示灯硬件电路
LED(Light Emitting Diode)是发光二极管的简称,在很多设备上常用它来作为一种简单的人机接口,如网卡、路由器等通过LED向用户指示设备的不同工作状态。所以,我们习惯把这种用于指示状态的LED称为LED指示灯。
IK-ZET6开发板上设计了4个LED指示灯,我们可以通过编程驱动LED指示灯点亮、熄灭、闪烁,从而达到状态指示的目的,LED指示灯驱动电路如下图所示。
图1:LED指示灯驱动电路
- 4个LED指示灯占用的单片机的引脚如下表:
表1:LED引脚分配
LED | 颜色 | 引脚 | 说明 |
D1 | 蓝色 | PG6 | 独立GPIO |
D2 | 蓝色 | PA8 | 独立GPIO |
D3 | 蓝色 | PB8 | 独立GPIO |
D4 | 蓝色 | PB9 | 独立GPIO |
- 注:独立GPIO表示开发板没有其他的电路使用这个GPIO。
LED指示灯驱动电路是一个很常见、简单的电路,但它也是一个典型的单元电路,对于初学者来说,类似常用的典型电路必须要掌握,不但要知其然、还要知其所以然。
接下来,我们来分析一下这个简单的LED指示灯驱动电路。
LED驱动电路设计的时候,要考虑两个方面:控制方式和限流电阻的选取。
控制方式
LED指示灯控制方式分为高电平有效和低电平有效两种,高电平有效是单片机GPIO输出高电平时点亮LED,低电平有效是单片机GPIO输出低电平时点亮LED。
低电平有效的控制方式
图2:LED控制-低电平有效原理
低电平有效控制方式中,当单片机的GPIO输出低电平(逻辑0)的时候,LED和电阻R上的压降等于(VCC-VCCIO = 3.3V),这时候,因为存在压降,同时,这个电路是闭合回环的,这就达到了电流产生的两个要素,LED上会有电流流过,LED被点亮。
当单片机的GPIO输出高电平(逻辑1)的时候,LED和电阻R上的压降等于(VCC-VCCIO = 0V),这时候,因为LED上没有压降,当然不会有电流流过,所以LED熄灭。
高电平有效的控制方式
图3:LED控制-高电平有效原理
高电平有效控制方式中,由单片机的GPIO输出电流驱动LED,当单片机的GPIO输出高电平(逻辑1)的时候,LED上存在压降,会有电流流过,这时LED被点亮,但要注意,单片机的GPIO要能提供足够的输出电流,否则,电流过小,会导致LED亮度很弱。
当单片机的GPIO输出低电平(逻辑0)的时候,LED和电阻R上的压降等于0V,这时候,LED上没有电流流过,LED熄灭。
选择哪种方式来控制LED
绝大多数情况下,我们会选择使用低电平有效的控制方式,如艾克姆科技IK-ZET6开发板中的LED指示灯就是低电平有效。这样设计指示灯驱动电路的好处是:
- 单片机GPIO口低电平时的灌入电流一般比高电平时的拉电流要大,能提供足够的电流驱动LED。
- 单片机上电或复位启动时,GPIO口一般都是高阻输入,用低电平有效的控制方式可以确保LED在上电或复位启动时处于熄灭状态。
- 注:当单片机是5V单片机时选择指示灯驱动的原理是相通的。
LED限流电阻的选取
限流电阻的计算
图4:LED限流电阻计算
由上图可以看出,LED限流电阻的计算公式如下:
Ω
其中,VCC=3.3V,VF是LED的正向压降,LED的数据手册都会给出正向电流为20mA时测试的VF的范围,下图是一款0603 LED的实物图和参数。在参数表中可以看到正向电流为20 mA时VF最小值是1.6V,典型值是2.0V,最大值是2.6V。
0603 LED
图5:0603 LED参数图
计算时VF的值可以用典型值来进行估算,对于电流,需要根据经验值和对LED亮度的要求相结合来确定,一般经验值是(2~5)mA,不过要注意,只要亮度符合自己的要求,电流低于2mA也没有任何问题。
电流为2mA时限流电阻值计算如下:
Ω = 650Ω
限流电阻的选择
根据上一节对限流电阻计算公式的描述及对选择的0603指示灯参数的了解,计算出限流电阻的理论值是650Ω,但650Ω不是一个常用的电阻值,所以我们需要选择一个电阻值为650Ω左右的常用的电阻器。
在IK-ZET6开发板上,我们选择的限流电阻的电阻值是最常用的1K的电阻器,选择限流电阻后,还需要实际测试观察LED的亮度是否符合自己的需求,经过实际测试观察,IK-ZET6开发板上使用1K限流电阻时亮度符合我们的要求,由此,限流电阻的阻值确定为1K。当然,如果实际项目中觉得亮度不够,可以将电阻值适当减小一些,如使用680Ω或510Ω的电阻器作为限流电阻。
还有一点需要强调的是,不同颜色的指示灯在即使同一亮度时所需的限流电阻不一定是相同的。这也是为什么有些产品的面板上有不同颜色的指示灯,各指示灯所使用的限流电阻不一样的原因。
软件设计
GPIO寄存器汇集
STM32F103提供了10个用于操作GPIO的寄存器,如下表所示:
表2:GPIO相关寄存器
序号 | 寄存器名 | 读/写 | 功能描述 |
1 | GPIOx_CRL | 读/写 | 端口配置低寄存器。 |
2 | GPIOx_CRH | 读/写 | 端口配置高寄存器。 |
3 | GPIOx_IDR | 只读 | 端口输入数据寄存器。 |
4 | GPIOx_ODR | 读/写 | 端口输出数据寄存器。 |
5 | GPIOx_BSRR | 只写 | 端口位设置/清除寄存器。 |
6 | GPIOx_BRR | 只写 | 端口位清除寄存器。 |
7 | GPIOx_LCKR | 读/写 | 端口配置锁定寄存器。 |
8 | AFIO_EVCR | 读/写 | 事件控制寄存器。 |
9 | AFIO _MAPR | 读/写 | 复用重映射和调试 I/O 配置寄存器。 |
10 | AFIO _EXTICRy | 读/写 | 外部中断线路 0-15 配置寄存器。 |
- 注:x取值:A、B、C、D、E、F、G , y取值:1、2、3、4。
每一种寄存器详细的描述在这里不做具体的介绍,大家可以参考目录:“第1部分:开发板硬件资料”--->“2 - 芯片资料”中“STM32英文参考手册_V15”或“STM32中文参考手册_V10”对应的GPIO章节的寄存器部分认真研读。
寄存器版本编写程序
首先普及一个常用知识点:为什么说STM32F103系列微处理器是32位微处理器呢?这个32位指的是什么?
一般来说某个单片机或微处理器是几位,指的是“机器字长”。每个单片机或微处理器最基本的功能是算术逻辑运算,而算术逻辑运算的主要部件是“算术逻辑单元(ALU)”。机器字长即是指ALU的数据位宽,也就是指令能直接处理的二进制位数。
通常单片机或微处理器的寄存器的位宽等于ALU的位宽,所以一般可通过识别单片机或微处理器寄存器的位宽来确定该单片机或微处理器是多少位的。大家学过STC的单片机,知道其各系列的寄存器都是8位的,所以STC的单片机都是8位的单片机。
下面我们举例配置主控芯片引脚PE3和PA8来简单学习下STM32的寄存器版本程序设计。
将PE3引脚配置为上拉输入模式
第1步: GPIOE_CRL寄存器MODE3[1:0]位赋值为00。
第2步: GPIOE_CRL寄存器CNF3[1:0]位赋值为10。
第3步: GPIOE_ODR寄存器ODR3位赋值为1。
下图是对端口配置低寄存器GPIOx_CRL的描述,从该寄存器各位代表的含义可知,该寄存器可以对STM32F103ZET6的PA0~PA7、PB0~PB7、PC0~PC7、PD0~PD7、PE0~PE7、PF0~PF7、PG0~PG7端口进行配置。
图6:端口配置低寄存器
若仅仅配置GPIOx_CRL寄存器只能实现上拉/下拉输入模式的配置,无法再进一步确定是上拉输入模式还是下拉输入模式。所以还需结合GPIOx_ODR寄存器来进一步确定。如下图所示,确定了上拉/下拉输入模式后再将GPIOx_ODR相应位置为1可实现上拉输入。
图7:GPIO端口配置
下图是GPIOx_ODR寄存器的介绍,需要注意的是该寄存器是32位的,但只使用了寄存器的低16位,高16位为保留位。保留的含义是将来可能会赋予高16位一些功能,但至少现在没有用,也就是操作该寄存器时,如果操作了高16位是没有意义的。
图8:端口输出数据寄存器
寄存器版本代码清单:配置PE3引脚为上拉输入模式
- GPIOE->CRL&=0xFFFF0FFF; //将GPIOE_CRL寄存器的MODE3[1:0]位赋值为00,CNF3[1:0]位赋值为00
- GPIOE->CRL|=0x00008000; //将GPIOE_CRL寄存器的CNF3[1:0]位赋值为10
- GPIOE->ODR|=0x00000008; //将GPIOE_ODR寄存器的ODR3位赋值为1
将PA8引脚配置为通用推挽模式且最高输出速率50MHZ
第1步:GPIOE_CRH寄存器MODE8[1:0] 位赋值为11。
第2步:GPIOE_CRH寄存器CNF8[1:0]位赋值为00。
下图是对端口配置低寄存器GPIOx_CRH的描述,从该寄存器各位代表的含义可知,该寄存器可以对STM32F103ZET6的PA8~PA15、PB8~PB15、PC8~PC15、PD8~PD15、PE8~PE15、PF8~PF15、PG8~PG15端口进行配置。
图9:端口配置高寄存器
通过上图关于GPIOx_CRH寄存器的描述可知,可配置GPIOA_CRH寄存器的MODE8[1:0]位赋值为11,CNF8[1:0]位赋值为00,便可实现配置PA8引脚为通用推挽模式且最大速度50MHZ。
寄存器版本代码清单:配置PA8引脚为通用推挽模式,最高输出速率50MHZ
- GPIOA->CRH&=0xFFFFFFF0; //将GPIOA_CRH寄存器的MODE8[1:0]位赋值为00,CNF8[1:0]位赋值为00
- GPIOA->CRH|=0x00000003; //将GPIOA_CRH寄存器的MODE8[1:0]位赋值为11
- 总结:
1、寄存器版本编写代码是直接操作的寄存器,如果结合手册中的寄存器介绍,会很容易读懂程序,知道实际控制了微处理器的什么地方。
2、以前学习8位单片机基本都是直接操作寄存器,很少使用库函数。当然,使用8位单片机的程序不会特别特别的复杂,而使用STM32微处理器开发的程序可能会非常复杂,这个时候使用寄存器版本会存在一些劣势:比如更容易出错,可读性较差,可移植性不强,大大增加开发周期等。
3、综合利弊,艾克姆科技还是决定以库函数的方式编写程序。需要强调的一点,库函数只是芯片厂家为了帮助用户快速开发而设计的一个“管家”,实际操作寄存器的是这个“管家”,而你只需要按照“管家”的“规矩”给其发送命令即可,通常这些命令的学习更易让人上手也更易大家共享。假如换一个芯片厂家,就换了一个“管家”和“规矩”,所以不要在库函数学习上花太多时间,重要的是要了解芯片的各个外设的原理。因为即使厂家不一样了,基本外设的原理是相通的,原理了解的透彻了,当遇到问题时,你甚至能反推到哪块程序出问题了、哪个寄存器没有配置正确等等。
GPIO库函数版本编写程序
官方提供的最终库函数版本是V3.5版本,该版本库函数提供了17个与GPIO操作有关的库函数,如下表所示:
表3:GPIO相关库函数汇集
序号 | 函数名 | 功能描述 |
1 | GPIO_DeInit | 将外设 GPIOx 寄存器重设为缺省值。 |
2 | GPIO_AFIODeInit | 将复用功能(重映射事件控制和 EXTI设置)重设为缺省值。 |
3 | GPIO_Init | 将GPIO_InitStruct中指定参数初始化外设 GPIOx 寄存器。 |
4 | GPIO_StructInit | 把GPIO_InitStruct中的每一个参数按缺省值填入。 |
5 | GPIO_ReadInputDataBit | 读取指定端口管脚的输入。 |
6 | GPIO_ReadInputData | 读取指定的 GPIO 端口输入。 |
7 | GPIO_ReadOutputDataBit | 读取指定端口管脚的输出。 |
8 | GPIO_ReadOutputData | 读取指定的 GPIO 端口输出。 |
9 | GPIO_SetBits | 设置指定的数据端口位。 |
10 | GPIO_ResetBits | 清除指定的数据端口位。 |
11 | GPIO_WriteBit | 设置或者清除指定的数据端口位。 |
12 | GPIO_Write | 向指定 GPIO 数据端口写入数据。 |
13 | GPIO_PinLockConfig | 锁定 GPIO 管脚设置寄存器。 |
14 | GPIO_EventOutputConfig | 选择 GPIO 管脚用作事件输出。 |
15 | GPIO_EventOutputCmd | 使能或者失能事件输出。 |
16 | GPIO_PinRemapConfig | 改变指定管脚的映射。 |
17 | GPIO_EXTILineConfig | 选择 GPIO 管脚用作外部中断线路。 |
- 注:x取值:A、B、C、D、E、F、G 。
每一种寄存器详细的描述在这里不做具体的介绍,大家可以参考目录:“第1部分:开发板硬件资料”--->“2 - 芯片资料”中“STM32固件库使用手册的中文翻译版”对应的GPIO章节的库函数部分认真研读。
下面我们举例配置主控芯片引脚PE3和PA8来简单学习下STM32的库函数版本程序设计。
将PE3引脚配置为上拉输入模式
第1步:配置库函数GPIO_Init的入参1,选择GPIO外设是GPIOE。
第2步:配置库函数GPIO_Init的入参2,选择GPIO管脚是GPIO_Pin_3。
第3步:配置库函数GPIO_Init的入参2,选择GPIO管脚工作状态是GPIO_Mode_IPU。
下图是对GPIO_Init函数的描述,从该函数的入参可知,可通过确定GPIO外设端口、管脚号、需配置的模式、工作速度等参数的赋值来设定指定端口的模式配置。通过调用这个函数,就可以很方便地实现所需配置功能,而无需再去操作寄存器。
图10:库函数GPIO_Init
库函数版本代码清单:实现对PE3引脚配置为上拉输入模式
- //定义IO初始化配置结构体
- GPIO_InitTypeDef GPIO_InitStructure;
- //配置的IO是PE3
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
- //配置为上拉输入模式
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- //调用库函数GPIO_Init()配置IO
- GPIO_Init(GPIOE, &GPIO_InitStructure);
将PA8引脚配置为通用推挽模式且最高输出速率50MHZ
第1步:配置库函数GPIO_Init的入参1,选择GPIO外设是GPIOA。
第2步:配置库函数GPIO_Init的入参2,选择GPIO管脚是GPIO_Pin_8。
第3步:配置库函数GPIO_Init的入参2,选择最大输出速率是GPIO_Speed_50MHZ。
第4步:配置库函数GPIO_Init的入参2,选择GPIO管脚工作状态是GPIO_Mode_Out_PP。
库函数版本代码清单: P配置A8引脚为通用推挽模式且最高输出速率为50MHZ
- //配置的IO是PA8
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
- //IO口速度为50MHz
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- //配置为推挽输出
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- //配置IO
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- 思考题1:在上述对PE3引脚配置为上拉输入模式时,并没有对库函数GPIO_Init的入参GPIO_InitStructure.GPIO_Speed进行配置,想一想为什么?会有影响吗?
- 思考题2:下图是库函数GPIO_Init使用手册的举例,个人观点,这个例子没有问题,但因为加了对引脚输出最高速率的赋值,容易给人误导,认为引脚的输入配置也需要对GPIO_InitStructure.GPIO_Speed这个参数赋值,为什么说即使加了对GPIO_Speed的赋值也不会影响待配置引脚的浮空输入的设置,大家想一想为什么?
图11:库函数GPIO_Init使用手册举例
- 备注:
1、微处理器GPIO管脚的最大输出速率并不是越大越好,只要可以满足要求,最大输出速率越小,则功耗就会越小。
2、微处理器GPIO管脚的最大输出速率是指GPIO管脚驱动电路的响应速度而不是输出信号的速度,输出信号的速度与应用程序有关。
驱动指示灯闪烁
- 注:本节对应的实验源码是:“实验1-1:GPIO输出驱动指示灯闪烁”。
工程需要用到的库文件
在使用库函数建“实验1-1:GPIO输出驱动指示灯闪烁”工程时,需要用到的c文件如下表所示。
表4:实验需要用到的C文件
序号 | 文件名 | 后缀 | 功能描述 |
1 | stm32f10x_rcc | .c | 复位与时钟控制器。 |
2 | stm32f10x_gpio | .c | 通用输入输出。 |
- 注:所建工程必须添加上面c文件。
可按下图所示在新建工程时添加需要的c文件。
图12:在新建工程中添加所需库函数c文件
表5:实验需要用到的H文件
序号 | 文件名 | 后缀 | 功能描述 |
1 | stm32f10x_rcc | .h | 复位与时钟控制器。 |
2 | stm32f10x_gpio | .h | 通用输入输出。 |
- 注:所建工程必须添加上面h文件所在的目录:..\..\ Lib\ F10x_FWLIB\ inc。
在使用该实验工程时,需要用到的h文件如表5所示。需要在MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
图13:如何添加头文件包含路径
编写代码
首先是调用库函数完成用户指示灯D1的初始化配置,初始化完成后,调用设置指定的数据端口位库函数GPIO_SetBits实现对所用GPIO口的电平置高操作,从而达到熄灭指示灯D1的目的。
代码清单:初始化控制指示灯D1的GPIO引脚,并控制初始状态熄灭指示灯D1
- /***************************************************************************
- * 描 述 : 初始化单片机控制D1的引脚PG6,并将D1的初始状态设置为熄灭
- * 参 数 : 无
- * 返回值 : 无
- **************************************************************************/
- void led_init(void)
- {
- //定义IO初始化配置结构体
- GPIO_InitTypeDef GPIO_InitStructure;
- //打开PA端口时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);
- //配置的IO是PG6
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
- //IO口速度为50MHz
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- //配置为通用推挽输出
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- //调用库函数GPIO_Init()配置IO
- GPIO_Init(GPIOG, &GPIO_InitStructure);
- //设置D1初始化状态为熄灭
- GPIO_SetBits(GPIOG,GPIO_Pin_6);
- }
最后,在主函数中加入用于驱动指示灯D1的GPIO口的初始化操作,之后循环执行将用户指示灯D1点亮,延时200ms,再熄灭,再延时200ms的过程,这样可出现指示灯D1不停闪烁的现象。
代码清单:主函数
- int main(void)
- {
- //初始化用于驱动指示灯D1的引脚PG6
- led_init();
- //主循环
- while(1)
- {
- //调用库函数GPIO_ResetBits()驱动LED指示灯D1的引脚(PG6)输出低电平,即点亮D1
- GPIO_ResetBits(GPIOG,GPIO_Pin_6);
- //软件延时200ms
- sw_delay_ms(200);
- //调用库函数GPIO_SetBits()驱动LED指示灯D1的引脚(PG6)输出高电平,即熄灭D1
- GPIO_SetBits(GPIOG,GPIO_Pin_6);
- //软件延时200ms
- sw_delay_ms(200);
- }
- }
实验步骤
- 解压“…\第3部分:标准库教程和实验源码\ 1 - 基础实验程序\”目录下的压缩文件“实验1-1:GPIO输出驱动指示灯闪烁”,将解压后得到的文件夹拷贝到合适的目录,如“D\STM32F103ZET6”。
- 启动MDK5.23。
- 在MDK5中执行“Project→Open Project”打开“…\ led_blinky \projec”目录下的工程“led_blinky.uvproj”。
- 点击编译按钮编译工程。注意查看编译输出栏,观察编译的结果,如果有错误,修改程序,直到编译成功为止。编译后生成的HEX文件“led_blinky.hex”位于工程目录下的“Objects”文件夹中。
- 点击下载按钮下载程序 。如果需要对程序进行仿真,点击Debug按钮,即可将程序下载到STM32F103ZET6中进行仿真。
- 程序运行后,可以观察到D1指示灯以200ms的间隔闪烁。
编写LED驱动函数
- 注:本节对应的实验源码是:“实验1-2:编写LED驱动函数”。
工程需要用到的库文件
在使用库函数建“实验1-2:编写LED驱动函数”工程时,需要用到的c文件以及添加头文件包含路径的方法与介绍“实验1-1:GPIO输出驱动指示灯闪烁”完全一样,在此不再赘述。
编写代码
首先在建立“实验1-2:编写LED驱动函数”工程时,新建了led.c的文件,专门用于封装和4个用户LED有关的所有函数。具体如下表6所示。
表6:用户LED相关函数汇集(详见led.c)
序号 | 函数名 | 功能描述 |
1 | leds_init | 初始化用于驱动开发板上的4个指示灯的引脚。 |
2 | leds_on | 点亮开发板上的4个指示灯。 |
3 | leds_off | 熄灭开发板上的4个指示灯。 |
4 | led_on | 点亮一个指定的LED。 |
5 | led_off | 熄灭一个指定的LED。 |
6 | led_toggle | 翻转一个指定的LED的状态。 |
- 注:后面3个函数有入参,入参值可选:LED_1、LED_2、LED_3、LED_4 。
下面仅仅简单介绍下leds_init和led_toggle两个函数。针对led_toggle函数,是使用了直接操作寄存器的部分,这样可以使控制灵活方便。
代码清单:初始化4个用户指示灯的GPIO引脚,并控制初始状态
- /**********************************************************************************************
- * 描 述 : 初始化单片机控制D1、D2、D3、D4的引脚PG6、PA8、PB8、PB9,并4个用户LED的初始状态设置为熄灭
- * 参 数 : 无
- * 返回值 : 无
- *********************************************************************************************/
- void leds_init(void)
- {
- //定义IO初始化配置结构体
- GPIO_InitTypeDef GPIO_InitStructure;
- //打开PA PB PG端口时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOG, ENABLE);
- //配置的IO是PG6
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
- //IO口速度为50MHz
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- //配置为推挽输出
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- //配置IO
- GPIO_Init(GPIOG, &GPIO_InitStructure);
- //配置的IO是PA8
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
- //IO口速度为50MHz
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- //配置为推挽输出
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- //配置IO
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- //配置的IO是PB8、PB9
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
- //IO口速度为50MHz
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- //配置为推挽输出
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- //配置IO
- GPIO_Init(GPIOB, &GPIO_InitStructure);
- //设置D1、D2、D3、D4初始化状态为熄灭
- leds_off();
- }
led_toggle函数有入参,入参可选LED_1、LED_2、LED_3、LED_4。入参值也是宏定义方便用户调用的,而不用再去考虑具体使用的是哪个GPIO口,该如何配置。之后只要是用到艾克姆科技IK-ZET6这款开发板,只要有用到指示灯,就可以把led.c直接拷贝过去,方便、省事又可靠。
代码清单:翻转一个指定的用户LED的状态
- /***************************************************************************
- * 描 述 : 翻转一个指定的LED的状态
- * 参 数 : [IN]led_idx:LED 对应的引脚编号
- * 返回值 : 无
- ***************************************************************************/
- void led_toggle(uint32_t led_idx)
- {
- //取得引脚的编号
- uint16_t pin_num = (uint16_t)(led_idx&0xFFFF);
- //区分数据端口,通过对ODR寄存器相应的位异或运算翻转对应的IO输出状态
- if(led_idx == LED_1)
- {
- GPIOG->ODR ^= pin_num;
- }
- if(led_idx == LED_2)
- {
- GPIOA->ODR ^= pin_num;
- }
- if((led_idx == LED_3) || (led_idx == LED_4))
- {
- GPIOB->ODR ^= pin_num;
- }
- }
最后,在主函数中加入用于翻转指示灯D1的操作,可通过是否对USE_LED_TOGGLE的宏定义,来观察指示灯D1不停闪烁的间隔时间。
代码清单:主函数
- int main(void)
- {
- //初始化用于驱动指示灯D1 D2 D3 D4的引脚PG6 PA8 PB8 PB9
- leds_init();
- //主循环
- while(1)
- {
- #ifdef USE_LED_TOGGLE
- //led_toggle实现指示灯闪烁:每1000毫秒翻转一次指示灯D1的状态
- led_toggle(LED_1);
- sw_delay_ms(1000);
- #else
- //点亮指示灯D1
- led_on(LED_1);
- //软件延时200ms
- sw_delay_ms(200);
- //熄灭指示灯D1
- led_off(LED_1);
- sw_delay_ms(200);
- #endif
- }
- }
实验步骤
- 解压“…\第3部分:标准库教程和实验源码\ 1 - 基础实验程序\”目录下的压缩文件“实验1-2:编写LED驱动函数”,将解压后得到的文件夹拷贝到合适的目录,如“D\STM32F103ZET6”。
- 启动MDK5.23。
- 在MDK5中执行“Project→Open Project”打开“…\ led_blinky \projec”目录下的工程“led_blinky.uvproj”。
- 点击编译按钮编译工程 。注意查看编译输出栏,观察编译的结果,如果有错误,修改程序,直到编译成功为止。编译后生成的HEX文件“led_blinky.hex”位于工程目录下的“Objects”文件夹中。
- 点击下载按钮下载程序 。如果需要对程序进行仿真,点击Debug按钮 即可将程序下载到STM32F103ZET6进行仿真。
- 程序运行后,可以观察到LED指示灯D1以1000ms的间隔闪烁。
- 屏蔽“#define USE_LED_TOGGLE”,编译程序并下载到开发板运行。可以观察到LED指示灯D1以200ms的间隔闪烁。
- 思考题1:分析下驱动LED以一定间隔闪烁用led_toggle()函数是不是更方便?为什么?
跑马灯
- 注:本节对应的实验源码是:“实验1-3:跑马灯”。
工程需要用到的库文件
在使用库函数建“实验1-3:跑马灯”工程时,需要用到的c文件以及添加头文件包含路径的方法与介绍“实验1-1:GPIO输出驱动指示灯闪烁”完全一样,在此不再赘述。
编写代码
首先建立工程将led.c添加进来,这样可以直接调用led.c中所有的函数,这些函数在“实验1-2:编写LED驱动函数”中有详细介绍,在此也不再介绍。跑马灯的实验主要是4个用户指示灯流水点亮,下面介绍main函数。
代码清单:主函数
- int main(void)
- {
- //初始化用于驱动指示灯D1 D2 D3 D4的引脚PG6 PA8 PB8 PB9
- leds_init();
- //主循环
- while(1)
- {
- //4个LED轮流点亮循环5次
- for(uint8_t i=0; i<LEDS_NUMBER; i++)
- {
- led_on(led_list[i]);
- sw_delay_ms(100);
- led_off(led_list[i]);
- sw_delay_ms(70);
- }
- }
- }
实验步骤
- 解压“…\第3部分:标准库教程和实验源码\ 1 - 基础实验程序\”目录下的压缩文件“实验1-3:跑马灯”,将解压后得到的文件夹拷贝到合适的目录,如“D\STM32F103ZET6”。
- 启动MDK5.23。
- 在MDK5中执行“Project→Open Project”打开“…\ led_blinky \projec”目录下的工程“led_blinky.uvproj”。
- 点击编译按钮编译工程。注意查看编译输出栏,观察编译的结果,如果有错误,修改程序,直到编译成功为止。编译后生成的HEX文件“led_blinky.hex”位于工程目录下的“Objects”文件夹中。
- 点击下载按钮下载程序 。如果需要对程序进行仿真,点击Debug按钮,即可将程序下载到STM32F103ZET6中进行仿真。
- 程序运行后,4个LED指示灯按照一定规律轮流闪烁,即流水灯。
猜你喜欢
- 2024-09-10 【STM32F103ZET6开发板】第2-5讲:外部中断EXTI
- 2024-09-10 关于Linux 进程编程入门(进阶)(linux进程代码)
- 2024-09-10 「STC8A8K64D4开发板」第2-12讲:数码管显示
- 2024-09-10 【STM32F103ZET6开发板】第2-6讲:USART串口通信
- 2024-09-10 【STM32F103ZET6开发板】第2-3讲:GPIO输出驱动蜂鸣器
- 2024-09-10 【STM32F103ZET6开发板】第2-2讲:GPIO输入按键检测
- 2024-09-10 【STM32F103ZET6开发板】第3-7讲:电子墨水屏显示
- 2024-09-10 【STM32F103ZET6开发板】第2-4讲:触摸按键输入检测
- 2024-09-10 【STC8A8K64D4开发板】——第2-2讲:有源蜂鸣器鸣响控制
- 2024-09-10 MySQL数据库审计核心实现(内有代码)
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)