E. O. Smith Telescope Adaptive Optics Tertiary Servo Control System Programmers manual

Gary J. McGrath

March 5, 1998


Index


Introduction

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 tertiary controller. Refer to the Tertiary Servo Software Users Manual for these purposes.

A brief description of the system and its requirements will be given. The purpose of the tertiary servo is to correct for image motion due to atmospheric conditions. As the image moves in the field of view, the tertiary servo detects the motion and actuates the tertiary mirror in tip and tilt to cancel the motion and produce an image that is as stationary as possible with respect to the CCD detector. A guide star is used to determine the image motion. Three Avalanche Photo Diodes (APDs) measure the position of the guide star image in the focal plane. The servo must compute the X and Y position of the center of the star from the output of the APDs. In order to lock the image into position, the error signals become the negative of the X and Y positions relative to the sensors. Since the tertiary mirror actuators are in a rotated coordinate system relative to the sensors, the error signals must be transformed using a rotation transformation. The angle of the instrument rotator axis determines the angle of the rotation transformation. Digital notch filters are used on each axis to reduce the effects of mechanical resonances. Also, a digital PID filter is used on each axis to compensate the mechanical system to achieve optimum performance. Each output is sent through a D/A converter to its respective actuator amplifier. The amplifiers drive the actuators to change the angle of the mirror in the X and Y directions. This servo system can be classified as multiple input multiple output (MIMO). Although, it can be analyzed as two independent single input single output (SISO) servos since the interaction between the two actuator axes is minimal. The desired bandwidth of the servo is about 40 Hz. The servo sample rate is 1 KHz, which may be increased somewhat if necessary.

The control program must control the servo, receive serial commands, receive keyboard commands, and update the display continuously and in real-time. This requires that the system be multitasking. Since some of these tasks may take longer to complete than the servo update rate, the multitasking must be preemptive. With the servo processing having the highest priority, other tasks may be interrupted before they complete so that the servo can be updated in time.

Hardware

The computer that controls the tertiary servo is a Ziatech ZT8902 66 MHz 486 DX2 single board computer. The system runs in a Ziatech STD32 bus passive backplane. The ZT8902 contains several chips that are specifically used for this application. The ZT8902 contains an 8250 compatible UART which is used for serial communications (ref 1, 3, 4). Unlike the standard PC which contains only one 8254, the ZT8902 contains two 8254 programmable interval timer chips. Each chip contains three timer/counters for a total of 6 devices. The counter/timers on the first chip are reserved for system and DOS functions. This leaves three counter timers available for the tertiary controller to use. Each counter/timer is capable of generating a periodic interrupt at a frequency of up to 8MHz. Alternatively, these devices can be programmed to count external pulses (ref 1, 2). The requirements for the external signal are that the rise and fall times must be less than 25 ns and the clock low and clock high times must be at least 50 ns. The ZT8902 also contains two 8259 Programmable interrupt controllers. As with the standard PC, these controllers are cascaded and work together as master and slave (ref 1, 2, 4).

Multitasking

Multitasking in this program has been implemented using the uC/OS real-time operating system written by Jean J. Labrosse (ref 5). The system consists of C and Assembly code that is compiled and linked into the executable program. The program is divided into separate logical sections called tasks. Each task runs seemingly simultaneously through the use of preemptive multitasking. The system is driven by a system timer tick that is generated by an interrupt from the 8254 counter/timer. The task with the highest priority that is ready to run will always execute. That is, if a lower priority task is running, and a higher priority task becomes ready to run, the lower priority task will relinquish the CPU to the higher priority task immediately. This is called preemptive multitasking.

The benefit of preemptive multitasking over cooperative multitasking is that the highest priority task will never have to wait for another task to finish before it gets use of the CPU. The disadvantage of preemptive multitasking is that common variables and resources that are accessed by more than one task could become corrupted if one task is in the middle of making a modification and execution switches to another task that accesses that resource. Semaphores are used to prevent this corruption from occurring, but special care must be taken to use these semaphores correctly.

