日期:2025/04/07 13:22来源:未知 人气:58
学习目的
掌握LED驱动电路的设计:控制方式、限流电阻的计算和确定。
掌握STC8A8K64D4单片机GPIO口四种工作模式。
编写驱动LED指示灯点亮和熄灭的程序,再此基础上,更进一步,编写流水灯的程序。
硬件电路设计
LED(Light Emitting Diode)是发光二极管的简称,在很多设备上常用他来作为一种简单的人机接口,如网卡、路由器等通过LED向用户指示设备的不同工作状态。所以,我们习惯把这种用于指示状态的LED称为LED指示灯。
IK-64D4开发板上设计了4个LED指示灯,我们可以通过编程驱动LED指示灯点亮、熄灭、闪烁,从而达到状态指示的目的,LED指示灯驱动电路如下图所示。
图1:LED指示灯驱动电路
4个LED指示灯占用的单片机的引脚如下表:
表1:LED引脚分配
LED指示灯驱动电路是一个很常见、简单的电路,但他也是一个典型的单元电路,对于初学者来说,类似常用的典型电路必须要掌握,不但要知其然、还要知其所以然。
接下来,我们来分析一下这个简单的LED指示灯驱动电路。
LED驱动电路设计的时候,需要我们考虑两个方面:控制方式和限流电阻的选取。
控制方式
LED指示灯控制方式分为高电平有效和低电平有效两种,高电平有效是单片机IO输出高电平时点亮LED,低电平有效是单片机IO输出低电平时点亮LED。
1.**低电平有效的控制方式**
图2:LED控制-低电平有效原理
低电平有效控制方式中,当单片机的GPIO输出低电平(逻辑0)的时候,LED和电阻R上的压降等于(VCC-VCCIO = 3.3V),这时候,因为存在压降,同时,这个电路是闭合回环的,这就达到了电流产生的两个要素,LED上会有电流流过,LED被点亮。
当单片机的GPIO输出高电平(逻辑1)的时候,LED和电阻R上的压降等于0V(VCC-VCCIO = 0V),这时候,因为LED上没有压降,当然不会有电流流过,所以LED熄灭。
低电平有效控制方式中,电流经过限流电阻和LED“灌入”GPIO,对于一般单片机来说,“灌电流”比较大,即低电平有效时驱动能力较强。
2.**高电平有效的控制方式**
图3:LED控制-高电平有效原理
高电平有效控制方式中,由单片机的GPIO输出电流驱动LED,当单片机的GPIO输出高电平(逻辑1)的时候,LED上存在压降,会有电流流过,这时LED被点亮,但要注意,单片机的GPIO要能提供足够的输出电流,否则,电流过小,会导致LED亮度很弱。
当单片机的GPIO输出低电平(逻辑0)的时候,LED和电阻R上的压降等于0V,这时候,LED上没有电流流过,LED熄灭。
3.**选择哪种方式来控制**LED
大多数情况下,我们会选择使用低电平有效的控制方式,如开发板中的LED指示灯就是低电平有效。这是因为:单片机IO低电平时的灌入电流一般比高电平时的拉电流要大,能提供足够的电流驱动LED,另外单片机上电或复位启动时,IO口一般都是高阻输入,用低电平有效的控制方式可以确保LED在上电或复位启动时处于熄灭状态。
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
计算时VF的值可以用典型值来进行估算,对于电流,需要根据经验值和对LED亮度的要求相结合来确定,一般经验值是(2~5)mA,不过要注意,只要亮度符合自己的要求,电流低于2mA也没有任何问题。
电流为2mA时限流电阻值计算如下:
Ω**= 650Ω**
计算出的电阻是650Ω,650Ω不是一个常用的电阻值,所以我们需要选择一个电阻值为650Ω左右常用的电阻器,在IK-64D4开发板上,我们选择的电阻值是最常用的1K的电阻器,选择限流电阻后,还需要实际测试观察LED的亮度是否符合自己的需求,经过实际测试观察,IK-64D4开发板上使用1K限流电阻时亮度符合我们的要求,由此,限流电阻的阻值确定为1K。当然,如果我们觉得亮度不够,可以将电阻值适当减小一些,如使用680Ω或510Ω的电阻器作为限流电阻。
GPIO输出驱动原理
GPIO数量
STC8A8K64D4系列单片机GPIO口数量取决于芯片的具体型号和封装,开发板使用的是STC8A8K64D4,封装为LQFP64,共有64个引脚。其中,电源和ADC参考电压使用了如下5个引脚,剩余的59个引脚均可配置为GPIO使用。
AVref+:ADC 外部参考电压源输入脚
ADC_AGnd:ADC地。
ADC_AVcc:ADC电源引脚
VCC:电源引脚。
GND:电源地。
STC8A8K64D4的59个GPIO分属8个端口P0~P7,如下表所示。这里,有必要说明一下,8个端口中,P4和P5的GPIO数量不是8个,其中P4是没有P4.5、P4.6和P4.7的,而P5是没有P5.6和P5.7的,读者使用GPIO时,应注意这一点。
表2:STC8A8K64D4单片机的GPIO
功能描述
STC8A8K64D4系列单片机所有GPIO口均有4种工作模式:准双向口/弱上拉(标准8051输出口模式)、推挽输出/强上拉、高阻输入(电流既不能流入也不能流出)、开漏输出,这4种工作模式是通过寄存器PnM1和PnM0的组合配置的(n为端口号)。
本章讲解是GPIO输出,和输出相关的工作模式有准双向口/弱上拉、推挽输出/强上拉、和开漏输出,接下来,我们细看一下这3种工作模式。
准双向口/弱上拉
准双向口(弱上拉)输出类型可用作输出和输入功能而不需重新配置端口输出状态,这是因为当端口输出为 1 时驱动能力很弱,允许外部装置将其拉低。
准双向口有3个上拉晶体管(强上拉、弱上拉和极弱上拉晶体管)适应不同的需要。为了方便描述,我们将这些晶体管分为两部分,如下图所示。
图5:准双向口I/O结构图
输出
当端口锁存数据为1时:A组晶体管中的3个上拉晶体管中的一个导通,此时B晶体管截止 ,由于上拉晶体管的作用,端口引脚输出1。
当端口锁存数据为 0 时:A组晶体管中的3个上拉晶体管中的一个导通,但此时B晶体管导通 ,因此,端口引脚输出0。
输入
准双向口(弱上拉)在读外部状态前,要先锁存为“1”,才可读到外部正确的状态。也就是作为输入时,要先向端口锁存器写入“1”,此时,A组晶体管中的3个上拉晶体管中的一个导通,B晶体管截止。
当端口引脚为1时:A组晶体管中的弱上拉晶体管导通,提供基本驱动电流使得引脚状态为1,此时读取的输入数据为1。
当端口引脚为0时:A组晶体管中的弱上拉晶体管截至,极弱上拉晶体管导通,此时端口引脚输入0将引脚状态拉低,从而读取的输入数据为0。
推挽输出
强推挽输出配置的下拉结构与开漏输出以及准双向口的下拉结构相同,但当锁存器为1时可提供持续的强上拉。所以,推挽输出一般用于需要更大驱动电流的情况。
图6:推挽输出I/O结构图
对于LED控制电路,如果采用的是高电平有效的控制方式,则控制LED的单片机GPIO口必须配置成推挽输出才可以驱动LED。
高阻输入
高阻输入模式下电流既不能流入也不能流出,输入口带有一个施密特触发输入以及一个干扰抑制电路。STC8A8K64D4系列单片机的 I/O 中,除了 ISP下载脚 P3.0/P3.1 为准双向口模式外,其余的所有 I/O 口在上电后都是高阻输入模式。
图7:高阻输入I/O结构图
开漏输出
开漏模式既可读外部状态也可对外输出(高电平或低电平),但是需要注意的是,如要正确读外部状态或需要对外输出高电平,必须外加上拉电阻。这是因为,开漏模式下单片机虽然可以写0写1,但引脚只能输出低电平,无法输出高电平,其原理如下图所示。
图8:开漏模式I/O结构图
软件设计
GPIO应用步骤
GPIO的使用相对比较简单,使用前先配置GPIO的工作模式,之后根据配置的模式操作即可(输出:写端口数据寄存器,输入:读端口数据寄存器)。
GPIO配置
STC8A8K64D4提供了7个用于操作GPIO的寄存器,如下表所示:
表3:GPIO相关寄存器
STC8A8K64D4的I/O模式是通过寄存器PnM1和PnM0的组合配置的,如下表所示。
表4:GPIO模式配置
PnM1和PnM0中的n表示的是I/O的端口,取值范围为0~7,即P0M1和P0M0用来配置端口P0中的I/O的工作模式,P1M1和P1M0用来配置端口P1中的I/O的工作模式,以此类推。
两个寄存器中的各个位对应于端口中的I/O:
即 P0M0 的第 0 位和 P0M1 的第 0 位组合起来配置 P0.0 口的模式。
即 P0M0 的第 1 位和 P0M1 的第 1 位组合起来配置 P0.1 口的模式,以此类推。
配置示例1:配置 P0.0为准双向口(P0M1的位0设置为0,P0M0的位0设置为0)
P0M1 &= 0xFE; P0M0 &= 0xFE;
P0M1 &= 0x7F; P0M0 |= 0x80;
GPIO输出/输入
GPIO配置为输出后,即可通过写端口数据寄存器(Px,x=0~7)中对应的位,让该GPIO输出高电平或低电平,示例如下。
P0.0 = 1; //P0.0输出高电平
P0.0 = 0; //P0.0输出低电平
GPIO配置为输入后,即可通过读端口数据寄存器(Px,x=0~7)中对应的位,从而获取该GPIO的状态,示例如下。
uint8 temp; //定义一个变量用于保存读取的GPIO P0.0的状态
temp = P0.0; //读取GPIO P0.0的状态
点灯实验
实验内容
配置驱动LED指示灯D3的GPIO P7.2为准双向口。
主循环中软件控制P7.2输出高低电平驱动D3闪烁:每200ms改变一次P7.2的输出电平,即D3以200ms的间隔闪烁。
代码编写
因为在“main.c”文件中使用了STC8的头文件“STC8.h”,所以需要引用下面的头文件。
代码清单: 引用头文件
//引用头文件
定义驱动指示灯D3的引脚
一般地,在编写程序时,为了使得程序代码清晰,便于程序阅读,通常会使用“#define”命令定义引脚,也就是给这个引脚重新取个便于理解和记忆的名字。
本例中,指示灯D3使用了GPIO P7.2驱动,因此,我们将P7.2重新定义为“LED3_P72”(即指示灯D3由GPIO P7.2驱动)。
代码清单: 定义驱动LED的引脚
//定义控制指示灯D3的IO,这样定义是为了更直观,当然,我们也可以不定义,直接用P72
编程知识点
使用“#define”命令定义引脚,有以下两个主要的好处:
代码清晰,便于阅读、编写和维护。比如本例中,我们将驱动指示灯D3的引脚 P7.2定义为“LED3_P72”,由这个名字,我们就可以知道:这个引脚是P7.2,用来驱动指示灯D3的。当然,读者也可以根据自己的习惯来定义。
可以做到一改全改,避免遗漏,省心省力。
因此,我们建议读者,尤其是刚入门的读者,要养成这样的编程习惯,这会提高程序开发的效率、降低出错的几率,同时,也能让你编写的程序美观、专业。
开发板的LED驱动电路使用的是低电平驱动方式,因为,我们配置P0.3为准双向口即可,代码清单如下:
代码清单: 配置引脚工作模式
//配置P7.2为准双向口
P7M1 &= 0xFB; P7M0 &= 0xFB;
输出驱动LED闪烁
驱动LED指示灯D3闪烁,只需P7.2以一定的间隔输出高低电平即可达到驱动LED指示灯闪烁的目的。编写代码的时候可以使用:重复“输出高电平延时输出低电平延时”的方式实现,也可以通过:重复“翻转输出状态延时”的方式实现。
因此,我们先编写延时函数,代码清单如下:
代码清单: 软件延时函数
/****
功能描述:软件延时函数
参 数:x [in]:延时毫秒数
返 回 值:无
*****/
void delay_ms(uint16 x)
{
uint16 i,j;
for(j=0;j<x;j++)
{
for(i=0;i<3400;i++);
}
}
之后,在主循环中使驱动LED指示灯D3的引脚P7.2输出高低电平即可实现D3闪烁。
代码清单: 指示灯D3闪烁
/**
功能描述:主函数
入口参数:无
返回值:int类型
*****/
int main(void)
{
P7M1 &= 0xFB; P7M0 &= 0xFB; //配置P7.2为准双向口
while(1)
{
LED1_P72=0; //P7.2端口输出低电平,点亮用户指示灯D3
delay_ms(200);
LED1_P72=1; //P7.2端口输出高电平,熄灭用户指示灯D3
delay_ms(200);
//对于这样的GPIO输出状态翻转,也可以用下面的方式写
//LED3_P72 = ~LED3_P72; //P7.2输出电平翻转
//delay_ms(200);
}
}
硬件连接
用配套的USB数据线按照下图所示将开发板的连接到电脑,本例中使用P7.2控制指示灯D3,因为D3是独立GPIO控制的,所以没有短接跳线帽的操作。
图9:硬件连接
实验步骤
解压“…\第3部分:配套例程源码”目录下的压缩文件“实验2-1-1:点灯实验”,将解压后得到的文件夹拷贝到合适的目录,如“D\STC8”(这样做的目的是为了防止中文路径或者工程存放的路径过深导致打开工程出现问题)。
双击“…\led_blinky\project”目录下的工程文件“led_blinky.uvproj”打开工程。
点击编译按钮编译工程,编译成功后生成的HEX文件“led_blinky.hex”位于工程的“…\led_blinky\project\Objects”目录下。
打开STC-ISP软件下载程序,下载时勾选使用内部IRC时钟,IRC频率选择:24MHz。
程序运行后,可以观察到蓝色LED灯D3以200ms的间隔闪烁。
流水灯实验
实验内容
配置驱动LED指示灯D1、D2、D3和D4的GPIO P2.6、P2.7、P7.2和P7.1为准双向口。
4个LED指示灯以100ms的时间间隔循环轮流点亮和熄灭,达到流水灯效果。
代码编写
指示灯D1、D2、D3、D4分别由GPIO P2.6、P2.7、P7.2和P7.1驱动,这里,我们使用“#define”命令定义如下。
代码清单: 定义驱动LED的引脚
//定义控制指示灯D1、D2、D3、D4的IO
实现流水灯
流水灯就是按照一定的时间间隔有规律的轮流点亮LED指示灯,也就是4个LED对应的引脚轮流输出高低电平,并加上一段时间的延时,从而实现轮流点亮的效果,代码清单如下。
代码清单: 流水灯
/**
功能描述:主函数
入口参数:无
返回值:int类型
*****/
int main(void)
{
P2M1 &= 0x3F; P2M0 &= 0x3F; //配置P2.6、P2.7为准双向口
P7M1 &= 0xF9; P7M0 &= 0xF9; //配置P7.2、P7.1为准双向口
while(1)
{
LED1_P26=0; //D1点亮
delay_ms(100); //延时100ms
LED1_P26=1; //D1熄灭
LED2_P27=0; //D2点亮
delay_ms(100); //延时100ms
LED2_P27=1; //D1熄灭
LED3_P72=0; //D3点亮
delay_ms(100); //延时100ms
LED3_P72=1; //D1熄灭
LED4_P71=0; //D4点亮
delay_ms(100); //延时100ms
LED4_P71=1; //D1熄灭
delay_ms(100); //延时100ms
}
}
硬件连接
本实验需要使用4个LED指示灯,因此需要用跳线帽短接复用引脚的指示灯(D1和D2),而指示灯D3和D4是独立引脚,没有和其他电路复用引脚,是没有短接跳线帽的操作的。
图10:跳线帽短接
实验步骤
解压“…\第3部分:配套例程源码”目录下的压缩文件“实验2-1-2:流水灯”,将解压后得到的文件夹拷贝到合适的目录,如“D\STC8”(这样做的目的是为了防止中文路径或者工程存放的路径过深导致打开工程出现问题)。
双击“…\led_blinky\project”目录下的工程文件“led_blinky.uvproj”。
点击编译按钮编译工程,编译成功后生成的HEX文件“led_blinky.hex”位于工程的“…\led_blinky\project\Objects”目录下。
打开STC-ISP软件下载程序,下载使用内部IRC时钟,IRC频率选择:24MHz。
程序运行后,可以观察到4个LED指示灯轮流闪烁的流水灯效果。
编写LED驱动实验
当我们开发的程序比较复杂的时候,通过GPIO直接操作LED显然比较麻烦,比较好的做法是将需要用到LED操作封装为函数,并将这些函数单独放到一个“.c”文件里面,其他文件中需要用到LED功能的时候,调用这些函数实现功能即可。
接下来,我们就用这种方式来实现实验2-1-2中的流水灯,读者可以体会一下,哪种方式更好。
实验内容
在“实验2-1-1:点灯实验”的基础上编写指示灯的驱动文件。
本实验实现的功能和“实验2-1-2:流水灯”一样。
规划
本例中,我们需要用到LED和延时,因此,我们新建名称为“delay.c”和“led.c”的文件,分别用于存放延时和LED操作相关的函数。另外,新建对应的头文件“delay.h”和“led.h”,以便于其他程序模块使用。
“delay.c”包含的函数分别如下表所示。
表5:延时函数
“led.c”包含的函数分别如下表所示。
表6:LED操作函数
新建文件和添加头文件路径
新建名称为“delay.c”和“led.c”的文件并保存到工程的“Source”文件夹,同时,为“delay.c”和“led.c”文件分别新建一个头文件“delay.h”和“led.h”也保存到工程的“Source”文件夹。每个文件夹加入到该组每个“.c”文件对应一个“.h”头文件,以方便其他文件引用。
Keil工程“led_blinky”中新建一个名称为“SOURCE”组,并将“delay.c”和“led.c”加入到该组。
添加头文件包含路径,如下图所示。
图11:添加头文件包含路径
编写LED驱动函数
为了软件操作方便,我们给每个指示灯按照原理图上的定义(D1、D2、D3、D4)赋予一个编号,注意这个编号不是物理引脚定义,而是为了软件操作方便,人为在软件层面对指示灯进行的编号。
代码清单: 指示灯编号
//定义指示灯的编号,注意这个编号是为了软件操作方便,人为对指示灯进行的编号
LED操作的函数代码清单如下:
代码清单: led_on函数
/**
功能描述:点亮一个指定的指示灯(D1、D2、D3、D4)
参 数:led_idx [in]: led指示灯编号,可取值LED_1、LED_2、LED_3、LED_4
返 回 值:无
*****/
void led_on(u8 led_idx)
{
switch(led_idx)
{
case LED_1:
LED1_P26=0; //控制P2.6端口输出低电平,点亮用户指示灯D1
break ;
case LED_2:
LED2_P27=0; //控制P2.7端口输出低电平,点亮用户指示灯D2
break ;
case LED_3:
LED3_P72=0; //控制7.2端口输出低电平,点亮用户指示灯D3
break ;
case LED_4:
LED4_P71=0; //控制P7.1端口输出低电平,点亮用户指示灯D4
break ;
default :
break ;
}
}
led_off:熄灭指定的LED指示灯。
代码清单: led_off函数
/**
功能描述:熄灭一个指定的指示灯(D1、D2、D3、D4)
参 数:led_idx [in]: led指示灯编号,可取值LED_1、LED_2、LED_3、LED_4
返 回 值:无
*****/
void led_off(u8 led_idx)
{
switch(led_idx)
{
case LED_1:
LED1_P26=1; //控制P2.6端口输出高电平,熄灭用户指示灯D1
break ;
case LED_2:
LED2_P27=1; //控制P2.7端口输出高电平,熄灭用户指示灯D2
break ;
case LED_3:
LED3_P72=1; //控制P7.2端口输出高电平,熄灭用户指示灯D3
break ;
case LED_4:
LED4_P71=1; //控制P7.1端口输出高电平,熄灭用户指示灯D4
break ;
default :
break ;
}
}
led_toggle:翻转指定的LED的状态。
代码清单: led_ toggle函数
/**
功能描述:翻转一个指定的指示灯的状态(D1、D2、D3、D4)
参 数:led_idx [in]: led指示灯编号,可取值LED_1、LED_2、LED_3、LED_4
返 回 值:无
*****/
void led_toggle(u8 led_idx)
{
switch(led_idx)
{
case LED_1:
LED1_P26=~LED1_P26; //P2.6输出电平取反,翻转用户指示灯D1
break ;
case LED_2:
LED2_P27=~LED2_P27; //P2.7输出电平取反,翻转用户指示灯D2
break ;
case LED_3:
LED3_P72=~LED3_P72; //7.2输出电平取反,翻转用户指示灯D3
break ;
case LED_4:
LED4_P71=~LED4_P71; //P7.1输出电平取反,翻转用户指示灯D4
break ;
default :
break ;
}
}
leds_on:点亮开发板上所有的LED指示灯(D1、D2、D3和D4)。
代码清单: leds_on函数
/**
功能描述:点亮开发板上的4个指示灯(D1、D2、D3、D4)
参 数:无
返 回 值:无
*****/
void leds_on(void)
{
LED1_P26=0; //控制P0.3端口输出低电平,点亮用户指示灯D1
LED2_P27=0; //控制P0.2端口输出低电平,点亮用户指示灯D2
LED3_P72=0; //控制P0.1端口输出低电平,点亮用户指示灯D3
LED4_P71=0; //控制P0.0端口输出低电平,点亮用户指示灯D4
//也可以直接操作GPIO端口
//P2 &= 0x3F;
//P7 &= 0xF9;
}
leds_off:熄灭开发板上所有的LED指示灯(D1、D2、D3和D4)。
代码清单: leds_off函数
/**
功能描述:熄灭开发板上的4个指示灯(D1、D2、D3、D4)
参 数:无
返 回 值:无
*****/
void leds_off(void)
{
LED1_P26=1; //控制P2.6端口输出高电平,熄灭用户指示灯D1
LED2_P27=1; //控制P2.7端口输出高电平,熄灭用户指示灯D2
LED3_P72=1; //控制P7.2端口输出高电平,熄灭用户指示灯D3
LED4_P71=1; //控制P7.1端口输出高电平,熄灭用户指示灯D4
//也可以直接操作IO端口
//P2 |= 0Xc0;
//P7 |= 0X06;
}
用LED驱动函数实现流水灯
下面是用LED驱动函数实现的流水灯的代码清单,他的效果和“实验2-1-2:流水灯”是一样的。
代码清单: LED驱动函数实现流水灯
/**
功能描述:主函数
入口参数:无
返回值:int类型
*****/
int main(void)
{
P2M1 &= 0x3F; P2M0 &= 0x3F; //配置P2.6、P2.7为准双向口
P7M1 &= 0XF9; P7M0 &= 0XF9; //配置P7.1、P7.2为准双向口
while(1)
{
led_on(LED_1); //点亮D1
delay_ms(100); //延时100ms
leds_off(); //熄灭所有用户指示灯
led_on(LED_2); //点亮D2
delay_ms(100); //延时100ms
leds_off(); //熄灭所有用户指示灯
led_on(LED_3); //点亮D3
delay_ms(100); //延时100ms
leds_off(); //熄灭所有用户指示灯
led_on(LED_4); //点亮D4
delay_ms(100); //延时100ms
leds_off(); //熄灭所有用户指示灯
delay_ms(100); //延时100ms
}
}
实验步骤
解压“…\第3部分:配套例程源码”目录下的压缩文件“实验2-1-3:流水灯(自编驱动文件方式)”,将解压后得到的文件夹拷贝到合适的目录,如“D\STC8”(这样做的目的是为了防止中文路径或者工程存放的路径过深导致打开工程出现问题)。
双击“…\led_blinky\project”目录下的工程文件“led_blinky.uvproj”。
点击编译按钮编译工程,编译成功后生成的HEX文件“led_blinky.hex”位于工程的“…\led_blinky\project\Objects”目录下。
打开STC-ISP软件下载程序,下载使用内部IRC时钟,IRC频率选择:24MHz。
程序运行后,可以观察到4个LED指示灯轮流闪烁的流水灯效果(和实验2-1-2:流水灯一样)。
说明:后续的实验涉及到的单片机外设程序编写,都会采用本节的方式编写代码。在后续的章节中将不再详细描述新建文件之类的操作(到这里,读者应该对这些常规操作很熟悉了)。