Chapter 12. Weather and Climate

The immediately obvious components of weather include temperature and humidity. To forecast weather, you can measure the atmospheric pressure.

Experiment: Is It Hot in Here?

An LM35 sensor reports temperature with varying voltage. Because it’s an analog resistance sensor, it’s easy to use with Arduino. Using it with Raspberry Pi requires an analog-to-digital converter chip.

The LM35 has three leads (Figure 12-1), similar to a potentiometer. The output lead voltage U is converted to temperature T with a simple formula:

T = 100*U

Some example values are in Table 12-1. From the table, you can see that the LM35 can measure temperatures between 2 C and 150 C. The output voltage varies from about 0 V to 1.5 V.

Do you need to measure subzero temperatures? Use LM36 or check for alternative connection options on the LM35 data sheet.

Table 12-1. LM35 reports temperature with voltage
Temperature CVoltage VComment

2 C

0.02 V

Minimum measured temperature

20 C

0.2 V

Room temperature

100 C

1.0 V

Boiling water

150 C

1.5 V

Maximum measured temperature

LM35
Figure 12-1. LM35

LM35 Code and Connection for Arduino

Figure 12-2 shows the wiring diagram for Arduino. Hook it up as shown, and then run the code from Example 12-1.

Example 12-1. temperature_lm35.ino
// temperature_lm35.ino - measure temperature (Celsius) with LM35 and print it
// (c) BotBook.com - Karvinen, Karvinen, Valtokari


int lmPin = A0;

void setup()
{
  Serial.begin(115200);
  pinMode(lmPin, INPUT);
}

float tempC()
{
  float raw = analogRead(lmPin);  // 1
  float percent = raw/1023.0; // 2
  float volts = percent*5.0; // 3
  return 100.0*volts;  // 4
}

void loop()
{
  Serial.println(tempC());
  delay(200); // ms
}
1

LM35 is just an analog resistance sensor, so you can measure its voltage with analogRead(). It returns a raw value between 0 and 1023.

2

Convert the raw value (0 to 1023) to a percentage of the HIGH voltage (5 V). The percent variable will be a float between 0.0 and 1.0.

3

Convert percentage to volts (between 0 and 5 V).

4

Return the temperature in Celsius. The formula we used comes from the LM35’s data sheet.

Arduino/LM35 connections
Figure 12-2. Arduino/LM35 connections

LM35 Code and Connection for Raspberry Pi

Figure 12-3 shows the connection diagram for the Raspberry Pi. Hook it up as shown, and then run the code in Example 12-2.

Raspberry Pi connections for the LM35
Figure 12-3. Raspberry Pi connections for the LM35

Because the maximum output voltage of LM35 is 1.5 V at 150 C, there is no danger of going over Raspberry Pi’s maximum safe input voltage level of 3.3 V.

Example 12-2. temperature_lm35.py
# temperature_lm35.py - print temperature
# (c) BotBook.com - Karvinen, Karvinen, Valtokari
import time
import botbook_mcp3002 as mcp   # 1
#LM35 data pin voltage keeps under 2V so no
#level conversion needed
def main():
        while True:
                rawData = mcp.readAnalog()      # 2
                percent = rawData / 1023.0      # 3
                volts = percent * 3.3   # 4
                temperature = 100.0 * volts     # 5
                print("Current temperature is %.1f C " % temperature)
                time.sleep(0.5)

if __name__ == "__main__":
        main()
1

The botbook_mcp3002.py library file must be in the same directory as this program (temperature_lm35.py). You must also install the spidev library, which is imported by botbook_mcp3002. See the comments at the top of botbook_mcp3002/botbook_mcp3002.py or Installing SpiDev.

2

Read voltage using MCP3002 analog-to-digital converter and botbook_mcp3002 library. The raw value is an integer between 0 and 1023.

3

Convert the raw value to a percentage of 3.3 V.

4

Convert percent to a voltage (between 0 and 3.3 V).

5

Convert to Celsius using the formula we obtained from the component’s data sheet. See examples in Table 12-1.

Environment Experiment: Changing Temperature

Air is a very good insulator. You’ll find that your patience will be tested if you take your LM35 temperature sensor (Figure 12-4) to the sauna, fridge, and balcony—it seems to change so slowly.

Measuring cold temperatures
Figure 12-4. Measuring cold temperatures

To get quick changes, press an ice cube directly against the LM35. Avoid wetting your wires, microcontrollers, or breadboard. Now the heat is conducted away from LM35, and the change of temperature is rapid.

Experiment: Is It Humid in Here?

DHT11 measures humidity and temperature (Figure 12-5).

DHT11 humidity sensor
Figure 12-5. DHT11 humidity sensor

The protocol that the DHT11 uses is weird. It sends bits as very short pulses very rapidly. Arduino doesn’t have an operating system, so it’s more real-time than Raspberry Pi and can easily read these pulses. But even Arduino needs to be coded in a special way, as even the built-in pulseIn() function is not fast enough.

Raspberry Pi is less real-time and has a difficult time reading the pulses reliably. That’s why you’ll connect Arduino to Raspberry Pi and use Arduino to do the actual reading of the DHT11. The connection is made using serial over USB. If you want, you can easily apply this technique to any other sensor.

The data pin is normally HIGH. To start reading, the microcontroller sends a LOW pulse of 18 milliseconds.

The DHT11 sends 5-byte packets. As each byte is 8 bits, the packet is 5 B * 8 bit/B = 40 bits.

How Humid Is Your Breath?

How can we change the measured humidity? Just buy an ultrasonic humidifier that uses a piezoelectric transducer to create mist from water, or book a flight to Thailand. No, wait, there is an easier way. You have a built-in humidifier in your lungs. Bring the sensor close to your mouth and breathe (Figure 12-6). Keep checking to see how values change by watching the Arduino Serial Monitor.

Breath to humidity sensor
Figure 12-6. Breath to humidity sensor

DHT11 Code and Connection for Arduino

Figure 12-7 shows the connections for Arduino. Wire them up as shown, and then run the code shown in Example 12-3.

DHT11 humidity sensor connected to Arduino
Figure 12-7. DHT11 humidity sensor connected to Arduino
Example 12-3. dht11.ino
// dht11.ino - print humidity and temperature using DHT11 sensor
// (c) BotBook.com - Karvinen, Karvinen, Valtokari

const int dataPin = 8;

int temperature = -1;
int humidity = -1;

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

