program LambdaCtrl; { VPascal routines for controlling Sutter Lambda 10-2 filter wheel By Scott C. Molitor, UNC - Chapel Hill (smolitor@med.unc.edu) A toolbar named Lambda 10-2 is created that contains four buttons: Set Filter Wheel Position, Set Filter Wheel Speed, Open Shutter, and Close Shutter. Although the Lambda 10-2 is able to control two separate filter wheels (A & B), this code assumes only wheel A is being controlled. The following routines can be run via DdeExecute() to provide external access for controlling the Lambda 10-2 from image acquisition routines: 1) FilterPosSpeed - set filter position and speed 2) OpenShutter - open shutter 3) CloseShutter - close shutter In addition, the following variables can be accessed by external routines via DdeRequest() and DdePoke(): 1) FilterPos - current filter wheel position 2) FilterSpeed - current filter wheel speed 3) LambdaError - error value (see LambdaCmd() routine) Note that external code which executes FilterPosSpeed() should set the value of FilterPos and/or FilterSpeed prior to calling FilterPosSpeed. } toolbar 'Lambda 10-2'; // create Lambda 10-2 toolbar const // global constants // serial port constants SERIAL_PORT = 1; // use COM1: port BAUD_RATE = 9600; // transmit at 9600 bps DATA_BITS = 8; // 8 bits per character PAR_FLAG = NoParity; // no parity checking STOP_BITS = 1; // 1 stop bit // Lambda 10-2 constants INIT_POS = 6; // initial filter wheel position INIT_SPEED = 5; // initial filter wheel speed LAMBDA_ATTN = 238; // command to put Lambda 10-2 into Serial mode LAMBDA_OPEN = 170; // command to open shutter A LAMBDA_CLOSE = 172; // command to close shutter A WAIT_FILTER = 2500; // wait 2500 ms for filter commands WAIT_SHUTTER = 25; // wait 25 ms for shutter commands // list strings for filter position & speed selection // positions should be changed to reflect filter wheel setup // speeds reflect calculations made from speed table in Lambda 10-2 manual LIST_POS = '0 = blank;1 = 340 nm;2 = 380 nm;3 = 360 nm;4 = blank;5 = 400 nm;6 = 450 - 490 nm;7 = 510 - 560 nm;8 = blank;9 = wide open'; LIST_SPEED = '0 = 14 + 38(# pos) ms;1 = 15 + 41(# pos) ms;2 = 17 + 47(# pos) ms;3 = 21 + 59(# pos) ms;4 = 29 + 80(# pos) ms;5 = 44 + 123(# pos) ms;6 = 71 + 198(# pos) ms;7 = 129 + 357(# pos) ms'; var // global variables accessed via DdePoke() FilterPos dde 'FilterPos'; // current filter position FilterSpeed dde 'FilterSpeed'; // current filter wheel speed LambdaError dde 'LambdaError'; // error value (see LambdaCmd() routine) LastCommand; // last command sent to Lambda 10-2 procedure LambdaCmd( NextCommand ); { This routine passes a command to the Lambda 10-2 via serial port and waits for the Lambda 10-2 to echo the command to confirm that it has been successfully executed. This prevents image acquisition from starting before a shutter has been opened or before the filter wheel has reached the desired position! The Lambda 10-2 uses the following rules when echoing commands: When COMMAND is received and executed, Lambda 10-2 returns COMMAND + CHR(13) When COMMAND is received but not executed, Lambda 10-2 returns CHR(13) only When COMMAND is repeated or there is an error, Lambda 10-2 does nothing To prevent false error messages from appearing, this routine ignores repeated commands. Note that a command is not executed if the Lambda 10-2 is already in the specified state (for example, sending the Close Shutter command when the shutter is already closed). However, this is distinguished from an error or repeated command by the transmission of a carriage return (CHR(13)) when an unexecutable command is received. This routine sets the variable LambdaError to one of the following values: -2 = serial port error -1 = Lambda 10-2 not responding 0 = command received and executed 1 = command received but not executed 2 = command repeated Normal execution can be confirmed by checking for LambdaError >= 0. } var ReceiveErr; // errors detected while waiting for Lambda 10-2 response ReceiveStr; // commands echoed back from Lambda 10-2 begin // check for repeated commands if ( LastCommand = NextCommand ) then LambdaError := 2 else begin // open the serial port // display error message if unsuccessful OpenSerial( SERIAL_PORT, BAUD_RATE, DATA_BITS, PAR_FLAG, STOP_BITS ); if ( SerialError <> ser_Ok ) then begin WriteError( 'Could not open serial port (COM', SERIAL_PORT, ':)' ); LambdaError := -2; end else begin // initialize Tx buffer // make sure the Lambda 10-2 is in Serial mode // this command is not echoed back TxFlush; Transmit( Chr( LAMBDA_ATTN ) ); // initialize Rx buffer // Timeout limit depends on command type // shutter is much faster than filter wheel! RxFlush; SetRxEnd( Chr( 13 ) ); if ( ( NextCommand = LAMBDA_OPEN ) or ( NextCommand = LAMBDA_CLOSE ) ) then SetRxTimeout( WAIT_SHUTTER ) else SetRxTimeout( WAIT_FILTER ); ReceiveErr := SerialError; // transmit the command // wait for Lambda 10-2 to respond Transmit( Chr( NextCommand ) ); ReceiveStr := RxString; ReceiveErr := SerialError; // check whether Lambda 10-2 returned a response // if response, replace LastCommand & check whether command was executed // if no response, check for RxTimeout or serial port error if ( ReceiveErr = ser_Ok ) then begin LastCommand := NextCommand; if ( Ord( ReceiveStr ) = NextCommand ) then LambdaError := 0 else LambdaError := 1; end else if ( ReceiveErr = ser_Timeout ) then begin WriteError( 'Lambda 10-2 is not responding' ); LambdaError := -1; end else begin WriteError( 'Serial port (COM', SERIAL_PORT, ':) error ', ReceiveErr ); LambdaError := -2; end; end; CloseSerial; end; end; procedure FilterPosSpeed; dde 'FilterPosSpeed'; { Set the position & speed of filter wheel A. This is accomplished by sending a single byte to the Lambda 10-2: bits 1 - 4 set the position and bits 5 - 7 set the speed. Bit 8 selects which wheel the command is being sent to (0 for wheel A and 1 for wheel B) and is set to wheel A by default. } begin LambdaCmd( FilterPos + 16*FilterSpeed ); end; procedure SelectFilterPos; button btn_fwN, 'Set Filter Wheel Position'; { Set the filter wheel position to a user-selected value } var NewString; // selected string NewPos; // selected position begin NewPos := SelectString( 'Filter Wheel Position', LIST_POS, NewString ); if ( NewPos >= 0 ) then begin FilterPos := NewPos; FilterPosSpeed; end; end; procedure SelectFilterSpeed; button btn_fwFwd, 'Set Filter Wheel Speed'; { Set the filter wheel speed to a user-selected value } var NewString; // selected string NewSpeed; // selected speed begin NewSpeed := SelectString( 'Filter Wheel Speed', LIST_SPEED, NewString ); if ( NewSpeed >= 0 ) then begin FilterSpeed := NewSpeed; FilterPosSpeed; end; end; procedure OpenShutter; dde 'OpenShutter'; button btn_BulbOn, 'Open Shutter'; { open the shutter } begin LambdaCmd( LAMBDA_OPEN ); end; procedure CloseShutter; dde 'CloseShutter'; button btn_BulbOff, 'Close Shutter'; { close the shutter } begin LambdaCmd( LAMBDA_CLOSE ); end; { main routine initializes Lambda 10-2 } begin FilterPos := INIT_POS; // default filter position FilterSpeed := INIT_SPEED; // default filter speed LastCommand := 0; // initialize LastCommand to force execution LambdaError := 0; // initialize LambdaError FilterPosSpeed; // set initial filter wheel position & speed if (LambdaError >= 0) then CloseShutter; // make sure shutter is closed end