Channels ▼

Embedded Systems

Building Intelligent, Web-Based Control Systems

Tom Milligan and Steve Coffin

, March 01, 1998

Source Code Accompanies This Article. Download It Now.

Dr. Dobb's Journal March 1998: Building Intelligent, Web-Based Control Systems

Embedding the EMIT web server

Tom and Steve are engineers at emWare Inc. They can be reached at

As engineers living in an arid climate, we've always been annoyed by "dumb" sprinklers that come on at ridiculous times, such as in the middle of a hot, sunny day when much of the water is lost to evaporation, or while it's raining and totally unnecessary. Sprinklers have primitive interfaces and are limited by when, where, and how they can be programmed.

Therefore, we decided to automate a sprinkler system using the Embedded Micro Internet Technology (EMIT) toolkit from emWare (the company we work for). EMIT is a toolkit for embedding web servers on devices, and developing web-based user interfaces. In this article, we'll describe our system design, explain how we built the device, and show how we developed the interface.

EMIT Background

EMIT includes five modular software components:

  • emMicro, a compact, special-purpose server.
  • emNet, a message-based protocol that combines packet and stream interchange.
  • Microtags, preprogrammed packets that define device controls such as switches, buttons, LEDs, and so forth.
  • emGateway, which expands microtags into their full parameters.
  • emObjects, a library of JavaBean components consisting of visual and utility objects.

These components work together to dynamically create the user interface for an electronic device. Figure 1 shows the EMIT architecture. When a user requests the device interface from a web browser, the browser sends the request to emGateway. emGateway then translates the high-level user request and sends it to emMicro at the device.

emGateway requests from emMicro an HTML page containing "Microtags," condensed tags that contain the information necessary to represent a specific device control (a slider, button, LED, or whatever). Condensing this information improves the space, resource, and bandwidth requirements of the device.

After emMicro returns the HTML page with Microtags, emGateway substitutes each Microtag with a corresponding emObject. emObjects are JPEG/GIF images or Java applets that represent device controls to the browser. The page with the substituted emObjects is then sent to the browser.

EMIT components process user requests to view and set device information, dynamically representing the results to the user from within the browser at the desktop. For example, users may ask to flip a switch from "off" to "on." Once a user makes the request, the browser invokes emGateway. Then, emGateway sends the request to emMicro at the device.

When emMicro receives the request, it causes the device's microcontroller or microprocessor to perform the request (flip the switch). Because device information has changed, the state of the embedded device changes, which will result in a state-change message being sent to any interface emObject (component) monitoring affected variables.

The emMicro web server is about one KB, and the sprinkler controller application is about 900 bytes. If we had to build physical interface logic onto the device (the LCD, buttons, and so on), an additional two to four KB of program space would have been required. The majority of the programming, however, is done on the client side, so hardware resource requirements are kept to a minimum.


The ideal sprinkler system (see Figure 2) would let you sit down at a computer and water the lawn from a Java-enabled GUI -- at anytime, from anywhere. In addition, the system should be able to water proportionately; that is, less in spring when it's raining all the time anyway, and more in the dog days of July and August.

Our system could also check the National Weather Service's web site for precipitation information. That way, you could program your sprinklers to come on or stay off based on one-, three-, or five-day forecasts. Links to temperature sensors and/or moisture probes might also be available as alternatives or in addition to National Weather Service information. Either way, you could easily program your sprinklers to water in the predawn hours before hot days of continuous sunshine, and not to water when rain is the order of the day. When your computer is turned off and the sprinkler system isn't able to access either the Internet or stored information concerning soil moisture, the system simply defaults to its regular schedule.

On days when the predictions are wrong, you can pull up the sprinkler interface and reset the sprinklers accordingly. The sprinkler interface allows for customized usage in other ways. For example, you can turn off the system for a few hours or a few days, or to accommodate a hard, driving rain. Also, forgetful users don't have to reset the system to prevent burning their lawns; the sprinklers have enough intelligence to turn back on after the allotted time according to their regular schedule.

Building the Device

We built the intelligent sprinkler system using about $15.00 worth of garden-variety components. Figure 3 is the schematic for the device. The microcontroller is the AT89C2051, a 20-pin 8051 derivative from Atmel with two KB of program space. (For more information on programming AT89C2051, see "Atmel's AT89C2051 Microcontroller," by Dhananjay V. Gadre, DDJ, July 1997; available electronically, see "Resource Center," page 3.) We installed an RS-232 transceiver to provide external communications. We wrote some code to build a real-time clock and calendar, and an alarm clock that would sequence throughout the program and manage all the various watering start times.

