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

文章目录

  • 前言
  • 一、硬件电路原理
    • 1.开发板指示灯硬件电路
    • 2.控制方式
      • 2.1.低电平有效的控制方式
      • 2.2.高电平有效的控制方式
      • 2.3.选择哪种方式来控制LED
    • 3.LED限流电阻的选取
      • 3.1.限流电阻的计算
      • 3.2.限流电阻的选择
    • 4.STC8A8K64S4A12系列单片机GPIO口
      • 4.1.芯片封装
      • 4.2.GPIO口工作模式
  • 二、软件编写
    • 1.GPIO寄存器汇集
    • 2.寄存器解析
      • 2.1.端口数据寄存器
      • 2.2.端口配置寄存器
    • 3.GPIO驱动LED实验(寄存器版本)
      • 3.1.头文件引用和路径设置
      • 3.2.编写代码
    • 4.GPIO驱动LED实验(库函数版本)
      • 4.1.工程需要用到的c文件
      • 4.2.头文件引用和路径设置
      • 4.3.编写代码
    • 5.流水灯实验(单个c文件)
      • 5.1.头文件引用和路径设置
      • 5.2.编写代码
    • 6.流水灯实验(多个c文件)
      • 6.1.工程需要用到的c文件
      • 6.2.头文件引用和路径设置
      • 6.3.编写代码

前言

最近空闲时间比较多,准备系统学习下单片机的开发,作为一名刚入行的新手来说,从哪种单片机开始学习很重要,经过一系列的选择过程……最终我选择了从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 引脚 功能描述 说明 备注
D1 P2.6 用户指示灯 非独立GPIO 蓝色LED灯
D2 P2.7 P2.7 用户指示灯 非独立GPIO
D3 P7.2 用户指示灯 独立GPIO 蓝色LED灯
D4 P7.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相关寄存器

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

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

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

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

1.#include    "GPIO.h"

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

表5:头文件包含路径

序号 路径 描述
1 …\ STC_LIB GPIO.h和config.h头文件在该路径,所以要包含
2 …\User STC8.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文件

序号 文件名 后缀 功能描述
1 led .c 包含与用户led控制有关的用户自定义函数
2 delay .c 包含用户自定义延时函数

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

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

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

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

表7:头文件包含路径

序号 路径 描述
1 …\ Source led.h和delay.h头文件在该路径,所以要包含
2 …\User STC8.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)

序号 路径 描述
1 led_on 点亮一个指定的LED
2 led_off 熄灭一个指定的LED
3 led_toggle 翻转一个指定的LED的状态
4 led_on 点亮开发板上的4个指示灯
5 led_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.}

好啦!以上就是今天要讲的内容,当然期间也咨询了艾克姆科技的技术人员帮忙搞定的,希望对你有所帮助!

