Gary J. McGrath
April 30, 1997
The purpose of the Instrument controller is to control all of the peripheral components of the instruments that are not controlled by the Leach Controller and Guide Camera Controller. These components include the WFCam Shutter Controller, WFCam and HRCam filter changers, and HRCam Adaptive Optics Tertiary Servo Controller. All of the commands to control these components originate from the Telescope Operator Interface, and are transmitted to the Instrument Controller via a network connection. In addition, the components can all be controlled from dialog windows in the Instrument controller. A Network interface window is also available to view the bi-directional communications on the Network.
Windows 95 was chosen as the operating system because of the built-in support for network communications, the integrated Winsock TCP/IP stack, the availability of low-cost high quality programming tools, and the use of low cost industry standard PC hardware.
The Instrument Controller hardware consists of a Pentium PC with ISA and PCI slots. A National Instruments PC-DIO-96 Digital I/O board is used to interface to the WFCam shutter controller and the filter insert logic boards. A National Instruments AT-232 board is used to communicate to the tertiary controller and filter changer stepper motor controller. An NE2000 compatible Network Interface Card is used to connect to the ethernet network hub.
Borland C++ 5.0 is used to develop the Instrument controller project. Borland C++ has an Integrated Development Environment (IDE) that contains all of the tools needed for the development. The AppExpert was used to create a project file and source code for a very simple application. Once AppExpert creates the project, it cannot be run again to edit the parameters. Also, there is no way to determine the parameters that were used to generate the initial application project that AppExpert created.
Once the initial project was created, it was customized to the final application by adding classes with ClassExpert, adding controls and menus with the Resource Editor, and editing source code with text editors.
The source code is located on the Pentium 100 machine (venus), in the directory C:\DATA\SRC\INSTRMENT. The IDE can be run by double clicking on the Borland C++ 5.0 icon on the Windows 95 desktop.
Windows programming naturally lends itself to Object Oriented Design. Borland includes a class library called "Object Windows Library" that contains most of the Windows API. OWL is used extensively wherever possible to encapsulate code and data, and to increase code reuse. In addition, ClassExpert is a very helpful tool that works well with OWL. Some of the classes used in this project include TInstrumentApp, TSDIDecFrame, TInstrumentAboutDlg, TMainDlg, TFilterDlg, TShutterDlg, TTOISocketDlg, TTertiaryDlg, and TTertiaryEdit.
AppExpert originally created the classes TInstrumentApp, TSDIDecFrame, TInstrumentAboutDlg, and TMainDlg. TInstrumentApp is the application class for this application. TSDIDecFrame is the Single Document Interface (SDI) extension of the TDecoratedFrame class. The TInstrumentAboutDlg is the dialog that is displayed when "About" is selected from the help menu. These three classes required little or no changes since AppExpert created them.
TMainDlg is the main TDialog class. This is the dialog that you see when the program is first started. It has a menu bar that contains "File", "View", and "Help" menus. The "File" menu has only one item, "Exit", which exits the program. The "Help" menu also has only one item, "About", which displays general information about the program. The "View" menu contains several items for displaying the other dialog boxes. These include "Operator Interface Socket", "Tertiary RS-232", "Shutter Servo", and "Filter Changer". The TMainDlg also contains radio buttons for selecting the active instrument and filter. There are edit controls for displaying the status of guiding, adaptive optics, and shutter.
The TFilterDlg is the class for the dialog that controls the filter changers. It contains an edit control for entering the stepper motor desired position, and a Push Button control for telling the stepper motor to move. There are also radio buttons for inserting and retracting the filter.
The TShutterDlg class contains Static Text, Text Edits, Push Buttons, Check Boxes, and Group Boxes. These controls are used in four levels of control for the shutter controller. The lowest level controls the Digital I/O ports directly. The second level allows the user to read and write to a particular address on the motion control chip. The third level controls the individual registers of the motion control chip. The highest level of control allows the user to initialize the controller and start an exposure of a specified duration.
TTOISocketDlg is the class that displays the communications between the Telescope Operator Interface (TOI) and the Instrument controller. This class contains a Text Edit control to display the communications.
The TTertiaryDlg class displays the dialog between the Instrument Controller and the Tertiary Servo Controller. It also allows the user to enter commands from the keyboard to be sent to the Tertiary Servo Controller. This class uses the derived class TTertiaryEdit to display the communications. This class was derived from TEdit to add the functionality of capturing the keyboard input in order to send commands to the RS-232 serial port.
In addition, there are other classes that are not derived from OWL classes. The class TTOIServerSocket is the interface to the network communications. It is derived from the class TServerSocket. TTOIServerSocket has member functions for interpreting received messages and sending response messages. TServerSocket is responsible for opening a network socket, sending, and receiving data. TServerSocket is derived from TSocketBuf that serves to buffer the incoming data.
The TTertSerialBuf class is the interface to the serial communications with the Tertiary Servo Controller. TTertSerialBuf is derived from TSerialBuf. TSerialBuf is responsible for opening the serial port, sending, and receiving data.
Every window in this program consists of a class derived from the TDialog class. To create a new class:
Controls can be added, removed and modified using the Resource Editor.
Setting up a Dialog to Open From a Menu
The View menu on the Main Dialog allows the user to open other dialogs. To add a dialog to this menu:
If(pDialogPointer->HWindow == 0)
{
pDialogPointer->Create();
}This will create an instance of the dialog.
Using a Control Class in a Window Class
In order for a control to be accessed by a member function of the dialog class that it belongs to, a pointer to the control must be declared local to the class. This pointer will point to a control object that is created within the dialog class and contains the resources that are specified by the resource ID. This is useful for such things as setting and getting data from a control such as a Text Edit. Push Buttons do not need to be created in this way, since they contain no data to access. Control Notifications (discussed later) are used by a control to signal events such as Push Button presses. To create the control object:
Getting feedback from Controls (Control Notifications)
It may be necessary to notify the class when events such as a button press or a check box click occur. These events notify the program through Control Notification functions. A Control Notification function can be added to a class as follows:
Other event functions can be added to a dialog class in a similar way.
A timer can be used to periodically trigger an event, such as servicing a communications port or updating a display. It is most convenient to use the SetTimer() member function of the TWindow class (parent of TDialog). A timer set up in this way is local to the window it was created in. See TWindow::SetTimer() for more information. A timer can be added to the system by:
Multiple threads can be used for functions that take a lot of time to execute. For example, we don't want to hold up the whole program while we wait for a peripheral device. A thread can be easily added to the system using the base class TThread as follows:
The TMainDlg class has several command notification functions that open the respective dialog window when it is chosen from the View menu. It also has a timer that is set up in the SetupWindow() function. This timer services the communication ports approximately once per second. All global data, including dialog pointers are declared at the top of the maindlg.cpp file. These global variables are necessary so that data can be communicated between windows. All dialog objects and other global objects are created dynamically in the constructor function of TMainDlg. Notice that only the objects are created here. The actual dialog windows cannot be created until after the main dialog window is created since it is the parent window.
The constructor function for TShutterDlg dynamically creates the control objects. It then initializes the digital I/O ports by calling NIDAQ Library functions. The shutter init and expose threads are then created. Finally, the shutter is initialized by calling the start function of the shutter init thread.
The destructor function terminates the shutter init and expose threads.
The SetupWindow() function sets up the timer to control the display. It then initializes the default display values.
There are two general purpose functions called ReadPort() and WritePort(), which are used to read and write to a particular port on the HCTL-1100 controller chip. They do this by manipulating the control bits with a particular timing and reading and writing to the data and address registers. See the HCTL-1100 data sheet and shutter controller schematic for more details.
The InitServo() member function initializes the shutter servo by performing a software reset on the HCTL-1100 chip and seeking the reference position. The position counter is then reset and the servo is moved to the home position.
The UpdateDisplay() member function reads the flags and status registers of the HCTL-1100 controller chip and displays the information on the screen. This function is called periodically from the EvTimer() function whenever a WM_TIMER signal is received.
The move() member function moves the shutter to the given position with the given maximum velocity and acceleration.
The Expose() member function performs an exposure by either moving across the aperture at a constant velocity so that all parts of the field of view get equal exposure, or by quickly opening then quickly closing the shutter after the given exposure time expires. The method used depends on the exposure duration. Exposures of duration less than the threshold use the first method, and longer exposures use the second method.
There are two functions that could take more than a few seconds to execute; Expose() and InitServo(). Since we are operating in an event driven environment, it is a requirement that any events be acted upon in a timely fashion, regardless of what state the system is in. For example, if we are taking a long exposure, we don't want the communications ports to be blocked for the entire exposure time. Therefore, we must use separate threads for functions that may take more than one or two seconds to execute.
The thread classes TShutterExposeThread and TShutterInitThread are defined in the files shutterthread.h and shutterthread.cpp. These objects are dynamically created in the TShutterDlg constructor. The TShutterDlg member functions StartExposure() and StartInitialization() are used to initiate the thread execution. (See "Using Multiple Threads" above.) These functions are called anytime an exposure or initialization is desired.
Software semaphore is used to coordinate access to the HCTL-1100 chip. Only one thread is permitted access to the chip at a time to prevent data from becoming corrupted. The data member shutter_semaphore is used for this purpose. The high-level routines (InitServo and Expose) pend on the semaphore by waiting until shutter_semaphore is zero. Once available, the semaphore is locked by setting shutter_semaphore to one. After all access to the HCTL-1100 is complete, the semaphore is unlocked by setting shutter_semaphore to zero.
Of course, it is possible for a low-level routine to collide with a high level routine in another thread because low level routines are not protected with semaphores. It would be difficult to protect these low-level functions since they are used by high level functions. It was decided to leave out this level of complexity since only the high level functions will be available for normal use. The purpose of the low-level functions is for testing only. They should not be used while multiple threads are executing.
[This section of the software has not yet been completed.]
The Tertiary Serial interface consists of a class hierarchy for the buffered serial communications and a dialog class that contains a text edit class. The class hierarchy of the buffered serial I/O is given here:

We will start with a description of the base classes and work our way up to the highest level class. Then, we will discuss the dialog and edit control classes.
The TSerialBuf is a general-purpose class that is used to open, send and receive data from a serial port. The constructor takes the serial communications port name as an argument, and opens the serial port with the default parameters (9600, N, 8, 1). The destructor closes the serial port.
The member function SendSerialMessage() takes a null terminated string pointer as an argument, and writes the string to the serial port. The terminating null character is not sent, but a CR/LF combination is sent at the end of the message.
The virtual function TranslateMessage() must be overridden by the derived class.
The TransmitChar() member function accumulates characters in a string buffer until a CR character is transmitted. The function then transmits the message with a terminating CR/LF combination.
The virtual function TransmitResponse() must be overridden by the derived class.
The member function ServicePort() calls the member function SerialMessageReceiver().
SerialMessageReceiver() receives characters from the serial port until the serial port is empty. If a CR character is received, a NULL is appended to the string and the string is translated using the function TranlsateMessage(). LF characters are ignored and BS characters remove the last character from the buffer.
The TTerminalBuf class is used to store multiple lines of characters. The AddLine() member function adds a character string to the buffer. The GetBuf() function returns a pointer to the character buffer. GetNChars() returns the number of characters in the buffer, and GetNLines() returns the number of lines in the buffer.
The TTertSerialBuf class is derived by multiple inheritance from TSerialBuf and TTerminalBuf.
The constructor for TTertSerialBuf takes the port name and opens the serial port using the inherited TSerialBuf constructor.
The virtual function, TransmitResponse(), inherited from TSerialBuf, is overridden in TTertSerialBuf. This function adds the message to the terminal buffer, redraws the terminal edit control, and transmits the message to the serial port.
The virtual function, TranslateMessage(), inherited from TSerialBuf, is overridden inTTertSerialBuf. It displays the message in the terminal edit control, then relays the message to the TOI via the network interface.
The RedrawTerminalEdit() member function updates the edit control with the contents of the terminal buffer.
The TTertiaryEdit class is derived from the OWL TEdit class and adds the functionality of intercepting the WM_CHAR message using the EvChar() handler function. Each character is not only displayed in the text edit control, but is also transmitted out the serial port.
The TTertiaryDlg class controls the display of the serial port buffer. The SetupWindow() member function sets the contents of the text edit to the contents of the serial terminal buffer. This allows the dialog to display previous serial communications even if the dialog is closed and reopened. The TTertiaryDlg class also has member functions for setting the contents of the text edit and setting the text insertion position. These functions are used by the TTertSerialBuf class when servicing the serial port.
The class hierarchy that comprises the network interface is complicated enough to warrant an inheritance diagram. These classes are related as follows:

We will start with a description of the base classes and work our way up to the highest level class.
The base class TSocketBuf contains three virtual functions that must be overridden in the derived class: ServiceSocket(), SendSocMsg(), and TranslateMessage().
The class also has a member function called SocketMessageReceiver(). This function assembles incoming characters into message strings. Characters are received until there are no more characters in the socket. They are combined into a message in the data member MessageBuf[ ] until a CR character is received. LF characters are ignored, and BS characters remove the last character in the buffer. When a complete message is received, the TranslateMessge() member function is called to translate the message.
The member function SocketErrorStr() returns a string containing the last Winsock error. This function can be called if an error is detected to get a string to display an error message. Not all errors are supported, but all error codes from accept(), bind(), closesocket(), connect(), listen(), recv(), send(), and socket() are supported. If an unknown error is detected, the string "Unknown Error" is returned. The returned string is stored as the data member SockErrMsg[ ] so that dynamic allocation and is not necessary.
The TServerSocket class encapsulates the Winsock implementation to establish and service a server socket connection.
The constructor function takes as an argument the port number, and creates a server socket. It then binds the socket to the port, and listens for connections. The socket uses the I/P protocol, the INET address family, and an address of ANY.
The destructor closes the socket.
The private member function AcceptClient() attempts to accept a client using the Winsock function select(). This function is non-blocking. If successful, it returns the accepted client socket. If no client is available, it returns INVALID_SOCKET. This function is used when servicing the socket.
There are several virtual member functions that must be overridden in the derived class. The virtual member function SendSocMsg() sends a message to a socket. The virtual function ServiceSocket() is called periodically to read from the socket. The virtual function TranslateMessage() translates the incoming message. The virtual function SocketErrorStr() returns a pointer to a string containing the last socket error message.
The TTerminalBuf class is the same class that is used in the serial communications interface described above.
The TTOIServerSocket class is derived by multiple inheritance from TServerSocket and TTerminalBuf. Its constructor calls the constructor for TServerSocket to open the socket connection.
The virtual function TranslateMessage() inherited from TServerSocket is overridden in TTOIServerSocket. This function matches command strings with known commands and executes the command. Commands beginning with "TERTIARY" are relayed to the tertiary controller via the serial interface. If a command is not recognized, the response "Error: unrecognized command" is transmitted back to the TOI via the network interface. The command is then added to the terminal buffer and the display is updated.
The TransmitResponse() member function attempts to transmit the given message to the socket. If there is no client accepted, the message is displayed "Error, no client accepted, cannot transmit message".
The RedrawTerminalEdit() member function sets the text edit control to the text in the terminal buffer.
The TTOISocketDlg class is the dialog class that displays the network communications between the TOI and the Instrument controller. Unlike the TTertiaryDlg, it does not need a special text edit control class since this dialog does not accept keyboard input.
The constructor function creates a new TEdit control to display the network communications.
Like the TTertiaryDlg, the SetupWindow() member function sets the contents of the text edit to the contents of the network terminal buffer. This allows the dialog to display previous network communications even if the dialog is closed and reopened.
The TTOISocketDlg class also has a member function for setting the contents of the text edit. This function is used by the TTOISocketBuf class when servicing the network socket.