【STC8A8K64S4A12开发板】—小白做GPIO点灯实验

版权声明:本文为博主原创文章,转载请附上原文出处链接。


前言

最近空闲时间比较多,准备系统学习下单片机的开发,作为一名刚入行的新手来说,从哪种单片机开始学习很重要,经过一系列的选择过程……最终我选择了从STC8单片机开始。 STC8兼顾传统的51单片机,但性能优于传统的51单片机,并且外设资源丰富,很适合我这样的小白入手,选择好了单片机,接下来就是选择一款合适的开发板了。逛了N天的某宝,最终选择了艾克姆的STC8A8K64S4A12开发板。 废话不多说,实验做起来——先从GPIO点灯开始。

一、硬件电路原理

1.开发板指示灯硬件电路

LED(Light Emitting Diode)是发光二极管的简称,在很多设备上常用它来做为一种简单的人机接口,如网卡、路由器等通过LED向用户指示设备的不同工作状态。所以,我们习惯把这种用于指示状态的LED称为LED指示灯。
STC8A8K64S4A12开发板上设计了4个LED指示灯,我们可以通过编程驱动LED指示灯点亮、熄灭、闪烁,从而达到状态指示的目的,LED指示灯驱动电路如下图所示。
在这里插入图片描述

图1:LED指示灯驱动电路

☆4个LED指示灯占用的单片机的引脚如下表:

表1:用户LED引脚分配
LED引脚功能描述说明备注
D1P2.6用户指示灯非独立GPIO蓝色LED灯
D2P2.7P2.7用户指示灯非独立GPIO
D3P7.2用户指示灯独立GPIO蓝色LED灯
D4P7.1用户指示灯独立GPIO蓝色LED灯

☆注:独立GPIO表示开发板没有其他的电路使用这个GPIO,非独立GPIO说明开发板有其他电路用到了该GPIO。针对非独立GPIO使用时需特别注意。

LED指示灯驱动电路是一个很常见、简单的电路,同时它也是一个典型的单元电路,对于初学者来说,类似常用的典型电路必须要掌握,不但要知其然、还要知其所以然。
接下来,我们来分析一下这个简单的LED指示灯驱动电路。
LED驱动电路设计的时候,要考虑两个方面:控制方式和限流电阻的选取。

2.控制方式

LED指示灯控制方式分为高电平有效和低电平有效两种,高电平有效是单片机GPIO输出高电平时点亮LED,低电平有效是单片机GPIO输出低电平时点亮LED。

2.1.低电平有效的控制方式

在这里插入图片描述

LED控制-低电平有效原理

低电平有效控制方式中,当单片机的GPIO输出低电平(逻辑0)的时候,LED和电阻R上的压降等于(VCC-VCCIO = 3.3V),这时候,因为存在压降,同时,这个电路是闭合回路,这就达到了电流产生的两个要素,LED上会有电流流过,LED被点亮。
当单片机的GPIO输出高电平(逻辑1)的时候,LED和电阻R上的压降等于(VCC-VCCIO = 0V),这时候,因为LED上没有压降,当然不会有电流流过,所以LED熄灭。

2.2.高电平有效的控制方式

在这里插入图片描述

图3:LED控制-高电平有效原理

高电平有效控制方式中,由单片机的GPIO输出电流驱动LED,当单片机的GPIO输出高电平(逻辑1)的时候,LED上存在压降,因为电路是闭合回路,所以会有电流流过,这时LED被点亮,但要注意,单片机的GPIO要能提供足够的输出电流,否则,电流过小,会导致LED亮度很弱。
当单片机的GPIO输出低电平(逻辑0)的时候,LED和电阻R上的压降等于0V,这时候,LED上没有电流流过,LED熄灭。

2.3.选择哪种方式来控制LED

绝大多数情况下,我们会选择使用低电平有效的控制方式,如艾克姆科技STC8A8K64S4A12开发板中的LED指示灯就是低电平有效。这样设计指示灯驱动电路的好处是:

① 单片机GPIO口低电平时的灌入电流一般比高电平时的拉电流要大,能提供足够的电流驱动LED。
② 单片机上电或复位启动时,GPIO口一般都是高阻输入,用低电平有效的控制方式可以确保LED在上电或复位启动时处于熄灭状态。

☆注:当单片机是5V单片机时选择指示灯控制方式的原理是一样的。

3.LED限流电阻的选取

3.1.限流电阻的计算

在这里插入图片描述

图4:LED限流电阻计算

由上图可以看出,LED限流电阻的计算公式如下:
在这里插入图片描述
其中,VCC=3.3V,VF是LED的正向压降,LED的数据手册都会给出正向电流为2mA时测试的VF的范围,下图是一款0805 LED的实物图和参数。在参数表中可以看到正向电流为2mA时VF最小值是2.5V,典型值是2.7V,最大值是3.6V。
在这里插入图片描述

图5:封装0805的LED

在这里插入图片描述

图6:0805 LED参数图

计算时VF的值可以用典型值来进行估算,对于电流,需要根据经验值和对LED亮度的要求相结合来确定,一般经验值是(1~5)mA,不过要注意,只要亮度符合自己的要求,电流低于1mA也没有任何问题。
电流为1mA时限流电阻值计算如下:
在这里插入图片描述
☆注:当供电VCC是5V时,电流为1mA计算得到的电阻是2.3KΩ。

