This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

LMX2572EVM: Programming with the Reference PRO using USB2ANY.dll version 2.7.0

Part Number: LMX2572EVM
Other Parts Discussed in Thread: LMX2572, USB2ANY

Hello, 

I am currently trying to make an automation programming the LMX2572EVM boards through python script.

I got the DLL files from here :SDK link

So far I can do these with the script: 

  1. Detect devices
  2. Retrieve their Serial Numbers
  3. Assign as a U2A_HANDLE
  4. Write hex values to registers with given succesful written Bytes

Even when it returns no error code during writing, it still doesn't change the output of the LMX2572 board. It does when i use the

TICS Pro program to write the registers. 

I think this has to do that I didn't set the u2aSPI_Control parameters correctly for writing. I couldn't retrieve any information through the datasheet or

I missed it by mistake...

Is there any default settings for this function, in order to write to the board?

Greetings, 

Dave

  • Dave,

    Can you share your code for review?

    The SDK link above has an API manual which explains how all the SPI functions work in USB2ANY - this could be consulted for additional information about the u2aSPI_Control parameter; for reference we use CPOL=1 (inactive state high), CPHA=0 (capture on trailing edge), CS Polarity active low, CS after every packet (for USB2ANY < v2.9.1.1), 8-bit SPI, MSB first bit direction, and a standard divider value as specified in the API manual for the various available frequencies.

    For LMX2572, the reset bit should be toggled once, then all registers present in TICS Pro should be written in descending order, highest address to lowest address. R0 should be written with FCAL_EN=1. The last write should wait 10ms before writing R0, to give the internal LDOs a chance to stabilize after the register programming introduces large current transients on the power supply pins.

    Regards,

    Derek Payne

  • Thanks for your reply Derek!

    I adjusted my code and took a few steps back just to simply see if I can write the RESET to those devices.

    But it still gives me a return code of No error while nothing changed to the devices. I hooked it up onto a scope and use an external supply to physcally check if the devices settings changes. Till no still no luck..

    Here is the code:

    import time
    
    import ctypes
    
    def getStatus(code):
        """
        Translate status code to readable text.
        """
        textBuff = ctypes.create_string_buffer(40)
        U2A.u2aStatus_GetText(code, textBuff, 40)
        status = ""
        for char in textBuff:
            status += char.decode("utf-8")
            if char == b'\x00':
                    break
        if code >= 0:
            return status
        print(f"Error: {status}")
        exit(1)
    
    U2A = ctypes.WinDLL(r"./USB2ANY.dll")
    
    SPI_ClockPhase=0        #SPI_Capture_On_Trailing_Edge 
    SPI_ClockPolarity=0     #SPI_Inactive_State_Low
    SPI_BitDirection=1      #SPI_MSB_First
    SPI_CharacterLength=0   #SPI__8_Bit
    SPI_CSType=1            #SPI_With_Every_Packet
    SPI_CSPolarity=0        #SPI_Active_High
    DividerHigh=0
    DividerLow=3           #8000 kbps
    
    NDEVS = U2A.u2aFindControllers()   
    print(f"Amount found devices: {NDEVS}") #check how many devices available.
    
    for dev in range(NDEVS):
        SERIALNUMBER_BUFFER = ctypes.create_string_buffer(40)
        if U2A.u2aGetSerialNumber(dev, SERIALNUMBER_BUFFER) == 0: 
            SERIALNUMBER= ""
            for s in SERIALNUMBER_BUFFER:
                SERIALNUMBER+=s.decode("utf-8")
                if s == b'\x00':
                    break
    
            U2A_HANDLE = U2A.u2aOpen(SERIALNUMBER_BUFFER)
            statusCode = U2A.u2aSPI_Control(
                U2A_HANDLE,
                SPI_ClockPhase,
                SPI_ClockPolarity,
                SPI_BitDirection,
                SPI_CharacterLength,
                SPI_CSType,
                SPI_CSPolarity,
                DividerHigh, 
                DividerLow)
    
            print("Status from u2aSPI_Control: ", getStatus(statusCode))
    
            resetVal = 2
            FCALVal = 4
            RESET = resetVal.to_bytes(3, 'big')
            FCAL_EN = FCALVal.to_bytes(3, 'big')
            print(RESET, FCAL_EN)
            U2A.u2aSPI_WriteAndRead(U2A_HANDLE, 3, RESET) #toggle reset bit once before writing..
            time.sleep(0.01) #sleep 10ms before writing FCAL_EN=1
            U2A.u2aSPI_WriteAndRead(U2A_HANDLE, 3, FCAL_EN) 

  • Thanks Dave.

    SPI_CSPolarity=1 (SPI_Active_Low) should be used.

    You may have fields and registers confused, based on how you're writing RESET and FCAL_EN. Within the register map, each register has an address (the first byte in the three-byte write sequence) and a set of data values (fields mapped to that register address). All fields in a single register address must be written in parallel, including other fields you may not be writing as desired in your example code (MUXOUT_LD_SEL, the mandatory 1 in bits 4 and 13, FCAL_LPFD_ADJ and FCAL_HPFD_ADJ, OUT_MUTE, VCO_PHASE_SYNC_EN, RAMP_EN, ADD_HOLD).

    A more generic definition of a field and register relation might be like the code below. This isn't necessarily code I'd endorse using - it's more to illustrate the field/register relationship. That said, a motivated effort could be made to parse the device INI file in the C:\ProgramData\Texas Instruments\TICS Pro device directories and to generate these field relationships, if one wanted to work with them directly... but that's beyond the scope of example code I'll generate for an E2E response.

    class Field:
        def __init__(self, val, width):
            if width < 1:
                raise ValueError("width must be > 1")
            self.width = width
            if val >= 1<<width:
                raise ValueError(f"{val} larger than {width}-bit field width")
            self._val = val
            
        @property
        def val(self):
            return self._val
            
        @val.setter
        def val(self, value):
            if value < 0 or value >= (1<<self.width)-1:
                raise ValueError(f"{value} larger than {self.width}-bit field width")
            self._val = value
        
    class Register:
        def __init__(self, address, address_offset=16, width=24, endian='big'):
            self.address = address
            self.address_offset = address_offset
            self._addrbits = address << address_offset
            self._datamask = (1<<address_offset)-1
            self.fields = []
            self.width = width
            self._bytes = (width+7)>>3
            self.endian = endian
        
        def add_field(self, field, offset, lsb=0):
            self.fields.append({
                'field' : field,
                'offset': offset,
                'lsb'   : lsb
            })
        
        @property
        def val(self):
            value = self._addrbits
            for f in self.fields:
                value |= ((f['field'].val>>f['lsb'])<<f['offset'])&self._datamask
            return value
    
        @property
        def bytes(self):
            return self.val.to_bytes(self._bytes, self.endian)
            
    # example: R0
    RAMP_EN = Field(0, 1)
    VCO_PHASE_SYNC_EN = Field(0, 1)
    _R0_13_12_RSVD = Field(2, 2)
    ADD_HOLD = Field(0, 1)
    _R0_10_RSVD = Field(0, 1)
    OUT_MUTE = Field(1, 1)
    FCAL_HPFD_ADJ = Field(0, 2)
    FCAL_LPFD_ADJ = Field(0, 2)
    _R0_4_RSVD = Field(1, 1)
    FCAL_EN = Field(1, 1)
    MUXOUT_LD_SEL = Field(1, 1)
    RESET = Field(0, 1)
    POWERDOWN = Field(0, 1)
    R0 = Register(0)
    R0.add_field(RAMP_EN, 15)
    R0.add_field(VCO_PHASE_SYNC_EN, 14)
    R0.add_field(_R0_13_12_RSVD, 12)
    R0.add_field(ADD_HOLD, 11)
    R0.add_field(_R0_10_RSVD, 10)
    R0.add_field(OUT_MUTE, 9)
    R0.add_field(FCAL_HPFD_ADJ, 7)
    R0.add_field(FCAL_LPFD_ADJ, 5)
    R0.add_field(_R0_4_RSVD, 4)
    R0.add_field(FCAL_EN, 3)
    R0.add_field(MUXOUT_LD_SEL, 2)
    R0.add_field(RESET, 1)
    R0.add_field(POWERDOWN, 0)
    
    print(R0.val) # should print 8732 (0x00221C)
    print(R0.bytes) # should print b'\x00"\x1c'
    #U2A.u2aSPI_WriteAndRead(U2A_HANDLE, 3, R0.bytes)
    
    # example 2: multi-byte field
    PLL_N = Field(0x28, 19)
    _R34_15_3_RSVD = Field(2, 13)
    R36 = Register(36)
    R34 = Register(34)
    R36.add_field(PLL_N, 0)
    R34.add_field(PLL_N, 0, lsb=16)
    R34.add_field(_R34_15_3_RSVD, 3)
    
    # demonstrating write works as expected
    PLL_N.val = 0x5BABE
    print(R34.val, R36.val) # should print 2228245 2407102 (0x220015, 0x24BABE)
    
    # example 3: converting HexRegisterFiles to writable byte arrays
    with open("HexRegisterValues.txt",'r') as f:
        lines = f.readlines()
    regmap = []
    for line in lines:
        regmap.append(int(line.split('\t')[1][2:], 16).to_bytes(3,'big'))
    
    # for reg in regmap:
    #     U2A.u2aSPI_WriteAndRead(U2A_HANDLE, 3, reg)

    I'm not sure if there's more programming omitted for the sake of clarity, but the LMX2572 does require programming additional registers to function. Default values for dividers, VCO calibration settings, etc are not usable - certain values must be programmed to values differing from defaults. TICS Pro tool can be used to generate the full hex register values (File -> Export Hex Registers) for programming all fields in the proper order. The hex register values would be programmed in between the RESET and FCAL_EN writes. I included an example in the code above to explain how the hex register values might be parsed and written.

    Regards,

    Derek Payne

  • Thanks Derek!!!

    It works and you explained it very well how the Fields and Registers work. I adjusted my code and I think the intial error was indeed the first RESET before writing the registers.It seems also that I can write one device only even when I give another handle value. Sometimes it doesn't even program at all and get stuck, unless I program it first with the TICS Pro application. I noticed that the applications forces the user to switch device/interface before programming and thus cannot program all connected devices at once. Here is the code so far: 

    import ctypes
    import time
    class Register():
        def __init__(self, address) -> None:
            self.address = address
            self.regmap = {}
    
        def addField(self, bit, field, val):
            self.regmap[field] = {  "bit":bit,
                                    "val":val}
    
        def getValue(self):
            mask = self.address << 16
            for field in list(self.regmap.keys()):
                mask = mask | (self.regmap[field]["val"]<<self.regmap[field]["bit"])
    
            return mask
        def getByte(self):
            return self.getValue().to_bytes(3, 'big')
    
        def getHex(self):
            return hex(self.getValue())
    
    class U2ADEV():
        def __init__(self, filepath) -> None:
            self.U2A = ctypes.WinDLL(filepath)
            self.SNList = [] #Serial numbers from devices
    
        def getSerialNumbers(self):
            ndevs = self.U2A.u2aFindControllers()   
            for dev in range(ndevs):
                SERIALNUMBER_BUFFER = ctypes.create_string_buffer(40)
                if self.U2A.u2aGetSerialNumber(dev, SERIALNUMBER_BUFFER) == 0: 
                    for i,c in enumerate(SERIALNUMBER_BUFFER,0):
                        if c == b'\x00':
                            self.SNList.append(SERIALNUMBER_BUFFER[:i])
                            break                     
        
        def setupSPI(self, U2A_HANDLE):
            SPI_ClockPhase=0        #SPI_Capture_On_Trailing_Edge 
            SPI_ClockPolarity=0     #SPI_Inactive_State_Low
            SPI_BitDirection=1      #SPI_MSB_First
            SPI_CharacterLength=0   #SPI__8_Bit
            SPI_CSType=1            #SPI_With_Every_Packet
            SPI_CSPolarity=1        #SPI_Active_Low
            DividerHigh=0
            DividerLow=3
            self.U2A.u2aSPI_Control(U2A_HANDLE,
                                    SPI_ClockPhase,
                                    SPI_ClockPolarity,
                                    SPI_BitDirection,
                                    SPI_CharacterLength,
                                    SPI_CSType,
                                    SPI_CSPolarity,
                                    DividerHigh, 
                                    DividerLow)
    
        def getStatus(self,code):
            """
            Translate status code to readable text.
            """
            textBuff = ctypes.create_string_buffer(40)
            self.U2A.u2aStatus_GetText(code, textBuff, 40)
            status = ""
            for char in textBuff:
                status += char.decode("utf-8")
                if char == b'\x00':
                        break
            if code >= 0:
                return status
            print(f"Error: {status}")
    
        def writeHexFile(self,SERIAL, filePath):
            U2A_HANDLE = self.U2A.u2aOpen(SERIAL)
            if U2A_HANDLE < 0:
                self.getStatus(U2A_HANDLE)
                exit(1)
    
            print(f"Opened: {SERIAL.decode('utf-8')}")
            hexData = open(filePath, "r")
            for line in hexData.readlines():
                datamap = line.split('\t')
                address = datamap[0]
                strRegMap = datamap[1].rstrip()
                regMap = int(strRegMap, 16).to_bytes(3,'big')
                if address == "R0": #sleep 10ms before writing R0
                    time.sleep(0.01)
                ret = self.U2A.u2aSPI_WriteAndRead(U2A_HANDLE, 3, regMap)
                print(f"Serial: {SERIAL.decode('utf-8')}\tAddress: {address}\tregMap: {strRegMap}\tByteSuccess: {ret}")
            self.U2A.u2aClose(U2A_HANDLE)
            print(f"Closed: {SERIAL.decode('utf-8')}")
    
        def writeRegisterMap(self, SERIAL, regmap):
            U2A_HANDLE = self.U2A.u2aOpen(SERIAL)
            if U2A_HANDLE < 0:
                self.getStatus(U2A_HANDLE)
                exit(1)
    
            hexRegMap = regmap.hex()
            address = int.from_bytes(regmap[:1], 'big')
            
            print(f"Opened: {SERIAL.decode('utf-8')}")
            ret = self.U2A.u2aSPI_WriteAndRead(U2A_HANDLE, 3,regmap) 
            print(f"Serial: {SERIAL.decode('utf-8')}\tAddress: R{address}\tregMap: 0x{hexRegMap}\tByteSuccess: {ret}")
            self.U2A.u2aClose(U2A_HANDLE)
            print(f"Closed: {SERIAL.decode('utf-8')}")
    
    
    
    R0 = Register(0)
    R0.addField(15, "RAMP_EN", 0)
    R0.addField(14, "VCO_PHASE_SYNC_EN", 0)
    R0.addField(12, "_R0_13_12_RSVD", 2)
    R0.addField(11, "ADD_HOLD", 0)
    R0.addField(10, "_R0_10_RSVD", 0)
    R0.addField(9, "OUT_MUTE", 1)
    R0.addField(7, "FCAL_HPFD_ADJ", 0)
    R0.addField(5, "FCAL_LPFD_ADJ", 0)
    R0.addField(4,"_R0_4_RSVD", 1)
    R0.addField(3, "FCAL_EN", 1)
    R0.addField(2,"MUXOUT_LD_SEL", 1)
    R0.addField(1, "RESET", 1)
    R0.addField(0, "POWERDOWN", 0)
    R0_RESET = R0.getByte()
    
    # _R0 = Register(0)
    # _R0.addField(15, "RAMP_EN", 0)
    # _R0.addField(14, "VCO_PHASE_SYNC_EN", 0)
    # _R0.addField(12, "_R0_13_12_RSVD", 2)
    # _R0.addField(11, "ADD_HOLD", 0)
    # _R0.addField(10, "_R0_10_RSVD", 0)
    # _R0.addField(9, "OUT_MUTE", 1)
    # _R0.addField(7, "FCAL_HPFD_ADJ", 0)
    # _R0.addField(5, "FCAL_LPFD_ADJ", 0)
    # _R0.addField(4,"_R0_4_RSVD", 1)
    # _R0.addField(3, "FCAL_EN", 1)
    # _R0.addField(2,"MUXOUT_LD_SEL", 1)
    # _R0.addField(1, "RESET", 0)
    # _R0.addField(0, "POWERDOWN", 0)
    # R0_FCAL_EN = _R0.getByte()
    
    R44 = Register(44)
    R44.addField(14, "_R44_15_RSVD", 0)
    R44.addField(8, "OUTA_PWR", 50)
    R44.addField(7, "OUTB_PD", 1)
    R44.addField(6, "OUTA_PD", 0)
    R44.addField(5, "MASH_RESET_N", 1)
    R44.addField(3, "_R44_15_RSVD", 0)
    R44.addField(0, "MASH_ORDER", 3)
    R44_MAP = R44.getByte()
    
    U2A = U2ADEV(r"./USB2ANY.dll")
    U2A.getSerialNumbers()
    dev0 = U2A.SNList[1]
    U2A.writeRegisterMap(dev0, R0_RESET) #Reset before writing
    # U2A.writeRegisterMap(dev0, R44_MAP) #adjusting output power channel A
    U2A.writeHexFile(dev0, r"./HexRegisterValuesScope.txt")
    

  • Dave,

    Glad it's working (at least better than before).

    TICS Pro doesn't really have a concept of multiple devices on the same bus (at this time). On the other hand, USB2ANY should support multiple devices on the same bus (as long as the ReadAndWriteEx API function is used to distinguish the chip selects) or multiple USB2ANY handles open simultaneously (both devices need their SPI buses initialized).

    I would suggest making your ctypes.WinDLL reference a class attribute instead of an instance attribute. Having multiple instances of the DLL wrapper may also cause some strange behavior, but I put a low likelihood on this.

    Regards,

    Derek Payne

  • Thanks again Derek!

    I swapped out the TI boards with another one and it seems working fine. I noticed that the

    u2aSPI_WriteAndRead adjust the bytes you pass, since it is by reference. For multiple boards you should adjust the byte again or copy by a pre defined byte.

    I also assume there might be an issue with that particular board, maybe the firmware. The original question of this thread has been resolved :) 

    Thanks again,

    Dave