Why bother? Because it is microcontrollers just like this that are permeating our lives, whether we realize it or not: embedded in our appliances and toys and transportation and even computers, sometimes alongside much more powerful Intel or ARM processors that do the heavy lifting required by graphical user interfaces. Microcontrollers are the universal machines that are called upon to bridge the gap between the real world and the digital, between meatspace and cyberspace. It is a market that is growing even as the market for traditional high-end microprocessors is being impacted as the typical consumer finds less and less reason to own a desktop computer or even a laptop, finding their cyberneeds better met by a smartphone or tablet and more and more computation is moved into the cloud. Thanks to their relatively low price tags, microcontrollers are increasingly becoming replacements for more complex discrete circuitry for glueing other digital components together and for implementing complex behavior. Microcontrollers bring the added advantage of being able to have their firmware updated in the field; discrete electronics, not so much.
One application for tiny microcontrollers is remote sensing. Because of their small size and frugal power consumption, microcontrollers are useful for deploying digital control and sensing into the physical world, where they can gather intelligence and report back. Playing with remote sensing is easily done with Arduino with the addition of a Ethernet shield.
Shields in the world of Arduino are daughter boards that provide specialized hardware and peripherals. They are designed with interface pins protruding from their lower surfaces that mate with the connectors or headers on the Arduino board. Those digital connections that they need themselves they take, those that they don't or which can be shared with additional boards they connect to another set of headers on their top surfaces. The Ethernet shield adds an Ethernet RJ45 connector and supporting hardware to provide an Arduino board with a wired network connection. (There are shields which provide wireless connectivity as well; one step at a time.)
Here is a side view of my Arduino Uno board with the Ethernet shield plugged in on top of it. You can see the metal pins from the shield above plugged into the headers on the Uno below. A CAT5 Ethernet cable is connected to the shield on the left. Below that you can see USB and power cables connecting to the Uno below. Jumper wires are plugged into the headers on the shield.
The SparkFun Inventor's Kit for Arduino has all the hardware you need to do several projects that involve reading sensors. I chose to combine two of them, CIRC-09 ("Light") that uses a photoresister (a device whose resistance changes with the amount of light falling on it), and CIRC-10 ("Temperature") that uses a low voltage temperature sensor from Analog Devices. The TMP36 temperature sensor is a clever little device that is complex enough that its data sheet deserves a look.
Both of these devices can be hooked up to pins on the Arduino board and read using the analog-to-digital convertor built into the chip.
So what about the "remote" part? The Arduino software includes an Ethernet library that has a rudimentary internet protocol (IP) stack that, for example, responds to ping. The Ethernet library supports DHCP but I gave the board a static IP address on the local private network. The bulk of the IP stack is actually hard-wired firmware inside a Wiznet W5100 chip on the Ethernet shield, which is how an IP stack can be shoehorned onto a microcontroller with only two kilobytes of RAM. (Indeed, there are Arduino shields with processors more powerful than the Arduino microcontroller itself.)
On top of that I added the Webduino software that allows you to build a tiny web server that runs on the Arduino board. This web server, when interrogated by the browser on my Mac desktop, responds with a measure of the amount of light impinging on the photoresistor and the temperature in both Centigrade and Fahrenheit. Here's what it looks like, right from Safari.
I compute the light measurement to look like a percentage, but some calibration might be necessary for other applications. Still, it is easy for the software to tell the difference between night and day or whether room lights are on or off.
Here's a photograph of the entire fixture from above. The photoresistor is on the left and the temperature sensor is on the right on the breadboard, but they are so tiny that the jumper wires make them hard to see. Green, yellow and blue are sensor wires (the green one is merely reading the power bus), red are for power, and brown are for ground.
Here is the complete source code for my remote sensing project. It's not long. The underlying libraries do most of the work.
#include <SPI.h>
#include <Dhcp.h>
#include <Dns.h>
#include <Ethernet.h>
#include <EthernetClient.h>
#include <EthernetServer.h>
#include <EthernetUdp.h>
#include <WebServer.h>
#include <util.h>
#include <avr/pgmspace.h>
byte lightLevel = 0;
float temperatureCentigrade = 0;
float temperatureFarenheit = 0;
unsigned long then = 0;
void getIndexHtml(WebServer & server, WebServer::ConnectionType type, char * urlTail, bool tailComplete) {
server.httpSuccess();
if (type == WebServer::HEAD) { return; }
server.print("<h1>");
server.print(lightLevel); server.print('%');
server.print(' ');
server.print(temperatureCentigrade); server.print('C');
server.print(' ');
server.print(temperatureFarenheit); server.print('F');
server.print("</h1>");
}
static const int referencePin = 3;
static const int lightPin = 4;
static const int temperaturePin = 5;
void sense(unsigned long now) {
int referenceRaw = analogRead(referencePin);
int lightRaw = analogRead(lightPin);
lightLevel = 100 - ((100UL * lightRaw) / 1023);
int temperatureRaw = analogRead(temperaturePin);
float temperatureVolts = (temperatureRaw * 5.0) / 1023.0;
temperatureCentigrade = (temperatureVolts - 0.5) * 100.0;
temperatureFarenheit = (temperatureCentigrade * 1.8) + 32.0;
Serial.print(referenceRaw);
Serial.print(' ');
Serial.print(lightRaw);
Serial.print(' ');
Serial.print(lightLevel);
Serial.print(' ');
Serial.print(temperatureRaw);
Serial.print(' ');
Serial.print(temperatureVolts);
Serial.print(' ');
Serial.print(temperatureCentigrade);
Serial.print(' ');
Serial.println(temperatureFarenheit);
then = now;
delay(1);
}
void sense() {
sense(millis());
}
static const byte __attribute__((__progmem__)) myMacAddress[] = { 0x90, 0xa2, 0xda, 0x0d, 0x03, 0x4c };
static const byte __attribute__((__progmem__)) myIpAddress[] = { 192, 168, 1, 223 };
static const byte __attribute__((__progmem__)) myGatewayAddress[] = { 192, 168, 1, 1 };
static const byte __attribute__((__progmem__)) mySubnetMask[] = { 255, 255, 255, 0 };
WebServer webServer;
#define countof(_ARRAY_) (sizeof(_ARRAY_)/sizeof(_ARRAY_[0]))
#define flash2sram(_TO_, _FROM_, _COUNT_) byte _TO_[_COUNT_]; memcpy_P(_TO_, _FROM_, sizeof(_TO_))
void setup() {
Serial.begin(9600);
flash2sram(tempMacAddress, myMacAddress, countof(myMacAddress));
flash2sram(tempIpAddress, myIpAddress, countof(myIpAddress));
flash2sram(tempGatewayAddress, myGatewayAddress, countof(myGatewayAddress));
flash2sram(tempSubnetMask, mySubnetMask, countof(mySubnetMask));
Ethernet.begin(tempMacAddress, tempIpAddress, tempGatewayAddress, tempSubnetMask);
webServer.setDefaultCommand(&getIndexHtml);
webServer.addCommand("index.html", &getIndexHtml);
webServer.begin();
sense();
}
void loop() {
unsigned long now = millis();
if ((now - then) > 1000) {
sense(now);
}
webServer.processConnection();
}
As is typical, I made this a little more difficult than it needed to be for pedagogical reasons.
To assist in debugging, I displayed a lot of the partial and final results over the serial port that is part of the ATmega328 microcontroller. This travels back over the USB cable to my desktop where it can be displayed using the serial monitor tool that is part of the Arduino development environment. Serial is a C++ reference to a pre-built object in the Arduino run-time system that provides an API to do this.
When all of your storage for volatile variables has to fit in two kilobytes of SRAM, it pays to put data that doesn't change somewhere else. You'll notice that, for example, the hard-coded IP addresses all have a mysterious __attribute__((__progmem__)) as part of their definitions. The __attribute__ keyword is a special directive to GNU C++ that can be used to communicate special implementation details to the compiler. The __progmem__ argument, specific to this family of microcontrollers, tells the compiler that the variable to which it is applied resides not in volatile SRAM but in non-volatile program memory, meaning the flash memory where executable code is normally stored persistently.
Access to program memory on this microcontroller has to be done through a different machine instruction, load program memory (lpm), than when accessing data memory (SRAM). If you are into this kind of thing, you will now realize that the ATmega328 is an example of a Harvard architecture, one in which data and instructions are stored and processed separately. The lpm instruction bridges these two worlds by allowing you to copy from program memory into temporary variables in data memory. (A von Neumann architecture is one in which data and instructions are stored in the same memory and may be accessed similarly to one another.)
The Arduino and underlying microcontroller software define C/C++ macros (for example, PROGMEM), data types (prog_char), and functions (memcpy_P), that simplify using this mechanism. But I coded the __attribute__((__progmem__)) explicitly because the use of the GCC __attribute__ directive is so common when developing close to bare metal, and for similar reasons, that I knew that many embedded developers reading this would immediately see something they recognize.
In the loop function, the main work loop, I only interrogate the sensors once a second. The call to processConnection looks to see if a web request has arrived, and if so processes it. The only request I've defined is an HTML GET for the web page index.html. Querying that web page on Arduino returns the latest sensed values.
It's pretty impressive what can be done within the resource footprint of a tiny microcontroller. This program takes a little more than half of the thirty-two kilobytes of flash, and a little less than a quarter of the two kilobytes of SRAM not including the stack. That's without much optimization on my part.
Small is beautiful.
4 comments:
You just recruited a new follower. Thank you for sharing knowledge with us. I'm a Computer Engineering Senior who's learning to love Arduino and trying to find out what all I can do with it.
Hi. I’m peter employee of wiznet.
We are looking for a project our IC is used. And we introduce them on our website.
If you agree, I want to introduce "Remote Sensing with Arduino" project on our site.(http://wiznetmuseum.com/)
I'll wait for your reply.
Hi. I’m peter employee of wiznet.
We are looking for a project our IC is used. And we introduce them on our website.
If you agree, I want to introduce "Remote Sensing with Arduino" project on our site.(http://wiznetmuseum.com/)
I'll wait for your reply.
You may introduce this article to your web site. Thanks!
Post a Comment