3.2.限流电阻的选择

根据上一节对限流电阻计算公式的描述及对选择的封装0805的指示灯参数的了解,计算出供电3.3V电流1mA时的限流电阻理论值是600Ω,供电5V电流1mA时的限流电阻理论值是2.3KΩ,为保证供电为3.3V时指示灯够亮但同时5V供电指示灯又不宜过亮,选择一个比较常见的阻值2K作为限流电阻。
还有一点需要强调的是,不同颜色的指示灯在即使同一亮度时所需的限流电阻不一定是相同的。这也是为什么有些产品的面板上有不同颜色的指示灯,各指示灯所使用的限流电阻不一样的原因。

4.STC8A8K64S4A12系列单片机GPIO口

STC8A8K64S4A12系列单片机GPIO口数量取决于芯片引脚的个数,芯片引脚个数和芯片封装密切相关。正常情况下,GPIO口数量是所选择单片机引脚个数减去5,因为单片机需要2个引脚作为供电引脚(电源正VCC、电源负GND),ADC外设会占用3个引脚(电源正ADC_Avcc、电源负ADC_Agnd、参考电压AVref)。

4.1.芯片封装

封装,Package,是把集成电路装配为芯片最终产品的过程,简单地说,就是把Foundry生产出来的集成电路裸片(Die)放在一块起到承载作用的基板上,把管脚引出来,然后固定包装成为一个整体。
STC8A8K64S4A12系列有多种封装,厂家批量常见的芯片封装是:LQFP44、LQFP48和LQFP64S。
☆注:单片机每个GPIO口驱动能力(强推挽输出模式时)均可达到20mA,但单片机整个芯片的工作电流最大不要超过90mA。

4.2.GPIO口工作模式

STC8A8K64S4A12系列单片机所有GPIO口均有4种工作模式:准双向口/弱上拉(标准8051输出口模式)、推挽输出/强上拉、高阻输入(电流既不能流入也不能流出)、开漏输出。下面针对内部结构图进行分析。
■ 准双向口/弱上拉模式:
在这里插入图片描述

图7:GPIO准双向口/弱上拉模式内部框图

1)准双向口(弱上拉) 输出类型可用作输出和输入功能而不需要重新配置端口输出状态。这是因为准双向口有3个上拉晶体管可适应输入输出不同的需要。
2)手册中有这样一句话:准双向口(弱上拉)在读外部状态前,要先锁存为‘1’,才可读到外部正确的状态。下图分别就锁存数据为‘1’和‘0’时进行了分析。
在这里插入图片描述

图8:GPIO准双向口/弱上拉模式内部框图分析

3)由上图分析可知,准双向口(弱上拉)在读外部状态前,如果锁存为‘0’,则GPIO引脚状态被固定,无法读到外部正确的状态。

☆注:单片机GPIO口在模式没有配置的情况下,一般都是默认的准双向口模式。

■ 推挽输出/强上拉模式:
在这里插入图片描述

图9:GPIO推挽输出/强上拉模式内部框图

1)强推挽输出配置的下拉结构与开漏输出以及准双向口的下拉结构相同,但当锁存器为1时可提供持续的强上拉。所以,推挽输出一般用于需要更大驱动电流的情况。
2)在控制LED时,如果采用的是高电平有效的控制方式,则控制LED的单片机GPIO口必须配置成推挽输出/强上拉模式方可。
■ 高阻输入模式:
在这里插入图片描述

图10:GPIO高阻输入模式内部框图

1)因带有一个施密特触发输入以及一个干扰抑制电路,GPIO配置为高阻输入时,电流既不能流入也不能流出GPIO口。
2)在很多STC相关的文档中有说的高阻态即是GPIO口被设置了高阻输入模式。
■ 开漏输出模式:
在这里插入图片描述

图11:GPIO开漏输出模式内部框图

1)开漏模式既可以读外部状态也可以对外输出高电平或低电平。
2)如果要正确读外部状态或需要对外输出高电平时,需外加上拉电阻。

二、软件编写

1.GPIO寄存器汇集

STC8A8K64S4A12系列单片机提供了40个用于操作GPIO的寄存器,如下表所示:

表2:GPIO相关寄存器
序号寄存器名读/写述功能描述
1P0读/写P0端口数据寄存器
2P1读/写P1端口数据寄存器
3P2读/写P2端口数据寄存器
4P3读/写P3端口数据寄存器
5P4读/写P4端口数据寄存器
6P5读/写P5端口数据寄存器
7P6读/写P6端口数据寄存器
8P7读/写P7端口数据寄存器
9P0M0可写P0端口配置寄存器0
10P0M1可写P0端口配置寄存器1
11P1M0可写P1端口配置寄存器0
12P1M1可写P1端口配置寄存器1
13P2M0可写P2端口配置寄存器0
14P2M1可写P2端口配置寄存器1
15P3M0可写P3端口配置寄存器0
16P3M1可写P3端口配置寄存器1
17P4M0可写P4端口配置寄存器0
18P4M1可写P4端口配置寄存器1
19P5M0可写P5端口配置寄存器0
20P5M1可写P5端口配置寄存器1
21P6M0可写P6端口配置寄存器0
22P6M1可写P6端口配置寄存器1
23P7M0可写P7端口配置寄存器0
24P7M1可写P7端口配置寄存器1
25P0PU可写P0端口上拉电阻控制寄存器
26P1PU可写P1端口上拉电阻控制寄存器
27P2PU可写P2端口上拉电阻控制寄存器
28P3PU可写P3端口上拉电阻控制寄存器
29P4PU可写P4端口上拉电阻控制寄存器
30P5PU可写P5端口上拉电阻控制寄存器
31P6PU可写P6端口上拉电阻控制寄存器
32P7PU可写P7端口上拉电阻控制寄存器
33P0NCS可写P0端口施密特触发控制寄存器
34P1NCS可写P1端口施密特触发控制寄存器
35P2NCS可写P2端口施密特触发控制寄存器
36P3NCS可写P3端口施密特触发控制寄存器
37P4NCS可写P4端口施密特触发控制寄存器
38P5NCS可写P5端口施密特触发控制寄存器
39P6NCS可写P6端口施密特触发控制寄存器
40P7NCS可写P7端口施密特触发控制寄存器

