Because of the holidays, TI E2E™ design support forum responses will be delayed from Dec. 25 through Jan. 2. Thank you for your patience.

AFE7900EVM: Power Level Difference for NCO Frequency on AFE7900 (Latte vs Python)

Part Number: AFE7900EVM
Other Parts Discussed in Thread: AFE7900

Tool/software:

Hi,

I am working with the AFE7900 and have noticed a significant difference in output power when setting the NCO frequency for Channel D at 2 GHz using different methods.

  • Using Latte: I get a power level of -27.03 dBm.
  • Using Python: The power level drops to -48.51 dBm.

Below are the SPI commands I used to set the NCO frequency to 2 GHz through Python:

SPIWrite 0012,08,0,7
SPIWrite 00a3,ad,0,7
SPIWrite 00a2,9c,0,7
SPIWrite 00a1,71,0,7
SPIWrite 00a0,c7,0,7
SPIWrite 0180,00,0,7
SPIWrite 0180,01,0,7
SPIWrite 0180,00,0,7
SPIWrite 0012,00,0,7
SPIWrite 0019,80,0,7
SPIWrite 0353,2b,0,7
SPIWrite 0352,67,0,7
SPIWrite 0351,1c,0,7
SPIWrite 0350,72,0,7
SPIWrite 0773,00,0,7
SPIWrite 0773,01,0,7
SPIWrite 0773,00,0,7
SPIWrite 0019,00,0,7

I checked for all the other channels and also observed the difference between power levels. Can anyone help identify what might be causing the power difference between the two methods? Are there any differences in how Latte and Python handle SPI configuration that could explain this?

Thanks in advance for any insights!

Regards,

