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()