本节书摘来自华章计算机《Arduino开发实战指南:LabVIEW卷》一书中的第2章,第2.2节,作者:余崇梓著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2.2 Arduino程序结构及基本函数
2.2.1 Arduino程序结构
Arduino编程语言的基本结构非常简单,包含至少两个部分或者叫两个函数。其他语句及函数都被包括在这两个不可或缺的函数中。
代码清单2-1:Arduino程序结构
void setup() //setup函数
{
statements;
}
Void loop() // loop函数
{
statements;
}
setup函数负责准备工作,loop函数负责执行。两个函数都是程序正常运行所必需的
部分。
setup函数跟随在程序最开始的变量声明之后,它是程序运行的第一个函数,只运行一次,主要用来设置端口模式(pinMode)或者初始化串口通信。
setup函数只在程序开始的时候调用一次,用它来初始化端口模式,或者开始串口通信。即使程序中没有实际要运行的语句也需要有setup函数。
代码清单2-2:setup函数结构
void setup()
{
pinMode(pin,OUTPUT); //设置“pin”为输出端口
}
loop函数跟随其后,主要包含将连续执行的代码,比如:读取(输入)、触发(输出)等。这个函数是所有Arduino程序的核心,它包含了大量的程序任务,使程序能够控制Arduino板。
代码清单2-3:loop函数结构
void loop()
{
digitalWrite(pin,HIGH); // 将“pin”置高电平
delay(1000); // 保持1s
digitalWrite(pin,LOW); // 将“pin”置低电平
delay(1000); // 保持1s
}
2.2.2 Arduino数据类型及运算符
Arduino IDE 开发语言使用的是C语言,所以Arduino的数据类型及运算符同基本C语言一样,这里进行简单介绍。
1.数据类型
1)整型
整型数据分为短整型和长整型,关键字分别为int和long,短整型为16位值,范围为32767~-32768;长整型为32位值,范围为2147483647~-2147483648。
2)字符型
字符型关键字为char。char在内存中占一个字节。
3)实型(浮点型)
实型数据分为单精度型和双精度型,关键字分别为float和double,float为32位值,范围为3.4E+38~-3.4E+38;double为64位值,范围为1.7E+308~-1.7E+308。
4)枚举型
枚举型关键字为enum。
2.常量
常量为程序运行中值不能改变的量。如:-1、23、'd'。常量也可以用一个标识符代表,例如:#define n30,定义n代表常量30。
Arduino中有几种特别的常量。
1)TRUE/FALSE常量
对于布尔常量,FALSE一般定义为0;TRUE经常定义为1,有时候也定义为非0。
代码清单2-4:TRUE/FALSE常量
if(b== TRUE);
{
doSomething;
}
2)HIGH/LOW常量
这些常量定义端口为HIGH或者LOW,经常用来读取或者写入数字端口。HIGH一般定义为逻辑值1、ON或者5V;而LOW一般定义为逻辑值0、OFF或者0V。
如:digitalWrite(13,HIGH)
3)INPUT/OUTPUT
这些常量用在pinMode()函数中,定义端口的模式为输入或者输出。
如:pinMode(13,OUTPUT);
3.变量
变量是一种通过命名及存储数值以便被后面程序使用的方法。下面的代码声明了一个命名为inputVariable的变量,并且将模拟输入端口2的值赋给该变量。所有的变量在使用前必须声明。
int inputVariable = 0; //声明变量,并赋初始值为0
intputVariable = analogRead(2); //将模拟输入端口2的值赋给变量
变量可以在程序最开始即void setup()之前,在某个函数内,或者某个语句(比如for语句)内声明。变量声明的地方决定了变量的作用域。
全局变量在setup()函数之前声明,这个全局变量可以被程序中的所有函数和语句
使用。
局部变量在函数内部或者for循环的一部分中被声明。它的作用域也只有在函数内部或者for循环内部。
代码清单2-5展示了不同的变量使用情况。
代码清单2-5:变量的使用
int value; //value可以在任何函数中使用
void setup()
{
//不需要setup
}
void loop()
{
for(int i=0;i<20;)
{
i++; //i只能在for loop中使用
}
float f; //f只能在loop中使用
}
4.运算符
运算符按照优先级顺序由上向下排列,如表2-1所示。
5.注释
在Arduino程序中有两种注释符,分别为:
块注释,以“/”开头并以“/”结束的字符串,两者之间的部分为注释。
行注释,使用“//”进行行注释,其后的字符串为注释。
注释可以出现在程序任何位置,添加注释可以帮助理解程序。
2.2.3 Arduino的控制语句
Arduino程序流程控制语句也与C语言中的流程控制语句相同,这里做一下简单介绍。
1.条件选择语句
(1)if语句
一般形式如下:
if (condition) statement;
或者
if (condition)
{
compound statement;
}
if语句流程图如图2-10所示。
代码清单2-6为if语句使用示例。
代码清单2-6:if语句示例
#include <stdio.h>
#define TRUE 1
#define FALSE 0
main ()
{
int i;
if (TRUE)
{
printf ("This is always printed");
}
if (FALSE)
{
printf ("This is never printed");
}
}
(2)if...else语句
一般形式如下:
if (condition) statement1; else statement2;
或者
if (condition)
{
statements;
}
else
{
statements;
}
if...else语句的流程图如图2-11所示。
图2-11 if...else语句的流程图
代码清单2-7:if...else语句示例
#include <stdio.h>
main()
{
int i;
scanf ("%ld",i);
if (i > 0)
{
printf ("That number was positive!");
}
else
{
printf ("That number was negative or zero!");
}
}
(3)switch语句
一般形式为:
switch (int or char expression)
{
case constant expression 1 : statement 1;
break; /* optional */
case constant expression 2 : statement 2;
break; /* optional */
case constant expression n : statement n;
break; /* optional */
default : default statement;
break;
}
对于switch条件语句,如果找到相同的结果值则执行case内的程序语句,当执行完case语句后,并不会直接离开switch条件语句,还是会住下继续执行其他case语句和default语句,这种情形称为“失败经过”(Falling Through)现象。
因此,在每一条case语句最后,必须加上break语句来结束switch语句,才可以避免“失败经过”的情况。default语句可放在switch条件语句的任何位置,如果找不到吻合的结果值,最后会执行default语句,除非摆在最后时,才可以省略default语句内的break语句,否则还是必须加上break语句。
代码清单2-8:switch语句示例
#include <stdio.h>
main()
{
for(int i=0; i<3; i++)
{
switch(i)
{
default: printf("%d", i);
case 1: printf("%d", i); break;
case 2: printf("%d", i);
}
}
return 0;
}
2.循环语句
(1)while循环语句
一般形式为:
while (condition)
{
statements;
}
while语句的流程图如图2-12所示。
代码清单2-9:while语句示例
/* count all the spaces in an line of input */
#include <stdio.h>
main()
{
char ch;
short count = 0;
printf ("Type in a line of text\n");
while ((ch = getchar()) != '\n')
{
if (ch == ' ')
{
count++;
}
}
printf ("Number of space = %d\n",count);
}
(2)do...while语句
一般形式为:
do
{
statements;
}
while (condition)
do...while语句的流程图如图2-13所示。
代码清单2-10:do...while语句示例
/**********************************************/
/* do ... while demo */
/**********************************************/
/* print a string enclosed by quotes " " */
/* gets input from stdin i.e. keyboard ???*/
/* skips anything outside the quotes ???*/
#include <stdio.h>
/*************************************************/
/* Level 0 */
/*************************************************/
main()
{
char ch,skipstring();
do
{
if ((ch = getchar()) == '"')
{
printf ("The string was:\n");
ch = skipstring();
}
}
while (ch != '\n')
{
}
}
/*************************************************/
/* Level 1 */
/*************************************************/
char skipstring () /* skip a string "" */
{
char ch;
do
{
ch = getchar();
putchar(ch);
if (ch == '\n')
{
printf ("\nString was not closed ");
printf ("before end of line\n");
break;
}
}
while (ch != '"')
{
}
return (ch);
}
(3)for循环语句
一般形式为:
for (expression1; condition; expression2)
{
}
for循环的流程图如图2-14所示。
代码清单2-11:for语句示例
/************************************************/
/* Prime Number Generator #1 */
/************************************************/
/* Check for prime number by raw number */
/* crunching. Try dividing all numbers */
/* up to half the size of a given i, if */
/* remainder == 0 then not prime! */
#include <stdio.h>
#define MAXINT 500
#define TRUE 1
#define FALSE 0
/*************************************************/
/* Level 0 */
/*************************************************/
main()
{
int i;
for (i = 2; i <= MAXINT; i++)
{
if (prime(i))
{
printf ("%5d",i);
}
}
}
/*************************************************/
/* Level 1 */
/*************************************************/
prime (i) /* check for a prime number */
int i;
{
int j;
for (j = 2; j <= i/2; j++)
{
if(i%j==0)
{
return FALSE;
}
}
return TRUE;
}
3.中断语句
(1)break语句
break语句的作用是跳出整个循环。如有嵌套的循环,则跳出最近的循环体。
代码清单2-12:break语句示例
/* an expensive way of assigning i to be 12 would be: */
for (i = 1; i <= 20; i++)
{
if(i==12)
{
break;
}
}
/* another way of making skipgarb() */
while(TRUE)
{
ch = getchar();
if(ch=='\n')
{
break;
}
}
(2)continue语句
continue语句的作用是跳出一次循环,即忽略continue后面的其他语句,紧接着执行下一次循环。
代码清单2-13:continue语句示例
/* to avoid dividing by zero */
for (i = -10; i <= 10; i++)
{
if (i==0)
{
continue;
}
printf ("%d", 20/i);
}
2.2.4 Arduino的基本函数
函数是包含一个名字和一段语句的代码,当函数被调用的时候,函数里面的语句被执行。前面已经讨论过void setup()和void loop()函数了,后面会讨论更多的函数。
可以自己编写自己的函数,自定义函数格式如下:
type functionName(parameters)
{
statements;
}
type为函数返回值类型,如果没有返回值,函数类型为void。parameters是传递给函数的参数。
“{}”用来定义函数块或语句块的开始和结束。如:
type function()
{
statements;
}
“;”用来结束一个语句,也用来在for循环中分隔不同的部分。
Arduino程序中有一些特定的函数,用来实现Arduino特定的操作,如下所示。
(1)pinMode(pin,mode)
用在void setup()函数中,用来配置某个端口为输入端口还是输出端口。
pinMode(pin,OUTPUT); //配置"pin"为输出端口
Arduino的数字端口默认为输入,所以不需要用pinMode()函数专门配置为输入端口。被配置为输入的端口处于高阻的状态。ATmega单片机芯片内部有20 kΩ上拉电阻,可以方便地通过软件来配置。这些上拉电阻可以通过下面的方式配置:
pinMode(pin,INPUT); //设置"pin"为输入端口
digitalWrite(pin,HIGH); //打开上拉电阻
上拉电阻一般用来连接像开关之类的输入信号。注意,在上面的例子中,并没有把端口配置为输出,而是启用了内部的上拉电阻。
配置为OUTPUT输出类型的端口处于低阻抗的状态,并能提供40mA电流给其他器件或电路。这个电流足够点亮LED,但是不足以驱动继电器、电磁铁及电机。
如果Arduino的端口被短路或者电流过大,会损坏输出端口甚至损坏ATmega芯片。所以,通常在OUTPUT输出端口上串联一个470Ω或者1kΩ的电阻会比较好。
(2)digitalRead(pin)
读取数字端口的值,一般为HIGH或者LOW。
pin可以为变量或者常量(0~13)。如:
value = digitalRead(pin);
(3)digitalWrite(pin,value)
在一个指定的端口输出逻辑HIGH或者LOW,pin可以为变量或者常量(0~13)。如:
digitalWrite(pin,HIGH); //设置"pin"为高电平
在代码清单2-14中,按键连接到数字输入端口,LED连接到数字输出端口,当数字按键按下的时候将LED点亮。
代码清单2-14:数字输入输出示例
int led =13;
int pin =7;
int value =0;
void setup()
{
pinMode(led,OUTPUT);
pinMode(pin,INPUT);
}
void loop()
{
value = digitalRead(pin);
digitalWrite(led,value);
}
(4)analogRead(pin)
读取模拟输入端口的数值,模拟输入端口的分辨率为10位,模拟输入端口为A10~A15,函数返回值范围为0~1023。如:
value = analogRead(pin);
注意,模拟输入端口不像模拟输出端口,不需要配置为INPUT或者OUTPUT。
(5)analogWrite(pin,value)
使用带有PWM硬件功能的输出端口产生类似于模拟值的PWM信号。这个函数可以在数字端口3、5、6、9、10、11使用。value数值可以为变量或者常量(0~255)。如:
analogWrite(pin,value);
如果value为0,那么在相应的端口,电压一直保持为0V;如果value为255,那么相应端口的电压一直保持为5V;如果vaule为64,那么端口电压将保持3/4时间为0V,1/4时间为5V;如果value为192,那么端口电压将保持1/4时间为0V,3/4时间为5V。
由于这是一个硬件功能,当analogWrite()被调用后,端口将始终保持稳定的波形,直至下次调用。
在代码清单2-15中,读取模拟输入端口的值,将该值除以4,并将结果通过PWM输出端口输出。
代码清单2-15:模拟采集示例
int led = 10;
int pin = 0;
int value;
void setup(){}
void loop()
{
value = analogRead(pin);
value /= 4;
analogWrite(led,value);
}
(6)delay(ms)
暂停程序等待设定的毫秒数,1000代表1000ms,即1s。如:
delay(1000);//等待1s
(7)millis()
返回Arduino板开始运行当前程序至今的毫秒数(时间),返回值为无符号长整型。如:
value = millis();
注意,这个返回值在9小时后可能溢出(重置为0)。
(8)min(x,y)
比较两个值的大小,返回较小的值。如:
value = min(value,100);
(9)max(x,y)
比较两个值的大小,返回较大的值。如:
value = max(value,100);
(10)randomSeed(seed)
设定一个值,或者种子,作为随机数函数的起始值。
(11)randomSeed(value);
Arduino只能产生伪随机数。seed可以是一个变量、函数返回值(millis())或者模拟端口采集的数值。
(12)random(min,max)
random函数允许返回设定的在最小值和最大值之间的伪随机数。如:
vaule = random(100,200); //设置value为在100~200范围内的一个随机数
注意,该函数需要在randomSeed()函数之后使用。
代码清单2-16创建了一个0~255之间的随机数,并且通过PWM端口将随机数输出。
代码清单2-16:random函数示例
int randNumber;
int led = 10;
void setup(){}
void loop()
{
randomSeed(millis());
randNumber = random(255);
analogWrite(led,randNumber);
delay(500);
}
(13)Serial.begin(rate)
打开串口,设置串口通信的波特率。Arduino和计算机通信的常用波特率为9600。当然,也支持其他速度。如:
void setup()
{
Serial.begin(9600);
}
注意,当使用串口通信的时候,数字端口0(RX)和1(TX)不能再被用作其他功能同时使用。
(14)Serial.println(data)
输出数据到串口,并且可以将输出数据返回,可以使用串口监听器读取输出的数据。
(15)Serial.println(analogValue);
在代码清单2-17中,读取模拟输入端口A10的值并且每隔1s将它发送给上位机。
代码清单2-17:println函数示例
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.println(analogRead(0));
delay(1000);
}