Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Emulating Non-DOS Systems Under MS-DOS


MAR94: Emulating Non-DOS Systems Under MS-DOS

Emulating Non-DOS Systems Under MS-DOS

Creating a development environment for working within the familiar confines of DOS

Dan Troy

Dan is a software engineer at Granite Communications Inc., 9 Townsend West, Nashua, NH 03063. He can be reached at 603-881-8666, or by fax at 603-881-4042.


One of the challenges embedded-systems designers have grappled with for years is developing for operating systems that require unfamiliar, nonstandard development tools. Coincidentally, this is the problem that is now confronting many programmers writing software for the emerging class of hand-held, wireless, personal-data communications devices such as the Newton MessagePad from Apple, Zoomer from Casio, and, in this case, my company's VP5 personal digital-communication device. Still, history has taught us that:

  • No matter how slick it is, a hardware device won't be accepted by users if there's no software for it.
  • Programmers won't write software for a hardware device unless the development environment is at least approachable.
Realizing this, we wanted to create a development environment for the VP5 that would allow programmers to work within the familiar confines of MS-DOS using DOS-based tools. Furthermore, we wanted programmers to test applications under MS-DOS before loading the cross-compiled program into the VP5. Just to complicate matters, we also needed to develop a simple mechanism for adding new operating-system function-emulation capability as existing products are enhanced, or as new products are developed. This article presents the solution we devised for the VP5 development environment.

Our approach to remotely executing an application isn't specific to the VP5, however. You should be able to apply it to any similar situation where noncompatible operating systems exist. Also, since the emulation code is written in ANSI C, it is portable to any other microprocessor/operating-system environment, as long as the necessary hooks exist between the development platform and the remote communication device. In addition to this, contained in the emulation code is the simple, table-driven function-execution mechanism, which is applicable to any other remote-execution environment written in C.

Our Solution

The VP5 is a wireless, hand-held, touchscreen personal data communicator (see Figure 1) designed to allow VARs and programmers to develop custom touchscreen applications written in C. To emulate as many videopad functions as possible--including the touchscreen and display--we decided to remotely execute the proprietary videopad operating-system functions from MS-DOS on the PC. The developer could then see the VP5 screen exactly as it appears when the application is cross-compiled, linked, loaded, and executed in the VP5 itself. Also, the developer could actuate developer-defined, touchscreen-sensitive keys to make sure the application did what it was expected to do. This gives WYSIWYG feedback in the development process. To accomplish this goal, we remotely commanded the VP5 via the serial ports on the PC and VP5. In order to allow easy emulation expandability for future enhancements and portability to new products, we developed a table-mapping mechanism that would handle C operating-system functions and their associated parameters.

The overall emulation logic is summed up in Figure 2. To implement all 89 videopad operating-system calls, we implemented a table-driven mechanism. The table is defined as an array of structures of type shown in Listing One (page 58). Listing Two (page 58) shows a portion of the table. According to the table, the inputs to the define_key() function are character, integer, character, character, character, character, character, string. There are no outputs (NULL string). This corresponds to the function prototype in Listing Three (page 58). The execution sequence to remotely define a key on the VP5 keyboard would be like that in Listing Four (page 58).

On the PC side, the VP5 operating-system library function looks like Listing Five (page 58) which calls process_cmd(); see Listing Six (page 58). This, in turn, sets up and sends a message to the special VP5 emulation application and receives the response from the VP5.

The counterpart of the define_key() function resides on the VP5. It uses the arg_table[] identical to that on the PC-emulation software. Listing Seven (page 60) is the VP5 emulation-command processor.

The spawn_define_key() function is a spawned function located in the arg_table[]; see Listing Eight (page 62). No return values must be passed to the define_key() function in the customer application. However, in get_key(), the videopad must return the key value pressed on the videopad. In this case, the VP5 operating-system library function looks like Listing Nine (page 62).

Since the get_key() function has no inputs, none are required to set up the call to process_cmd(). The actual key pressed on the videopad is in c[1]. But why are there two outputs--c[0] and c[1]--in the get_key() table entry, when there's only one output from get_key()? Because in this case, get_key() on the videopad will wait forever for a key to be pressed under normal operation. If the application-program developer decides to take his or her time pressing a key after get_key() has been called, then the emulation software on the PC will eventually time out. To avoid this and allow the developer to spend as much time as he or she desires before pressing a key on the videopad, we implemented the algorithm in Listing Ten (page 62) on the VP5. Here, the spawned function spawn_get_key() is found in the arg_table[]. If no key has been pressed, the function immediately returns a False condition for c[0]. The emulated get_key() function on the PC loops until a key is pressed, thus maintaining constant communication between the PC and the VP5.

One drawback of executing the application remotely is that the VP5 RS-232 port is not available for the application since it's being used by the emulator. In order to emulate the VP5 RS-232 COMserial-port communications within the emulated application, the other RS-232 port on the PC, COM2, is used. In other words, data that would normally be transferred on the VP5 COMport is instead transferred via COM2 on the PC, since the VP5 COM port is being used for application emulation. We created a special cable so that the VP5 COM port is now represented by p4 (see Figure 3). The p4 connector is the same as the COMport on the V5.