☆注:STC8A8K64S4A12系列单片机是没有P4.5,P4.6和P4.7三个IO的。另外,选择的单片机封装不同,具有的端口也不同。比如LQFP48引脚单片机没有P6和P7端口,在程序设计时操作P6和P7端口对应的寄存器是没有意义的。

2.寄存器解析

首先普及一个常用知识点:为什么说STC8A8K64S4A12系列单片机是8位单片机呢?这个8位指的是什么?
一般来说某个单片机或微处理器是几位,指的是“机器字长”。每个单片机或微处理器最基本的功能是算术逻辑运算,而算术逻辑运算的主要部件是“算术逻辑单元(ALU)”。机器字长即是指ALU的数据位宽,也就是指令能直接处理的二进制位数。
通常单片机或微处理器的寄存器的位宽等于ALU的位宽,所以一般可通过识别单片机或微处理器寄存器的位宽来确定该单片机或微处理器是多少位的。我们所接触的STC的单片机,其寄存器都是8位的,所以STC的单片机都是8位的单片机。以后学习STM32F103系列的微处理器,其寄存器是32位的,所以会说STM32F103系列微处理器是32位的。

2.1.端口数据寄存器

下图是对端口数据寄存器P0、P1、P2、P3、P4、P5、P6、P7的描述,端口数据寄存器各位代表对应端口的IO口,比如,P7端口数据寄存器B0位代表P7.0口,B7位代表P7.7口。
在这里插入图片描述

图12:端口数据寄存器

2.2.端口配置寄存器

端口配置寄存器PnM1和PnM0都是8位的寄存器,PnM1和PnM0寄存器必须组合使用才能正确地配置IO口工作模式。
STC8A8K64S4A12系列单片机所有GPIO口均有4种工作模式:准双向口/弱上拉(标准8051输出口模式)、推挽输出/强上拉、高阻输入(电流既不能流入也不能流出)、开漏输出。每个GPIO口工作模式由PnM1和PnM0寄存器中的相应位控制。如下图。
在这里插入图片描述

图13:GPIO端口配置

3.GPIO驱动LED实验(寄存器版本)

☆注:本节对应的实验源码是:“实验2-1-1:GPIO驱动LED(寄存器版本)”。

3.1.头文件引用和路径设置

■ 需要宏定义部分及引用的头文件
因为在“main.c”文件中使用了STC8的头文件“STC8.H”,所以需要引用下面的头文件。在头文件“STC8.H”中需要确定主时钟取值,所以宏定义主时钟值。

1.#define MAIN_Fosc       11059200L   //定义主时钟  
2.#include    "STC8.H" 

在程序设计中会用到定义变量的类型,为了定义变量方便,将较为复杂的“unsigned int”和“unsigned char ”进行了宏定义。

1.#define  uint16   unsigned int    
2.#define  uint8    unsigned char    

这样,再定义变量时可直接使用“uint16”和“uint8”来取代“unsigned int”和“unsigned char ”即可。

■ 需要包含的头文件路径
本例需要包含的头文件路径如下表:

表3:头文件包含路径
序号路径描述
1…\UserSTC8.H头文件在该路径,所以要包含

MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
在这里插入图片描述

图14:添加头文件包含路径

3.2.编写代码

首先介绍下毫秒级的延时函数。控制指示灯亮和灭需要中间有足够的间隔时间,这个间隔一般通过延时函数实现。微秒级的延时时间很难控制,这和主频大小及其精度有密切关系。但毫秒级的延时还是可以控制的,下面给出在11.0592MHZ下的毫秒延时函数,仅供参考。

代码清单:毫秒延时函数

1./************************************** 
2.功能描述:延时函数 
3.入口参数:uint16 x ,该值为1时,延时1ms 
4.返回值:无 
5.***************************************/  
6.void delay_ms(uint16 x)   
7.{    
8.    uint16 j,i;     
9.    for(j=0;j<x;j++)     
10.    {      
11.        for(i=0;i<1580;i++);     
12.    }    
}  

在对端口数据寄存器介绍时我们简单介绍过控制单片机GPIO的过程。需要再说明的地方是关于两个关键字“sfr”和“sbit”。
sfr是Keil C51为能直接访问51内核单片机中的SFR而提供了一个关键词,其用法是:
1)sfrt 变量名=地址值。
sbit是定义特殊功能寄存器的位变量。其用法有三种:
1)sbit 位变量名=地址值。
2)sbit 位变量名=SFR名称^变量位地址值。
3)sbit 位变量名=SFR地址值^变量位地址值。

