analogWrite()

可以认为 analogWrite() 是把 0-255 映射到 0-5V. 最简单直观的实验就是用它来调节 LED 的亮度。

linear
Figure 1. analogWrite() 和输出等效电压的线性对应关系

RGB LED

RGB-LED_schem.svg
Figure 2. 原理图
/*
  RGB fading
  Modified from the following example.
  http://arduino.cc/en/Tutorial/Fading
 */

int rPin = 11; // red
int gPin = 9;  // green
int bPin = 10; // blue

void setup() {
  //Serial.begin(9600);
}

void loop() {

  fading(rPin); // red
  fading(gPin); // green
  fading(bPin); // blue
}

void fading(int ledPin)
{
  // fade in from min to max in increments of 5 points:
  for (int fadeValue = 0; fadeValue <= 255; fadeValue += 5) {
    analogWrite(ledPin, fadeValue); // sets the value (range from 0 to 255):
    delay(10);                      // wait for 10 milliseconds to see the dimming effect
  }

  // fade out from max to min in increments of 5 points:
  for (int fadeValue = 255; fadeValue >= 0; fadeValue -= 5) {
    analogWrite(ledPin, fadeValue); // sets the value (range from 0 to 255):
    delay(10);                      // wait for 10 milliseconds to see the dimming effect
  }
}

上面的代码简单地对 lab kit 中的 RGB LED 进行测试。

红绿蓝每种颜色的变化范围有 256 种可能性(0 到 255),256x256x256 = 16777216, 因此你可以用 RGB LED 混合出 1600 万种颜色。

Tip
结合 Lab04 中的按键方法,你可以通过按键调节 analogWrite(ledPin, val); 中的 val 值,进行手动控制。

呼吸灯

// 呼吸灯
// 在 Uno 上 pin 3, 5, 6, 9, 10, 11 支持 analogWrite()
// 也就是带 ~ 的 pin
#define LED_PIN 3

// 存放 LED 亮度值的查找表(lookup table)
uint8_t sine_table[] = {
  1,   1,   2,   3,   5,   8,  11,  15,  20,  25,
 30,  36,  43,  49,  56,  64,  72,  80,  88,  97,
105, 114, 123, 132, 141, 150, 158, 167, 175, 183,
191, 199, 206, 212, 219, 225, 230, 235, 240, 244,
247, 250, 252, 253, 254, 255, 254, 253, 252, 250,
247, 244, 240, 235, 230, 225, 219, 212, 206, 199,
191, 183, 175, 167, 158, 150, 141, 132, 123, 114,
105,  97,  88,  80,  72,  64,  56,  49,  43,  36,
 30,  25,  20,  15,  11,   8,   5,   3,   2,   1, 0};

void setup() {
  //pinMode(LED_PIN, OUTPUT); // 用 analogWrite() 时无需把 pin 设置成输出
}

void loop() {
  for (int i = 0; i < 91; i++) { // <1>
    analogWrite(LED_PIN, sine_table[i]);
    delay(50);
  }
}

在程序中标示 <1> 处,用 for 循环去遍历查找表中的 91 个数据,这并不是一个好的做法。因为一旦表格中的数据个数发生了变化,或变多变少之后,不好数清楚。像 91 这样的数字在编程中被称为魔术数字(Magic number), 以后你自己或别人看你代码时,都不知道这儿为什么是这个数字而不是另一个,天知道它是从哪儿来的。

我们可以用 C 语言的 sizeof 运算符来帮助我们偷懒,避免出现魔术数字。见下面的版本。

// 呼吸灯
// 在 Uno 上 pin 3, 5, 6, 9, 10, 11 支持 analogWrite()
// 也就是带 ~ 的 pin
#define LED_PIN 3

// 存放 LED 亮度值的查找表(lookup table)
uint8_t sine_table[] = {
  1,   1,   2,   3,   5,   8,  11,  15,  20,  25,
 30,  36,  43,  49,  56,  64,  72,  80,  88,  97,
105, 114, 123, 132, 141, 150, 158, 167, 175, 183,
191, 199, 206, 212, 219, 225, 230, 235, 240, 244,
247, 250, 252, 253, 254, 255, 254, 253, 252, 250,
247, 244, 240, 235, 230, 225, 219, 212, 206, 199,
191, 183, 175, 167, 158, 150, 141, 132, 123, 114,
105,  97,  88,  80,  72,  64,  56,  49,  43,  36,
 30,  25,  20,  15,  11,   8,   5,   3,   2,   1, 0};

// 计算查找表中的元素数量
int numData = (sizeof sine_table) / (sizeof sine_table[0]); // 在这里 numData = 91 <1>

void setup() {
  Serial.begin(9600);
  Serial.println(numData); // 打印出数字帮助我们确认
}

void loop() {
  for (int i = 0; i < numData; i++) {
    analogWrite(LED_PIN, sine_table[i]);
    delay(50);
  }
}
  1. sizeof sine_table 计算出数组 sine_table[] 一共占用了多少个 bytes, sizeof sine_table[0] 计算出数组中每个元素占用了多少个 bytes. 两者相除,就得到了数组中的元素数量。Magic number 消失了!

我们再来看下面的代码片断:

int sine_table[] = {1, 2, 3, 4};

int numData = (sizeof sine_table) / (sizeof sine_table[0]); // numData = 4

因为 C 语言并没有规定一个 int 类型占用几个字节。例如一台计算机上 int 用 4 bytes, sizeof sine_table 就得到 16, sizeof sine_table[0] 得到 4, 16/4 = 4, 通过采用除法(/),这段代码在任何计算机上得到的结果 numData 都为 4, 提高了程序的可移植性。

