Building Embedded Systems With Turbo C

Pat Villani


Pat Villani received his B.S.E.E. from Polytechnic Institute of Brooklyn and his M.S.E.E. from Polytechnic Institute of New York. Pat has developed applications ranging from avionics and guidance to computer peripherals. He is president of Network Software Systems, Ltd., which specializes in C applications and embedded control systems. Readers may reach him at (908) 206-0320.

Embedded systems have traditionally represented a specialized area of computer science and electrical engineering, an application where both disciplines merge. System design often requires tradeoffs between programming convenience and system cost. Quite often, code produced for embedded systems is created by both software and hardware engineers.

The first microprocessors used in embedded systems implemented a lot of code in assembly langauage because of the type of software development packages available for these processors. These packages were often expensive and required the manufacturer's development system or a large machine to build code. Only large corporations could afford these programming platforms. As microprocessor capabilities increased, manufacturers introduced programming langauges geared toward embedded systems. These new languages were limited to the same platforms, so the trend of limited embedded system development continued.

Concurrently, great strides were made in desktop computers. These computers, based on the same microprocessors that were used in embedded systems, were capable of running applications and small software development packages that targeted these machines. Further developments in desktop computers enabled the developers of these packages to include more advanced features. The result: powerful langauges such as the Borland Turbo languages and Microsoft languages that include debuggers and integrated development environments that allow rapid development of applications.

Today, low-cost clone motherboards, hybrids, and single chip computers based on XT and AT architectures exist. When combined with low-cost peripherals, these motherboards and devices present a unique opportunity to system developers. Low-cost and small-volume controllers, data loggers, and specialized processors are feasible. Adapting langauges such as Turbo C to produce code for these environments makes these designs even more affordable.

Basic Principles

Compilers such as Borland Turbo C generate efficient code that is independent of any operating system. What ties the compiler to an operating system or any environment is the system startup code and the libraries that are linked to produce the executable object file. Replacing these files with new files specific to embedded systems allows the code to work in an embedded system.

The first file you must replace is the startup file. Turbo-C uses one of the files COx. OBJ (where x is T, S, L, etc., depending on the memory model used) found in \TC\LIB. This file transforms the unique parameters of MS-DOS passed at startup into the conventional C environment argc, argv, envp, and errno. The file also sets up the stacks and aligns the segments of memory.

The next file that you must replace is the corresponding flavor of \TC\LIB\Cx.LIB. This file contains all the operating system calls, such as read and write. It also contains library functions such as strcpy and tolower. Our approach is to replace this file with a new library emulating these calls.

Design Advantages

Rapid development is a key factor in today's market. This approach helps the system developer achieve this goal by reducing development time. The project is developed using the Turbo C integrated environment or Turbo Debugger. When the developer is satisfied with the code's functionality, the code is recompiled and linked using make files and the command line environment. A commercial or public domain locater is then used to create an EPROM for the target system.

The project is quickly debugged in a convenient environment in which code changes are rapidly made and revision control is maintained. Using the Turbo Debugger, nearly all C code and assembly code can be debugged, registers examined, breakpoints set, etc., without the need for a costly in-circuit emulator. If a suitable ROM monitor is used during the final debug cycle, an in-circuit emulator may not be needed at all.

Since the code is transported from an MS-DOS environment to a target system, the functional code can be easily transported to a different 80x86-based target system by simply changing the device drivers and library I/O files. This built-in hardware independence aids the developer in writing reusable code, further reducing development time in future projects.

Example Project

