diff --git a/CCD_I2C.py b/CCD_I2C.py
new file mode 100644
index 0000000000000000000000000000000000000000..a9b13bde6dd4d7bc7c1acd9112fbd04bc86870b3
--- /dev/null
+++ b/CCD_I2C.py
@@ -0,0 +1,64 @@
+"""
+Copyright 2022 Stichting Nederlandse Wetenschappelijk Onderzoek Instituten,
+ASTRON Netherlands Institute for Radio Astronomy
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+ Created: 2022-12-07
+This file contains the definitions of the APSPU registers etc.
+
+
+"""
+#
+# I2C address, registers and ports for APSCT
+# Created: 2023-01-06
+#
+
+#
+# Power supplies
+#
+
+PWR_LOCATIONS = {"CLK_IN":  0,
+                 "PLL": 1,
+                 "OCXO": 2,
+                 "CLK_DIST": 3,
+                 "PPS_IN": 4,
+                 "PPS_DIST": 5,
+                 "CTRL": 6}
+
+PWR_VOUT = {"CLK_IN":  3.3,
+            "PLL": 3.3,
+            "OCXO": 3.3,
+            "CLK_DIST": 3.3,
+            "PPS_IN": 3.3,
+            "PPS_DIST": 3.3,
+            "CTRL": 3.3}
+
+#
+# PLL constants / pin locations
+#
+CS = 6
+SCLK = 4
+SDO = 5
+SDI = 7
+
+PLL = 0x20
+
+#
+# Central I2C Devices
+#
+EEPROM = 0x50
+
+
+#
+# ID Pins 
+#
+ID_PINS = [8, 7, 12, 16, 20, 21]
diff --git a/ccd_lib.py b/ccd_lib.py
new file mode 100644
index 0000000000000000000000000000000000000000..5de831790137e088d5b39d288eddfc55e9403aef
--- /dev/null
+++ b/ccd_lib.py
@@ -0,0 +1,537 @@
+"""
+Copyright 2021 Stichting Nederlandse Wetenschappelijk Onderzoek Instituten,
+ASTRON Netherlands Institute for Radio Astronomy
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Set CCD_CLK
+
+"""
+import sys
+import CCD_I2C
+import time
+import RPi.GPIO as gpio
+import random
+import numpy as np
+sys.path.insert(0, '.')
+import os
+if os.name == "posix":
+    from I2C_serial_pi2 import *
+else:
+    from I2C_serial import *
+
+I2CBUSNR = 1
+sleep_time = 0.0001
+DEBUG = False
+
+
+class CcdClass:
+    #
+    # Toplevel class that contains all controllable parts of the CCD
+    #
+    def __init__(self):
+        self.status = False
+        self.frequency = "10 MHz"
+        self.eeprom = EepromClass()
+        self.pll = PllClass()
+        self.sensors = ApsctSensors()
+        self.ccd_id = CcdId()
+
+    def power(self, state):
+        #
+        # Set power supply CCD in the give state
+        #
+        # state is True: Power on
+        # state is False: Power off
+        #
+        ADDRESS_IO = 0x20
+        I2C_IO_device = I2C(ADDRESS_IO, BUSNR=I2CBUSNR)
+        I2C_IO_device.write_bytes(0x06, 0x2C)
+        if state:
+            bits_to_set = 0x42
+        else:
+            bits_to_set = 0x40
+        I2C_IO_device.write_bytes(0x02, bits_to_set)
+
+    def set_ccd(self):
+        #
+        # set CCD to 10MHz
+        #
+        self.power(True)
+        self.pll.setup_pll()
+
+    def check_ccd(self):
+        #
+        # Check voltages, temperature and PLL-lock on CCD
+        #
+        # Return Result, True when OK, False in case of an error
+        result = self.sensors.check_values()
+        result = result & self.pps.check_timing()
+        result = result & self.ccd_id.check_id()
+        self.pll.read_lock()
+        lock = self.pll.lock
+        result = result & lock
+        return result
+
+
+class PllClass:
+    #
+    # Toplevel class that contains all function to set the PLL
+    #
+    def __init__(self):
+        self.status = False
+        self.lock = False
+        self.frequency = "10 MHz"
+        self.i2c_address = CCD_I2C.PLL
+        self.dev_i2c_pll = I2C(self.i2c_address, BUSNR=I2CBUSNR)
+
+    def reset_interface(self):
+        #
+        # Reset the SPI interface 
+        #
+        self.dev_i2c_pll.write_bytes(0x06, 0x2C)
+        sleep(0.01)
+        write_data = 0x02 | (0 << CCD_I2C.CS) | (0 << CCD_I2C.SCLK) | (0 << CCD_I2C.SDI)
+        self.dev_i2c_pll.write_bytes(0x02, write_data)
+        for cnt in range(4):
+            for clk in [0, 1, 0]:
+                write_data = 0x02 | (0 << CCD_I2C.CS) | (clk << CCD_I2C.SCLK) | (0 << CCD_I2C.SDI)
+                self.dev_i2c_pll.write_bytes(0x02, write_data)
+                sleep(sleep_time)
+        write_data = 0x02 | (1 << CCD_I2C.CS) | (0 << CCD_I2C.SCLK) | (0 << CCD_I2C.SDI)
+        self.dev_i2c_pll.write_bytes(0x02, write_data)
+        sleep(0.01)
+
+    def write_byte_pll(self, reg_address, wr_data):
+        #
+        # Write Byte to the PLL
+        #
+        pll_rw = 0x00  # 0 for write, 1 for read
+        if DEBUG:
+            stri = "Write to address : 0x{1:{fill}2x} value  0x{0:{fill}2x}".format(wr_data, reg_address, fill='0')
+            print(stri)
+        rw_reg_pll = 0b00101101  # 1 is input
+        self.dev_i2c_pll.write_bytes(0x06, rw_reg_pll)
+        rd_bytes = self.dev_i2c_pll.read_bytes(0x06, 1)
+        if DEBUG:
+            stri = "IO expander wrote 0x{0:x}, read 0x{1}".format(rw_reg_pll, rd_bytes[1])
+            print(stri)
+        nof_bytes = 1
+        data = (pll_rw << 23) + ((nof_bytes-1) << 21) + (reg_address << 8) + wr_data
+        bit_array = "{0:{fill}24b}".format(data, fill='0')
+        self.dev_i2c_pll.write_bytes(0x02, 0x02 | (0x1 << CCD_I2C.CS))
+        for bit in bit_array:
+            for clk in [0, 1, 0]:  # range(2):
+                write_data = 0x02 | (0 << CCD_I2C.CS) | (clk << CCD_I2C.SCLK) | (int(bit) << CCD_I2C.SDI)
+                self.dev_i2c_pll.write_bytes(0x02, write_data)
+                sleep(sleep_time)
+        write_data = 0x02 | (1 << CCD_I2C.CS) | (0 << CCD_I2C.SCLK) | (0 << CCD_I2C.SDI)
+        self.dev_i2c_pll.write_bytes(0x02, write_data)
+
+    def read_byte_pll(self, reg_address, nof_bytes=1, print_value=False):
+        #
+        # Read byte from the PLL
+        #
+        pll_rw = 0x01  # 0 for write, 1 for read
+        read_bit = ''
+        self.dev_i2c_pll.write_bytes(0x06, 0x2C)
+        data = (pll_rw << 15) + ((nof_bytes-1) << 13) + reg_address
+        bit_array = "{0:{fill}16b}".format(data, fill='0')
+        if DEBUG:
+            print(f"Write data: {bit_array}")
+        write_data = 0x02 | (0 << CCD_I2C.CS) | (0 << CCD_I2C.SCLK) | (0 << CCD_I2C.SDI)
+        self.dev_i2c_pll.write_bytes(0x02, write_data)
+        sleep(sleep_time)
+        for bit in bit_array:
+            for clk in [0, 1, 0]:
+                write_data = 0x02 | (0 << CCD_I2C.CS) | (clk << CCD_I2C.SCLK) | (int(bit) << CCD_I2C.SDI)
+                self.dev_i2c_pll.write_bytes(0x02, write_data)
+                sleep(sleep_time)
+        self.dev_i2c_pll.write_bytes(0x06, 0xAC)
+        for cnt in range(8*nof_bytes):
+            for clk in [0, 1]:  # Read after rizing edge
+                write_data = 0x02 | (0 << CCD_I2C.CS) | (clk << CCD_I2C.SCLK) | (0 << CCD_I2C.SDI)
+                self.dev_i2c_pll.write_bytes(0x02, write_data)
+                sleep(sleep_time)
+            ret_ack, ret_value = self.dev_i2c_pll.read_bytes(0x00, 1)
+            if ret_ack:
+                read_bit += str((int(ret_value, 16) >> CCD_I2C.SDO) & 0x01)
+            else:
+                print("ACK nok")
+        if print_value:
+            print(f"Read bits {read_bit}")
+        self.dev_i2c_pll.write_bytes(0x06, 0x2C)
+        write_data = 0x02 | (1 << CCD_I2C.CS) | (0 << CCD_I2C.SCLK) | (0 << CCD_I2C.SDI)
+        self.dev_i2c_pll.write_bytes(0x02, write_data)
+        if DEBUG:
+            stri = "Read back at address 0x{0:{fill}2x} result : 0x{1:{fill}2x} ".format(reg_address,
+                                                                                         int(read_bit, 2), fill='0')
+            print(stri)
+        return int(read_bit, 2)
+
+    def setup_pll(self):
+        #
+        # Set registers on the PLL
+        #
+        print(f"Setup PPL {self.frequency}")
+        divider_r = 1
+        divider_a = 1
+        divider_p = 1
+        divider_b = 1
+        if DEBUG:
+            print(f"Divider P : {divider_p}, Divider A : {divider_a}, Divider B : {divider_b}")
+        charge_pump_current = 3  # 0 is low (0.6 mA), 7 is high (4.8 mA)
+        self.write_byte_pll(0x04, (divider_a & 0x3F))
+        self.write_byte_pll(0x05, (divider_b & 0x1F00) >> 8)
+        self.write_byte_pll(0x06, (divider_b & 0x00FF))
+        self.write_byte_pll(0x07, 0x00)  # No LOR
+        self.write_byte_pll(0x08, 0x3B)  # Charge pump normal + Status bit
+        self.write_byte_pll(0x09, (charge_pump_current & 0x7) << 4)
+        self.write_byte_pll(0x0A, 0x00)  # Fixed Divide 1
+        self.write_byte_pll(0x0B, 0x00)
+        self.write_byte_pll(0x0C, 0x01)
+        self.write_byte_pll(0x45, 0x00)  # CLK2 as feedback clock input
+        self.write_byte_pll(0x3D, 0x08)  # OUT0 ON LVDS Standard
+        self.write_byte_pll(0x3E, 0x0A)  # OUT1 OFF
+        self.write_byte_pll(0x3F, 0x0A)  # OUT2 OFF
+        self.write_byte_pll(0x40, 0x03)  # OUT3 OFF
+        self.write_byte_pll(0x41, 0x02)  # OUT4 ON LVDS Standard
+        self.write_byte_pll(0x4B, 0x80)  # OUT0 bypass divider
+        self.write_byte_pll(0x4D, 0x80)  # OUT1 bypass divider
+        self.write_byte_pll(0x4F, 0x80)  # OUT2 bypass divider
+        self.write_byte_pll(0x51, 0x80)  # OUT3 bypass divider
+        self.write_byte_pll(0x53, 0x80)  # OUT4 bypass divider
+        self.write_byte_pll(0x5A, 0x0F)  # Update registers
+
+    def read_all_regs_pll(self):
+        #
+        # Read all registers on the PLL and print on screen
+        #
+        bytes_to_read = 90
+        for cnt in range(bytes_to_read):
+            ret_value = self.read_byte_pll(cnt, 1)
+            stri = f"Reg nr 0x{cnt:0>2x} value: 0x{ret_value:0>2x}"
+            print(stri)
+
+    def read_lock(self):
+        #
+        # Read lock status
+        #
+        i2_c_io_device = I2C(0x20, BUSNR=I2CBUSNR)
+        i2_c_io_device.write_bytes(0x06, 0x2C)  # '0' is output
+        i2_c_io_device.write_bytes(0x07, 0x00)  # '0' is output
+        ack, ret_value = i2_c_io_device.read_bytes(0x00, 1)
+        status_pll = int(ret_value, 16) & 0x04 
+        if status_pll:
+            self.lock = True
+            stri = f"PLL is in lock"
+        else:
+            self.lock = False
+            stri = f"PLL is not locked"
+        print(stri)
+        return self.lock
+
+    def read_lol(self):
+        #
+        # Read loss of lock status
+        #
+        i2_c_io_device_a = I2C(0x20, BUSNR=I2CBUSNR)
+        i2_c_io_device_a.write_bytes(0x06, 0x2C)  # '0' is output
+        i2_c_io_device_a.write_bytes(0x07, 0x00)  # '0' is output
+        i2_c_io_device_b = I2C(0x21, BUSNR=I2CBUSNR)
+        i2_c_io_device_b.write_bytes(0x06, 0x2C)  # '0' is output
+        i2_c_io_device_b.write_bytes(0x07, 0xFF)  # '0' is output
+        ack, ret_value = i2_c_io_device_b.read_bytes(0x01, 1)
+        status_reg = int(ret_value, 16)
+        lol = (status_reg & 0x04)
+        if lol:
+            print(f"{self.frequency} has lost lock")
+        ack, ret_value = i2_c_io_device_a.read_bytes(0x01, 1)
+        old_reg = int(ret_value, 16)
+        i2_c_io_device_a.write_bytes(0x03, (old_reg | 0x20))  # '0' is output
+        sleep(1)
+        i2_c_io_device_a.write_bytes(0x03, (old_reg & 0xDF))  # '0' is output
+
+
+class EepromClass:
+    #
+    # Class to handle EEPROM communication
+    #
+    def __init__(self):
+        self.dev_i2c_eeprom = I2C(CCD_I2C.EEPROM)
+        self.dev_i2c_eeprom.bus_nr = I2CBUSNR
+
+    def write_eeprom(self, data="CCD", address=0):
+        #
+        # Write the EEPROM with the serial number etc.
+        #
+        # Data = data to write in string formal
+        # Address = address to write the data to
+        # Return True if successfully
+        #
+        ret_ack, ret_value = self.dev_i2c_eeprom.read_bytes(0)
+        if ret_ack < 1:
+            print("EEPROM not found during write")
+            return False
+        else:
+            wr_data = bytearray(data.encode("utf-8", errors="ignore"))
+            for loc, data_byte in enumerate(wr_data):
+                self.dev_i2c_eeprom.write_bytes(address + loc, data_byte)
+                sleep(0.1)
+            return True
+
+    def read_eeprom(self, address=0, nof_bytes=5):
+        #
+        # Read the EEPROM with the serial number etc.
+        #
+        # Address = address to read from
+        # nof_bytes = number of bytes to read
+        # return string with the data from the flash
+        #
+        ret_ack, ret_value = self.dev_i2c_eeprom.read_last_reg(1)
+        if ret_ack < 1:
+            print("no EEPROM found during read")
+            return False
+        else:
+            ret_ack, ret_value = self.dev_i2c_eeprom.read_bytes(address, nof_bytes)
+            ret_value = bytes.fromhex(ret_value[:nof_bytes * 2])
+            str_return = ret_value.decode('UTF-8')
+            return str_return
+
+    def wr_rd_eeprom(self, value="CCD-1", address=0):
+        #
+        # Write and Read the EEPROM to check functionality
+        #
+        # value = string with data to write
+        # address = address to write the data to
+        # return True if read back is same as write value
+        #
+        if self.write_eeprom(value, address=0):
+            ret_value = self.read_eeprom(address=0, nof_bytes=len(value))
+            print(ret_value)
+            stri = "Wrote to EEPROM register 0x{2:x} : {0}, Read from EEPROM: {1}".format(value, ret_value, address)
+            print(stri)
+            if ret_value == value:
+                return True
+            else:
+                return False
+
+
+class CcdSensors:
+    #
+    # All monitor. read and write functions for the voltage sensors on CCD
+    #
+    def __init__(self):
+        #
+        # All monitor. read and write functions for the EEPROM
+        #
+        self.dev_i2c_sensor = I2C(0x74)
+        self.dev_i2c_sensor.bus_nr = I2CBUSNR
+        self.power_supplies = list(CCD_I2C.PWR_LOCATIONS.keys())
+        self.voltages = {}
+        self.temperature = 9999
+        self.dev_i2c_sensor.write_bytes(0xB0, 0xB8)
+
+    def ccd_sensors(self):
+        #
+        # read all 7 power sens lines
+        #
+        # Return True when done
+        #
+        for sens_line in range(7):
+            self.read_voltage(sens_line)
+        self.read_temp()
+        return True
+
+    def read_all_avg_voltages(self, avg_nr=6):
+        #
+        # Function to read and process one sensline
+        #
+        # Return True when done
+        #
+        # To remove errors, repeat measurement when returned voltage is < 3 V
+        #
+        self.read_all_voltages()
+        sum_values = {"CLK_IN":  [],
+                      "PLL": [],
+                      "OCXO": [],
+                      "CLK_DIST": [],
+                      "PPS_IN": [],
+                      "PPS_DIST": [],
+                      "CTRL": []}
+
+        for cnt in range(avg_nr):
+            self.read_all_voltages()
+            for pwr in self.power_supplies:
+                sum_values[pwr].append(self.voltages[pwr])
+        for pwr in self.power_supplies:
+            median_value = np.median(sum_values[pwr])
+            if DEBUG: 
+                print(f"Power {pwr} last value {self.voltages[pwr]:.2f} V median {median_value:.2f} V")
+            self.voltages[pwr] = median_value 
+
+    def read_all_voltages(self):
+        #
+        # Function to read and process one sensline
+        #
+        # Return True when done
+        #
+        # To remove errors, repeat measurement when returned voltage is < 3 V
+        #
+        for pwr in self.power_supplies:
+            self.voltages[pwr] = self.read_voltage(CCD_I2C.PWR_LOCATIONS[pwr])
+            if self.voltages[pwr] < 3:
+                self.voltages[pwr] = self.read_voltage(CCD_I2C.PWR_LOCATIONS[pwr])
+        return True
+
+    def check_values(self):
+        #
+        # Function to check sensor values (voltages and temperature)
+        #
+        # return result, True when OK, False in case of error
+        #
+        print("Check power sensor values")
+        result = True
+        self.read_all_avg_voltages()
+        self.read_temp()
+        for pwr in self.power_supplies:
+            expected = CCD_I2C.PWR_VOUT[pwr]
+            if not (0.8*expected < self.voltages[pwr] < 1.2*expected):
+                result = False
+                print(f"Error: {pwr: <9} expected: {expected} V read: {self.voltages[pwr]:4.2f} V")
+            else:
+                print(f"OK   : {pwr: <9} expected: {expected} V read: {self.voltages[pwr]:4.2f} V")
+        if not (15 < self.temperature < 50):
+            result = False
+            print(f"Error temperature read {self.temperature:4.2f} °C")
+        else:
+            print(f"OK    temperature read {self.temperature:4.2f} °C")
+        return result
+
+    def read_voltage(self, input_channel=0):
+        #
+        # Function to read a voltages of CCD
+        #
+        # input_channel = sens port
+        # return value
+        #
+        voltage = 9999
+        vref = 3.0
+        one_step = vref/(2**16)
+        channel_select_word = 0xB0 | ((input_channel % 2) << 3) | ((input_channel >> 1) & 0x7)
+        if DEBUG:
+            stri = "Word to select sens input is 0x{0:x}".format(channel_select_word)
+            print(stri)
+#        self.dev_i2c_sensor.write_bytes(channel_select_word, 0xB8)
+        self.dev_i2c_sensor.write_bytes(channel_select_word, 0x80)
+        sleep(0.2)  # Wait for device to take snapshot
+        ret_ack, ret_value = self.dev_i2c_sensor.read_last_reg(3)
+        if DEBUG:
+            stri = "Return value input 0 : 0x{0} ".format(ret_value)
+            print(stri)
+        if int(ret_value, 16) >= 0xC00000:
+            print("over range")
+        else:
+            steps = (int(ret_value, 16) & 0x1FFFFF) / 2**6
+            voltage = one_step * steps
+            voltage = ((4.7+2.2)/2.2)*2*voltage  # Resistor network + half swing
+            if DEBUG:
+                string = "Voltage sens line {1} is {0:.4f} V".format(voltage, input_channel)
+                print(string)
+        sleep(0.2)  # wait for device to go to sleep
+        return voltage
+
+    def read_temp(self):
+        #
+        # Function to read temperature of CCD
+        #
+        # return value
+        #
+        vref = 3.0
+        temp_slope = 93.5E-6 * 2**(16+1) / vref
+        ret_ack = self.dev_i2c_sensor.write_bytes(0xA0, 0xC0)
+        if not ret_ack:
+            self.temperature = 9999
+            return self.temperature
+        sleep(0.5)
+        self.temperature = 9999
+        loops = 0
+        while (self.temperature > 100) & (loops < 2):
+            loops = loops + 1
+            ret_ack, ret_value = self.dev_i2c_sensor.read_last_reg(3)
+            if ret_ack:
+                raw_value = (int(ret_value, 16) & 0x1FFFFF) >> 6
+                temperature_k = (raw_value/temp_slope)
+                self.temperature = temperature_k-273
+            else:
+                self.temperature = 9999
+            sleep(0.2)
+        return self.temperature
+
+
+
+
+class CcdtId:
+    #
+    # Class to check ID pins
+    #
+    def __init__(self):
+        #
+        # CCD Backplane ID
+        #
+        self.id = 9999  # placeholder for CCD-ID
+        gpio.setmode(gpio.BCM)  # Set IO pins used for the CCD-ID
+        for pin in CCD_I2C.ID_PINS:
+            gpio.setup(pin, gpio.IN)
+
+    def read_id(self):
+        #
+        # Function to read the CCD-ID from the backplane
+        #
+        loc_id = 0
+        for pin in CCD_I2C.ID_PINS:
+            loc_id = loc_id * 2
+            bit = gpio.input(pin)
+            loc_id = loc_id + bit
+        self.id = loc_id
+        return self.id
+
+    def check_id(self):
+        #
+        # Function Check the ID.
+        #
+        self.read_id()
+        if self.id == 63:
+            print(f"OK   : Back ID is 0x{self.id:02X}")
+            return True
+        else:
+            print(f"ERROR : Back ID is 0x{self.id:02X} expected 0x{63:2X}")
+            return False
+
+
+def main():
+    Ccd = CcdClass()
+    if False:
+        Ccd.power(False)
+        sleep(1)
+        Ccd.power(True)
+        sleep(1)
+    if False:
+        Ccd.pll.write_byte_pll(0x06, 0xA5)
+        Ccd.pll.write_byte_pll(0x5A, 0x01)
+    Ccd.pll.setup_pll()
+    Ccd.pll.read_lol()
+    Ccd.pll.read_lock()
+    Ccd.pll.read_all_regs_pll()
+    Ccd.sensors.check_values()
+
+
+if __name__ == "__main__":
+    main()