We included the emMicro module to provide a remote interface for manually programming the system and customizing the watering schedules. We included a serial EEPROM device to store interface documents and program times in a nonvolatile array, just to make sure that if the device loses power, it will retain essential information. In addition, we included a nine-volt battery as a power backup, to keep the clock running in the event of a power outage.

All we needed on the board was the microcontroller and the hardware necessary to allow it to turn on 24-volt AC devices. We used a 74hc138 decoder to allow eight valves to be controlled using three lines from the microcontroller. And we made provisions on the schematic for an additional decoder that can provide control for as many as 16 valves.

Figure 4 shows the programming logic for this device. The real-time clock/calendar for the sprinkler controller was implemented completely in software (see Listing One) and uses as its time base the microcontroller's 18.432MHz crystal. The clock has two main software components: an interrupt-service routine, and the clock routine itself. The 8051's hardware timer 0 is used to execute the ISR once every ten ms. The ISR must reset the hardware timer for the next interrupt and account for interrupt latency, which would cause the clock to drift off. Unfortunately, the 8051 architecture doesn't support a 16-bit auto-reload mode, which would make it unnecessary to correct for interrupt latency; fortunately, this is not hard to correct.

Because t0lo is small (zero), there is no real possibility of a rollover when the addition is done, and therefore, no reason to account for a carry into the upper portion of the counter. The most important thing the ISR does is to set the bit variable called temMSbit. This is used to tell the real-time clock routine that a time tick has taken place.

The other software component of the clock is the routine responsible for counting all the ticks created by the ISR. Basically, the routine just counts up hours, minutes, seconds, and days, and sets a few bits here and there to tell other routines when to operate. For instance, once every minute, the bit variable CheckWater is set, so that the alarm-clock routine knows to check all the scheduled watering times to see if any are scheduled to begin watering. There are two-day counters, one used for watering schedules based on the days of the week (Monday, Tuesday, Wednesday, and so on), and the other for periodic watering (every other day, every third day, every fourth day, and so on).

The counting routine (Listing Two) is called by the main calling loop in a round-robin multitasking methodology. It is called much more frequently than the ten-ms rate at which the timer ISR takes place, so it is sure not to miss a tick. Whenever the ISR sets the tenMSbit, the clock routine increments its clock registers as necessary. It then clears tenMSbit so that it doesn't respond more than once to each tick.

Programming the Controller

There were three main steps involved in integrating the emWare software into our application:

  1. Include the emMicro code module in the application.
  2. Create the resource tables.
  3. Insert calls into the entry points of emMicro.

emMicro is usually included by placing an include statement after the application code. This tells the assembler or the compiler to include the emWare code when building the executable.

The resource tables are where all of the information about the application is specified for the browser interface. Creating the resource tables really means editing the example tables included with EMIT to match the application's needs. For example, the sprinkler controller primarily uses the Variable Table to make a large number of variables -- such as Hrs, Min, and Sec -- visible to the web-browser interface. These tables tie the browser interface to the microcontroller application. Listing Three presents a small section of the Variable Table.

The structure of each table is similar. The first byte is the number of elements in the table. Our sprinkler controller uses 37 variables, so the first byte of the sprinkler controller's variable table is 37. Next, there are two bytes to describe the type of each variable (byte or word), and whether the variable is read only or read/write. Finally, the variable names are placed into the table in the form of NULL-terminated strings. There are other tables that specify Events, Functions, Static Documents, and Capabilities. Once these tables are created, they are included at the end of the application source code either by using include statements or by simply directly including the tables.

Placing calls to the emMicro entry points is straightforward, as there are only two, but there are some rules applications have to obey. One entry point is the serial port interrupt vector entry. emMicro has its own serial ISR, but the vector has to be installed into the microcontroller's main vector table. The other entry is the main emMicro entry (called emMicroEntry).

For an application to coexist with emMicro, the application must be written using a cooperative multitasking methodology. In other words, each process, including emMicro, is called by a main calling loop, and each process must respect all the others by releasing the process control back to the calling loop in a timely fashion. Just what "timely" means depends on how often each process really needs to have the attention of the processor. emMicro needs to be called at least often enough to receive each character from the serial port.