In the Tertiary Control Program, the highest priority task is the ServoTask(). This task performs its servo processing, then executes a delay until the next system clock tick. During this delay, other tasks are allowed to run. It is important that the ServoTask() begin executing immediately after the system timer tick so that the sampling will be periodic. Similarly, the other tasks also have mechanisms that allow lower priority tasks to execute. The CommTask() continuously checks the serial receive buffer for characters then waits for one clock tick. If few characters are being received, then the CommTask spends most of its time waiting. During this time, other tasks are allowed to run. The master task waits for 1000 clock ticks after periodically updating the screen and checking for a key press on the keyboard.

The MasterTask()

The MasterTask() is created in the main() function after initializing the uC/OS system and creating the display semaphore. Once main() executes the OSStart() system call, the MasterTask() begins executing. The MasterTask() installs the system timer tick interrupt service routine by calling TIMER_config(). Then the serial communications are initialized with a call to ASYNC_config(). The MasterTask() is also responsible for measuring the CPU usage. So, it initializes the variable max to OSIdleCtr which is the number of times the idle task executes within a 1000 clock tick period while the system is completely idle. The percentage of CPU usage can later be calculated by dividing the number of times the Idle task executes into this maximum value. The ServoTask() and CommTask () are then created.

Finally, the Master task enters an infinite loop where it computes the CPU usage, Updates the screen display, checks for a keyboard press, and performs a delay for 1000 clock ticks. The DispScreenUpdate() function is used to display the dynamically changing parameters on the screen. If the ESC key is pressed, the program is terminated by calling terminate(). If any other key is pressed, the entire screen is redrawn by calling DispScreen(). Note that all writes to the screen use the display functions in the file Display.c which use the DispSem semaphore to prevent multiple tasks from writing to the screen at the same time and corrupting the screen. You should never use the standard C functions such as printf() to write to the screen.

The CommTask()

The CommTask() continually waits for characters to arrive 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 (carriage return), the command string is NULL terminated and sent to the ProcessCommand() function this function displays the message on the screen and sends it to the interpret() function. This function determines which command it is and executes the required code. It then sends back a response to the serial port.

The ServoTask()

The ServoTask() initializes the servo system, then performs an infinite loop that updates the servo once every timer tick. The task first calls ReadConfig which reads the configuration file TERTIARY.CFG and sets the servo system constants. These constants are stored in a structure of type Tservo_const which is defined in the file SERVO.H. The global Tservo_const variable, servo_const, is declared in main.c as are all global variables (except those that are referenced in a single file). All global variables declared in main.c are also found as externs in the file globals.h. This keeps all global variables in one place so that they can be easily found. A structure of dynamic servo parameters (servo_param) is similarly defined. In the ServoTask(), pointers to servo_const and servo_param, called C, and P are created to simplify the code. This method is also used in other functions as well.

After the constants are read in from the configuration file, the function InitServo() is called to create instances of the PID, notch filter, and I/O device driver objects using the appropriate constants. The servo loop is then started. Channels 1-15 odd on the ZT8961 digital I/O board are set high at the beginning of the processing and set low at the end of the processing. This gives a way to measure the period of the sampling and the time it takes to perform the servo processing.

The servo processing begins by reading the digital APD sensor interface, or reading the analog photodiodes, depending on whether the constant ANALOG_INPUT is defined. All of the #define's that set the definitions for the Tertiary Control Program in general are made in the file DEFINES.H. The X-Y spot position is then calculated relative to the detectors using equation 1 from "ER-88 HRCam Detector to Tertiary Transformation" (ref 6) . The coordinates are then transformed to actuator coordinates using a rotation transformation with the instrument rotator angle. This angle can be set by sending a command through the serial interface. The error is then calculated for each axis. Each error signal is passed through its own set of PID and notch filters. the resulting drive signals are limited and outputted through the respective analog output channel of the VL-1297 analog I/O board. During servo processing, various diagnostic inputs and outputs are made which are enabled or disabled in the configuration file.

The System Timer Tick

The system timer tick provides the interrupt to the uC/OS kernel that controls the timing of the system. To set up the timer, the 8254 counter/timer chip must be programmed to generate a periodic interrupt at a given frequency. The two cascaded 8259 programmable interrupt controllers must also be programmed to enable the correct interrupt level. Finally the address of the interrupt service routine (ISR) must be installed at the correct location in memory so that the CPU can execute it when the interrupt is received.

