A Serial Protocol Analyzer Program

Craig's SPA offers a slick way to use your PC to visually monitor data flow and handshaking between serial devices.


February 01, 1988
URL:http://www.drdobbs.com/architecture-and-design/a-serial-protocol-analyzer-program/184407905

Figure 1

Figure 1

Figure 2

Figure 2

Figure 3

Figure 4

Figure 4

Figure 6

Figure 6

FEB88: A SERIAL PROTOCOL ANALYZER PROGRAM

A SERIAL PROTOCOL ANALYZER PROGRAM

Debugging the bit stream with Turbo Pascal

Craig A. Lindley

Craig A Lindley, 6 Sutherland Pl., Manitou Springs, CO 80829. Craig has been working with real-time operating systems for many years. He has worked at the Jet Propulsion Laboratory on the implementation of part of the real-rime system to be used on the upcoming Galileo spacecraft and is currently emplayed at Rolm Corp. as a software engineer involved in real-time telephony control.


This article describes the implementation of an RS-232 serial protocol analyzer (SPA) program hosted on an IBM PC computer. it utilizes the multitasking kernel I presented in the July 1987 issue of DDJ. The software provided in this article converts a PC into a tool that can be used to monitor most RS-232 connections.

The program was developed as an alternative to spending $5,000-20,000 on a dedicated serial protocol analyzer device. Don't misunderstand, this program (in its present form) doesn't have half the features and functions provided by a dedicated protocol tester, but it performs very well when simply displaying RS-232 data--the function a dedicated analyzer is used for 95 percent of the time. What's even better is that the SPA software is available, so you can tailor it to your specific applications.

This program will find use in many software development labs, computer/data-processing centers, and just about anywhere else that RS-232 devices are used. In this day and age of corporate frugality, buying or borrowing an IBM PC to run an application of this type is probably a lot easier than trying to justity the cost of a dedicated protocol analyzer. Also, you can't run Lotus 1-2-3 or word-processing software on your dedicated protocol analyzer when it isn't being used for its designed function.

What Does an SPA Do?

An SPA provides a visual picture of data flowing between two serial devices connected via an RS-232 interface. In addition, it provides a method of monitoring the Rs-232 handshake lines (RTS, CTS, DTR, DSR, and so on) in a manner not unlike the two-color LED arrangement used on RS-232 breakout boxes. Figuring out what a serial interface is doing (or is not doing) without a device of this sort is extremely difficult.

The SPA will find use in two major areas-first, in software labs to assist in the development of RS-232 data protocols for new products or devices, and second, in data communication users environments in figuring out why two supposedly compatible serial devices are not talking to each other. I'll give an example of each application.

Consider the development of a terminal emulator program. The SPA could be used to verify that key codes programmed on a special function key were indeed being sent out of the serial port when the special function key was pressed. The SPA could also be used to verify that the terminal program could really generate a break condition on its port lines. Finally, the SPA could be used to verify the terminal emulator's implementation of the Xmodem file transfer protocol.

In a data communications environment, consider the case of a serial printer loosing some of the characters sent to it. The SPA could be used to determine that the printer was sending the XOFF software handshake command but that the source of the serial data was not responding to it by stopping its transmission of data.

These are two of the many possible applications of the SPA program. In a general sense, the SPA can take the guesswork out of serial device interfacing and can be considered a serial interface debugger. Any aids in the debugging process will equate to increased productivity and reduced frustration.

What Is Required

The following is the minimum equipment required for use of the SP4 program:

    1. An IBM PC, PC/XT, or PC AT (or compatible) with at least 256K RAM. An AT is required for data rates greater than 4,800 baud.

    2. Two serial ports: COM1 configured at address 3F8H using interrupt IRQ4 and COM2 configured at address 2F8H using interrupt IRQ3.

    3. The SPA executable program (SPA.COM).

lf you would like to experiment with the SPA code, these additional items are required:

1. 256K of additional memory for compiling the program in memory and executing it.

    2. Turbo Pascal, Version 3.0 or later, for the IBM PC.

    3. The following source code files:

Personally, I think the SPA is a useful program. In addition, if you examine the software components that make up the SPA, you'll find the following components are useful in themselves:

    1. A generalized multitasking kernel written in Turbo Pascal.

    2. A generalized 1-2-3-like menuing system that can be converted for use in another application simply by changing its database.

    3. A BIOS-independent, interruptdriven, serial interface package supporting both the COM1 and COM2 ports. This, too, is written in Turbo Pascal.

The moral of this story is, if you can't use the whole program, maybe you can use the pieces.

Connecting the SPA for Use

Two methods exist for connecting the SPA to the RS-232 interface to be monitored. The first I call the passive monitoring connection and is shown in Figure 1, right.

Figure 1 : The passive montior connection

This diagram shows the minimum connections necessary to use the SPA to monitor an RS-232 connection. In this case the SPA does not pass the data through. it merely monitors the data sent between both serial devices.

The additional monitor lines can be connected to any of the serial lines you wish to monitor on the SPAs' screen. The signal connected to: pin 5 will show up as CTS; pin 6 will show up as DSR; pin 8 will show up as CD; pin 22 will show up as RI. With this connection method the PC, which is hosting the SPA program, does not sit between the two ends of the RS-232 cable. The two endpoint serial devices are connected just as they would be without the SPA attached. The SPA is effectively connected in parallel to the serial data path. The data being transmitted by the devices on both ends of the serial interface is routed to the receive data inputs of COM ports 1 and 2 of the SPA's PC. Also, any of the four available inputs to the COM ports (CTS, DSR, CD, or RI) can be tied to any of the lines of the serial interface being monitored to allow the SPA to display their states visually to the user.

The second connection method I call the pass through connection. Figure 2, page 31, shows the cabling required for this connection method. With this connection, the SPA program sits between both serial devices. All data and handshake line states generated and received by both serial endpoint devices pass through (and can therefore be processed by) the SPA program. This is the mode for which the current SPA software is optimized. It allows the maximum flexibility for future SPA program functionality.

Figure 2: Pass through monitoring

The CD and RI lines may require additional connections if the serial devices being monitored require that these signals are actively driven. The PC serial hardware does not have the driver outputs necessary to drive these signals as it does the RTS and DTS signals. The CD and RI lines from one side could be connected to the same signal pins on the other side.

How to Use the SPA

After connecting the SPA's PC to the serial devices using one of the two connection methods, you must execute the SPA program. The SPA program is normally compiled into an executable .COM file called SPA.COM. To execute it just type SPA at the DOS prompt.

The first thing you'll notice is that the SPA program verifies the presence of two serial ports (COM1 and COM2 at the proper addresses). Unless two serial ports are found, the program will halt with an error message.

If the hardware verification is successful, the operator is presented with the main display screen, similiar to that shown in Figure 3, page 32. As you can see, this screen presents a lot of information about both the COM1 and the COM2 ports. I'll now describe each item:

In Fifo, Out Fifo, and Display Fifo: These numbers, expressed in percentages, indicate how full the various FIFOs used by the SPA program are. See the block diagram in Figure 4, page 32, for an explanation of the function of each of these FIFOs.

Note: These percentages are not updated in real time, only every half second. They are shown to provide a feel for how well the SPA is processing the serial data. It is quite possible, however, to have a FIFO overflow occur even though the numbers suggest there is still considerable room in the FIFO.

Stat--BRK, FE, PE, and OR: These are the various status and error conditions of the COM ports in the PC. BRK indicates a break condition on the line, FE indicates a framing error, PE indicates a parity error, and OR indicates an overrun error has occurred. See the IBM technical reference manual for an explanation of these conditions. The no-error condition is indicated with a dash whereas the error condition is indicated by an E or B in the case of a break.

In--CD, RI, DSR, and CTS: These are the various input lines into the COM ports. Their states are continually monitored by the SPA program, and any changes are shown on the display. Their states are either M for marking or S for spacing.

Out--DTR and RTS: These are the two output lines from the COM ports. Their states are also either M for marking or S for spacing. Note: The SPA program routes what is received on one COM port's DSR line to the other COM port's DTR line, effectivley passing the state through the PC. The same is true of the CTS and RTS lines. This handshake line pass through happens in both directions.

The information line at the bottom of the display informs the user that:

    1. If the Esc key is pressed, the menu system will be entered. The highest-level menu is shown in Figure 5, page 34.

    2. If the space bar is pressed wide data is being displayed, the display will pause until the Enter key is pressed to restart it.

    3. If COM1 data is being displayed (the data received at the COM1 port), it will be shown in normal video. If COM2 data is being displayed, it will be in reverse video to allow an easy visual distinction.

Figure 5: Main menu of the SPA program


======================================================================
                    Serial Protocol Analyzer Menu
                    -- Main Menu Selection --

QUIT      Parameters     Display   Trigger   Format    Control   End

Exit Analyzer Menus

======================================================================

To set up the SPA program for use, you must enter the menu system using the Esc key. Normally, the setup consists of selecting the proper baud rate, parity, and word length of the serial data stream to be monitored. The defaults chosen for all other setup parameters are adequate for an initial data display. Once the setup is complete, the end-point serial devices should be enabled to start data transmission. If all is well, the data being passed back and forth between the serial devices should be shown on the SPA's display.

The menu system has the capability of altering the operation of the SPA program. In all, there are 35 possible alterable parameters that determine how the SPA operates. The procedure ProcessCmd in the file SPA.PAS (Listing One) shows exactly how each of the menu options is processed. A quick trip through the menuing system will acquaint you with flexibility of the SPA program. Note that data will still be acquired and displayed by the SPA program while you are using the menus. That is the beauty of a multitasking system.

The more important features of the menu system are:

    1. Under the Parameters menu, you set the baud rate, parity, and number of stop bits with which the data should be interpreted. The program supports rates from 300 to 9,600; five through eight data bits; odd, even, or no parity; and one or two stop bits. If the serial parameters are set incorrectly, the data displayed on the SPA's screen will be garbage.

    2. Under the Display menu, you set whether the data from COM1, COM2, or both is displayed on the screen.

    3. Under the Trigger menu, you set up the SPA's triggering capability. Here you input the channel (COM1 or COM2) to trigger from, the single-byte trigger data pattern, and the trigger mode (display data until trigger or display data after trigger). You can also stop (wait for) the trigger in this submenu. Note: After the trigger parameters are set, the trigger must be enabled before it goes into effect.

    4. The Format submenu is where the format of the displayed data is set. The options are:

When data is displayed with the handshake option, the port input lines that were acquired when the data was acquired and certain COM port error conditions will be displayed. The information is bit encoded into the displayed hex byte as shown in Table 1, page 37.

Table 1: Correspondence between data bit encoded and displayed bex byte

                         Bit Numbers

          7    6    5    4    3    2    1    0
          CD   RI   DSR  CTS  BRK  FE   PE   OR

The final option in the Format menu is simply called SPACE. This is a toggle that determines whether the data displayed on the screen is separated or not--in other words, whether a space will be inserted between each displayed data item. If a space is being inserted and this menu option is selected, the spacing will be stopped; the reverse is also true.

    5. The Control submenu contains several commands that control the operation of the SPA program. Data acquisition can be stopped and started; the display can be cleared; and finally, the SPA program can be reset to its power-on defaults.

    6. The Quit menu option returns you to the main SPA display screen, and the End option ends the operation of the SPA program completely and returns control to DOS. Note: Selecting Quit from any submenu will always bring you back up one menu level.

The complete hierarchical menu structure is too big to list. You can look at the procedure Init__Menu in the file MENU.PAS for more information. Also, a short textual message is displayed with each possible menu selection. This is a limited form of context-sensitive help to guide you through the menuing system without your having to consult any documentation.

The SPA Program's Architecture

Now that you understand how to use the SPA program, I'll spend a few minutes discussing the underlying technical aspects of the program's operation. For the following discussion, please refer to Figure 4 .

As shown in this block diagram, the SPA program is made up of seven relatively independent tasks, most of which are bound to a FIFO. In summary, the tasks perform the following:

The tasks MoveCOM1Data and MoveCOM2Data perform the same function on different FIFOs for different COM ports. These tasks perform three basic functions. First, they move serial data and handshake information from their respective input FIFO to the opposite output FIFO. This makes the serial data path through the PC. Second, the serial data is tagged with a source identifier (either from COM1 or COM2), and if the display data flag is set, the data is moved into the display FIFO. Finally, code in these tasks provides the data triggering function.

The tasks OutputCOM1Data and OutputCOM2Data again are identical in function but access different FIFOs. They check to see whether there is data in their respective output FIFOs, and if so they send it out the COM port.

The task DisplayCOMData removes entries from the display FIFO, formats them according to the SPA user's specification, and displays them on the SPA's display.

The Timer task runs every half second. Its purpose is to update the information on the main SPA display screen. Specifically, it updates the handshake information and the buffer usage information displayed to the user.

The final task, ProcessKeysTask, monitors the SPA's keyboard and processes all user keystrokes. The menuing system is invoked via this task.

The Menuing System

As previously mentioned, a hierarchical series of menus is used to control the operation of the SPA program. The menuing system is modeled after Lotus 1-2-3 because of its ease of use and the appropriateness of its operation. A menu item or submenu is selected by using the cursoo arrow keys to place the reverse-video selector box over the desired item and pressing Enter or by typing the first character of the item's name. The user of the SPA program can move through almost the entire menuing sustem by pressing only single keys. In only one instance--the selection of a trigger byte--is the use of more than one key necessary.

Five procedures from the file MENU.PAS (DisplayMenu, ProcessCr, ProcessMenu, ProcessCmd, and DoMenu) are used in conjunction with a large data structure to provide the Lotus 1-2-3-like menuing system. These procedures implement an A-tree walk through the menu data structure. For more information on the A (or Awkward) tree structure, see the article "Indexing Open Ended Tree Structures" by John Snyder in the May 1984 issue of Byte magazine.

The A-tree menu data structure is rather inefficiently, but simply, realized in the SPA program using a three-dimensional array of menu entry records. A menu entry record is used for each item in the entire menu. Figure 6, page 34, shows a portion of the menu structure. Approximately 24K of data is required to support the menuing system as the array of menu entry records is very sparse.

Figure 6: The A tree menu structure (partial)

The numbers in () are the array indices of the menu entry record corresponding to this menu item or submenu. All items with an assigned ccode are leaf or terminal nodes of the A tree. These codes are processed by the procedure ProcessCdm. Transition characters are those which cause movement to a different menu level. Typically, they are the first character of each menu item. The complete A tree menu structure for the SPA program is built by the procedure lnit__Menu. Figure 7, page 34, shows a detailed breakdown of the menu entry record used in the A-tree structure.

Figure 7: A Menu Entry Record

menu_entry  = RECORD

title     --Item or submenu name. This string with a maximum length of
            ten characters is displayed on the selector line and can be
            selected by pressing a key corresponding to the first
            character of its name
desc      --Description of item or submenu up to 40 characters in
            length. Explanatory text describing the item or submenu.
chars     --A string of upper case characters which can be used to
            select any item on this menu level. This string usually
            contains the first character of each items title.
index     --This entry contains the length of the "chars" string above.
code      --Only non-zero at A tree leaf nodes. It is the command code
            to be processed by the procedure ProcessCmd which is
            associated with this menu entry.
END;

The following keys have special significance while using the menus. They are:

Enter--Selects the currently highlighted menu item. If the item is a submenu, the submenu is displayed with its own selectable options. If the item is a leaf node of the tree, it returns a unique command code (ccode) specifying an action to be performed.

Cursor arrow keys--Move the highlighted, reverse-video selector box through the items in a menu. Full selection wrapping is supported.

Esc--Terminates the menuing system and returns to the main SPA display screen.

Home--Moves the selector to the first menu entry.

End--Moves the selector to the last menu entry.

Hopefully, the operation of the menuing system will be discernable from the commented listings. Possibly a future article could be written to discuss the menus in detail if interest warrants it.

Changes to the Multitasking Kernel

I've made two changes to the kernel presented in the July issue of DDJ to augment its capabilities for use with the SPA program. They are:

    1. The constant task__stack__size has been increased to 1,000 bytes. This was necessary because of procedure nesting depths, the use of intermpts, and Turbo Pascal's use of BIOS calls. Each task is assigned a stack size of 1,000 when forked. The seven tasks utilized for the SPA program consume approximately 7K of memory for their stacks.

    2. Substantial portions of the procedures Yield and Wait have been recoded into assembly language. This minimizes the task-switching overhead experienced by the CPU.Listing Four , page 80, shows the assembly-language recoding. This change was not absolutely necessary for the operation of the SPA, but it seemed to help the performance of the data display when operating above 2,400 baud. By way of comparison, the hand-optimized assembly-language code uses half the instructions generated by the Turbo Pascal compiler for the equivalent code.

Possible Enhancements

As presented, the SPA program is a rather basic tool. Its present form is in part attributable to the application for which I have been using it. Other applications will require different incarnations of the basic program. Additional features and functions will begin to suggest themselves with prolonged use of the SPA. Already my wish list is growing.

    1. A setup file that would be read at the start-up of the SPA program that would configure the SPA as I had previously left it. Included in this setup could be serial parameters, trigger info, and display-formatting information.

    2. Logging of captured data to a file or printer. Currently, the Shift-PrtScn key sequence must be used to get hard copy of the displayed data.

    3. The output of canned serial data to a COM port on the occurrence of a trigger.

    4. Increasing the trigger capability from a single byte to multibyte patterns or strings.

    5. Other serial data formats--for example, BiSync, HDLC, and SDLC.

Maybe inspired readers can add these functions (and more) and graciously provide me with the code modifications. If I receive enough suggestions and modifications, I'll collect them into a usable form and write another article describing them.

Modification and Testing Issues

At the present time, the SPA program has never been tested (for any length of time) above 2,400 baud because I lack the equipment to test it throughly. lt has been tested substantially at 1,200 baud in the development of an Xmodem protocol. lf you require baud rates above 2,400 for your application, you may need to make minor changes to the program to allow it to keep up. With rates above 4,800 baud, I recommend using an IBM PC AT. In the future I hope to test it out thoroughly above 2,400 baud (read, as soon as my project at work slows down some).

As mentioned previously, the software as presented is optimized for the pass through mode of connection. if you would rather use the passive monitodng connection, you can modity the software to improve its performance. The modifications are as follows:

    1. Remove the section of code in SPA.PAS (in main) that forks off the OutputCOM1Data and OutputCOM2Data tasks. These aren't needed for the passive monitor conection because the SPA's PC doesn't have to output any data.

    2. Modify the code in the tasks MoveCOM1Data and MoveCOM2Data so that they don't move the serial data to the output FIFOs. From MoveCOM1Data, remove the line:

    PutSerial-Data(Sd,COM2__Output__Fifo);

and from MoveCOM2Data, remove the line:

   PutSerial-Data(Sd,COM1__ Output__Fifo);

These changes will eliminate two unnecessary tasks from being executed and will speed up (slightly) the processing of data in two other tasks. The original code is required, however, when the SPA is configured for the pass through method of connection.

[LISTING ONE]



_A Serial Protocol Analyzer Program_
by Craig Lindley






{$K-}             {Compiler switch - never change}

{************************************************}
{***                                          ***}
{***                Turbo Pascal              ***}
{***            Multitasking Kernel           ***}
{***                 written by               ***}
{***              Craig A. Lindley            ***}
{***                                          ***}
{***    Ver: 2.0     Last update: 08/15/87    ***}
{***                                          ***}
{************************************************}

CONST

   task_stack_size  = 1000; {stack size for each}
                           {task}
   turbodseg: integer = 0; {storage for turbos}
                           {data segment value}


TYPE

{possible states for a task}
   task_state = (ready,waiting,running);

{808X register set}
   register_type = RECORD
   CASE integer OF
      1: (ax,bx,cx,dx,bp,si,di,ds,es,flags:integer);
      2: (al,ah,bl,bh,cl,ch,dl,dh         :byte);
   END;


{Task control block (tcb) structure}

 tcbptr = ^ tcb;       {ptr to tcb}

 tcb = RECORD
    link:  tcbptr;     {link to next tcb in dseg}
    bptr:  integer;    {base ptr offset in sseg}
    state: task_state; {ready, waiting, running}
    id:    byte;       {task number}
 END;

 waitptr = ^tcbptr;    {ptr to ptr to tcb}
                       {used for passing parms}
                       {to wait}

 semaphore = RECORD    {Semaphore data type}
    count:  integer;   {number of times signaled}
    signal: tcbptr;    {pointer to waiting task}
                       {if there is one}
 END;


{******** Begin Multitasking Variables *********}

VAR

   cp,                  {current task pointer}
   new_tcb_ptr,         {ptr to new tcb in dseg}
   temp_ptr:   tcbptr;

   waitfor:    waitptr; {address of item to}
                        {wait on}
   stk,bp:     integer; {variables for setting}
                        {808X sp and bp}
   frame_ptr:  integer; {stack frame pointer}
   next_id:    integer; {next task id number}
   i:          integer;
   child_process: boolean; {fork successful flag}


{******** Begin Multitasking Procedures ********}


PROCEDURE Fork;        {fork off a new task}

