Program Listing for File tle5012b.py

Return to documentation for file (src/framework/microPython/lib/tle5012b.py)

# \file        TLE5012b.py
# \name        TLE5012b core library for the TLx5012B angle sensor family.
# \author      Infineon Technologies AG
# \copyright   2019-2024 Infineon Technologies AG
# \version     Version: 4.0.0
# \brief       GMR-based angle sensor for angular position sensing in automotive applications
#
# SPDX-License-Identifier: MIT

from spi3w import SPI3W
from tle5012b_reg import Reg


TRIGGER_DELAY              = 5         # @brief 5 microseconds trigger delay time
READ_SENSOR                = 0x8000    # @brief base command for read
WRITE_SENSOR               = 0x5000    # @brief base command for write
READ_BLOCK_CRC             = 0x8088    # @brief initialize block CRC check command
SYSTEM_ERROR_MASK          = 0x4000    # @brief System error masks for safety words
INTERFACE_ERROR_MASK       = 0x2000    # @brief Interface error masks for safety words
INV_ANGLE_ERROR_MASK       = 0x1000    # @brief Angle error masks for safety words
CRC_POLYNOMIAL             = 0x1D      # @brief values used for calculating the CRC
CRC_SEED                   = 0xFF      # @brief values used for calculating the CRC
CRC_NUM_REGISTERS          = 0x0008    # @brief number of CRC relevant registers
MAX_REGISTER_MEM           = 0x0030    # @brief max readable register values buffer
MAX_NUM_REG                = 0x16      # @brief defines the value for temporary data to read all readable registers
DELETE_BIT_15              = 0x7FFF    # @brief Value used to delete everything except the first 15 bits
CHANGE_UINT_TO_INT_15      = 0x8000    # @brief Value used to change unsigned 16bit integer into signed
CHECK_BIT_14               = 0x4000    # @brief Value used to check if the 14th bit is set
GET_BIT_14_4               = 0x7FF0    # @brief Value used to get the 14th to 4th bit
DELETE_7BITS               = 0x01FF    # @brief values used to calculate 9 bit signed integer sent by the sensor
CHANGE_UNIT_TO_INT_9       = 0x0200    # @brief Value used to change unsigned 9bit integer into signed
CHECK_BIT_9                = 0x0100    # @brief Value used to check if the 9th bit is set
POW_2_15                   = 32768.0   # @brief values used to for final calculations of angle speed, revolutions, range and value
POW_2_7                    = 128.0     # @brief values used to for final calculations of angle speed, revolutions, range and value
ANGLE_360_VAL              = 360.0     # @brief values used to for final calculations of angle speed, revolutions, range and value
TEMP_OFFSET                = 152.0     # @brief values used to calculate the temperature
TEMP_DIV                   = 2.776     # @brief values used to calculate the temperature

# @brief Error types from safety word
NO_ERROR                   = 0x00      # @brief NO_ERROR = Safety word was OK
SYSTEM_ERROR               = 0x01      # @brief SYSTEM_ERROR = over/under voltage, VDD negative, GND off, ROM defect
INTERFACE_ACCESS_ERROR     = 0x02      # @brief INTERFACE_ACCESS_ERROR = wrong address or wrong lock
INVALID_ANGLE_ERROR        = 0x03      # @brief INVALID_ANGLE_ERROR = NO_GMR_A = 1 or NO_GMR_XY = 1
ANGLE_SPEED_ERROR          = 0x04      # @brief ANGLE_SPEED_ERROR = combined error, angular speed calculation wrong
CRC_ERROR                  = 0xFF      # @brief CRC_ERROR = Cyclic Redundancy Check (CRC), which includes the STAT and RESP bits wrong

# @brief Set the UPDate bit high (read from update buffer) or low (read directly)
UPD_low                    = 0x0000    # @brief read normal registers
UPD_high                   = 0x0400    # @brief read update buffer registers

# @brief Switch on/off safety word generation
SAFE_low                   = 0x0000    # @brief switch of safety word generation
SAFE_high                  = 0x0001    # @brief switch on safety word generation

TLE5012B_S0                = 0x0000    # @brief TLE5012B_S0 default setting for only one sensor on the SPI
TLE5012B_S1                = 0x2000    # @brief TLE5012B_S1 second sensor needs also a second CS pin
TLE5012B_S2                = 0x4000    # @brief TLE5012B_S2 third sensor and ditto
TLE5012B_S3                = 0x6000    # @brief TLE5012B_S3 fourth sensor and ditto