In any case, no process should ever stall the processor in a tight loop waiting for some event to happen. That's what interrupts are for. The only other rule is that no process should get exclusive use of the CPU registers. Each process can use them without regard to their previous state. Most experienced programmers abide by these rules anyway. Listing Four is the main calling loop for the sprinkler controller.

There are several advantages to designing the application in the aforementioned manner. For instance, the clock is set from the browser; not a single line of code in the microcontroller application has to be concerned with how the clock is set. Also, all the configuration parameters are adjusted without any code in the microcontroller. Forcing the browser to display new information (or fancy graphics) is as easy as changing the value of a variable in a resource table. If the microcontroller application calls for a special function to be executed when a button is pushed on the browser interface, you only need to write the code for the function; emMicro takes care of executing the function for you.

Developing the Interface

Little code was needed to interface to the controller, since EMIT provides prebuilt emObject interface components and handles the communication details. To further simplify our task, we used Symantec's Visual Café, and integrated our emObject components with their existing component toolkit. This way, we could select the objects from the component palette, add them directly to the applet, and easily manipulate layout and component properties.

The heart of the applet is an invisible object called emitjri, which is the Java Runtime Interface for EMIT. Example 1 creates Sprinkler1, a new emitjri object configured to communicate with our sprinkler controller, which has a Manufacturer ID of four and a Device ID of 11. All emObjects are passed a reference to the emitjri object. This allows emObject events to directly set variables on the embedded controller and allows changes in embedded variables to change emObject properties directly. emitjri also contains function calls that allow the applet to directly set and get embedded variable values, and also allow it to register a callback to be notified when an embedded variable value changes.

An example of an emObject directly controlling an embedded variable is our watering on/off image switch. This object displays one of two images depending on its state, which is active or inactive. Example 2 establishes the connection between the image switch waterOnOff and the embedded variable Water. This means that whenever an ActionEvent occurs (when the switch is pressed and released), the event value (True or False) is used to set the embedded variable Water.

The embedded controller clock is a good example of communicating variable state change to the display applet. The embedded variables Hrs, Min, and Sec change regularly, and these changes need to be relayed to the applet. Example 3(a) sets up the callbacks for the embedded variables. Whenever the variable values change, the function notifyChange, which is defined in the object this (the applet), is called with the variable name and the new value; see Example 3(b). Similarly, we could have omitted the notifyChange callback for Sec by setting a direct link between the Sec variable and the emObject timeDisplay, with the function call in Example 3(c).

Since emitjri handled our communications requirements, the majority of our coding effort was spent defining event-handler functions. Whenever a button is pressed, text is entered, list items are selected, checkboxes are checked, and so on, events are generated. The event handling functions for 24/12 hour clock, am/pm, and day select are characteristic of many of the event-handling functions that we had to create. Listing Five presents these functions.

We built many features into the user interface that could not easily be accomplished with a hardware solution, or by defining the interface completely within the embedded controller. This expanded the size of our applet (approximately 75 KB), but reduced the controller and support interface hardware costs while increasing flexibility. The end result is a Java class file that we could compress onto a 32-KB EEPROM and serve from the device. We could also let emGateway serve the client the prestaged class file along with the standard set of preinstalled emObjects. Figure 5 shows the completed user interface.

For More Information

1225 E. Fort Union Blvd., Suite 200
Midvale, UT 84047


Listing One

;***************************************************************************;************************* Timer0 Interrupt Handler ************************
; emWare doesn't use or require the use of the timer interrupt.
; System clock is 18.432Mhz
; One machine cycle is 1/12 system cycle or .65104us
; Timer 0 counts up once every machine cycle.
; Timer will fire an interrupt when it counts up and rolls over to 0.
; We want a 10 ms interrupt rate .010/.65104 = 15360 machine cycles.
;                                             = 3c00 hex
;                                             10000 - c300 = c400
; So t0hi = 0xc3 and t0lo = 0
INT_T0          push    PSW                
                push    ACC                
                mov     TH0,  #t0hi   ;Load timer reload register
                mov     A, #t0low          
                add     A, TL0        ;Account for interrupt latency
                mov     TL0, A        ;by adding preload value current value
                setb    tenMSbit      ;set this bit once every 10ms
                setb    GreenLED      ;Turn off green LED
                pop     ACC
                pop     PSW
                reti                   ;Return from Interrupt

Back to Article

Listing Two