A Sample Program

Listing Eleven (page 62) is a typical VP5 program in which the touch-sensitive, boxed key appears on the VP5 and the VP5 waits until the defined key is pressed. When the defined key is activated by the user touchscreen, the screen clears, and the message displayed in the WELCOME_KEY is replaced with the "Requesting Info" message. The VP5 then logs on to the base station, sends a message to the host, and waits until a message is received back from the host. Then the received message is displayed in the defined text window on the VP5 screen for ten seconds. This sample program can be tested under CodeView, utilizing its full functionality and incorporating DOS calls such as printf() while debugging.

Figure 1: Non-DOS VP5 handheld device.

Figure 2: Flow of control in VP5 emulation environment.

Figure 3: VP5 RS-232 COM serial-port configuration.

[LISTING ONE]


typedef struct args
{
  unsigned char *input;     /* string of inputs to function */

  unsigned char *output;    /* string of outputs from function */
  void (*spawn_function)(); /* emulated VP5 OS function */
};


[LISTING TWO]



const struct args arg_table[] =
{

/* COMMAND   RESPONSE      SPAWN           COMMAND */
/*   ARGS      ARGS       FUNCTION          OFFSET */

  "cicccccs", "",     spawn_define_key,        /* 0 */
  "cs",       "",     spawn_display_key_label, /* 1 */
  "ccc",      "",     spawn_display_window,    /* 2 */
  "",         "cc",   spawn_get_key,           /* 3 */
  "c",        "c",    spawn_rf_close,          /* 4 */
  "cic",      "cc",   spawn_rf_open,           /* 5 */
  "i",        "cis",  spawn_rf_read,           /* 6 */
  "is",       "c",    spawn_rf_write,          /* 7 */
  "",         "",     spawn_turn_off,          /* 8 */
  "",         "",     spawn_wait_1_sec,        /* 9 */
};


[LISTING THREE]



  void define_key(char key_name, int key_attributes, char
    start_row, char start_col, char end_row, char end_col,
    char font_table, char *key_label).


[LISTING FOUR]



#define WELCOME_KEY 'W'
 .
 .
 .
define_key(WELCOME_KEY, BOX|SENSITIVE, 'A', '1', 'B', '8',
            STANDARD_FONT,
           "WELCOME to GRANITE'S VP5\nPRESS HERE to BEGIN");
 .
 .
 .

[LISTING FIVE]



void define_key(char key_name, int key_attributes, char
    start_row, char start_col, char end_row, char end_col,
    char font_table, char *key_label)
{
     c[0] = key_name;
     i[0] = key_attributes;
     c[1] = start_row;
     c[2] = start_col;
     c[3] = end_row;
     c[4] = end_col;
     c[5] = font_table;
     strcpy(s[0], key_label);
     cmd = DEFINE_KEY;
     process_cmd();
}


[LISTING SIX]



void process_cmd(void)
{
   .
   .
   .
   /* frame up command to send to videopad */
   while(element_type = arg_table[cmd].input[element++])
   {
      switch(element_type)
      {
         /* argument type of character */
         /* pack it into global cmd_buff via pack_char() */
         case 'c': pack_char(c[c_cnt++]);
                   cmd_buff_length++;
                   break;
         /* argument type of string */
         /* pack it into global cmd_buff via pack_char() */
         case 's': str_length = 0;
                   do
                   {
                      temp_char = s[s_cnt][str_length++];
                      pack_char(temp_char);
                   }
                   while(temp_char);
                   s_cnt++;
                   cmd_buff_length += str_length;
                   break;

         /* argument type of integer */
         /* pack it into global cmd_buff via pack_char() */
         case 'i': pack_char((char)(i[i_cnt] >> 8));
                   pack_char((char)(i[i_cnt++] & 0xff));
                   cmd_buff_length += 2;
                   break;
      }
   }
   /* send framed up function command to videopad */
   if(link_layer_send(cmd_buff, cmd_buff_length) !=
            TX_OK) process_fatal_error(NO_LINK_RESPONSE);
   /* look for link layer response from videopad - time out
         if none is found within expected time */
   while((rx_status = link_layer_received()) != READY ||
              rx_status != TIMED_OUT);
   if(rx_status == TIMED_OUT) process_fatal_error(TIMED_OUT);
   /* process response from videopad */
   .
   .

   .
   /* if first byte of response buffer (status) is
        not a status command character, then process error */
   if(response_buff[0] != RESPONSE_STATUS_COMMAND)
        process_fatal_error(NON_STATUS_RETURNED);
   while(element_type = (arg_table[cmd].output[element++]))
   {
      switch(element_type)
      {
         /* argument of type character */
         /* unpack it and put into global c[] array */
         case 'c': c[c_cnt++] = unpack_char();
                   break;
         /* argument of type string */
         /* unpack it and put into global s[][] array */
         case 's': str_length = 0;
                   while(((s[s_cnt][str_length++] =
                     unpack_char()) != 0) && (str_length <=
                        MAX_STRING_SIZE));
                   break;
         /* argument of type integer */
         /* unpack it and put into global i[] array */
         case 'i': i[i_cnt] = (int)(unpack_char() << 8);
                   i[i_cnt] |= (int)unpack_char();
                   break;
      }
   }
   .
   .
   .
}