Bala

  • Hi Bala,

    In Latte are you using the python function, updateTxNco, or applying the direct register writes? Is the power before updating the NCO correct? Can you share the script you are using the configure the AFE?

    I have tested using the exact same register writes for TxD and see no change in amplitude when using the direct register writes. 

    Regards,

    David Chaparro 

  • Hi David,

    In latte, I am using the AFE.updateTxNco() function to update the NCO frequency. The power level was correct before updating the NCO frequency.

    First time I have updated the NCO frequency to 2 GHz using latte(using AFE.updateTxNco()). the power level is -27.03 dBm.

    Now I have restarted the AFE and this time I tried to update the NCO frequency(2 GHz) through python scripts in linux where the python script will take the SPI write file as argument. The power level here is -48.51 dBm. So I think the power level is not correct when updating through the python script.

    I have attached the python script for generating the NCO SPI writes and the python script to update the NCO frequency.

    """
    	@file	  NcoControllerCommandsGenerator.py
    	@brief	  Generates NCO controller commands.
    	@details  This python script is called by the frequency receiver server.
    			  This gets a frequency and a filename as command line arguments.
    			  This then generates NCO register values for Rx and Tx
    			  configuration and writes to the specified file.
    	
    	@param	  [in] NCO_FREQUENCY The frequency for which Tx and Rx NCOs of 
    			  AFE has to be configured.
    	@param	  [in] OUTPUT_FILENAME The filename to which modified register
    			  values have to be written.
    """
    
    from sys import argv
    
    F_ADC = 2949.12
    F_DAC = 2949.12 * 4
    
    TX_PAGE_ADDRESS = 0x19
    TX_MIXER_BASE_ADDRESS = 0x350
    TX_RESET_ADDRESS = 0x773
    
    RX_PAGE_ADDRESS = 0x12
    RX_MIXER_BASE_ADDRESS = 0xa0
    RX_RESET_ADDRESS = 0x180
    
    DATA_LENGTH_IN_BYTES = 4
    
    COMMAND_FORMAT = "SPIWrite {:04x},{:02x},0,7"
    
    CHANNELS = ['A', 'B', 'C', 'D']
    DEFAULT_OPENED_RX_CHANNELS = 0x00	# By default no channels will be opened
    DEFAULT_OPENED_TX_CHANNELS = 0x00	# By default no channels will be opened
    
    def get_afe_config_commands(f_nco, channels_to_be_opened, options):
        """
            @brief    Main function that runs when this file is ran.
            @details  This function modifies some register values for configuring Tx
                      and Rx NCOs of AFE. Those values are got after some calculations
                      on the nco frequency received as command line argument. These 
                      outputs are then written to a file which is also got as second
                      command line argument.
            @return   Returns nothing.
        """
        
        F_NCO = f_nco
        
        ##################
        ##  For Rx NCO  ##
        ##################
        
        opened_rx_channels = DEFAULT_OPENED_RX_CHANNELS
        for channel in CHANNELS:
            if(channel in channels_to_be_opened):
                opened_rx_channels = opened_rx_channels | (0x01 << (ord(channel) - ord(CHANNELS[0])))
        
        F_NCO_RX = F_NCO
        
        # Bringing F_NCO_RX to a value less than or equal to F_ADC (only if F_NCO_RX is greater than F_ADC)
        if(F_NCO_RX > F_ADC):
            if(F_NCO_RX % F_ADC == 0):
                F_NCO_RX = F_ADC
            else:
                F_NCO_RX = F_NCO_RX % F_ADC
                
        #print("F_NCO_RX is %.1f"%(F_NCO_RX))
        
        FCW_DECIMAL = int(round((F_NCO_RX*2**32)/F_ADC))
        #print("FCW_DECIMAL is %d"%(FCW_DECIMAL))
        
        # Commands in the beginning of output file
        rx_commands_start = [
            COMMAND_FORMAT.format(RX_PAGE_ADDRESS, opened_rx_channels),    # Command to open RX channels
        ]
        
        # Commands in between of output file
        rx_commands_between = []
        
        # Commands at the end of output file
        rx_commands_end = [
            COMMAND_FORMAT.format(RX_RESET_ADDRESS, 0x00),    # Commands to reset opened RX channels
            COMMAND_FORMAT.format(RX_RESET_ADDRESS, 0x01),
            COMMAND_FORMAT.format(RX_RESET_ADDRESS, 0x00),
            COMMAND_FORMAT.format(RX_PAGE_ADDRESS, 0x00),    # Command to close opened RX channels
        ]
        
        address = RX_MIXER_BASE_ADDRESS
        
        for i in range(DATA_LENGTH_IN_BYTES):
            data = FCW_DECIMAL & 0xff    # Taking the last byte
            rx_commands_between.append(COMMAND_FORMAT.format(address, data))
            FCW_DECIMAL = FCW_DECIMAL >> 8    # Removing the last byte
            address = address + 1
        
        rx_commands = rx_commands_start + rx_commands_between[::-1] + rx_commands_end
        
        ##################
        ##  For Tx NCO  ##
        ##################
        
        opened_tx_channels = DEFAULT_OPENED_TX_CHANNELS
        for channel in CHANNELS:
            if(channel in channels_to_be_opened):
                opened_tx_channels = opened_tx_channels | (0x10 << (ord(channel) - ord(CHANNELS[0])))
        #print(opened_tx_channels)
        F_NCO_TX = F_NCO
        
        # Bringing F_NCO_TX to a value less than or equal to F_DAC (only if F_NCO_TX is greater than F_DAC)
        if(F_NCO_TX > F_DAC):
            if(F_NCO_TX % F_DAC == 0):
                F_NCO_TX = F_DAC
            else:
                F_NCO_TX = F_NCO_TX % F_DAC
        
        FCW_DECIMAL = int(round((F_NCO_TX*2**32)/F_DAC))
        
        # Commands in the beginning of output file
        tx_commands_start = [
            COMMAND_FORMAT.format(TX_PAGE_ADDRESS, opened_tx_channels),    # Command to open 4 TX channels
        ]
        
        # Commands in between of output file
        tx_commands_between = []
        
        # Commands at the end of output file
        tx_commands_end = [
            COMMAND_FORMAT.format(TX_RESET_ADDRESS, 0x00),    # Commands to reset opened TX channels
            COMMAND_FORMAT.format(TX_RESET_ADDRESS, 0x01),
            COMMAND_FORMAT.format(TX_RESET_ADDRESS, 0x00),
            COMMAND_FORMAT.format(TX_PAGE_ADDRESS, 0x00),    # Command to close opened TX channels
        ]
        
        address = TX_MIXER_BASE_ADDRESS
        
        for i in range(DATA_LENGTH_IN_BYTES):
            data = FCW_DECIMAL & 0xff    # Taking the last byte
            tx_commands_between.append("SPIWrite %04x,%02x,0,7"%(address, data))
            FCW_DECIMAL = FCW_DECIMAL >> 8    # Removing the last byte
            address = address + 1
        
        tx_commands = tx_commands_start + tx_commands_between[::-1] + tx_commands_end
        
        commands = []
        
        if("--skip-rx" not in options):
            commands.extend(rx_commands)
        else:
            print("Skipping rx commands...")
        
        if("--skip-tx" not in options):
            commands.extend(tx_commands)
        else:
            print("Skipping tx commands...")
        
        return commands
    
    if __name__ == "__main__":
        if(len(argv) < 7):
            print("Usage: python NcoControllerCommandsGenerator.py --c -hannels [CHANNEL_NAMES_SEPARATED_WITH_COMMA] --fnco [NCO_FREQUENCY] --of [OUTPUT_FILENAME] [OPTIONS]")
            print("Options: ")
            print("--skip-rx\t-\tTo skip commands for rx channels")
            print("--skip-tx\t-\tTo skip commands for tx channels")
            exit(-1)
        
        channels_to_be_opened = [channel.upper() for channel in argv[argv.index("--channels") + 1].split(",")]
        f_nco = float(argv[argv.index("--fnco") + 1])
        output_filename = argv[argv.index("--of") + 1]
        
        commands = get_afe_config_commands(f_nco, channels_to_be_opened, argv)
        
        with open(output_filename, "w") as file:
            file.write("\n".join(commands))
        
        print("A new file %s has been created with register values for frequency %.1f"%(output_filename, f_nco))

    #!/bin/python
    from sys import argv, stdout
    from time import sleep
    from pyftdi.spi import SpiController
    
    SCHEME = "ftdi"
    VENDOR_ID = 0x0403
    PRODUCT_ID = 0x6011
    INTERFACE = 1
    
    SPI_WRITE = "SPIWrite"
    SPI_READ = "SPIRead"
    SPI_READ_CHECK = "SPIReadCheck"
    SPI_POLL = "SPIPoll"
    WAIT = "WAIT"
    
    SPI_POLL_MAX_COUNT = 100
    
    URL_FMT = "{scheme}://{vendor_id}:{product_id}/{interface}"
    URL = URL_FMT.format(scheme=SCHEME, vendor_id=VENDOR_ID, product_id=PRODUCT_ID, interface=INTERFACE)
    
    def get_afe_spi_handle():
        spi_controller = SpiController()
        spi_controller.configure(url=URL)
        afe_spi_handle = spi_controller.get_port(cs=0, freq=12E6, mode=0)
        
        return afe_spi_handle
    
    def program_afe(afe_spi_handle, spi_commands):
        ## Iterating through each line of commands
        for spi_command in spi_commands:
            
            ## Stripping the string
            spi_command = spi_command.strip()
            
            ## Skip the line if empty
            if(not spi_command):
                continue
            
            if(spi_command.startswith(SPI_WRITE)):
                spi_command_split = spi_command.split()[1].split(",")
                spi_command_bytes = bytes([int(spi_command_split[0][:2], 16), int(spi_command_split[0][2:4], 16), int(spi_command_split[1], 16)])
                afe_spi_handle.write(spi_command_bytes)
                response_bytes = afe_spi_handle.read(1)
                print("[{}] Addr: {:04x} Data: {:02x}".format(SPI_WRITE, spi_command_bytes[0] << 8 | spi_command_bytes[1], spi_command_bytes[2]))
            elif(spi_command.startswith(SPI_READ_CHECK)):
                spi_command_split = spi_command.split()[1].split(",")
                spi_command_bytes = bytes([int(spi_command_split[0][:2], 16) | 1 << 7, int(spi_command_split[0][2:4], 16), int(spi_command_split[3], 16)])
                afe_spi_handle.write(spi_command_bytes[:2])
                response_bytes = afe_spi_handle.read(1)
                print("[{}] Addr: {:04x} Exp: {:02x} Read: {:02x}".format(SPI_READ_CHECK, (spi_command_bytes[0] & ~(1 << 7)) << 8 | spi_command_bytes[1], spi_command_bytes[2], response_bytes[0]))
            elif(spi_command.startswith(SPI_READ)):
                spi_command_split = spi_command.split()[1].split(",")
                spi_command_bytes = bytes([int(spi_command_split[0][:2], 16) | 1 << 7, int(spi_command_split[0][2:4], 16)])
                afe_spi_handle.write(spi_command_bytes)
                response_bytes = afe_spi_handle.read(1)
                print("[{}] Addr: {:04x} Read: {:02x}".format(SPI_READ, (spi_command_bytes[0] & ~(1 << 7)) << 8 | spi_command_bytes[1], response_bytes[0]))
            elif(spi_command.startswith(SPI_POLL)):
                spi_poll_count = SPI_POLL_MAX_COUNT
                spi_command_split = spi_command.split()[1].split(",")
                spi_command_bytes = bytes([int(spi_command_split[0][:2], 16) | 1 << 7, int(spi_command_split[0][2:4], 16), int(spi_command_split[3], 16)])
                response_bytes = None
                print("[{}] Addr: {:04x} Exp: {:02x}".format(SPI_POLL, (spi_command_bytes[0] & ~(1 << 7)) << 8 | spi_command_bytes[1], spi_command_bytes[2]))
                while(spi_poll_count > 0):
                    afe_spi_handle.write(spi_command_bytes[:2])
                    response_bytes = afe_spi_handle.read(1)
                    if(response_bytes[0] == spi_command_bytes[2]):
                        break
                    spi_poll_count = spi_poll_count - 1
                if(spi_poll_count == 0):
                    print("[{}] Failed".format(SPI_POLL))
                else:
                    print("[{}] Success".format(SPI_POLL))
            elif(spi_command.startswith(WAIT)):
                sleep_time_s = float(spi_command.split()[1])
                print("[{}] {}".format(WAIT, sleep_time_s))
                sleep(sleep_time_s)
            else:
                pass
    
    if(__name__ == "__main__"):
        if(len(argv) < 2):
            print("Usage: python3 programAFE.py [LOG FILE]")
            exit(1)
        
        log_file_name = argv[1]
        
        try:
            log_file = open(log_file_name, "r")
            spi_commands = log_file.readlines()
            log_file.close()
            
            afe_spi_handle = get_afe_spi_handle()
            program_afe(afe_spi_handle, spi_commands)
        except FileNotFoundError:
            print("Error: Could not find file \"{}\"".format(log_file_name))
            exit(1)

    Regards,

    Bala

  • Hi David,

    I have tried again setting the NCO frequency through python code. On further testing, I found that the NCO frequency for RX is being set but the NCO frequency for TX is not while using the python code. So please check this on your side and give me a update regarding this.

    Regards,

    Bala

  • Hi Bala,

    One thing that could cause an issue like this is the SPI writes not closing the appropriate pages. Can you share the register sequence you use to program the AFE before running the update NCO writes? 

    Regards,

    David Chaparro 

  • Hi David,

    I have attached the register sequence to program AFE. 

    Regards,AFECommands.txt

    Bala