☆注:SFR是Special Function Register(特殊功能寄存器)的缩写。

程序清单:头文件“STC8.H”定义P0端口部分

1.sfr P7          =   0Xf8;    
2.sbit P70        =   P7^0;  
3.sbit P71        =   P7^1;  
4.sbit P72        =   P7^2;  
5.sbit P73        =   P7^3;  
6.sbit P74        =   P7^4;  
7.sbit P75        =   P7^5;  
8.sbit P76        =   P7^6;  
9.sbit P77        =   P7^7; 
}  

然后,在主函数中先对P7.2口进行模式配置,针对P7.2口是被配置了准双向口,后主循环中将用户指示灯D3点亮,延时200ms,再熄灭,再延时200ms的过程,这样可观察到指示灯D3不停闪烁的现象。

☆注:主函数可以不对P7.2口进行模式配置,因为不配置的话,P7.2口默认也是准双向口。

代码清单:主函数

1.int main()  
2.{  
3.    P7M1 &= 0xFB;    P7M0 &= 0xFB;    //设置P7.2为准双向口  
4.    // P7M1 &= 0xFB;  P7M0 |= 0x04;      //设置P7.2为推挽输出
5.    // P7M1 |= 0x04;  P7M0 &= 0xFB;      //设置P7.2为高阻输入  
6.    //  P7M1 |= 0x04;  P7M0 |= 0x04;    //设置P7.2为开漏输出  
7.      
8.  while(1)  
9.  {  
10.        P72=0;        //控制P7.2端口输出低电平,点亮用户指示灯D3  
11.        delay_ms(200);  
12.        P72=1;        //控制P7.2端口输出高电平,熄灭用户指示灯D3  
13.        delay_ms(200);  
14.    }  
15.} 

4.GPIO驱动LED实验(库函数版本)

4.1.工程需要用到的c文件

本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。

表4:实验需要用到的C文件
序号文件名后缀功能描述
1GPIO.c通用输入输出
该GPIO.c是STC官方提供的有关GPIO配置的函数库。

4.2.头文件引用和路径设置

■ 需要引用的头文件
因为在“main.c”文件中使用了GPIO相关的库,所以需要引用下面的头文件。

1.#include    "GPIO.h"  

■ 需要包含的头文件路径
本例需要包含的头文件路径如下表:

表5:头文件包含路径
序号路径描述
1…\ STC_LIBGPIO.h和config.h头文件在该路径,所以要包含
2…\UserSTC8.h头文件在该路径,所以要包含

MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
在这里插入图片描述

图15:添加头文件包含路径

4.3.编写代码

首先在GPIO口初始化函数中调用库函数GPIO_Inilize完成P7.2口的工作模式配置,即配置P7.2为准双向口。

代码清单:GPIO口初始化函数

1./************************************** 
2.功能描述:GPIO口初始化 
3.入口参数:无 
4.返回值:无 
5.***************************************/  
6.void    GPIO_config(void)  
7.{  
8.    GPIO_InitTypeDef  GPIO_InitStructure;  
9.   
10.    //设置P7.2口工作模式  
11.    GPIO_InitStructure.Mode=GPIO_PullUp;      //配置P7.2口为准双向口  
12.      //GPIO_InitStructure.Mode=GPIO_OUT_PP;    //配置P7.2口为推挽输出(强上拉)  
13.      //GPIO_InitStructure.GPIO_HighZ;          //配置P7.2口为高阻输入  
14.      //GPIO_InitStructure.Mode=GPIO_OUT_OD;    //配置P7.2口为开漏输出  
15.    GPIO_InitStructure.Pin=GPIO_Pin_2;  
16.    GPIO_Inilize(GPIO_P7,&GPIO_InitStructure);  
17.   
18.}  

打开库函数GPIO_Inilize后会发现,配置P7.2口实际最终操作还是P0M0和P0M1寄存器。代码如下。

程序清单:头文件“GPIO.c”定义GPIO初始化库函数

