Skip to content
Snippets Groups Projects
Commit 5a623aee authored by Gijs Schoonderbeek's avatar Gijs Schoonderbeek
Browse files

Added CCD scripts for new revision CCD. (Modified copy of APSCT scripts.

parent cf1de7b6
No related branches found
No related tags found
No related merge requests found
"""
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]
"""
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()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment