C++ SDK Guide

Setting up the Project

First create a new project.

Include Mirrorcle’s header files:

#include "MTIDevice.h"        // Includes all released functions
#include "MTIDefinitions.h"   // Includes structures: parameters, data modes, error codes, etc
#include "MTIDataGenerator.h" // Includes content generation and manipulation functions

Create MTIDevice instance by calling the constructor MTIDevice::MTIDevice()

MTIDevice* mti = new MTIDevice;

This handle can now be used to call any other Mirrorcle API functions

Search for Devices

Scan for connected devices and (let user) select the target device. All detected Mirrorcle devices along with their respective firmware and connection properties are stored in the structure MTIAvailableDevices.

To optionally print a list of Mirrorcle devices to the console, use function MTIDevice::ListAvailableDevices().

MTIAvailableDevices* mDeviceList = mti->GetAvailableDevices();
mti->ListAvailableDevices( mDeviceList );

To assist users in finding the right device, Mirrorcle provides ‘MirrorcleListDevices.exe’ - a tool which lists all connected Mirrorcle devices on the host.

See MTIAvailableDevices table for a full list of parameters, their corresponding enumerated code, and a brief description.

Note

If the user already knows the COM port name of the USB Controller, it is not necessary to do this step.

Error Handling

Check MTIDevice::GetLastError() method for details after transmitting data. Error codes are defined in MTIError. Variable MTIError::MTI_SUCCESS represents successful transfer communication between API and Controller.

API waits to receive a confirmation from the Controller or times out after 2 seconds.

Example:

MTIError LastError;
LastError = mti->GetLastError();
if (LastError != MTIError::MTI_SUCCESS)
{
   // Handle the error
};

Or, alternatively:

if ( mti->GetLastError() == MTIError::MTI_SUCCESS)
{
   // Continue in the case of no error.
};

Initializing the Device

After creation, the parameter structure MTIDeviceParams is empty (values default set to 1e9).

Mirrorcle recommends to get all device parameters from the device to fill the whole structure.

Update parameters during initialization. Afterwards send the whole structure to the device.

If only a single parameter value has to be changed, it is recommended to update only the single parameter with the MTIDevice::SetDeviceParam() function.

Optionally load parameter values from a file into the MTIDeviceParams structure before sending it to the Controller using MTIDevice::LoadDeviceParams().

The parameters can also be saved into a file (including read only firmware settings).

Getting Device Parameters

After the connection to the device is established, device parameters can be queried directly from the USB Controller. The controller responds with all of the parameters which are stored then in the MTIDeviceParams properties. This includes read only device properties (e.g. the firmware name) and all adjustable device parameters. The first property in the class is “Success” which returns a Boolean value showing whether the communication with the Controller was without error (Success = true) – an important property to check that validates others.

MTIDeviceParams* GetDeviceParams()

MTIDeviceParams* Params = mti->GetDeviceParams( );

Refer to MTIDeviceParams table for a full list of device parameters, and a brief description

Setting Device Parameters

In most cases, user may alter some of the device parameters in the application to be different from the boot-up defaults. A single call to the SetDeviceParams function will send all values to the Controller, after the user modifies some of the parameters in the application.

void SetDeviceParams( MTIDeviceParams* Params )

MTIDeviceParams * Params = mti->GetDeviceParams( );
params->HardwareFilterBw = 300;
params->VdifferenceMax = 80;
params->Vbias = 70;
params->MEMSDriverEnable = true;
params->DataMode = MTIDataMode::Sample_Output;
mti->SetDeviceParams( Params );  // Sends the structure to the device.

Load and Save Device Parameters

Load parameter(s) from different files into the local structure. The structure will be (partially) updated by the provided parameters. Only writable parameters can be loaded. Please note that after loading only the local MTIDeviceParams structure is updated. The settings still need to be transferred to the device.

MTIDeviceParams* LoadDeviceParams( char* fileName )

char *iniFile = "mtidevice.ini";
MTIDeviceParams* iParams = mti->LoadDeviceParams( iniFile );
mti->SetDeviceParams( iParams );

Save the complete MTIDeviceParams structure into a file.

void SaveDeviceParams( MTIDeviceParams Params, char fileName )

Example

mti->SaveDeviceParams( Params, "MyFileName.ini" );

Get and Set a Single Device Parameter

Get a single parameter from the device or set a single parameter in the device. If a parameter during operation needs to be updated, it may be more simple or convenient to alter only that specific parameter rather than operating on the complete structure of parameters.

The two functions for individual MTIDeviceParams manipulation are:

cpp:function:MTIDevice::GetDeviceParam( MTIParam Param )

void SetDeviceParam( MTIParam Param, float paramValue1, float paramValue2=0)

Example

float Vmax = mti->GetDeviceParam( MTIParam::VmaxMEMSDriver );
mti->SetDeviceParam( MTIParam::VdifferenceMax, Vmax * 0.75f );

// Example of SetDeviceParams with two float value inputs:
mti->SetDeviceParam( MTIParam::OutputOffsets, 0.5f, -0.155f );

Initialize Device - Example

Initialization Example

MTIDeviceParams* Params = mti->GetDeviceParams( );
params->VdifferenceMax = 100;       // Maximum Voltage in device datasheet
params->HardwareFilterBw = 300;     // Recommended LPF Cutoff in device datasheet
params->MEMSDriverEnable = true;
params->DigitalOutputEnable = true;
params->SampleRate = 5000;
params->DataScale = 0.7f;
params->DeviceAxes = MTIAxes::MirrorX;
params->BootSetting = MTIBoot::Boot_With_Factory_Defaults;

mti->SetDeviceParams( Params );

if ( mti->GetLastError( ) != MTIError::MTI_SUCCESS)
{
   // Check for error
};

mti->ResetDevicePosition();  // Resets device position to center

Content Generation and Execution

Output Operation

Please note that all user-supplied coordinate data (Xdata and Ydata in normalized co-ordinates from -1 to +1 on each axis) is processed by parameters defined in the MTIDevice object.

Those parameters are applied according to the following scheme:

All User Data is stored as normalized coordinates in memory. The data is first scaled by the DataScale value (0 to 1 value). Note that data may also be rotated in this step in the x-y plane, if the rotation parameter is non-zero when setting the DataScale.

Then, offsets OffsetX and OffsetY are added (each can range from -1 to +1), and resulting processed values Xdata_processed and Ydata_processed are limited to the same -1 to +1 range.

The processed data is then converted into Biased Differential Quad channel (BDQ) outputs with Vbias and scaled with VdifferenceMax, appropriate for outputting to the 4-channel MEMS driver. The final outputs of each channel are therefore computed as:

VX+ = Vbias + Xdata_processed * VdifferenceMax/2

VX- = Vbias - Xdata_processed * VdifferenceMax/2

VY- = Vbias - Ydata_processed * VdifferenceMax/2

VY+ = Vbias + Ydata_processed * VdifferenceMax/2

../_images/output_operation.png

Please Note: Different output modes of operation are available and introduced on the next slides. It is possible to steer the MEMS mirror to a position by MTIDevice::GoToDevicePosition() function or make use of different DataModes. All modes require normalized coordinates of float type for X and Y and unsigned 8-bit value, char type for M (digital output).

(X-Coordinate, Y-Coordinate, M-DigitalOutput)

Digital Output

For each MEMS position sample, there is a corresponding M Digital Output value that is outputted on the DOut connector on the MEMS controller. These 8-bit correlated digital outputs can be used to trigger various lasers, cameras, sensors, etc.

Additionally, the DOut0 bit can be copied over to the Sync Port, which has a power and ground pin available to drive low power lasers with TTL modulation

The Digital Output port can be enabled and disabled using the MTIParam.DigitalOutputEnable.

mti->SetDeviceParam( MTIParam::DigitalOutputEnable, 0 );

Move MEMS Mirror to a Position

The MTIDevice::GoToDevicePosition() function steers the MEMS mirror from the present position to new X, Y coordinates in a defined time in milliseconds (minimum value: 1ms).

All necessary points inbetween are created for the mirror to move. It requires normalized coordinates (float) with an 8 bit value (char) to define the digital output state.

(X-Coordinate, Y-Coordinate, M-DigitalOutput)

void GoToDevicePosition( float x, float y, unsigned char m, unsigned int mSec)

float xData = 0.5324f;
float yData = 0.6f;
unsigned char mData = 8;
unsigned int mSec = 10;
mti->GoToDevicePosition( xData, yData, mData, mSec );
// Go to new position:
mti->GoToDevicePosition( 0.3f, -0.44f, 128, 100 );

