====== Windows ====== You can download the library from [[http://sub2r-download.com/SUB2r-lib-2022-08-22.zip|this location]]. What you get in this package is: - A header file ''FX3.h'' - the only include you need besides the standard headers - Both ''Release'' and ''Debug'' libraries with full symbol files (''.pdb'') and support for edit and continue (''.idb'' - currently only for ''Debug'' mode). These include the libraries from Cypress that this package depends on. - Sample code that demonstrates some basics on how to use the library: ''sample1.cpp'' - Sample code that showcases the use of ''sysinfo'': ''sample2.cpp'' - Sample code for controlling FPS: ''sample3.cpp'' The library is built using MSVC 2022 version ''17.3.1'' with Windows SDK ''10.0.19041.0'' for use with ''UNICODE'' and ''multi-threaded'' CRT in ''64-bit'' and ''32-bit'' mode. The library requires a C++20 toolchain to be built. ===== Basic "vendor request" call ===== SUB2r API calls are implemented as USB "vendor request" commands, where ''USB_SETUP_PACKET::bmRequest::Type'' is set to ''2'', following the USB specification. Sample usage: using S2R; using S2R::FX3; FX3 fx3; // by default opens device #0 if(fx3.isValid()){ array vi{}; const auto rc{m_fx3.vrCmd(Fx3Cmd::fx3_version // vendor request command , VrCmdOpType::read // read from device , 0 // "value" , 0 // "index" , vi)}; // buffer to read into return vi; // 32 bits of the "version info" (4 bytes) } ===== Class diagram ===== {{ :code:sub2r-lib-class-overall.png?nolink |SUB2r-lib class diagram overview}} ==== ApiCmdLog ==== {{ :code:class-apicmdlog.png?nolink|Class diagram - ApiCmdLog}} A simple logging mechanism for all the API calls to the camera. This is a singleton object that pre-allocates 10240 log entries (320KB memory) and grows beyond that if necessary. ^ Method ^ Signature ^ Functionality ^ | ''enable'' | static void enable(bool _b) noexcept; | enable/disable logging, doesn't affect already stored log entries | | ''clear'' | static void clear() noexcept; | clear the log, removing all the entries (this doesn't necessarily free up memory) | | ''getLog'' | static const std::vector
& getLog() noexcept; | access the log | This class provides a convenient data type for the "time stamp" values: using timestamp_t = decltype(std::chrono::high_resolution_clock::now()); === ApiCmdLog::Entry === //Used to be %%ApiCmdLog::Details%%// This internal class represents each individual log entry. ^ Method ^ Signature ^ Functionality ^ | ''constructor'' | Details(bool _dir2Dev , uint8_t _cmd , uint16_t _val , uint16_t _idx) noexcept; | log an ''FX3'' entry | | ''constructor'' | Details(uint16_t _addr = 0 , uint16_t _val = 0 , uint16_t _mask = 0) noexcept; | log an ''I²C'' entry | | ''toString'' | std::string toString() const; | returns a formatted string representation of the log entry | | ''legend'' | static std::string legend(); | description of the log entries' fields | ^ Variable ^ Use ^ | timestamp_t started; | the time when API request was issued | | std::chrono::nanoseconds duration{0ns}; | duration, in ns | | const enum class DebugLogType : uint8_t{fx3, i2c} type; | the type of log entry | | bool success{}; | result of the API request | | fx3; | valid if ''type == %%DebugLogType%%::fx3'' | | i2c; | valid if ''type == %%DebugLogType%%::i2c'' | == ApiCmdLog::Entry:: fx3 == ''FX3''-related log entry data. ^ Variable ^ Use ^ | const bool dir2Dev; | to or from device (from or to host) | | const uint8_t cmd; | numeric code of the command | | const uint16_t val; | "value" provided to the API request | | const uint16_t idx; | "index" value of the API request | | std::array ret{}; | first 4 bytes of the return buffer | == ApiCmdLog::Entry:: i2c == ''I²C''-related log entry data. ^ Variable ^ Use ^ | const uint16_t addr; | target register | | const uint16_t val; | value to be written | | const uint16_t mask; | bits to affect when writing the above value | | uint16_t valRead; | value that was read back | | uint16_t valWrite; | value that was written out (after combining the write-out value with the read-in value using the ''mask'') | === ApiCmdLog::Helper === This following tasks are automated by this internal class: add a new entry on construction and start the clock, log time in destructor. The user of this object is responsible for updating API's status info (''status'' member of the ''Details'' struct). ^ Method ^ Signature ^ Functionality ^ | ''constructor'' | Helper(bool _dir2Dev , uint8_t _cmd , uint16_t _val , uint16_t _idx); | log an ''FX3'' entry | | ''constructor'' | Helper(uint16_t _addr , uint16_t _val , uint16_t _mask); | log an ''I²C'' entry | | ''destructor'' | ~Helper(); | just calls close(); | | ''curr'' | auto & curr() noexcept; | "safe" method to access the current log entry - provides dummy entry if the logging is disabled. Here the ''auto'' is ''%%ApiCmdLog%%::Entry'' | | ''close'' | void close() noexcept; | log the time and close the entry | ==== FX3 ==== {{ :code:class-fx3.png?nolink|Class diagram - FX3}} This is the main class to access the FX3 hardware component - the module responsible for the overall communication with the camera. ^ Method ^ Signature ^ Functionality ^ | ''FX3'' | FX3(std::chrono::milliseconds _defaultTimeout = 500ms) noexcept; | Construct the object, optionally setting the USB communication timeout, in milliseconds. This automatically opens the device #0 for read/write and queries its properties | | ''isValid'' | bool isValid() const noexcept; | returns ''true'' if the device is open and operational | | ''deviceCount'' | uint8_t deviceCount() const noexcept; | return the number of Cypress-based USB devices attached to the system | | ''open'' | bool open(uint8_t _dev); | open a device by its ''0''-based index, return ''true'' on success | | ''close'' | virtual void close() noexcept; | close the device, freeing up the associated resources | | ''getDevPath'' | static std::wstring getDevPath(uint8_t _devNo = 0); | device path is unique per device and is valid across reboots, returns empty string if device not found | | ''open'' | bool open(IMFActivate * _src); | **Windows-specific**\\ open FX3 device that corresponds to the given %%IUnknown%% (%%IMFMediaSource%% or %%IMFActivate%%) - useful when you need to access camera's extended features when only having the standard Windows's media object | | ''toInterface'' | CComPtr toInterface() const; | **Windows-specific**\\ find the media interface for this FX3 device - useful when you need to access UVC for the camera device that corresponds to that ''FX3'' | | ''operator[]'' | XXX operator[](YYY _prop) const; | 6 overloads to read various types of properties. The return type ''XXX'' depends on the argument type ''YYY''. These are used to access properties like ''device name'', '' USB class'', ''vendor ID'', and others | | ''fx3Version fpgaVersion'' | const VersionInfo & fx3Version() const noexcept; const VersionInfo & fpga3Version() const noexcept; | get detailed version information on both the ''FX3'' and ''FPGA'' as reported by the device. This information is read and cached when the device is opened | | ''vrCmd'' | bool vrCmd( Fx3Cmd _cmd /*uint8_t _cmd*/ , VrCmdOpType _opType , WORD _val, , WORD _idx , gsl::span _buf = {}); | 2 overloaded versions that differ in the first parameter: whether it is one of the predefined ''S2R::FX3::Fx3Cmd'' or a plain ''uint8_t'' command code. Runs a "vendor request command", providing it with the following info:\\ ''_opType'' - either ''read'' or ''write''\\ ''_val'' - a ''WORD'' value to use\\ ''_idx'' - some commands require a ''WORD'' ''index'' parameter\\ ''_buf'' - a buffer to read into, only for the ''read'' type operations\\ ''_bufLen'' - a length of the ''_buf'' buffer in bytes | | ''vrCmd'' | template< typename T = std::enable_if_t< std::is_standard_layout::value > > bool vrCmd( Fx3Cmd _cmd /*uint8_t _cmd*/ , VrCmdOpType _opType , WORD _val, , WORD _idx , gsl::span _buf = {}); | similar to non-template version but instead of a byte buffer these accept a pointer to a "standard layout" object | | ''programFwFPGA'' | bool programFwFPGA( const vector & _buf , std::chrono::milliseconds _timeout = 90s ) const; | program the FPGA with the provided buffer that contains the firmware code, fail if timeout is reached | | ''programFwFX3'' | bool programFwFX3( const vector & _buf ) const; | program the FX3 with the provided buffer that contains the firmware code | | ''destructor'' | virtual ~FX3(); | calls the ''close()'' | ==== I2C ==== {{ :code:class-i2c.png?nolink|Class diagram - I²C}} When accessing image sensor's chip via I²C you use an instance of this class. ^ Method ^ Signature ^ Functionality ^ | ''I2C'' | I2C() | default constructor, just calls ''FX3'' with a ''750ms'' timeout parameter | | ''operator bool'' | operator bool() const; | returns ''true'' if the device is opened and ready to accept vendor and I²C commands | | ''setCtrlPort'' | void setCtrlPort( uint8_t _port ); | sets a new value to be used as a ''control port'' and if successful read the chipset info and create a corresponding ''%%IImgSensor%%'' object | | ''getCtrlPort'' | uint8_t getCtrlPort() const noexcept; | get the current ''control port'', which is also set automatically on ''open()'' | | ''operator->'' | const IImgSensor* operator->() const noexcept; | access the interface object that is created based on the detected image sensor's chipset and encapsulates a subset of commands that provide direct control over the image sensor | | ''sensor'' | IImgSensor* sensor() noexcept; const IImgSensor* sensor() const noexcept; | 2 versions, const and non-const, that, respectively, return a pointer to a const or a non-const ''IImgSensor'' object. These provide access to the interface object that is created based on the detected image sensor's chipset and encapsulates a subset of commands that provide direct control over the image sensor | | ''operator()'' | uint16_t operator()( uint16_t _addr , uint16_t _val = 0 , uint16_t _mask = 0 ) const; | primary function to send commands\\ ''_addr'' - an address of a register to read/write\\ ''_val'' - a new value for the ''write'' operation, 0 otherwise\\ ''_mask'' - a bitmask to use that specifies which bits to affect when writing a new value, only bits that are set to ''1'' in the bitmask will be changed, then new value of those bits will be copied over from the ''_val'' | | ''groupWrite'' | void groupWrite( const CVecRegValPairs & _group , bool _bUseGroupWrite = true ) const; | For the image sensors that support the notion of a ''group write'' this function allows to update a set of registers as a group at a vertical sync time. No error checking on the results of all those write operations is done in this call.\\ ''_group'' - a vector of address/value pairs to be written\\ ''_bUseGroupWrite'' - if set to ''false'' will do an immediate write, not waiting for the ''VSYNC'' | | ''readN'' | int readN( const CVecRegAddr & _r , int _n ) const; | read ''_n''-bit value into a single integer (up to 32 bits on the current 64-bit code - **not 64 bits!**), the bits are read starting with the first address, that represents the most significant bits (MSB) and continues on into LSB at the end. The first register address contains the "spill-over" MSB while all the others are full 8-bit parts of the final value | | ''writeN'' | void writeN( const CVecRegAddr & _r , int _n , int _val , bool _bUseGroupWrite = true ) const; | similarly to the ''readN()'' this function is used to write an integer value that is over 8 bits, spread over multiple registers with the first address representing the byte with "spill-over" MSB and the last one storing the LSB portion of the value | ==== VersionInfo ==== {{ :code:class-versioninfo.png?nolink|Class diagram - VersoinInfo}} ''%%VersionInfo%%'' encapsulates the details about the version info of the code running on ''FX3'' and ''FPGA'' and is populated once the device is opened with an ''open()'' call (or during the default construction, which implicitly opens the device #0). This is an abstract base class, the actual implementations are only used by the ''FX3'' class. ^ Method ^ Signature ^ Functionality ^ | ''fromBuffer'' | virtual void fromBuffer( gsl::span _buf = {} ) = 0; | aching to a "virtual constructor" this virtual function fills-in the version info from the 4 bytes' buffer returned by the API, normally only used internally by the library itself | | ''legend'' | static wstring legend(); | a UNICODE string representing field-by-field description of the version info elements when formatting as a one-liner | | ''relType'' | static wstring relType(int _rel); | release cycle number -> release cycle text | | ''countRelTypes'' | static int countRelTypes() noexcept; | how many steps are there in the release cycle, ranging from 1..N, inclusive. You can iterate through these values with ''relType()'' to get textual values for all of them | | ''format'' | virtual wstring format( const wstring & _i = L"" ) const = 0; | format the version info into a UNICODE text block, using the provided ''_i'' indent at the beginning of each line | | ''operator string'' | virtual operator string() const = 0; | format the version info as a single line of UNICODE text, call ''legend()'' to get the header for this line | | ''listFw'' | virtual vector listFw( int _rel = 4 ) const = 0; | Get a list of firmware info objects for this device that are for the release cycle ''_rel'' and above.\\ ''Product ID'', ''hardware config ID'', and ''vendor ID'' from the version information must strictly match.\\ Throws an exception of type ''std::string'' on error. | | ''isUpgrade'' | virtual bool isUpgrade(int _bldNo) const noexcept = 0; | returns ''true'' if that build number is an upgrade to the firmware code currently running on the device | | ''isAtLeast'' | virtual bool isAtLeast(unsigned int _bldNo) const noexcept | check if the current code on the device is a given build number or newer | === FwInfo === This internal class caches details about the current firmware code running on the device. ^ Variable ^ Use ^ | unsigned int rel{}; | release cycle | | unsigned int bld{}; | build # | | string url; | a URL to download the latest version of the firmware code based on the currently set desired "earliest release cycle to consider" | | int fsz{-1}; | the size (in bytes) of the firmware code to download from the server | ==== SPIConfigStatus ==== {{ :code:class-spiconfigstatus.png?nolink|Class diagram - SPIConfigStatus}} This ''struct'' simplifies the use of ''FPGA'''s configuration status encoded as individual fields on the hardware. The individual status bits are accessible either as a single ''uint16_t'' member ''m_ui16'', an array of 2 ''uint8_t'' bytes in a member ''m_ui8'', or individually as ''bool'' through the member ''bit''. Here's a short sample code on how to create an instance of and use that struct: S2R::FX3 fx3; /*const auto rc =*/ (void) fx3.open(0); // though don't ignore errors in production code! if(fx3){ uint16_t buf = 0; const auto rc = m_fx3.vrCmd(FX3::Fx3Cmd::fpga_config_status, FX3::VrCmdOpType::read, 0, 0, buf); const S2R::SPIConfigStatus cfg(buf); // use the cfg object, e.g. test for if the config is busy: if(cfg.bit.configNotBusy){ // ... } } ^ Method ^ Signature ^ Functionality ^ | ''constructor'' | SPIConfigStatus(const gsl::span _buf) noexcept; constexpr SPIConfigStatus(uint16_t _val = 0) noexcept; | construct an object from a provided 2-byte buffer or a ''uint16_t'' value | | ''operator uint16_t'' | constexpr operator uint16_t() const noexcept; | return a 16-bit ''WORD'' containing all the config bits in the order listed in the documentation (e.g. if you just want to run a ''for()'' loop over the bits and display their statuses) | | ''hasErrors'' | constexpr bool hasErrors() const noexcept; | returns ''true'' if any of the error bits is set | Individual bits are described in the [[code:fx3_hvci_and_fpga_i_c_commands#fpga_i_c_bridge|FPGA I²C Bridge]] section of the documentation. ==== IImgSensor ==== {{ :code:class-iimgsensor.png?nolink|Class diagram - IImgSensor}} Abstract interface class to deal with the variety of supported image sensors which all have different sets of commands and incompatible register maps. ''I2C'' automatically instantiates a proper subclass once the chipset is detected. All the concrete implementations are in the ''.cpp'' file. ^ Method ^ Signature ^ Functionality ^ | ''constructor'' | = delete | an instance of this class can only be created with the ''create()'' factory method | | ''create'' | static unique_ptr create(const I2C & _fx3); | provided a valid ''_fx3'' instance of an ''I2C'' class this returns a corresponding ''%%IImgSensor%%'' concrete class | | ''chipsetType'' | Chipset chipsetType() const noexcept; | returns a value from ''Chipset'' enumeration for this object | | ''chipsetName'' | string chipsetName() const; | an ASCII string for the detected chipset | | ''chipsetIdStr'' | string chipsetIdStr() const; | a dot-separated list of numbers that represent an internal chip version followed by a dash and a stepping | | ''compatible'' | virtual bool compatible( const string & _cs ) const; | Check if this chipset is compatible with the given one. An empty (unknown) chipset is compatible with every chipset. The compatibility is defined as "can use the settings (''.fws'') file with this chipset model" | | ''getLimit'' | virtual int getLimit(Value _lt) const noexcept; | read an upper limit for a given parameter, specific to this particular chipset | | ''cmd'' | virtual void cmd(Command _c) const; | Execute one of the known commands. If you have access to the manufacturer's documentation you can execute your own ''I²C'' commands via ''S2R::I2C::operator()'' interface | | ''mode'' | virtual bool mode( Mode _mt , int _val = -1 ) const; | configure the device to be a specific mode of operation\\ ''_mt'' - mode type, one of the values from ''S2R::%%IImgSensor%%::Mode'' enum\\ ''_val'' - a value to be used for that mode, if the value is negative causes a read operation in which case the return is the read value\\return is unspecified for write operations | | ''val'' | virtual bool val( Value _vt , int _val = -1 ) const; | read/write a specific value attribute of the device, like a red channel gain or a black level\\ ''_vt'' - value type, one of the values from ''S2R::%%IImgSensor%%::Value'' enum\\ ''_val'' - a value to send to device, if the value is negative causes a read operation in which case the return is the read value\\return is unspecified for write operations | | ''operator[]'' | int operator[]( Value _vt ) const; | a read access operator that reads a specified value from the device | | ''operator[]'' | virtual CDevProp operator[]( Value _vt ) = 0; | a "device value" access operator that returns an access objects to allow a code like the following: I2C dev; // also opens the device #0 auto & ov = *dev.sensor(); // set the red gain to 123 ov[IImgSensor::Value::gain_r] = 123; | | ''testPattern'' | virtual TestPattern testPattern( TestPattern _tp = {} ) const; | read/write a ''S2R::%%TestPattern%%'' object that represents configuration parameters to use for setting up test-pattern-tun on the image sensor. Not all the chips support all the functionality in that object and some don't even support the test pattern as such (or it is not documented and is thus not implemented) | | ''groupStart'' | virtual void groupStart( uint8_t _grpNo ) const = 0; | begin a group write operation for a group # ''_grpNo'', refer to the documentation of the image sensor to see which group numbers are valid and what is the size of those groups. Issuing a group-writing command before closing the current group will always overwrite the group #0. Issuing a new ''groupStart()'' for the same group will re-start that group's accumulation of commands | | ''groupEnd'' | virtual void groupEnd( uint8_t _grpNo , GroupWriteMode _mode = GroupWriteMode:: at_vert_sync ) const = 0; | Finalize a write to a group. If this call is not made and another group write is started (either explicitly via a call to ''groupStart()'' or implicitly by other functions) a write group could be overwritten. There's no need to explicitly call this function if you want to throw away (cancel) the group. Possible values for the ''_mode'':\\ ''immediate'' - write the values immediately, no wait\\ ''AT_LINE_SYNC'' - wait until the end of the current scan line and then write the values\\ ''at_vert_sync'' - wait until the ''VSYNC'' and then write out the values. This is the preferred method for making sure the update is not done mid-frame | === Internal class === ^ Name ^ Description ^ | struct CDevProp final | returned from the non-const access ''operator[](Value)'' this object allows for the access operator to be used to write values into device. It supports the 4 basic assignment arithmetic operations and a conversion to ''int'' | === Enumerations === ^ Name ^ Description ^ | Chipset | the type of a detected chipset or ''unknown'' in case of errors | | Value | lists the values you can read/write for the image sensor | | Mode | specifies a mode in which the device is to be configured | | Command | commands to send to the device | | GroupWriteMode | specify a desired group-write execution mode at the end, affects whether the values are to be written out immediately or with a delay at a pre-defined time (like a ''VSYNC'') | ==== TestPattern ==== {{ :code:class-testpattern.png?nolink|Class diagram - TestPattern}} Some image sensors support a special test mode in which the output can be either replaced by or alpha-blended with a test pattern. %%OmniVision%% image sensors support a variety of those patterns (vertical color bars, checkers, random noise, etc) and to unify the process of working with those this ''struct'' was created. Not all the image sensors support the full range of presented test pattern features and furthermore not every image sensor even has this mode (documented). Refer to your manufacturer's documentation for more specifics on the exact image sensor you are using. Not all the combination of the configuration parameters are valid when combined with others (for instance you cannot combine a black square and a random noise). ^ Value ^ Usage ^ | m_mode | a test pattern mode to set:\\ ''read'' - used for reading the current state of the test pattern configuration\\ ''color_bar'' - set the test pattern to vertical colored bars\\ ''random'' - generate random noise\\ ''square'' - checkers pattern\\ ''black'' - just a black frame video stream | | m_colorBarType | only for the ''m_mode == color_bar'', the values represent the following configurations:\\ ''std'' - a __st__an__d__ard solid __c__olor bar\\ ''tb_dark'' - __t__op-__b__ottom __d__arker __c__olor bar\\ ''rl_dark'' - __r__ight-__l__eft __d__arker __c__olor bar\\ ''bt_dark'' - __b__ottom-__t__op __d__arker __c__olor bar | | m_squareType | color mode for the checkers' mode when ''m_mode == square'', values are:\\ ''clr'' - use various colors for the checker squares\\ ''bw'' - use alternating black and white square, just like on a checkers board | | m_transparent | ''true'' for alpha-blending the test pattern with the video stream, ''false'' for replacing the video stream with the test pattern | | m_rollingbar | enable/disable a rolling bar, only valid when ''m_mode == color_bar'' | | m_enabled | enable/disable the test pattern mode | ===== Sample code ===== The sample code is a Windows console app that just lists some basic properties of the connected cameras, like in the screenshot below: {{ :code:sub2r-lib-sample1-output.png?nolink |Sample output}} The code itself is pretty minimal and basically fits onto a single screen (depending on your font size, duh): #include #include "FX3.h" #pragma comment(lib, "SUB2r-lib.lib") int main() { using namespace std; using namespace S2R; cout << "SUB2r-lib sample #1: basic functionality\n\n"; const auto numDevs{ImgSensor::deviceCount()}; cout << "Cypress devices attached to this system: " << static_cast(numDevs) << "\n\n"; ImgSensor dev(false); for(uint8_t i{}; i < numDevs; ++i){ auto GAIN_R{IImgSensorChip::Value::gain_r}; cout << "Opening device #" << static_cast(i) << "... "; if(dev.open(i)){ wcout << L"success.\nDevice name is: " << dev[FX3::PropWString::friendly_name].c_str() << "\n"; cout << "Sensor chipset: " << dev->chipsetName() << "\n"; cout << "FX3 version info: " << string(dev.fx3Version()) << "\n"; cout << "FPGA version info: " << string(dev.fpgaVersion()) << "\n"; { auto & ov{dev.sensor()}; // shortcut to the sensor chip cout << "current red channel gain is: " << dev->val(GAIN_R) << "\n"; auto gainR{ov[GAIN_R]}; gainR = gainR + 1; // increment the red gain by one and update the sensor with the new value cout << "after increasing red channel gain by 1 it is now: " << gainR << "\n"; ov[GAIN_R] -= 1; cout << "and back to the previous value of: " << gainR << "\n"; ov[GAIN_R] *= 1.0; } cout << "Closing the device... "; dev.close(); // or the destructor (or even the next call to open()) will take care of that if(dev){ cout << "something has gone terribly wrong - the device was supposed to be closed by now!\n"; }else{ cout << "the device is now closed and cannot be accessed without re-opening it\n"; cout << "For example the red channel gain is now: " << dev.sensor()[GAIN_R] << "\n"; cout << "But some info is still available, like the sensor chipset: " << dev->chipsetName() << "\n"; cout << "Or the information about the limit of red channel gain: " << dev->getLimit(GAIN_R) << "\n"; } }else{ cout << "failed.\n"; if(dev.isValid()){ wcout << L"But the FX3 part is still functional, so we can get info like the device's name: " << dev[FX3::PropWString::friendly_name].c_str() << "\n"; } } cout << "\n"; } return 0; } ==== Built binary ==== If you just want to launch that executable - here's the built binary (for Windows) {{ :code:lib-sample-1.rar |}} ====== Linux ====== Planned for later FIXME ====== MacOS X ====== Planned for later FIXME ====== Previous versions ====== * [[SUB2r-lib-2020-10-08]] * [[SUB2r-lib-2019-04-29]]