int readDHT11() {
  uint8_t recvBuffer[5];        // 1
  uint8_t cnt = 7;
  uint8_t idx = 0;
  for(int i = 0; i < 5; i++) recvBuffer[i] = 0; // 2

  // Start communications with LOW pulse
  pinMode(dataPin, OUTPUT);
  digitalWrite(dataPin, LOW);
  delay(18);
  digitalWrite(dataPin, HIGH);

  delayMicroseconds(40);        // 3
  pinMode(dataPin, INPUT);
  pulseIn(dataPin, HIGH);   // 4
  // Read data packet
  unsigned int timeout = 10000; // loop iterations
  for (int i=0; i<40; i++)      // 5
  {
          timeout = 10000;
          while(digitalRead(dataPin) == LOW) {
                  if (timeout == 0) return -1;
                  timeout--;
          }

          unsigned long t = micros();   // 6

          timeout = 10000;
          while(digitalRead(dataPin) == HIGH) { // 7
                  if (timeout == 0) return -1;
                  timeout--;
          }

          if ((micros() - t) > 40) recvBuffer[idx] |= (1 << cnt);       // 8
          if (cnt == 0)   // next byte?
          {
                  cnt = 7;    // restart at MSB
                  idx++;      // next byte!
          }
          else cnt--;
  }

  humidity = recvBuffer[0];     // %    // 9
  temperature = recvBuffer[2];  // C
  uint8_t sum = recvBuffer[0] + recvBuffer[2];
  if(recvBuffer[4] != sum) return -2;   // 10
  return 0;
}

void loop() {
  int ret = readDHT11();
  if(ret != 0) Serial.println(ret);
  Serial.print("Humidity: "); Serial.print(humidity); Serial.println(" %");
  Serial.print("Temperature: "); Serial.print(temperature); Serial.println(" C");
  delay(2000); // ms
}
1

The packet from DHT11 is 5 bytes (40 bits) in length.

2

Initialize the buffer with zeroes.

3

Wait a very short time for the DHT11 to start sending data.

4

Receive the DHT11’s response signal.

5

For each of the 40 bits, perform the operations inside the curly braces.

6

Uptime in microseconds (µs); this is stored to measure the duration of the pulse.

7

Measure the pulse with a loop.

8

If the pulse was longer than 40 µs, it was a 1. Otherwise, that bit is left as its initialized value, 0. To toggle the corresponding bit in the corresponding byte, a new number is created with bit shifting (e.g., 1<<2 == 0b100). This bit is then combined with the previous byte value using a bitwise inplace OR. So if the value was already a 1, it stays set at 1. If it was a zero, but the new number (0b100) was 1, then this sets it to 1. See Bitwise Operations for more details.

9

Humidity is the first byte of the received packet.

10

The checksum (byte 4) is the sum of humidity and temperature.

DHT11 Code and Connection for Raspberry Pi

Would you like to combine Arduino and Raspberry Pi to get the best of both worlds? We hope so, because DHT11 doesn’t like the Raspberry Pi. This code shows you how you can read data from Arduino, in a way you can easily adapt to other projects.

Even if it’s possible to use DHT11 from Raspberry Pi with complicated code, the result is not satisfactory. Luckily, Arduino can be used for fast low-level tasks, letting Raspberry Pi concentrate on the big picture.

After you have tried the code, you should read Talking to Arduino from Raspberry Pi.

To use the serial port in Raspberry Pi, you must first release it from its default use as a login terminal. See Enabling the Serial Port in Raspberry Pi for instructions. Wire them up as shown in Figure 12-8, and then run the code shown in Example 12-4.

Arduino connected to Raspberry Pi
Figure 12-8. Arduino connected to Raspberry Pi
Example 12-4. dht11_serial.py
# dht11_serial.py - print humidity and temperature using DHT11 sensor
# (c) BotBook.com - Karvinen, Karvinen, Valtokari

import time
import serial   # 1

def main():
        port = serial.Serial("/dev/ttyACM0", baudrate=115200, timeout=None)     # 2
        while True:
                line = port.readline()  # 3
                arr = line.split()      # 4
                if len(arr) < 3:        # 5
                        continue        # 6
                dataType = arr[2]
                data = float(arr[1])    # 7
                if dataType == '%':
                        print("Humidity: %.1f %%" % data)
                else:
                        print("Temperature: %.1f C" % data)


                time.sleep(0.01)

if __name__ == "__main__":
        main()
1

You might need to run these commands to install this library: sudo apt-get update && sudo apt-get -y install python-serial.

2

Open the USB serial for reading. The Arduino program must use the same speed, 115,200 bit/s to communicate.

3

Read one line, until reaching a newline. The readline() function is a blocking function, so it will wait here until it reads something in.

4

Split the text string into a list. This makes it easy to parse the human-readable input from the Arduino. For example, "Humidity: 25 %".split() creates a convenient list: ['Humidity:', '25', '%'].

5

If the line length is incorrect…

6

…ignore this line and jump to the next iteration of the while loop.

7

Typecast (convert) the string (“25”) to a number (25.0).

Talking to Arduino from Raspberry Pi

Arduino is real-time, robust, and simple. Also, Arduino has a built-in ADC (analog-to-digital converter). Raspberry Pi is high level, and runs a whole Linux operating system. Why not combine the benefits?

You can use Arduino to directly interact with sensors and outputs, and control Arduino from Raspberry Pi.

First, make the sensors work with Arduino, and print the readings to serial. Use your normal desktop computer and Arduino IDE for this. You can write to serial with Serial.println(). Use the Serial Monitor (Tools→Serial Monitor) to see what your program prints. Only proceed to the Raspberry Pi part once you are happy with how the Arduino part of your project works.

The connection between Arduino and Raspberry Pi is simple: just connect them with a USB cable.

For a more intimate combination of the Arduino platform and Raspberry Pi, check out the AlaMode for Raspberry Pi, which incorporates an Arduino-compatible microcontroller board into a Raspberry Pi expansion that snaps right on top of your Pi. There’s no need to connect them by USB, since they use the Raspberry Pi’s expansion header.

Arduino communicates with serial over USB. To read serial from Raspberry Pi, use Python and PySerial. For a complete example of using PySerial, see Figure 12-8 or Chapter 7 of Make: Arduino Bots and Gadgets.

You’ll have Arduino print normal text, then extract the numbers with Python’s string handling. In many prototyping applications, you don’t have to design a new low-level protocol.

For example, consider an Arduino program printing the following:

Humidity: 83 %

If you have read this into a Python string, use split() to separate the words into a list:

>>> s="Humidity: 83 %"
>>> s.split()
['Humidity:', '83', '%']