def get_first_byte(two_byte_word):
    return (two_byte_word >> 8) & 0xFF

def get_second_byte(two_byte_word):
    return two_byte_word & 0xFF

def crc8(data, length):
    crc = CRC_SEED
    for i in range(length):
        crc ^= data[i]
        for bit in range(8):
            if (crc & 0x80) != 0:
                crc <<= 1
                crc ^= CRC_POLYNOMIAL
            else:
                crc <<= 1
    return (~crc) & CRC_SEED

def crc_calc(crc_data, length):
    return crc8(crc_data, length)

def calculate_angle_speed(ang_range, raw_angle_speed, fir_md, prediction_val):
    microsec_to_sec = 0.000001
    fir_md_val = {1: 42.7, 2: 85.3, 3: 170.6}.get(fir_md, 21.3)
    dividend = ang_range * raw_angle_speed / (POW_2_15 * microsec_to_sec)
    divisor = prediction_val * fir_md_val
    final_angle_speed = dividend / divisor
    return final_angle_speed

class Tle5012b:
    def __init__(self):
        self.safetyWord = 0
        self.mSlave = TLE5012B_S0
        self.SPI3W = SPI3W(0)
        self.Reg = Reg(self)

    def begin(self, miso='P9_1', mosi='P9_0', sck='P9_2', cs='P9_3', slaveNum=TLE5012B_S0):
        self.SPI3W.begin(miso, mosi, sck, cs)
        self.mSlave = slaveNum
        self.write_slave_number(self.mSlave)
        return self.read_block_crc()

    def read_from_sensor(self, command, upd, safe):
        self._command = [READ_SENSOR | command | upd | safe]
        _received = [0] * MAX_REGISTER_MEM
        self.SPI3W.send_receive(self._command, 1, _received, 2)
        data = _received[0]
        if safe == SAFE_high:
            check_error = self.check_safety(_received[1], self._command[0], _received, 1)
            if check_error != NO_ERROR:
                data = 0
        return check_error, data

    def read_more_registers(self, command, upd, safe):
        self._command = [READ_SENSOR | command | upd | safe]
        _received = [0] * MAX_REGISTER_MEM
        _rec_data_length = self._command[0] & 0x000F
        self.SPI3W.send_receive(self._command, 1, _received, _rec_data_length + safe)
        data = _received[:_rec_data_length]
        if safe == SAFE_high:
            check_error = self.check_safety(_received[_rec_data_length], self._command[0], _received, _rec_data_length)
            if check_error != NO_ERROR:
                data = 0
        return check_error, data

    def write_to_sensor(self, command, data_to_write, change_crc):
        self._command = [WRITE_SENSOR | command | SAFE_high, data_to_write]
        safety = 0
        self.SPI3W.send_receive(self._command, 2, [safety], 1)
        check_error = self.check_safety(safety, self._command[0], [self._command[1]], 1)
        if change_crc:
            check_error = self.regular_crc_update()
        return check_error

    def write_temp_coeff_update(self, data_to_write):
        safety = 0
        self.SPI3W.trigger_update()
        self._command = [WRITE_SENSOR | self.Reg.REG_TCO_Y | SAFE_high, data_to_write]
        self.SPI3W.send_receive(self._command, 2, [safety], 1)
        check_error = self.check_safety(safety, self._command[0], [self._command[1]], 1)
        check_error = self.read_status()
        if self.read_status() & 0x0008:
            check_error = self.regular_crc_update()
        return check_error

    def check_safety(self, safety, command, readreg, length):
        self.safetyWord = safety
        if not (safety & SYSTEM_ERROR_MASK):
            return SYSTEM_ERROR
        elif not (safety & INTERFACE_ERROR_MASK):
            return INTERFACE_ACCESS_ERROR
        elif not (safety & INV_ANGLE_ERROR_MASK):
            return INVALID_ANGLE_ERROR
        else:
            self.reset_safety()
            temp = [get_first_byte(command), get_second_byte(command)]
            for i in range(length):
                temp.extend([get_first_byte(readreg[i]), get_second_byte(readreg[i])])
            crc_received_final = get_second_byte(safety)
            crc = crc_calc(temp, length * 2 + 2)
            if crc == crc_received_final:
                return NO_ERROR
            else:
                self.reset_safety()
                return CRC_ERROR

    def reset_safety(self):
        command = READ_SENSOR + SAFE_high
        receive = [0] * 4
        self.SPI3W.trigger_update()
        self.SPI3W.send_receive([command], 1, receive, 3)

    def regular_crc_update(self):
        self.read_block_crc()
        temp = []
        for i in range(CRC_NUM_REGISTERS):
            temp.extend([get_first_byte(self._registers[i]), get_second_byte(self._registers[i])])
        crc = crc_calc(temp, 15)
        val_to_send = (temp[14] << 8) | crc
        self._registers[7] = val_to_send
        return self.write_temp_coeff_update(val_to_send)

    def read_block_crc(self):
        self._command = [READ_BLOCK_CRC]
        self._registers = [0] * (CRC_NUM_REGISTERS + 1)
        self.SPI3W.send_receive(self._command, 1, self._registers, CRC_NUM_REGISTERS + 1)
        check_error = self.check_safety(self._registers[8], READ_BLOCK_CRC, self._registers, CRC_NUM_REGISTERS)
        self.reset_safety()
        return check_error

    def read_status(self, upd=UPD_low, safe=SAFE_high):
        return self.read_from_sensor(self.Reg.REG_STAT, upd, safe)

    def read_activation_status(self, upd=UPD_low, safe=SAFE_high):
        return self.read_from_sensor(self.Reg.REG_ACSTAT, upd, safe)

    def read_sil(self):
        return self.read_from_sensor(self.Reg.REG_SIL, UPD_low, SAFE_high)

    def read_int_mode1(self):
        return self.read_from_sensor(self.Reg.REG_MOD_1, UPD_low, SAFE_high)

    def read_int_mode2(self):
        return self.read_from_sensor(self.Reg.REG_MOD_2, UPD_low, SAFE_high)

    def read_int_mode3(self):
        return self.read_from_sensor(self.Reg.REG_MOD_3, UPD_low, SAFE_high)

    def read_int_mode4(self):
        return self.read_from_sensor(self.Reg.REG_MOD_4, UPD_low, SAFE_high)

    def read_offset_x(self):
        return self.read_from_sensor(self.Reg.REG_OFFX, UPD_low, SAFE_high)

    def read_offset_y(self):
        return self.read_from_sensor(self.Reg.REG_OFFY, UPD_low, SAFE_high)

    def read_synch(self):
        return self.read_from_sensor(self.Reg.REG_SYNCH, UPD_low, SAFE_high)

    def read_ifab(self):
        return self.read_from_sensor(self.Reg.REG_IFAB, UPD_low, SAFE_high)

    def read_temp_coeff(self):
        return self.read_from_sensor(self.Reg.REG_TCO_Y, UPD_low, SAFE_high)

    def read_temp_dmag(self):
        return self.read_from_sensor(self.Reg.REG_D_MAG, UPD_low, SAFE_high)

    def read_temp_iif_cnt(self):
        return self.read_from_sensor(self.Reg.REG_IIF_CNT, UPD_low, SAFE_high)

    def read_temp_raw(self):
        return self.read_from_sensor(self.Reg.REG_T_RAW, UPD_low, SAFE_high)

    def read_temp_t25(self):
        return self.read_from_sensor(self.Reg.REG_T25O, UPD_low, SAFE_high)

    def read_raw_x(self):
        status, raw_data = self.read_from_sensor(self.Reg.REG_ADC_X)
        if status != NO_ERROR:
            return status, None
        return status, raw_data

    def read_raw_y(self):
        status, raw_data = self.read_from_sensor(self.Reg.REG_ADC_Y)
        if status != NO_ERROR:
            return status, None
        return status, raw_data

    def get_angle_value(self):
        return self.get_angle_value_with_params(UPD_low, SAFE_high)

    def get_angle_value_with_params(self, upd, safe):
        status, raw_data = self.read_from_sensor(self.Reg.REG_AVAL, upd, safe)
        if status != NO_ERROR:
            return status, None, None
        raw_data &= DELETE_BIT_15
        if raw_data & CHECK_BIT_14:
            raw_data -= CHANGE_UINT_TO_INT_15
        raw_angle_value = raw_data
        angle_value = (ANGLE_360_VAL / POW_2_15) * raw_angle_value
        return status, angle_value, raw_angle_value

    def get_temperature(self):
        return self.get_temperature_with_params(UPD_low, SAFE_high)

    def get_temperature_with_params(self, upd, safe):
        status, raw_data = self.read_from_sensor(self.Reg.REG_FSYNC, upd, safe)
        if status != NO_ERROR:
            return status, None, None
        raw_data &= DELETE_7BITS
        if raw_data & CHECK_BIT_9:
            raw_data -= CHANGE_UNIT_TO_INT_9
        raw_temp = raw_data
        temperature = (raw_temp + TEMP_OFFSET) / TEMP_DIV
        return status, temperature, raw_temp

    def get_num_revolutions(self, upd=UPD_low, safe=SAFE_high):
        status, raw_data = self.read_from_sensor(self.Reg.REG_AREV, upd, safe)
        if status != NO_ERROR:
            return status, None
        raw_data &= DELETE_7BITS
        if raw_data & CHECK_BIT_9:
            raw_data -= CHANGE_UNIT_TO_INT_9
        num_rev = raw_data
        return status, num_rev

    def get_angle_speed(self):
        return self.get_angle_speed_with_params(UPD_low, SAFE_high)

    def get_angle_speed_with_params(self, upd, safe):
        num_of_data = 0x7
        status, raw_data = self.read_more_registers(self.Reg.REG_ASPD + num_of_data, upd, safe)
        if status != NO_ERROR:
            return status, None, None
        raw_speed = raw_data[0] & DELETE_BIT_15
        if raw_speed & CHECK_BIT_14:
            raw_speed -= CHANGE_UINT_TO_INT_15
        fir_md = raw_data[3] >> 14
        int_mode2_prediction = 3 if raw_data[5] & 0x0004 else 2
        raw_angle_range = (raw_data[5] & GET_BIT_14_4) >> 4
        angle_range = ANGLE_360_VAL * (POW_2_7 / raw_angle_range)
        final_angle_speed = calculate_angle_speed(angle_range, raw_speed, fir_md, int_mode2_prediction)
        return status, final_angle_speed, raw_speed

    def get_angle_range(self):
        status, raw_data = self.read_int_mode2()
        if status != NO_ERROR:
            return status, None
        raw_data = (raw_data & GET_BIT_14_4) >> 4
        angle_range = ANGLE_360_VAL * (POW_2_7 / raw_data)
        return status, angle_range

    def write_int_mode2(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_MOD_2, data_to_write, True)

    def write_int_mode3(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_MOD_3, data_to_write, True)

    def write_offset_x(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_OFFX, data_to_write, True)

    def write_offset_y(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_OFFY, data_to_write, True)

    def write_synch(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_SYNCH, data_to_write, True)

    def write_ifab(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_IFAB, data_to_write, True)

    def write_int_mode4(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_MOD_4, data_to_write, True)

    def write_temp_coeff(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_TCO_Y, data_to_write, True)

    def write_activation_status(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_ACSTAT, data_to_write, False)

    def write_int_mode1(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_MOD_1, data_to_write, False)

    def write_sil(self, data_to_write):
        return self.write_to_sensor(self.Reg.REG_SIL, data_to_write, False)

    def write_slave_number(self, data_to_write):
        return self.write_to_sensor(WRITE_SENSOR, data_to_write, False)

    def read_reg_map(self):
        self.SPI3W.trigger_update()
        for i in range(MAX_NUM_REG):
            status = self.read_from_sensor(self.Reg.addrFields[i].regAddress, self.Reg.regMap[i], UPD_low, SAFE_high)
        return status

    def write_interface_type(self, iface):
        status, raw_data = self.read_int_mode4()
        if status != NO_ERROR:
            return status
        raw_data &= ~(1 << 0)
        raw_data &= ~(1 << 1)
        raw_data |= iface
        return self.write_int_mode4(raw_data)

    def set_calibration(self, cal_mode):
        status, raw_data = self.read_int_mode2()
        if status != NO_ERROR:
            return status
        raw_data &= ~(1 << 0)
        raw_data &= ~(1 << 1)
        raw_data |= cal_mode
        return self.write_int_mode2(raw_data)