1.//========================================================================  
2.// 函数: uint8    GPIO_Inilize(uint8 GPIO, GPIO_InitTypeDef *GPIOx)  
3.// 描述: 初始化IO口.  
4.// 参数: GPIOx: 结构参数,请参考gpio.h里的定义.  
5.// 返回: 成功返回0, 空操作返回1,错误返回2.  
6.//========================================================================  
7.uint8   GPIO_Inilize(uint8 GPIO, GPIO_InitTypeDef *GPIOx)  
8.{  
9.    if(GPIO > GPIO_P7)               return 1;   //空操作  
10.    if(GPIOx->Mode > GPIO_OUT_PP) return 2;   //错误  
11.  
12.    if(GPIO == GPIO_P0)  
13.    {  
14.        if(GPIOx->Mode == GPIO_PullUp)       P0M1 &= ~GPIOx->Pin, P0M0 &= ~GPIOx->Pin;  //上拉准双向口  
15.        if(GPIOx->Mode == GPIO_HighZ)          P0M1 |=  GPIOx->Pin,   P0M0 &= ~GPIOx->Pin;  //浮空输入  
16.        if(GPIOx->Mode == GPIO_OUT_OD)       P0M1 |=  GPIOx->Pin, P0M0 |=  GPIOx->Pin;  //开漏输出  
17.        if(GPIOx->Mode == GPIO_OUT_PP)       P0M1 &= ~GPIOx->Pin, P0M0 |=  GPIOx->Pin;  //推挽输出  
18.    }  
19.    if(GPIO == GPIO_P1)  
20.    {  
21.        if(GPIOx->Mode == GPIO_PullUp)       P1M1 &= ~GPIOx->Pin, P1M0 &= ~GPIOx->Pin;  //上拉准双向口  
22.        if(GPIOx->Mode == GPIO_HighZ)          P1M1 |=  GPIOx->Pin,   P1M0 &= ~GPIOx->Pin;  //浮空输入  
23.        if(GPIOx->Mode == GPIO_OUT_OD)       P1M1 |=  GPIOx->Pin, P1M0 |=  GPIOx->Pin;  //开漏输出  
24.        if(GPIOx->Mode == GPIO_OUT_PP)       P1M1 &= ~GPIOx->Pin, P1M0 |=  GPIOx->Pin;  //推挽输出  
25.    }  
26.    if(GPIO == GPIO_P2)  
27.    {  
28.        if(GPIOx->Mode == GPIO_PullUp)       P2M1 &= ~GPIOx->Pin, P2M0 &= ~GPIOx->Pin;  //上拉准双向口  
29.        if(GPIOx->Mode == GPIO_HighZ)          P2M1 |=  GPIOx->Pin,   P2M0 &= ~GPIOx->Pin;  //浮空输入  
30.        if(GPIOx->Mode == GPIO_OUT_OD)       P2M1 |=  GPIOx->Pin, P2M0 |=  GPIOx->Pin;  //开漏输出  
31.        if(GPIOx->Mode == GPIO_OUT_PP)       P2M1 &= ~GPIOx->Pin, P2M0 |=  GPIOx->Pin;  //推挽输出  
32.    }  
33.    if(GPIO == GPIO_P3)  
34.    {  
35.        if(GPIOx->Mode == GPIO_PullUp)       P3M1 &= ~GPIOx->Pin, P3M0 &= ~GPIOx->Pin;  //上拉准双向口  
36.        if(GPIOx->Mode == GPIO_HighZ)          P3M1 |=  GPIOx->Pin,   P3M0 &= ~GPIOx->Pin;  //浮空输入  
37.        if(GPIOx->Mode == GPIO_OUT_OD)       P3M1 |=  GPIOx->Pin, P3M0 |=  GPIOx->Pin;  //开漏输出  
38.        if(GPIOx->Mode == GPIO_OUT_PP)       P3M1 &= ~GPIOx->Pin, P3M0 |=  GPIOx->Pin;  //推挽输出  
39.    }  
40.    if(GPIO == GPIO_P4)  
41.    {  
42.        if(GPIOx->Mode == GPIO_PullUp)       P4M1 &= ~GPIOx->Pin, P4M0 &= ~GPIOx->Pin;  //上拉准双向口  
43.        if(GPIOx->Mode == GPIO_HighZ)          P4M1 |=  GPIOx->Pin,   P4M0 &= ~GPIOx->Pin;  //浮空输入  
44.        if(GPIOx->Mode == GPIO_OUT_OD)       P4M1 |=  GPIOx->Pin, P4M0 |=  GPIOx->Pin;  //开漏输出  
45.        if(GPIOx->Mode == GPIO_OUT_PP)       P4M1 &= ~GPIOx->Pin, P4M0 |=  GPIOx->Pin;  //推挽输出  
46.    }  
47.    if(GPIO == GPIO_P5)  
48.    {  
49.        if(GPIOx->Mode == GPIO_PullUp)       P5M1 &= ~GPIOx->Pin, P5M0 &= ~GPIOx->Pin;  //上拉准双向口  
50.        if(GPIOx->Mode == GPIO_HighZ)          P5M1 |=  GPIOx->Pin,   P5M0 &= ~GPIOx->Pin;  //浮空输入  
51.        if(GPIOx->Mode == GPIO_OUT_OD)       P5M1 |=  GPIOx->Pin, P5M0 |=  GPIOx->Pin;  //开漏输出  
52.        if(GPIOx->Mode == GPIO_OUT_PP)       P5M1 &= ~GPIOx->Pin, P5M0 |=  GPIOx->Pin;  //推挽输出  
53.    }  
54.    if(GPIO == GPIO_P6)  
55.    {  
56.        if(GPIOx->Mode == GPIO_PullUp)       P6M1 &= ~GPIOx->Pin, P6M0 &= ~GPIOx->Pin;  //上拉准双向口  
57.        if(GPIOx->Mode == GPIO_HighZ)          P6M1 |=  GPIOx->Pin,   P6M0 &= ~GPIOx->Pin;  //浮空输入  
58.        if(GPIOx->Mode == GPIO_OUT_OD)       P6M1 |=  GPIOx->Pin, P6M0 |=  GPIOx->Pin;  //开漏输出  
59.        if(GPIOx->Mode == GPIO_OUT_PP)       P6M1 &= ~GPIOx->Pin, P6M0 |=  GPIOx->Pin;  //推挽输出  
60.    }  
61.    if(GPIO == GPIO_P7)  
62.    {  
63.        if(GPIOx->Mode == GPIO_PullUp)       P7M1 &= ~GPIOx->Pin, P7M0 &= ~GPIOx->Pin;  //上拉准双向口  
64.        if(GPIOx->Mode == GPIO_HighZ)          P7M1 |=  GPIOx->Pin,   P7M0 &= ~GPIOx->Pin;  //浮空输入  
65.        if(GPIOx->Mode == GPIO_OUT_OD)       P7M1 |=  GPIOx->Pin, P7M0 |=  GPIOx->Pin;  //开漏输出  
66.        if(GPIOx->Mode == GPIO_OUT_PP)       P7M1 &= ~GPIOx->Pin, P7M0 |=  GPIOx->Pin;  //推挽输出  
67.    }  
68.    return 0;   //成功  
}  