Note

The resulting device response is also a function of the hardware filter setting.

DataModes for Input/Output Operation

Another possibility to drive the output and to read analog inputs are DataModes.

Note

The typical maximum length is limited to 25,000 points and normalized coordinates (float) with an 8 bit value (char) to define the digital output state is required.

(X-Coordinate, Y-Coordinate, M-DigitalOutput)

These modes require different input data to be set with proper parameters before sending data. (Refer to MTIDataMode table for details)

void SetDeviceParam( MTIParam Param, float paramValue1, float paramValue2=0 )

The content data (if applicable) has to be transferred. Operation can be started and stopped.

Send Data to the Device

The content data has to be transferred to the device.

void SendDataStream( float* x, float* y, unsigned char* m, unsigned int numSamples, unsigned int delaySamples, bool minimizeJump = true )

mti->SendDataStream( xData, yData, mData, nSamples, 0, true );
if ( mti->GetLastError( ) != MTIError::MTI_SUCCESS)
{
   // Check for error
};

If the device is already running, i.e. outputting samples, executing this command will automatically transition the device to running the new data.

The transition will occur after the transfer of data is complete and also the device completes one fully cycle of buffer output that it is already tasked to run.

If the device is not running during this command, the command will only transfer the data and the device will not automatically start executing/outputting the new data (a StartDataStream command is necessary)

Start and Stop Output Operation

After uploading the data to the device, it will by default start outputting it immediately.

Controlled start and stop operation is also possible by calling the command. The number of repetition can be defined. For infinite runs the device will confirm the start (GetLastError) and run till it received a stop command.

It is recommended to StopDataStream before making any changes to DataMode or SyncMode params.

After the params have been changed, download content to the controller with MTIDevice::SendDataStream() and issue a MTIDevice::StartDataStream() when ready.

void StartDataStream( int repeatCount = -1)

void StopDataStream( )

mti->StartDataStream(  );           // Start and infinite repeat
mti->StartDataStream( -1 );         // Start and infinite repeat
mti->StartDataStream( 1 );          // Start once
mti->StartDataStream( 32 );         // Repeat 32 times (maximum)
mti->StopDataStream( );             // Stop the output operation

if ( mti->GetLastError( ) != MTIError::MTI_SUCCESS)
{
   // Check for error
};

Sample_Output DataMode

Mode Sample_Output is the default mode if no DataMode is set during initialization. The sample length is defined by the user.

First set the MTIDataMode (optional), second set the sample length, and finally send the data stream. By default output operation starts after data transfer.

void SetDeviceParam( MTIParam Param, float paramValue1, float paramValue2=0 )

Example:

mti->SetDeviceParam(MTIParam::DataMode,  MTIDataMode::Sample_Output);
unsigned int nSampleLength = 5500;
mti->SendDataStream( xData, yData, mData, nSampleLength, 0, true );

// A single run start data stream can be issued
mti->StartDataStream( 1 );       //  Start and run once

// Alternatively, an infinite run start data stream can be issued
mti-> StartDataStream( -1 );     //  Start and run infinitely

Sample_And_Analog_Input_Buffer MTIDataMode

Mode Sample_And_Analog_Input_Buffer is similar to basic “Sample_Output” mode in the sense that the Controller streams X,Y,M samples to its outputs according to user-provided parameters. However in this mode, the Controller is also simultaneously storing measurements on two analog inputs into the buffer for later upload by user. As each sample is executed at the SampleRate, two analog input values are stored into the buffer.

In this mode, MTIDevice::StartDataStream() has to be used with a single repeat (cannot be set to infinity).

In this mode, available Buffer size is 1/2 of total, allotting space for storing of AI values.

To use, first stop the data stream, set the DataMode, send the data and start operation. Afterwards the data has to be requested from the device with GetAnalogInputBuffer.

void GetAnalogInputBuffer( float* DataAI0, float* DataAI1, unsigned int DataLength )

mti->StopDataStream(); // First stop the data stream
mti->SetDeviceParam( MTIParam::DataMode, MTIDataMode::Sample_And_Analog_Input_Buffer ); // Set the DataMode
unsigned int nSampleLength = 2500;
mti->SendDataStream( xData, yData, mData, nSampleLength, 0, true ); // Send the data to the Controller
mti->StartDataStream( 1 ); // Start the data stream (argument of 1 scans the waveform once)
float* ChannelData0;
float* ChannelData1;
mti->GetAnalogInputBuffer( ChannelData0, ChannelData1, 500 );

