Lawrence Technological University
College of Arts and Science
Department of Mathematics and Computer Science

Handouts

WiFi and the Arduino

by John M. Miller M.D.

   The experiment on this page may not be a good "starter" Arduino Uno project. There were two main problem areas:

  1. Many other serial devices are easy to use with the Arduino Wire library. The Sensirion devices are non standard and have no working Arduino style library that I could find. The interface I made was patterned after the Sensirion data sheets and non-Arduino sample code.
  2. Using the WiFi Web server with the small Uno variable space (which is also shared with the stack) worked better when I stored most of the HTML strings in the flash memory, program space. This brings up the issue of C pointers, which many beginners would prefer to avoid.

   This page is an experiment in measuring temperature and humidity using a digital sensor with the Arduino platform. The results are monitored remotely on a home WiFi network. The parts:

  1. Arduino Uno
  2. Sensirion's SHT10 Digital Humidity Sensor assembled as Emartee's Digital Temperature & Humidity Sensor module, Product ID 42049
  3. Emartee's TLG10UA03 Wifi module and Arduino shield kit, Product ID 42121
  4. Disk drive power cord from a defunct PC power supply.

   Assembled, the remote sensor looks like this
Arduino with Emartee's 42049 and 42121 products.

   So what use is all this? Squawk the parakeet likes to be near a window. Squawk's favorite window faces south and around 2 pm on a sunny winter day can heat up Squawk's cage area quite a bit. Turning the furnace fan on and the heating off redistributes this heated air nicely. Maybe for a further experiment we can have an Arduino watch the temperature and control the furnace as well. For this iteration we will just try to monitor the temperature when we are away from Squawk's room. A cellular modem, made from a GSM Arduino shield and SIM card, to send text messages would work better for people, unlike me, who regularly answer their phones. An XBee wireless network would be an option we might try later. Bluetooth communications are somewhat limited in range. The house has some WiFi networks in place already so we can readily use the hardware as shown above. Sending our data to Oak Ridge's Sensorpedia would be interesting. Twitter tweets might be appropriate for a project for a bird. Facebook updates would be OK for an experimenter who had a Facebook page. But since Web browsers are ubiquitous, we can begin with having a browser send a simple HTTP GET request and receive a reply containing the current temperature and relative humidity.

   Before using our hardware setup, we have to intialize the flash memory on the WiFi module so it will connect to the password protected network we have picked for our experiment. In general the Emartee document: WiFi_test_v2.pdf (available on the Emartee site through their link to Google Docs) was followed. Except after using the Arduino IDE Serial Monitor to send the escape sequence of "+++" (in order to force the WiFi module to exit "Auto Work Mode" and accept AT configuration commands) we continue configuration still using the Serial Monitor because there was no Window's machine handy to run the supplied configuration program. Here is a session, verifying all the settings that were changed manually with AT commands, using

(Set for "no line ending")
(Sent +++ with no line ending) to exit into AT command mode.
+OK
(Set for "cr line ending")
(Sent at+ with cr line ending) to test command mode.
+OK
(Sent at+e with cr line ending) to begin echo of commands sent.
+OK

at+ssid
+OK="2WIRE148"

at+encry
+OK=3     3 seems faster than 6 at joining.

at+key
+OK=1,0,"0123456789"

at+wscan
+OK=c0830a255691,0,1,1,"2WIRE596",86
00304409384a,0,4,1,"Greenview Observatory3",58
00235140dba1,0,5,1,"2WIRE674",82
00173f9288c1,0,10,0,"Belkin_G_Plus_MIMO_180170",84
640f28108449,0,10,1,"2WIRE148",29
0022a40c9ad9,0,10,1,"2WIRE002",82

at+qver
+OK=H0.00.00.0000,F1.02.01@ 17:23:31 Dec 16 2010

at+wjoin
+OK=640f28108449,0,10,1,"2WIRE148",37

at+lkstt
+OK=1,"192.168.1.66","255.255.255.0","192.168.1.254","192.168.1.254"

at+nip
+OK=0

at+uart
+OK=115200,0,0,0

at+webs
+OK=1,80

finally: at+pmtf records changes in flash memory.
         at+z resets module so will come back up in auto work mode.

   The rest of the initialization was done via the WiFi interface exactly the same as in the Emartee module test procedure -- including setting the port to 8090.

   Testing the server code I found that without using a Content-Length: header the browser would wait about 5 minutes for the reply to end under HTTP 1.1. Here is the code running on the Arduino: (Uploading using the Arduino IDE produces an error unless the jumper plugs that connect the WiFi module to the Arduino Tx and Rx pins are removed temporarily.)