然后,在主函数中先调用GPIO口初始化函数,后主循环中将用户指示灯D3点亮,延时500ms,再熄灭,再延时500ms的过程,这样可观察到指示灯D3不停闪烁的现象。

代码清单:主函数

1.int main()  
2.{  
3.  GPIO_config();   //设置P7.2口为准双向口  
4.      
5.  while(1)  
6.  {  
7.        P72=0;        //控制P7.2端口输出低电平,点亮用户指示灯D3  
8.        delay_ms(500);  
9.        P72=1;        //控制P7.2端口输出高电平,熄灭用户指示灯D3  
10.        delay_ms(500);  
11.    }  
12.} 

5.流水灯实验(单个c文件)

☆注:本节的实验源码是在“实验2-1-1:GPIO驱动LED(寄存器版本)”的基础上修改。本节对应的实验源码是:“实验2-1-3:流水灯(单个c文件)”。

5.1.头文件引用和路径设置

本实验需要用到的头文件以及添加头文件包含路径的方法请参考“实验2-1-1:GPIO驱动LED(寄存器版本)”部分。
在程序设计中重新定义了P2寄存器的位变量P26和P27及P7寄存器的位变量P71和P72,这是为了控制GPIO口比较鲜明地知道其用途。

1./********************************************* 
2.引脚别名定义 
3.**********************************************/           
4.sbit LED_D1=P2^6;     //用户指示灯D1用IO口P26    
5.sbit LED_D2=P2^7;     //用户指示灯D2用IO口P27    
6.sbit LED_D3=P7^2;     //用户指示灯D3用IO口P72    
sbit LED_D4=P7^1;     //用户指示灯D4用IO口P71   

须知,语句“LED_D1=0; ”和语句“P26=0;” 效果是完全一样的;语句“LED_D1=1; ”和语句“P26=1;” 效果也是完全一样的。

5.2.编写代码

首先编写一个函数,该函数会控制4个用户LED分别依次点亮,代码如下。

程序清单:流水灯点亮函数

1./************************************************************************* 
2.功能描述:流水灯 
3.入口参数:无 
4.返回值:无 
5. ************************************************************************/  
6.void LED_Blink(void)  
7.{  
8.      LED_D1=0;        //点亮用户指示灯D1  
9.      LED_D2=1;        //熄灭用户指示灯D2  
10.      LED_D3=1;        //熄灭用户指示灯D3  
11.      LED_D4=1;        //熄灭用户指示灯D4  
12.      delay_ms(300);  
13.      LED_D1=1;        //熄灭用户指示灯D1  
14.      LED_D2=0;        //点亮用户指示灯D2  
15.      LED_D3=1;        //熄灭用户指示灯D3  
16.      LED_D4=1;        //熄灭用户指示灯D4  
17.      delay_ms(300);  
18.      LED_D1=1;        //熄灭用户指示灯D1  
19.      LED_D2=1;        //熄灭用户指示灯D2  
20.      LED_D3=0;        //点亮用户指示灯D3  
21.      LED_D4=1;        //熄灭用户指示灯D4  
22.      delay_ms(300);  
23.      LED_D1=1;        //熄灭用户指示灯D1  
24.      LED_D2=1;        //熄灭用户指示灯D2  
25.      LED_D3=1;        //熄灭用户指示灯D3  
26.      LED_D4=0;        //点亮用户指示灯D4  
27.      delay_ms(300);  
28.      LED_D1=1;        //熄灭用户指示灯D1  
29.      LED_D2=1;        //熄灭用户指示灯D2  
30.      LED_D3=1;        //熄灭用户指示灯D3  
31.      LED_D4=1;        //熄灭用户指示灯D4  
32.      delay_ms(300);  
33.}  

然后,在主函数中先对P2.6、P2.7、P7.1、P7.2口进行模式配置,后主循环中调用流水灯函数,这样可观察到指示灯D1、D2、D3、D4被流水点亮。

代码清单:主函数

1.int main(void)  
2.{  
3.    P2M1 &= 0x3F;   P2M0 &= 0x3F;     //设置P2.6~P2.7为准双向口  
4.    //P2M1 &= 0x3F; P2M0 |= 0xC0;   //设置P2.6~P2.7为推挽输出  
5.  //P2M1 |= 0xC0;   P2M0 &= 0x3F;   //设置P2.6~P2.7为高阻输入  
6.  //P2M1 |= 0xC0;   P2M0 |= 0xC0;   //设置P2.6~P2.7为开漏输出  
7.    P7M1 &= 0xF9;   P7M0 &= 0xF9;     //设置P7.1~P7.2为准双向口  
8.    //P7M1 &= 0xF9; P7M0 |= 0x06;   //设置P7.1~P7.2为推挽输出  
9.  //P7M1 |= 0x06;   P7M0 &= 0xF9;   //设置P7.1~P7.2为高阻输入  
10.  //P7M1 |= 0x06;   P7M0 |= 0x06;   //设置P7.1~P7.2为开漏输出  
11.      
12.  while(1)  
13.  {  
14.        LED_Blink();      //指示灯流水点亮  
15.    }  
16.}  

