diff options
author | Martin Braun <martin.braun@ettus.com> | 2017-11-15 14:52:12 -0800 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2017-12-22 15:05:07 -0800 |
commit | f14b49ff5b9c55972966c74b014850ecc89e75e2 (patch) | |
tree | 6d98fc58ae9e4184c4936765d45be4d2ae7dce6b /mpm/python/usrp_mpm/dboard_manager | |
parent | 63f4049a101d66c01f7e89098b03f3f780647cbd (diff) | |
download | uhd-f14b49ff5b9c55972966c74b014850ecc89e75e2.tar.gz uhd-f14b49ff5b9c55972966c74b014850ecc89e75e2.tar.bz2 uhd-f14b49ff5b9c55972966c74b014850ecc89e75e2.zip |
n3xx: add support for 122.88 and 153.6 MHz sample clock rates
- re-wrote portions of the LMK driver for flexible rates and configuration
- tweaked TDC driver for compatibility and ease of debugging
- updated comments and log statements throughout for uniformity
Diffstat (limited to 'mpm/python/usrp_mpm/dboard_manager')
-rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/lmk_mg.py | 123 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/magnesium.py | 70 |
2 files changed, 121 insertions, 72 deletions
diff --git a/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py b/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py index e6a1821af..dc3fe203b 100644 --- a/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py +++ b/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py @@ -19,27 +19,60 @@ LMK04828 driver for use with Magnesium """ import time +import math from builtins import zip from builtins import hex from ..mpmlog import get_logger from ..chips import LMK04828 class LMK04828Mg(LMK04828): - def __init__(self, regs_iface, spi_lock, ref_clock_freq, log=None): + def __init__(self, regs_iface, spi_lock, ref_clock_freq, master_clock_freq, log=None): LMK04828.__init__(self, regs_iface, log) - self.log.trace("Using reference clock frequency {} MHz".format(ref_clock_freq/1e6)) + self.log.trace("Using reference clock frequency: {} MHz".format(ref_clock_freq/1e6)) + self.log.trace("Using master clock frequency: {} MHz".format(master_clock_freq/1e6)) self.spi_lock = spi_lock assert hasattr(self.spi_lock, 'lock') assert hasattr(self.spi_lock, 'unlock') self.ref_clock_freq = ref_clock_freq + self.master_clock_freq = master_clock_freq + # VCXO on Mg runs at 96 MHz + self.vcxo_freq = 96e6 + # Feedback clock divider is constant for Mg regardless of the master_clock_freq. + self.clkfb_divider = 24 + # PLL2 R value is also constant across all sample clock combinations. + self.pll2_r_divider = 4 + # PLL1 PFD is 1 MHz. Divide the ref_clock_freq by 1 MHz to set the R divider. + self.clkin_r_divider = int(math.floor(self.ref_clock_freq/1e6)) + self.clkout_divider = {122.88e6: 25, 125e6: 24, 153.6e6: 20}[self.master_clock_freq] + self.pll1_n_divider = {122.88e6: 128, 125e6: 125, 153.6e6: 128}[self.master_clock_freq] + self.sysref_divider = {122.88e6: 500, 125e6: 480, 153.6e6: 400}[self.master_clock_freq] + self.pll2_prescaler = {122.88e6: 2, 125e6: 5, 153.6e6: 2}[self.master_clock_freq] + self.pll2_n_divider = {122.88e6: 64, 125e6: 25, 153.6e6: 64}[self.master_clock_freq] + self.pll2_vco_freq = (self.vcxo_freq/self.pll2_r_divider)*self.pll2_prescaler*self.pll2_n_divider + self.log.trace("Variable Configuration Report: " + "clkin1_r = 0d{}, clkout_div = 0d{}, pll1_n = 0d{}" + .format(self.clkin_r_divider, self.clkout_divider, self.pll1_n_divider)) + self.log.trace("Variable Configuration Report: " + "sysref_divider = 0d{}, pll2_pre = 0d{}, pll2_n = 0d{}" + .format(self.sysref_divider, self.pll2_prescaler, self.pll2_n_divider)) + self.log.trace("Variable Configuration Report: " + "pll2_vco_freq = 0d{}" + .format(self.pll2_vco_freq)) + # Run .init() and .config() right off the bat. Save clock shifty-ness for later. self.init() self.config() + def get_vco_freq(self): + """ + Return the calculated VCO frequency in the LMK PLL2. + """ + return self.pll2_vco_freq + def init(self): """ Basic init. Turns it on. Let's read SPI. """ - self.log.info("Reset LMK & Verify") + self.log.info("Reset and Verify Chip ID") self.pokes8(( (0x000, 0x90), # Assert reset (0x000, 0x10), # De-assert reset @@ -47,66 +80,67 @@ class LMK04828Mg(LMK04828): (0x148, 0x33), # Clock Select as SDO )) if not self.verify_chip_id(): - raise Exception("Unable to locate LMK04828") - + raise Exception("Unable to locate LMK04828!") def config(self): """ Write lots of config foo. """ - self.log.trace("LMK Initialization") - clkin0_r_divider = {10e6: 0x0A, 20e6: 0x14}[self.ref_clock_freq] + clkout_div_val = self.divide_to_reg(self.clkout_divider) + clkout_cnt_val = self.divide_to_cnth_cntl_reg(self.clkout_divider) + + self.log.info("Register Initialization Commencing...") self.pokes8(( - (0x100, 0x78), # CLKout Config - (0x101, 0xCC), # CLKout Config + (0x100, clkout_div_val), # CLKout Config + (0x101, clkout_cnt_val), # CLKout Config (0x102, 0xCC), # CLKout Config (0x103, 0x00), # CLKout Config (0x104, 0x20), # CLKout Config (0x105, 0x00), # CLKout Config (0x106, 0x72), # CLKout Config MYK: (0xAB where A = SYSREF, B = CLK) (0x107, 0x15), # CLKout Config 0x15 = LVDS, 0x55 = LVPECL - (0x108, 0x7E), # CLKout Config - (0x109, 0xFF), # CLKout Config + (0x108, clkout_div_val), # CLKout Config + (0x109, clkout_cnt_val), # CLKout Config (0x10A, 0xFF), # CLKout Config (0x10B, 0x00), # CLKout Config (0x10C, 0x00), # CLKout Config (0x10D, 0x00), # CLKout Config (0x10E, 0x70), # CLKout Config (0x10F, 0x55), # CLKout Config - (0x110, 0x78), # CLKout Config - (0x111, 0xCC), # CLKout Config + (0x110, clkout_div_val), # CLKout Config + (0x111, clkout_cnt_val), # CLKout Config (0x112, 0xCC), # CLKout Config (0x113, 0x00), # CLKout Config (0x114, 0x00), # CLKout Config (0x115, 0x00), # CLKout Config (0x116, 0xF9), # CLKout Config (0x117, 0x00), # CLKout Config - (0x118, 0x78), # CLKout Config - (0x119, 0xCC), # CLKout Config + (0x118, self.divide_to_reg(self.clkfb_divider)), # CLKout Config + (0x119, self.divide_to_cnth_cntl_reg(self.clkfb_divider)), # CLKout Config (0x11A, 0xCC), # CLKout Config (0x11B, 0x00), # CLKout Config (0x11C, 0x20), # CLKout Config (0x11D, 0x00), # CLKout Config (0x11E, 0xF1), # CLKout Config (0x11F, 0x00), # CLKout Config - (0x120, 0x78), # CLKout Config - (0x121, 0xCC), # CLKout Config + (0x120, clkout_div_val), # CLKout Config + (0x121, clkout_cnt_val), # CLKout Config (0x122, 0xCC), # CLKout Config (0x123, 0x00), # CLKout Config (0x124, 0x20), # CLKout Config 0x20 = SYSREF output, 0x00 = DEVCLK (0x125, 0x00), # CLKout Config (0x126, 0x72), # CLKout Config FPGA: (0xAB where A = SYSREF, B = CLK) (0x127, 0x55), # CLKout Config 0x1 = LVDS, 0x5 = LVPECL - (0x128, 0x78), # CLKout Config - (0x129, 0xCC), # CLKout Config + (0x128, clkout_div_val), # CLKout Config + (0x129, clkout_cnt_val), # CLKout Config (0x12A, 0xCC), # CLKout Config (0x12B, 0x00), # CLKout Config (0x12C, 0x00), # CLKout Config (0x12D, 0x00), # CLKout Config (0x12E, 0x72), # CLKout Config (0x12F, 0xD0), # CLKout Config - (0x130, 0x78), # CLKout Config - (0x131, 0xCC), # CLKout Config + (0x130, clkout_div_val), # CLKout Config + (0x131, clkout_cnt_val), # CLKout Config (0x132, 0xCC), # CLKout Config (0x133, 0x00), # CLKout Config (0x134, 0x20), # CLKout Config @@ -115,8 +149,8 @@ class LMK04828Mg(LMK04828): (0x137, 0x05), # CLKout Config (0x138, 0x30), # VCO_MUX to VCO 1; OSCout off (0x139, 0x00), # SYSREF Source = MUX; SYSREF MUX = Normal SYNC - (0x13A, 0x01), # SYSREF Divide [12:8] - (0x13B, 0xE0), # SYSREF Divide [7:0] + (0x13A, (self.sysref_divider & 0x1F00) >> 8), # SYSREF Divide [12:8] + (0x13B, (self.sysref_divider & 0x00FF) >> 0), # SYSREF Divide [7:0] (0x13C, 0x00), # SYSREF DDLY [12:8] (0x13D, 0x08), # SYSREF DDLY [7:0] ... 8 is default, <8 is reserved (0x13E, 0x00), # SYSREF Pulse Count = 1 pulse/request @@ -141,13 +175,13 @@ class LMK04828Mg(LMK04828): (0x151, 0x02), # Holdover Settings (defaults) (0x152, 0x00), # Holdover Settings (defaults) (0x153, 0x00), # CLKin0_R divider [13:8], default = 0 - (0x154, clkin0_r_divider), # CLKin0_R divider [7:0], default = d120 - (0x155, 0x00), # CLKin1_R divider [13:8], default = 0 - (0x156, clkin0_r_divider), # CLKin1_R divider [7:0], default = d120 + (0x154, 0x78), # CLKin0_R divider [7:0], default = d120 + (0x155, (self.clkin_r_divider & 0x3F00) >> 8), # CLKin1_R divider [13:8], default = 0 + (0x156, (self.clkin_r_divider & 0x00FF) >> 0), # CLKin1_R divider [7:0], default = d120 (0x157, 0x00), # CLKin2_R divider [13:8], default = 0 (0x158, 0x01), # CLKin2_R divider [7:0], default = d120 - (0x159, 0x00), # PLL1 N divider [13:8], default = 0 - (0x15A, 0x7D), # PLL1 N divider [7:0], default = d120 + (0x159, (self.pll1_n_divider & 0x3F00) >> 8), # PLL1 N divider [13:8], default = 0 + (0x15A, (self.pll1_n_divider & 0x00FF) >> 0), # PLL1 N divider [7:0], default = d120 (0x15B, 0xCF), # PLL1 PFD (0x15C, 0x27), # PLL1 DLD Count [13:8] (0x15D, 0x10), # PLL1 DLD Count [7:0] @@ -155,7 +189,7 @@ class LMK04828Mg(LMK04828): (0x15F, 0x0B), # Status LD1 pin = PLL1 LD, push-pull output (0x160, 0x00), # PLL2 R divider [11:8]; (0x161, 0x04), # PLL2 R divider [7:0] - (0x162, 0xA4), # PLL2 prescaler; OSCin freq + (0x162, self.pll2_pre_to_reg(self.pll2_prescaler)), # PLL2 prescaler; OSCin freq 0xA4 (0x163, 0x00), # PLL2 Cal = PLL2 normal val (0x164, 0x00), # PLL2 Cal = PLL2 normal val (0x165, 0x19), # PLL2 Cal = PLL2 normal val @@ -163,9 +197,9 @@ class LMK04828Mg(LMK04828): (0x172, 0x02), # Write this val after x165 (0x17C, 0x15), # VCo1 Cal; write before x168 (0x17D, 0x33), # VCo1 Cal; write before x168 - (0x166, 0x00), # PLL2 N[17:16] - (0x167, 0x00), # PLL2 N[15:8] - (0x168, 0x19), # PLL2 N[7:0] + (0x166, (self.pll2_n_divider & 0x030000) >> 16), # PLL2 N[17:16] + (0x167, (self.pll2_n_divider & 0x00FF00) >> 8), # PLL2 N[15:8] + (0x168, (self.pll2_n_divider & 0x0000FF) >> 0), # PLL2 N[7:0] (0x169, 0x51), # PLL2 PFD (0x16A, 0x27), # PLL2 DLD Count [13:8] = default d32 (0x16B, 0x10), # PLL2 DLD Count [7:0] = default d0 @@ -187,16 +221,14 @@ class LMK04828Mg(LMK04828): (0x183, 0x1), # Clear Lock Detect Sticky (0x183, 0x0), # Clear Lock Detect Sticky )) - # Wait a bit before checking for lock - # time.sleep(0.050) if self.check_plls_locked(): locked = True - self.log.info("LMK PLLs Locked!") + self.log.trace("PLLs are Locked!") break if not locked: - raise RuntimeError("At least one LMK PLL did not lock! Check the logs for details.") + raise RuntimeError("At least one PLL did not lock! Check the logs for details.") - self.log.trace("Setting SYNC and SYSREF config...") + self.log.trace("Synchronizing output dividers...") self.pokes8(( (0x143, 0xF1), # toggle SYNC polarity to trigger SYNC event (0x143, 0xD1), # toggle SYNC polarity to trigger SYNC event @@ -204,17 +236,15 @@ class LMK04828Mg(LMK04828): (0x144, 0xFF), # Disable SYNC on all outputs including sysref (0x143, 0x52), # Pulser selected; SYNC enabled; 1 shot enabled )) - self.log.info("LMK init'd and locked!") + self.log.info("Clocks Initialized and PLLs Locked!") def lmk_shift(self, num_shifts=0): """ - Apply time shift + Apply time shift using the dynamic digital delays inside the LMK. """ - # TODO: these numbers need to be based off the radio clock freq. - self.log.trace("LMK04828 Clock Phase Shifting Commencing...") - ddly_value = 0xCD if num_shifts >= 0 else 0xCB - ddly_value_sysref_reg0 = 0x01 - ddly_value_sysref_reg1 = 0xE1 if num_shifts >= 0 else 0xDF # 0xE0 is normal + self.log.trace("Clock Shifting Commencing using Dynamic Digital Delay...") + ddly_value = self.divide_to_cnth_cntl_reg(self.clkout_divider+1) if num_shifts >= 0 else self.divide_to_cnth_cntl_reg(self.clkout_divider-1) + ddly_value_sysref = self.sysref_divider+1 if num_shifts >= 0 else self.sysref_divider-1 self.pokes8(( (0x141, 0xB1), # Dynamic digital delay enable on outputs 0, 8, 10 (0x143, 0x53), # SYSREF_CLR; SYNC Enabled; SYNC from pulser @ regwrite @@ -225,13 +255,12 @@ class LMK04828Mg(LMK04828): (0x122, ddly_value), # Hidden register. Write the same as previous based on inc/dec. (0x129, ddly_value), # Set DDLY values for DCLKout10 +/-1 on low cnt (0x12A, ddly_value), # Hidden register. Write the same as previous based on inc/dec. - (0x13C, ddly_value_sysref_reg0), # SYSREF DDLY value - (0x13D, ddly_value_sysref_reg1), # SYSREF DDLY value + (0x13C, (ddly_value_sysref & 0x1F00) >> 8), # SYSREF DDLY value [12:8] + (0x13D, (ddly_value_sysref & 0x00FF) >> 0), # SYSREF DDLY value [7:0] (0x144, 0x4E), # Enable SYNC on outputs 0, 8, 10 )) for x in range(abs(num_shifts)): self.poke8(0x142, 0x1) # Put everything back the way it was before shifting. self.poke8(0x144, 0xFF) # Disable SYNC on all outputs including SYSREF - # self.poke8(0x143, 0xD2) # Reset SYSREF engine to proper SYNC settings self.poke8(0x143, 0x52) # Pulser selected; SYNC enabled; 1 shot enabled diff --git a/mpm/python/usrp_mpm/dboard_manager/magnesium.py b/mpm/python/usrp_mpm/dboard_manager/magnesium.py index 5b6b81598..6c100e992 100644 --- a/mpm/python/usrp_mpm/dboard_manager/magnesium.py +++ b/mpm/python/usrp_mpm/dboard_manager/magnesium.py @@ -161,7 +161,7 @@ class DboardClockControl(object): """ Uninitialize and reset the MMCM """ - self.log.trace("Disabling all Radio Clocks, then resetting MMCM...") + self.log.trace("Disabling all Radio Clocks, then resetting FPGA MMCM...") self.enable_outputs(False) self.poke32(self.RADIO_CLK_MMCM, 0x1) @@ -171,16 +171,16 @@ class DboardClockControl(object): If MMCM is not locked after unreset, an exception is thrown. """ - self.log.trace("Un-resetting MMCM...") + self.log.trace("Enabling FPGA Radio Clock MMCM...") self.poke32(self.RADIO_CLK_MMCM, 0x2) if not poll_with_timeout( lambda: bool(self.peek32(self.RADIO_CLK_MMCM) & 0x10), 500, 10, ): - self.log.error("MMCM not locked!") - raise RuntimeError("MMCM not locked!") - self.log.trace("MMCM locked. Enabling output MMCM clocks...") + self.log.error("FPGA Radio Clock MMCM not locked!") + raise RuntimeError("FPGA Radio Clock MMCM not locked!") + self.log.trace("FPGA Radio Clock MMCM locked. Enabling clocks to design...") self.enable_outputs(True) def check_refclk(self): @@ -387,7 +387,7 @@ class Magnesium(DboardManagerBase): self.eeprom_fs, self.eeprom_path = self._init_user_eeprom( self.user_eeprom[self.rev] ) - self.radio_regs = UIO( + self.dboard_ctrl_regs = UIO( label="dboard-regs-{}".format(self.slot_idx), read_only=False ) @@ -397,7 +397,7 @@ class Magnesium(DboardManagerBase): for key in self._spi_nodes } self.cpld = MgCPLD(self._spi_ifaces['cpld'], self.log) - self.dboard_clk_control = DboardClockControl(self.radio_regs, self.log) + self.dboard_clk_control = DboardClockControl(self.dboard_ctrl_regs, self.log) def _power_on(self): " Turn on power to daughterboard " @@ -472,7 +472,7 @@ class Magnesium(DboardManagerBase): """ Execute necessary init dance to bring up dboard """ - def _init_lmk(lmk_spi, ref_clk_freq, + def _init_lmk(lmk_spi, ref_clk_freq, master_clk_freq, pdac_spi, init_phase_dac_word): """ Sets the phase DAC to initial value, and then brings up the LMK @@ -483,7 +483,7 @@ class Magnesium(DboardManagerBase): init_phase_dac_word )) pdac_spi.poke16(0x0, init_phase_dac_word) - return LMK04828Mg(lmk_spi, self.spi_lock, ref_clk_freq, self.log) + return LMK04828Mg(lmk_spi, self.spi_lock, ref_clk_freq, master_clk_freq, self.log) def _sync_db_clock(synchronizer): " Synchronizes the DB clock to the common reference " synchronizer.run_sync(measurement_only=False) @@ -508,35 +508,48 @@ class Magnesium(DboardManagerBase): error_msg = "Cannot run init(), peripherals are not initialized!" self.log.error(error_msg) raise RuntimeError(error_msg) + if 'ref_clk_freq' in args: + self.ref_clock_freq = float(args['ref_clk_freq']) + assert self.ref_clock_freq in (10e6, 20e6, 25e6) + if 'master_clock_rate' in args: + self.master_clock_rate = float(args['master_clock_rate']) + assert self.master_clock_rate in (122.88e6, 125e6, 153.6e6) + self.log.trace("Creating jesdcore object") + self.jesdcore = nijesdcore.NIMgJESDCore(self.dboard_ctrl_regs, self.slot_idx) + + self.log.info("Reset Daughterboard Clocking and JESD204B interfaces...") self.dboard_clk_control.reset_mmcm() + self.jesdcore.reset() + self.lmk = _init_lmk( self._spi_ifaces['lmk'], self.ref_clock_freq, + self.master_clock_rate, self._spi_ifaces['phase_dac'], self.INIT_PHASE_DAC_WORD, ) self.dboard_clk_control.enable_mmcm() self.log.info("Sample Clocks and Phase DAC Configured Successfully!") # Synchronize DB Clocks + # Future Work: target_value needs to be tweaked to support heterogenous rate sync. + self.target_value = {122.88e6: 128e-9, 125e6: 128e-9, 153.6e6: 122e-9}[self.master_clock_rate] self.clock_synchronizer = ClockSynchronizer( - self.radio_regs, - self.dboard_clk_control, + self.dboard_ctrl_regs, self.lmk, self._spi_ifaces['phase_dac'], - 0, # TODO this might not actually be zero + 0, # register offset value. future work. self.master_clock_rate, self.ref_clock_freq, - 860E-15, # TODO don't hardcode. This should live in the EEPROM + 860E-15, # fine phase shift. TODO don't hardcode. This should live in the EEPROM self.INIT_PHASE_DAC_WORD, - 3e9, # lmk_vco_freq - [128e-9,], # target_values + [self.target_value,], # target_values 0x0, # spi_addr TODO: make this a constant and replace in _sync_db_clock as well self.log ) _sync_db_clock(self.clock_synchronizer) # Clocks and PPS are now fully active! - self.init_jesd(self.radio_regs, args) + self.init_jesd(args) self.mykonos.start_radio() return True @@ -571,15 +584,12 @@ class Magnesium(DboardManagerBase): self.log.debug("args[tracking_cals]=0x{:02X}".format(self._tracking_cals_mask)) self.mykonos.setup_cal(self._init_cals_mask, self._tracking_cals_mask, self._init_cals_timeout) - def init_jesd(self, uio, args): + def init_jesd(self, args): """ Bring up the JESD link between Mykonos and the N310. + All clocks must be set up and stable before starting this routine. """ - # CPLD Register Definition - self.log.trace("Creating jesdcore object") - self.jesdcore = nijesdcore.NIMgJESDCore(uio, self.slot_idx) self.jesdcore.check_core() - self.jesdcore.unreset_qpll() self.jesdcore.init() self.log.trace("Pulsing Mykonos Hard Reset...") @@ -599,7 +609,7 @@ class Magnesium(DboardManagerBase): self.mykonos.begin_initialization() # Multi-chip Sync requires two SYSREF pulses at least 17us apart. self.jesdcore.send_sysref_pulse() - time.sleep(0.001) + time.sleep(0.001) # 17us... ish. self.jesdcore.send_sysref_pulse() self.mykonos.finish_initialization() # TODO:can we call this after JESD? @@ -618,7 +628,7 @@ class Magnesium(DboardManagerBase): self.log.trace("Starting Mykonos framer...") self.mykonos.start_jesd_tx() self.log.trace("Enable FPGA SYSREF Receiver.") - self.jesdcore.enable_lmfc() + self.jesdcore.enable_lmfc(True) self.jesdcore.send_sysref_pulse() self.log.trace("Starting FPGA deframer...") self.jesdcore.init_deframer() @@ -645,11 +655,11 @@ class Magnesium(DboardManagerBase): def dump_jesd_core(self): " Debug method to dump all JESD core regs " - radio_regs = UIO(label="dboard-regs-{}".format(self.slot_idx)) + dboard_ctrl_regs = UIO(label="dboard-regs-{}".format(self.slot_idx)) for i in range(0x2000, 0x2110, 0x10): print(("0x%04X " % i), end=' ') for j in range(0, 0x10, 0x4): - print(("%08X" % radio_regs.peek32(i + j)), end=' ') + print(("%08X" % dboard_ctrl_regs.peek32(i + j)), end=' ') print("") def get_user_eeprom_data(self): @@ -716,6 +726,16 @@ class Magnesium(DboardManagerBase): " Return master clock rate (== sampling rate) " return self.master_clock_rate + def update_ref_clock_freq(self, freq): + """ + Call this function if the frequency of the reference clock changes (the + 10, 20, 25 MHz one). + """ + self.log.info("Changing reference clock frequency to {} MHz".format(freq/1e6)) + assert self.ref_clock_freq in (10e6, 20e6, 25e6) + self.ref_clock_freq = freq + # JEPSON FIXME call init() ? Maybe not yet! + ########################################################################## # Sensors |