// // Copyright 2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // #ifndef MAX287X_HPP_INCLUDED #define MAX287X_HPP_INCLUDED #include #include #include #include #include #include #include #include #include #include "max2870_regs.hpp" #include "max2871_regs.hpp" /** * MAX287x interface */ class max287x_iface { public: typedef boost::shared_ptr sptr; typedef boost::function)> write_fn; /** * LD Pin Modes */ typedef enum{ LD_PIN_MODE_LOW, LD_PIN_MODE_DLD, LD_PIN_MODE_ALD, LD_PIN_MODE_HIGH } ld_pin_mode_t; /** * MUXOUT Modes */ typedef enum{ MUXOUT_TRI_STATE, MUXOUT_HIGH, MUXOUT_LOW, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD, MUXOUT_SYNC, MUXOUT_SPI } muxout_mode_t; /** * Charge Pump Currents */ typedef enum{ CHARGE_PUMP_CURRENT_0_32MA, CHARGE_PUMP_CURRENT_0_64MA, CHARGE_PUMP_CURRENT_0_96MA, CHARGE_PUMP_CURRENT_1_28MA, CHARGE_PUMP_CURRENT_1_60MA, CHARGE_PUMP_CURRENT_1_92MA, CHARGE_PUMP_CURRENT_2_24MA, CHARGE_PUMP_CURRENT_2_56MA, CHARGE_PUMP_CURRENT_2_88MA, CHARGE_PUMP_CURRENT_3_20MA, CHARGE_PUMP_CURRENT_3_52MA, CHARGE_PUMP_CURRENT_3_84MA, CHARGE_PUMP_CURRENT_4_16MA, CHARGE_PUMP_CURRENT_4_48MA, CHARGE_PUMP_CURRENT_4_80MA, CHARGE_PUMP_CURRENT_5_12MA } charge_pump_current_t; /** * Output Powers */ typedef enum{ OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM } output_power_t; typedef enum { LOW_NOISE_AND_SPUR_LOW_NOISE, LOW_NOISE_AND_SPUR_LOW_SPUR_1, LOW_NOISE_AND_SPUR_LOW_SPUR_2 } low_noise_and_spur_t; typedef enum { CLOCK_DIV_MODE_CLOCK_DIVIDER_OFF, CLOCK_DIV_MODE_FAST_LOCK, CLOCK_DIV_MODE_PHASE } clock_divider_mode_t; /** * Make a synthesizer * @param write write function * @return shared pointer to object */ template static sptr make(write_fn write) { return sptr(new max287X_t(write)); } /** * Destructor */ virtual ~max287x_iface() {}; /** * Power up the synthesizer */ virtual void power_up(void) = 0; /** * Shut down the synthesizer */ virtual void shutdown(void) = 0; /** * Check if the synthesizer is shut down */ virtual bool is_shutdown(void) = 0; /** * Set frequency * @param target_freq target frequency * @param ref_freq reference frequency * @param target_pfd_freq target phase detector frequency * @param is_int_n enable integer-N tuning * @return actual frequency */ virtual double set_frequency( double target_freq, double ref_freq, double target_pfd_freq, bool is_int_n) = 0; /** * Set output power * @param power output power */ virtual void set_output_power(output_power_t power) = 0; /** * Set lock detect pin mode * @param mode lock detect pin mode */ virtual void set_ld_pin_mode(ld_pin_mode_t mode) = 0; /** * Set muxout pin mode * @param mode muxout mode */ virtual void set_muxout_mode(muxout_mode_t mode) = 0; /** * Set charge pump current * @param cp_current charge pump current */ virtual void set_charge_pump_current(charge_pump_current_t cp_current) = 0; /** * Enable or disable auto retune * @param enabled enable auto retune */ virtual void set_auto_retune(bool enabled) = 0; /** * Set clock divider mode * @param mode clock divider mode */ virtual void set_clock_divider_mode(clock_divider_mode_t mode) = 0; /** * Enable or disable cycle slip mode * @param enabled enable cycle slip mode */ virtual void set_cycle_slip_mode(bool enabled) = 0; /** * Set low noise and spur mode * @param mode low noise and spur mode */ virtual void set_low_noise_and_spur(low_noise_and_spur_t mode) = 0; /** * Set phase * @param phase the phase offset */ virtual void set_phase(boost::uint16_t phase) = 0; /** * Write values configured by the set_* functions. */ virtual void commit(void) = 0; /** * Check whether this is in a state where it can be synchronized */ virtual bool can_sync(void) = 0; }; /** * MAX287x * Base class for all MAX287x synthesizers */ template class max287x : public max287x_iface { public: max287x(write_fn func); virtual ~max287x(); virtual void power_up(void); virtual void shutdown(void); virtual bool is_shutdown(void); virtual double set_frequency( double target_freq, double ref_freq, double target_pfd_freq, bool is_int_n); virtual void set_output_power(output_power_t power); virtual void set_ld_pin_mode(ld_pin_mode_t mode); virtual void set_muxout_mode(muxout_mode_t mode); virtual void set_charge_pump_current(charge_pump_current_t cp_current); virtual void set_auto_retune(bool enabled); virtual void set_clock_divider_mode(clock_divider_mode_t mode); virtual void set_cycle_slip_mode(bool enabled); virtual void set_low_noise_and_spur(low_noise_and_spur_t mode); virtual void set_phase(boost::uint16_t phase); virtual void commit(); virtual bool can_sync(); protected: max287x_regs_t _regs; bool _can_sync; bool _write_all_regs; private: write_fn _write; bool _delay_after_write; }; /** * MAX2870 */ class max2870 : public max287x { public: max2870(write_fn func) : max287x(func) {} ~max2870() {} double set_frequency( double target_freq, double ref_freq, double target_pfd_freq, bool is_int_n) { _regs.cpoc = is_int_n ? max2870_regs_t::CPOC_ENABLED : max2870_regs_t::CPOC_DISABLED; _regs.feedback_select = target_freq >= 3.0e9 ? max2870_regs_t::FEEDBACK_SELECT_DIVIDED : max2870_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; return max287x::set_frequency(target_freq, ref_freq, target_pfd_freq, is_int_n); } void commit(void) { // For MAX2870, we always need to write all registers. _write_all_regs = true; max287x::commit(); } }; /** * MAX2871 */ class max2871 : public max287x { public: max2871(write_fn func) : max287x(func) {} ~max2871() {}; void set_muxout_mode(muxout_mode_t mode) { switch(mode) { case MUXOUT_SYNC: _regs.muxout = max2871_regs_t::MUXOUT_SYNC; break; case MUXOUT_SPI: _regs.muxout = max2871_regs_t::MUXOUT_SPI; break; default: max287x::set_muxout_mode(mode); } } double set_frequency( double target_freq, double ref_freq, double target_pfd_freq, bool is_int_n) { _regs.feedback_select = max2871_regs_t::FEEDBACK_SELECT_DIVIDED; double freq = max287x::set_frequency(target_freq, ref_freq, target_pfd_freq, is_int_n); // According to Maxim support, the following factors must be true to allow for synchronization if (_regs.r_counter_10_bit == 1 and _regs.reference_divide_by_2 == max2871_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED and _regs.reference_doubler == max2871_regs_t::REFERENCE_DOUBLER_DISABLED and _regs.rf_divider_select <= max2871_regs_t::RF_DIVIDER_SELECT_DIV16 and _regs.low_noise_and_spur == max2871_regs_t::LOW_NOISE_AND_SPUR_LOW_NOISE) { _can_sync = true; } return freq; } }; // Implementation of max287x template class // To avoid linker errors, it was either include // it here or put it in a .cpp file and include // that file in this header file. Decided to just // include it here. template max287x::max287x(write_fn func) : _can_sync(false), _write_all_regs(true), _write(func), _delay_after_write(true) { power_up(); } template max287x::~max287x() { shutdown(); } template void max287x::power_up(void) { _regs.power_down = max287x_regs_t::POWER_DOWN_NORMAL; _regs.double_buffer = max287x_regs_t::DOUBLE_BUFFER_ENABLED; // According to MAX287x data sheets: // "Upon power-up, the registers should be programmed twice with at // least a 20ms pause between writes. The first write ensures that // the device is enabled, and the second write starts the VCO // selection process." // The first write and the 20ms wait are done here. The second write // is done when any other function that does a write to the registers // is called (such as tuning). _write_all_regs = true; _delay_after_write = true; commit(); _write_all_regs = true; // Next call to commit() writes all regs } template void max287x::shutdown(void) { _regs.rf_output_enable = max287x_regs_t::RF_OUTPUT_ENABLE_DISABLED; _regs.aux_output_enable = max287x_regs_t::AUX_OUTPUT_ENABLE_DISABLED; _regs.power_down = max287x_regs_t::POWER_DOWN_SHUTDOWN; commit(); } template bool max287x::is_shutdown(void) { return (_regs.power_down == max287x_regs_t::POWER_DOWN_SHUTDOWN); } template double max287x::set_frequency( double target_freq, double ref_freq, double target_pfd_freq, bool is_int_n) { _can_sync = false; //map rf divider select output dividers to enums static const uhd::dict rfdivsel_to_enum = boost::assign::map_list_of (1, max287x_regs_t::RF_DIVIDER_SELECT_DIV1) (2, max287x_regs_t::RF_DIVIDER_SELECT_DIV2) (4, max287x_regs_t::RF_DIVIDER_SELECT_DIV4) (8, max287x_regs_t::RF_DIVIDER_SELECT_DIV8) (16, max287x_regs_t::RF_DIVIDER_SELECT_DIV16) (32, max287x_regs_t::RF_DIVIDER_SELECT_DIV32) (64, max287x_regs_t::RF_DIVIDER_SELECT_DIV64) (128, max287x_regs_t::RF_DIVIDER_SELECT_DIV128); //map mode setting to valid integer divider (N) values static const uhd::range_t int_n_mode_div_range(16,65535,1); static const uhd::range_t frac_n_mode_div_range(19,4091,1); //other ranges and constants from MAX287X datasheets static const uhd::range_t clock_div_range(1,4095,1); static const uhd::range_t r_range(1,1023,1); static const double MIN_VCO_FREQ = 3e9; static const double BS_FREQ = 50e3; static const int MAX_BS_VALUE = 1023; int T = 0; int D = ref_freq <= 10.0e6 ? 1 : 0; int R = 0; int BS = 0; int N = 0; int FRAC = 0; int MOD = 4095; int RFdiv = 1; double pfd_freq = target_pfd_freq; bool feedback_divided = (_regs.feedback_select == max287x_regs_t::FEEDBACK_SELECT_DIVIDED); //increase RF divider until acceptable VCO frequency (MIN freq for MAX287x VCO is 3GHz) double vco_freq = target_freq; while (vco_freq < MIN_VCO_FREQ) { vco_freq *= 2; RFdiv *= 2; } // The feedback frequency can be the fundamental VCO frequency or // divided frequency. The output divider for MAX287x is actually // 2 dividers, but only the first (1/2/4/8/16) is included in the // feedback loop. int fb_divisor = feedback_divided ? (RFdiv > 16 ? 16 : RFdiv) : 1; /* * The goal here is to loop though possible R dividers, * band select clock dividers, N (int) dividers, and FRAC * (frac) dividers. * * Calculate the N and F dividers for each set of values. * The loop exits when it meets all of the constraints. * The resulting loop values are loaded into the registers. * * f_pfd = f_ref*(1+D)/(R*(1+T)) * f_vco = (N + (FRAC/MOD))*f_pfd * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD * f_rf = f_vco/RFdiv */ for(R = int(ref_freq*(1+D)/(target_pfd_freq*(1+T))); R <= r_range.stop(); R++) { //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) pfd_freq = ref_freq*(1+D)/(R*(1+T)); //keep the PFD frequency at or below target if (pfd_freq > target_pfd_freq) continue; //ignore fractional part of tuning N = int((vco_freq/pfd_freq)/fb_divisor); //Fractional-N calculation FRAC = int(boost::math::round(((vco_freq/pfd_freq)/fb_divisor - N)*MOD)); if(is_int_n) { if (FRAC > (MOD / 2)) //Round integer such that actual freq is closest to target N++; FRAC = 0; } //keep N within int divider requirements if(is_int_n) { if(N <= int_n_mode_div_range.start()) continue; if(N >= int_n_mode_div_range.stop()) continue; } else { if(N <= frac_n_mode_div_range.start()) continue; if(N >= frac_n_mode_div_range.stop()) continue; } //keep pfd freq low enough to achieve 50kHz BS clock BS = std::ceil(pfd_freq / BS_FREQ); if(BS <= MAX_BS_VALUE) break; } UHD_ASSERT_THROW(R <= r_range.stop()); //Reference divide-by-2 for 50% duty cycle // if R even, move one divide by 2 to to regs.reference_divide_by_2 if(R % 2 == 0) { T = 1; R /= 2; } //actual frequency calculation double actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))) * fb_divisor / RFdiv; UHD_LOGV(rarely) << boost::format("MAX287x: Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f" ) % ref_freq % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl << boost::format("MAX287x: tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, type=%s" ) % R % BS % N % FRAC % MOD % T % D % RFdiv % ((is_int_n) ? "Integer-N" : "Fractional") << std::endl << boost::format("MAX287x: Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; //load the register values _regs.rf_output_enable = max287x_regs_t::RF_OUTPUT_ENABLE_ENABLED; if(is_int_n) { _regs.cpl = max287x_regs_t::CPL_DISABLED; _regs.ldf = max287x_regs_t::LDF_INT_N; _regs.int_n_mode = max287x_regs_t::INT_N_MODE_INT_N; } else { _regs.cpl = max287x_regs_t::CPL_ENABLED; _regs.ldf = max287x_regs_t::LDF_FRAC_N; _regs.int_n_mode = max287x_regs_t::INT_N_MODE_FRAC_N; } _regs.lds = pfd_freq <= 32e6 ? max287x_regs_t::LDS_SLOW : max287x_regs_t::LDS_FAST; _regs.frac_12_bit = FRAC; _regs.int_16_bit = N; _regs.mod_12_bit = MOD; _regs.clock_divider_12_bit = std::max(int(clock_div_range.start()), int(std::ceil(400e-6*pfd_freq/MOD))); UHD_ASSERT_THROW(_regs.clock_divider_12_bit <= clock_div_range.stop()); _regs.r_counter_10_bit = R; _regs.reference_divide_by_2 = T ? max287x_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : max287x_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; _regs.reference_doubler = D ? max287x_regs_t::REFERENCE_DOUBLER_ENABLED : max287x_regs_t::REFERENCE_DOUBLER_DISABLED; _regs.band_select_clock_div = BS & 0xFF; _regs.bs_msb = (BS & 0x300) >> 8; UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); _regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; if (_regs.clock_div_mode == max287x_regs_t::CLOCK_DIV_MODE_FAST_LOCK) { // Charge pump current needs to be set to lowest value in fast lock mode _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_0_32MA; // Make sure the register containing the charge pump current is written _write_all_regs = true; } return actual_freq; } template void max287x::set_output_power(output_power_t power) { switch (power) { case OUTPUT_POWER_M4DBM: _regs.output_power = max287x_regs_t::OUTPUT_POWER_M4DBM; break; case OUTPUT_POWER_M1DBM: _regs.output_power = max287x_regs_t::OUTPUT_POWER_M1DBM; break; case OUTPUT_POWER_2DBM: _regs.output_power = max287x_regs_t::OUTPUT_POWER_2DBM; break; case OUTPUT_POWER_5DBM: _regs.output_power = max287x_regs_t::OUTPUT_POWER_5DBM; break; default: UHD_THROW_INVALID_CODE_PATH(); } } template void max287x::set_ld_pin_mode(ld_pin_mode_t mode) { switch(mode) { case LD_PIN_MODE_LOW: _regs.ld_pin_mode = max287x_regs_t::LD_PIN_MODE_LOW; break; case LD_PIN_MODE_DLD: _regs.ld_pin_mode = max287x_regs_t::LD_PIN_MODE_DLD; break; case LD_PIN_MODE_ALD: _regs.ld_pin_mode = max287x_regs_t::LD_PIN_MODE_ALD; break; case LD_PIN_MODE_HIGH: _regs.ld_pin_mode = max287x_regs_t::LD_PIN_MODE_HIGH; break; default: UHD_THROW_INVALID_CODE_PATH(); } } template void max287x::set_muxout_mode(muxout_mode_t mode) { switch(mode) { case MUXOUT_TRI_STATE: _regs.muxout = max287x_regs_t::MUXOUT_TRI_STATE; break; case MUXOUT_HIGH: _regs.muxout = max287x_regs_t::MUXOUT_HIGH; break; case MUXOUT_LOW: _regs.muxout = max287x_regs_t::MUXOUT_LOW; break; case MUXOUT_RDIV: _regs.muxout = max287x_regs_t::MUXOUT_RDIV; break; case MUXOUT_NDIV: _regs.muxout = max287x_regs_t::MUXOUT_NDIV; break; case MUXOUT_ALD: _regs.muxout = max287x_regs_t::MUXOUT_ALD; break; case MUXOUT_DLD: _regs.muxout = max287x_regs_t::MUXOUT_DLD; break; default: UHD_THROW_INVALID_CODE_PATH(); } } template void max287x::set_charge_pump_current(charge_pump_current_t cp_current) { switch(cp_current) { case CHARGE_PUMP_CURRENT_0_32MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_0_32MA; break; case CHARGE_PUMP_CURRENT_0_64MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_0_64MA; break; case CHARGE_PUMP_CURRENT_0_96MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_0_96MA; break; case CHARGE_PUMP_CURRENT_1_28MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_1_28MA; break; case CHARGE_PUMP_CURRENT_1_60MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_1_60MA; break; case CHARGE_PUMP_CURRENT_1_92MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_1_92MA; break; case CHARGE_PUMP_CURRENT_2_24MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_2_24MA; break; case CHARGE_PUMP_CURRENT_2_56MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_2_56MA; break; case CHARGE_PUMP_CURRENT_2_88MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_2_88MA; break; case CHARGE_PUMP_CURRENT_3_20MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_3_20MA; break; case CHARGE_PUMP_CURRENT_3_52MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_3_52MA; break; case CHARGE_PUMP_CURRENT_3_84MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_3_84MA; break; case CHARGE_PUMP_CURRENT_4_16MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_4_16MA; break; case CHARGE_PUMP_CURRENT_4_48MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_4_48MA; break; case CHARGE_PUMP_CURRENT_4_80MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_4_80MA; break; case CHARGE_PUMP_CURRENT_5_12MA: _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_5_12MA; break; default: UHD_THROW_INVALID_CODE_PATH(); } } template void max287x::set_auto_retune(bool enabled) { _regs.retune = enabled ? max287x_regs_t::RETUNE_ENABLED : max287x_regs_t::RETUNE_DISABLED; } template void max287x::set_clock_divider_mode(clock_divider_mode_t mode) { switch(mode) { case CLOCK_DIV_MODE_CLOCK_DIVIDER_OFF: _regs.clock_div_mode = max287x_regs_t::CLOCK_DIV_MODE_CLOCK_DIVIDER_OFF; break; case CLOCK_DIV_MODE_FAST_LOCK: _regs.clock_div_mode = max287x_regs_t::CLOCK_DIV_MODE_FAST_LOCK; break; case CLOCK_DIV_MODE_PHASE: _regs.clock_div_mode = max287x_regs_t::CLOCK_DIV_MODE_PHASE; break; default: UHD_THROW_INVALID_CODE_PATH(); } } template void max287x::set_cycle_slip_mode(bool enabled) { if (enabled) throw uhd::runtime_error("Cycle slip mode not supported on this MAX287x synthesizer."); } template void max287x::set_low_noise_and_spur(low_noise_and_spur_t mode) { switch(mode) { case LOW_NOISE_AND_SPUR_LOW_NOISE: _regs.low_noise_and_spur = max287x_regs_t::LOW_NOISE_AND_SPUR_LOW_NOISE; break; case LOW_NOISE_AND_SPUR_LOW_SPUR_1: _regs.low_noise_and_spur = max287x_regs_t::LOW_NOISE_AND_SPUR_LOW_SPUR_1; break; case LOW_NOISE_AND_SPUR_LOW_SPUR_2: _regs.low_noise_and_spur = max287x_regs_t::LOW_NOISE_AND_SPUR_LOW_SPUR_2; break; default: UHD_THROW_INVALID_CODE_PATH(); } } template void max287x::set_phase(boost::uint16_t phase) { _regs.phase_12_bit = phase & 0xFFF; } template void max287x::commit() { std::vector regs; std::set changed_regs; // Get only regs with changes if (_write_all_regs) { for (int addr = 5; addr >= 0; addr--) regs.push_back(_regs.get_reg(boost::uint32_t(addr))); } else { try { changed_regs = _regs.template get_changed_addrs (); for (int addr = 5; addr >= 0; addr--) { if (changed_regs.find(boost::uint32_t(addr)) != changed_regs.end()) regs.push_back(_regs.get_reg(boost::uint32_t(addr))); } } catch (uhd::runtime_error&) { // No saved state - write all regs for (int addr = 5; addr >= 0; addr--) regs.push_back(_regs.get_reg(boost::uint32_t(addr))); } } _write(regs); _regs.save_state(); _write_all_regs = false; if (_delay_after_write) { boost::this_thread::sleep(boost::posix_time::milliseconds(20)); _delay_after_write = false; } } template bool max287x::can_sync(void) { return _can_sync; } #endif // MAX287X_HPP_INCLUDED