{This procedure manipulates Turbo Pascal's stack}
{frame as required to fool it into operating in}
{a new task's environment.}

BEGIN

   child_process:=false;   {indicate the parent}
                           {process until proven}
                           {otherwise}
   {check if enough stack space for a new task}

   IF abs(frame_ptr - task_stack_size) > 0 THEN
   BEGIN                   {if enough}
      INLINE($89/$26/stk); {get 808X Sp to}
                           {calculate Bp pointer}
      cp^.bptr:=stk+2;     {save Bp ptr in this}
                           {frame}
      new(new_tcb_ptr);    {allociate new tcb}

      {link new tcb into scheduler loop}
      {make its state running and give it an id}

      new_tcb_ptr^.link:=cp^.link;
      cp^.link:=new_tcb_ptr;
      new_tcb_ptr^.state:=running;
      next_id:=next_id+1;
      new_tcb_ptr^.id:=next_id;

      cp^.state:=ready;    {old frame is ready}

      {copy old stack to new stack frame}
      FOR i:=0 TO 5 DO
         mem[sseg:frame_ptr-6+i]:=mem[sseg:stk+i];

      {make Bp storage in stack frame point at}
      {this frame}

      memw[sseg:frame_ptr-4]:=frame_ptr;
      bp:=frame_ptr-4;     {calculate Bp pointer}

      INLINE($8B/$2E/bp);  {set 808X Bp reg to}
                           {this new value}

     {reserve stack frame space}
     frame_ptr:=frame_ptr-task_stack_size;
     cp:=new_tcb_ptr;      {cp points at new task}
     child_process:=true;  {indicate child process}
   END;

END;


(*
PROCEDURE Yield;

{This procedure cause the executing task to}
{relinquish control of the CPU to the next ready}
{task.}

BEGIN


   child_process:=false;   {reset variable}
   IF cp^.link <> cp THEN  {must have more than}
                           {one task forked to be}
                           {able to yield}
   BEGIN
      INLINE($89/$26/stk); {get 808X sp}
      cp^.bptr:=stk+2;     {save Bp ptr in}
                           {current task frame}
      cp^.state:=ready;    {yielded task ready}
      temp_ptr:=cp;

      {look for next ready task in scheduler loop}
      {there must be at least one or else}

      WHILE (temp_ptr^.link^.state <> ready) DO
         temp_ptr:=temp_ptr^.link;

      cp:=temp_ptr^.link;  {cp points at new task}
      cp^.state:=running;  {indicate running}
      bp:=cp^.bptr;        {get the bp of task}

     INLINE($8B/$2E/bp);   {restore it to 808X bp}
   END
   ELSE
   BEGIN
      writeln('Cannot yield only single task running');
      halt;
   END;

END;
*)

PROCEDURE Yield;

{This version in assembly language for}
{speed. See version above for comments.}

BEGIN

   IF cp^.link <> cp THEN  {must have more than}
                           {one task forked to be}
                           {able to yield}
   BEGIN
      INLINE($C6/$06/child_process/$00/
                              {child_process is false}
             $C4/$3E/cp/      {les di,[cp]}
             $89/$E0/         {mov ax,sp}
             $05/$02/$00/     {add ax,2}
             $26/             {es:}
             $89/$45/$04/     {mov [di+4],ax}
             $26/             {es:}
             $C6/$45/$06/$00/ {mov byte ptr [di+6],0}
             $89/$FB/         {L1: mov bx,di}
             $26/             {es:}
             $C4/$1F/         {les bx,[bx]}
             $26/             {es:}
             $80/$7F/$06/$00/ {cmp byte ptr [bx+6],0}
             $74/$04/         {je L2}
             $89/$DF/         {mov di,bx}
             $EB/$F0/         {jmp L1}
             $89/$1E/cp/      {L2: mov [cp],bx}
             $8C/$06/cp+2/    {    mov [cp+2],es}
             $26/             {es:}
             $C6/$47/$06/$02/ {mov byte ptr [bx+6],2}
             $26/             {es:}
             $8B/$6F/$04);    {mov bp,[bx+4]}
   END
   ELSE
   BEGIN
      writeln('Cannot yield only single task running');
      halt;
   END;

END;

(*
PROCEDURE Wait;  {put current task in wait mode}
                 {until a send makes it ready}

{Due to constraints of this kernel, parameters}
{cannot be passed directly to the wait procedure.}
{To overcome this limitation, a global variable}
{called waitfor is used. The address of the}
{tcbptr on which to wait should be stored in}
{waitfor. See the fifo routines for an example of}
{the proper usage of Wait.}

BEGIN

   child_process:=false;   {reset variable}
   IF cp^.link <> cp THEN  {must have more than}
                           {one task forked to be}
                           {able to wait}
   BEGIN
      waitfor^ := cp;      {waitfor points at the}
                           {current task}

      INLINE($89/$26/stk); {get 808X sp}
      cp^.bptr:=stk+2;     {save it in current}
                           {task frame}
      cp^.state:=waiting;  {task now waiting}
      temp_ptr:=cp;

      {look for next ready task in scheduler loop}
      {there must be at least one or else}

      WHILE (temp_ptr^.link^.state <> ready) DO
         temp_ptr:=temp_ptr^.link;

      cp:=temp_ptr^.link;  {cp points at new task}
      cp^.state:=running;  {indicate running}
      bp:=cp^.bptr;        {get bp for this task}
      INLINE($8B/$2E/bp);  {restore it to 808X bp}
   END
   ELSE
   BEGIN
      writeln('Cannot wait only single task running');
      halt;
   END;

END;
*)

PROCEDURE Wait;  {put current task in wait mode}
                 {until a send makes it ready}

BEGIN

   IF cp^.link <> cp THEN  {must have more than}
                           {one task forked to be}
                           {able to wait}
   BEGIN
      waitfor^ := cp;      {waitfor points at the}
                           {current task}
      INLINE($C6/$06/child_process/$00/
                              {child_process is false}
             $C4/$3E/cp/      {les di,[cp]}
             $89/$E0/         {mov ax,sp}
             $05/$02/$00/     {add ax,2}
             $26/             {es:}
             $89/$45/$04/     {mov [di+4],ax}
             $26/             {es:}
             $C6/$45/$06/$01/ {mov byte ptr [di+6],1}
             $89/$FB/         {L1: mov bx,di}
             $26/             {es:}
             $C4/$1F/         {les bx,[bx]}
             $26/             {es:}
             $80/$7F/$06/$00/ {cmp byte ptr [bx+6],0}
             $74/$04/         {je L2}
             $89/$DF/         {mov di,bx}
             $EB/$F0/         {jmp L1}
             $89/$1E/cp/      {L2: mov [cp],bx}
             $8C/$06/cp+2/    {    mov [cp+2],es}
             $26/             {es:}
             $C6/$47/$06/$02/ {mov byte ptr [bx+6],2}
             $26/             {es:}
             $8B/$6F/$04);    {mov bp,[bx+4]}

   END
   ELSE
   BEGIN
      writeln('Cannot wait only single task running');
      halt;
   END;

END;


PROCEDURE Send(VAR s:tcbptr);

{Make the specified task ready for next scheduler}
{go around}

BEGIN

   s^.state:=ready;    {task state is ready}
   s:=NIL;             {clear pointer}

END;



PROCEDURE Pause(t:integer);

{Pause the execution of a task for t 1/4 sec}
{intervals. Note even t results in more}
{accurate timmings.}

 FUNCTION tic_count : integer;

 {Get the current tic count from the Bios}

 VAR

    regs: register_type;

 BEGIN

    regs.ax:=0;         {request clock tic read}
    intr($1A,regs);
    tic_count:=regs.dx; {LSB of count in dx}

 END;


VAR

   tics,i: integer;

BEGIN

   tics:=0;             {initial tic count to 0}
   IF t > 0 THEN        {if a legal tic count}
   BEGIN
      FOR i:=1 TO t DO  {250 msec = 4.55 tics}
         IF odd(i) THEN {use this algorithm for}
                        {approximation}
            tics:=tics+4 {250 msec = 4.5 tics}
         ELSE
            tics:=tics+5;
      {add tics to current tic_count to get}
      tics:=tics+tic_count;    {target time}

      REPEAT
         yield;    {return when tic count is}
                   {reached or exceeded}
      UNTIL tics <= tic_count;
   END
   ELSE
      writeln('Bad tic count specified');

END;


PROCEDURE Init_Kernel;

{This procedure initializes the multitasking}
{for use. It sets up the TCB for task 0 and}
{indicates that it is running.}

BEGIN

   turbodseg:=dseg;     {save turbo data segment}
   frame_ptr:= $FFFE;   {initial stack location}
   next_id:=0;          {first task id}
   new(new_tcb_ptr);    {get new tcb in dseg}
   cp:=new_tcb_ptr;     {cp points at tcb}
   cp^.link:=cp;        {points at itself}
   cp^.state:=running;  {in running state}
   cp^.id:=next_id;     {id = 0}

   {now allociate 1st frame for task 0}
   frame_ptr:=frame_ptr-task_stack_size;

END;



{********* Begin Semaphore Procedures **********}

PROCEDURE Initialize_semaphore(VAR s:semaphore);

{Initialize a semaphore data structure}

BEGIN

   s.count := 0;       {indicate resource is}
                       {available}
   s.signal:=NIL;      {and that there are no}
                       {waiters}

END;



PROCEDURE Alloc(VAR s:semaphore);

{This procedure allociates exclusive use of a}
{resource to the task that executes it. This}
{claim is maintained even though the task}
{gives up control of the CPU via a yield etc.}

BEGIN

   WHILE s.count <> 0 DO  {wait for semaphore}
                          {controlled resource}
                          {to become available}
   BEGIN
      waitfor := addr (s.signal);
      wait;
   END;                   {then}
   s.count:=1;            {claim it}

END;


PROCEDURE Dealloc(VAR s:semaphore);

{This procedure deallociates a resource.}
{Note this routine yields so the deallociated}
{resource has a chance of being used}
{immediately}

BEGIN

   s.count:=0;     {remove claim on resource}
   send(s.signal); {and awaken the waiting task}
   yield;          {give other tasks a chance}

END;

{End of kernel procedures}




[LISTINGS TWO]

{************************************************}
{***                                          ***}
{***       Menu System support procedures     ***}
{***      for the serial protocol analyzer    ***}
{***                 written by               ***}
{***              Craig A. Lindley            ***}
{***                                          ***}
{***    Ver: 2.0     Last update: 08/15/87    ***}
{***                                          ***}
{************************************************}

CONST

 {max # of video attributes - 1}
 AttribMax = 5;

 Attributes: ARRAY[0..AttribMax] OF
 RECORD
    f,              {foreground color}
    b: Integer;     {background color}
 END

 {Attributes are:}
 {Low, High, Rev, LowBlink, HighBlink, RevBlink}

  = ((f:7; b:0) , (f:15;b:0) , (f:0; b:7),
     (f:23;b:0) , (f:31;b:0) , (f:16;b:7));


 {menu display lines relative to screen line 1}
 MenuLine1 = 2;
 MenuLine2 = 3;
 MenuLine3 = 4;
 MenuLine4 = 5;

 {max A tree dimensions}
 level2width = 7;
 level3width = 5;
 level4width = 7;

 home=    #199;    {home key code}
 larrow=  #203;    {left cursor arrow code}
 rarrow=  #205;    {right cursor arrow code}
 endkey=  #207;    {end key code}
 bs=      #08;     {backspace key}
 lf=      #10;     {line feed code}
 cr=      #13;     {carrage return code}
 Esc=     #27;     {escape key code}
 sp=      #32;     {ascii space code}

TYPE

 AttribType  = (Low ,High, Rev, LowBlink,
                HighBlink, RevBlink);

 {data structure for atree menu entry}
 {see text for details}

 menu_entry=
 RECORD
    title:  STRING[10];
     desc:  STRING[40];
    chars:  STRING[8];
    index:  Byte;
    ccode:  Byte;
 END;

tree = ARRAY[0..level2width,
             0..level3width,
             0..level4width]
       OF menu_entry;

VAR

 ExitMenu,
 ExitProgram:      Boolean;

 ind1,ind2,
 ind3,
 selector,
 cmd_code,
 level:            Byte;

 {atree data structure used for nested menus}
 atree:            tree;


{******* Keyboard and Display Procedures ********}

PROCEDURE Beep;

BEGIN

   Sound(1000);
   Pause(1);
   NoSound;

END;


FUNCTION GetKey : Byte;

VAR

 Ch: Char;
 B:  Byte;

 CurrentX,
 CurrentY:  Integer;

BEGIN

  {save cursor position}
  {because we are going to yield}
  {and loose control of display}

  CurrentX := WhereX;
  CurrentY := WhereY;

  {yield until a key is pressed}

  WHILE NOT keypressed DO
      yield ;

  {put the cursor back}
  GoToXY(CurrentX,CurrentY);

  {read the key}
  read(kbd,Ch);

  {see if its an extended key}
  IF (Ch = Esc) AND keypressed THEN
  BEGIN
    {if so read again and mark}
    {as extended by setting MSB}
    read(kbd,Ch);
    B := ord(Ch)+128;
  END
  ELSE
    {in either case B has key code}
    B := ord(Ch);

  GetKey := B;

END;


FUNCTION repl (Count:Integer; ch:Char):FullString;

{replicate a char into a string of}
{specified length}

VAR

 i:               Integer;

BEGIN

   FOR i:=1 TO Count DO repl[i]:=ch;
   repl[0]:=Char(Count);

END;


PROCEDURE WriteString (Stng:FullString;
                       Attrib:AttribType);

BEGIN

   {set the foreground and background}
   {colors and write the string}

   WITH Attributes[ORD(Attrib)] DO
   BEGIN
      TextColor(f);
      TextBackGround(b);
   END;
   write(Stng);

   {go back to low video mode}
   WITH Attributes[ORD(Low)] DO
   BEGIN
      TextColor(f);
      TextBackGround(b);
   END;

END;


PROCEDURE WriteStringAt (Stng:FullString;
                         Attrib:AttribType;
                         X,Y:Integer);

BEGIN

 GoToXY(X,Y);
 WriteString(Stng,Attrib);

END;


{*********** Start of Menu Procedures ***********}

PROCEDURE center (Line,Width:Integer;
                  Outstr:FullString;
                  Attrib:AttribType);

{Center and write string with attribute on a}
{specified line in a given width field}

BEGIN

   GoToXY(1,Line);
   Clreol;
   GoToXY((Width DIV 2)-(length(Outstr) DIV 2),
           Line);
   WriteString(Outstr,Attrib);

END;


PROCEDURE DrawFrame (x,y,width,height:Integer);

VAR

 top,bottom:     Str80;

BEGIN

   top:=   repl(width,Char(205));
   bottom:=repl(width,Char(205));
   WriteStringAt(top,High,x,y);
   WriteStringAt(bottom,High,x,y+height-1);

END;


PROCEDURE DrawMenuFrame;

BEGIN

   DrawFrame(1,1,80,6);
   center(MenuLine1,78,
          ' Serial Protocol Analyzer Menu ',Rev);

END;


PROCEDURE DisplayMenu (ind1,ind2,ind3,level,
                       selector:Byte);

VAR

 tind1,tind2,
 tind3,i:     Integer;
 attrib:      AttribType;
 titlestr:    FullString;

BEGIN
   titlestr:='-- '+
     atree[ind1,ind2,ind3].title+' Selection'+' --';
   center(MenuLine2,78,titlestr,Low);
   GoToXY(1,MenuLine4);
   clreol;
   GoToXY(9,MenuLine3);
   clreol;

   FOR i:= 1 TO atree[ind1,ind2,ind3].index DO
   BEGIN
      CASE level OF
         1: ind1:=i;
         2: ind2:=i;
         3: ind3:=i;
      END;
      IF selector <> i THEN  {item not selected}
          attrib:=Low
      ELSE
      BEGIN
         attrib:=Rev;
         tind1:=ind1;
         tind2:=ind2;
         tind3:=ind3;
      END;
      WriteString(atree[ind1,ind2,ind3].title,
                  attrib);
      write('   '); {spaces to separate items}
   END;
   GoToXY(9,MenuLine4);
   clreol;
   write(atree[tind1,tind2,tind3].desc);

END;


PROCEDURE ProcessCr (VAR ind1,ind2,ind3,level,
                     selector,cmd_code:Byte);

BEGIN
 {assign selector to index as it was picked}
 CASE level OF
    1: ind1:=selector;
    2: ind2:=selector;
    3: ind3:=selector;
 END;
 {if entry is terminal entry}
 IF atree[ind1,ind2,ind3].index = 0 THEN
 BEGIN
    {get associated cmd code}
    cmd_code:=atree[ind1,ind2,ind3].ccode;

    {process depending upon level}
    CASE level OF
       1: ind1:=0;
       2: BEGIN
             ind1:=0;
             ind2:=0;
             ind3:=0;
             level:=1;
          END;
       3: BEGIN
             ind2:=0;
             ind3:=0;
             level:=2;
          END;
    END;
 END
 ELSE
 BEGIN
    {down to next menu level}
    level:=level+1;
 END;
 {select set to 1st item}
 selector:=1;

END;


PROCEDURE ProcessMenu (VAR ind1,ind2,ind3,level,
                       selector,cmd_code:Byte);

VAR

 key:          Char;
 position:     Integer;

BEGIN

   key:=upcase(GetKey);
   CASE key OF
    rarrow: BEGIN
               selector:=selector+1;
               IF selector >
                  length(atree[ind1,ind2,ind3].chars) THEN
                   selector:=1;
            END;

    larrow: BEGIN
               selector:=selector-1;
               IF selector < 1 THEN
                  selector :=
                 length(atree[ind1,ind2,ind3].chars);
            END;

    endkey: selector :=
              length(atree[ind1,ind2,ind3].chars);

      home: selector:=1;

        cr: ProcessCr(ind1,ind2,ind3,level,
                      selector,cmd_code);

       esc: cmd_code := 1;     {force exit}

'A'..'Z','0'..'9'  {1st letter of menu item ?}
          : BEGIN
               position :=
                 pos(key,atree[ind1,ind2,ind3].chars);
               IF position <> 0 THEN
               BEGIN             {is 1st letter}
                  selector:=position;
                  ProcessCr(ind1,ind2,ind3,level,
                            selector,cmd_code);
               END
               ELSE     {is not so beep}
                  Beep;
            END;
      ELSE              {invalid so beep}
          beep;
   END;

END;


{Procedure used to process the operator}
{selected commands}
{Defined in the main program's code}

PROCEDURE ProcessCmd (cmd_code: Byte); FORWARD;


FUNCTION DoMenu : Boolean;

VAR

 Line: Integer;

BEGIN

   {stop update of screen info}
   UpdateScreenStatus := False;

   {clear top 6 display lines}
   FOR Line := 1 TO 6 DO
   BEGIN
      GoToXY(1,Line);
      Clreol;
   END;

   {and 2nd to the last one}
   GoToXY(1,24);
   Clreol;

   DrawMenuFrame;
   {clear the menu exit flag}
   ExitMenu := False;

   {clear the program exit flag}
   ExitProgram := False;

   {selector to 1st item in menu}
   selector:=1;

   {command code initialized to 0 }
   cmd_code:=0;

   {1st tier of heirachy}
   level:=1;

   {root menu array location}
   ind1:=0;
   ind2:=0;
   ind3:=0;

   REPEAT

      DisplayMenu (ind1,ind2,ind3,level,selector);
      ProcessMenu (ind1,ind2,ind3,level,selector,
                   cmd_code);
      ProcessCmd  (cmd_code);
      cmd_code:=0;  {reset code selected last}

   UNTIL ExitMenu;

   {start update of screen info}
   UpdateScreenStatus := True;
   {return flag}
   DoMenu := ExitProgram;

END;


PROCEDURE Init_Menu;

{initialize the menu tree}

BEGIN

   fillchar(atree,sizeof(atree),0);

   WITH atree[0,0,0] DO
   BEGIN
      title:='Main Menu';
      chars:='QPDTFCE';
      index:=7;
   END;

   WITH atree[1,0,0] DO
   BEGIN
      title:='Quit';
      desc:='Exit Analyzer Menus';
      ccode:=1;
   END;

   WITH atree[2,0,0] DO
   BEGIN
      title:='Parameters';
      desc:='Set Serial Parameters';
      chars:='QBSWP';
      index:=5;
   END;

   WITH atree[2,1,0] DO
   BEGIN
      title:='Quit';
      desc:='Exit this submenu';
   END;

   WITH atree[2,2,0] DO
   BEGIN
      title:='Baud Rate';
      desc:='Change Serial Baud Rate';
      chars:='Q361249';
      index:=7;
   END;

   WITH atree[2,2,1] DO
   BEGIN
      title:='Quit';
      desc:='Exit this submenu';
   END;

   WITH atree[2,2,2] DO
   BEGIN
      title:='300';
      ccode:=2;
   END;

   WITH atree[2,2,3] DO
   BEGIN
      title:='600';
      ccode:=3;
   END;

   WITH atree[2,2,4] DO
   BEGIN
      title:='1200';
      ccode:=4;
   END;

   WITH atree[2,2,5] DO
   BEGIN
      title:='2400';
      ccode:=5;
   END;

   WITH atree[2,2,6] DO
   BEGIN
      title:='4800';
      ccode:=6;
   END;

   WITH atree[2,2,7] DO
   BEGIN
      title:='9600';
      ccode:=7;
   END;

   WITH atree[2,3,0] DO
   BEGIN
      title:='Stop Bits';
      desc:='Set Number of Stop Bits';
      chars:='Q12';
      index:=3;
   END;

   WITH atree[2,3,1] DO
   BEGIN
      title:='Quit';
      desc:='Exit this submenu';
   END;

   WITH atree[2,3,2] DO
   BEGIN
      title:='1';
      ccode:=8;
   END;

   WITH atree[2,3,3] DO
   BEGIN
      title:='2';
      ccode:=9;
   END;

   WITH atree[2,4,0] DO
   BEGIN
      title:='Word Len.';
      desc:='Set Bits / Word';
      chars:='Q5678';
      index:=5;
   END;

   WITH atree[2,4,1] DO
   BEGIN
     title:='Quit';
     desc:='Exit this SubMenu';
   END;

   WITH atree[2,4,2] DO
   BEGIN
      title:='5';
      ccode:=10;
   END;

   WITH atree[2,4,3] DO
   BEGIN
      title:='6';
      ccode:=11;
   END;

   WITH atree[2,4,4] DO
   BEGIN
      title:='7';
      ccode:=12;
   END;

   WITH atree[2,4,5] DO
   BEGIN
      title:='8';
      ccode:=13;
   END;

   WITH atree[2,5,0] DO
   BEGIN
      title:='Parity';
      desc:='Set Serial Parity';
      chars:='QOEN';
      index:=4;
   END;

   WITH atree[2,5,1] DO
   BEGIN
      title:='Quit';
      desc:='Exit this SubMenu';
   END;

   WITH atree[2,5,2] DO
   BEGIN
      title:='Odd';
      ccode:=14;
   END;

   WITH atree[2,5,3] DO
   BEGIN
      title:='Even';
      ccode:=15;
   END;

   WITH atree[2,5,4] DO
   BEGIN
      title:='None';
      ccode:=16;
   END;

   WITH atree[3,0,0] DO
   BEGIN
     title:='Display';
     desc:='Select Channels for Display';
     chars:='Q12B';
     index:=4;
   END;

   WITH atree[3,1,0] DO
   BEGIN
      title:='Quit';
      desc:='Exit this SubMenu';
   END;

   WITH atree[3,2,0] DO
   BEGIN
      title:='COM1';
      ccode:=17;
   END;

   WITH atree[3,3,0] DO
   BEGIN
      title:='COM2';
      ccode:=18;
   END;

   WITH atree[3,4,0] DO
   BEGIN
      title:='Both 1 & 2';
      ccode:=19;
   END;

   WITH atree[4,0,0] DO
   BEGIN
      title:='Trigger';
      desc:='Controls Trigger Modes';
      chars:='QCPMS';
      index:=5;
   END;

   WITH atree[4,1,0] DO
   BEGIN
     title:='Quit';
     desc:='Exit this SubMenu';
   END;

   WITH atree[4,2,0] DO
   BEGIN
      title:='Channel';
      desc:='Set Trigger Channel';
      chars:='Q12';
      index:=3;
   END;

   WITH atree[4,2,1] DO
   BEGIN
      title:='Quit';
      desc:='Exit this SubMenu';
   END;

   WITH atree[4,2,2] DO
   BEGIN
      title:='COM1';
      ccode:=20;
   END;

   WITH atree[4,2,3] DO
   BEGIN
      title:='COM2';
      ccode:=21;
   END;

   WITH atree[4,3,0] DO
   BEGIN
      title:='Pattern';
      desc:='Input Pattern for Trigger';
      ccode:=22;
   END;

   WITH atree[4,4,0] DO
   BEGIN
      title:='Mode';
      desc:='Select Trigger Mode';
      chars:='QBAE';
      index:=4;
   END;

   WITH atree[4,4,1] DO
   BEGIN
      title:='Quit';
      desc:='Exit this SubMenu';
   END;

   WITH atree[4,4,2] DO
   BEGIN
      title:='Before';
      desc:='Display Data Before Trigger';
      ccode:=23;
   END;

   WITH atree[4,4,3] DO
   BEGIN
      title:='After';
      desc:='Display Data After Trigger';
      ccode:=24;
   END;

   WITH atree[4,4,4] DO
   BEGIN
      title:='Enable';
      desc:='Enable the Trigger';
      ccode:=25;
   END;

   WITH atree[4,5,0] DO
   BEGIN
      title:='Stop';
      desc:='Stop waiting for Trigger Event';
      ccode:=26;
   END;

   WITH atree[5,0,0] DO
   BEGIN
      title:='Format';
      desc:='Set Display Format';
      chars:='QAHS';
      index:=4;
   END;

   WITH atree[5,1,0] DO
   BEGIN
      title:='Quit';
      desc:='Exit this SubMenu';
   END;

   WITH atree[5,2,0] DO
   BEGIN
      title:='Ascii';
      desc:='Ascii Display Format';
      chars:='QNH';
      index:=3;
   END;

   WITH atree[5,2,1] DO
   BEGIN
      title:='Quit';
      desc:='Exit this SubMenu';
   END;

   WITH atree[5,2,2] DO
   BEGIN
      title:='Normal';
      desc:='Data Only';
      ccode:=27;
   END;

   WITH atree[5,2,3] DO
   BEGIN
      title:='HandShake';
      desc:='Data and HandShake';
      ccode:=28;
   END;

   WITH atree[5,3,0] DO
   BEGIN
      title:='Hex';
      desc:='Hex Display Format';
      chars:='QNH';
      index:=3;
   END;

   WITH atree[5,3,1] DO
   BEGIN
      title:='Quit';
      desc:='Exit this SubMenu';
   END;

   WITH atree[5,3,2] DO
   BEGIN
      title:='Normal';
      desc:='Data Only';
      ccode:=29;
   END;

   WITH atree[5,3,3] DO
   BEGIN
      title:='HandShake';
      desc:='Data and HandShake';
      ccode:=30;
   END;

   WITH atree[5,4,0] DO
   BEGIN
      title:='Spaces';
      desc:='Insert space in data display';
      ccode:=31;
   END;

   WITH atree[6,0,0] DO
   BEGIN
      title:='Control';
      desc:='Alter Analyzer Operation';
      chars:='QASCR';
      index:=5;
   END;

   WITH atree[6,1,0] DO
   BEGIN
      title:='Quit';
      desc:='Exit this SubMenu';
   END;

   WITH atree[6,2,0] DO
   BEGIN
      title:='Acquire';
      desc:='Acquire Data';
      ccode:=32;
   END;

   WITH atree[6,3,0] DO
   BEGIN
      title:='Stop';
      desc:='Stop Data Acquisition';
      ccode:=33;
   END;

   WITH atree[6,4,0] DO
   BEGIN
      title:='Clear';
      desc:='Clear Screen of Data';
      ccode:=34;
   END;

   WITH atree[6,5,0] DO
   BEGIN
      title:='Reset';
      desc:='Reset Analyzer';
      ccode:=35;
   END;

   WITH atree[7,0,0] DO
   BEGIN
      title:='End';
      desc:='End the Analyzer Session';
      ccode:=36;
   END;

END;




[LISTING THREE]



{************************************************}
{***                                          ***}
{***          RS-232 support procedures       ***}
{***      for the serial protocol analyzer    ***}
{***                 written by               ***}
{***              Craig A. Lindley            ***}
{***                                          ***}
{***    Ver: 2.0     Last update: 08/15/87    ***}
{***                                          ***}
{************************************************}

CONST

 COM1 = $3f8;              {com one port addr}
 COM2 = $2f8;              {com two port addr}

 {table of values for the various baud rates}
 {supported by the 8250. Rates from 50..9600}

 BaudRate: ARRAY[1..15] OF Integer =
   ($900,$600,$417,$359,$300,$180,$0C0,$060,
    $040,$03A,$030,$020,$018,$010,$00C);

 {8259 registers}

 Int_Mask_Reg = $21; {interrupt enable register}
 Int_Cmd_Reg  = $20; {command register}
 End_Int_Cmd  = $20; {end of interrupt cmd}

 {offsets from PortAddr for the various}
 {8250 registers}

 DLL            = 0;
 DLM            = 1;
 Int_Enable_Reg = 1;
 Int_Id_Reg     = 2;
 LineControl    = 3;
 ModemControl   = 4;
 LineStatus     = 5;
 ModemStatus    = 6;

 {Status bit definitions}

 DataRdyBit     = $01;
 DTRBit         = $01;
 Out2Bit        = $08;
 ORBit          = $01;
 RTSBit         = $02;
 PEBit          = $02;
 FEBit          = $04;
 BrkBit         = $08;
 CTSBit         = $10;
 DSRBit         = $20;
 TxRdyBit       = $20;
 RIBit          = $40;
 CDBit          = $80;

TYPE

 ParityType = (odd,even,none);

VAR

 {Global storage of COM parameters}
 {Used by the SetNewCOMParameter}
 {procedure}

 COM_Rate,
 COM_StopBits,
 COM_DataBits:  Integer;
 COM_Parity:    ParityType;


{************** Serial Procedures ***************}

PROCEDURE Enable_Serial_Device (PortAddr:Integer);

VAR

 Temp:   Byte;

BEGIN

   {clear the 8250 serial device of any garbage}
   {by reading data port, int id reg. line}
   {status reg and modem status reg}

    Temp := port[PortAddr];
    Temp := port[PortAddr+Int_Id_Reg];
    Temp := port[PortAddr+LineStatus];
    Temp := port[PortAddr+ModemStatus];

    {read 8259 int mask reg and set IRQ3 or IRQ4}
    {low to enable requested interrupts.}
    {write result back to 8259 when finished}

    Temp := port[Int_Mask_Reg];
    IF PortAddr = COM1 THEN
       Temp := Temp AND $EF
    ELSE
       Temp := Temp AND $F7;
    port[Int_Mask_Reg] := Temp;

    {Out2, DTR and RTS high for 8250}
    port[PortAddr+ModemControl] :=
         Out2Bit + DTRBit + RTSBit;

    {all ints active except Tx}
    port[PortAddr+Int_Enable_Reg] := $0D;
    delay(100);

END;


PROCEDURE Disable_Serial_Devices;

VAR

 Temp:   Byte;

BEGIN

   {Read int mask in 8259, set both IRQ3}
   {and IRQ4 bits high to disable. Set Out2}
   {low for both COM1 and COM2 to prevent ints}
   {from leaving the async card and finally}
   {disable all interrupt sources in the UART}

   Temp := port[Int_Mask_Reg];
   Temp := Temp OR $18;
   port[Int_Mask_Reg] := Temp;
   port[COM1+ModemControl] := 0;
   port[COM2+ModemControl] := 0;
   port[COM1+Int_Enable_Reg] := 0;
   port[COM2+Int_Enable_Reg] := 0;

END;


PROCEDURE COM1_ISR;

{COM1 interrupt service routine}

VAR

 SerialInfo:  DataRec;

 Temp:        Integer;

 LineState,
 ModemState:  Byte;

BEGIN

   {read linestatus and modem status and}
   {combine into COM1_Status. See text for}
   {bit encoding.}

   LineState := port[COM1+LineStatus];
   ModemState:= port[COM1+ModemStatus];
   Temp := (LineState SHR 1) AND $0F;

   {COM1_Status has the UART state in 8 bits}
   {this is comprised of status info only, no data}

   COM1_Status := (ModemState AND $F0) OR lo(Temp);

   {if there is data to receive and we are}
   {acquiring data}

   IF (((LineState AND DataRdyBit) <> 0) AND
         COM1_Data_Acquire) THEN
   BEGIN
      {if there is room in the COM1 input fifo}
      IF COM1_Input_Fifo.Ovd.Count <
                    SerialDataFifoSize THEN
      BEGIN
         {Put received data and status into fifo}
         SerialInfo.Data   := port[COM1];
         SerialInfo.Status := COM1_Status;
         PutSerialData(SerialInfo,COM1_Input_Fifo);
      END
      ELSE
      BEGIN
         writeln('COM1 Input fifo overflowed');
         halt;
     END;
   END;

   {signal end of int to 8259}
   port[Int_Cmd_Reg] := End_Int_Cmd;

END;


PROCEDURE COM2_ISR;

{COM2 interrupt service routine}

VAR

 SerialInfo:  DataRec;

 Temp:        Integer;

 LineState,
 ModemState:  Byte;

BEGIN

   {read linestatus and modem status and}
   {combine into COM2_Status. See text for}
   {bit encoding.}

   LineState := port[COM2+LineStatus];
   ModemState:= port[COM2+ModemStatus];
   Temp := (LineState SHR 1) AND $0F;

   {COM2_Status has the UART state in 8 bits}
   {this is comprised of status info only, no data}

   COM2_Status := (ModemState AND $F0) OR lo(Temp);

   {if there is data to receive and we are}
   {acquiring data}

   IF (((LineState AND DataRdyBit) <> 0) AND
         COM2_Data_Acquire) THEN
   BEGIN
      {if there is room in the COM2 input fifo}
      IF COM2_Input_Fifo.Ovd.Count <
                    SerialDataFifoSize THEN
      BEGIN
        {Put received data and status into fifo}
        SerialInfo.Data   := port[COM2];
        SerialInfo.Status := COM2_Status;
        PutSerialData(SerialInfo,COM2_Input_Fifo);
      END
      ELSE
      BEGIN
         writeln('COM2 Input fifo overflowed');
         halt;
      END;
   END;

   {signal end of int to 8259}
   port[Int_Cmd_Reg] := End_Int_Cmd;

END;


PROCEDURE COM1_Int_Service_Routine;

BEGIN

 INLINE($50/$53/$51/$52/$57/   {Push ax,bx,cx,dx,}
        $56/$06/$1e/           {di,si,es,ds}
        $2e/$a1/turbodseg/     {mov ax,cs:turbodseg}
        $8e/$d8/               {mov ds,ax}
        $fb);                  {sti}

 COM1_ISR;

 {standard interrupt service routine postamble}

 INLINE($fa/$1f/$07/$5e/$5f/   {interrupts off}
        $5a/$59/$5b/$58/       {Pop ds,es,si,di,}
                               {dx,cx,bx,ax}
        $5d/$5d/$cf);          {trash sp, restore}
                               {Bp and iret}

END;


PROCEDURE COM2_Int_Service_Routine;

BEGIN

 INLINE($50/$53/$51/$52/$57/   {Push ax,bx,cx,dx,}
        $56/$06/$1e/           {di,si,es,ds}
        $2e/$a1/turbodseg/     {mov ax,cs:turbodseg}
        $8e/$d8/               {mov ds,ax}
        $fb);                  {sti}

 COM2_ISR;

 {standard interrupt service routine postamble}

 INLINE($fa/$1f/$07/$5e/$5f/   {interrupts off}
        $5a/$59/$5b/$58/       {Pop ds,es,si,di,}
                               {dx,cx,bx,ax}
        $5d/$5d/$cf);          {trash sp, restore}
                               {Bp and iret}

END;



PROCEDURE Install_Serial_Handlers;

BEGIN

 WITH regs DO      {install into IRQ3 & 4}
 BEGIN
    ah := $35;       {get vector func. code}
    al := $0B;       {for IRQ3}
    msdos(regs);     {call dos to get vector}
    OldIRQ3_CS := es;{save code seg}
    OldIRQ3_IP := bx;{and instruction ptr}

    ah := $35;       {get vector func. code}
    al := $0C;       {for IRQ4}
    msdos(regs);     {call dos to get vector}
    OldIRQ4_CS := es;{save code seg}
    OldIRQ4_IP := bx;{and instruction ptr}

    ah := $25;       {set vector func. code}
    al := $0C;       {for IRQ4}
    ds := cseg;      {code in our segment}
                     {at this offset}
    dx := ofs(COM1_Int_Service_Routine);
    msdos(regs);     {call dos to set vector}

    ah := $25;       {set vector func. code}
    al := $0B;       {for IRQ3}
    ds := cseg;      {code in our segment}
                     {at this offset}
    dx := ofs(COM2_Int_Service_Routine);
    msdos(regs);     {call dos to set vector}

 END;
END;


PROCEDURE Remove_Serial_Handlers;

BEGIN
   {Put saved vectors for IRQ3 and IRQ4 back}
   WITH regs DO
   BEGIN
      ah := $25;
      al := $0C;
      ds := OldIRQ4_CS;
      dx := OldIRQ4_IP;
      msdos(regs);

      ah := $25;
      al := $0B;
      ds := OldIRQ3_CS;
      dx := OldIRQ3_IP;
      msdos(regs);
   END;

END;


PROCEDURE Set_Serial_Parameters
     (PortAddr,Baud,StopBits,DataBits:Integer;
      Parity: ParityType);
VAR

 Temp,
 Rate:   Integer;

BEGIN

   {set DLAB high for divisor regs}
   port[PortAddr+LineControl] := $80;

   {look up rate word in table}
   Rate := BaudRate[Baud];

   {MSB into most sign divisor reg}
   port[PortAddr+DLM] := hi(Rate);

   {LSB into less sign divisor reg}
   port[PortAddr+DLL] := lo(Rate);

   {move databits into 2 least sign bits}
   {add in stop bit into bit pos 2}
   {set parity enable and parity even}
   {bits if appropriate}

   Temp := (DataBits - 5) AND $03;
   Temp := Temp OR ((StopBits - 1) SHL 2);
   CASE Parity OF
     odd:  Temp := Temp + $08;
    even: Temp := Temp + $18;
    none: ;
   END;

   {remove DLAB and setup parameters}
   port[PortAddr+LineControl] := lo(Temp);
   delay(100);

END;


FUNCTION Get_Serial_Status (PortAddr:Integer)
              :Integer;

VAR

 Temp:  Integer;

BEGIN

   {Get full 16 bits of COM port status}
   {grouped as ModemStatus:LineStatus}

   Temp := port[PortAddr+ModemStatus];
   Temp := Temp SHL 8;
   Temp := Temp OR port[PortAddr+LineStatus];
   Get_Serial_Status := Temp;

END;


PROCEDURE SetNewCOMParameter;

BEGIN

   {take the COM ports down}
   Disable_Serial_Devices;

   {Set the COM ports to the global parameters}

   Set_Serial_Parameters(COM1,COM_Rate,
          COM_StopBits,COM_DataBits,COM_Parity);
   Set_Serial_Parameters(COM2,COM_Rate,
          COM_StopBits,COM_DataBits,COM_Parity);

   {bring COM ports back up}
   Enable_Serial_Device(COM1);
   Enable_Serial_Device(COM2);

END;





[LISTING FOUR]


{ Changes to the multitasking kernel }

CONST
  task_stack_size = 1000;
PROCEDURE Yield;
{This version in assembly language for}
{speed. See original version for comments.}
BEGIN
   IF cp^.link <> cp THEN  {must have more than}
                           {one task forked to be}
                           {able to yield}
   BEGIN
      INLINE($C6/$06/child_process/$00/
                              {child_process is false}
             $C4/$3E/cp/      {les di,[cp]}
             $89/$E0/         {mov ax,sp}
             $05/$02/$00/     {add ax,2}
             $26/             {es:}
             $89/$45/$04/     {mov [di+4],ax}
             $26/             {es:}
             $C6/$45/$06/$00/ {mov byte ptr [di+6],0}
             $89/$FB/         {L1: mov bx,di}
             $26/             {es:}
             $C4/$1F/         {les bx,[bx]}
             $26/             {es:}
             $80/$7F/$06/$00/ {cmp byte ptr [bx+6],0}
             $74/$04/         {je L2}
             $89/$DF/         {mov di,bx}
             $EB/$F0/         {jmp L1}
             $89/$1E/cp/      {L2: mov [cp],bx}
             $8C/$06/cp+2/    {    mov [cp+2],es}
             $26/             {es:}
             $C6/$47/$06/$02/ {mov byte ptr [bx+6],2}
             $26/             {es:}
             $8B/$6F/$04);    {mov bp,[bx+4]}
   END
   ELSE
   BEGIN
      writeln('Cannot yield only single task running');
      halt;
   END;
END;

PROCEDURE Wait;  {put current task in wait mode}
                 {until a send makes it ready}
BEGIN
   IF cp^.link <> cp THEN  {must have more than}
                           {one task forked to be}
                           {able to wait}
   BEGIN
      waitfor^ := cp;      {waitfor points at the}
                           {current task}
      INLINE($C6/$06/child_process/$00/
                              {child_process is false}
             $C4/$3E/cp/      {les di,[cp]}
             $89/$E0/         {mov ax,sp}
             $05/$02/$00/     {add ax,2}
             $26/             {es:}
             $89/$45/$04/     {mov [di+4],ax}
             $26/             {es:}
             $C6/$45/$06/$01/ {mov byte ptr [di+6],1}
             $89/$FB/         {L1: mov bx,di}
             $26/             {es:}
             $C4/$1F/         {les bx,[bx]}
             $26/             {es:}
             $80/$7F/$06/$00/ {cmp byte ptr [bx+6],0}
             $74/$04/         {je L2}
             $89/$DF/         {mov di,bx}
             $EB/$F0/         {jmp L1}
             $89/$1E/cp/      {L2: mov [cp],bx}
             $8C/$06/cp+2/    {    mov [cp+2],es}
             $26/             {es:}
             $C6/$47/$06/$02/ {mov byte ptr [bx+6],2}
             $26/             {es:}
             $8B/$6F/$04);    {mov bp,[bx+4]}
   END
   ELSE
   BEGIN
      writeln('Cannot wait only single task running');
      halt;
   END;
END;




[LISTING FIVE]


{************************************************}
{***                                          ***}
{***                Turbo Pascal              ***}
{***         Serial Protocol Analyzer         ***}
{***                 written by               ***}
{***              Craig A. Lindley            ***}
{***                                          ***}
{***    Ver: 2.0     Last update: 08/15/87    ***}
{***                                          ***}
{************************************************}

{$K-,U-,C-,G30,D-}

{ ------- Notes on compiler directives --------- }
{ K-     No stack checking otherwise multitasking}
{         kernal will not run.                   }
{ U-,C-  Turn off user break checks to speed     }
{         screen I/O.                            }
{ G30,D- Buffer standard input device (keyboard) }
{         and disable device checks. This makes  }
{         keyboard respond much faster and be    }
{         buffered.                              }

CONST

 HexDigits:  STRING[16] = '0123456789ABCDEF';

 AsciiStrs: ARRAY[0..31] OF STRING[3] =
 ('Nul','Soh','Stx','Etx','Eot','Enq','Ack','Bel',
  'Bs' ,'Ht' ,'Lf' ,'Vt' ,'Ff' ,'Cr' ,'So' ,'Si' ,
  'Dle','Dc1','Dc2','Dc3','Dc4','Nak','Syn','Etb',
  'Can','Em' ,'Sub','Esc','Fs' ,'Gs' ,'Rs' ,'Vs');


 SerialDataFifoSize = 2000;  {serial data fifo size}
 DisplayFifoSize    = 3000;  {display fifo size}

 {$I multi.pas}              {include the}
                             {multitasking kernel}

TYPE

 FullString  = STRING[255];
 Str80       = STRING[80];
 Str3        = STRING[3];

 DataRec = RECORD
  Data,
  Status:  Byte;
 END;

 DisplayRec = RECORD
  Tag: (FromCOM1,FromCOM2);
  DR: DataRec;
 END;

{This fifo overhead structure is the same for}
{all fifo types regardless of the items to be}
{stored in the fifo. Two types are fifos are }
{defined.}

 OverHead = RECORD     {fifo overhead data}
                       {structure}
    Count,             {# of items in fifo}
    Inptr,             {ptr to where items are}
                       {stored}
    Outptr:   Integer; {ptr to where items are}
                       {fetched}
    NotEmpty,         {ptrs to waiting tasks}
    NotFull:  tcbptr;
 END;


 {definition of a serial data fifo}
 SerialDataFifo = RECORD
   Ovd:  OverHead;           {fifo overhead}
   Data: ARRAY[1..SerialDataFifoSize]
           OF DataRec;       {fifo data area}
 END;

 {definition of display fifo}
 DisplayFifoType = RECORD
   Ovd:  OverHead;
   Data: ARRAY[1..DisplayFifoSize]
           OF DisplayRec;
 END;

 DisplayTriggerType = (Before,After);

VAR

 regs:        register_type;

 {storage for the original IRQ3 & 4 code segment}
 {and instruction pointer addresses}

 OldIRQ3_CS,
 OldIRQ3_IP,
 OldIRQ4_CS,
 OldIRQ4_IP:  Integer;

 {UART status storage variables}

 OldCOM1_Status,
 OldCOM2_Status,
 COM1_Status,
 COM2_Status: Byte;

 {Display formatting boolean flags}

 UpdateScreenStatus,
 AsciiDisplay,
 HandShakeDisplay,
 AddSpace,

 {Data display boolean flags}
 {If true the data from the specified COM port}
 {is tagged and then moved into the display fifo}

 COM1_Display_Data,
 COM2_Display_Data,

 {Data Acquisition boolean flags}
 {Controls acquisiton of data by the Interrupt}
 {Serivce Routines. If true then serial data is}
 {stored by the ISR.}

 COM1_Data_Acquire,
 COM2_Data_Acquire,

 {Variables used for the triggering function}

 TriggerEnabled,
 COM1_Is_Triggered,
 COM2_Is_Triggered:  Boolean;
 TriggerPattern:     Integer;
 TriggerMode:        DisplayTriggerType;

 {Fifo declarations}

 COM1_Input_Fifo,
 COM2_Input_Fifo,
 COM1_Output_Fifo,
 COM2_Output_Fifo:   SerialDataFifo;

 DisplayFifo:        DisplayFifoType;

 {Screen formatting strings built at run time to}
 {format the screen.}

 Line1, Line2,
 Line3, Line4,
 Line5, Line6,
 Line24:        Str80;

 {Cursor storage for DisplayCOMData procedure}

 OldXPos,
 OldYPos:  Integer;

 {Lock for screen control}
 ScreenAccess: Semaphore;

{************ Begin FIFO Procedures ************}

PROCEDURE Init_Fifos;

 PROCEDURE Initialize_fifo(VAR o:OverHead);

 {Initialize a fifo's overhead data structure.}
 {This procedure will work with any type fifo.}
 {This makes the fifo appear empty.}

 BEGIN

    o.Count  := 0;       {count is empty}
    o.Inptr  := 1;       {ptrs to 1st entry}
    o.Outptr := 1;       {put in and take out at}
                         {entry 1}
    o.NotEmpty :=NIL;    {signals to nil}
    o.NotFull  :=NIL;

 END;

BEGIN

   Initialize_fifo(COM1_Input_fifo.Ovd);
   Initialize_fifo(COM1_Output_fifo.Ovd);
   Initialize_fifo(COM2_Input_fifo.Ovd);
   Initialize_fifo(COM2_Output_fifo.Ovd);
   Initialize_fifo(DisplayFifo.Ovd);

END;


PROCEDURE PutSerialData (d:DataRec;
                         VAR f:SerialDataFifo);

BEGIN

   WITH f.Ovd DO
   BEGIN                {check if fifo full}
      IF Count = SerialDataFifoSize THEN
      BEGIN             {if so go to sleep}
         waitfor := addr (NotFull);
         wait;
      END;              {when not full add}
      Count:=Count+1;   {one more to count}
      f.data[Inptr]:=d; {store the data record}
      Inptr:=Inptr+1;   {bump input pointer}
      IF Inptr > SerialDataFifoSize THEN
         Inptr:=1;      {wrap ptr if necessary}

      {if waiters for this fifo wake them}

      IF NotEmpty <> NIL THEN
         send(NotEmpty);
   END;

END;


PROCEDURE GetSerialData (VAR f:SerialDataFifo;
                         VAR d:DataRec);

BEGIN

   WITH f.Ovd DO
   BEGIN               {check if fifo empty}
      IF Count = 0 THEN
      BEGIN            {if so go to sleep}
         waitfor := addr (NotEmpty);
         wait;
      END;
                       {when data is available}
      Count:=Count-1;  {one less to count}
      d :=f.data[Outptr];  {get the data record}
      Outptr:=Outptr+1;{bump output pointer}
      IF Outptr > SerialDataFifoSize THEN
         Outptr:=1;    {wrap ptr if necessary}
      {if waiters for this fifo wake them}

      IF NotFull <> NIL THEN
         send(NotFull);
   END;

END;


PROCEDURE PutDisplayData (d:DisplayRec;
                      VAR f:DisplayFifoType);

BEGIN

   WITH f.Ovd DO
   BEGIN                {check if fifo full}
      IF Count = DisplayFifoSize THEN
      BEGIN             {if so go to sleep}
         waitfor := addr (NotFull);
         wait;
      END;              {when not full add}
      Count:=Count+1;   {one more to count}
      f.data[Inptr]:=d; {store the data record}
      Inptr:=Inptr+1;   {bump input pointer}
      IF Inptr > DisplayFifoSize THEN
         Inptr:=1;      {wrap ptr if necessary}

      {if waiters for this fifo wake them}

      IF NotEmpty <> NIL THEN
         send(NotEmpty);
   END;

END;


PROCEDURE GetDisplayData (VAR f:DisplayFifoType;
                          VAR d:DisplayRec);

BEGIN

   WITH f.Ovd DO
   BEGIN               {check if fifo empty}
      IF Count = 0 THEN
      BEGIN            {if so go to sleep}
         waitfor := addr (NotEmpty);
         wait;
      END;
                       {when data is available}
      Count:=Count-1;  {one less to count}
      d :=f.data[Outptr];  {get the data record}
      Outptr:=Outptr+1;{bump output pointer}
      IF Outptr > DisplayFifoSize THEN
         Outptr:=1;    {wrap ptr if necessary}

      {if waiters for this fifo wake them}

      IF NotFull <> NIL THEN
         send(NotFull);

   END;

END;

{Include the menuing system}

{$I menu.pas}

{Include the serial procedures}

{$I serial.pas}

{********* Additional Serial Procedures *********}


PROCEDURE SetBreak (PortAddr:Integer; State:Boolean);

{Controls the break generation for the specified}
{COM port.}

VAR

 Temp:  Byte;

BEGIN

   {Read the LineControl reg of the 8250}
   {either set or reset the break bit D6}
   {as specified. Write the new reg value}
   {back to the port}

   Temp := port[PortAddr+LineControl];
   IF State THEN
      Temp := Temp OR $40
   ELSE
      Temp := Temp AND $BF;
   port[PortAddr+LineControl] := Temp;

END;


PROCEDURE MakeHandShake (PortAddr:Integer;
                         Status:Byte);

VAR

 Temp:   Byte;

BEGIN

   {Set the bits in the ModemControl reg of the}
   {specified COM port according to the bits}
   {of the variable Status. This procedure is}
   {used to always force the handshake lines of}
   {the COM ports to agree}

   Temp := port[PortAddr+ModemControl];

   IF (Status AND BrkBit) <> 0 THEN
      SetBreak(PortAddr,True)
   ELSE
      SetBreak(PortAddr,False);

   IF (Status AND CTSBit) <> 0 THEN
      Temp := Temp OR $02
   ELSE
      Temp := Temp AND $FD;

   IF (Status AND DSRBit) <> 0 THEN
      Temp := Temp OR $01
   ELSE
      Temp := Temp AND $FE;

   {Store the new value of the ModemControl}
   {register back}

   port[PortAddr+ModemControl] := Temp;

END;


{************** Display Procedures *************}

PROCEDURE BuildDisplay;

{Setup the main SPA display screen}
{The OldCOM?_Status variables are consciencely}
{clobbered to force the screen to be updated}
{after it is built or rebuilt after the menus}
{are displayed}

BEGIN

   OldCOM1_Status    := $FF;
   OldCOM2_Status    := $FF;

   WriteStringAt(Line1,High,1,1);
   WriteStringAt(Line2,Low,1,2);
   WriteStringAt(Line3,Low,1,3);
   WriteStringAt(Line4,Low,1,4);
   WriteStringAt(Line5,Low,1,5);
   WriteStringAt(Line6,Low,1,6);
   WriteStringAt(Line24,Rev,1,24);

END;


PROCEDURE DisplayHandShakeStatus (PortAddr:Integer;
                                  InStatus,
                                  OutStatus:Byte);

CONST

 StatLineNum = 3; {screen line of line status}
 InLineNum   = 4; {screen line of in handshake}
                  {lines}
 OutLineNum  = 5; {screen line of out handshake}
                  {lines}

 CDOffset  = 11;  {offsets on a screen line for}
 RIOffset  = 16;  {the individual items to be}
 DSROffset = 22;  {displayed}
 CTSOffset = 28;
 BRKOffset = 13;
 FEOffset  = 18;
 PEOffset  = 23;
 OROffset  = 28;
 DTROffset = 12;
 RTSOffset = 22;

VAR

 DisplayOffset:  Integer;
 Ind:            Char;

BEGIN

   {Where an item is displayed is determined in}
   {part by which COM status is being displayed}
   {COM1's offset is 0 whereas COM2's offset is}
   {51 character positions.}

   IF PortAddr = COM1 THEN
      DisplayOffset := 0
   ELSE
      DisplayOffset := 51;

   IF (InStatus AND BrkBit) <> 0 THEN
      Ind := 'B'
   ELSE
      Ind := '-';
   WriteStringAt(Ind,High,
                 BRKOffset+DisplayOffset,
                 StatLineNum);

   IF (InStatus AND FEBit) <> 0 THEN
      Ind := 'E'
   ELSE
      Ind := '-';
   WriteStringAt(Ind,High,
                 FEOffset+DisplayOffset,
                 StatLineNum);

   IF (InStatus AND PEBit) <> 0 THEN
      Ind := 'E'
   ELSE
      Ind := '-';
   WriteStringAt(Ind,High,
                 PEOffset+DisplayOffset,
                 StatLineNum);

   IF (InStatus AND ORBit) <> 0 THEN
      Ind := 'E'
   ELSE
      Ind := '-';
   WriteStringAt(Ind,High,
                 OROffset+DisplayOffset,
                 StatLineNum);

   IF (InStatus AND CDBit) <> 0 THEN
      Ind := 'M'
   ELSE
      Ind := 'S';
   WriteStringAt(Ind,High,
                 CDOffset+DisplayOffset,
                 InLineNum);

   IF (InStatus AND RIBit) <> 0 THEN
      Ind := 'M'
   ELSE
      Ind := 'S';
   WriteStringAt(Ind,High,
                 RIOffset+DisplayOffset,
                 InLineNum);

   IF (InStatus AND DSRBit) <> 0 THEN
      Ind := 'M'
   ELSE
      Ind := 'S';
   WriteStringAt(Ind,High,
                 DSROffset+DisplayOffset,
                 InLineNum);

   IF (InStatus AND CTSBit) <> 0  THEN
      Ind := 'M'
   ELSE
      Ind := 'S';
   WriteStringAt(Ind,High,
                 CTSOffset+DisplayOffset,
                 InLineNum);

   {Note: The OTHER COM port is consulted about}
   {the output status. DTR of this port should}
   {equal DSR of other port. RTS of this port}
   {should equal CTS of other port}

   IF (OutStatus AND DSRBit) <> 0 THEN
      Ind := 'M'
   ELSE
      Ind := 'S';
   WriteStringAt(Ind,High,
                 DTROffset+DisplayOffset,
                 OutLineNum);

   IF (OutStatus AND CTSBit) <> 0  THEN
      Ind := 'M'
   ELSE
      Ind := 'S';
   WriteStringAt(Ind,High,
                 RTSOffset+DisplayOffset,
                 OutLineNum);

END;


PROCEDURE DisplayBufferStatus;

VAR

 PerCentage:  Integer;
 PerCentStr:  Str80;

BEGIN

   PerCentage := Round((COM1_Input_Fifo.Ovd.Count/
                        SerialDataFifoSize) * 100);
   Str(PerCentage:2,PerCentStr);
   WriteStringAt(PerCentStr + '%',High,12,2);

   PerCentage := Round((COM1_Output_Fifo.Ovd.Count/
                        SerialDataFifoSize) * 100);
   Str(PerCentage:2,PerCentStr);
   WriteStringAt(PerCentStr + '%',High,26,2);

   PerCentage := Round((COM2_Input_Fifo.Ovd.Count/
                        SerialDataFifoSize) * 100);
   Str(PerCentage:2,PerCentStr);
   WriteStringAt(PerCentStr + '%',High,63,2);

   PerCentage := Round((COM2_Output_Fifo.Ovd.Count/
                        SerialDataFifoSize) * 100);
   Str(PerCentage:2,PerCentStr);
   WriteStringAt(PerCentStr + '%',High,77,2);

   PerCentage := Round((DisplayFifo.Ovd.Count/
                        DisplayFifoSize) * 100);
   Str(PerCentage:2,PerCentStr);
   WriteStringAt(PerCentStr + '%',High,47,5);

END;


FUNCTION FormatCharAscii (Num:Integer) : Str3;

BEGIN

   IF Num < ORD(' ') THEN
       FormatCharAscii := AsciiStrs[Num]
   ELSE
       FormatCharAscii := chr(Num);

END;


FUNCTION FormatCharHex (Num:Integer) : Str3;

BEGIN

   FormatCharHex :=
      HexDigits[(Num SHR 4) AND $000F +1 ] +
      HexDigits[(Num AND $000F) + 1];

END;


FUNCTION DisplayData (D:DisplayRec) : Integer;

VAR

 VideoAttrib:  AttribType;
 Temp:         STRING[7];

BEGIN

   WITH D DO
   BEGIN
      {If data from either channel should be}
      {displayed then...}

      IF (((Tag = FromCOM1) AND COM1_Display_Data)  OR
          ((Tag = FromCOM2) AND COM2_Display_Data)) THEN
      BEGIN
         {set video attribute depending upon}
         {which COM channel it is from}
         IF Tag = FromCOM1 THEN
            VideoAttrib := Low
         ELSE
            VideoAttrib := Rev;

         {choose ASCII or Hex format}
         IF AsciiDisplay THEN
            Temp := FormatCharAscii(DR.Data)
         ELSE
            Temp := FormatCharHex(DR.Data);

         IF HandShakeDisplay THEN
            Temp := Temp + ':$'+
               FormatCharHex(DR.Status);

         {write serial data string to display}
         WriteString(Temp,VideoAttrib);

         {if formatting with spaces}
         IF AddSpace THEN
         BEGIN
            {output space to display and add}
            {a space to string for length calc}
            write(' ');
            Temp := Temp + ' ';
         END;

         {ret length of formatted item}
         DisplayData := length(Temp);
      END
      ELSE
         {if no data ret 0 length}
         DisplayData := 0;
   END;

END;

{*********** Miscellaneous Procedures ***********}

PROCEDURE Init_Program;

{Perform default initialization for protocol}
{analyzer program}

BEGIN

   Init_Fifos;

   COM1_Data_Acquire := True;
   COM2_Data_Acquire := True;

   COM1_Display_Data := True;
   COM2_Display_Data := True;

   TriggerEnabled    := False;
   COM1_Is_Triggered := False;
   COM2_Is_Triggered := False;

   AsciiDisplay      := True;
   HandShakeDisplay  := False;
   AddSpace          := True;

   COM1_Status       := 0;
   COM2_Status       := 0;

   {set serial com defaults 1200 baud, 8 bit word,
   {1 stopbit, no parity}
   {both COM ports always set the same}

   COM_Rate     := 8;          {1200 baud}
   COM_StopBits := 1;
   COM_DataBits := 8;
   COM_Parity   := none;

   SetNewCOMParameter; {apply the parameters}

END;


PROCEDURE VerifyHardware;

CONST

 TestByte = $5A;   {try to store and retrieve}
                   {this value}
 SafeByte = $03;   {set default 8 bits 1 stop}

BEGIN

   ClrScr;
   {just in case of reentry}
   Disable_Serial_Devices;

   WriteString('  Serial Protocol Analyzer Hardware Verification Check  '
               ,Rev);
   WriteStringAt('Checking COM1',HighBlink,2,3);
   port[COM1+LineControl] := TestByte;
   delay(700);
   IF port[COM1+LineControl] <> TestByte THEN
   BEGIN
      WriteStringAt('COM1 Hardware Bad or Missing',
                    Rev,2,3);
      Halt;
   END;
   port[COM1+LineControl] := SafeByte;
   WriteStringAt('COM1 Hardware Verified',
                 Low,2,3);

   delay(700);
   WriteStringAt('Checking COM2',HighBlink,
                 2,4);
   port[COM2+LineControl] := TestByte;
   delay(500);
   IF port[COM2+LineControl] <> TestByte THEN
   BEGIN
      WriteStringAt('COM2 Hardware Bad or Missing',
                    Rev,2,4);
      Halt;
   END;
   port[COM2+LineControl] := SafeByte;
   WriteStringAt('COM2 Hardware Verified',Low,2,4);

   delay(2000);
   ClrScr;

END;


{****** Menu Command Processing Procedure *******}

{Declared forward previously}

PROCEDURE ProcessCmd;

VAR

 HexStr:    Str3;
 HexChar,
 ErrCode:   Integer;
 Ch:        Char;

BEGIN

   IF cmd_code <> 0 THEN
   BEGIN
      CASE cmd_code OF
         1: ExitMenu := True;
         2: BEGIN   {300 baud}
               COM_Rate := 6;
               SetNewCOMParameter;
            END;

        3: BEGIN   {600 baud}
              COM_Rate := 7;
              SetNewCOMParameter;
           END;

       4: BEGIN   {1200 baud}
             COM_Rate := 8;
             SetNewCOMParameter;
          END;

       5: BEGIN   {2400 baud}
             COM_Rate := 11;
             SetNewCOMParameter;
          END;

       6: BEGIN   {4800 baud}
             COM_Rate := 13;

             SetNewCOMParameter;
          END;

       7: BEGIN   {9600 baud}
             COM_Rate := 15;
             SetNewCOMParameter;
          END;

       8: BEGIN   {1 stop bit}
             COM_StopBits := 1;
             SetNewCOMParameter;
          END;

       9: BEGIN   {2 stop bit}
             COM_StopBits := 2;
             SetNewCOMParameter;
          END;

      10: BEGIN   {5 bit word}
             COM_DataBits := 5;
             SetNewCOMParameter;
          END;

      11: BEGIN   {6 bit word}
             COM_DataBits := 6;
             SetNewCOMParameter;
          END;

      12: BEGIN   {7 bit word}
             COM_DataBits := 7;
             SetNewCOMParameter;
          END;

     13: BEGIN   {8 bit word}
            COM_DataBits := 8;
            SetNewCOMParameter;
         END;

     14: BEGIN   {Odd Parity}
            COM_Parity := Odd;
            SetNewCOMParameter;
         END;

     15: BEGIN   {Even Parity}
            COM_Parity := Even;
            SetNewCOMParameter;
         END;

     16: BEGIN   {No Parity}
            COM_Parity := None;
            SetNewCOMParameter;
         END;

     17: BEGIN   {Display COM1 only}
            COM1_Display_Data := True;
            COM2_Display_Data := False;
         END;

     18: BEGIN   {Display COM2 only}
            COM1_Display_Data := False;
            COM2_Display_Data := True;
         END;

     19: BEGIN   {Display both COM1 and COM2}
            COM1_Display_Data := True;
            COM2_Display_Data := True;
         END;

     20: BEGIN   {COM1 is triggered}
            GoToXY(1,25);
            ClrEol;
            COM1_Is_Triggered := True;
            COM2_Is_Triggered := False;
            WriteStringAt('COM1 awaiting trigger',
                          HighBlink,3,25);
         END;

     21: BEGIN   {COM2 is triggered}
            GoToXY(1,25);
            ClrEol;
            COM1_Is_Triggered := False;
            COM2_Is_Triggered := True;
            WriteStringAt('COM2 awaiting trigger',
                           HighBlink,3,25);
         END;

     22: BEGIN   {Input trigger pattern}
            GoToXY(1,MenuLine4);  {position cursor}
            ClrEol;               {clear line}
            WriteString(
             'Input trigger pattern as two hex digits: ',
             High);

            {initialize str with hex prefix}
            HexStr := '$';
            FOR HexChar := 1 TO 2 DO
            BEGIN
               REPEAT
                  {get a char}
                  Ch := upcase(Char(GetKey));
                  {loop until valid char is input}
               UNTIL pos(Ch,HexDigits) <> 0;
               {display to user}
               write(Ch);
               {add to hex string}
               HexStr := HexStr + Ch;
            END;
            {convert hex to int}
            Val(HexStr,TriggerPattern,ErrCode);
            GoToXY(28,25);
            Write('Trigger Pattern: ',
                  FormatCharHex(TriggerPattern));
         END;

     23: BEGIN   {Display BEFORE Trigger mode}
            TriggerMode := Before;
            WriteStringAt('Mode: Display Before Trigger'
                          ,Low,51,25);
            IF COM1_Is_Triggered THEN
               {start with displaying data}
               COM1_Display_Data := True
            ELSE
            IF COM2_Is_Triggered THEN
               COM2_Display_Data := True
            ELSE
               WriteStringAt('Mode Error -- Select channel'
                             ,HighBlink,51,25);
         END;

     24: BEGIN   {Display AFTER Trigger mode}
            TriggerMode := After;
            WriteStringAt('Mode: Display After Trigger'
                          ,Low,51,25);
            IF COM1_Is_Triggered THEN
               {start without displaying data}
               COM1_Display_Data := False
            ELSE
            IF COM2_Is_Triggered THEN
               COM2_Display_Data := False
            ELSE
            WriteStringAt('Mode Error -- Select channel'
                          ,HighBlink,51,25);
         END;

     25: TriggerEnabled := True; {enable trigger}

     26: BEGIN   {Stop Triggering}
            TriggerEnabled := False;
            {stop triggering for both channels}
            COM1_Is_Triggered := False;
            COM2_Is_Triggered := False;

            {start data display for both channels}
            COM1_Display_Data := True;
            COM2_Display_Data := True;
            GoToXY(3,25);
            ClrEol;
            WriteString('Triggering Disabled',Low);
         END;

     27: BEGIN   {Ascii Normal Data Display}
            AsciiDisplay := True;
            HandShakeDisplay := False;
         END;

     28: BEGIN   {Ascii with handshake Data Display}
            AsciiDisplay := True;
            HandShakeDisplay := True;
         END;

     29: BEGIN   {Hex Normal Data Display}
            AsciiDisplay := False;
            HandShakeDisplay := False;
         END;

     30: BEGIN   {Hex with handshake Data Display}
            AsciiDisplay := False;
            HandShakeDisplay := True;
         END;

     31: BEGIN   {Toggle adding spaces to display}
            AddSpace := NOT AddSpace;
         END;

     32: BEGIN   {Start data acquire}
            COM1_Data_Acquire := True;
            COM2_Data_Acquire := True;
         END;

     33: BEGIN   {Stop data acquire}
            COM1_Data_Acquire := False;
            COM2_Data_Acquire := False;
         END;

     34: BEGIN   {Clear the display}
            {claim the screen}
            Alloc(ScreenAccess);
            ClrScr;
            DrawMenuFrame;
            {set cursor postion to home}
            OldXPos := 1;
            OldYPos := 1;
            Dealloc(ScreenAccess);
         END;

     35: Init_Program; {reset the analyzer}

     36: BEGIN   {End the analyzer program}
            ExitMenu := True;
            ExitProgram := True;
         END;


  END;
 END;
END;


{************ Begin Task Procedures *************}

PROCEDURE ProcessKeysTask;

VAR

 Ch:   Char;

 Done,
 OldCOM1Flag,
 OldCOM2Flag:  Boolean;

BEGIN

   Done := False;

   REPEAT

      {read key if available else yield}

      Ch := Char(GetKey);

      CASE Ch OF

      Esc: BEGIN  {if key is Esc}
              {display and process the menu}
              Done := DoMenu;
              {rebuild display when finished}
              BuildDisplay;
           END;

      Sp: BEGIN  {if space bar}
             {save old state}
             OldCOM1Flag := COM1_Display_Data;
             OldCOM2Flag := COM2_Display_Data;

             {stop filling of the display fifo so}
             {it does not overflow durin pause}
             {turn off display data both channels}
             COM1_Display_Data := False;
             COM2_Display_Data := False;
             GoToXY(1,24);
             ClrEol;
             GoToXY(23,24);
             WriteString(
               'Press <Enter> to restart the display'
               ,HighBlink);
             REPEAT
                {loop until Cr starts display again}
                Ch := Char(GetKey);
             UNTIL Ch = Cr;


            {restore previous display status}
            COM1_Display_Data := OldCOM1Flag;
            COM2_Display_Data := OldCOM2Flag;
            BuildDisplay; {rebuild display}
          END;
      END;

   UNTIL Done;

END;


PROCEDURE MoveCOM1Data;

{Move data from the COM1 input buffer to the}
{COM2 output buffer and to the display buffer}
{if enabled}

VAR

 Sd:   DataRec;
 Dd:   DisplayRec;

BEGIN

   REPEAT
      {yield if no data to move}
      WHILE COM1_Input_Fifo.Ovd.Count = 0 DO
         yield;
      {when data is available move it}

      {turn ints off, read data, turn ints on}
      INLINE($FA);
      GetSerialData(COM1_Input_Fifo,Sd);
      INLINE($FB);

      {store in COM2 output fifo}
      PutSerialData(Sd,COM2_Output_Fifo);

      {if a trigger is enabled}
      IF COM1_Is_Triggered AND
         TriggerEnabled    THEN
      BEGIN
         {and a match is found}
         IF Sd.Data = TriggerPattern THEN
         {indicate match & disable further matches}
         {trigger is single shot event}
         BEGIN
            TriggerEnabled := False;

            WriteStringAt('   COM1 Triggered    '
                          ,Low,3,25);
           {If displaying before trigger}
           {then stop data display}
           IF TriggerMode = Before THEN
               COM1_Display_Data := False
           ELSE
               {start the data display}
               COM1_Display_Data := True;

         END;
      END;

      {if we are displaying data}
      IF COM1_Display_Data THEN
      {move data to the display fifo also}
      BEGIN
         {tagged from this COM device}
         Dd.DR := Sd;
         Dd.Tag := FromCOM1;
         PutDisplayData(Dd,DisplayFifo);
      END;

   UNTIL False;

END;


PROCEDURE MoveCOM2Data;

{Move data from the COM2 input buffer to the}
{COM1 output buffer and to the display buffer}
{if enabled}

VAR

 Sd:   DataRec;
 Dd:   DisplayRec;

BEGIN

   REPEAT
      {yield if no data to move}
      WHILE COM2_Input_Fifo.Ovd.Count = 0 DO
          yield;

      {when data is available move it}

      {ints off, read data, turn ints on}
      INLINE($FA);
      GetSerialData(COM2_Input_Fifo,Sd);
      INLINE($FB);

      {store data in COM1 output fifo}
      PutSerialData(Sd,COM1_Output_Fifo);

      {if a trigger is enabled}
      IF COM2_Is_Triggered AND
         TriggerEnabled    THEN
      BEGIN
         {indicate match & disable further matches}
         {trigger is single shot event}
         IF Sd.Data = TriggerPattern THEN
         BEGIN
            TriggerEnabled := False;
            WriteStringAt('   COM2 Triggered    ',
                          Low,3,25);
            {If displaying before trigger}
            {stop data display}
            IF TriggerMode = Before THEN
               COM2_Display_Data := False
            ELSE
               {If displaying after trigger}
               {start data display}
               COM2_Display_Data := True;

         END;
      END;

      {if we are displaying data}
      IF COM2_Display_Data THEN
      BEGIN
         {move data to the display fifo also}
         Dd.DR := Sd;

         {tagged from this COM device}
         Dd.Tag := FromCOM2;
         PutDisplayData(Dd,DisplayFifo);
      END;

   UNTIL False;

END;


PROCEDURE OutputCOM1Data;

{Move serial data from the output fifo}
{to the COM port}

CONST

 {max of 80 chars to serial port}
 {between yields to other tasks}

 OutputsPerYield = 80;


VAR

 Outs:   Integer;
 Sd:     DataRec;
 Urgent: Boolean;

BEGIN

   REPEAT

      {do until data is available in fifo}
      WHILE (COM1_Output_Fifo.Ovd.Count = 0) DO
      BEGIN
         {always make the handshake lines}
         {between COM1 and COM2 agree then yield}
         MakeHandshake(COM1,COM2_Status);
         yield;
      END;

      {we have data to output}
      {Its urgent if fifo is over half full}

      Urgent := (COM1_Output_Fifo.Ovd.Count >=
                       (SerialDataFifoSize/2));

      {Initialize output counter to max value}
      Outs := OutputsPerYield;

      {While there is data to output}

      WHILE ((COM1_Output_Fifo.Ovd.Count <> 0) AND
             (Outs <> 0)) DO
      BEGIN

         {test if UART is ready to transmit}

         WHILE (Get_Serial_Status(COM1) AND
                TxRdyBit) = 0 DO
         BEGIN
            {if we have time yield until ready}
            IF NOT Urgent THEN
               yield;
         END;

         {get data record to output}
         {set the handshake lines and output}
         {the data}

         GetSerialData(COM1_Output_Fifo,Sd);
         MakeHandshake(COM1,Sd.Status);
         port[COM1] := Sd.Data;

         {one less to output}
         Outs := Outs - 1;

      END;
      yield;

   UNTIL False;

END;


PROCEDURE OutputCOM2Data;

{Move serial data from the output fifo}
{to the COM port}

CONST

 {max of 80 chars to serial port}
 {between yields to other tasks}

 OutputsPerYield = 80;


VAR

 Outs:   Integer;
 Sd:     DataRec;
 Urgent: Boolean;

BEGIN

   REPEAT

      {do until data is available in fifo}

      WHILE (COM2_Output_Fifo.Ovd.Count = 0) DO
      BEGIN
         {always make the handshake line}
         {between COM1 and COM2 agree then yield}
         MakeHandshake(COM2,COM1_Status);
         yield;
      END;

      {we have data to output}
      {Its urgent if fifo is over half full}

      Urgent := (COM2_Output_Fifo.Ovd.Count >=
                 (SerialDataFifoSize/2));

      {Initialize output counter to max value}
      Outs := OutputsPerYield;

      {While there is data to output}

      WHILE ((COM2_Output_Fifo.Ovd.Count <> 0) AND
             (Outs <> 0)) DO
      BEGIN
         {test if UART is ready to transmit}

         WHILE
(Get_Serial_Status*******************************************************
*************************************************************************
*************************************************************************
*************************************************************************
*************************************************************************
*************************************************************************
******************************************************
**********************NTIL False;

END;


PROCEDURE DisplayCOMData;

CONST

 {max # of items displayed before yielding}

 MaxDisplayItems = 20;

VAR

 DisplayItems,
 DataLen:      Integer;
 D:            DisplayRec;

BEGIN

   {initial cursor is homed}
   {in data display window}
   OldXPos := 1;
   OldYPos := 1;

   REPEAT
      {if data in display fifo}
      IF DisplayFifo.Ovd.Count <> 0 THEN
      BEGIN
         {claim the screen, define the window}
         {position the cursor and initialize}
         {items counter}
         Alloc(ScreenAccess);
         Window(1,7,80,23);
         GoToXY(OldXPos,OldYPos);
         DisplayItems := MaxDisplayItems;

         WHILE ((DisplayFifo.Ovd.Count <> 0 ) AND
                (DisplayItems <> 0)) DO
         {while there is data to display}
         BEGIN
            {get the data, display it, dec}
            {item counter. Save len of displayed}
            {item.}

            GetDisplayData(DisplayFifo,D);
            DataLen := DisplayData(D);
            DisplayItems := DisplayItems - 1;

            {if not room to display for another}
            {item of same length and we're on the}
            {last display line in window, then}
            {home cursor and start overwriting}
            {screen so as to avoid the scroll of}
            {the display. If not on last line of}
            {window, advance to next line. Either}
            {way, the next line must be cleared.}

            IF WhereX + DataLen >= 80 THEN
               IF WhereY = 17 THEN
                  GoToXY(1,1)
               ELSE
                  WriteLn;

            ClrEol;
         END;

         {save new cursor position}
         OldXPos := WhereX;
         OldYPos := WhereY;

         {back to full screen}
         {and release screen lock and yield}
         Window(1,1,80,25);
         Dealloc(ScreenAccess);
      END
      ELSE
         {if no data to display just yield}
         yield;

   UNTIL False;

END;


PROCEDURE Timer;

VAR

 Toggle:  Boolean;

BEGIN

   Toggle := True;

   REPEAT

      {every 1/2 second do the following}
      pause(4);

      {if ok to update the screen}
      IF UpdateScreenStatus THEN
      BEGIN
         {lock the screen}
         Alloc(ScreenAccess);

         {if status of either port has changed}
         IF ((COM1_Status <> OldCOM1_Status)  OR
             (COM2_Status <> OldCOM2_Status)) THEN
         {then update the screen}
         BEGIN
            {display new COM1 status}
            DisplayHandShakeStatus(COM1,COM1_Status
                                  ,COM2_Status);
            {save new status}
            OldCOM1_Status := COM1_Status;

            {display new COM2 status}
            DisplayHandShakeStatus(COM2,COM2_Status
                                  ,COM1_Status);
            OldCOM2_Status := COM2_Status;

         END;

         {every full second update}
         {buffer status on screen}
         IF Toggle THEN
            DisplayBufferStatus;

         {unlock the screen}
         Dealloc(ScreenAccess);

         Toggle := NOT Toggle;
      END;

   UNTIL False;

END;



BEGIN {main}

   {verify presence of COM1 and COM2}
   VerifyHardware;

   {initialize the multitasker}
   Init_Kernel;

   {initialize the menu structure}
   Init_Menu;

   {install the serial ISRs}
   Install_Serial_Handlers;

   {Initialize strings used to format the display}
   Line1 := Char(201)+' COM1 '+repl(16,Char(205))+
          ' Serial Protocol Analyzer Ver: 1.0 '+
          repl(15,Char(205))+ ' COM2 '+Char(187);

   Line2 := Char(186)+' In Fifo:'+repl(5,' ')+
            'Out Fifo:    '+ Char(186)+
            repl(10,' ')+'By'+repl(10,' ')+
            Char(186)+' In Fifo:'+repl(5,' ')+
            'Out Fifo:    '+Char(186);

   Line3 := Char(186)+' Stat- BRK:  FE:  PE:  OR: '+
            Char(186)+ '   Craig A. Lindley   '+
            Char(186)+ ' Stat- BRK:  FE:  PE:  OR: '+
            Char(186);

   Line4 := Char(186)+' In - CD:  RI:  DSR:  CTS: '+
            Char(186)+repl(22,' ')+Char(186)+
            ' In - CD:  RI:  DSR:  CTS: '+Char(186);

   Line5 := Char(186)+ ' Out- DTR:      RTS:'+
            repl(7,' ')+Char(186)+
            '   Display Fifo:      '+Char(186)+
            ' Out- DTR:      RTS:'+repl(7,' ')+
            Char(186);

   Line6 := Char(200)+repl(27,Char(205))+
            Char(202)+repl(22,Char(205))+
            Char(202)+repl(27,Char(205))+
            Char(188);

   Line24 := '  <Esc> for Menu  <Space Bar> to Pause'+
           '    COM1 - Normal : COM2 - Reverse Video';

   BuildDisplay;   {show main display}
   Init_Program;   {initialize all vars}

   {initialize screen lock}
   Initialize_Semaphore(ScreenAccess);

   {enable status update}
   UpdateScreenStatus:= True;

   {Fork off all tasks}

   Fork;

   IF child_process THEN
      MoveCOM1Data;

   Fork;

   IF child_process THEN
      MoveCOM2Data;

   Fork;

   IF child_process THEN
      OutputCOM1Data;

   Fork;

   IF child_process THEN
      OutputCOM2Data;

   Fork;

   IF child_process THEN
      DisplayCOMData;

   Fork;

   IF child_process THEN
      Timer;


   ProcessKeysTask;

   ClrScr;
   Disable_Serial_Devices;
   Remove_Serial_Handlers;

END








Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.