diff --git a/raptor_eagle_ccd.cpp b/raptor_eagle_ccd.cpp index cdb8aaa..4adf231 100644 --- a/raptor_eagle_ccd.cpp +++ b/raptor_eagle_ccd.cpp @@ -1,4 +1,3 @@ -#include #include #include "raptor_eagle_cameralink.h" @@ -7,6 +6,62 @@ #include +namespace details +{ + +// compute checksum as XOR operation along elements of byte array +template +auto computeChecksum(const R& bytes, bool final_etx = true) + requires std::convertible_to, char> +{ + std::ranges::range_value_t res = 0; + + if (std::ranges::size(bytes) == 0) { + return res; + } + + for (auto& byte : bytes) { + res ^= byte; + } + + if (final_etx) { + res ^= CL_ETX; + } + + return res; +} + +// assume that least significant byte is the last one in 'bytes' +template +size_t convert40BitToCounts(const R& bytes) + requires std::same_as, unsigned char> +{ + size_t counts = 0, i = std::ranges::size(bytes); + + for (auto& byte : bytes) { + counts += byte << (--i * 8); + } + + return counts; +} + + +template R> +R convertCountsTo40Bit(uint64_t counts) +{ + R res; + + auto sp = std::span(reinterpret_cast(&counts), 8); + + // least significant byte in the end of the output range + std::ranges::copy(sp | std::views::take(5) | std::views::reverse, std::back_inserter(res)); + + return res; +} + +} // namespace details + + #define DEFAULT_EPIX_VIDEO_FMT_FILE "raptor_eagle-v.fmt" /* CONSTRUCTORS AND DESTRUCTOR */ @@ -17,8 +72,8 @@ RaptorEagleCCD::RaptorEagleCCD(const adc::traits::adc_input_char_range auto& epi adc::AdcSpdlogLogger(logger), _epixFmtVideoFilename(), _cameraUnitmap(1), // by default only the single camera - _clCommandAckBit(1), // enable by default - _clChecksumBit(1), // enable by default + _clCommandAckBit(1), // enable by default (at camera boot up) + _clChecksumBit(1), // enable by default (at camera boot up) _expTime(0.0) { @@ -111,7 +166,7 @@ size_t RaptorEagleCCD::clRead(byte_seq_t& bytes) bytes.resize(nbytes); } - xclibApiCall(pxd_serialRead(_cameraUnitmap, 0, bytes.data(), nbytes), + xclibApiCall(pxd_serialRead(_cameraUnitmap, 0, (char*)bytes.data(), nbytes), std::format("pxd_serialRead({}, 0, {}, {})", _cameraUnitmap, (void*)bytes.data(), nbytes)); @@ -139,9 +194,19 @@ size_t RaptorEagleCCD::clReadAndCheckAck(byte_seq_t& bytes) return nbytes; } +size_t RaptorEagleCCD::clReadAndCheckAck() +{ + byte_seq_t bytes; + return clReadAndCheckAck(bytes); +} + + +// 'bytes' must contain only data without trailing ETX and checksum bytes! size_t RaptorEagleCCD::clWrite(const byte_seq_t& bytes) { + static unsigned char etx_checksum_bytes[] = {CL_ETX, 0xFF}; + if (bytes.empty()) { logWarn("An empty transmitted byte sequence! Nothing to do!"); return 0; @@ -155,21 +220,23 @@ size_t RaptorEagleCCD::clWrite(const byte_seq_t& bytes) } - size_t nbytes, tr_nbytes = bytes.size(); + size_t nbytes, tr_nbytes = 1 + _clChecksumBit; // how many bytes are available in Tx-buffer xclibApiCall(nbytes = pxd_serialWrite(_cameraUnitmap, 0, nullptr, 0), std::format("pxd_serialWrite({}, 0, NULL, 0)", _cameraUnitmap)); if (nbytes) { - tr_nbytes += _clCommandAckBit + _clChecksumBit; - - if (nbytes < tr_nbytes) { + if (nbytes < (bytes.size() + tr_nbytes)) { logWarn( "Not enough of available space in the internal Tx-buffer (needs = {}, available = {})! Nothing to do!", - tr_nbytes, nbytes); + bytes.size() + tr_nbytes, nbytes); nbytes = 0; } else { + if (_clChecksumBit) { + etx_checksum_bytes[1] = details::computeChecksum(bytes); + } + xclibApiCall( nbytes = pxd_serialWrite(_cameraUnitmap, 0, (char*)bytes.data(), bytes.size()), std::format("pxd_serialWrite({}, 0, {}, {})", _cameraUnitmap, (void*)bytes.data(), bytes.size())); @@ -177,6 +244,23 @@ size_t RaptorEagleCCD::clWrite(const byte_seq_t& bytes) if (nbytes != bytes.size()) { throw std::error_code(RaptorEagleCCDError::ERROR_CAMLINK_WRITE); } + + // send trailing ETX and possible checksum bytes + size_t n; + + if (tr_nbytes > 1) { + logDebug("Write trailing ETX and checksum bytes"); + } else { + logDebug("Write trailing ETX byte"); + } + xclibApiCall( + n = pxd_serialWrite(_cameraUnitmap, 0, (char*)etx_checksum_bytes, tr_nbytes), + std::format("pxd_serialWrite({}, 0, {}, {})", _cameraUnitmap, (void*)etx_checksum_bytes, tr_nbytes)); + if (n != tr_nbytes) { + throw std::error_code(RaptorEagleCCDError::ERROR_CAMLINK_WRITE); + } + + nbytes += n; } } else { logWarn("No available space in the internal Tx-buffer! Nothing to do!"); @@ -186,6 +270,69 @@ size_t RaptorEagleCCD::clWrite(const byte_seq_t& bytes) } +RaptorEagleCCD::byte_seq_t RaptorEagleCCD::readRegisters(const RaptorEagleCCD::byte_seq_t& addrs, + byte_seq_t set_addr_cmd) +{ + // to protect in multi-threading environment (multiple read-write operations, see below) + std::lock_guard lock_guard(_camlinkMutex); + + byte_seq_t reg_vals, ans(3); + + if (addrs.empty()) { + logWarn("Registers addresses array is an empty! Nothing to do!"); + return reg_vals; + } + + // from Eagle V 4240 instruction manual (rev 1.1) + byte_seq_t set_addr_comm = std::move(set_addr_cmd); // set address controller command + static const byte_seq_t read_reg_comm{0x53, 0xE1, 0x01}; // read register controller command + + + reg_vals.resize(addrs.size()); + + size_t i = 0; + for (auto& addr : addrs) { + // set address + set_addr_comm[3] = addr; + clWrite(set_addr_comm); + clReadAndCheckAck(ans); + + // get value + clWrite(read_reg_comm); + clReadAndCheckAck(ans); + + reg_vals[i++] = ans[0]; + } + + return reg_vals; +} + + +void RaptorEagleCCD::writeRegisters(const byte_seq_t& addrs, const byte_seq_t& values) +{ + // to protect in multi-threading environment (multiple read-write operations, see below) + std::lock_guard lock_guard(_camlinkMutex); + + if (addrs.empty() || values.empty()) { + logWarn("Registers addresses or values array is an empty! Nothing to do!"); + return; + } + + size_t N = addrs.size() < values.size() ? addrs.size() : values.size(); + + // from Eagle V 4240 instruction manual (rev 1.1) + byte_seq_t comm{0x53, 0xE0, 0x02, 0x00, 0x00}; + + for (size_t i = 0; i < N; ++i) { + comm[3] = addrs[i]; + comm[4] = values[i]; + + clWrite(comm); + clReadAndCheckAck(); // no data from controller here just check answer for errors + } +} + + /* CREATE COMMANDS AND ATTRIBUTES */ void RaptorEagleCCD::initAttrComm() diff --git a/raptor_eagle_ccd.h b/raptor_eagle_ccd.h index 8379011..2b1f0d0 100644 --- a/raptor_eagle_ccd.h +++ b/raptor_eagle_ccd.h @@ -43,7 +43,21 @@ public: // static constexpr std::string_view CAMERA_CMD_OPEN_SHUTTER{"OPEN_SHUTTER"}; // static constexpr std::string_view CAMERA_CMD_CLOSE_SHUTTER{"CLOSE_SHUTTER"}; + // system status byte bits + struct SystemStatus { + bool checkSumEnabled; + bool ackEnabled; + bool bootedFPGA; + bool holdFPGAInReset; + bool commsFPGAEnabled; + }; + // FPGA status bits + struct FPGAStatus { + bool highGainEnabled; + bool overTemp; + bool TECEnabled; + }; RaptorEagleCCD(const adc::traits::adc_input_char_range auto& epix_video_fmt_filename, std::shared_ptr logger = spdlog::null_logger_mt("EAGLE_CCD_NULLLOGGER")); @@ -53,7 +67,7 @@ public: ~RaptorEagleCCD(); private: - typedef std::vector byte_seq_t; + typedef std::vector byte_seq_t; std::string _epixFmtVideoFilename; int _cameraUnitmap; @@ -62,6 +76,8 @@ private: uint8_t _clCommandAckBit; uint8_t _clChecksumBit; + std::mutex _camlinkMutex; + // attributes inner variables double _expTime; size_t _frameNumbers; @@ -73,13 +89,28 @@ private: void openPIXCI(); void closePIXCI(); + + // CameraLink-related low-level methods size_t clRead(byte_seq_t& bytes); size_t clReadAndCheckAck(byte_seq_t& bytes); + size_t clReadAndCheckAck(); size_t clWrite(const byte_seq_t& bytes); + // CameraLink-related registers read/write methods + + // there are two kind of SET-READ_REGISTER-ADDRESS command in the current version of the camera controller firmware: + // {0x53, 0xE0, 0x01} and + // {0x53, 0xE0, 0x02} + // + // NOTE: given 'set_addr_cmd' byte sequence must be one-byte longer than the command itself!!! + byte_seq_t readRegisters(const byte_seq_t& addrs, byte_seq_t set_addr_cmd = {0x53, 0xE0, 0x01, 0x00}); + void writeRegisters(const byte_seq_t& addrs, const byte_seq_t& values); + + + // logging helper methods template