Note

sizeof 是 C 语言的一个运算符(+, -, *, /… 都是运算符), 可以用它"测量"一个变量或一种数据类型占用的字节数。

int a, b;

b = sizeof(a);  // 在 Arduino 上,结果是 b = 2, 因为一个 int 占用两个 byte
//b = sizeof a; // 上面的语句也可以写成这样,当 sizeof 作用于变量名时,() 可以省略
int b;

// 计算一个长整型数据类型要占用几个 byte
// 计算数据类型时,sizeof 后面必须加 ()
b = sizeof(unsigned long); // 在 Arduino 上,结果是 b = 4

问题: 为什么用 analogWrite() 写完一个亮度值之后要加延时?改变延时的时间长短,甚至去掉延时,会有什么效果?

analogRead()

读电位器

pot.

读 LM35 温度传感器

LM35 bradboard
Figure 3. LM35 和 Arduino 的连接

因为 LM35 被设计成输出电压和环境温度成线性关系,因此你可以直接用万用表测量中间引脚的电压值得到温度值。例如,测得的电压是 234mV,则温度是 234/10 = 23.4 摄氏度。

LM35 Vout VS temp
Figure 4. 温度和 LM35 输出电压的关系
/*
  用 LM35 进行温度测量

  inspired by
  ladyada https://learn.adafruit.com/tmp36-temperature-sensor
  pscmpf http://pscmpf.blogspot.com/2008/12/arduino-lm35-sensor.html

*/

int reading;        // 存放 ADC 原始数据
float voltage;      // 存放电压值
float temperature;  // 存放温度值
int sensorPin = A0; // LM35 第 2 pin 接到 A0 上

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  // 读 sensorPin 上的电压
  // 会得到一个 0 to 1023 之间的值
  reading = analogRead(sensorPin);

  // 转换成电压值
  voltage = reading * 5.0;     // 5V Arduino
  voltage = voltage / 1024.0;
  //voltage = (reading * 5) / 1024; // 这行代码似乎可以代替上面两行,试一试把上两行换成这一行
                                    // 观察有什么效果

  // 打印电压
  // 随时可以用 print 查看数据的正确性
  Serial.print(voltage); Serial.println(" V");

  // 得到温度值,单位 °C
  temperature = voltage * 100;

  // 把温度值发送到电脑串口
  Serial.print(temperature); Serial.println(" °C");
  delay(1000); // 等一会儿再进行下次测量
}

Arduino 的 analogRead() 是把模拟输入引脚上的电压和 5V 作比较,把比值映射到 0 到 1023 之间。

           Vin
reading = ----- * 1024
           5V

只要对公式进行简单变形就得到:

 reading
--------- * 5V = Vin
  1024

Vin: LM35 输出的电压

这个公式反应在上面的代码中。

我们得到电压的单位是 V, 电压和温度的对应关系是 10mV/°C = 0.01V/°C,因此:

 voltage
---------- = temperature (单位: °C)
 0.01V/°C

再对上面的代码作一些很小的修改。

/*
  LM35 输出 CSV 文件进行数据记录
*/

int reading;        // 存放 ADC 原始数据
float voltage;      // 存放电压值
float temperature;  // 存放温度值
int sensorPin = A0; // LM35 第 2 pin 接到 A0 上
unsigned long time; // 时间戳

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  time = millis(); // 读当前时间
  // 读 sensorPin 上的电压
  // 会得到一个 0 to 1023 之间的值
  reading = analogRead(sensorPin);

  // 转换成电压值
  voltage = reading * 5.0;     // 5V Arduino
  voltage = voltage / 1024.0;

  // 得到温度值,单位 °C
  temperature = voltage * 100;

  //prints time since program started
  Serial.print(time/1000); // in seconds
  Serial.print(", ");      // 打印 CSV (Comma-Separated Values) 格式

  Serial.print(temperature); // 把温度值发送到电脑串口
  delay(1000); // 等一会儿再进行下次测量
}

打开 Arduino IDE 的串口监视器,PC 就可以开始接收 CSV 格式的数据,下面是约 1 分钟的数据。

0, 24.41
1, 24.41
2, 24.41
3, 24.90
4, 24.41
5, 24.41
6, 24.41
7, 24.41
8, 24.41
9, 25.39
10, 26.86
11, 27.83
12, 28.32
13, 28.81
14, 29.30
15, 29.79
16, 29.79
17, 30.27
18, 30.27
19, 30.27
20, 30.76
21, 30.76
22, 30.76
23, 30.76
24, 30.76
25, 30.76
26, 31.25
27, 31.25
28, 31.74
29, 31.74
30, 31.25
31, 31.25
32, 31.25
33, 30.76
34, 30.76
35, 30.76
36, 30.27
37, 29.79
38, 29.79
39, 29.30
40, 29.30
41, 28.81
42, 28.81
43, 28.32
44, 28.32
45, 28.32
46, 28.32
47, 28.32
48, 27.83
49, 27.83
50, 27.83
51, 27.83
52, 27.34
53, 27.34
54, 26.86
55, 26.86
56, 26.86
57, 26.86

再用电子表格或别的软件,你可以把数据可视化。

lm35-templogger.svg
Figure 5. CSV 转成图表

Wow, cool, 现在你有一个简易的温度记录仪(temperature logger)了! 只是它需要用电脑来存数据,如果给 Arduino 加一个 SD 卡,就能脱机存储了。

读光敏电阻

Your turn!

你可以发挥自己的想象力,用 lab kit 中的模拟量传感器设计自己的实验!