You can refer to items of the list with square brackets, []. The first item is number zero. This prints the second item:

>>> x = s.split()[1]
>>> x
'83'

Finally, convert the string “83” into integer 83 so you can process it as a number. You can perform sensible mathematical operations and comparisons with a number, but not with a string.

>>> int(x)
83

You can use any Arduino-compatible sensor from Raspberry Pi this way.

Atmospheric Pressure GY65

Atmospheric pressure can help you forecast weather. High pressure means sunny, clear weather. Low pressure means rainy, snowy, or cloudy weather.

The GY65 barometric pressure sensor
Figure 12-9. The GY65 barometric pressure sensor

Normal air pressure is about 1,000 hPa (hectopascals). The exact value depends on your altitude.

1,000 hPa = 100,000 Pa = 1,000 mbar = 1 bar

The pressure doesn’t vary much, even in extreme weather. The world records are a high of 1,084 hPa and a low of 870 hPa (inside a typhoon). Typical changes in normal weather are a few hectopascals.

Pressure changes in the lower atmosphere are tiny compared with other everyday pressure changes. A hand-pumped bike tire has pressure of 4,000 hPa. Outside a passenger airplane over 30,000 feet, the pressure is less than 300 hPa.

So what level of atmospheric pressure is considered high, then? Meteorologists say that high pressure is just relatively high, compared with the pressure of surrounding areas.

You can still predict weather with pressure over time. If pressure goes up, better weather is coming. The faster it changes, the sunnier it will be.

For a simpler forecast, you can compare current pressure to expected pressure for your altitude. You can find out your altitude with a GPS or Google Maps. According to SparkFun, the distributor of the GY65 module we used, a difference of +2.5 hPa means great, sunny weather. Conversely, low pressure of 2.5 hPa under the normal pressure means bad, rainy weather.

GY65 is a breakout board for the BMP085 sensor. You can find the data sheet by searching for “BMP085 digital pressure sensor data sheet.”

GY65 Code and Connection for Arduino

Figure 12-10 shows the connection diagram for Arduino. Hook it up as shown, and then run the code shown in Example 12-5.

GY65 atmospheric pressure sensor connected to Arduino
Figure 12-10. GY65 atmospheric pressure sensor connected to Arduino

Arduino code uses the gy_65 library from http://botbook.com. If you want an in-depth, technical view how the I2C/SMBus protocol works, have a look at GY65 Code and Connection for Raspberry Pi.

Example 12-5. gy_65.ino
// gy_65.ino - print altitude, pressure and temperature with GY-65 BMP085
// (c) BotBook.com - Karvinen, Karvinen, Valtokari

#include <Wire.h>
#include <gy_65.h>      // 1

void setup() {
  Serial.begin(115200);
  readCalibrationData(); // 2
}

void loop() {
  float temp = readTemperature();
  float pressure = readPressure();      // 3
  float altitude = calculateAltitude(pressure); // 4

  Serial.print("Altitude: ");
  Serial.print(altitude,2);
  Serial.println(" m");
  Serial.print("Pressure: ");
  Serial.print(pressure,2);
  Serial.println(" Pa");
  Serial.print("Temperature: ");
  Serial.print(temp,2);
  Serial.println("C");
  delay(1000);
}
1

The gy_65 library makes this sensor very easy to use. It hides the complicated I2C protocol that’s behind the sensor. The library folder gy_65 must be in the same directory as the main program, gy_65.ino.

2

Update global variables.

3

With the library, it’s trivial to retrieve the pressure in Pascals.

4

Air is thinner when you are higher, so pressure can also be used for altitude estimation.

Using Arduino Libraries

If you have a lot of code, it’s best to split it into multiple files. The Arduino code for GY65 is such code. Also, you’ll use the same code again in Test Project: E-paper Weather Forecast.

The library is in a folder, within the same directory as the main program (you will probably also encounter libraries that need to be installed into your Arduino sketch folder’s libraries subdirectory). Here is the folder structure:

gy_65.ino         # the main program
gy_65/            # folder that contains the library
gy_65/gy_65.cpp   # code for the library
gy_65/gy_65.h     # prototypes of each library function

The location of the main program, gy_65.ino, should seem normal to you. Every Arduino project has one main program.

The library is in its own folder. The code for the library is in gy_65.cpp. This code looks similar to other Arduino code you have seen.

The header file, gy_65.h, contains the prototypes of the functions in the cpp file. Prototypes are just copies of the first line of each function in gy_65.cpp. For example, the header file has the line:

float readTemperature();

GY65 Arduino Library Explained

You can use the GY65 without going through the implementation details of the library. But if you want a detailed understanding of how communication with the GY65 works, read on.

You can learn the communication protocol by studying the library itself, the Raspberry Pi program (Figure 12-11), or both.

This section also introduces you to creating your own C++ libraries for Arduino. You should also see Arduino’s documentation on Writing a Library for Arduino.

The header file gy_65.h (see Example 12-6) has prototypes for each function. It’s simply a list of functions in the cpp file, which is where you’ll find the actual implementation of those functions.

Example 12-6. gy_65.h
// gy_65.h - library for altitude, pressure and temperature with GY-65 BMP085
// (c) BotBook.com - Karvinen, Karvinen, Valtokari

void readCalibrationData();
float readTemperature();
float readPressure();
float calculateAltitude(float pressure);

The implementation of the definitions from the h file are in a cpp file, gy_65.cpp (Example 12-7). If some parts of this code seem demanding, you might want to review the I2C code explanation in Figure 8-5, as well as Hexadecimal, Binary, and Other Numbering Systems and Bitwise Operations.

Example 12-7. gy_65.py
// gy_65.cpp - library for altitude, pressure and temperature with GY-65 BMP085
// (c) BotBook.com - Karvinen, Karvinen, Valtokari
#include <Arduino.h>
#include <Wire.h>
#include "gy_65.h"
const char i2c_address = 0x77;
int OSS = 0; // Oversampling
const long atmosphereSeaLevel = 101325; // Pa

struct calibration_data // 1
{
  int16_t ac1;
  int16_t ac2;
  int16_t ac3;
  int16_t ac4;
  int16_t ac5;
  int16_t ac6;
  int16_t b1;
  int16_t b2;
  int16_t mb;
  int16_t mc;
  int16_t md;
};

calibration_data caldata;

long b5;

int16_t swap_int16_t(int16_t value)     // 2
{
  int16_t left = value << 8;
  int16_t right = value >> 8;
  right = right & 0xFF;
  return left | right ;
}