;********************************* Real Time Clock ***********************;Once every 10ms the timer ISR sets the tenMSbit.
;use this routine to update the real time clock
              jnb   tenMSbit,RTCdone     ;Just exit if temMSbit isn't set
              clr   tenMSbit             ;only count the tick once
              inc   Hundredths           ;100 ticks to 1 second
              clr   A
              mov   R1, Hundredths
              cjne  R1, #100, RTCdone    ;Has one second elapsed?
              mov   Hundredths, A
              inc   Seconds              ;Yes increment seconds
              setb  waterbit             ;
              setb  ManSecBit
              mov   R1, Seconds
              clr   GreenLED             ;Turn on green LED
                                         ;ISR will turn it off in 10ms
              cjne  R1, #60, RTCdone     ;Has one minute elapsed?
              mov   Seconds, A
              inc   Minutes              ;Yes, increment minutes
              mov   R1, Skip
              cjne  R1, #0, RTCskp       ;Don't check if Skip is not zero
              setb  CheckWater           ;See if it is time to start watering
RTCskp        mov   R1, Minutes
              cjne  R1, #60, RTCdone     ;Has one hour expired?
              mov   Minutes, A
              inc   Hours                ;Yes, increment hours
              mov   R1, Hours
              cjne  R1, #24, RTCdone     ;Has one Day expired?
              mov   A, Skip
              jz    RTCnoskip            ;is the skip watering variable zero?
              dec   Skip                 ;decrement skip-watering variable
              clr   A
RTCnoskip     mov   Hours, A
              inc   Days
              mov   R1, Days             ;Has one week expired
              cjne  R1, #7, RTCmod
              mov   Days, A
              inc   DayMod               ;Increment modulus 120 day counter
              mov   R1, DayMod           ;Have 120 days expired?
              cjne  R1, #120, RTCdone
              mov   DayMod, A

Back to Article

Listing Three

;*** variable table send in response to GetVarSymbols request from host ****EMvartable      .byte  37                 ;= # of variables
                .byte  0                  ; ATTRIBUTE byte for each variable
                .byte  BYTETYPE+READWRITE ; TYPE byte for each variable 
                .byte  0
                .byte  BYTETYPE+READWRITE   
                .byte  0
                .byte  BYTETYPE+READWRITE   
                    . . .
                .text "water\000"                
                .text "DayMod\000"                
                .text "Days\000"   ;Null terminated string for each variable
                .text "Hrs\000"                
                .text "Min\000"                
                .text "Sec\000"
                    . . .

Back to Article

Listing Four

;************************** Main Idle Loop *******************************; Cooperative multitasking main calling loop. Each process should release the 
; processor whenever possible. No stalling allowed. User and em processes
; are called from here. On processors with more than 2K program memory, the 
; acall's should be replaced with lcalls. emMicroEntry must be called 
; once per character tx time (at 9600 baud this is approx once per 960us )

         acall   emMicroEntry  ;Emware app layer also calls Link layer
         acall   debug         ;general purpose debugging stuff
         acall   RealTimeClock ;Update the RTC when necessary
         acall   Water         ;Water the grass when appropriate
         acall   CheckTime     ;Check if its appropriate to water
         acall   ManualCntrl   ;allow for manual control
         ajmp    main          ;loop forever

;********************** Main Idle Loop End *******************************

Back to Article

Listing Five

    // Select 24 hour clock modevoid hr24RadioButton_Action(Event event) {
    // get the value of the embedded variable "State"
      Object object = emJri.getJSJriVariable("State"); 
          int state;
        // disable am/pm buttons
      isClock12 = false;
      state = ((Integer)object).intValue();
/* instead of using a separate variable (wasted resource) to hold the clock 
   mode set bit 0 of the "State" variable to indicate 24 hour clock mode.  */
new Integer(state | 0x0001));
      set_hours(); // update clock
    // change the clock to am
    void amRadioButton_Action(Event event) {
/* reset the embedded variable "Hrs." Device runs in 24-hour mode so simply 
subtract 12 from the current hour. Since there is a JriLink to this 
variable "notifyChange" will be called and complete the clock update */
        emJri.setJSJriVariable("Hrs", new Integer(hours-12));
    // set the current day
    void dayChoice_Action(Event event) {
            int day = dayChoice.getSelectedIndex();
/* set the current day from 0 - 6 depending on the selected choice item */
            emJri.setJSJriVariable("Days", new Integer(day));

Back to Article

Copyright © 1998, Dr. Dobb's Journal

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.