【STC8A8K64S4A12开发板】—小白做GPIO点灯实验相关推荐

  1. 【STC8A8K64S4A12开发板】—小白做GPIO按键实验

    版权声明:本文为博主原创文章,转载请附上原文出处链接. 文章目录 前言 一.硬件电路设计 1.开发板用户按键硬件电路 2.按键检测接法 3.按键检测电路考虑因素 二.软件设计 1.寄存器解析 1.1. ...

  2. 【STC8A8K64S4A12开发板】—RS485总线通信

    版权声明:本文为博主原创文章,转载请附上原文出处链接. 文章目录 前言 一.硬件设计 1.开发板串口硬件电路 2.RS485电气性能 3.RS485通信协议 4.RS485电路设计 二.软件设计 1. ...

  3. 【STC8A8K64S4A12开发板】—4x4矩阵按键检测

    版权声明:本文为博主原创文章,转载请附上原文出处链接. 文章目录 前言 一.硬件电路设计 1.矩阵按键检测介绍 2.矩阵按键检测原理介绍 二.软件设计 1.矩阵按键扫描实验 – 指示灯闪烁 1.1.工 ...

  4. STC8A8K64S4A12开发板介绍

    版权声明:本文为博主原创文章,转载请附上原文出处链接. 文章目录 前言 一.STC8A8K64S4A12系列单片机介绍 二.STC8A8K64S4A12开发板概述 三.STC8A8K64S4A12开发 ...

  5. 【STC8A8K64S4A12开发板】—片外存储器FLASH讲解

    版权声明:本文为博主原创文章,转载请附上原文出处链接. 文章目录 前言 一.硬件设计 1.FRAM铁电存储器介绍 2.W25Q128JV存储芯片介绍 2.1.芯片引脚定义 2.2.芯片介绍及使用注意事 ...

  6. 【沁恒WCH CH32V307V-R1开发板两路ADC读取实验】

    [沁恒WCH CH32V307V-R1开发板两路ADC读取实验] 1. 前言 2. 软件配置 2.1 安装MounRiver Studio 3. ADC项目测试 3.1 打开ADC工程 3.2 编译项 ...

  7. 【STC8A8K64S4A12开发板】—开始做 定时器/计数器 实验啦

    版权声明:本文为博主原创文章,转载请附上原文出处链接. 文章目录 前言 一.硬件设计 1.TIMER概念介绍 2.STC8A8K64S4A12系列单片机定时器/计数器介绍 3.定时器/计数器工作模式 ...

  8. AB32开发板测评:GPIO控制RGB彩灯

    文章目录 [AB32VG1]开发板测评:RGB彩灯 一.初始准备 1.硬件平台 2.软件平台 二.操作步骤 1.RTT环境生成 2.RTT程序编写 3.程序下载,观察现象 三.心得 [AB32VG1] ...

  9. 迅为linux下串口,迅为IMX6ULL开发板Linux RS232/485驱动实验(上)

    在 arm 设备中串口是很常用的一个外设,不仅可以用来打印信息,还可以用于外接设备和其他传感器通信.根据不同的电平,串口分为 TTL 和 RS232,但是在Linux内核中的驱动程序是一样的,在串口上 ...

  10. 【MM32F5270开发板试用】GPIO输入+EXTI外部中断例程demo试用

    本篇文章来自极术社区与灵动组织的MM32F5270开发板评测活动,更多开发板试用活动请关注极术社区网站.作者:Zeee 前言: 首先,感谢灵动微电子与极术社区给予宝贵的试用机会.借助本次对Plus-F ...

最新文章

  1. 什么是动态DNS 动态DNS有什么用
  2. 4000字干货分享|一文学会搭建炫酷可视化大屏
  3. 手把手系列|实操市场风险Var
  4. vue2.0中transition组件的用法
  5. Apache Rewrite实现URL的301跳转和域名跳转
  6. 2007年会计从业资格练习第三章会计科目和账户
  7. 面向AMD64的文件xxx与项目的目标平台x86不兼容
  8. 零基础数据挖掘入门-二手车价格预测part(一):EDA-数据探索性分析
  9. web前端需要学MySQL吗_HTML是web前端工程师必须要学的
  10. [python 学习笔记] openpyxl -- excel样式设置 冻结窗格
  11. python 变量 fields_理解Python数据类:Dataclass fields 的概述(下)
  12. XDOJ 235-月份判断
  13. 他励直流电动机的制动
  14. java后端中GET 和 POST 底层原理,深入了解一下
  15. 随笔 qsnctf misc三体
  16. python爬取新闻并汇总_【python】 爬虫-爬取新闻
  17. 一个开发者自述:我是如何设计针对冷热读写场景的 RocketMQ 存储系统
  18. web项目设置最小宽度
  19. 520特辑 有一个IDC运维工程师的女朋友是什么体验
  20. 一个完整的产品专题页面策划思路是什么样子?

热门文章

  1. linux图片转成pdf文件大小,Linux下实现图片转pdf以及pdf转图片的命令_沃航科技
  2. python不允许使用关键字_Python不允许使用关键字作为变量名,但是允许使用内置函数名作为变量名,不过这会改变函数名的含义,所以不建议这样做...
  3. office各版本网盘免费下载
  4. 【直播礼物特效】vapxtool简介(一)(企鹅电竞)
  5. python程序实现rep后剪枝算法
  6. 个人博客搭建教程——基于WordPress
  7. matlab 正交park变换 功率守恒,克拉克(CLARKE)和帕克(PARK)变换.doc
  8. oracle 定时任务
  9. 精选 Hive 高频面试题11道,附答案详细解析(好文收藏)
  10. Blumind 思维图软件