[LISTING SEVEN]



void main(void)
{
   int status;
   init_link_layer();
   for(;;)
   {
      /* look for link layer command from PC - time out if
         none is found within expected time */
      while((rx_status = link_layer_received()) != READY ||
              rx_status != TIMED_OUT);
      if(rx_status == TIMED_OUT)
              process_fatal_error(TIMED_OUT);
      /* process command from PC */
      /* first byte of command message is command */
      cmd = cmd_buff[0];
      while(element_type = (arg_table[cmd].input[element++]))
      {
         switch(element_type)
         {
            /* argument type character */
            /* unpack it and put into global c[] array */
            case 'c': c[c_cnt++] = unpack_char();
                      break;
            /* argument type string */
            /* unpack it and put into global s[][] array */
            case 's': str_length = 0;
                      while(((s[s_cnt][str_length++] =
                        unpack_char()) != 0) && (str_length
                          <= MAX_STRING_SIZE));
                       break;
            /* argument type integer */
            /* unpack it and put into global i[] array */
            case 'i': i[i_cnt] = (int)(unpack_char() << 8);
                      i[i_cnt] |= (int)unpack_char();
                      break;
         }
      }
      /* look up spawning function corresponding to cmd */
      function_address = arg_table[cmd].spawn_function;
      /* execute spawning function */
      (*function_address)();
      /* prepare response message to last received cmd */
      response_buff[0] = STATUS_OK;

      /* frame up command to send to videopad */
      while(element_type = arg_table[cmd].input[element++])
      {
         switch(element_type)
         {
            /* argument type of character */
            /* pack it into global response_buff via
                  pack_char() */
            case 'c': pack_char(c[c_cnt++]);
                      response_buff_length++;
                      break;
            /* argument type of string */
            /* pack it into global response_buff via
                  pack_char() */
            case 's': str_length = 0;
                      do
                      {
                         temp_char = s[s_cnt][str_length++];
                         pack_char(temp_char);
                      }
                      while(temp_char);
                      s_cnt++;
                      response_buff_length += str_length;
                      break;
            /* argument type of integer */
            /* pack it into global response_buff via
                  pack_char() */
            case 'i': pack_char((char)(i[i_cnt] >> 8));
                      pack_char((char)(i[i_cnt++] & 0xff));
                      response_buff_length += 2;
                      break;
         }
      }
      /* send framed up function command to videopad */
      if(link_layer_send(response_buff,
           response_buff_length) !=
               TX_OK) process_fatal_error(LINK_ERROR);
   }
}



[LISTING EIGHT]



void spawn_define_key(void)
{
  define_key(c[0], i[0], c[1], c[2], c[3], c[4], c[5], s[0]);
}









[LISTING NINE]



char get_key(void)
{
   do
   {
      cmd = GET_KEY;
      process_cmd();
   }while(!c[0]);

   /* return actual key pressed on videopad here */
   return(c[1]);
}



[LISTING TEN]



void spawn_get_key(void)
{
     if(key_pressed())
     {
        c[0] = TRUE;
        c[1] = get_key();
     }
     else c[0] = c[1] = FALSE;
}



[LISTING ELEVEN]



/* special vp5 library header files */
#include <key.h>
#include <times.h>
#include <control.h>
#include <rf.h>
#include <display.h>

#define WELCOME_KEY          'W'
#define BASE_STATION_NUMBER  7
#define PERIOD               30

char *msg_to_send = "Please send me the info";
unsigned int unsol_size;

unsigned char unsol_msg[2048];

void main(void)
{
   unsigned char key;
   int i;
   unsigned char remote_number;
   unsigned int msg_length;
  /* define a boxed, touch sensitive key on rows A and B of
        the VP5 screen */
   define_key(WELCOME_KEY, BOX|SENSITIVE, 'A', '1', 'B', '8',
      STANDARD_FONT, "WELCOME to GRANITE'S VP5\n"
                     "PRESS HERE to BEGIN");
   /* wait until a sensitive VP5 key is touched */
   key = get_key();
   /* replace message in WELCOME_KEY with a new message */
   display_key_label(WELCOME_KEY, "Requesting Info\n"
                      "Waiting for response");
   /* log on to base station via RF */
   rf_open(BASE_STATION_NUMBER, PERIOD, unsol_msg,
            &unsol_size, unsol_handler, &remote_number);
   /* send the message via RF to the host */
   rf_write(strlen(msg_to_send), msg_to_send);
   /* wait for the response from the host */
   rf_read(&msg_length, msg_rcvd);
   /* display response from host on VP5 screen from line 3
           through line 12 */
   display_window(TEXT_WINDOW, 3, 12);
   for(i = 0; i < msg_length; i++) display_char(msg_rcvd[i]);
   /* log off the base station via RF */
   rf_close(BASE_STATION_NUMBER);
   wait_1_sec(10);     /* display rcvd msg for 10 seconds */
   turn_off();        /* shut off VP5 */
}


Copyright © 1994, 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.