// wifi_trh
// March 8, 2012
// Libraries:
#include <avr/pgmspace.h>
#include <PString.h>
// Notes:
/* Uploading this program with the Arduino IDE, when the Wifi Shield
   is attached to the Arduino, requires removing the 2 jumper plugs
   on the shield that connect the Wifi module's and Arduino's Tx and Rx
   pins.
*/
/* Temperature and Humidity are measured with digital results using
   Emartee's Digital Temperature & Humidity Sensor module, Product ID: 42049
*/
/* Read temperature and humidity values from an SHT10 sensor.
   Coefficients for adjusting for temperature and non-linearity
   are from Sensirion SHT1x datasheet Version 4.3 May 2010. 
   Used coefficients for 3.5 volts as nearest to VDD of 3.3 volts.
*/
/* SHT10 module connections:
  Function   SHT10  SHT Module  Wire       Arduino                             `
  Ground     1      3           Black      Gnd
  Data       2      4           Red        D10
  Clock      3      1           Yellow     D11
  VDD +3.3V  4      2           Black/Red  +3.3v
*/
/* Results are served with Emartee's TLG10UA03 WiFi module which
   was purchased, with a nice shield that makes the wiring easier
   and a USB converter that makes initialization easier, as a kit
   -- Product ID: 42121
*/
/* With a little JavaScript, the time-stamp for the served measurements
   is left to the client Web browser.
*/
// #defines for SHT10:
// Pins:
#define DATA_PIN  10
#define CLOCK_PIN 11
// Commands:
#define TEMPERATURE 3
#define HUMIDITY 5
#define WRITE_STATUS 6
#define READ_STATUS 7
#define SOFT_RESET 0x1e
// Delays in microseconds:
#define PLATEAU 40
#define HOLD 20
#define MAX_MEASUREMENT_WAIT 4000
#define MAX_RETRIES 1
// Constant strings in flash/program memory:
prog_char html1[] PROGMEM = "<html>\n<head>\n"
                            "<title>Temperature and Relative Humidity "
                            "from Arduino Uno WiFi Server</title>\n";
prog_char html1_sr[] PROGMEM = "<meta name=\"send receive\" content=\"";
prog_char html1_se[] PROGMEM = "<meta name=\"serial errors\" content=\"";
prog_char html1_rth[] PROGMEM = "<meta name=\"raw data t,h\" content=\"";
prog_char html1_ms[] PROGMEM = "<meta name=\"wait time\" content=\"";
prog_char html2[] PROGMEM = "</head>\n<body>\n"
                            "<h1>Squawk's Weather Station</h1>\n";
prog_char html3[] PROGMEM = "<h3><script type=\"text/javascript\">\n"
                            "var d = new Date();\n"
                            "document.write(d.toString());\n"
                            "</script></h3>\n";
prog_char html3_t[] PROGMEM = "<p>Temperature: ";
prog_char html3_h[] PROGMEM = "<p>Relative Humidity: ";
prog_char html3_na[] PROGMEM = "<p>Temperature and Humidity not available. "
                               " Please try later.\n";