Analog_Input_To_Output MTIDataMode

MTIDataMode Analog_Input_To_Output lets the device sample the analog inputs at maximum speed and use these values as normalized coordinates to drive the output.

When changing between any DataModes, it is recommended to first issue a StopDataStream, make the changes to the parameters, download new content, and issue a StartDataStream to begin.

void SetDeviceParam( MTIParam Param, float paramValue1, float paramValue2=0 )

mti->StopDataStream();
// Edit additional parameters and generate content
mti->SendDataStream(x, y, m, numSamples);
mti->SetDeviceParam( MTIParam::DataMode, MTIDataMode::Analog_Input_To_Output );
mti->StartDataStream();

Analog_Input_Stream MTIDataMode

Gets the sampled values from channel 1 or 2 as stream in real time. Gets as many as are available, returns how many were obtained.

unsigned int GetAnalogInputStream( float* AI0, float* AI1, unsigned int DataLength = 0 );

unsigned int npts = 800, length = 0;
float *ai0 = new float[npts], *indices = new float[npts];
length = mti->GetAnalogInputStream( ai0, indices, npts );

Real-Time Content Manipulation

During execution the output can be modified by changing the parameters. Certain parameters can be changed in Real Time without having to download new content.

void SetDeviceParam( MTIParam Param, float paramValue1, float paramValue2=0)

Scale and/or Rotate output amplitude by changing the parameter DataScale.

mti->SetDeviceParam( MTIParam::DataScale, 0.5 );
mti->SetDeviceParam( MTIParam::DataScale, 0.5, 45 );

Change devices axes (flip and mirror) by modifying the parameter MTIAxes.

mti->SetDeviceParam( MTIParam::MTIAxes, MirrorX_MirrorY );
mti->SetDeviceParam( MTIParam::MTIAxes, MirrorX_FlipXY );

Apply an offset on the outputs with the parameter OutputOffsets.

X and Y offset values must be provided simultaneously, therefore two float parameters must be passed to the function.

SetDeviceParam( MTIParam::OutputOffsets, 0.1f, 0.5f );

Sample Analog Inputs

The USB controller has 2 Analog Input channels.

Both ADC have a resolution of 12 Bit and can sample external analog signals in a range of +/- 10V.

To get one sample during operation:

float GetAnalogInputValue( unsigned int ChannelNumber )

unsigned int ChannelNo = 1;
float OneAnalogSample = mti->GetAnalogInputValue( ChannelNo );

To sample for a defined length into the buffer:

void GetAnalogInputBuffer( float* DataAI0, float* DataAI1, unsigned int DataLength )

For more information on the GetAnalogInputBuffer function, please refer to the Sample_And_Analog_Input MTIDataMode section.

Synchronization (Sync) Port

The SyncMode parameter allows the user to set the Sync port on the controller to provide triggers to external sources such as lasers, cameras, etc.

Alternately, the sync port can be used synchronize multiple MEMS controllers. This function re-defines the operation of the middle pin of the Sync port.

When changing between any SyncModes, it is recommended to first issue a StopDataStream, make the changes to the parameters, download new content, and issue a StartDataStream to begin.

void SetDeviceParam( MTIParam Param, float paramValue1, float paramValue2=0)

mti->StopDataStream();
// This sets the controller to output its current sample clock signal to the Sync port.
mti->SetDeviceParam(MTIParam::SyncMode, MTISync::Output_Sample_Clock);
mti->SendDataStream(x, y, m, numSamples);
mti->StartDataStream();

Ensure you change the SyncMode back to Output_DOut0 before disconnecting from the controller at the end of the session.

Synchronization Port for External Triggering

The SyncMode parameter allows the user to set the Sync port on the controller to be triggered from external sources.

This function re-defines the operation of the middle pin of the Sync port.

void SetDeviceParam( MTIParam Param, float paramValue1, float paramValue2=0)

mti->StopDataStream();
mti->SetDeviceParam (MTIParam::SyncMode, MTISync::External_Sample_Clock);
// This sets the controller to input a sample clock signal from an external source, and run at that clock rate
mti->SendDataStream(x, y, m, numSamples);
mti->StartDataStream();

