diff --git a/camerad/archon_controller.cpp b/camerad/archon_controller.cpp index c4f2a3d..e6150af 100644 --- a/camerad/archon_controller.cpp +++ b/camerad/archon_controller.cpp @@ -1324,11 +1324,13 @@ namespace Camera { char message[256]; auto index = this->frameinfo.index.load(); uint32_t maxblocks = (uint32_t)(1.5E9 / this->activebufs / 1024 ); - uint64_t maxoffset = this->frameinfo.bufbase[index]; - uint64_t maxaddress = maxoffset + maxblocks; + uint64_t minaddress = this->frameinfo.bufbase[index]; // start of the buffer being read + uint64_t maxaddress = minaddress + (uint64_t)maxblocks * BLOCK_LEN; // end of that buffer + uint64_t endaddress = bufferaddress + (uint64_t)bufferblocks * BLOCK_LEN; // end of the request - if (bufferaddress > maxaddress) { - SNPRINTF(message, "fetch Archon buffer requested address 0x%0lX exceeds 0x%0lX", bufferaddress, maxaddress); + if (bufferaddress < minaddress || endaddress > maxaddress) { + SNPRINTF(message, "fetch Archon buffer requested 0x%0lX:0x%0lX outside buffer 0x%0lX:0x%0lX", + bufferaddress, endaddress, minaddress, maxaddress); logwrite(function, std::string(message)); return ERROR; } diff --git a/emulator/emulator-archon.cpp b/emulator/emulator-archon.cpp index df186cd..43381ef 100644 --- a/emulator/emulator-archon.cpp +++ b/emulator/emulator-archon.cpp @@ -35,6 +35,7 @@ namespace Archon { : instr( instr ), poweron( false ), bigbuf( false ), + samplemode( 0 ), frame{ 0, // index 0, // frame "", // timer @@ -433,23 +434,28 @@ namespace Archon { return ERROR; } - // Find which buffer matches the requested base address + // Find the frame buffer containing the requested address. The buffers are + // equally spaced, and an address may point anywhere inside one, such as at + // its raw data. Bytes past what the buffer holds are sent as zeros. + const uint64_t bufspan = this->frame.bufbase.at(1) - this->frame.bufbase.at(0); int bufidx = -1; for ( int i = 0; i < this->image->activebufs; i++ ) { - if ( this->frame.bufbase.at(i) == bufaddr ) { bufidx = i; break; } + if ( bufaddr >= this->frame.bufbase.at(i) && bufaddr < this->frame.bufbase.at(i) + bufspan ) { bufidx = i; break; } } - if ( bufidx < 0 || this->frame.bufdata.at(bufidx).empty() ) { - std::cerr << get_timestamp() << function << "ERROR: no frame data for address 0x" - << std::hex << bufaddr << "\n"; + if ( bufidx < 0 ) { + std::cerr << get_timestamp() << function << "ERROR: address 0x" + << std::hex << bufaddr << " is not in a frame buffer\n"; return ERROR; } const auto &data = this->frame.bufdata.at(bufidx); size_t data_size = data.size(); + size_t data_offset = bufaddr - this->frame.bufbase.at(bufidx); std::cout << get_timestamp() << function << "sending " << std::dec << reqblocks - << " blocks (" << data_size << " bytes available) from buffer " << bufidx+1 << "\n"; + << " blocks from offset " << data_offset << " of buffer " << bufidx+1 + << " (" << data_size << " bytes held)\n"; // Disable Nagle's algorithm for low-latency block transfer int flag = 1; @@ -459,7 +465,6 @@ namespace Archon { size_t header_len = header.size(); size_t block_with_header = header_len + BLOCKLEN; int totalbyteswritten = 0; - size_t data_offset = 0; // Assemble header + one block into a single buffer per write std::vector sendbuf(block_with_header); @@ -488,6 +493,12 @@ namespace Archon { if ( retval > 0 ) written += retval; else break; } + // Wait for this block to drain from the socket send queue before writing + // the next one. A host that waits for a full block to be available before + // reading can stall with a partial block buffered. + // + int outq; + while ( ioctl( sock.getfd(), TIOCOUTQ, &outq ) == 0 && outq > 0 ) usleep( 10 ); totalbyteswritten += BLOCKLEN; } std::cout << get_timestamp() << function << "wrote " << std::dec << totalbyteswritten << " bytes\n"; @@ -634,6 +645,26 @@ namespace Archon { this->image->linecount = std::stoi(value); } + else if ( key == "SAMPLEMODE" ) { + this->samplemode = std::stoi(value); + } + + else if ( key == "RAWENABLE" ) { + this->rawinfo.enable = std::stoi(value); + } + + else if ( key == "RAWSAMPLES" ) { + this->rawinfo.samples = std::stoi(value); + } + + else if ( key == "RAWSTARTLINE" ) { + this->rawinfo.startline = std::stoi(value); + } + + else if ( key == "RAWENDLINE" ) { + this->rawinfo.endline = std::stoi(value); + } + } // end else } catch( std::invalid_argument & ) { @@ -892,15 +923,35 @@ namespace Archon { int width = iface.image->pixelcount * iface.image->taplines; int height = iface.image->linecount; - size_t frame_bytes = static_cast(width) * height * sizeof(uint16_t); + int bytes_per_pixel = ( iface.samplemode == 1 ) ? 4 : 2; + size_t frame_bytes = static_cast(width) * height * bytes_per_pixel; + + // With RAWENABLE=1, raw samples for the captured lines are stored after + // the image, starting at the next whole block. Each line holds RAWSAMPLES + // rounded up to a multiple of 1024 samples. + int raw_lines = 0; + if ( iface.rawinfo.enable == 1 && iface.rawinfo.samples > 0 ) { + raw_lines = std::max( std::min( iface.rawinfo.endline, height - 1 ) - iface.rawinfo.startline + 1, 0 ); + } + int raw_samples = ( ( iface.rawinfo.samples + 1023 ) / 1024 ) * 1024; + int raw_blocks = ( raw_lines > 0 ) ? raw_samples * 2 / BLOCKLEN : 0; + size_t raw_offset = ( raw_lines > 0 ) ? ( ( frame_bytes + BLOCKLEN - 1 ) / BLOCKLEN ) * BLOCKLEN : 0; + size_t raw_line_bytes = static_cast(raw_blocks) * BLOCKLEN; - iface.frame.bufdata.at(idx).resize(frame_bytes); + iface.frame.bufdata.at(idx).resize( raw_lines > 0 ? raw_offset + raw_line_bytes * raw_lines : frame_bytes ); if (iface.frame_source) { - iface.frame_source->fill_frame(iface.frame.bufdata.at(idx).data(), width, height); + iface.frame_source->fill_frame(iface.frame.bufdata.at(idx).data(), width, height, bytes_per_pixel); + } + if ( raw_lines > 0 ) { + iface.raw_source.fill_raw( iface.frame.bufdata.at(idx).data() + raw_offset, raw_samples, raw_lines, raw_line_bytes ); } iface.frame.bufwidth.at(idx) = width; iface.frame.bufheight.at(idx) = height; + iface.frame.bufsample.at(idx) = iface.samplemode; + iface.frame.bufrawblocks.at(idx) = raw_blocks; + iface.frame.bufrawlines.at(idx) = raw_lines; + iface.frame.bufrawoffset.at(idx) = static_cast(raw_offset); double rowtime = iface.image->calc_rowtime(); diff --git a/emulator/emulator-archon.h b/emulator/emulator-archon.h index 9bc034b..a88fb06 100644 --- a/emulator/emulator-archon.h +++ b/emulator/emulator-archon.h @@ -51,6 +51,7 @@ namespace Archon { std::unique_ptr image; ///!< smart pointer to the base class std::unique_ptr frame_source; + Emulator::SyntheticSource raw_source; ///!< raw samples are synthetic whatever the frame source Interface( const std::string &instr ); @@ -63,6 +64,7 @@ namespace Archon { unsigned long long init_time; bool poweron; //!< is the power on? bool bigbuf; //!< is BIGBUF==1 in ACF file? + int samplemode; //!< from "SAMPLEMODE=" in ACF file, 0=16 bit, 1=32 bit std::string exposeparam; //!< param name to trigger exposure when set =1 std::string active_mode; //!< currently active mode detected from ACF parameters @@ -197,11 +199,10 @@ namespace Archon { * rawinfo_t is a struct which contains variables specific to raw data functions */ struct rawinfo_t { - int adchan; // selected A/D channels - int rawsamples; // number of raw samples per line - int rawlines; // number of raw lines - int iteration; // iteration number - int iterations; // number of iterations + int enable = 0; // from "RAWENABLE=" in ACF file + int samples = 0; // from "RAWSAMPLES=", raw samples per line + int startline = 0; // from "RAWSTARTLINE=", first line captured + int endline = 0; // from "RAWENDLINE=", last line captured } rawinfo; /** diff --git a/emulator/emulator-server.cpp b/emulator/emulator-server.cpp index a74b9cd..ccd5a2f 100644 --- a/emulator/emulator-server.cpp +++ b/emulator/emulator-server.cpp @@ -290,6 +290,7 @@ void doit(Network::TcpSocket sock) { } else if (cmd.compare("APPLYALL")==0) { + server->image->readoutlines = server->image->linecount; // READOUT_TIME is for the frame as loaded retstream << "<" << ref; } else diff --git a/emulator/frame_source.h b/emulator/frame_source.h index 3716a8b..4d572ce 100644 --- a/emulator/frame_source.h +++ b/emulator/frame_source.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "utilities.h" @@ -17,38 +18,39 @@ namespace Emulator { // Abstract interface for filling frame buffers with pixel data + // class FrameSource { + public: virtual ~FrameSource() = default; - // Fill buffer with one frame of pixel data (uint16 pixels) - /// @param buffer destination buffer, must be at least width*height*2 bytes - /// @param width frame width in pixels - /// @param height frame height in pixels - /// @return true on success - virtual bool fill_frame(char* buffer, int width, int height) = 0; + /** @brief Fill buffer with one frame of pixel data + * @param buffer destination buffer, must be at least width*height*bytes_per_pixel bytes + * @param width frame width in pixels + * @param height frame height in pixels + * @param bytes_per_pixel 2 for 16-bit pixels, 4 for 32-bit pixels + * @return true on success + */ + virtual bool fill_frame(char* buffer, int width, int height, int bytes_per_pixel) = 0; }; // Generate synthetic frames with bias + Gaussian noise // Mode-aware: RXR generates signal/reset halves with correlated noise + // class SyntheticSource : public FrameSource { + private: + std::mt19937 rng{std::random_device{}()}; double noise_stddev = 50.0; std::string* active_mode; // non-owning pointer to emulator's active mode int taplines = 0; - public: - // @param mode pointer to the active mode string (owned by Interface) - // @param taps number of taplines for RXR half-width calculation - SyntheticSource(std::string* mode = nullptr, int taps = 0) - : active_mode(mode), taplines(taps) {} - - void set_taplines(int taps) { taplines = taps; } - - bool fill_frame(char* buffer, int width, int height) override { - auto* pixels = reinterpret_cast(buffer); + // Pixel values are the same at either depth; T sets the pixel width and the clamp + template + void fill_pixels(T* pixels, int width, int height) { + const double maxval = static_cast(std::numeric_limits::max()); std::normal_distribution noise(0.0, noise_stddev); size_t npixels = static_cast(width) * height; @@ -71,8 +73,8 @@ namespace Emulator { double common_noise = noise(rng); double sig_val = 10000.0 + common_noise + noise(rng) * 0.3; double res_val = 5000.0 + common_noise + noise(rng) * 0.3; - pixels[tap_offset + col] = static_cast(std::clamp(sig_val, 0.0, 65535.0)); - pixels[tap_offset + col + half] = static_cast(std::clamp(res_val, 0.0, 65535.0)); + pixels[tap_offset + col] = static_cast(std::clamp(sig_val, 0.0, maxval)); + pixels[tap_offset + col + half] = static_cast(std::clamp(res_val, 0.0, maxval)); } } } @@ -82,9 +84,43 @@ namespace Emulator { for (size_t i = 0; i < npixels; i++) { int col = i % width; double val = 5000.0 + noise(rng) + static_cast(col) * 100.0 / width; - pixels[i] = static_cast(std::clamp(val, 0.0, 65535.0)); + pixels[i] = static_cast(std::clamp(val, 0.0, maxval)); } } + } + + public: + + // @param mode pointer to the active mode string (owned by Interface) + // @param taps number of taplines for RXR half-width calculation + // + SyntheticSource(std::string* mode = nullptr, int taps = 0) + : active_mode(mode), taplines(taps) {} + + void set_taplines(int taps) { taplines = taps; } + + /** @brief Fill raw ADC samples with bias + noise, zero-padding each line out to line_bytes + * @param buffer destination buffer, must be at least lines*line_bytes bytes + * @param samples 16-bit samples per line + * @param lines number of lines + * @param line_bytes bytes occupied by each line + */ + void fill_raw(char* buffer, int samples, int lines, size_t line_bytes) { + const double maxval = static_cast(std::numeric_limits::max()); + std::normal_distribution noise(0.0, noise_stddev); + for (int line = 0; line < lines; line++) { + char* linestart = buffer + line * line_bytes; + std::memset(linestart, 0, line_bytes); + auto* raw = reinterpret_cast(linestart); + for (int sample = 0; sample < samples; sample++) { + raw[sample] = static_cast(std::clamp(32768.0 + noise(rng), 0.0, maxval)); + } + } + } + + bool fill_frame(char* buffer, int width, int height, int bytes_per_pixel) override { + if (bytes_per_pixel == 4) fill_pixels(reinterpret_cast(buffer), width, height); + else fill_pixels(reinterpret_cast(buffer), width, height); return true; } }; @@ -92,6 +128,7 @@ namespace Emulator { // Read FITS files from folder, serve them sequentially, cycling // Not mode-aware: FITS data is already in the correct format for its mode + // class FitsFileSource : public FrameSource { private: std::vector files; @@ -112,7 +149,7 @@ namespace Emulator { << " FITS files found in " << datadir << "\n"; } - bool fill_frame(char* buffer, int width, int height) override { + bool fill_frame(char* buffer, int width, int height, int bytes_per_pixel) override { std::string function = "(Emulator::FitsFileSource::fill_frame) "; if (files.empty()) { std::cerr << get_timestamp() << function << "ERROR: no FITS files\n"; @@ -153,7 +190,8 @@ namespace Emulator { long fpixel[2] = {1, 1}; long nelements = static_cast(width) * height; - fits_read_pix(fptr, TUSHORT, fpixel, nelements, nullptr, buffer, nullptr, &status); + int datatype = (bytes_per_pixel == 4) ? TUINT : TUSHORT; + fits_read_pix(fptr, datatype, fpixel, nelements, nullptr, buffer, nullptr, &status); fits_close_file(fptr, &status); if (status) { @@ -168,6 +206,7 @@ namespace Emulator { // Create the appropriate FrameSource based on config + // inline std::unique_ptr make_frame_source( const std::string &datadir, std::string* active_mode = nullptr, diff --git a/emulator/generic.h b/emulator/generic.h index a09fc03..d840d4e 100644 --- a/emulator/generic.h +++ b/emulator/generic.h @@ -23,7 +23,8 @@ namespace Archon { * */ virtual double calc_rowtime() override { - double rowtime = std::floor( 10 * this->readtime / this->linecount ); // row time in 10 msec + int lines = ( this->readoutlines > 0 ) ? this->readoutlines : this->linecount; // lines READOUT_TIME applies to + double rowtime = std::floor( 10 * this->readtime / lines ); // row time in 10 msec rowtime *= 90.0; // 90% of row time in usec return rowtime; } diff --git a/emulator/image_info_base.h b/emulator/image_info_base.h index b1e0f22..6e6cbf7 100644 --- a/emulator/image_info_base.h +++ b/emulator/image_info_base.h @@ -31,6 +31,7 @@ namespace Archon { int pixelcount; //!< from "PIXELCOUNT=" in ACF file int readtime; //!< from "READOUT_TIME=" in configuration file int readouttime; //!< + int readoutlines; //!< LINECOUNT that READOUT_TIME applies to, latched at APPLYALL int imwidth; //!< int imheight; //!< int exptime; //!< requested exposure time in msec from WCONFIG @@ -38,7 +39,7 @@ namespace Archon { ImageInfoBase() : framen(0), activebufs(-1), taplines(-1), linecount(-1), pixelcount(-1), - readtime(-1), readouttime(0), + readtime(-1), readouttime(0), readoutlines(-1), imwidth(0), imheight(0), exptime(0), exposure_factor(1000) { }