本节实现LED周期闪烁和LED流水灯。
目录
一、LED闪烁
二、LED流水灯
一、LED闪烁
首先来实现让一个 LED 以 1 秒为周期闪烁。下面是具体步骤。
首先,新建一个工程,在外面再次新建一个名为 “2 - 2 led 闪烁” 的文件夹,工程名设为 “project”。然后选择器件,可搜索 “at”,选择Atmel的 89C52,点击 “OK”。启动文件点击否,右键添加新的 C 文件main.c,点击 “add”。
在右侧编写 C 文件代码,先右键添加一个头文件,一般会添加常见的头文件。接着编写主函数 “void main ()”,加上一对花括号,并按 “Tab” 键缩进。
先把上一节点亮一个 LED 的代码 “P2 = 0xfe” 写出来,若要熄灭 LED,则使用 “P2 = 0xff” ,二进制的 8 个 1 表示所有 LED 都不亮。
下面设置一个主循环的死循环 “while (1)”,在这个循环里要实现 LED 的亮灭循环。将点亮和熄灭 LED 的代码放到 “while (1)” 循环中,并注意代码对齐,选中这两行按 “Tab” 键缩进。
编译代码后,每次建工程都要在选项里选择创建 HEX 文件,再次点击编译。
点击打开程序文件,找到 LED 闪烁的程序并下载。此时会发现 LED 始终亮着,只是亮度不是特别亮,这是因为单片机运行速度是兆赫兹等级,每秒能运行 100 万次,亮灭代码执行得特别快,LED 闪烁速度太快,肉眼难以察觉。
为了让 LED 以 1 秒为周期闪烁,需要在亮灭代码执行后分别进行 500 毫秒的延时。
可以使用 STC-ISP 里的软件延时计算器来生成延时代码,选择定时长度为 500 毫秒,同时要注意系统频率,开发板上的晶振是 12 兆赫兹,所以将系统频率改成 12.000 兆赫兹。指令级默认选的 “Y5” 适用于 15 系列,而我们的单片机是 89 系列,要改成 “Y1”,它适用于 89C、89L 等。
void Delay500ms() //@12.000MHz{unsigned char i, j, k;_nop_();i = 4;j = 205;k = 187;do{do{while (--k);} while (--j);} while (--i);}
点击复制代码,在主函数上面粘贴,会生成一个延时子函数。函数左边的返回值 “void” 表示函数执行后没有返回值,括号里若不写传入参数则默认是 “void”。生成的函数里定义了几个变量,通过 “do - while” 循环兜圈子计数来达到延时目的,其中还有个 “nop” 函数。
编译时会出现一个错误,提示 “nop” 没有函数原型,解决办法是添加一个名为 “” 的头文件,该头文件里定义了一些函数,包括 “nop”,它是一个空语句。
再次编译又会出现警告,提示 “未调用段”,意思是定义了 “delay500 毫秒” 子函数但未调用。
在主函数里调用这个子函数,即 “Delay500 ms ();” ,在点亮和熄灭 LED 代码后分别调用。
如下为完整代码:
#include#includevoid Delay500ms() //@12.000MHz{unsigned char i, j, k;_nop_();i = 4;j = 205;k = 187;do{do{while (--k);} while (--j);} while (--i);}void main(){while(1){P2=0xFE; //1111 1110Delay500ms();P2=0xFF; //1111 1111Delay500ms();}}
点击编译,没有错误和警告,将代码下载到单片机,就能看到 LED 以 1 秒为周期闪烁。
二、LED流水灯
有了前两个代码的经验,我们可以完成第三个 LED 流水灯程序。具体步骤如下:
首先,重新建一个工程,点击新建,创建一个名为 “2 - 3 LED 流水灯” 的文件夹,工程名设为 “project”,选择器件 “at89c52”,点击 “OK”。新建并添加新的 C 文件。
接下来复习前两个的代码,先添加头文件,然后编写主函数 “void main ()”,加上一对花括号并回车,按 “Tab” 键缩进,确保括号匹配并在同一列,遇到花括号进行缩进。在主函数里写一个 “while (1)” 循环,添上花括号
把上一个 LED 以 1 秒为周期闪烁的代码写出来,即 “P2 = 0xFE” ,这是点亮第一个 LED,然后进行延时 “Delay 500ms ();”。“Delay 500ms” 函数需要提前定义,在stc-isp选好相关设置后点击 “生成代码”复制代码粘贴过来。代码里的 “nop” 可以删掉(不删更加精确),如果不删则要在上面包含头文件 “” ,这个头文件大小写要求不严格,但标识符大小写区分。
#include#includevoid Delay500ms() //@12.000MHz{unsigned char i, j, k;_nop_();i = 4;j = 205;k = 187;do{do{while (--k);} while (--j);} while (--i);}void main(){while(1){P2=0xFE;//1111 1110Delay500ms();}}
延时之后,复制点亮和延时的两行代码,多次粘贴。原本是 LED 亮灭,现在要实现流水灯效果。第一个 LED 点亮是二进制的 “1111 1110”,第二个是 “1111 1101”,第三个是 “1111 1011” ,以此类推,通过复制粘贴让 “0” 左移,实现流水灯的二进制表示。再将二进制转化为十六进制,如 “1111 1110” 是 “fe”,“1111 1101” 是 “fd” 等,多转化几次就能记住转换方法。
#include#includevoid Delay500ms() //@12.000MHz{unsigned char i, j, k;_nop_();i = 4;j = 205;k = 187;do{do{while (--k);} while (--j);} while (--i);}void main(){while(1){P2=0xFE;//1111 1110Delay500ms();P2=0xFD;//1111 1101Delay500ms();P2=0xFB;//1111 1011Delay500ms();P2=0xF7;//1111 0111Delay500ms();P2=0xEF;//1110 1111Delay500ms();P2=0xDF;//1101 1111Delay500ms();P2=0xBF;//1011 1111Delay500ms();P2=0x7F;//0111 1111Delay500ms();}}
点击编译代码,若下载时发现没有文件,需要在选项的 “output” 里创建 HEX 文件,重新编译后点击下载
重启电源开关,就能看到 LED 以 500 毫秒为间隔向左移动。这里使用的是复制粘贴的方式,如果花样多,可能要复制很多行代码,之后会介绍更高级的操作方式。
第三个 LED 流水灯程序完成后保存。当前程序是以 500 毫秒为变化时间,如果想让流水灯走得更快,以 200 毫秒为变化间隔,就需要重新写延时函数,这会使代码不灵活。为解决这个问题,可以改造代码,让一个函数能根据传入的参数实现不同时长的延时。
重新建立一个名为 “2 - 4 LED 流水灯 plus” 的工程,选择 “at89C52” ,右键添加新的 C 文件。添加头文件,编写主函数基本框架。
把 500 毫秒的延时函数改造成能根据传入参数延时的函数,先生成一个 12 兆和 1 毫秒的延时函数,复制代码粘贴过来。
原本函数括号里没写入口参数,现在要写一个参数,比如 “xms”,并指定变量类型。
单片机在存储和执行代码时,变量运算很重要,需要用 “数据小盒子” 来存储数据。常见的数据类型有:
- “int”(整数形):用 16 位二进制表示一个十进制数字,在单片机系统里是 16 位,在计算机里是 32 位。默认是有符号的 “signed int” ,范围是 - 32768 到 32767;若要表示无符号整数,用 “unsigned int”,表示正整数范围是 0 到 65535。
- “long”(长整形):能表示更大的数,对应的无符号类型是 “unsigned long”。
- “float”(单精度浮点型)和 “double”(双精度浮点型):用于表示小数,使用科学计数法,没有无符号的实形。
- “char”(字符型):有符号的 “char” 和无符号的 “unsigned char”,以 8 位二进制表示数字,范围是 0 到 255,常用于存放字符,也可存数字。
在函数里,将 “xms” 定义为 “unsigned int” 类型的变量。
函数里使用 “while” 循环,判断 “xms” 是否非零,若非零则执行循环内容,每次循环将 “xms” 减 1,减 1 之前进行 1 毫秒的延时。“x 毫秒 = x 毫秒 - 1” 可缩写为 “x 毫秒 --”。
在主函数里调用这个带参数的延时函数,先编译确保代码正确。把上一节的流水灯代码复制粘贴过来,若打开方式有问题,可选择记事本打开。将原来无参数的延时 500 毫秒函数替换成新的带参数函数,如 “delay1ms (500);” ,替换完所有相关代码后编译,在工程选项里勾选 HEX 文件,再次编译并下载代码,重启后能看到 LED 流水灯效果。
完整main.c程序如下
#includevoid Delay1ms(unsigned int xms); //@12.000MHzvoid main(){while(1){P2=0xFE;//1111 1110Delay1ms(1000);P2=0xFD;//1111 1101Delay1ms(1000);P2=0xFB;//1111 1011Delay1ms(100);P2=0xF7;//1111 0111Delay1ms(100);P2=0xEF;//1110 1111Delay1ms(100);P2=0xDF;//1101 1111Delay1ms(100);P2=0xBF;//1011 1111Delay1ms(100);P2=0x7F;//0111 1111Delay1ms(100);}}void Delay1ms(unsigned int xms) //@12.000MHz{unsigned char i, j;while(xms){i = 2;j = 239;do{while (--j);} while (--i);xms--;}}
使用带参数的子函数更灵活,比如把延时参数改成 100 毫秒,再次编译下载,流水灯就会变快。
还能设置不同的延时长度,如前两个是 1 秒,后面是 100 毫秒,这样就能随心所欲地调节流水灯的延时。