Ensure to change the SyncMode back to Output_DOut0 before disconnecting from the controller at the end of the session.

Synchronizing Multiple Devices

Work with multiple devices with the MTISync settings.

In order to synchronize multiple devices, one Controller should be set as the Master, with the MTISync setting as Output_Start_Trigger or Output_Sample_Clock.

All other devices should be Slave devices, with the MTISync setting as External_Start_Trigger or External_Sample_Clock.

void SetDeviceParam( MTIParam Param, float paramValue1, float paramValue2=0)

Example (for the Master Device):

mti->SetDeviceParam (MTIParam::SyncMode, MTISync::Output_Start_Trigger);
// This sets the controller to output a start trigger at the beginning of each frame

Example (for the Slave Device):

mti->SetDeviceParam (MTIParam::SyncMode, MTISync::External_Start_Trigger);
// This sets the controller to receive an external start trigger from the Master device

Refer to the ‘Additional Parameter Tables’ at the beginning of this guide for details.

Save to Flash – Parameters

Store customized parameters and data in the device.

Make use of the auto run mode.

Manipulate device parameters and/or prepare output data for the device.

void SaveToFlash( MTIFlash flashsave )

Example:

mti->SaveToFlash( MTIFlash::Device_Params );
mti->SetDeviceParam( MTIParam::BootSetting, MTIBoot::Boot_With_Flash_Data_And_Autorun);

To return to boot with factory defaults:

mti->SetDeviceParam( MTIParam::BootSetting, MTIBoot::Boot_With_Factory_Defaults);

Refer to the ‘Additional Parameter Tables and Settings’ at the beginning of this guide for details.

Save to Flash – Data in Buffer

Store customized parameters and data in the device. Make use of the auto run mode. Manipulate the device parameters and/or prepare output data for the device.

void SaveToFlash( MTIFlash flashsave )

mti->SendDataStream( x, y, m, npts, 0, false );
mti->SaveToFlash( MTIFlash::Data_In_Buffer );
mti->SaveToFlash( MTIFlash::Device_Params);
mti->SetDeviceParam( MTIParam::BootSetting, MTIBoot::Boot_With_Flash_Data_And_Autorun);

Refer to the ‘Additional Parameter Tables and Settings’ at the beginning of this guide for details.

Debug, Disable, and Disconnect

Debug - Mirror Position

Query X, Y, or M data of the sample on which the execution was last stopped.

Note

Data is only valid after StopDataStream() is executed.

Data can be used to determine where the device is relatively pointing:

Query the memory address after stop of output operation or stop command send:

Reset Mirror position to center:

Debug - Communication

To check if the device is running (outputting data).

Clear the input buffer if the data wasn’t transferred successfully or the output is wrong.

Reset the device communication and clear receive buffer.

To reset the device to its boot-up settings:

Debug - Lost Connection

It could happen that the user unplugs the USB connection or leaves the devices wireless range.

In case of a lost connection, it is useful to try to reconnect for a certain defined time.

The devices default timeout will be after 2 seconds.

In some cases the device will continue execution of the content before disconnection. If the output projection is affected it may be helpful to reset the mirror position after reconnection.

To verify that a device is responding to serial commands:

bool status = mti->IsDeviceResponding();

To verify that a device socket is non-NULL (should be true when serial socket is connected):

bool status = mti->IsDeviceConnected();
if (status == true)
   printf("Connected");

Disable & Save Device Parameters

The application has to consider different events. The typical case will be the user likes to switch off the device and/or close the application.

In any case Mirrorcle recommends to disable the digital outputs and switch off the MEMS driver.

mti->SetDeviceParam( MTIParam::DigitalOutputEnable, 0 );
mti->SetDeviceParam( MTIParam::MEMSDriverEnable, 0 );

Optionally save all parameters in a file.

MTIDeviceParams* sParams = mti->GetDeviceParams();
mti->SaveDeviceParams( sParams, "MyFileName.ini");

Disconnect & Delete Application

Disconnect the device (this releases the COM port).

MTIDevice::DisconnectDevice()

Example:

mti->DisconnectDevice();

Delete the MTIDevice object in memory:

MTIDevice::DeleteDevice()

DeleteDevice();