Gary J. McGrath
March 5, 1998
This manual was written to be used as a reference to programmers that will be modifying this software. It is not intended for users or programmers writing software to interface with the Guide Camera Controller. Refer to the Guide Camera Users Manual for these purposes.
The Guide Camera is used to correct image motion that can occur while using the Wide Field Camera. The guide camera control system stabilizes the image of a guide star in its field of view by sending offset commands to the TCC which move the telescope in altitude and azimuth thus correcting any image position errors in CCD coordinates. The Guide camera consists of an AX series CCD camera from Axiom Research. This camera has a built-in thermoelectric cooler and remote power supply. It is connected to the Guide Camera Controller PC through an ISA interface card that plugs into the Controller PC. The Guide Camera is controlled using the GUIDECAM.EXE program which was written specifically for the EOST Guide Camera Control System. It uses the AXLIB software library from Axiom Research to control the camera hardware. The CauseWay 386 DOS Extender is used to create an extended 32 bit DOS Executable. The controller can be operated locally using the command line interface and graphical display, or it can be operated remotely by communicating to its host computer through an RS-232 serial port.
The source code is located in directory c:\data\src\dos386\guidecam on the Pentium 100 PC. The command line version of Watcom C/C++ is used. To compile the program, open a DOS window, change directory to c:\data\src\dos386\guidecam, type WMAKE at the DOS prompt. The compiler uses the MAKEFILE and LINKRESP.WAT files as the rules used to compile the source code. The Watcom compiler is installed on the D: drive, in directory D:\WATCOM. The CauseWay DOS Extender files are located in the source directory. The AXLIB SDK is located in the directory C:\AXLIB\AXLIB310.
Axiom Research recommended using the Watcom C/C++ compiler and The CauseWay DOS Extender to create a 32 bit DOS executable with AXLIB. The "AXLIB SDK" manual gives other options for compilers and DOS extenders, but Axiom Research told us that it would be nearly impossible to generate a program without using Watcom and CauseWay. Also, developing code for Microsoft Windows would be virtually impossible with AXLIB.
The Watcom C/C++ Compiler version 10.6 is used to compile and link the GuideCam software. The command line version is used with a makefile and linkresp.wat file. The command "wmake" is used to make the target GuideCam.exe in the makefile. Optionally, the /a command line argument can be used to compile all source files regardless of their dates. See the "Watcom Make Utility" under "Watcom C/C++ Tools Guide" in the "C_C++ Master Help Index" for more information on wmake. Also, see the "Watcom Linker Guide" in the "C_C++ Master Help Index" for more information on the linker.
The makefile is similar to the standard UNIX makefile. It contains compiler flags, a list of object files, autodependency compilation, and a call to wlink for the guidecam.exe target. The /c flag instructs the compiler to compile only, and not to link. The object files will be linked later using wlink. The /l=CauseWay flag must be used to tell the compiler to generate a CauseWay executable. The /I flag adds the directory to the list of directories to search for include files. The /fpi87 flag is used to generate inline 80x87 instructions. The /4s flag is used to generate 386 instructions based on the 486 instruction timings and to use stack-based argument passing conventions. The /mf flag must also be used to instruct the compiler to use the flat memory model required by CauseWay. The /zp4 flag sets the minimum structure packing (member alignment) to 4 bytes. The /w4 sets the compiler warning level to 4. The /d2 flag instructs the compiler to include full debug information in the executable.
The call to wlink in the makefile uses the file linkresp.wat to instruct the linker on how to perform the link. The line "system CauseWay" is used to instruct the linker to generate a CauseWay executable. The "file" lines tell the linker which files to link. Similarly, the "library" lines tell the linker which libraries to link. The line "name GuideCam.exe" instructs the linker to generate the executable named GuideCam.exe.
In order to use the CauseWay DOS Extender, the following installation is necessary. This installation differs somewhat from the installation instructions in the CauseWay manual. The files CWSTUB.EXE, CWDSTUB.EXE, and CWSYSTEM.LNK must be copied from the CauseWay distribution diskette to the \watcom\binnt directory. The following line must be added to the end of the Watcom link file WLSYSTEM.LNK in the WATCOM\BINW directory:
@%watcom%\binnt\cwsystem.lnk
This command instructs the Watcom linker on how to build a CauseWay program when CauseWay is specified as the target program.
In order to use the Watcom WD debugger, copy CW.TRP and CWHELP.EXE into the WATCOM\BINNT directory. Also copy CWHELP.CFG into the GUIDECAM directory. The files CWD.CFG, CWD.EXE, and CWD.OVL in the GUIDECAM directory are the CauseWay Debugger files. The CauseWay Debugger has not been used with this version of Watcom C/C++.
The AXLIB software library (AXLIB.LIB) is used for camera hardware control. In addition, there are two source files (AXMAIN1.C and AXMAIN2.C) that provide additional high level functions for controlling the camera hardware. The version of the library that is used is 3.10. Previous versions had severe bugs and would not even compile on our system. The functions in AXLIB are described in the "AXLIB SDK" manual from Axiom Research.
The main() function calls setup_video() to initialize the video display. It then calls init_settings() which initializes the user settings structure to the default values. ASYNC_config() is then called to initialize the asynchronous serial communications. The draw_screen() function is called next. This function draws the screen labels and windows according to the definitions in the defines.h header file. Next, the init_camera() function is called. This function initializes the camera hardware. If it cannot communicate with the camera, an error message is given in the command line interface, and the program exits. The main_loop() is executed next, this function runs in an infinite loop, capturing keyboard input, checking for received serial communications, updating the display, and performing guiding and focusing when enabled.
The setup_video() function first sets the Watcom __SVGAType global variable to 1. This forces the video board into SVGA mode. The _getvideoconfig() Watcom graphics function is then called in order to save the original configuration to restore upon exiting the program. The Watcom graphics function _setvideomode() is next called to set the video mode to VIDEO_MODE which is defined as 1024x768 256 color SVGA mode in defines.h. When the program exits, the restore_video() function is called to restore the video mode to its original configuration.
The init_settings() function initializes the settings structure which is created as a global structure of type GCSettingsT in MAIN.C. The GCSettingsT type is defined in GUIDECAM.H. the settings structure is declared as an extern in globals.h which is included in all source files except MAIN.C All global variables that are used in more than one file are defined in this way. This simplifies the code by putting the declarations of all global files in one place.
The serial communications hardware is initialized in ASYNC_config(). The details of the initialization are explained in the "Serial Communications" section of this document.
The draw_screen() function first maps the screen colors to a palette of 256 by calling map_palette(). The 240 grayscale colors used to display images are mapped to colors 0-239 of the palette. Next, the 16 user interface colors are mapped to colors 240-255 of the palette. The RGB components of the user interface colors are stored in the array palette_colors[ ][ ] in PALETTE.C. The palette locations of these colors are #defined in defines.h. These are the values that are used when calling graphics functions.
After the palette is defined, Watcom graphics functions are used to draw the screen. First, the background color is set by calling _setbkcolor(). Then, the screen is cleared with a call to _clearscreen(). The _setwindow() function is called to set the graphics window to the full screen. The Watcom functions _settextalign(), _settextorient(), _settextpath(), _setcharspacing(), and _setcharsize() are called to set the defaults for graphical text display. Definitions for size, placement, color, etc. of screen objects are #defined in defines.h. This makes it easy to change the screen layout without changing code. Screen boarders, titles, and labels are drawn with the Watcom graphics functions: _grtext_w(), _getviewcoord_w(), and _rectangle_w(). The function display_settings() is called to display the values in the settings structure.
The init_camera() function consists mostly of copied code from the frontend.c example included in the AXLIB distribution. The camera is initialized and memory is allocated for frame buffers. Calls to AXLIB functions lockmem() and lockcode() are made to prevent data and code from being swapped to disk by CauseWay.
The main_loop() function contains a loop that runs continuously to process input and update the display. Before the loop starts, the required_ticks variable is set to the desired screen update period (DISPLAY_UPDATE_TIME) times the timer tick frequency in Hz. Since the standard PC timer tick is used, the timer tick frequency is 18.8679 Hz. Next, the command line prompt is displayed and the infinite loop is started. Within the loop, keyboard and serial input are processed with calls to userio() and serialio(). The CCD temperature data is updated on the screen every time the system timer ticks exceed the required ticks plus the time of the last update. And, if guiding or focusing is turned on, the guideproc() or focusproc() is called to process the guiding or focusing.
Several functions are used to display and perform processing for the user interface. The draw_screen() function draws all of the boarders, labels, and static text. The draw_settings() function draws all of the user defined settings on the screen. The draw_param() function writes the value of a particular system parameter to the screen at the given location.
The userio.c file contains functions for controlling the command line interface. The function userio() reads the keyboard buffer, and constructs lines of user input or commands. Characters are displayed as they are received, and when a (CR) character is received, the command is processed, and a new command prompt is displayed. The backspace (BS) character removes the last character from the buffer and erases it from the screen. The global variables (only in file userio.c) display_line, and display_col keep track of the current position in the command line interface window.
The display_prompt() function displays either the serial prompt or the command line prompt, depending on the value of the argument "source".
The display_new_line() function displays a blank new line. If the window is full, all lines in the buffer are shifted up one line and redisplayed. Otherwise, the current position is moved to the beginning of the next line and the next line in the buffer is null terminated.
The display_backspace() function erases the last character typed in, removes it from the buffer, and moves the current position to the left one space. This is only performed if the current position is not at the beginning of the line.
The display_char() function displays a character by drawing the character in the next terminal window position. If the line is full, wrapping is performed by calling display_new_line() before drawing the character. The character is also added to the terminal buffer, and the current position is incremented.
The setup_cmdline_attr() function is used by display_char() to setup the attributes of the displayed characters, including size, color and alignment. It must be called every time characters are displayed since other functions that use the Watcom graphical text library could change the attributes.
The message() function displays the given string on a new line of the command line interface. This function is used extensively throughout the program to output messages. This function should be used instead of printf() since printf() only works in text mode under DOS. message() can be used for displaying informational, error, warning, and debugging messages to the screen.
The check_esc_key() function is used to check to see if the (ESC) key was pressed. This function is called from various places in the guiding and focusing routines. Since the GuideCam software has no multitasking capabilities, I/O is blocked while other processing is being executed. Since the guiding and focusing operations take at least several seconds to complete, the user would otherwise have to wait a considerable amount of time before keyboard input is even displayed. The check_esc_key() allows the code to check for the (ESC) key and terminate the current operation at various points in the execution. check_esc_key() reads all of the characters from the keyboard buffer. If an (ESC) key is found, and guiding or focusing is enabled they are immediately disabled. The status parameter is also erased from the screen, a new command prompt is displayed, and the function returns 1. If no (ESC) key is found, the function returns 0. The calling function is responsible for terminating if 1 is returned from check_esc_key().
Finally, the display_image() function displays an image in either the Full Frame window or the Guiding Subframe window. display_image() is located in display.c. The display_image() function takes a pointer to an image, the number of rows and columns, and the image_type as arguments. The image is a one dimensional array of pixels. Each pixel is of size int. The cols argument is used to determine where in the one dimensional array the next row of pixels will begin. The rows argument indicates the number of rows in the image. The calling function is responsible for allocating and setting the correct number of array elements. The image type must be either FULL_IMAGE or SUBFRAME_IMAGE. These types are defined in defines.h.
If image display is turned off for the given image type in the user settings, then display_image() simply returns. Before displaying the image, "displaying" is written to the screen in the status field. After the display is complete, the status string is erased on the screen display.
The upper and lower limit labels are displayed for both axes of the image being displayed.
Next, the brightest and dimmest pixels are found, and the intensity scaling and offset are determined. The range of intensities is evenly spaced to fit within the range of grayscales available. The image is written to the bitmap image (bitmap0) which is part of the AXINFO structure. This bitmap image has the same size as the original image.
Next, the screen coordinates of the image window are obtained and the number of screen pixels in the row and column directions are determined. The bitmap pixels are displayed in the window by looping through each screen column and screen row. The color is determined by scaling the rows and columns of the bitmap image and using the color of the pixel from the scaled bitmap that matches the row and column of the screen image.
There are three types of exposures: normal, dark, and bias. These are specified by the AXLIB definitions: AX_EXP_TYPE_NORMAL, AX_EXP_TYPE_DARK, and AX_EXP_TYPE_BIAS. A flat field exposure is taken using the AX_EXP_TYPE_NORMAL of a white screen. In addition, there are two types of images: FULL_IMAGE and SUBFRAME_IMAGE. An exposure is taken by calling the expose() function with the exposure type, image type, and the centroid of the subframe image in row and column coordinates. If the image type is FULL_IMAGE, then the centroid coordinates are ignored.
The first thing that expose() does is it displays "Flushing" for the status parameter on the screen. The required AXLIB function AX_check_camera_state() is called next. Then, binning and exposure time are set up in the AXINFO structure from the settings structure. The number of rows and columns, and the row and column offsets are set based upon the image type and the user settings. The image filename is set in the AXINFO structure based on the exposure type. Then, the exposure is initiated by calling AX_start_exposure(). If the camera is still flushing the CCD from the last exposure, then the execution will wait in a while loop until the camera_status element of the AXINFO structure is AX_STATUS_EXPOSING. At that time, "Exposing" is displayed in the status field on the screen. Now that the exposure has been initiated, the expose() function returns.
The image is read out of the CCD to memory using the readout() function. This function determines the image type by the size of the image as indicated in the AXINFO data structure. This function can be called immediately after the expose() function. If the exposure is still taking place, readout() will wait in a loop until the AX_readout_ready() function returns TRUE. The status field on the display is now "Readout". The check_esc_key() function is called to determine if the user pressed the (ESC) key to abort the process. If so, readout() immediately returns 1. Next, the AX_check_camera_state() function is called which performs the readout. When the function returns, the readout is complete. The status field is erased on the screen, and readout() returns 0 indicating a successful readout.
Guiding is performed by two functions: initiate_guiding() and guideproc(). The initiate_guiding() function turns on the guiding flag and exposes the initial guiding full image and finds the centroid of the guide star. The guideproc() function is called by the main_loop() as long as the guiding flag is on. It exposes the sub-frame, locates the centroid of the guide star, and sends the offset commands to the TCC.
The initiate_guiding() function begins by setting the guiding element of the settings structure to 1. This enables guiding by signaling the main_loop() function to call guideproc() each time it goes through the loop. Next, the expose function is called to initiate the exposure. the readout() function is then called to readout the CCD. If the (ESC) key was pressed during the readout, the function will return 1, and initiate_guiding() will immediately return. The check_esc_key() function is called liberally throughout the initiate_guiding() function to check for an (ESC) key press. The centroid() function is called with the pointer to the image and the number of rows and columns.
The centroid() function begins by displaying "Centroiding" in the status field on the screen. Since the image is a FULL_IMAGE, a sub-image is created by calling subimage(). This creates a new image centered around the brightest pixel in the original image. The sub-image size is defined by the subframe_size divided by the binning. The row and column offsets of the brightest pixel in the sub-image are also returned from subimage(). These values are stored in the row_offset and col_offset elements of the settings structure and are used to define the location of future guiding subframes.
A threshold image is created from the sub-image by calling the threshold() function. This function determines the noise threshold intensity level and sets all pixels below this level to zero. Pixels with intensities above the threshold are reduced by the threshold level. This reduces the effect of noise skewing the location of the computed centroid. The details of the centroiding can be found in "ER-75 WFCam Guider Centroid Algorithm".
The centroid of the threshold image calculated by the calc_centroid() function, and stored in the row_centroid and col_centroid elements of the settings structure. The centroid coordinate for each direction is calculated by multiplying the intensity of each pixel by the component of the distance from the center of the image in the given direction. This results in the intensity weighted first moment.
Now that the subframe image offsets and initial guide star centroid are calculated, the guiding subframes can be taken. Since guiding is now turned on, guideproc() is called the next time through the main_loop(). In guideproc(), the expose() function is called to initiate the exposure of a SUBFRAME_IMAGE with the offsets calculated from the initial full frame image. The check_esc_key() function is called liberally throughout the guideproc() function to check for a user (ESC) key press. If the (ESC) key is detected, guideproc() returns immediately.
Next, centroiding is performed on the previous frame to overlap processing time with the exposure. The first time guideproc() is called, there is no previous frame, so centroiding is skipped. The readout is completed by calling readout(). The previous_image is then stored for the next time guideproc() is called. The image is displayed on the screen by calling display_image().
Since the guideproc() function is to be called no more than every sample_period seconds (as defined in the settings structure), the function waits until the time expires. When the time expires, the function returns.
The centroiding for the sub-frame is somewhat different than it is for the full frame. The subimage() function is not called because the image is already a SUBFRAME_IMAGE. For a SUBFRAME_IMAGE, the centroid() function performs thresholding and calculates the centroid by calling calc_centroid(). centroid() then displays the centroid in CCD coordinates on the screen. Then, the centroid error in CCD pixels is calculated and displayed. The centroid error in arcseconds is also calculated and displayed. The row and column RMS error are calculated by calling row_rms_update() and col_rms_update(). These errors are also displayed on the screen. Finally, the offset commands in CCD pixels are sent to the TCC via the serial port.
Within the main_loop() the cooler status is checked and displayed periodically (usually every 2 seconds or more). The main_loop() function calls check_cooler(). The check_cooler() function calls AX_check_temperature(), then uses the values of AXINFO variables to determine the status and temperature of the cooler. These values are displayed in the appropriate fields on the screen.
The show_cooler() function is used by the command interpreter to obtain the cooler status and temperature from the command line or the serial port. This function also obtains its data by calling AX_check_temperature(), and using the values of the AXINFO variables. The functions message() and TransmitResponse() are used to send the information to the command line and serial port.
If a SET TEMPERATURE command is received from either the serial port or the command line, the command is interpreted by the interpret() function and the cooler is either turned off or turned on and the setpoint is set. The functions AX_set_temperature(), AX_lower_temperature(), and AX_raise_temperature() are used to command the temperature controller.
The serial communications is divided into two parts; hardware control functions, and buffer control functions. The hardware control functions are located in the source file async.c, and the buffer control functions are located in the file asyncbuf.c.
The hardware control functions directly manipulate the UART, and interrupt controller to setup the communications and process serial interrupts. The function ASYNC_config(), which is called by main() to set up the serial communications, calls the function open_com() with the serial port settings to configure the UART and the PIC. The function open_com() first checks to see that the parameters are within allowable limits. If a problem is found, an error code is returned to ASYNC_config(). Then, open_com() OR's the serial configuration codes into the comdata variable to be used in the call to _bios_serialcom().
The interrupt mask register (IMR) of the master PIC is programmed with the mask to enable the correct interrupt number. Then, the original ISR is saved to com[Cport].oldvect using the _dos_getvect() standard C function. This is done so that the original ISR can be restored when the program terminates. Then, the new ISR (either com1_isr_handler() or com2_isr_handler()) is installed using _dos_setvect().
Next, _bios_serialcom() is called to set the data bits, stop bits, parity, and baud rate in the 8250 UART. Then, Data Terminal Ready and Request to Send are set in the Modem Control Register. Any pending errors are cleared in the Line Status Register. Any pending characters are then cleared from the input buffer, and the interrupts are enabled in the Interrupt Enable Register. At this point the UART is ready to send and receive characters.
To be as general as possible, interrupt service routines (ISR) are written for both COM1 and COM2, even though only COM2 is used in this program. The ISRs are com1_isr_handler() and com2_isr_handler() . An interrupt can be generated by the UART for several reasons. Therefore, the first thing that the ISR must do is determine the reason the interrupt was generated. The Interrupt Status Register (ISR) is read to determine the reason. If the interrupt was generated because there is data in the input buffer, the data is read from the Receive Buffer Register (RBR) and put into the software receive buffer with a call to rxputc(). If the interrupt was generated because there is room in the transmit buffer a call is made to txrdy() to see if there are any characters in the software transmit buffer. If so, AsyncOutchar() is called to write the next character to the Transmit Holding Register. If the interrupt was generated for any other reason, nothing is done. The interrupt handler then sends an end of interrupt command to the master interrupt controller.
The buffer control functions in ASYNCBUF.C manage the software receive and transmit buffers for the serial communications. The transmit buffer consists of the global array AsyncOutBuffer[ ], and the receive buffer consists of the global array com_queue[ ]. The function txputc() is called at the function level (as opposed to the ISR level) to put a character into the transmit buffer for output. The txrdy() function is called from the ISR to get the next character to be transmitted from the transmit buffer. If there are no characters to be transmitted the function returns -1, otherwise the function returns zero and the character to be transmitted is placed in the ichar parameter for the ISR to transmit. The rxputc() function is called by the ISR to put a character into the receive buffer for AsyncGetStr() to receive. The rxgetc() function is called by AsyncGetStr() to check for received characters in the receive buffer. The function returns 1 if a character was received. The character pointed to by the parameter data is set to the received character (if any) for AsyncGetStr() to receive.
The AsyncGetStr() function is called periodically by serialio() to check to see if a message has been received by the serial port. The end of message character (EOM) is the carriage return (CR). The AsyncGetStr() function checks to see if characters have arrived from the serial port and assembles them into a message string. The rxgetc() function is used to check the receive buffer for characters. If one exists, it is added to the message string. If the character is an EOM character, the command string is NULL terminated and sent back to the serialio() function where it calls the interpret() function. The interpret() function determines which command it is and executes the required code. The serialio() function sends back the command string and "OK" to the serial port using the TransmitResponse() function to tell the calling computer that it received the command.