6.流水灯实验(多个c文件)

6.1.工程需要用到的c文件

本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。

表6:实验需要用到的C文件
序号文件名后缀功能描述
1led.c包含与用户led控制有关的用户自定义函数
2delay.c包含用户自定义延时函数

6.2.头文件引用和路径设置

■ 需要引用的头文件
因为在“main.c”文件中使用了控制led的函数和延时函数(延时函数没有在main.c中定义),所以需要引用下面的头文件。

1.#include    "led.h"  
2.#include "delay.h" 

■ 需要包含的头文件路径
本例需要包含的头文件路径如下表:

表7:头文件包含路径
序号路径描述
1…\ Sourceled.h和delay.h头文件在该路径,所以要包含
2…\UserSTC8.h头文件在该路径,所以要包含

MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
在这里插入图片描述

图16:添加头文件包含路径

6.3.编写代码

首先在delay.c文件中编写两个延时函数delay_ms和Delay10us,delay_ms函数是毫秒延时,Delay10us函数是10微秒延时,代码如下。

程序清单:延时函数

1./************************************** 
2.功能描述:延时函数 
3.入口参数:uint16 x ,该值为1时,延时1ms 
4.返回值:无 
5.***************************************/  
6.void delay_ms(uint16 x)   
7.{    
8.    uint16 j,i;     
9.    for(j=0;j<x;j++)     
10.    {      
11.        for(i=0;i<1580;i++);     
12.    }    
13.}

程序清单:延时函数

1./******************************************************************* 
2.功能描述:延时函数,延时约10us,在11.0592MHZ下 
3.入口参数:无 
4.返回值:无 
5.********************************************************************/  
6.void Delay10us(void)          
7.{  
8.    uint8 i;  
9.    _nop_();  
10.    i = 33;  
11.    while (--i);  
12.}  

该delay_ms函数会在delay.h头文件中被声明,这样可以被外部调用。如下。

1.extern void delay_ms(uint16 x);  
extern void Delay10us(void);  

然后在led.c文件中封装和4个LED有关的所有基本操作函数。如下表所示的5个函数。

表8:用户LED基本操作函数汇集(详见led.c)
序号路径描述
1led_on点亮一个指定的LED
2led_off熄灭一个指定的LED
3led_toggle翻转一个指定的LED的状态
4led_on点亮开发板上的4个指示灯
5led_off熄灭开发板上的4个指示灯

LED基本操作函数程序清单如下:

程序清单:点亮一个指定的LED

1. /************************************************************************** 
2.功能描述:点亮一个指定的指示灯(D1、D2、D3、D4) 
3.入口参数:uint8 led_idx  (可取值LED_1、LED_2、LED_3、LED_4) 
4.返回值:无 
5. *************************************************************************/  
6.void led_on(uint8 led_idx)  
7.{  
8.  switch(led_idx)  
9.    {  
10.        case LED_1:  
11.      LED_D1=0;        //控制P2.6端口输出低电平,点亮用户指示灯D1  
12.          break;          
13.        case LED_2:  
14.      LED_D2=0;        //控制P2.7端口输出低电平,点亮用户指示灯D2  
15.          break;  
16.        case LED_3:  
17.      LED_D3=0;        //控制P7.2端口输出低电平,点亮用户指示灯D3  
18.          break;          
19.        case LED_4:  
20.      LED_D4=0;        //控制P7.1端口输出低电平,点亮用户指示灯D4  
21.          break;  
22.        default:  
23.          break;  
24.  }  
25.}  

程序清单:熄灭一个指定的LED

1./************************************************************************** 
2.功能描述:熄灭一个指定的指示灯(D1、D2、D3、D4) 
3.入口参数:uint8 led_idx  (可取值LED_1、LED_2、LED_3、LED_4) 
4.返回值:无 
5. *************************************************************************/  
6.void led_off(uint8 led_idx)  
7.{  
8.  switch(led_idx)  
9.    {  
10.        case LED_1:  
11.      LED_D1=1;        //控制P2.6端口输出高电平,熄灭用户指示灯D1  
12.          break;          
13.        case LED_2:  
14.      LED_D2=1;        //控制P2.7端口输出高电平,熄灭用户指示灯D2  
15.          break;  
16.        case LED_3:  
17.      LED_D3=1;        //控制P7.2端口输出高电平,熄灭用户指示灯D3  
18.          break;          
19.        case LED_4:  
20.      LED_D4=1;        //控制P7.1端口输出高电平,熄灭用户指示灯D4  
21.          break;  
22.        default:  
23.          break;  
24.  }  
25.}  

程序清单:翻转一个指定的LED的状态

