// TITLE     : Scilab driver for the ISG500 5 MHz Functions Generator
//             The ISG1200 12MHz FG should also be supported.
// SOFTWARE  : for Scilab >= 5.0 : http://www.scilab.org/
// DEPENDENCY: http://atoms.scilab.org/toolboxes/serial must be loaded
// RELEASE HISTORY 
//   04/2009, 1.0 : Initial release. All commands available
// AUTHOR & COPYRIGHT: Samuel GOUGEON - Université du Maine / ENSIM (France)
// LICENSE  : CeCILL-C
//   http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
// WARRANTY : None of any kind. Use as is to your own risks & responsability

function rep = FG_ISG500(varargin)
// Function Generator ISG500: Driver for Scilab >= 5.0
// ---------------------------------------------------
// The ISG1200 12 MHz Functions Generator should also be supported.
//
// MAIN DEVICE CHARACTERISTICS (See full datasheet from IeS manufacturer):
//
//  Frequency range : 10 mHz - 5 MHz
//  Function shapes : DC Sinus Triangle Square Slope+ Slope- TTL (Pulse)
//  Modulation: 
//    Sweep (internal): 
//        Range   : 200 mHz - 100 KHz
//        Type    : Linear  or logarithmic
//        Periode : 20 ms - 20 s
//    AM : 0 - 100%        BW= 0-20 kHz
//       internal: 500 Hz sinus
//       external: 1 Vrms = 100%
//    FM : 100 Hz - 5 MHz  BW= 0-5 kHz
//    FSK: 100 Hz - 5 MHz  BW= 0-100 kHz
//    PSK: 0 - 360°        BW= 0-100 kHz
//  Output can be limited (for safety)
//
// SERIAL COM: BEWARE: Please set RS-232 speed = 19200 bauds on the device: 
//                       Button MENU => Interface => RS232 => choix
// LIMITATION: Only one ISG500 device can be driven from a computer
//
// COMMANDS: 
//   . Command names are case unsensitive
//   . Commands may be sent whatever is the output status for power (on|off)
//   . Every command (except 'close' & 'reset') needs an argument, making
//      a (command, argument) pair
//   . Multiple pairs of (command, argument) can be provided and processed in a once.
//     Then, commands are performed in the order to which they are provided. 
//   . Commands only set the device. There is no command to query its status.
//
// Possible (command, argument) pairs and signification are:
// 'open', portId : Open the serial communication
//                  portId: serial port number (1,2,..) to be used
//
// 'reset'        : Reset parameters to default values stored in set #0 (see 'recall').
//
// 'close'        : Close serial communication for this device
//
// 'shape', 'sinus'|'triangle'|'square'|'DC'|'TTL'|'pulse'|'slope+'|'slope-' :
//                  Set the shape of delivered signal. Shape's name is case unsensitive.
//
// 'amplitude', A : Set the amplitude of delivered signal to A (decimal number).
//                  in TTL mode    : A = -10 -> 10 V
//                  in other modes : A =   0 -> 20 V
//                  In case of overvalue, amplitude is set to the nearest accepted limit
//                   and a warning messaeg is displayed
//
// 'offset', val  : Set the offset to val V.  offset = -10 -> 10 V (decimal number)
//
// 'frequency', F : Set the main Frequency to F Hz. F = 10 mHz - 5 MHz  (0.01 -> 5e6)
//                  F may be specified in normal or scientific notations.
//
// 'limit', 'on'|'off'|value [V] : 
//                  Enable | Disable | Set voltage limitation output for securing
//                  the powered circuitry. Beware that setting a new value does not 
//                  automatically enable the limitation.
//
// 'rWidth', rW   : Set the relative width [%] of square signal:  5% <= rW <= 95%
//                  rW = Relative duration of Vmax state / periode
//
// 'width', w     : Set pulse duration [s].  At a given frequency F, the relative 
//                   pulse width rW is set to achieve the required pulse duration. 
//                   One should have w = rW.F , with  0.05 < rW < 0.90
//
// 'modulation', [ Type Source] :
//                  Start modulation mode of the specified Type and Source, with possible values:
//                  Type: 'SWP' | 'AM' | 'FM' | 'FSK' | 'PSK' | 'TTL'
//                        SWP: Frequency Sweeping (vobulation)
//                         AM: Amplitude Modulation
//                         FM: Frequency Modulation
//                        FSK: Frequency Shift Keying (Numerical FM)
//                        PSK: Phase Shift Keying (Numerical Phase modulation)
//                        TTL: Gate modulation (then, source parameter is not required)
//                   Source: 'int' | 'ext'  (respectively: internal, or external)
//                    External modulation can be performed when an external source is
//                     connected to the device through its "Modulation input" BNC connector.
//                  Example: 'modulation', ['FSK' 'int']: start FSK internal modulation
// 'modulation','stop': Stop running modulation & return to fixed frequency delivery.
//
// 'modParam',arg : Set modulation parameters (without stopping any running modulation).
//                  Note: A running modulation must be stopped before changing its own 
//                  parameters (else, parameters are not updated).
//            arg=[ 'AM' modRate ] : Set modulation rate for AM modulation, in %. 
//                  modeRate must be a positive decimal number <= 100 specified as a string
//            arg=[ 'FM' FM_shift] : Set the frequency shift for FM modulation. FM_shift must 
//                   be one of the following strings (as shifts specified in Hz):
//                  '100'|'300'|'1K'|'3K'|'10K'|'30K'|'100K'|'300K'|'1M'
//            arg=[ Type F_start F_end Periode ] : Set parameters for internal sweeping:
//                  Type=0  : Linear variation of the frequency
//                  Type=1  : Logarithmic variation of the frequency
//                  F_start : Starting frequency [Hz].  200 mHz <= F_start <= 5 MHz
//                  F_end   : Ending frequency [Hz].    200 mHz <=  F_end  <= 5 MHz
//                  Warning: Sweeping is done automatically with increasing frequency
//                   (so F_start and F_end are inverted whether F_start > F_end)
//                  Periode : Sweeping periode [s].  20 ms <=  Periode <= 20 s
//                  The 4 parameters must be specified as numbers
//            arg=[ 'FSK' F2 ] : Set the second frequency F2 for FSK modulation. 
//                   F2 in Hz as a string
//            arg=[ 'PSK' phase ] : Set the phase shift [°] for PSK modulation. 
//                  phase must belong to [0 360[ ° and be specified in a string.
//
// 'store', nS    : Store in the nSth set of parameters the current ones. nS = 1->9 (integer)
//
// 'recall', nS   : Set current parameters to their values stored in the nSth set of parameters.
//                  nS=1 -> 9 (integer). Set #0 is reserved for default parameters.
//
// 'Fmeter', flag : Set or unset the device as a frequency-meter
//                  flag= %F | 'off'| 0: reset the device as a Function Generator (default)
//                  flag= %T | 'on' | any not null number: Set the device as Frequency-meter.
//                  Frequency range: 100 mHz - 100 MHz. Input: "Counter Input" BNC
//

    Driver = "FG_ISG500";
    if  argn(2)==0
        head_comments(Driver)
        rep = ''
    end
    
	rep = 0;
	an = 0;	// number of current input argument: Initialisation
	
	while an<argn(2),	// Main loop over ("command", arg) pairs

		an = an+1;
		// Missing argument for last command specified:
		if an>argn(2), error(77,Driver); end   // %s : wrong number of rhs arguments

		command = convstr(varargin(an),'l');
		tmp = "shape,amplitude,offset,frequency,rwidth,limite,modulation,fmeter,store,recall,reset";
		if ( strstr(tmp,command)~='' & ~exists('writeserial'))
			tmp = _("%s: writeserial() from SERIAL TCL toolbox is missing\n");
			error(msprintf(tmp, Driver));
		end

		if command~= 'open',
			channel = TCL_GetVar(Driver);               // RS-232 communication id
			Shape   = TCL_GetVar(channel+".shape");	 // Shape of generated function
			F       = strtod(TCL_GetVar(channel+".F"));	    // Frequency 
								// (needed for relative width when pulse duration is required)
		end

		if command~='close' & command ~='reset'
			an = an+1
			argu = varargin(an)
		end
		
		if ( (command=="shape" | command=="amplitude" | command=="offset" | command=="frequency" ..
		    | command=="rwidth"| command=="limite"    | command=="fmeter" | command=="store"  ..
		    | command=="recall" ) & size(argu,"*")~=1 )
			error(204,an); // 204: Argument %d, wrong type argument: expecting a scalar
		end

		// =================================================================================

		select command

			case "open"
				if (~exists("openserial"))
					tmp = _("%s: openserial() from SERIAL TCL toolbox is missing\n");
					error(msprintf(tmp, Driver))
				else
                    channel = openserial(argu,"19200,n,8,1")
                    sleep(100)
                    TCL_EvalStr("fconfigure "+channel+" -buffersize 10000 -sysbuffer 10000")
					TCL_SetVar(Driver,channel)
					TCL_SetVar(channel+'.shape','sinus')  // Default signal shape
					TCL_SetVar(channel+'.F','1000')       // Default frequency
					rep = FG_ISG500("reset")
					rep = channel
				end

			// ==============================================================================

			case "close"
				if ~exists('closeserial')
					tmp = "from SERIAL TCL toolbox is missing"
					error(msprintf("%s: closeserial() "+gettext(tmp)+"\n",Driver))
				else
					rep = -closeserial(channel)
					TCL_UnsetVar(Driver)
					TCL_UnsetVar(channel+'.shape')
					TCL_UnsetVar(channel+'.F')
				end

			// ==============================================================================

			case "shape"
				if typeof(argu)~='string'
					error(55,an); // %dth argument type must be character string
				end
				sigShape = convstr(argu,'l');
				select sigShape
					case 'sinus',    tmp = 'SIN';
					case 'triangle', tmp = 'TRI';
					case 'square',   tmp = 'SQR';
					case 'slope+',   tmp = 'RMP';
					case 'slope-',   tmp = 'RMN';
					case 'pulse',    tmp = 'IMP';
					case 'dc',       tmp = 'DC';
					case 'ttl',      tmp = 'LOG';
					else
						tmp = gettext("%s: shape %s unknown\n");
						error(msprintf(tmp, Driver, sigShape));
				end
				TCL_SetVar(channel+".shape",sigShape);
				Shape = sigShape;
				rep = rep + writeserial(channel,"SIG:"+tmp+ascii(13));
	
			// ==============================================================================

			case "amplitude"
				val = string(argu);
				if ~isnum(val),
					tmp = gettext("%s: Argument for /%s/ command must be a real number\n");
					error(msprintf(tmp,Driver,'Amplitude'));
				else val = strtod(val);
				end
				if Shape=='ttl' & ( val<-10 | val>10)
					tmp = gettext("%s: %s A=%d V must be %d<A<%d V => Set to %d V\n"); 
					warning(msprintf(tmp,Driver,'Amplitude',val,-10,10, sign(val)*10));
					val = sign(val)*10;
				else
					val = abs(val);
					if val>20, 
						tmp = gettext("%s: %s must be < %d V => Set to %d V\n");
						warning(msprintf(tmp,Driver,'Amplitude',20,20));
					end
				end
				rep = rep + writeserial(channel,"AMP:"+string(min(val,20))+ascii(13));
		
			// ==============================================================================

			case "offset"
				val = string(argu);
				if ~isnum(val),
					tmp = gettext("%s: Argument for /%s/ command must be a real number\n");
					error(msprintf(tmp,Driver,'Offset'));
				else val = strtod(val);
				end
				if val<-10 | val>10, 
					val = sign(val)*10;
					tmp = gettext("%s: |%s| value must be < %d V => Set to %d V\n");
					tmp = msprintf(tmp,Driver,'Offset',10,val);
					warning(tmp);
				end
				rep = rep + writeserial(channel,"OFF:"+string(val)+ascii(13));

			// ==============================================================================

			case "frequency"
				val = string(argu);
				if ~isnum(val),
					tmp = gettext("%s: Argument for /%s/ command must be a real number\n");
					error(msprintf(tmp,Driver,'Frequency'));
				else val = abs(strtod(val));
				end
				if val<0.01 | val>5e6
					val0 = val;
					if val<0.01
                         val = 0.01
                    else val = 5e6
                    end
					tmp = gettext("%s: %s F=%d Hz must be 10 mHz <F< 5 MHz => Set to %d Hz\n");
					tmp = msprintf(tmp,Driver,'Frequency',val0,val);
					warning(tmp);
				end
				if Shape=='triangle' & val>1e5
					tmp = "%s: Triangle signal: Linearity < 1%% ensured up to F=100 kHz\n"+..
						  "           F=%d Hz might result in distorted signal\n";
					tmp = msprintf(gettext(tmp),Driver,val);
					warning(tmp);
				end
				if (Shape=='slope-' | Shape=='slope+') & (val < 0.2 | val>1e5 )
					tmp = "%s: Slope signal: ~ Linearity ensured over 200 mHz - 100 kHz domain\n"+..
						  "           F=%d Hz might result in distorted signal\n";
					tmp = msprintf(gettext(tmp),Driver,val);
					warning(tmp);
				end

                rep = rep + writeserial(channel,"FRQ:"+string(val)+ascii(13))
                TCL_SetVar(channel+".F",string(val)) 

			// ==============================================================================

			case "rwidth"
				val = string(argu);
				if ~isnum(val),
					tmp = gettext("%s: Argument for /%s/ command must be a real number\n");
					error(msprintf(tmp,Driver,'rWidth'));
				else val = abs(strtod(val));
				end