unsigned char read_i2c_unsigned_char(unsigned char address)     // 3
{
  unsigned char data;
  Wire.beginTransmission(i2c_address);
  Wire.write(address);
  Wire.endTransmission();
  Wire.requestFrom(i2c_address,1);
  while(!Wire.available());
    return Wire.read();
}
void read_i2c(unsigned char point, uint8_t *buffer, int size)
{
  Wire.beginTransmission(i2c_address);
  Wire.write(point);
  Wire.endTransmission();

  Wire.requestFrom(i2c_address,size);

  int i = 0;

  while(Wire.available() && i < size) {
    buffer[i] = Wire.read();
    i++;
  }

  if(i != size) {
    Serial.println("Error reading from i2c");
  }

}

int read_i2c_int(unsigned char address) {
    int16_t data;
    read_i2c(address,(uint8_t *)&data,sizeof(int16_t));
    data = swap_int16_t(data);
    return data;
}

void readCalibrationData()      // 4
{
  Wire.begin();
  read_i2c(0xAA,(uint8_t *)&caldata,sizeof(calibration_data)); // 5

  uint16_t *p = (uint16_t*)&caldata;    // 6
  for(int i = 0; i < 11; i++) { // 7
    p[i] = swap_int16_t(p[i]);
  }
}

float readTemperature() {       // 8
  // Read raw temperature
  Wire.beginTransmission(i2c_address);
  Wire.write(0xF4); // Register
  Wire.write(0x2E); // Value
  Wire.endTransmission();
  delay(5); // 9
  unsigned int rawTemp = read_i2c_int(0xF6);

  // Calculate true temperature
  long x1,x2;
  float t;
  x1 = (((long)rawTemp - (long)caldata.ac6) * (long)caldata.ac5) / pow(2,15);
  long mc = caldata.mc;
  int md = caldata.md;
  x2 = (mc * pow(2,11)) / (x1 + md);
  b5 = x1 + x2;
  t = (b5 + 8) / pow(2,4);
  t = t / 10;
  return t;     // Celsius
}

long getRealPressure(unsigned long up){ // 10
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;
  int b1 = caldata.b1;
  int b2 = caldata.b2;
  long ac1 = caldata.ac1;
  int ac2 = caldata.ac2;
  int ac3 = caldata.ac3;
  unsigned int ac4 = caldata.ac4;

  b6 = b5 - 4000;
  x1 = (b2 * (b6 * b6) / pow(2,12)) / pow(2,11);
  x2 = (ac2 * b6) / pow(2,11);
  x3 = x1 + x2;

  b3 = (((ac1*4 + x3) << OSS) + 2) / 4;
  x1 = (ac3 * b6) / pow(2,13);
  x2 = (b1 * ((b6 * b6) / pow(2,12)) ) / pow(2,16);
  x3 = ((x1 + x2) + 2) / 4;
  b4 = (ac4 * (unsigned long)(x3 + 32768) ) / pow(2,15);

  b7 = ((unsigned long)up - b3) * (50000 >> OSS);
  if (b7 < 0x80000000)  p = ( b7 * 2 ) / b4;
  else p = (b7 / b4) * 2;

  x1 = (p / pow(2,8)) * (p / pow(2,8));
  x1 = (x1 * 3038) / pow(2,16);
  x2 = (-7357 * p) / pow(2,16);
  p += (x1 + x2 + 3791) / pow(2,4);

  long temp = p;
  return temp;
}

float readPressure() {  // 11
  // Read uncompensated pressure
  Wire.beginTransmission(i2c_address);
  Wire.write(0xF4); // Register
  Wire.write(0x34 + (OSS << 6)); // Value with oversampling setting.
  Wire.endTransmission();

  delay(2 + (3 << OSS));

  unsigned char msb,lsb,xlsb;
  unsigned long rawPressure = 0;
  msb = read_i2c_unsigned_char(0xF6);
  lsb = read_i2c_unsigned_char(0xF7);
  xlsb = read_i2c_unsigned_char(0xF8);

  rawPressure = (((unsigned long) msb << 16) |
        ((unsigned long) lsb << 8) |
        (unsigned long) xlsb) >> (8-OSS);

  return getRealPressure(rawPressure);
}

float calculateAltitude(float pressure) {       // 12
  float pressurePart = pressure / atmosphereSeaLevel;
  float power = 1 / 5.255;
  float result = 1 - pow(pressurePart, power);
  float altitude = 44330*result;
  return altitude; // m
}
1

Global structs and variables. The calibration data will be read from the sensor’s non-volatile (EEPROM) memory.

2

Take a two-byte integer, swap the left byte and the right byte. This is needed because the sensor represents numbers differently than the Arduino does.

3

These are convenience functions: read_i2c_unsigned_char(), read_i2c(), and read_i2c_int(). They are wrappers for functionality in Wire.h to make it easier to use in this program.

4

Read the calibration data from the non-volatile EEPROM memory of the sensor. The format is described on the BMP085 data sheet.

5

The trick is to use your own calibration_data struct to decode the EEPROM data. The bytes from the device are overlaid on the struct residing in Arduino’s memory. The struct is created so that each variable has a length that corresponds to each piece of data read from the EEPROM. After this line, caldata is filled with bytes from the sensor, and each piece of data is accessible through the struct.

6

As the sensor has different endianness than Arduino, you must swap the bytes of each two-byte integer. First, get a two-byte pointer to the start of caldata

7

…and then walk through caldata and swap the bytes. Notice how the two-byte (16 bit) pointer correctly points to a new two-byte integer on each iteration, instead of naively pointing to single bytes.

8

readTemperature() first reads the raw temperature using I2C. Then it calculates the actual temperature, using a formula from the data sheet.

9

The datasheet specifies that a delay of at least 4.5 ms is needed.

10

getRealPressure() takes the raw temperature read by readPressure(), then returns the result in Pascals (Pa). It uses the calibration data read by readCalibrationData(), and applies the formula from page 13 of the BMP085 data sheet.

11

Read the raw pressure using I2C. The register values and data format are taken from the data sheet.

12

Estimate altitude from pressure. The formula is from the international barometric formula, available on the BMP085 data sheet, page 14.

GY65 Code and Connection for Raspberry Pi

This Raspberry Pi code is easy to use, and you should try running it before reading it in detail. If you go through it line by line, you’ll see that the code can be quite demanding to understand. With Raspberry Pi, the I2C communication with the GY65 sensor is not in a separate file, so the code is longer than the Arduino example. The code uses some techniques you have already seen, such as the ones described in Hexadecimal, Binary, and Other Numbering Systems and Bitwise Operations.

