6. LED闪烁及花式点灯
6.1 基于Delay实现LED闪烁
控制LED一亮一灭的过程使得LED不断闪烁,在程序中使用Delay语句实现,若实现0.5秒闪烁则为500微秒,可进行单位换算。
在工程中添加如下语句:
#define MAIN_Fosc 24000000UL
如上,定义了IRC系统时钟的频率为24MHz,在单片机使用过程中,内部设定IRC频率要和程序一致。定义主时钟频率后,接下来定义延时函数:
void delay_ms(u16 ms){
u16 i;
do{
i=MAIN_Fosc/6000;
while(i--);
}
while(--ms);
}
在如上代码中,"u16"定义为 typedef unsigned int u16;
该定义是stc.h中的定义语句,用于简化定义语句的书写。函数执行时,先定义了局部变量u16 i,变量i会执行对定义的时钟值运算后得到一个确切的值,并且在循环中执行i--(备注:变量运算性质不同,--i和i--的区别在于,前者--i是先进行运算后再输出i的值,后者i--是先输出i的值后再进行运算);该语句经过测试,24MHz的频率下属于1毫秒的延时。
那么在main()程序中,保持0.5s的不断闪烁则将程序写入到while(1)死循环中进行,如下程序所示:
while(1) // 死循环
{
P40 = 0; // 三极管引脚输出低电平
P60 = 1; // led4引脚输出高电平
P61 = 0; // led5引脚输出低电平
delay_ms(500);
P60 = 0; // led4引脚输出低电平
P61 = 1; // led5引脚输出高电平
delay_ms(500);
}
while和do while语句的功能使用不同:
![[Pasted image 20250106152124.png]]
while语句会执行括号内的条件1,当条件1为真时不断执行代码,当条件为假(条件1=0)时会跳出循环体;do while语句是先执行代码B后,进行一个while判断,当while()中条件2为真时执行代码,条件2为假时(条件2=0)跳出循环。
下面为使用案例:
//定义变量a的值
int a = 10;
//do while方式执行程序
do{
printf("a的值: %d\n",a);
a = a+1;
}while(a<20);
//while方式执行程序
while(a<20){
printf("a的值: %d\n",a);
a++;
}
在上述代码当中,要注意的是int a的值放在main()函数内外的性质不同,放在main()函数外表示全局变量,在任意函数中该a的值已被定义,放置在任意函数中如main()函数中,则表示局部变量,在该函数中a的值已被定义。代码部分do while先执行“打印a的值”后对变量“a"进行处理,当a变量的值已经不满足条件时,则跳出循环。while循环性质一样,在语句中执行对a变量处理,当a变量值无法满足条件语句时,则跳出循环。在一般的程序编写中,使用到的while语句相对do while较多。
在单片机中,使用如上示例来打印一个值,具体的示例代码如下:
int a = 10;
while(1){
if(DeviceState != DEVSTATE_CONFIGURED)
continue;
if(bUsbOutReady){
usb_OUT_done();
do{
printf("a的值为:%d\r\n",a);
a = a+1;
}while(a<20);
}
}
在如上代码中,while(1)循环内的if-----if语句是5.1节中说明的在单片机中使用printf语句,将此部分代码烧录后,打开调试软件的USB-CDC/HID串口助手并发送1作为数据,返回上列程序中计算的值。while方式执行程序代码如下,和do while几乎相似:
int a = 10;
while(1){
if(DeviceState != DEVSTATE_CONFIGURED)
continue;
if(bUsbOutReady){
usb_OUT_done();
while(a<20){
printf("a的值: %d\n",a);
a++;
}
}
}
6.2 函数及其他
6.2.1 函数的定义
函数的大致结构如下:
返回值 函数名称(入口参数)
{
函数要执行的功能
}
@返回值:没有返回值则为void
@函数名称:避开关键词,不重复,非特殊字符随便取
@入口参数:类型+名称,多个参数“,”分开,空则写void
一般来说在单片机中所使用的函数返回值为void,返回值的概念和用法可在网上查找到,此处使用ChatGPT来生成了一段回答:
在C语言中,“函数的返回值”是指函数执行完毕后,返回给调用者的一个值。返回值的类型由函数的声明决定。函数返回值可以是各种数据类型,例如 int、float、char,甚至可以是指针或自定义的结构体。
函数返回值的关键点:
1. 返回值类型:在函数定义时需要指定,比如 `int` 表示返回一个整数。
2. return语句:用于指定返回值,并终止函数的执行。
3. void类型:如果函数不需要返回值,可以用 void 声明。
示例1、返回整数:
#include <stdio.h>
// 定义一个函数,返回两个整数的和
int add(int a, int b) {
return a + b; // 返回两个整数的和
}
int main() {
int result = add(3, 5); // 调用函数并接收返回值
printf("The result is: %d\n", result);
return 0;
}
>>>>> The result is: 8
示例2、返回浮点数
#include <stdio.h>
// 定义一个函数,返回两个浮点数的乘积
float multiply(float x, float y) {
return x * y; // 返回两个浮点数的乘积
}
int main() {
float result = multiply(2.5, 4.0); // 调用函数并接收返回值
printf("The result is: %.2f\n", result);
return 0;
}
>>>>> The result is: 10.00
示例3、无返回值的函数void
#include <stdio.h>
// 定义一个函数,只打印信息,没有返回值
void greet() {
printf("Hello, World!\n");
}
int main() {
greet(); // 调用函数
return 0;
}
>>>>> Hello, World!
注意事项:
1. 如果函数声明了返回值类型(如 `int`),但没有使用 `return` 返回值,会导致未定义行为。
2. 如果函数声明为 `void` 类型,使用 `return` 时不能带返回值。
3. 函数的返回值可以直接用于表达式中,例如:
int x = add(1, 2) * 3; // 使用返回值进行运算
上述案例中列举了三类返回值示例。需要注意的是,在实际的程序编写过程中,需要避开已定义的关键词,对于冲突的用户关键词则需要重新命名使用。一般来说,此类关键词在编辑器中会以不同颜色被区分开来如char、void、while等,并且在实际的使用过程中不可重复命名,如已使用了delay_ms则下一个定义的函数名称就不可用了。
6.2.2 函数的声明
define定义用法为 #define name value
其中name为定义名称,在代码实例运行过程中,name的属性则由value表示。
下例会使用一个定义了的加法计算函数来直观地展示函数定义声明的作用:
int add(int parm1, int parm2){
return parm1 + parm2;
}
在如上代码中,入口函数为parm1、parm2,返回值为 parm1+parm2
,进行了计算(该示例参考了6.2.1中的示例1)。
在使用过程中,将该部分代码块放置在main()函数后,则需要进行声明,需要在void main()之前进行对add函数的声明:
int add(int parm1, int parm2);
在如上代码中,可见函数声明的大致结构如下:
返回值 函数名称(入口参数);
我们在实际的例程中使用一下加法函数查看下其效果如何:
//加法函数声明
int add(int parm1, int parm2);
void main(){
... 此处省略前部分代码 ...
while(1){
if( DeviceState != DEVSTATE_CONFIGURED )
continue;
if( bUsbOutReady ){
usb_OUT_done();
printf("计算结果为:%d\r\n",add(1,2));
}
}
}
//加法函数定义
int add(int parm1, int parm2){
return parm1 + parm2;
}
通过编译并烧录后,实际调试串口助手返回值为3。在上述程序中,printf语句中调用add函数,入口参数值为1、2,该值分别带入了 int parm1
和 int parm2
中,并且在add函数中执行了一步运算,再通过printf中的 %d
打印输出。
6.2.3 模块化编程
在上述内容中,介绍了函数定义以及函数声明如何进行,在函数声明中我们会发现在单个文件中既要定义函数又要声明函数才可调用函数,会使得在单个程序文件中编写代码变得极为繁琐。那么可根据需要实现函数定义的功能,来新建一个.c和.h文件存储该函数定义的功能,如下为示例:
在mathca.h文件中,建立基本的定义语句:
#ifndef __MATH_H
#define __MATH_H
#endif
上述代码中 #ifndef __MATH_H
含义为“如果没有定义 MATH_H”配合下列 #define __MATH_H
来定义一个MATH_H,且 #endif
结束定义。此部分语句的主要作用是防止在单个工程中重复定义语句。
在mathca.c中引用mathca.h:
#include "mathca.h"
完成上列步骤后,我们添加6.2.2中函数的部分 int add(int parm1, int parm2);
到mathca.h中:
#ifndef __MATH_H
#define __MATH_H
int add(int parm1, int parm2);
#endif
mathca.c文件中也需要添加add函数:
#include "mathca.h"
int add(int parm1, int parm2){
return parm1 + parm2;
}
需要注意的是,在main.c文件中引用了mathca.h后,添加一下引用路径:
![[Pasted image 20250108113122.png]]
在添加了引用文件夹后,可以将mathca.c和mathca.h放入到引用文件夹中,添加引用文件夹双击工程文件夹,弹出选择文件窗口,选择引用文件夹内的文件即可。
为了验证其可行性,我们添加一则新的应用:
#ifndef __MATH_H
#define __MATH_H
int add(int parm1, int parm2);
int sub(int parm1, int parm2);
int mul(int parm1, int parm2);
int div(int parm1, int parm2);
#endif
上述为mathca.h中代码
#include "mathca.h"
int add(int parm1, int parm2){
return parm1 + parm2;
}
int sub(int parm1, int parm2){
return parm1 - parm2;
}
int mul(int parm1, int parm2){
return parm1 * parm2;
}
int div(int parm1, int parm2){
return parm1 / parm2;
}
上述为mathca.c中代码
在main()函数中执行下列语句:
printf("计算结果为:%d\r\n",add(1,2));
printf("计算结果为:%d\r\n",sub(1,2));
printf("计算结果为:%d\r\n",mul(1,2));
printf("计算结果为:%d\r\n",div(1,2));
返回结果为:
![[Pasted image 20250108114838.png]]
作业部分:编写SOS求救灯光
while(1){
P40 = 0;
P60 = 1;
delay_ms(200);
P60 = 0;
delay_ms(200);
P60 = 1;
delay_ms(200);
P60 = 0;
delay_ms(200);
P60 = 1;
delay_ms(200);
P60 = 0;
delay_ms(200);
P60 = 1;
delay_ms(500);
P60 = 0;
delay_ms(500);
P60 = 1;
delay_ms(500);
P60 = 0;
delay_ms(500);
P60 = 1;
delay_ms(500);
P60 = 0;
delay_ms(500);
}