//				if val<1e-6 | val>90,
//					tmp = "%s: Pulse''s width W [s] must be  1µs < W < 90 s => Value unchanged\n";
//					tmp = msprintf(gettext(tmp), Driver);
//					error(tmp);
//				end
				if val<5 | val>95,
					val0 = val;
					if val <5, val=5; else val=95; end
					tmp = "%s: Relative width rW=%d %% must be 5%% < rW < 95 %% => set to %d %%\n";
					tmp = msprintf(gettext(tmp),Driver,val0,val);
					warning(tmp);
				end
				rep = rep + writeserial(channel,"DUT:"+string(val)+ascii(13));

			// ==============================================================================

			case "width"
				val = string(argu)
				if ~isnum(val),
					tmp = gettext("%s: Argument for /%s/ command must be a real number\n")
					error(msprintf(tmp,Driver,'rWidth'))
				else val = abs(strtod(val))
				end
				tmin = 0.05/F 
                tmax = 0.9/F
				if val<tmin | val>tmax,
					val0 = val
					if val <tmin
                        val = tmin
                    else 
                        val = tmax
                    end
					Format = "%10.4g"
					Fs = stripblanks(msprintf(Format,F))
					val0s = stripblanks(msprintf(Format,val0))
					tmins = stripblanks(msprintf(Format,tmin))
					tmaxs = stripblanks(msprintf(Format,tmax))
					vals  = stripblanks(msprintf(Format,val))
					tmp = "%s: At F="+Fs+" Hz, pulse width W="+val0s+" s must be "+..
						tmins+" < W < "+tmaxs+" s => set to "+vals+" s\n"
					warning(msprintf(tmp,Driver))
				end
				rep = rep + writeserial(channel,"DUT:"+string(val*F*100)+ascii(13))
			// ==============================================================================

			case "modulation"
				if (typeof(argu)~='string')
					tmp = "%s: Modulation start/stop: Argument must be a (matrix of) string\n"
					error(msprintf(gettext(tmp),Driver))
				else
					Argu = convstr(argu,'u')
					if size(Argu,'*')==2
						if     Argu(2)=='INT', s='I'
						elseif Argu(2)=='EXT', s='X'
						else
							if Argu(1)~='TTL' & Argu(1)~='STOP'
								tmp = _("%s: Starting modulation: Source type /%s/ unknown. Must be ''int'' or ''ext''\n")
								error(msprintf(tmp, Driver, argu(2)))
							// else: second component ignored
							end
						end
					end
					if or(size(Argu,'*')==[1 2]),
						a = Argu(1);
						if     a=='TTL',  rep=rep + writeserial(channel,"MOD:GATE"+ascii(13))
						elseif a=='STOP', rep=rep + writeserial(channel,"MOD:OFF"+ascii(13))
						elseif or(a==['SWP' 'AM' 'FM' 'FSK' 'PSK'])
							rep = rep + writeserial(channel,"MOD:"+a+s+ascii(13))
						else
							tmp = _("%s: Modulation type|command /%s/ unknown. Available types are ''SWP''|''AM''|''FM''|''FSK''|''PSK''\n");
							error(msprintf(tmp, Driver, argu(1)))
						end
					else
						tmp = _("%s: Starting modulation: Wrong argument size\n")
						error(msprintf(tmp, Driver))
					end
				end

			// ==============================================================================
			
			case "modparam"
				if size(argu,'*')==2
					if typeof(argu)~='string',
						tmp = _("%s: AM|FM|FSK|PSK Modulation parameters: Wrong argument type: Must be a string vector\n")
						error(msprintf(tmp, Driver))
					else
						a = convstr(stripblanks(argu(1)),'u')
						if ~or(a==['AM' 'FM' 'FSK' 'PSK'])
							tmp = _("%s: Modulation parameters: Unknown modulation type /%s/. Must be ''AM''|''FM''|''FSK''|''PSK''\n")
							error(msprintf(tmp, Driver, argu(1)))
						else
							// Checking second argument (value)
							b = convstr(stripblanks(argu(2)),'u')
							select a
								// --------------------
								case 'AM'
									if ~isnum(b)
										tmp = _("%s: AM amplitude must belong to [0 100] %%\n")
										error(msprintf(tmp, Driver))
									end
									b = strtod(b);
									if b<0 | b>100
										tmp = _("%s: AM amplitude A=%10.4g must be 0< A <100 [%%]\n")
										error(msprintf(tmp, Driver, b))
									end
								// ---------------------
								case 'FM'
									arr = ['100' '300' '1K' '3K' '10K' '30K' '100K' '300K' '1M']
									if ~or(b==arr)
										tmp = _("%s: FM amplitude must be one of ")+..
												"''"+strcat(arr,"''|''")+"''\n"
										error(msprintf(tmp, Driver))
									end
								// ---------------------
								case 'FSK'
									if ~isnum(b)
										tmp = _("%s: FSK amplitude must be a number\n")
										error(msprintf(tmp, Driver))
									end
								// ---------------------
								case 'PSK'
									if ~isnum(b)
										tmp = _("%s: PSK amplitude must be a number [0 360] °\n")
										error(msprintf(tmp, Driver));
									end
							end	// select a
							rep = rep + writeserial(channel,a+":"+string(b)+ascii(13))
						end		// if AM FM FSK PSK
					end			// argument is a 1x2 string matrix

				// ------------------------------------
				elseif size(argu,'*')==4   // Sweep parameters
				
					dt = 150;	// for sleep(). 100ms too short
					if typeof(argu)~='constant'
						tmp = _("\n%s: Wrong argument type for /Sweep/ parameters.")+ascii(10)+..
							  _("         Must be a 4-real vector [ 0|1  Fstart  Fend  Periode]\n")
                        error(msprintf(tmp, Driver))
					end
					//else: Sweep parameters: [ 0|1 , F_start , F_end , Periode ]
					
					// Type of sweep: Linear | Logarithmic
					if argu(1)~=0 & argu(1)~=1
						tmp = _("%s: Sweep parameters: arg(1) for Type must be 0 (Lin) or 1 (Log)\n")
						error(msprintf(tmp, Driver))
					elseif argu(1)==0, rep = rep + writeserial(channel,"SWP:LIN"+ascii(13))
					else,               rep = rep + writeserial(channel,"SWP:LOG"+ascii(13))
					end
					sleep(dt)
					
					// Starting frequency [Hz]
					val = abs(argu(2))
                    i = %F
					if val<0.2
                        i = %T
                        val = 0.2
                    elseif val>5e6
                        i = %T
                        val = 5e6
                    end
					if i
						tmp = _("%s: Sweep parameters: %s must be 200 mHz < Fstart < 5 MHz : %d reset to %d\n")
						warning(msprintf(tmp, Driver, "F_start", argu(2), val))
					end
					rep = rep + writeserial(channel,"STT:"+string(val)+ascii(13))
					sleep(dt)
					
					// Ending frequency [Hz]
					val = abs(argu(3))
                    i = %F
					if val<0.2
                        i = %T
                        val = 0.2
                    elseif val>5e6
                        i = %T
                        val = 5e6
                    end
					if i
						tmp = _("%s: Sweep parameters: %s must be 200 mHz < Fstart < 5 MHz : %d reset to %d\n")
						warning(msprintf(tmp, Driver, "F_end", argu(3), val))
					end
					rep = rep + writeserial(channel,"STP:"+string(val)+ascii(13))
					sleep(dt)

					// Periode [s]
					val = abs(argu(4))
                    i = %F
					if val<0.02
                        i = %T
                        val = 0.02
                    elseif val>20
                        i = %T
                        val = 20
                    end
					if i
						tmp = _("%s: Sweep parameters: %s must be 20 ms < Periode < 20 s : %d reset to %d s\n")
						warning(msprintf(tmp, Driver, "Periode", argu(4), val))
					end
					rep = rep + writeserial(channel,"TIM:"+string(val)+ascii(13))
					sleep(dt)

				else
					tmp = _("%s: Modulation parameters: Wrong argument size\n")
					error(msprintf(tmp, Driver))
				end
				
			// ==============================================================================

			case "limite"
				val = convstr(string(argu),'l')
				if     val=='on',  rep = rep + writeserial(channel,'LIM:ON'+ascii(13))
				elseif val=='off', rep = rep + writeserial(channel,'LIM:OFF'+ascii(13))
				elseif isnum(val), rep = rep + writeserial(channel,'LIM:'+val+ascii(13))
				else
					tmp = _("%s: Possible argument for /%s/ command: ''on'' | ''off'' | numerical value\n")
					error(msprintf(tmp, Driver, 'Limite'))
				end
				
			// ==============================================================================				
			
			case "fmeter"
				txt = 'ON';
				if	(typeof(argu)=='boolean')
					if ~argu, txt = 'OFF'; end
				elseif typeof(argu)=='string'
					if convstr(argu,'l')=='on', 
					elseif convstr(argu,'l')=='off'
                        txt = 'OFF'
					else txt = 'err'
					end
				elseif	typeof(argu)=='constant'
					if argu==0
                        txt = 'OFF'
                    end
				else txt = 'err'
				end
				if txt~='err'
					rep = rep + writeserial(channel,"CNT:"+txt+ascii(13))
				else
					tmp = _("%s: Unrelevant argument /%s/ for Fmeter command\n")
					error(msprintf(tmp, Driver, string(argu)))
				end
				
			// ==============================================================================

			case "store"
				// Note: One reserves set #0 to set of default parameters
				tmp = _("%s: Sets of parameters are numbered from 1 to 9. Store''s argument must be an integer from 1 to 9\n")
				val = string(argu)
				if ~isnum(val), error("\n"+msprintf(tmp, Driver))
				else
					val = strtod(val)
					if val~=fix(val) | val<1 | val>9
                        error(msprintf("\n"+tmp, Driver))
                    end
					rep = rep + writeserial(channel,"STO:"+string(val)+ascii(13))
				end
				
			// ==============================================================================

			case "recall"
				// Note: One reserves set #0 to set of default parameters
				tmp = _("%s: Sets of parameters are numbered from 0 to 9. Recall''s argument must be an integer from 0 to 9\n")
				val = string(argu)
				if ~isnum(val), error(msprintf(tmp, Driver))
				else
					val = strtod(val)
					if val~=fix(val) | val<0 | val>9
                        error(msprintf(tmp, Driver))
                    end
					rep = rep + writeserial(channel,"RCL:"+string(val)+ascii(13))
				end
				
			// ==============================================================================

			case "reset"
				rep = rep + writeserial(channel,"RCL:0"+ascii(13))

		end // select command
	end // while there are ("command", arg) pairs

endfunction