prog_char html4[] PROGMEM = "</body>\n</html>\n";
// Other constant strings:
const char meta_end[] = "\" />\n";
// Globals:
unsigned long started;
unsigned long elapsed;
int base_reply_length;
int reply_length;
int raw_temperature;
int raw_humidity;
float temp_C;
float temp_F;
float humidity;
int retries;
char buffer[100];
char buffer_sr[50];
char buffer_se[20];
char buffer_rth[15];
char buffer_ms[25];
char buffer_t[30];
char buffer_h[15];
// Debugging traces for reporting in META tags:
PString send_receive(buffer_sr,sizeof(buffer_sr));
PString serial_errors(buffer_se,sizeof(buffer_se));
PString raw_th(buffer_rth,sizeof(buffer_rth));
PString millisec(buffer_ms,sizeof(buffer_ms));
// The actual reply temperature and relative humidity.
PString t(buffer_t,sizeof(buffer_t));
PString h(buffer_h,sizeof(buffer_h));
// Conversion coefficients from SHT1x datasheet Version 4.3 May 2010
const float D1 = -39.7;  // for 14 Bit @ 3.5V
const float D2 =   0.01; // for 14 Bit DEGC
const float C1 = -2.0468;       // for 12 Bit
const float C2 =  0.0367;       // for 12 Bit
const float C3 = -0.0000015955; // for 12 Bit
const float T1 =  0.01;      // for 14 Bit
const float T2 =  0.00008;   // for 14 Bit
// Subroutines:
void start_transmission()
{
  //                ____      _____
  // CLOCK_PIN ____|    |____|     |____
  //           ______            _______
  // DATA_PIN        |__________|
  pinMode(DATA_PIN,OUTPUT);
  digitalWrite(DATA_PIN,HIGH);
  digitalWrite(CLOCK_PIN,LOW);
  delayMicroseconds(PLATEAU);
  digitalWrite(CLOCK_PIN,HIGH);
  delayMicroseconds(HOLD);
  digitalWrite(DATA_PIN,LOW);
  delayMicroseconds(HOLD);
  digitalWrite(CLOCK_PIN,LOW);
  delayMicroseconds(PLATEAU);
  digitalWrite(CLOCK_PIN,HIGH);
  delayMicroseconds(HOLD);
  digitalWrite(DATA_PIN,HIGH);
  delayMicroseconds(HOLD);
  digitalWrite(CLOCK_PIN,LOW);
  delayMicroseconds(PLATEAU);
}
void serial_reset()
// This reset has not been tested!
{
  pinMode(DATA_PIN,OUTPUT);
  digitalWrite(DATA_PIN,HIGH);
  digitalWrite(CLOCK_PIN,LOW);
  for (int i = 0;i < 9;i++) {
    delayMicroseconds(HOLD);
    digitalWrite(CLOCK_PIN,HIGH);
    delayMicroseconds(HOLD);
    digitalWrite(CLOCK_PIN,LOW);
  }
  retries++;
}
void bit_out(int d)
{
  digitalWrite(DATA_PIN,d);
  delayMicroseconds(HOLD);
  digitalWrite(CLOCK_PIN,HIGH);
  delayMicroseconds(PLATEAU);
  digitalWrite(CLOCK_PIN,LOW);
  delayMicroseconds(HOLD);
  digitalWrite(DATA_PIN,LOW);
}
int bit_in()
{
   delayMicroseconds(HOLD);
   digitalWrite(CLOCK_PIN,HIGH);
   delayMicroseconds(HOLD);
   int d = digitalRead(DATA_PIN);
   delayMicroseconds(HOLD);
   digitalWrite(CLOCK_PIN,LOW);
   delayMicroseconds(HOLD);
   return d;
 }
int read_ack()
{
  pinMode(DATA_PIN,INPUT);     // Assume was in DATA_PIN output mode. 
  digitalWrite(DATA_PIN,HIGH); // Pullup DATA_PIN.
  return bit_in();
}
void send_ack(boolean ack)
{
  pinMode(DATA_PIN,OUTPUT); // Assume was in DATA_PIN input mode.
  //            ____          __
  // DATA_PIN       |________|    Case where ack is true.
  //                   ____
  // CLOCK_PIN  ______|    |____
  delayMicroseconds(PLATEAU);
  bit_out(ack ? LOW : HIGH);
  pinMode(DATA_PIN,INPUT);     // Restore to DATA_PIN input mode.
  digitalWrite(DATA_PIN,HIGH); // Restore pullup.
 }
int read_byte(boolean ack)
{
  int b,i;
  for(b = 0,i = 0x80;i;i >>= 1) { if ( bit_in() == HIGH) b |= i; }
  send_ack(ack);
  send_receive.print("<");
  send_receive.print(b,HEX);
  send_receive.print(ack ? "_" : "^");
  return b;
}
int send_byte(int b)
{
  for (int i = 0x80;i > 0;i >>= 1) { bit_out( (i & b) ? HIGH : LOW); }
  int ack = read_ack();
  send_receive.print(">");
  send_receive.print(b,HEX);
  send_receive.print(ack ? "^" : "_");
  return ack;
}
int get_measurement(int measurement)
{
  int m = -1;
  while (retries <= MAX_RETRIES) {
    start_transmission();
    if (send_byte(measurement) == HIGH) {
      serial_errors.print("A");
      serial_errors.print(measurement);
      serial_reset();
      continue;
    }
    // Wait during measurement.
    delayMicroseconds(PLATEAU);
    started = millis();
    while ((elapsed = millis() - started) < MAX_MEASUREMENT_WAIT) {
      if (digitalRead(DATA_PIN) == LOW) {
        millisec.print(elapsed);
        millisec.print("|");
        break;
      }
    } 
    if (elapsed >= MAX_MEASUREMENT_WAIT) {
      millisec.print(elapsed);
      serial_errors.print("T");
      serial_errors.print(measurement);
      serial_reset();
      continue;
    }
    // Receive MSB.
    m = read_byte(true);
    // Receive  and add on LSB.
    m = m * 256 + read_byte(false); // Skip CRC by sending HIGH acknowledgment.
    break;
  }
  return m;
}