GY65 atmospheric pressure sensor connected to Raspberry Pi
Figure 12-11. GY65 atmospheric pressure sensor connected to Raspberry Pi
Example 12-8. gy_65.py
# gy_65.py - print altitude,pressure and temperature to serial
# (c) BotBook.com - Karvinen, Karvinen, Valtokari

import smbus # sudo apt-get -y install python-smbus     # 1
import time
import struct

bus = None
address = 0x77
caldata = None

atmosphereSeaLevel = 101325.0
OSS = 0
b5 = 0

def readCalibrationData():      # 2
  global bus, caldata
  bus = smbus.SMBus(1)
  rawData = ""

  for i in range(22):
    rawData += chr(bus.read_byte_data(address, 0xAA+i)) # 3

  caldata = struct.unpack('>hhhhhhhhhhh', rawData) # 4

def readTemperature():
  global b5
  bus.write_byte_data(address, 0xF4, 0x2E)      # 5
  time.sleep(0.005)     # 6
  rawTemp = bus.read_byte_data(address, 0xF6) << 8      # 7
  rawTemp = rawTemp | bus.read_byte_data(address, 0xF7)
  x1 = ((rawTemp - caldata[5]) * caldata[4]) / 2**15
  x2 = (caldata[9] * 2**11) / (x1 + caldata[10])
  b5 = x1 + x2
  temp = (b5 + 8) / 2**4
  temp = temp / 10.0
  return temp


def readPressure():     # 8
  bus.write_byte_data(address, 0xF4, 0x34 + (OSS << 6))
  time.sleep(0.005)
  rawPressure = bus.read_byte_data(address, 0xF6) << 16
  rawPressure = rawPressure | bus.read_byte_data(address, 0xF7) << 8
  rawPressure = rawPressure | bus.read_byte_data(address, 0xF8)
  rawPressure = rawPressure >> (8 - OSS)

  #Calculate real pressure
  b6 = b5 - 4000

  x1 = (caldata[7] * ((b6 * b6) / 2**12 )) / 2**11
  x2 = caldata[1] * b6 / 2**11
  x3 = x1 + x2
  b3 = (((caldata[0] * 4 + x3) << OSS) + 2) / 4
  x1 = caldata[2] * b6 / 2**13
  x2 = (caldata[6] * ((b6 * b6) / 2**12 )) / 2**16
  x3 = ((x1 + x2) + 2) / 2*2
  b4 = (caldata[3] * (x3 + 32768)) / 2**15
  b4 = b4 + 2**16 # convert from signed to unsigned
  b7 = (rawPressure - b3) * (50000 >> OSS)
  if b7 < 0x80000000:
    p = (b7 * 2) / b4
  else:
    p = (b7 / b4) * 2
  x1 = (p / 2**8) * (p / 2**8)
  x1 = (x1 * 3038) / 2**16
  x2 = (-7357 * p) / 2**16
  p = p + (x1 + x2 + 3791) / 2**4
  return p

def calculateAltitude(pressure):        # 9
  pressurePart = pressure / atmosphereSeaLevel;
  power = 1 / 5.255;
  result = 1 - pressurePart**power;
  altitude = 44330*result;
  return altitude

def main():
  readCalibrationData()
  while True:
    temperature = readTemperature()
    pressure = readPressure()
    altitude = calculateAltitude(pressure)
    print("Altitude %.2f m" % altitude)
    print("Pressure %.2f Pa" % pressure)        # 10
    print("Temperature %.2f C" % temperature)
    time.sleep(10)


if __name__ == "__main__":
  main()
1

The SMBus standard is a subset of I2C. The smbus library makes it easy to use. The python-smbus package must be installed on Raspberry Pi for this to work (see SMBus and I2C Without Root).

2

The sensor ships with 176 bits of calibration data stored into its EEPROM.

3

Read data from address 0xAA, 0xAB, on up through 0xBF. Append these character values to the string. The string ends up having 22 bytes (176 bits). You didn’t have to think about how many bits the struct had in Arduino when you were doing this, because the struct’s length is defined by the length of the variables that are inside it.

4

Unpack 11 big endian (>) short two-byte integers (h). This consumes all the bytes from the string.

5

Write the command for “read temperature” (0x2E) to the sensor’s control register (0xF4).

6

Wait for the measurement to finish.

7

Read the answer, and start manipulating the raw number into a temperature in Celsius. The formulas are from the data sheet.

8

The readPressure() function works just like readTemperature().

9

The higher you go, the lower the pressure. Based on this, the international barometric formula can give you an estimate of your altitude.

10

100,000 Pa = 1 bar

Experiment: Does Your Plant Need Watering? (Build a Soil Humidity Sensor)

A soil humidity sensor is a simple analog resistance sensor. Stick it in the soil to see if your plant needs watering.

Normal tap water and groundwater contain diluted salts and other material. This makes water conductive. The soil humidity sensor (Figure 12-12) simply measures that conductivity.

Soil humidity sensor
Figure 12-12. Soil humidity sensor

Some soil humidity sensors have a built-in circuit. The sensor used here doesn’t have built-in electronics, so you could make your own from two pieces of metal (for the sensor probes) and use Arduino or Raspberry Pi to measure the resistance. The circuit uses a 1 megohm resistor (brown-black-green).

Soil Sensor Code and Connection for Arduino

Figure 12-13 shows the wiring diagram for Arduino. Hook it up as shown, and then run the code from Example 12-9.

Soil humidity sensor connected to Arduino
Figure 12-13. Soil humidity sensor connected to Arduino
Example 12-9. soil_humidity_sensor.ino
// soil_humidity_sensor.ino - read soil humidity by measuring its resistance.
// (c) BotBook.com - Karvinen, Karvinen, Valtokari


const int sensorPin = A0;
int soilHumidity = -1;

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

void loop() {
  soilHumidity = analogRead(sensorPin); // 1
  Serial.println(soilHumidity);
  delay(100);
}
1

It’s a simple analog resistance sensor.

Soil Sensor Code and Connection for Raspberry Pi

Figure 12-14 shows the connection diagram for Raspberry Pi. Hook it up as shown, and then run the code from Example 12-10.

Soil humidity sensor connected to Raspberry Pi
Figure 12-14. Soil humidity sensor connected to Raspberry Pi
Example 12-10. soil_humidity_sensor.py
# soil_humidity_sensor.py - read soil humidity by measuring its resistance.
# (c) BotBook.com - Karvinen, Karvinen, Valtokari