The system timer tick is set up when MasterTask() calls TIMER_config(). TIMER_config() then calls install() with the address of the ISR and the interrupt number. The install() function disables interrupts so that the setup can be completed without being interrupted. This prevents data from being corrupted. The old interrupt address is saved to the variable old_handler using getvect(). This is done so that it can be restored when the interrupt is removed just before the program terminates. The new ISR is then installed by calling setvect() with the interrupt number and the address of the ISR. setvect() writes the ISR address to the correct place in memory which corresponds to the interrupt number so that the CPU will know which code to execute when it receives an interrupt.

The 8259 PIC is programmed by first reading the interrupt mask register (IMR). The bit corresponding to the interrupt number is unmasked by ANDing it with the inverted bit mask of the interrupt number. This mask is then written back to the IMR. This enables the interrupt corresponding to the interrupt number. At this point, interrupts are enabled.

Channel 2 of the second 8254 counter timer chip is programmed to generate a 1 KHz interrupt by writing the mode byte to the Timer Mode Register. The mode byte consists of bit 0 which selects the count format (0=BCD or 1=Binary, Binary is selected), bits 1, 2, and 3 which select the mode (mode 3 software strobe is selected), access mode (low byte followed by high byte is selected), and counter select (channel 2 is selected). Then the rate is programmed by setting the count to the external clock rate (8MHz) divided by the desired tick frequency (1 KHz). The count is written to the Count/Status register as two bytes. First the low byte is written, then the high byte is written by shifting the 16 bit count down by 8 bits. At this point the timer/counter is generating periodic interrupts that cause the CPU to execute the ISR.

The ISR is an assembly language routine, OSTickISR, that calls the uC/OS timer tick function OSTimeTick(), then calls pic_eoi() to send an end of interrupt signal to both the master and slave PICs.

When the program terminates, it is considered good practice to return the system as close to its original state as possible. For the timer tick ISR, we restore the original ISR that was replaced by OSTickISR using setvect. When the program terminates normally, the terminate() function is called. This function calls remove_periodic() which calls uninstall() with the correct interrupt number. The uninstall() function then restores the original ISR by calling setvect().

Serial Communications

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 MasterTask() 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 getvect() standard C function. This is done so that the original ISR can be restored when the program terminates. Then, the new ISR (either ISR_COM1 or ISR_COM2) is installed using 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 character 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.

The interrupt service routines (ISR) are assembly language routines that were added to the uC/OS source code. It is required by uC/OS that these routines be in assembly, but all they do is call the respective C handler function (com1_isr_handler() or com2_isr_handler() ). An interrupt can be generated by the UART for several reasons. Therefore, the first thing that the handler 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, OSOutchar() 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 task 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 the CommTask() to receive. the rxgetc() function is called by the communications task 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 the CommTask() to receive.

Software Locations

The source code is located on the Pentium 100 PC (venus) in the directory C:\DATA\SRC\DOS\TERTIARY. The Borland C ver. 3.1 compiler is used to compile the source code. To run the Integrated Development Environment (IDE) double click on the Borland C 3.1 icon on the Windows 95 desktop. Then select "Open Project" from the "Project" menu. Enter the tertiary directory by double clicking on TERTIARY\. Then open the project by double clicking on TERTIARY.PRJ.

References

  1. Ziatech Corporation, "ZT8902 Rev B Reference Manual": Information on 8250 serial controller, 8254 counter/timer, 8259 programmable interrupt controller, and general information on the ZT8902 Single board computer.
  2. Intel Corporation, "Peripheral Components Databook": Data sheets for the 8254 Programmable Interval Timer, and 8259 Programmable interrupt controller.
  3. National Semiconductor, "Interface: Line Drivers and Receivers Databook": Data sheets for the 8250 UART.
  4. Mark Nelson, 1992, "Serial Communications: A C++ Developers Guide": Information on 8250 UART and 8259 PIC.
  5. Jean J. Labrosse, 1992, "uC/OS The Real-Time Kernel": Guide to multitasking in general and a reference for uC/OS.
  6. Bruce E. Truax, The Danbury Corporation, "ER-88 HRCam Detector to Tertiary Transformation"
  7. Bruce E. Truax, The Danbury Corporation, "ER-77 Tertiary Mirror Control Equations"