void setup()
{
  base_reply_length = strlen_P(html1) +
                      strlen_P(html1_sr) +
                      strlen_P(html1_se) +
                      strlen_P(html1_rth) +
                      strlen_P(html1_ms) +
                      strlen_P(html2) +
                      strlen_P(html3) +
                      strlen_P(html4) +
                      strlen(meta_end) * 4;
  Serial.begin(115200);
  pinMode(CLOCK_PIN,OUTPUT);
  digitalWrite(CLOCK_PIN,LOW);
  pinMode(DATA_PIN,INPUT);
  digitalWrite(DATA_PIN,HIGH);
}
void loop()
{
  boolean blankLine = true;
  // Initialize debugging traces and the result:
  send_receive.begin();
  serial_errors.begin();
  raw_th.begin();
  millisec.begin();
  t.begin();
  h.begin();
  retries = 0;
  while(1){
    if (Serial.available()) {
      char c = Serial.read();
      if (c == '\n' && blankLine) { // End of an HTTP request.
        // Read the current values from the SHT10.
        raw_temperature = get_measurement(TEMPERATURE);
        raw_humidity = get_measurement(HUMIDITY);
        // Prepare the reply.
        raw_th.print(raw_temperature);
        raw_th.print(",");
        raw_th.print(raw_humidity);
        // Report the measurements only if both are good.
        if (raw_temperature >= 0 && raw_humidity >= 0) {
          temp_C = (raw_temperature * D2) + D1;
          temp_F = temp_C * 9.0 / 5.0 + 32.0;
          float adj_humidity = C1 + C2 * raw_humidity +
                               C3 * raw_humidity * raw_humidity;
          humidity = (temp_C - 25.0 ) * (T1 + T2 * raw_humidity) + adj_humidity;
          t.print(temp_C,1);
          t.print(" &deg;C, ");
          t.print(temp_F,1);
          t.print(" &deg;F\n");
          h.print(humidity,1);
          h.print("%\n");
          reply_length = base_reply_length +
                         send_receive.length() +
                         serial_errors.length() +
                         raw_th.length() +
                         millisec.length() +
                         strlen_P(html3_t) + t.length() +
                         strlen_P(html3_h) + h.length();
        } else {
          reply_length = base_reply_length + 
                         send_receive.length() +
                         serial_errors.length() +
                         raw_th.length() +
                         millisec.length() +
                         strlen_P(html3_na);
        }
        // Send the reply.
        Serial.print("HTTP/1.1 200 OK\r\n");
        Serial.print("Content-Type: text/html\r\n");
        Serial.print("Content-Length: ");
        Serial.print(reply_length);
        Serial.print("\n\n");
        // Send body of the reply:
        strcpy_P(buffer,html1);
        Serial.print(buffer);
        strcpy_P(buffer,html1_sr);
        Serial.print(buffer);
        Serial.print(send_receive);
        Serial.print(meta_end);
        strcpy_P(buffer,html1_se);
        Serial.print(buffer);
        Serial.print(serial_errors);
        Serial.print(meta_end);
        strcpy_P(buffer,html1_rth);
        Serial.print(buffer);
        Serial.print(raw_th);
        Serial.print(meta_end);
        strcpy_P(buffer,html1_ms);
        Serial.print(buffer);
        Serial.print(millisec);
        Serial.print(meta_end);
        strcpy_P(buffer,html2);
        Serial.print(buffer);
        strcpy_P(buffer,html3);
        Serial.print(buffer);
        if (raw_temperature >= 0 && raw_humidity >= 0) {
          strcpy_P(buffer,html3_t);
          Serial.print(buffer);
          Serial.print(t);
          strcpy_P(buffer,html3_h);
          Serial.print(buffer);
          Serial.print(h);
        } else {
          strcpy_P(buffer,html3_na);
          Serial.print(buffer);
        }
        strcpy_P(buffer,html4);
        Serial.print(buffer);
        break;
      } else if (c == '\n') { // && !blankLine
        blankLine = true;     // So start a new line.
      } else if (c != '\r') {
        blankLine = false;    // Line is not blank.
      }
    }
  }
}

   As seen from a client Web Browser:
Arduino server replying

   Viewing the source code of that reply:

<html>
<head>
<title>Temperature and Relative Humidity from Arduino Uno WiFi Server</title>
<meta name="send receive" content=">3_<18_<6F^>5_<4_<4D^" />
<meta name="serial errors" content="" />
<meta name="raw data t,h" content="6255,1101" />
<meta name="wait time" content="221|63|" />
</head>
<body>
<h1>Squawk's Weather Station</h1>
<h3><script type="text/javascript">
var d = new Date();
document.write(d.toString());
</script></h3>
<p>Temperature: 22.8 &deg;C, 73.1 &deg;F
<p>Relative Humidity: 36.2%
</body>
</html>

Revised March 13, 2012