import time
import botbook_mcp3002 as mcp

def main():
        while True:
                h = mcp.readAnalog()    # 1
                h = h / 1024 * 100      # 2
                print("Current humidity is %d %%" % h)
                time.sleep(5)

if __name__ == "__main__":
        main()
1

It’s a simple analog resistance sensor. As with other analog resistance sensors in this book, the botbook_mcp3002.py library must be in the same directory as this program. You must also install the spidev library, which is imported by botbook_mcp3002. See the comments at the top of botbook_mcp3002/botbook_mcp3002.py or Installing SpiDev.

2

The raw value is converted to percentage of the maximum measurement. For display purposes, h is actually 100 times percentage, e.g., .53 becomes 53 so it can be displayed as 53%.

Test Project: E-paper Weather Forecast

Create your own weather forecast on e-paper. The weather forecast is based on changes in atmospheric pressure. The e-paper display is quite special: you can see it well in bright light, it looks a bit like paper, and the picture stays on without consuming electricity.

You’re reading the hardest project in the book. If you haven’t practiced with the easier experiments and projects already, you might want to go back and complete some of those first.

E-paper Weather Forecast
Figure 12-15. E-paper Weather Forecast

What You’ll Learn

In the E-paper Weather Forecast project, you’ll learn how to:

  • Build a box that shows a graphical weather forecast.
  • Predict weather using atmospheric pressure.
  • Display graphics on e-paper with zero energy consumption.
  • Make Arduino sleep to conserve power.
E-paper display
Figure 12-16. E-paper display

Weather Forecast Code and Connection for Arduino

The code combines many techniques. You can just build it first, and then learn about the implementation details once it works.

To create your own version, it’s not required that you understand all the code. After you have your weather station running, have a look at drawScreen(). It’s the main function, and quite high level. For example, you could start by changing pos, the location where the plus sign is drawn:

int pos = 10;
drawCharacter(pos, 70, font,'+'),

Techniques used in this code:

This code uses the Arduino Mega. It would need modification to work on another board.

Figure 12-17 shows the connections for the Arduino Mega. Wire it up as shown, and then run the code from Example 12-11.

Connections on Arduino Mega
Figure 12-17. Connections on Arduino Mega
Example 12-11. weather_station.ino
// weather_station.ino - print weather data to epaper
// (c) BotBook.com - Karvinen, Karvinen, Valtokari

#include <inttypes.h>
#include <ctype.h>

#include <SPI.h>
#include <Wire.h>
#include <EPD.h>        // 1
#include <gy_65.h>      // 2
#include <avr/sleep.h>
#include <avr/power.h>

#include "rain.h"       // 3
#include "sun.h"
#include "suncloud.h"
#include "fonts.h"

uint8_t imageBuffer[5808]; // 264 * 176 / 8

const int pinPanelOn = 2;
const int pinBorder = 3;
const int pinDischarge = 4;
const int pinPWM = 5;
const int pinReset = 6;
const int pinBusy = 7;
const int pinEPDcs = 8;

EPD_Class EPD(EPD_2_7,
              pinPanelOn,
              pinBorder,
              pinDischarge,
              pinPWM,
              pinReset,
              pinBusy,
              pinEPDcs,
              SPI);

float weatherDiff;
float temperature;

const int sleepMaxCount = 10;   // min
volatile int arduinoSleepingCount = sleepMaxCount;

void setup() {
  Serial.begin(115200);
  pinMode(pinPanelOn, OUTPUT);
  pinMode(pinBorder, OUTPUT);
  pinMode(pinDischarge, INPUT);
  pinMode(pinPWM, OUTPUT);
  pinMode(pinReset, OUTPUT);
  pinMode(pinBusy, OUTPUT);
  pinMode(pinEPDcs, OUTPUT);


  digitalWrite(pinPWM, LOW);
  digitalWrite(pinReset, LOW);
  digitalWrite(pinPanelOn, LOW);
  digitalWrite(pinDischarge, LOW);
  digitalWrite(pinBorder, LOW);
  digitalWrite(pinEPDcs, LOW);

  SPI.begin();  // 4
  SPI.setBitOrder(MSBFIRST);    // 5
  SPI.setDataMode(SPI_MODE0);   // 6
  SPI.setClockDivider(SPI_CLOCK_DIV4);  // 7

  WDTCSR |= (1<<WDCE) | (1<<WDE);       // 8
  WDTCSR = 1<<WDP0 | 1<<WDP3;   // 9
  WDTCSR |= _BV(WDIE);  // 10
  MCUSR &= ~( 1 << WDRF);       // 11

  for(int i = 0; i < 5808; i++) // 12
    imageBuffer[i] = 0;

  readCalibrationData();        // 13

}

char characterMap[14] = {'+', '-', 'C', 'd',
                         '0', '1', '2', '3',
                         '4', '5', '6', '7',
                         '8', '9'};     // 14

void drawCharacter(int16_t x, int16_t y, const uint8_t *bitmap, char character) {
  int charIndex = -1;
  for(int i = 0; i < 14; i++) { // 15
    if(character == characterMap[i]) {
      charIndex = i;
      break;
    }
  }
  if(charIndex == -1) return;
  drawBitmap(x,y,bitmap,charIndex*25,0,25,27,350);      // 16
}

void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t x2,
    int16_t y2, int16_t w, int16_t h, int16_t source_width) {   // 17

  int16_t i, j, byteWidth = source_width / 8;

  for(j=y2; j<y2+h; j++) {      // 18
    for(i=x2; i<x2+w; i++ ) {
      byte b= pgm_read_byte(bitmap+j * byteWidth + i / 8);
      if(b & (128 >> (i & 7))) {
             drawPixel(x+i-x2, y+j-y2, true);
      }
    }
  }
}

void drawPixel(int x, int y, bool black) { // 19
  int bit = x & 0x07;
  int byte = x / 8 + y * (264 / 8);
  int mask = 0x01 << bit;
  if(black == true) {
    imageBuffer[byte] |= mask;
  } else {
    imageBuffer[byte] &= ~mask;
  }
}

void drawBufferToScreen() {     // 20
  for (uint8_t line = 0; line < 176 ; ++line) {
        EPD.line(line, &imageBuffer[line * (264 / 8)], 0, false, EPD_inverse);
  }
  for (uint8_t line = 0; line < 176 ; ++line) {
        EPD.line(line, &imageBuffer[line * (264 / 8)], 0, false, EPD_normal);
  }
}

