语言参考-Variables
Arduino编程语言可以分为三个主要部分:函数、值(变量和常量)以及结构。
变量
常量
Floating Point Constants
Description
类似于整数常量,浮点数常量用于使代码更具可读性。浮点数常量在编译时会被替换为表达式计算的值。
Example Code
float n = 0.005; // 0.005 是一个浮点数常量
Notes and Warnings
浮点数常量也可以用多种科学记数法表示。'E' 和 'e' 都被接受为有效的指数标志。
浮点数常量 | 计算结果: | 也计算结果为: |
---|---|---|
10.0 | 10 | |
2.34E5 | 2.34 * 10^5 | 234000 |
67e-12 | 67.0 * 10^-12 | 0.000000000067 |
See also
HIGH | LOW
定义引脚电平:高电平与低电平
在对数字引脚进行读取或写入时,引脚只能取/被设置为两个可能的值:HIGH
(高电平)和LOW
(低电平)。这些与true
(真)和false
(假)以及1
和0
是相同的。
高电平
HIGH
(高电平)的含义(相对于引脚而言)根据引脚是被设置为INPUT
(输入)还是OUTPUT
(输出)有所不同。当引脚被配置为使用pinMode()
的INPUT
(输入),并且使用digitalRead()
读取时,如果满足以下条件,Arduino(ATmega)将报告HIGH
(高电平):
- 引脚处的电压高于3.0V(5V板)
- 引脚处的电压高于2.0V(3.3V板)
引脚也可以使用pinMode()
配置为输入,随后通过digitalWrite()
设置为高电平。这将启用内部20K上拉电阻,除非外部电路将其拉低,否则将拉高输入引脚至高电平读数。通过向pinMode()
函数传递INPUT_PULLUP
参数,也可以实现这一点,这在下面的“定义数字引脚模式:INPUT、INPUT_PULLUP 和 OUTPUT”部分中有更详细的解释。
当引脚被配置为输出,并且使用digitalWrite()
设置为HIGH
(高电平)时,引脚处于:
- 5伏特(5V板)
- 3.3伏特(3.3V板)
在此状态下,它可以输出电流,例如点亮一个通过串联电阻连接到地的LED灯。
低电平
低电平的含义也根据引脚是被设置为输入还是输出而有所不同。当引脚被配置为输入,并且使用digitalRead()读取时,如果满足以下条件,Arduino(ATmega)将报告LOW(低电平):
- 引脚处的电压低于1.5V(5V板)
- 引脚处的电压低于1.0V(大约)(3.3V板)
当引脚被配置为输出,并且使用digitalWrite()设置为LOW(低电平)时,引脚处于0伏特(5V板和3.3V板均是如此)。在此状态下,它可以吸收电流,例如点亮一个通过串联电阻连接到+5伏特(或+3.3伏特)的LED灯。
See also
INPUT | INPUT_PULLUP | OUTPUT
定义数字引脚模式:输入(INPUT)、带上拉(INPUT_PULLUP)和输出(OUTPUT)
数字引脚可以作为INPUT
(输入)、INPUT_PULLUP
(带上拉输入)或OUTPUT
(输出)使用。通过pinMode()
更改引脚会改变引脚的电气行为。
INPUT(输入)
使用pinMode()
配置为INPUT
(输入)的Arduino(ATmega)引脚被认为处于高阻抗状态。配置为INPUT
(输入)的引脚对它们正在采样的电路的要求极低,相当于在引脚前串联一个100兆欧姆的电阻。这使它们适用于读取传感器。
如果你将引脚配置为INPUT
(输入),并且正在读取一个开关,当开关处于打开状态时,输入引脚将会“悬浮”,导致不可预测的结果。为了确保在开关打开时能够得到正确的读数,必须使用上拉或下拉电阻。这个电阻的目的是在开关打开时将引脚拉到已知状态。通常选择10K欧姆的电阻,因为它的值足够低,可以可靠地防止输入悬浮,同时又足够高,不会在开关关闭时吸引太多电流。有关更多信息,请参阅数字读取串行教程。
如果使用下拉电阻,当开关打开时输入引脚将为LOW
(低电平),当开关关闭时为HIGH
(高电平)。
如果使用上拉电阻,当开关打开时输入引脚将为HIGH
(高电平),当开关关闭时为LOW
(低电平)。
INPUT_PULLUP(带上拉输入)
Arduino上的ATmega微控制器内置了上拉电阻(内部连接到电源的电阻),你可以访问这些电阻。如果你更愿意使用这些而不是外部上拉电阻,你可以在pinMode()
中使用INPUT_PULLUP
(带上拉输入)参数。
有关这方面的使用示例,请参阅输入上拉串行教程。
配置为INPUT
(输入)或INPUT_PULLUP
(带上拉输入)的引脚如果连接到低于地线(负电压)或高于正电源轨(5V或3V)的电压,可能会受损或被摧毁。
OUTPUT(输出)
使用pinMode()
配置为OUTPUT
(输出)的引脚被认为处于低阻抗状态。这意味着它们可以向其他电路提供大量的电流。ATmega引脚可以向其他设备/电路提供(source)或吸收(sink)高达40毫安(mA)的电流。这使它们适用于供电LED,因为LED通常使用的电流少于40mA。大于40mA的负载(例如电机)将需要晶体管或其他接口电路。
如果将配置为输出的引脚连接到地线或正电源轨,可能会受损或被摧毁。
See also
Integer Constants
描述
整型常量是直接在草图中使用的数字,比如123。默认情况下,这些数字被当作 int 类型,但是你可以通过U和L修饰符来改变这一点(见下文)。
通常,整型常量被当作基数为10(十进制)的整数,但是可以使用特殊的符号(格式化器)输入其他进制的数字。
进制表格
进制 | 示例 | 格式化器 | 注释 |
---|---|---|---|
10(十进制) | 123 | 无 | |
2(二进制) | 0b1111011 | 前导 "0b" | 有效字符为0&1 |
8(八进制) | 0173 | 前导 "0" | 有效字符为0-7 |
16(十六进制) | 0x7B | 前导 "0x" | 有效字符为0-9, A-F, a-f |
十进制(基数10)
这是你熟悉的常识性数学。没有其他前缀的常量被假定为十进制格式。
示例代码:
n = 101; // 等同于十进制的101 ((1 * 10^2) + (0 * 10^1) + 1)
二进制(基数2)
只有字符0和1是有效的。
示例代码:
n = 0b101; // 等同于十进制的5 ((1 * 2^2) + (0 * 2^1) + 1)
八进制(基数8)
只有字符0到7是有效的。八进制数值通过前缀"0"(零)来表示。
示例代码:
n = 0101; // 等同于十进制的65 ((1 * 8^2) + (0 * 8^1) + 1)
通过(无意中)在常量前加上一个前导零,并且让编译器无意中将你的常量解释为八进制,有可能产生一个难以发现的错误。
十六进制(基数16)
有效字符是0到9以及字母A到F;A的值是10,B是11,直到F,即15。十六进制值通过前缀"0x"来表示。注意A-F可以是大写(A-F)或小写(a-f)。
示例代码:
n = 0x101; // 等同于十进制的257 ((1 * 16^2) + (0 * 16^1) + 1)
注释和警告
U和L格式化器:
默认情况下,一个整型常量被当作一个int,伴随着值的限制。要指定一个具有另一数据类型的整型常量,其后面可以跟随:
- 一个'u'或'U'来强制常量进入无符号数据格式。例如:33u
- 一个'l'或'L'来强制常量进入长整型数据格式。例如:100000L
- 一个'ul'或'UL'来强制常量进入无符号长整型常量。例如:32767ul
See also
LED_BUILTIN
定义内置变量:LED_BUILTIN
大多数Arduino板都有一个引脚通过电阻与板载LED相连。常量LED_BUILTIN
是与板载LED相连的引脚编号。大部分板的这个LED连接到数字引脚13。
See also
true | false
在Arduino语言中用来表示真和假的有两个常量:true
和false
。
true
true
常常被定义为1,这是正确的,但true的定义更广泛。任何非零的整数在布尔意义上都是真的。因此,-1、2和-200在布尔意义上也都被定义为真。
请注意,与HIGH
、LOW
、INPUT
和OUTPUT
不同,true
和false
常量是用小写字母输入的。
false
false
是这两个中更容易定义的。false被定义为0(零)。
请注意,与HIGH
、LOW
、INPUT
和OUTPUT
不同,true
和false
常量是用小写字母输入的。
See also
转换
数据类型
array
Description
数组是一系列变量的集合,可以通过索引号来访问。在 C++ 编程语言中编写的 Arduino 程序中的数组可能会很复杂,但使用简单数组相对直接。
创建(声明)数组
以下所有方法都是创建(声明)数组的有效方式。
// 声明一个给定长度的数组,但不初始化值:
int myInts[6];
// 声明一个数组而不显式选择大小(编译器
// 计算元素数量并创建适当大小的数组):
int myPins[] = {2, 4, 8, 3, 6, 4};
// 声明一个给定长度的数组并初始化其值:
int mySensVals[5] = {2, 4, -8, 3, 2};
// 声明 char 类型的数组时,你需要使其长度
// 多一个元素以容纳所需的空终止字符:
char message[6] = "hello";
访问数组
数组是从零开始索引的,也就是说,参考上面的数组初始化,数组的第一个元素位于索引 0,因此
mySensVals[0] == 2, mySensVals[1] == 4,
等等。
这也意味着在一个有十个元素的数组中,索引九是最后一个元素。因此:
int myArray[10]={9, 3, 2, 4, 3, 2, 7, 8, 9, 11};
// myArray[9] 包含 11
// myArray[10] 是无效的并包含随机信息(其他内存地址)
因此,在访问数组时应该小心。访问数组的末尾之后(使用一个大于你声明的数组大小减 1 的索引号)是在读取用于其他目的的内存。从这些位置读取可能不会做太多事情,除了产生无效数据。随机写入内存位置绝对是个坏主意,通常会导致不愉快的结果,如崩溃或程序故障。这也可能是一个难以追踪的错误。
与 BASIC 或 JAVA 不同,C++ 编译器不会检查数组访问是否在你声明的数组大小的合法范围内。
给数组赋值:
mySensVals[0] = 10;
从数组中检索值:
x = mySensVals[4];
数组和 FOR 循环
数组通常在 for 循环内操作,其中循环计数器用作每个数组元素的索引。例如,要通过串行端口打印数组的元素,你可以这样做:
for (byte i = 0; i < 5; i = i + 1) {
Serial.println(myPins[i]);
}
Example Code
有关使用数组的完整程序示例,请参见(如何使用数组示例) 来自(内置示例)。
See also
- 语言 PROGMEM
bool
Description
bool
可以持有两个值之一,true
或 false
。(每个 bool
变量占用一字节内存。)
Syntax
bool var = val;
Parameters
var
:变量名。val
:要赋给该变量的值。
Example Code
此代码展示了如何使用 bool
数据类型。
int LEDpin = 5; // LED 在引脚 5
int switchPin = 13; // 瞬时开关在 13,另一端连接到地线
bool running = false;
void setup() {
pinMode(LEDpin, OUTPUT);
pinMode(switchPin, INPUT);
digitalWrite(switchPin, HIGH); // 打开上拉电阻
}
void loop() {
if (digitalRead(switchPin) == LOW) {
// 开关被按下 - 上拉电阻通常保持引脚高电平
delay(100); // 延迟以消抖开关
running = !running; // 切换 running 变量
digitalWrite(LEDpin, running); // 通过 LED 指示
}
}
See also
boolean
string
Description
文本字符串可以用两种方式表示。你可以使用 String 数据类型,或者你可以用一个以 null 结尾的 char 类型数组构建一个字符串。本页描述了后一种方法。有关 String 对象的更多详细信息,它提供了更多功能,但代价是更多内存,请参阅 String 对象页面。
Syntax
以下所有声明对于字符串都是有效的。
char Str1[15];
char Str2[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o'};
char Str3[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o', '\0'};
char Str4[] = "arduino";
char Str5[8] = "arduino";
char Str6[15] = "arduino";
声明字符串的可能性
- 声明一个 char 数组,不进行初始化,如 Str1
- 声明一个 char 数组(多一个 char),编译器会添加所需的空字符,如 Str2
- 显式添加空字符,Str3
- 用双引号中的字符串常量初始化;编译器会将数组大小设置为适合字符串常量加一个结尾空字符,Str4
- 用显式大小和字符串常量初始化数组,Str5
- 初始化数组,为更大的字符串留出额外空间,Str6
Null 结尾
通常,字符串以空字符(ASCII 码 0)结尾。这允许函数(如 Serial.print()
)告知字符串的结尾在哪里。否则,它们将继续读取实际上不属于字符串的内存后续字节。
这意味着你的字符串需要为包含的文本预留一个额外的字符位置。这就是为什么 Str2 和 Str5 需要八个字符,即使 "arduino" 只有七个 - 最后一个位置会自动填充空字符。Str4 将自动调整大小为八个字符,其中一个用于额外的空字符。在 Str3 中,我们自己显式包含了空字符(写为 '\0')。
请注意,可能会有一个没有最终空字符的字符串(例如,如果你将 Str2 的长度指定为七而不是八)。这将破坏大多数使用字符串的函数,所以你不应该有意这样做。但是,如果你注意到某些行为很奇怪(对不属于字符串的字符进行操作),这可能就是问题所在。
单引号还是双引号?
字符串总是用双引号("Abc")定义,字符总是用单引号('A')定义。
换行长字符串
你可以这样换行长字符串:
char myString[] = "这是第一行"
" 这是第二行"
" 等等";
字符串数组
在处理大量文本时,例如带有 LCD 显示屏的项目,设置一个字符串数组通常是很方便的。由于字符串本身就是数组,这实际上是一个二维数组的示例。
在下面的代码中,数据类型 char
之后的星号 "char*" 表示这是一个 "指针" 数组。所有数组名称实际上都是指针,因此这是创建数组数组所需的。指针是 C++ 中初学者较难理解的一个晦涩部分,但在这里使用它们而无需详细了解指针的工作原理。
Example Code
char *myStrings[] = {"这是字符串 1", "这是字符串 2", "这是字符串 3",
"这是字符串 4", "这是字符串 5", "这是字符串 6"
};
void setup() {
Serial.begin(9600);
}
void loop() {
for (int i = 0; i < 6; i++) {
Serial.println(myStrings[i]);
delay(500);
}
}
See also
- LANGUAGE PROGMEM
String()
Description
构造 String 类的实例。有多个版本可以从不同的数据类型构造 String (即将它们格式化为字符序列),包括:
- 用双引号括起来的常量字符串(即字符数组)
- 用单引号括起来的单个常量字符
- 另一个 String 对象实例
- 一个常量整数或长整型数
- 使用指定进制的常量整数或长整型数
- 一个整数或长整型变量
- 使用指定进制的整数或长整型变量
- 使用指定小数位数的浮点数或双精度浮点数
从数字构造 String 会得到一个包含该数字 ASCII 表示的字符串。默认使用十进制,所以
String thisString = String(13);
会得到字符串 "13"。不过你也可以使用其他进制。例如,
String thisString = String(13, HEX);
会得到字符串 "d",这是十进制值 13 的十六进制表示。或者如果你更喜欢二进制,
String thisString = String(13, BIN);
会得到字符串 "1101",这是 13 的二进制表示。
Syntax
String(val)
String(val, base)
String(val, decimalPlaces)
Parameters
val
: 要格式化为 String 的变量。允许的数据类型: string、char、byte、int、long、unsigned int、unsigned long、float、double。base
: (可选) 用于格式化整数值的进制。decimalPlaces
: 仅当 val 为 float 或 double 时。所需的小数位数。
Returns
String 类的一个实例。
Example Code
以下都是 String 的有效声明。
String stringOne = "Hello String"; // 使用常量字符串
String stringOne = String('a'); // 将常量字符转换为 String
String stringTwo = String("This is a string"); // 将常量字符串转换为 String 对象
String stringOne = String(stringTwo + " with more"); // 连接两个字符串
String stringOne = String(13); // 使用常量整数
String stringOne = String(analogRead(0), DEC); // 使用整数和进制
String stringOne = String(45, HEX); // 使用整数和进制(十六进制)
String stringOne = String(255, BIN); // 使用整数和进制(二进制)
String stringOne = String(millis(), DEC); // 使用长整型和进制
String stringOne = String(5.698, 3); // 使用浮点数和小数位数
Functions
请参阅《Arduino Language Reference-Variables-String()-Functions》
Operators
请参阅《Arduino Language Reference-Variables-String()-Operators》
See also
变量作用域与限定符
实用
PROGMEM
Description
将常量数据只保留在闪存(程序)内存中,而不是在程序开始时复制到SRAM中。有关Arduino板上可用的各种内存类型的描述。
PROGMEM
关键词是一个变量修饰符,它只应与pgmspace.h中定义的数据类型一起使用。它告诉编译器“将这些信息仅保留在闪存内存中”,而不是在启动时复制到SRAM中,就像它通常会做的那样。
PROGMEM是pgmspace.h库的一部分。它会自动包含在Arduino IDE中。
虽然PROGMEM
可以用于单个变量,但真正值得这么做的是当你有一大块需要存储的数据时,通常最容易在数组中存储,(或其他超出我们当前讨论范围的C++数据结构)。
使用PROGMEM
是一个两步程序。一旦变量被定义为PROGMEM
,就不能像常规的基于SRAM的变量那样读取:你必须使用在pgmspace.h中也定义的特定函数来读取它。
==== 重要提示!
PROGMEM
仅在使用AVR板(Uno Rev3, Leonardo等)时有用。较新的板(Due, MKR WiFi 1010, GIGA R1 WiFi等)在变量声明为const
时自动使用程序空间。然而,为了向后兼容,PROGMEM
仍可以与新板一起使用。这个实现当前存在这里。
Syntax
const dataType variableName[] PROGMEM = {data0, data1, data3…};
注意,因为PROGMEM是一个变量修饰符,关于它应该放在哪里没有硬性规定,所以Arduino编译器接受下面所有的定义,这些定义也是同义的。然而,实验表明,在不同版本的Arduino(与GCC版本有关)中,PROGMEM可能在一个位置工作而在另一个位置不工作。“字符串表”示例已经经过测试,可与Arduino 13一起使用。如果PROGMEM包含在变量名之后,较早版本的IDE可能会更好。
const dataType variableName[] PROGMEM = {}; // 使用这种形式
const PROGMEM dataType variableName[] = {}; // 或这种
const dataType PROGMEM variableName[] = {}; // 不用这种
Parameters
dataType
: 允许的数据类型:任何变量类型。variableName
: 数据数组的名称。
Example Code
以下代码片段说明了如何读写保存在PROGMEM中的无符号字符(字节)和整数(2字节)。
// 保存一些无符号整数
const PROGMEM uint16_t charSet[] = { 65000, 32796, 16843, 10, 11234};
// 保存一些字符
const char signMessage[] PROGMEM = {"I AM PREDATOR, UNSEEN COMBATANT. CREATED BY THE UNITED STATES DEPART"};
unsigned int displayInt;
char myChar;
void setup() {
Serial.begin(9600);
while (!Serial); // 等待串口连接。对于原生USB来说是必需的
// 将你的设置代码放在这里,以便一次性运行:
// 读回一个2字节的整型
for (byte k = 0; k < 5; k++) {
displayInt = pgm_read_word_near(charSet + k);
Serial.println(displayInt);
}
Serial.println();
// 读回一个字符
int signMessageLength = strlen_P(signMessage);
for (byte k = 0; k < signMessageLength; k++) {
myChar = pgm_read_byte_near(signMessage + k);
Serial.print(myChar);
}
Serial.println();
}
void loop() {
// 将你的主代码置于此,以便重复运行:
}
字符串数组
在处理大量文本时,例如涉及LCD的项目,设置字符串数组会很方便。因为字符串本身就是数组,这实际上是一个二维数组的例子。
这些通常是大型结构,所以将它们放入程序内存中通常是可取的。下面的代码说明了这个想法。
/*
PROGMEM字符串演示
如何在程序内存(闪存)中存储字符串表,
并检索它们。
信息汇总自:
http://www.nongnu.org/avr-libc/user-manual/pgmspace.html
在程序内存中设置字符串表(数组)有点复杂,但
这里有一个好模板可以遵循。
设置字符串是一个两步过程。首先,定义字符串。
*/
#include <avr/pgmspace.h>
const char string_0[] PROGMEM = "String 0"; // "String 0"等是要存储的字符串 - 根据需要更改。
const char string_1[] PROGMEM = "String 1";
const char string_2[] PROGMEM = "String 2";
const char string_3[] PROGMEM = "String 3";
const char string_4[] PROGMEM = "String 4";
const char string_5[] PROGMEM = "String 5";
// 然后设置一个表来引用你的字符串。
const char *const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};
char buffer[30]; // 确保这足够大,可以容纳它必须持有的最大字符串
void setup() {
Serial.begin(9600);
while (!Serial); // 等待串口连接。对于原生USB来说是必需的
Serial.println("OK");
}
void loop() {
/* 使用程序内存中的字符串表需要使用特殊函数来检索数据。
strcpy_P函数将程序空间的字符串复制到RAM中的字符串("buffer")。
确保你的RAM中接收字符串足够大,以容纳
你从程序空间检索的任何内容。 */
for (int i = 0; i < 6; i++) {
strcpy_P(buffer, (char *)pgm_read_ptr(&(string_table[i]))); // 需要的强制转换和解引用,只需复制。
Serial.println(buffer);
delay(500);
}
}
Notes and Warnings
请注意,变量必须是全局定义的,或者使用static关键词定义,才能与PROGMEM一起工作。
以下代码在函数内部不会工作:
const char long_str[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n";
即便是在函数内局部定义,以下代码也会工作:
const static char long_str[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n"
F()
宏
当使用像:
Serial.print("Write something on the Serial Monitor");
这样的指令时,要打印的字符串通常保存在RAM中。如果你的草稿在串行监视器上打印了很多东西,你可以很容易地填满RAM。这可以通过不在启动时从FLASH内存空间加载字符串来避免。你可以很容易地表明字符串不被复制到RAM中,使用以下语法:
Serial.print(F("Write something on the Serial Monitor that is stored in FLASH"));
See also
- 示例 Arduino板上可用的内存类型
- 定义 array
- 定义 string