1./************************************************************************** 
2.功能描述:翻转一个指定的指示灯(D1、D2、D3、D4) 
3.入口参数:uint8 led_idx  (可取值LED_1、LED_2、LED_3、LED_4) 
4.返回值:无 
5.*************************************************************************/  
6.void led_toggle(uint8 led_idx)  
7.{  
8.  switch(led_idx)  
9.    {  
10.        case LED_1:  
11.      LED_D1=~LED_D1;      //控制P2.6端口输出不同于上一次的电平,翻转用户指示灯D1  
12.          break;          
13.        case LED_2:  
14.      LED_D2=~LED_D2;    //控制P2.7端口输出不同于上一次的电平,翻转用户指示灯D2  
15.          break;  
16.        case LED_3:  
17.      LED_D3=~LED_D3;      //控制P7.2端口输出不同于上一次的电平,翻转用户指示灯D3  
18.          break;          
19.        case LED_4:  
20.      LED_D4=~LED_D4;    //控制P7.1端口输出不同于上一次的电平,翻转用户指示灯D4  
21.          break;  
22.        default:  
23.          break;  
24.  }  
25.}  

程序清单:同时点亮开发板上的4个指示灯

1./************************************************************************** 
2.功能描述:点亮开发板上的4个指示灯(D1、D2、D3、D4) 
3.入口参数:无 
4.返回值:无 
5. *************************************************************************/  
6.void leds_on(void)  
7.{  
8.      LED_D1=0;        //控制P2.6端口输出低电平,点亮用户指示灯D1  
9.      LED_D2=0;        //控制P2.7端口输出低电平,点亮用户指示灯D2  
10.      LED_D3=0;        //控制P7.2端口输出低电平,点亮用户指示灯D3  
11.      LED_D4=0;        //控制P7.1端口输出低电平,点亮用户指示灯D4  
12.}  

程序清单:同时熄灭开发板上的4个LED

1./************************************************************************** 
2.功能描述:熄灭开发板上的4个指示灯(D1、D2、D3、D4) 
3.入口参数:无 
4.返回值:无 
5. *************************************************************************/  
6.void leds_off(void)  
7.{  
8.      LED_D1=1;        //控制P2.6端口输出高电平,熄灭用户指示灯D1  
9.      LED_D2=1;        //控制P2.7端口输出高电平,熄灭用户指示灯D2  
10.      LED_D3=1;        //控制P7.2端口输出高电平,熄灭用户指示灯D3  
11.      LED_D4=1;        //控制P7.1端口输出高电平,熄灭用户指示灯D4  
12.} 

在led.c文件中还编写了一个流水灯点亮的函数LED_Blink,该函数调用LED的基本函数实现流水点亮4个用户指示灯的目的。代码如下。

程序清单:流水灯点亮函数

1./************************************************************************** 
2.功能描述:流水灯 
3.入口参数:无 
4.返回值:无 
5. *************************************************************************/  
6.void LED_Blink(void)  
7.{  
8.        leds_off();           //熄灭所有用户指示灯  
9.        led_on(LED_1);        //点亮用户指示灯D1  
10.        delay_ms(300);  
11.        leds_off();           //熄灭所有用户指示灯  
12.        led_on(LED_2);        //点亮用户指示灯D2  
13.        delay_ms(300);  
14.        leds_off();           //熄灭所有用户指示灯  
15.        led_on(LED_3);        //点亮用户指示灯D3  
16.        delay_ms(300);  
17.        leds_off();           //熄灭所有用户指示灯  
18.        led_on(LED_4);        //点亮用户指示灯D4  
19.        delay_ms(300);  
20.        leds_off();           //熄灭所有用户指示灯  
21.        delay_ms(300);  
}  

在led.h头文件中会声明可供外部调用的函数,LED_Blink函数便是其中之一。如下。

1.extern void led_on(uint8 led_idx);  
2.extern void led_off(uint8 led_idx);  
3.extern void led_toggle(uint8 led_idx);  
4.extern void leds_on(void);  
5.extern void leds_off(void);  
6.extern void LED_Blink(void);  

最后,在主函数中先对P2.6、P2.7、P7.1、P7.2口进行模式配置,后主循环中调用流水灯函数,这样可观察到指示灯D1、D2、D3、D4被流水点亮。

代码清单:主函数

1.int main(void)  
2.{  
3.    P2M1 &= 0x3F;   P2M0 &= 0x3F;     //设置P2.6~P2.7为准双向口  
4.    //P2M1 &= 0x3F; P2M0 |= 0xC0;   //设置P2.6~P2.7为推挽输出  
5.  //P2M1 |= 0xC0;   P2M0 &= 0x3F;   //设置P2.6~P2.7为高阻输入  
6.  //P2M1 |= 0xC0;   P2M0 |= 0xC0;   //设置P2.6~P2.7为开漏输出  
7.    P7M1 &= 0xF9;   P7M0 &= 0xF9;     //设置P7.1~P7.2为准双向口  
8.    //P7M1 &= 0xF9; P7M0 |= 0x06;   //设置P7.1~P7.2为推挽输出  
9.  //P7M1 |= 0x06;   P7M0 &= 0xF9;   //设置P7.1~P7.2为高阻输入  
10.  //P7M1 |= 0x06;   P7M0 |= 0x06;   //设置P7.1~P7.2为开漏输出  
11.  
12.  while(1)  
13.  {  
14.      LED_Blink();    //指示灯流水点亮  
15.    }  
16.} 
好啦!以上就是今天要讲的内容,当然期间也咨询了艾克姆科技的技术人员帮忙搞定的,希望对你有所帮助!
  • 7
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

电子友人张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值