void drawScreen() {     // 21
  EPD.begin();
  EPD.setFactor(temperature);
  EPD.clear();
  if(weatherDiff > 250) {       // Pa
    // Sunny
    drawBitmap(140,30,sun,0,0,117,106,117);     // 22
  } else if ((weatherDiff <= 250) || (weatherDiff >= -250)) {
    // Partly cloudy
    drawBitmap(140,30,suncloud,0,0,117,106,117);
  } else if (weatherDiff < -250) {
    // Rain
    drawBitmap(140,30,rain,0,0,117,106,117);
  }
  //Draw temperature
  String temp = String((int)temperature);

  int pos = 10;
  drawCharacter(pos,70,font,'+');       // 23
  pos += 25;
  drawCharacter(pos,70,font,temp.charAt(0));
  pos += 25;
  if(abs(temperature) >= 10) {
    drawCharacter(pos,70,font,temp.charAt(1));
    pos += 25;
  }
  drawCharacter(pos,70,font,'d');
  pos += 25;
  drawCharacter(pos,70,font,'C');


  drawBufferToScreen(); // 24

  EPD.end();

  for(int i = 0; i < 5808; i++) // 25
    imageBuffer[i] = 0;
}

void loop() {
  Serial.println(temperature);  // 26
  if(arduinoSleepingCount >= sleepMaxCount) {   // 27
    readWeatherData();  // 28
    drawScreen();
    arduinoSleepingCount = 0; // 29
    arduinoSleep(); // 30
  } else {
    arduinoSleep();
  }
}

const float currentAltitude = 40.00; // Installation altitude in meters
const long atmosphereSeaLevel = 101325; // Pa
const float expectedPressure = atmosphereSeaLevel * pow((1-currentAltitude / 44330), 5.255);

void readWeatherData(){
  temperature = readTemperature();
  float pressure = readPressure();
  weatherDiff = pressure - expectedPressure;
}

ISR(WDT_vect)   // 31
{
  arduinoSleepingCount++;
}

void arduinoSleep() { // 32
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_mode(); // 33
  sleep_disable();      // 34
  power_all_enable();
} // 35
1

Library for e-paper display. You can download the library from https://github.com/repaper/gratis/tree/master/Sketches/libraries. Follow the instructions at http://arduino.cc/en/Guide/Libraries to install the library.

2

This is the botbook.com GY65 library. You used it earlier in Atmospheric Pressure GY65. Copy the library directory into your sketch folder (if you downloaded the sample code for this book, it will already be in there).

3

Images are saved as header files.

4

Prepare SPI before we talk to the e-paper display.

5

Configure SPI to have the most significant bit first (see Bitwise Operations).

6

Configure SPI for SPI_MODE0: this sets the clock polarity to mode CPOL 0, and the clock phase to mode CPHA 0. This means that data is captured on the clock’s rising edge (when the clock signal goes from LOW to HIGH). Data is propagated on the falling edge (when the clock signal goes from HIGH to LOW). Modes (SPI_MODE0) are listed in Arduino documentation (Help→Reference→Libraries→SPI). The SPI settings for the sensor are on its data sheet.

7

SPI_CLOCK_DIV4 sets the SPI clock to 1/4 of the Arduino CPU frequency. This project uses Arduino Mega, so the SPI clock frequency is 16 MHz / 4 = 4 MHz.

8

Enable the watchdog timer. This is what will allow Arduino to go into a deep sleep. Set WDCE (watch dog change enable) and WDE (watch dog enable) in WDTCSR (Watchdog Timer Control Register). These registers are so low level that they are from ATmega documentation (instead of being part of the core Arduino library). Arduino Mega uses the ATmega 1280 chip, so you can find the documentation by searching for “ATmega 1280 data sheet.” The symbol |= is an inplace bitwise XOR (performs an XOR against the variable’s current value and replaces the variable’s value with the result). The symbol << represents the bit shift operation. See Bitwise Operations.

9

Set the watchdog to wake up the Arduino every 8 seconds.

10

Enable the watchdog interrupt. WDIE is “watch dog interrupt enable.”

11

Clear the watchdog system reset flag (WDRF) from the microcontroller unit status register (MCUSR).

12

Initialize the image buffer (where graphics are drawn before being displayed) with zeroes.

13

For details about using the GY65 sensor, see the code explanations in Atmospheric Pressure GY65.

14

List of the 14 characters that are in font.h, a large image that contains these characters.

15

Find the index of the character in the font.h image.

16

Draw a character to screen. In practice, the bitmap will contain a font, a big picture with characters side by side. The calculation just selects one of these characters.

17

drawBitmap() takes a source bitmap, position, and dimensions. It then draws each pixel of the picture to an intermediate image buffer (but does not yet display it on the e-paper display).

18

Traverse the area two dimensionally to draw the bitmap one pixel at a time.

19

Draw one pixel to image buffer (but not yet to the e-paper display). The image is stored one bit per pixel, so every byte (8 bits) contains 8 pixels. The width of the display is 264 pixels, so one line is stored in 264/8 = 33 bytes. The code then traverses over each bit, changing it to 1 or 0 as needed.

20

Blit the already drawn graphics from imageBuffer to screen. This uses the EPD library. The display has 176 lines, 264 dots per line. As one bit represents one pixel, the display has 264/8 (33) bytes per line. Blitting the image is a simple matter of drawing each byte of imageBuffer, using EPD.line.

21

drawScreen() uses the GY65 library to measure environment, and the EPD library to draw on the e-paper display. The larger the pressure difference, the worse the weather. If you want to create your own version of the program, this drawScreen() function is where you should begin your customizations.

22

Draw the image from sun.h to imageBuffer. To put your own images on the screen, use this function. The parameters are drawBitmap(imageBufferX, imageBufferY, sourceImage, sourceX, sourceY, sourceWidth, sourceHeight, totalSourceWidth). The first two are the target position (imageBufferX, imageBufferY). The rest of the parameters concern the source bitmap: the source bitmap (sourceImage), what to take from the source bitmap (sourceX, sourceY, sourceWidth, sourceHeight). Finally, the total width of the source bitmap is listed (totalSourceWidth).

23

Write a character to image buffer.

24

Blit the imageBuffer from memory to the e-paper display. Without this, none of the images would be shown.

25

Clear the image buffer.

26

With an unfamiliar component such as a fancy e-paper display, it’s a good idea to confirm sensor data separately by writing it to the serial console.

27

The watchdog wakes Arduino periodically. If enough time has passed…

28