The development of a simple 8086 ROM monitor illustrates this design approach (Listing 1) . The monitor, called mon86, performs two functions:

  • examine and change memory
  • go to specified address
  • It uses a simple table-driven executive that can be extended by adding new table entries and suitable functions to execute these new commands. An error function terminates the table.

    The monitor uses only Turbo C calls that comply with both SVID and POSIX definitions, improving portability. DOS-unique system calls, such as INT21, etc., are not used. Using them would complicate the code by requiring the use of conditional preprocessor directives to selectively include the non-portable sections of code. It also complicates library coding, since it requires that DOS code also be emulated.

    For this project, the C library is broken into two libraries: a system call emulation and a portable C library. The example project uses simple I/O calls such as read and write. The functions open, close, and ioctl are also implemented for completeness. Restricting I/O function calls to this small set reduces the work required to implement the emulation package.

    The monitor C library calls are limited to simple getc and putc. These calls are implemented as links to Turbo C calls _fputc and _fgetc, which allow for full use of Turbo C's <stdio.h>, further enhancing portability. Also, simple formatted output is required for memory display. To achieve this with an eye on portability, the library provides an integer-only printf. The choice of a limited printf has two justifications:

  • it is simpler to implement, because it does not require knowledge of compiler floating point representation
  • limiting its use to int and long guarantees that the floating-point library is not used, yielding smaller ROM code.
  • A machine library, called libm. lib, is also needed. This library is a collection of all subroutines required to supplment C operations not supported by the processor itself, such as long divides, multiplies, etc. These subroutines are naturally operating system independent. It is best to extract them from the Turbo C library as needed during the ROM build portion of the project. A limited library is built at that time by linking the final MS-DOS code, noting the routines used in the code map, and extracting and creating a new library of just those routines.

    Startup Assembly Code

    Probably the most important piece of code for an embedded system is the system startup code (Listing 2) . This code will vary from compiler to compiler, but the same operations generally appear in some form. The first part of the module is usually initialization related to the assembler or compiler. For Turbo C, this is the specification of segments. It is here that the order of segments for the module is first specified. Turbo C requires three segments, MS-DOS requires one.

    MS-DOS requires a stack segment. In a .EXE file, this is the only segment specified in the header that is separate from the rest. That is why the stack segment is used by some locate utilities as a place to separate RAM code from ROM code when generating hex files suitable for EPROM programmers.

    Turbo C requires only three segments, _TEXT, _DATA, and _BSS. These closely follow the UNIX C model. The mon86 generated here is a small version, therefore, the CODE segment is text only, and the DATA segment is used for data, bss (data initialized to zeros), and stack. These segments are mapped into DGROUP.

    All 80x86 processors begin executing code after reset at address 0xffff:0x0000. The usual procedure is to place a far jump at this location to the ROM entry address. For our example, the system entry point is defined as the far procedure entry. This label is used as the startup address supplied to the locater.

    Next, the processor is initialized. Initializing the processor means intializing any segment registers that the processor needs for proper operation. Since the module presented here is for an 8086 or 8088 machine, only the segment registers must be initialized. Note that segment register initialization is performed with all interrupts disabled, which prevents spurious interrupts that may occur during power-up from crashing the system.

    Once the processor is initialized, it usually requires the proper setting of the stack pointer. For this example, all you must do is initialize the sp and bp registers. Note that initializing the bp register is required by Turbo C code generation conventions, not by the 8086.

    Special hardware initialization should be performed after the processor has been initialized. This includes initializing the interrupt vector table, moving the data segment to RAM, and initializing the _BSS segment to all zeros to match C programming conventions. The example in Listing 2 includes a hook for hardware initialization with a call to __hdw_init. (For clarity, I've omitted moving data and initializing vectors.)

    After system initialization, you must enter main so the application can run. You enter main by building the stack frame containing the arguments envp, argv, and argc. You use these arguments to pass system configuration information, dip switch settings, etc. to your C code. These arguments also serve as a convenient debugging tool, since you can test different configuration switch positions by passing a command line argument during the debugging phase.

    One critical piece of code that all systems require is the function exit. This code is very implementation sensitive. You may want to light LEDs and sound alarms. In certain cases, you may want only to display a message and restart. What to do when the application terminates must be decided during system design. The startup code also contains a call to exit just after the call to main, which allows main to return to its caller if an error occurs.

    One final small but necessary bit of code is errno. errno is the location where C usually stores an integer value to signify the error condition that occurred during a library or system call. Although using errno this way is not necessary in all embedded systems, it is safer to follow convention than risk an unkown subroutine yielding a link error. Your system call emulation will also make use of this variable, allowing for a closer emulation.

    Operating System Emulation

    The design example uses a single file for operating system emulation (Listing 3) . This code closely emulates the standard C equivalents. Following this practice minimizes ROM debugging.

    A small data structure, fd, tracks file status. The only parameter set by open and close are file open or close status. This structure can be extended to satisfy system requirements, but should be kept as simple as possible to assure ease of debugging. The open call checks for only a single possible file to be opened. /der/con (the control console), stdin, stdout, and stderr are also hard-wired as read-only or write-only, as appropriate. In some systems, open and close will link to the I/O drivers to perform driver initialization.

    The most used portion of the emulation code is the functions ioctl, read, and write. The function ioctl is most often used for setting baud rates, screen modes, etc. It is shown here for completeness. The write function simply links to the driver call _cout (Listing 4) . It maps newlines into carriage return and line feed pairs. The read function buffers input. It terminates each request on receipt of a newline. Any necessary processing to put text in canonical C form should be performed here.

    Portable Library Functions

    In order to simplify debugging, standard I/O calls should be emulated. This example uses getc, puts, and printf. The functions here must closely emulate their respective MS-DOS counterparts, preventing unexpected surprises in the target system.

    Examining Turbo C's <stdio.h> file reveals that getc is a macro that invokes fgetc (as is typical in many systems). However, _fgetc calls DOS through another function, _fgetc. Therefore, your code closely follows this by mapping _fgetc and _fputc to return the correct values for success and error (Listing 5) . A simple puts is implemented in a portable fashion through calls to putchar (Listing 6) .

    This application uses the abbreviated version of printf described earlier (Listing 7) . This function only calls write, which minimizes unexpected dependencies on other library functions. Guaranteeing that floating point is not used helps in not wasting ROM space, usually a premium when system cost is considered. In many embedded applications, the only requirements for floating point support result from printf's floating point display options.

    Programming And Debugging

    Turbo C's greatest asset is its integrated programming environment. A bug can be quickly discovered and the code can be quickly recompiled from the same environment. Typically, it is in this environment that the first operational code is developed. (An interesting note: the new environment for Turbo C++ includes a register display window that works well with files assembled using the masm debugging switch). This phase should be used for all logic testing and customer demonstrations, since it is the most flexible of the development phases.

    Once the main logic has been debugged, the code should be linked with the portable libraries and tested. Listing 8 shows an example make file. Both DOS and ROM version targets should be defined, so that any changes that may be necessary to the main logic resulting from target system testing can be incorporated into the DOS version and retested in the integrated environment. The make file presented here also contains two additional targets, ex0 and ex0r. These targets are typically limited versions that test the device drivers. Quite often, these versions provide repeating patterns and echo routines for testing peripherals. Upon satisfactory completion of testing, the resulting code is burned into a final EPROM and delivered.

    Conclusions

    This example provides a good start for an embedded system design. Although the XT and AT architecture is used for this example, the techniques presented here are not limited to them. These techniques have been used for new designs in projects ranging from simple computer peripherals, such as printers and tape transports, to specialized sonar signal analysis hardware. They provide a broad approach that can be applied to both native and cross compilations, substituting cross compilers and cross compilation targets in the make file.

    Of course, an embedded system need not include support for printf, read, or write. An application can instead make simple calls directly to the device driver, which results in much smaller code. However, since many tradeoffs are considered during the system design phase, more often than not the extra functions are worth providing.

    Listing 1

    /*************************************
     *      mon86.c
     *      Simple 8086 rom based monitor.
     **************************************/
    
    #include <stdio.h>
    #include <dos.h>
    
    void debug();
    int smatch();
    
    struct table
    {
        char *t_name;    /* the name for this entry */
        int (*t_fn)();   /* the function to execute */
    };
    
    int mem(), go(), error();
    
    struct table cmd[]=
    {
        "mem", mem,
        "go",  go,
        "",    error
    };
    
    
    void main(argc, argv)
    int argc;
    char *argv[];
    {
        if(argc != 2)
        {
             printf("%s: incorrect argument count\n",
                    argv [0] );
             exit(1);
        }
        else
             printf("mon86 - demo 8086 monitor\n%s
                    version\n", argv[1] );
        debug ();
    }
    
    void debug()
    {
        char line[BUFSIZ];
        char key[32];
        char *lp, *p;
        struct table *tp;
        char *skipwh();
        
        for(;;)
        {
            printf("/nmon86:");
            gets(line);
            lp = skipwh(line);
            if(*lp == NULL)
                continue;
            p = key;
            while(*lp != NULL && !iswhite(*lp))
                *ptt  = *lp++;
            *p = NULL;
            for(tp = cmd; *(tp -> t_name) != NULL; tp++)
                if(smatch(key, tp -> t_name))
                    break;
            (*(tp -> t_fn))(lp);
        }
    }
    
    int iswhite(c)
    register int c;
    {
        return (c != NULL && (c == ' ' || c == '\t, ||
                c == '\n' || c == '\r'));
    }
    
    char *skipwh(p)
    register char *p;
    {
        while(iswhite(*p))
            ++p;
        return p;
    }
    
    /**********************************************
     * smatch:
     *      match two strings. return true if equal
     *********************************************/
    static int smatch(s1, s2)
    char *s1, *s2;
    {
        while(*s1 != '\0')
        {
            if(*s1 !=*s2)
                break;
            ++s1;
            ++s2;
        }
        return *s1 == *s2;
    }
    
    int mem(lp)
    char *lp;
    {
        unsigned long l = 0l;
        unsigned int seg, offset;
        char far *fp;
        char buf[80];
        
        printf("Memory examine and change\n");
        lp = skipwh(lp);
        while(hexdigit(*lp) >= 0)
            l = (l << 4) + hexdigit(*lp++);
        if(*lp == ':')
        {
            ++lp;
            seg = l;
            l = 0l;
            while(hexdigit(*lp) >= 0)
                l = (l << 4) + hexdigit(*lp++);
            offset = l;
        }
        else
        {
            seg = 0;
            offset = l;
        }
        fp = MK_FP(seg, offset);
        for(;;)
        {
            printf("%04x:%04x - %02x ", FP_SEG(fp),
                  FP_OFF(fp), *fp & 0xff);
            gets(buf);
            lp = skipwh(buf);
            if(smatch(".", lp))
                break;
            if(!hexdigit(*lp) >= 0)
            {
                ++fp;
                continue;
            }
            while(hexdigit(*lp) >= 0)
                l = (l << 4) + hexdigit(*lp++);
            *fp++ = l & 0xff;
        }
    }
    int go(lp)
    
    char *lp;
    {
        int (far *fp)();
        unsigned long l = 0l;
        unsigned int seg, offset;
        
        lp = skipwh(lp);
        while(hexdigit(*lp) >= 0)
            l = (l << 4) + hexdigit(*lp++);
        if(*lp == ':')
        {
            ++lp;
            seg= l;
            l = 0l;
            while(hexdigit(*lp) >= 0)
                l = (l << 4) + hexdigit(*lp++);
            offset = l;
        }
        else
        {
            seg= 0;
            offset = l;
        }
        fp = NK_FP(seg, offset);
        printf("Go from %04x:%04x", FP_SEG(fp),
             FP_OFF(fp));
        (*fp)();
    }
    
    int error()
    {
        printf("*** Unknown command\n");
    }
    
    hexdigit(c)
    int c;
    {
        register char *p;
        register i;
        static char *set = "0123456789abcdef",
               *alt_set = "0123456789ABCDEF";
        
        for(i = 0, p = set; *p != NULL; i++, p++)
            if(c == *p)
                return i;
        for(i = 0, p = alt_set; *p != NULL; i++, p++)
            if(c == *p)
                return i;
        return -1;
    }
    

    Listing 2

                  page    60,132
                  title   start1 - simple start-up code
    ;**************************************************************;
    ;                                                              ;
    ;                         start1.asm                           ;
    ;                                                              ;
    ;   start-up for stand-alone C code - Turbo C/C++ version      ;
    ;                                                              ;
    ;                   created: November 17, 1990                 ;
    ;                                                              ;
    ;                      Copyright (c) 1990                      ;
    ;                      Pasquale J. Villani                     ;
    ;                      All Rights Reserved                     ;
    ;                                                              ;
    ;**************************************************************;
    
    _TEXT           segment byte public 'CODE'
                  assume  cs:_TEXT                  ; small model
    _TEXT           ends
    
    _DATA           segment word public 'DATA'
    DGROUP          group   _DATA,_BSS,_BSSEND         ; small model
                  assume  ds:DGROUP,ss:DGROUP
    _DATA           ends
    
    _BSS            segment word public 'BSS'
    _BSS            ends
    
    _BSSEND         segment byte public 'STACK'
    _BSSEND         ends
    
    _TEXT           segment byte public 'CODE'
                  
                  extrn    _main:near           ; C entry point
                  extrn    _hdw_init:near       ; Hardware init (asm or C)
    
    ;
    ; entry:
    ;    Our ROM madule's entry point. This should be used as the
    ;    'jmp far ptr entry' at the top of ROM.
    ;
    entry      proc     far
             public   entry;
           
           ;
           ; First order of business is the initialization of the
           ; machine itself. This is basically the initialization
           ; of any segment registers that the processor needs for
           ; proper operation. On an 80X86 class processor, this is
           ; usually initializing the segmentation registers, going
           ; to the proper mode (i. e. protected, etc.) for
           ; 80[234]86 processors, etc. This file is for an 8086 or
           ; 8088 machine (or any other in real mode). Segment
           ; registers are the only processor initialization.
           ;
           ; NOTE: the cs register is not initialized because the
           ; processor itself initializes the first cs, followed
           ; by the far jump changing it to point to segment entry.
           ;
           cli                     ; prevent interrupts while starting
           mov     ax,DGROUP       ; initialize the segment registers
           mov     ds,ax
           mov     ss,ax
           mov     es,ax
           ;
           ; Once the processor is initialized, it usually requires
           ; the proper setting of the stack pointer(s). For the
           ; 8086 case, all we need is to initialize the sp and bp
           ; registers.
           ;
           mov     sp,offset DGROUP:tos
           mov     bp,sp
           ;
           ; We are now ready to make any special hardware
           ; initialization. This includes initializing the interrupt
           ; vector table, moving the data segment to RAM and
           ; initializing the BSS to zero to match C programming
           ; conventions. This should end with a call to __hdw_init
           ; to initialize I/O, etc.
           call  near ptr __hdw_init; initialize the system without ints
           sti                   ;now enable them
           ;
           ; After the hardware is initialized, we wish to enter
           ; main(). This is accomplished by building the stack for
           ; envp, argv and argc. This is a convienent mechanism
           ; for passing system configuration, dip switch settings,
           ; etc. to our C code.
           ;
           mov     ax,offset env   ; envp for this example
           push    ax
           mov     ax,offset args  ; argv for this example
           push    ax
           mov     ax,2            ; argc = 2
           push    ax
           call    near ptr _main  ; finally enter C code
           add     sp,6            ; clean stack
           ;
           ; If main should return, we need a mechanism to catch this
           ; condition. Depending on the system design, we should
           ; alarm, restart, etc., therefore, the following code
           ; is very implementation dependant. In our case, all we
           ; want to do is call exit with a special -1 exit code.
           ;
           mov     ax,-1
           push    ax
           call    near ptr _exit
           jmp     $               ; belt and suspenders
    
    entry       endp
    
    ;
    ; exit:
    ;    Where to go for error conditions (typically) or shutdown
    ;    conditions. This code is very implementaion sensitive, since
    ;    we may want to light LEDs and sound alarms. In this case, we'll
    ;    only silently stop. In certain cases, we may want to only
    ;    display a message and restart. This should be decided upon
    ;    during system design.
    ;
    _exit           proc    near
                 public  _exit
                 
                 cli
                 hlt
                 jmp     _exit
    
    _exit           endp
    
    T_EXT           ends
    
    _DATA           segment  word public 'DATA'
    env0    label   byte
                 db       'ENV=ROM',0
    arg0    label   byte
                 db               'ex0s', 0
    arg1    label   byte
                 db               'rom', 0
    env     label   word
           dw      DGROUP:env0
           dw      0
    args    label   word
           dw      DGROUP:arg0
           dw      DGROUP:arg1
           dw      0
    _DATA           ends
    
    ;
    ; The stack segment. This size should be adjusted to fit
    ; any particular system requirements.
    ;
    _STACK          SEGMENT
                  dw      512 dup (?)
    tos             label    byte
                  dd      (?)           ; safety area
    last            label    word          ; must always be end of stack area
    _STACK  ENDS
    
    _BSS            segment  word public 'BSS'
    _errno          label    word          ; c lib errno
                  public  _errno
                  dw      (?)
    _BSS            ends
    
    _BSSEND segment  byte    public 'STACK'
    _BSSEND ends
    
           end
    

    Listing 3

    /***************************************/
    /*                                     */
    /*              osem.c                 */
    /*                                     */
    /* Operating system emulation routines */
    /* for embedded system example ROM     */
    /* monitor.                            */
    /*                                     */
    /*          Copyright (c) 1990         */
    /*          Pasquale J. Villani        */
    /*          All Rights Reserved        */
    /*                                     */
    /*                                     */
    /***************************************/
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    
    /* convience defines                               */
    #define STDIN   0
    #define STDOUT  1
    #define STDERR  2
    #define MAXFD   3
    #define BUFSIZ  512            /* buffers are 512 for this vsn  */
    
    #ifndef TRUE
    #define TRUE    1
    #endif
    #ifndef FALSE
    #define FALSE   0
    #endif
    #ifndef ERROR
    #define ERROR   -1
    #endif
    #ifndef OK
    #define OK  0
    #endif
    #ifndef EOF
    #define EOF -1
    #endif
    
    /* make errno accessible to the entire file            */
    extern int errno;
    
    /* special data structure for ioctl optional argument   */
    union arg
    {
       int i;
       void    *p;
    };
    
    /* a simple file table to keep track of stdin & stdout status  */
    static char fd[MAXFD] =
    {
       FALSE,          /* stdin                 */
       FALSE,          /* stdout                */
       FALSE           /* stderr                */
    };
    
    /* a simple set of i/o buffers for input buffering  */
    struct _buf
    {
       int nbytes;
       char    *bp;
       char    buf[BUFSIZ];
    };
    
    static struct _buf iobuf =
    {
       -1, (char *)0
    };
    
    /*                                 */
    /* smatch:                             */
    /* match two strings. return true if equal          */
    /*                                 */
    static int smatch(s1, s2)
    char *s1, *s2;
    
    {
       while(*s1 != '\0')
       {
          if(*s1 != *s2)
             break;
          ++s1;
          ++s2;
       }
       return s1 == s2;
    }
    
    /*                                  */
    /* open:                               */
    /*  open a file for read or write                   */
    /*  follows posix specification                  */
    /*                                  */
    int open(path, oflag, mode)
    char *path;
    int oflag, mode;
    {
      if(smatch(path, "/dev/con") || ((oflag & O_RDWR) == O_RDWR))
      {
         errno = EACCES;
         return ERROR;    /* error - only console allowed  */
      }
      /* for this system, only stdin and stdout are available
      /* based on oflag and availability, assign appropraie fd
      if((oflag & O_RDONLY) && !fd[STDIN])
      {
         fd[STDIN] = TRUE;
         return STDIN;
      }
      else if(oflag & O_WRONLY)
      {
         fd[STDOUT] = TRUE;
         return STDOUT;
      }
      else
      {
         errno = EACCES;
         return ERROR;
      }
    }
    
    /*                                  */
    /* close:                              */
    /*  close a file                            */
    /*                                  */
    int close(fildes)
    int fildes;
    {
       if((fildes < 0) || (fildes > MAXFD) || !(fd[fildes]))
       {
          errno = EBADF;
          return ERROR;
       }
       fd[fildes] = FALSE;
       return OK;
    }
    
    /*                                  */
    /* ioctl:                               */
    /* direct device control                    */
    /*                                  */
    int ioctl(fildes, request, arg)
    int fildes, request;
    union arg arg;
    {
       errno = EINVAL;
       return ERROR;
    }
    /*                                  */
    /* write:                               */
    /* write to a file                          */
    /*                                  */
    int write(fildes, buf, nbyte)
    int fildes;
    void *buf;
    unsigned int nbyte;
    {
       int cnt = nbyte;
       if(fildes != STDOUT && fildes != STDERR)
       {
          errno = EBADF;
          return ERROR;
       }
       while(cnt-- > 0)
       {
          if(*(char *)buf == '\n')
             _cout('\r');
          _cout(*((char *)buf)++);
       }
       return nbyte;
    }
    int read(fildes, buf, nbyte)
    int fildes;
    void *buf;
    unsigned int nbyte;
    {
      register char *bp;
      register int c;
    
      if(fildes != STDIN)
      {
         errno = EBADF;
         return ERROR;
      }
      if(iobuf.nbytes <= 0)
      {
         bp = iobuf.buf;
         iobuf.nbytes = 0;
         do
         {
             *bp++ = (_cout(c = _cin()));
             if(c == '\r')
                _cout('\n');
             ++iobuf.nbytes;
         } while(c != '\n' && c != '\r');
         iobuf.bp = iobuf.buf;
      }
      for(bp = iobuf.bp, c = 0; c < iobuf.nbytes && c <nbyte; c++)
      {
         *((char *)buf)++ = *bp == '\r' ? ++bp, '\n' : *bp++;
      }
         iobuf.bp = bp;
      iobuf.nbytes -= c;
      return c;
    }
    

    Listing 4

    ;
    ; _cio - console i/o for embedded system example
    ;  near version
    ;
    
    _TEXT segment byte public 'CODE'
    DGROUP group  _DATA,_BSS
       assume  cs:_TEXT,ds:DGROUP,ss:DGROUP
    _TEXT  ends
    
    _DATA  segment word public 'DATA'
    _DATA  ends
    
    _BSS  segment word public 'BSS'
    _BSS  ends
    
    _TEXT  segment byte public 'CODE'
    
    
    ;
    ; _cin - input to console
    ;  near version
    ;
    
    __cin  proc    near
       public  __cin
       push    bp        ; perform c entry
       mov bp,sp
       push    bx        ; save bx for c
       mov ah,0h         ; get the byte
       mov bx,7
       int 16h       ; using the bios
       mov ah,0          ; "sign extend" - convert to int
       pop bx        ; restore context
       pop bp
       ret
    __cin endp
    
    ;
    ;_cout - output to console
    ; near version
    ;
    
    __cout proc  near
       public  __cout
       push    bp          ; c entry
       mov bp,sp
       push    bx          ; save register for c
       mov ax,word ptr [bp+4]  ; get c
       mov ah,0eh          ; and output the byte
       mov bx,7
       int 10h         ; call bios
       pop bx          ; restore context
       pop bp
       ret
    __cout endp
    
    ;
    ; _cinit - console initialization
    ;  **** No initialization required for bios version ****
    ;
    ;  near version
    ;
    __cinit  proc  near
       public  __cinit
       push    bp
       ; perform any required hardware initialization here
       mov ax,1            ; return true for success
       pop bp
       ret
    
    __cinit  endp
    
    _TEXT ends
    
       end
    

    Listing 5

    /****************************************************/
    /*                                                  */
    /*             iolink.c                             */
    /*                                                  */
    /*         stdio tie for Turbo C/C++ Compilers      */
    /*                                                  */
    /*           Copyright (C) 1990                     */
    /*           Pasquale J. Villani                    */
    /*           All Rights Reserved                    */
    /*                                                  */
    /****************************************************/
    
    #include <stdio.h>
    
    FILE _streams[20];
    
    fputc(c, f)
    char c;
    FILE *f;
    {
       return _fputc(c, f);
    }
    _fputc(c, f)
    char c;
    FILE *f;
    {
       if(write(1, &c, 1) == 1)
          return c;
       else
          return EOF;
    }
    
    _fgetc(f)
    FILE *f;
    {
       char c;
       
       if(read(0, &c, 1) != 1)
          return EOF;
       else
          return c;
    }
    

    Listing 6

    /*************************************************/
    /*                                               */
    /*            puts.c                             */
    /*                                               */
    /* Put a string to console. Uses stdio.h putchar */
    /* macro for operating system interface. Outputs */
    /* a newline after the string                    */
    /*                                               */
    /*          Copyright (c) 1990                   */
    /*          Pasquale J. Villani                  */
    /*          All Rights Reserved                  */
    /*                                               */
    /*                                               */
    **************************************************/
    
    #include <stdio.h>
    
    puts(s)
    const char *s;
    {
       int c;
       
       while ((c = *s++) != '\0')
          putchar(c);
       putchar('\n');
    }
    

    Listing 7

    /*
    *      abbreviated printf routine
    *
    *      embedded system version
    *      11/30/90
    */
    
    /* ltob -- convert an long integer to a string in any
       base (2-36) */
    static char *ltob(n, s, base)
    long n;
    char *s;
    {
        unsigned long u;
        register char *p, *q;
        register negative, c;
        
        if (n < 0 && base == -10)
        {
               negative = 1;
               u = -n;
        }
        else
        {
               negative = 0;
               u = n;
        }
        if (base == -10)                /* signals signed
                                     conversion */
               base = 10;
        p = q = s;
        do
        {                               /* generate digits in reverse
                                     order */
               *p++ = "0123456789abcdef"[u % base];
        } while ((u /= base) > 0);
        if (negative)
               *p++ = -;
        *p = '\0';                      /* terminate the string */
        while (q < --p)
        {                               /* reverse the digits */
               c = *q;
               *q++ = *p;
               *p = c;
        }
        return s;
    }
    
    #define NONE   0
    #define LEFT   1
    #define RIGHT  2
    
    /* printf -- short version of printf to conserve
       space */
    int printf(fmt, args)
    const char *fmt;
    char *args;
    {
           register base;
           register char **arg;
           char s[11], *p, *ltob();
           char c, slen, flag, size, fill;
           
           arg = &args;
           flag = NONE;
           size = 0;
           while ((c = *fmt++) != '\0')
           {
                  if (size == 0 && flag == NONE && c != '%')
                  {
                         
                         write(1, &c, 1);
                         continue;
                  }
                  switch(*fmt)
                  {
                  case '-':
                         flag = RIGHT;
                         fill = *(fmt + 1) == '0' ? '0' : ' ';
                         continue;
                  
                  case '0':
                  case '1':
                  case '2':
                  case '3':
                  case '4':
                  case '5':
                  case '6':
                  case '7':
                  case '8':
                  case '9':
                         if(flag == NONE)
                                flag = LEFT;
                         size = *fmt++ - '0';
                         while((c = *fmt++) != '\0')
                         {
                                switch(c)
                                {
                                case '0':
                                case '1':
                                case '2':
                                case '3':
                                case '4':
                                case '5':
                                case '6':
                                case '7':
                                case '8':
                                case '9':
                                       size = size * 10 + (c - '0');
                                       continue;
                                
                                default:
                                       --fmt;
                                       break;
                                }
                                break;
                         }
                         break;
                  }
                  switch (c = *fmt++)
                  {
                  case 'c':
                         write(1, (char *)arg++, 1);
                         continue;
                  
                  case 'd':
                         base = -10;
                         goto prt;
                  
                  case 'o':
                         base = 8;
                         goto prt;
                  
                  case 'u':
                         base = 10;
                         goto prt;
                  
                  case 'x':
                         base = 16;
                  
                  prt:
                         ltob((long)(*((int *)arg)++), s, base);
                         if(flag == RIGHT || flag == LEFT)
                         {
                                for(slen = 0, p = s; *p != '\0'; p++)
                                       ++slen;
                         }
                         if(flag == RIGHT && slen < size)
                         
                         {
                                int i;
                                
                                for(i = size - slen; i > 0; i--)
                                       write(1, &fill, 1);
                         }
                         for(p = s; *p != '\0'; p++)
                         write(1, p, 1);
                         if(flag == LEFT)
                         {
                                int i;
                                char sp = ' ';
                                
                                for(i = size - slen; i > 0; i--)
                                       write(1, &sp, 1);
                         }
                         size = 0;
                         flag = NONE;
                         continue;
                  
                  case 'l':
                         switch(c = *fmt++)
                         {
                         case 'd':
                                base = -10;
                                goto lprt;
                         
                         case 'o':
                                base = 8;
                                goto lprt;
                         
                         case 'u':
                                base = 10;
                                goto lprt;
                         
                         case 'x':
                                base = 16;
                         
                         lprt:
                                ltob(*((long *)arg)++, s, base);
                                if(flag == RIGHT || flag == LEFT)
                                {
                                       for(slen = 0, p = s; *p != '\0'; p++)
                                              ++slen;
                                }
                                if(flag == RIGHT && slen < size)
                                {
                                       int i;
                                       
                                       for(i = size - slen; i > 0; i--)
                                              write(1, &fill, 1);
                                }
                                for(p = s; *p != '\0'; p++)
                                       write(1, p, 1);
                                if(flag == LEFT)
                                
                                {
                                       int i;
                                       char sp = ' ';
                                       
                                       for(i = size - slen; i > 0; i--)
                                               write(1, &sp, 1);
                                }
                                size = 0;
                                flag = NONE;
                                continue;
                         
                         default:
                         write(1, &c, 1);
                         }
                  
                  case 's':
                         for(p = *arg; *p != '\0'; p++)
                                write(1, p, 1);
                         ++arg;
                         continue;
                         
                         default:
                                write(1, &c, 1);
                                continue;
                         }
                  }
           }
    

    Listing 8

    #
    # makefile for example 0
    #
    # 12/02/90 1830 EST
    #
    
    CC = tcc
    CFLAGS = -ms -v
    AS = masm
    ASFLAGS = /Mx/Zi
    LD = tlink
    LDFLAGS = /v/c
    
    OBJSROM = start1.obj cio.obj osem.obj iolink.obj\
             hdwinit.obj prf.obj puts.obj gets.obj
    LIBSROM = libm.lib
    
    all:        ex0.exe ex0s.exe mon86.exe mon86r.exe
    
    mon86.exe: mon86.obj
           $(CC) $(CFLAGS) -emon86 mon86
    
    osem.obj: osem.c
           $(CC) $(CFLAGS) -c osem.c
    
    mon86.obj: mon86.c
           $(CC) $(CFLAGS) -c mon86.c
    
    iolink.obj: iolink.c
           $(CC) $(CFLAGS) -c iolink.c
    
    prf.obj:    prf.c
           $(CC) $(CFLAGS) -c prf.c
    
    puts.obj: puts.c
           $(CC) $(CFLAGS) -c puts.c
    
    gets.obj: gets.c
           $(CC) $(CFLAGS) -c gets.c
    
    start0.obj: start0.asm
           $(AS) $(ASFLAGS) start0,,,;
    
    ex0s.exe: ex0.obj $(OBJSROM)
           $(LD) $(LDFLAGS) $(OBJSROM) ex0.obj,\
           ex0s,nul,$(LIBSROM)
    
    mon86r.exe: mon86.obj $(OBJSROM)
           $(LD) $(LDFLAGS) $(OBJSROM) mon86.obj,\
           mon86r,nul,$(LIBSROM)
    
    start1.obj: start1.asm
           $(AS) $(ASFLAGS) start1,,,;
    
    cout.obj: cout.asm
           $(AS) $(ASFLAGS) cout,,,;
    
    cin.obj:    cin.asm
           $(AS) $(ASFLAGS) cin,,,;
    
    cio.obj:    cio.asm
           $(AS) $(ASFLAGS) cio,,,;
    
    ex0.obj:    ex0.c
           $(CC) $(CFLAGS) -c ex0.c