最近空闲时间比较多,准备说说STC8A8K64S4A12开发板。 实验做起来——先从GPIO点灯开始。
LED(Light Emitting Diode)是发光二极管的简称,在很多设备上常用它来做为一种简单的人机接口,如网卡、路由器等通过LED向用户指示设备的不同工作状态。所以,我们习惯把这种用于指示状态的LED称为LED指示灯。
STC8A8K64S4A12开发板上设计了4个LED指示灯,我们可以通过编程驱动LED指示灯点亮、熄灭、闪烁,从而达到状态指示的目的,LED指示灯驱动电路如下图所示。
图1:LED指示灯驱动电路
LED指示灯驱动电路是一个很常见、简单的电路,同时它也是一个典型的单元电路,对于初学者来说,类似常用的典型电路必须要掌握,不但要知其然、还要知其所以然。
接下来,我们来分析一下这个简单的LED指示灯驱动电路。
LED驱动电路设计的时候,要考虑两个方面:控制方式和限流电阻的选取。
LED指示灯控制方式分为高电平有效和低电平有效两种,高电平有效是单片机GPIO输出高电平时点亮LED,低电平有效是单片机GPIO输出低电平时点亮LED。
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熄灭。
绝大多数情况下,我们会选择使用低电平有效的控制方式,如艾克姆科技STC8A8K64S4A12开发板中的LED指示灯就是低电平有效。这样设计指示灯驱动电路的好处是:
① 单片机GPIO口低电平时的灌入电流一般比高电平时的拉电流要大,能提供足够的电流驱动LED。
② 单片机上电或复位启动时,GPIO口一般都是高阻输入,用低电平有效的控制方式可以确保LED在上电或复位启动时处于熄灭状态。
图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时限流电阻值计算如下:
根据上一节对限流电阻计算公式的描述及对选择的封装0805的指示灯参数的了解,计算出供电3.3V电流1mA时的限流电阻理论值是600Ω,供电5V电流1mA时的限流电阻理论值是2.3KΩ,为保证供电为3.3V时指示灯够亮但同时5V供电指示灯又不宜过亮,选择一个比较常见的阻值2K作为限流电阻。
还有一点需要强调的是,不同颜色的指示灯在即使同一亮度时所需的限流电阻不一定是相同的。这也是为什么有些产品的面板上有不同颜色的指示灯,各指示灯所使用的限流电阻不一样的原因。
STC8A8K64S4A12系列单片机GPIO口数量取决于芯片引脚的个数,芯片引脚个数和芯片封装密切相关。正常情况下,GPIO口数量是所选择单片机引脚个数减去5,因为单片机需要2个引脚作为供电引脚(电源正VCC、电源负GND),ADC外设会占用3个引脚(电源正ADC_Avcc、电源负ADC_Agnd、参考电压AVref)。
封装,Package,是把集成电路装配为芯片最终产品的过程,简单地说,就是把Foundry生产出来的集成电路裸片(Die)放在一块起到承载作用的基板上,把管脚引出来,然后固定包装成为一个整体。
STC8A8K64S4A12系列有多种封装,厂家批量常见的芯片封装是:LQFP44、LQFP48和LQFP64S。
STC8A8K64S4A12系列单片机所有GPIO口均有4种工作模式:准双向口/弱上拉(标准8051输出口模式)、推挽输出/强上拉、高阻输入(电流既不能流入也不能流出)、开漏输出。下面针对内部结构图进行分析。
■ 准双向口/弱上拉模式:
图7:GPIO准双向口/弱上拉模式内部框图
1)准双向口(弱上拉) 输出类型可用作输出和输入功能而不需要重新配置端口输出状态。这是因为准双向口有3个上拉晶体管可适应输入输出不同的需要。
2)手册中有这样一句话:准双向口(弱上拉)在读外部状态前,要先锁存为‘1’,才可读到外部正确的状态。下图分别就锁存数据为‘1’和‘0’时进行了分析。
图8:GPIO准双向口/弱上拉模式内部框图分析
3)由上图分析可知,准双向口(弱上拉)在读外部状态前,如果锁存为‘0’,则GPIO引脚状态被固定,无法读到外部正确的状态。
■ 推挽输出/强上拉模式:
图9:GPIO推挽输出/强上拉模式内部框图
1)强推挽输出配置的下拉结构与开漏输出以及准双向口的下拉结构相同,但当锁存器为1时可提供持续的强上拉。所以,推挽输出一般用于需要更大驱动电流的情况。
2)在控制LED时,如果采用的是高电平有效的控制方式,则控制LED的单片机GPIO口必须配置成推挽输出/强上拉模式方可。
■ 高阻输入模式:
图10:GPIO高阻输入模式内部框图
1)因带有一个施密特触发输入以及一个干扰抑制电路,GPIO配置为高阻输入时,电流既不能流入也不能流出GPIO口。
2)在很多STC相关的文档中有说的高阻态即是GPIO口被设置了高阻输入模式。
■ 开漏输出模式:
图11:GPIO开漏输出模式内部框图
1)开漏模式既可以读外部状态也可以对外输出高电平或低电平。
2)如果要正确读外部状态或需要对外输出高电平时,需外加上拉电阻。
STC8A8K64S4A12系列单片机提供了40个用于操作GPIO的寄存器,如下表所示:
表2:
首先普及一个常用知识点:为什么说STC8A8K64S4A12系列单片机是8位单片机呢?这个8位指的是什么?
一般来说某个单片机或微处理器是几位,指的是“机器字长”。每个单片机或微处理器最基本的功能是算术逻辑运算,而算术逻辑运算的主要部件是“算术逻辑单元(ALU)”。机器字长即是指ALU的数据位宽,也就是指令能直接处理的二进制位数。
通常单片机或微处理器的寄存器的位宽等于ALU的位宽,所以一般可通过识别单片机或微处理器寄存器的位宽来确定该单片机或微处理器是多少位的。我们所接触的STC的单片机,其寄存器都是8位的,所以STC的单片机都是8位的单片机。以后学习STM32F103系列的微处理器,其寄存器是32位的,所以会说STM32F103系列微处理器是32位的。
下图是对端口数据寄存器P0、P1、P2、P3、P4、P5、P6、P7的描述,端口数据寄存器各位代表对应端口的IO口,比如,P7端口数据寄存器B0位代表P7.0口,B7位代表P7.7口。
图12:端口数据寄存器
端口配置寄存器PnM1和PnM0都是8位的寄存器,PnM1和PnM0寄存器必须组合使用才能正确地配置IO口工作模式。
STC8A8K64S4A12系列单片机所有GPIO口均有4种工作模式:准双向口/弱上拉(标准8051输出口模式)、推挽输出/强上拉、高阻输入(电流既不能流入也不能流出)、开漏输出。每个GPIO口工作模式由PnM1和PnM0寄存器中的相应位控制。如下图。
图13:GPIO端口配置
■ 需要宏定义部分及引用的头文件
因为在“main.c”文件中使用了STC8的头文件“STC8.H”,所以需要引用下面的头文件。在头文件“STC8.H”中需要确定主时钟取值,所以宏定义主时钟值。
1.#define uint16 unsigned int
2.#define uint8 unsigned char
在程序设计中会用到定义变量的类型,为了定义变量方便,将较为复杂的“unsigned int”和“unsigned char ”进行了宏定义。
#define uint16 unsigned int
#define uint8 unsigned char
这样,再定义变量时可直接使用“uint16”和“uint8”来取代“unsigned int”和“unsigned char ”即可。
■ 需要包含的头文件路径
本例需要包含的头文件路径如下表:
MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
图14:添加头文件包含路径
首先介绍下毫秒级的延时函数。控制指示灯亮和灭需要中间有足够的间隔时间,这个间隔一般通过延时函数实现。微秒级的延时时间很难控制,这和主频大小及其精度有密切关系。但毫秒级的延时还是可以控制的,下面给出在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地址值^变量位地址值。
程序清单:头文件“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不停闪烁的现象。
代码清单:主函数
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.}
本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。
该GPIO.c是STC官方提供的有关GPIO配置的函数库。
■ 需要引用的头文件
因为在“main.c”文件中使用了GPIO相关的库,所以需要引用下面的头文件。
1.#include "GPIO.h"
■ 需要包含的头文件路径
本例需要包含的头文件路径如下表:
MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
图15:添加头文件包含路径
首先在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.}
本实验需要用到的头文件以及添加头文件包含路径的方法请参考“实验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;” 效果也是完全一样的。
首先编写一个函数,该函数会控制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.}
本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。
■ 需要引用的头文件
因为在“main.c”文件中使用了控制led的函数和延时函数(延时函数没有在main.c中定义),所以需要引用下面的头文件。
1.#include "led.h"
2.#include "delay.h"
■ 需要包含的头文件路径
本例需要包含的头文件路径如下表:
MDK中点击魔术棒,打开工程配置窗口,添加头文件包含路径。
首先在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个函数。
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.}
好啦!以上就是今天要讲的内容,当然期间也咨询了艾克姆科技的技术人员帮忙搞定的,希望对你有所帮助!
全部0条评论
快来发表一下你的评论吧 !