…it’s time to run the meat of the program.

29

Then reset the sleep counter…

30

…and fall back to sleep to save power.

31

The ISR() Interrupt Service Routine is called automatically. ISR() runs every time Arduino wakes up, just before anything else runs. This code wakes up Arduino every 8 seconds, so ISR() runs every 8 seconds.

32

Make Arduino fall asleep to conserve power. As you saw earlier, this required quite some preparation.

33

This command makes Arduino sleep and stop executing code. Nothing happens after this—until the watchdog wakes up Arduino.

34

When the watchdog wakes Arduino, code flow continues on this line and wakeup starts.

35

You worked through all this difficult code? Pat yourself on the back—you’re on your way to becoming a guru!

Environment Experiment: Look Ma, No Power Supply

E-paper displays use power only to change the picture on the display. They don’t need any energy to leave a picture on the screen.

You can try it yourself. Use Arduino to display something on your e-paper display. Then power down Arduino, and even disconnect the e-paper display. The picture stays unchanged. In fact, there is no visible difference at all between keeping e-paper connected or disconnecting it (Figure 12-18).

The same image stays on an e-paper display even without power
Figure 12-18. The same image stays on an e-paper display even without power

Storing Images in Header Files

Your very own e-paper weather station can already show you the sun. What if you want to draw your own graphics? You can draw your images in an image editing program like GIMP or Photoshop, and then convert the saved BMP images to the C code headers we use in the sketch.

First, use your favorite drawing program to draw an image. GIMP is a good, free choice for this. Save your image in the BMP format. Put the image into the same folder with the other images in this project (images/).

Next, you’ll need to convert the BMP image (sun.bmp) to a C header file (sun.h). You can use the included image2c-botbook.py script for this.

First, install the requirements: Python and Python Imaging Library. Windows users can download a free installer for Python from http://python.org. Mac users will already have Python installed, but Mac and Windows users will need to download the Python Imaging Library from http://www.pythonware.com/products/pil/. Linux users have it easier. For example, here’s how to install the needed libraries:

$ sudo apt-get update
$ sudo apt-get -y install python python-imaging

If your source image is not called foo.bmp, change the filenames in the script. Then you’re ready to convert. These commands work in all of Linux, Windows, and Mac. The dollar sign ($) represents the shell prompt, so don’t type that. Windows users probably have a different-looking prompt. The first command assumes you’re in the subdirectory containing the example code for this book. You can download the sample code from http://botbook.com.

$ cd arduino/weather_station/
$ python image2c-botbook.py
BMP (117, 106) 1

The file is now converted. With the ls or dir command, you can see that foo.h was created in the current working directory. You have now converted your BMP image to a C header file.

In foo.h, your image is now C code:

// File generated with image2c-botbook.py
const unsigned char sun [] PROGMEM= {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ..
0x7F, 0xF0, 0x00, 0xFF, 0xC0, 0x00, 0x1F, 0xF8, 0x00, // ..

This format is convenient for programming. Each byte represents eight pixels, one pixel per bit. For example, hex 0xFC is number 252. In bits, it’s 0b11111100. This means eight bits side by side: black black black black, black black white white.

BMP to C Conversion Program

Image2c-botbook.py converts a BMP image to C header file.

If you need some “code golf” (see http://codegolf.com), try making it work with PNG source files.

This code uses hexadecimal numbers and bitwise operations. To review them, see Hexadecimal, Binary, and Other Numbering Systems and Bitwise Operations.

Example 12-12. Image to C header code
# image2c-botbook - convert a BMP image to C for use in eInk and LCD displays
# (c) BotBook.com - Karvinen, Karvinen, Valtokari

import Image    # 1
import math
imageName = "images/foo.bmp"    # 2
outputName = "foo.h"    # 3

im = Image.open(imageName)      # 4
print im.format, im.size, im.mode

width, height = im.size
pixels = list(im.getdata())     # 5
length = int(math.floor(width * height / 8.0))  # 6
carray = length * [0x00]        # 7
for y in xrange(0, height):     # 8
        for x in xrange(0, width):      # 9
                pixel = pixels[y * width + x]   # 10

                bit = 7 - x & 0x07      # 11
                byte = x / 8 + y * (width / 8)  # 12

                mask = 0x01 << bit      # 13
                if pixel == 0:
                        carray[byte] |= mask    # 14
                else:
                        carray[byte] &= ~mask



fileHandle = open(outputName, "w")      # 15
index = 0
fileHandle.write("// File generated with image2c-botbook.py
")
fileHandle.write("const unsigned char sun [] PROGMEM= {
")
for b in carray:
        fileHandle.write("0x%02X, " % b)        # 16
        index += 1
        if index > 15:
                fileHandle.write("
")
                index = 0
fileHandle.write("};
")
1

PIL, the Python Imaging Library, must be installed as described earlier.

2

The source image, saved in BMP format. Change this to match the name and location of your file if needed.

3

Output filename. Change this to match the file you want to create.

4

PIL can open many formats.

5

Break the image into individual pixels. This is half of the work.

6

Length of the whole image in bytes (1 B == 8 bit)

7

Create the target C array, and initialize it with zeroes.

8

For each line…

9

…and for each pixel in the current line, perform the indented operations.

10

The current pixel.

11

The index of bit in the current byte.

12

The current byte index.

13

Create the bit mask for current bit in current byte. For example, the mask 0b00000001 would be created to change the last bit of a byte to zero.

14

Change the bit.

15

C headers are just text files.

16

Write each of the bytes as a hex code.

Enclosure Tips

We used a plastic box made by Hammond for our weather forecaster. But how do you make an odd-shaped hole like the one we need here? First draw the shape you want to cut out on the box lid. Drill holes in each corner of the shape with a large drill bit (10-20 mm). Then start going from corner to corner with a jigsaw blade. Finish the hole with file and sandpaper (see Figure 12-19).

Hole for the screen
Figure 12-19. Hole for the screen

Use hot glue to attach the screen to the box as shown in Figure 12-20.

Screen hot glued
Figure 12-20. Screen hot glued

On the back of the box, we made a pattern of small holes (see Figure 12-21). Without those, air would not be able to get to the sensor. The finished gadget is shown in Figure 12-22.

Holes to let the air in
Figure 12-21. Holes to let the air in
The finished gadget
Figure 12-22. The finished gadget

Congratulations, you now have your own weather prophet with e-paper display!

This might be the end of this book, but it’s just the beginning for your projects. Now that you can work with so many sensors and many outputs too, what are you going to build?

Good luck with your projects!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset