AD9361 SPI Configuration and Debugging: Register Timing, Zynq Code, and Common Issues

AD9361 SPI Configuration and Debugging: Register Timing, Zynq Code, and Common Issues

Complete AD9361 SPI debugging guide. Covers 10-bit vs 16-bit address modes, CS timing, clock polarity, Zynq PS driver code, initialization sequence, and troubleshooting for cp ovrg high, RX PLL unlock, and 0x247 register anomalies.

If you've worked with the AD9361, chances are you've been tormented by register configuration. This RF transceiver chip delivers solid performance, but the initialization sequence for hundreds of registers, SPI timing details, and PLL lock conditions mean any single issue can keep you debugging late into the night. What makes it worse is that many problems that appear to be "registers not configured correctly" actually stem from the SPI link itself — wrong command word format, incorrect chip-select timing, or ignored readback verification. Any of these details will manifest as "wrote but no response" or "readback values make no sense."

This article starts from the SPI protocol fundamentals and walks through complete AD9361 configuration on the Zynq PS side using C code, exposing the pitfalls and debugging paths I've encountered in real projects. It's suitable for those working on software-defined radio, engineers who just got an AD9361 evaluation board, and those who have been configuring for ages but still can't figure out why readback never matches. I'll share verified code, debugging methods, and complete troubleshooting paths for several high-frequency errors (cp ovrg high, RX PLL unlock, 0x247 register anomaly) to help you avoid detours.

1. AD9361 SPI Communication Protocol: From Frame Format to Timing

1.1 10-bit vs. 16-bit Address: Know Which Mode You're Using

The AD9361 register address space spans 0x000 to 0x3FF, but the SPI interface supports two address modes: 10-bit and 16-bit. The chip defaults to 10-bit address mode on power-up. In 10-bit mode, a single SPI frame requires three bytes: the first byte is the command word with bit7 as the read/write direction, followed by the upper 4 bits of the address; the second byte holds the lower 6 bits of the address (shifted left by 2, with the lower 2 bits zeroed); the third byte is the actual register data to read or write.

This sounds convoluted, but a table makes it clear:

Byte Bit Definition Value Calculation
Byte 1 bit7=R/W, bit6-5=multi-byte control, bit4=UB, bit3-0=A[9:6] (R/W << 7)
Byte 2 bit7-2=A[5:0], bit1-0=0 (addr & 0x3F) << 2
Byte 3 Write: value to write; Read: send any value, readback in rx[2] N/A

16-bit address mode is enabled by writing to register 0x0A2. After switching, the lower 6 bits of the command word carry A[13:8], and the second byte directly carries A[7:0]. Many official examples and Linux drivers default to 16-bit address mode, so when you see code where the second byte is written as reg_addr & 0xFF, don't be surprised — the chip has already been switched to 16-bit mode. If you copy that code but forget to switch modes after power-up, all high-address register accesses will be scrambled, manifesting as "writes have no effect" or "readback values are nonsensical."

Strong recommendation: If writing code from scratch, use 10-bit address mode directly — just calculate the second byte correctly yourself. If reusing someone else's code, first confirm which mode the chip is currently in, then decide whether to keep the 0x0A2 switch statement. The most common pitfall: someone else's code writes a value to 0x0A2 that switches the chip to 16-bit address mode. You think that line is redundant and delete it — and suddenly all registers above 0x3F become inaccessible or corrupted.
Warning: Never confuse the read and write direction bit (bit7). Read is 0, write is 1. Many people migrating code fill in this direction bit backwards, resulting in the bizarre phenomenon of "a read operation becoming a write operation" — register values get overwritten without you even knowing.

1.2 Read/Write Timing and Chip Select: Why Readback Data Is Always Wrong

The AD9361's SPI protocol is similar to ordinary Flash chips, but several details must be precise. First is chip select (CS). The CS low level must cover the entire transfer frame: from before the first bit of the first byte to after the last bit of the last byte. If CS glitches mid-frame, or if SCLK shows glitches while CS is low, the chip's internal state machine may get stuck in the middle, causing the entire frame to be discarded — and register readback will forever return the previous value or default.

In actual debugging, I once encountered a very strange phenomenon: write a register, read it back immediately, and the value is correct; wait a few seconds and read again, and it reverts to the original default value. After extensive troubleshooting, I found the CS trace was too long, generating ground bounce noise during SPI clock switching, which caused the SPI slave to reset erroneously during transfer. After shortening the CS trace, adding a 10 kΩ pull-up resistor, and adding a 2 µs delay in software after register writes, the problem disappeared. This board taught me a lesson: although the AD9361's SPI speed isn't particularly high, its signal quality requirements are not low.

The second detail is clock polarity and phase. The SPI timing diagram in the AD9361 datasheet effectively requires CPOL=0 and CPHA=0 — SCLK idle low, data sampled on the rising edge. If you use the Zynq PS-side SPI controller, the default Mode 0 is correct. If you've been working with Flash chips and are used to Mode 3 (CPOL=1, CPHA=1), and you don't change it when migrating, you'll see "data appears to be sent, but the chip receives nothing" — because the SCLK idle level is inverted, and the slave's sampling instants are all wrong.

The third detail is data bit order. The AD9361 supports both MSB-first and LSB-first transmission, with MSB-first as default. Many beginners debugging discover the first byte's R/W bit is wrong and suspect a broken chip — but it's likely that a value with the LSB_FIRST bit was written to 0x0A2, changing the data order. Before modifying 0x0A2, be sure to read back the current value of this register and use a read-modify-write approach rather than overwriting it wholesale.

1.3 Hardware vs. Software Chip Select: Common Zynq PS Pitfalls

When driving the AD9361 from the Zynq PS side, the SPI controller offers two chip-select methods: hardware automatic chip select and GPIO software chip select. The Xilinx SPI controller supports hardware automatic chip select, which automatically pulls CS low before each transfer and high after. This sounds convenient, but in practice, I strongly recommend using GPIO manual chip select for AD9361 debugging rather than relying on XSpi's automatic chip select.

There are two reasons. First, the AD9361 requires all bytes to be transmitted continuously while CS is low, with no excessively long idle gaps. XSpi's hardware automatic chip select may show CS glitches between multi-byte transfers in some driver versions. Although in theory a single XSpi_Transfer call maintains CS low, if the SCLK baud rate is relatively high (e.g., above 10 MHz), the hardware chip-select pin's switching delay can produce visible CS glitches on an oscilloscope. Second, using GPIO manual CS control allows you to give SCLK a stable setup time before pulling CS low, and give the chip some processing time after pulling CS high — extremely important for an analog RF chip as sensitive to SPI timing as the AD9361.

GPIO chip-select control has another hidden benefit: when troubleshooting, you can create timing arbitrarily. For example, pull CS low without transmitting and observe whether the chip can stably read back its ID; or send a half-length illegal command to test the robustness of the chip's SPI state machine. This is especially useful during product development, because you can force out many hidden problems.

2. Initialization Sequence and Key Registers: Out of Order, All for Nothing

2.1 SPI Reset and Register 0x0A2: Prerequisites for All Configuration

After the AD9361 powers up, the first thing is not to rush into configuring RF parameters, but to bring the SPI interface into a defined state. The recommended approach: write 0x01 to register 0x0A0 for a SPI soft reset (see UG-671 for specific bit definitions), wait at least 10 ms, then configure register 0x0A2. Register 0x0A2 is the SPI configuration register, containing key bits such as 16-bit address mode enable, LSB/MSB order, and multi-byte transfer type.

The importance of this step is underestimated by many. Some engineers skip the reset and write 0x0A2 directly, and the chip responds with unpredictable behavior: all registers can be read but not written, or writing one register affects adjacent registers. In such cases, don't suspect a broken chip — first perform a SPI soft reset to bring the SPI interface back to a known state.

I once encountered a situation where, after a firmware upgrade, the initialization code included a fast-configuration block (merging what were originally step-by-step register writes into a multi-byte continuous write). As a result, the chip behaved completely incorrectly after startup. Investigation revealed that on first power-up, the SPI state machine's power-on defaults did not match expectations, requiring a soft reset at the very beginning of initialization followed by reconfiguring 0x0A2 — after which subsequent multi-byte continuous writes worked properly. Since then, I've made "SPI soft reset + reconfigure 0x0A2" a fixed first step of AD9361 initialization, never skipping it regardless of the scenario.

2.2 Clock Chain: Reference Clock, BBPLL, and RF PLL Dependencies

The AD9361 has three main clock chains internally: the reference clock, the baseband PLL (BBPLL), and the RF PLL (RX LO, TX LO). Their configuration order must proceed from the reference clock upward, level by level — it cannot be reversed.

After the reference clock (e.g., a 40 MHz TCXO) enters, it passes through dividers and multipliers before feeding the BBPLL. The BBPLL generates the baseband sampling clock. The RF PLL uses the reference clock as its reference to generate the RF local oscillator frequency. If you write the RX LO registers first while the reference clock isn't configured yet, the PLL has no usable reference frequency and lock will certainly fail. This causal relationship is like building a house: if the foundation isn't solid, no matter how beautiful the upper structure, it will collapse.

Practical troubleshooting method: If you find RX PLL won't lock, don't rush to adjust the charge pump — first confirm the reference clock path. Use an oscilloscope to check the CLK_OUT pin (if brought out) to see whether the frequency is as expected, or check the PLL lock status register to see whether BBPLL locked first. Many PLL problems ultimately trace back to the reference clock not running at all, or a divider ratio written incorrectly. I recommend encapsulating each step of the clock chain configuration into a separate function, reading back the corresponding status register after each step to confirm — rather than writing all registers at once and checking at the end. That way, any problem can be quickly localized to which clock stage failed.

2.3 Filters and Calibration: Must Be Done After PLL Lock

The AD9361 contains many analog calibrations, such as ADC calibration, TIA calibration, DC offset calibration, and quadrature calibration. These calibrations depend on stable clocks and PLLs, so they must be triggered only after PLL lock succeeds. Otherwise, calibration results are unreliable and may even write calibration parameters into the wrong domain, causing subsequent RF performance anomalies.

Many engineers transitioning from pure digital systems tend to overlook this: they habitually write all registers in sequence and check status at the end. But the AD9361's initialization is a strict chain of causal events — operations like calibration, if executed before the previous state is ready, will contaminate the subsequent state. For example, if DC offset calibration is executed before the LO has stabilized, the calibration loop may converge the mixer DC component to an incorrect value, and no amount of gain adjustment afterward can correct it — only recalibration.

So my recommendation is: break the initialization sequence into several stages, with explicit waits and status checks between each stage:

  1. Power-on delay + SPI soft reset
  2. Configure SPI mode (0x0A2)
  3. Configure reference clock + check BBPLL lock
  4. Configure RX/TX LO + check RF PLL lock
  5. Configure baseband filters and data interface
  6. Trigger calibration + wait for completion
  7. Finally write AGC, gain tables, and other runtime parameters

Don't stuff all registers into one continuous write function just to save effort — when something goes wrong, you won't know which step caused it. Staged initialization may look verbose, but debugging efficiency is much higher.

3. High-Frequency Pitfalls: cp ovrg high, RX PLL Unlock, and 0x247 Register Anomaly

3.1 Three Troubleshooting Paths for cp ovrg high

"cp ovrg high" translates to charge pump overload indication. The charge pump is the module in the PLL that converts the phase detector's digital error into analog current. When the VCO tuning voltage is pushed near the supply rail, the charge pump enters an overload state, and the PLL loop cannot function properly — manifesting as lock failure.

When encountering cp ovrg high, troubleshoot along these three paths. The first path is the VCO calibration result. The AD9361's RF PLL contains multiple VCO bands, and the chip automatically calibrates to select the correct band. If the LO frequency falls outside the VCO tuning range, the calibration result will be abnormal and the charge pump will be pushed into overload. The check method is to decompose the LO frequency into integer and fractional divisions, confirming the final value falls within the chip's supported range. The second path is the charge pump current setting. If set too high, the VCO tuning voltage rapidly hits the rail; if set too low, the VCO tuning voltage can't stabilize — both trigger the overload flag. This value usually must be calculated in conjunction with loop filter parameters; directly copying someone else's initialization values may fail because your board's loop filter differs. The third path is reference clock quality. If the reference clock has significant jitter or frequency deviation, the phase detector will repeatedly output abnormal pulses, effectively increasing the charge pump's average current and also triggering overload.

Real-world case: During a test using an external signal generator as the reference clock, I encountered a situation where the signal source output amplitude was slightly low (should have been 0 dBm but was only -10 dBm), and the AD9361's PLL showed cp ovrg high. I spent a long time checking software before finally measuring the reference clock with a power meter and discovering the reference source was the problem. So when you see this error, don't rush to change software — confirm the reference clock's amplitude and waveform first.

3.2 RX PLL Not Locking: Localization Workflow

RX PLL not locking often appears together with cp ovrg high, but sometimes appears alone. My localization workflow is as follows: first, read the PLL lock status register to determine whether it's "never locked" or "locked momentarily then dropped." "Never locked" usually means a reference clock problem, VCO calibration failure, or incorrect PLL switch sequence. "Locked momentarily then dropped" is more likely a loop stability issue, such as a mismatch between charge pump current and loop filter.

Second, check the LO frequency and reference clock divider ratio you configured. In the AD9361's PLL registers, the calculation of integer and fractional division values directly determines the VCO output frequency. In one project, I encountered a case where the fractional division was calculated as 0 because a variable in the code was an integer type and the fractional part was truncated — causing the PLL output frequency to be off by a margin and naturally fail to lock. This kind of problem has nothing to do with RF itself; it's purely a C language type conversion pitfall. If you find the LO frequency doesn't match the configured value, look for type truncation in the calculation code first.

Third, confirm the configuration order. Some registers automatically trigger a VCO calibration inside the chip after being written. If you write other PLL registers during its calibration process, the calibration result may be corrupted and the PLL won't lock. This situation is especially likely to occur on the Zynq PS side if SPI transfers are interrupted. The solution is to disable all interrupts during configuration of PLL-related registers, or simply complete initialization in a non-interrupt context.

3.3 Root Cause Analysis: Register 0x247 Always Reads 0x80

Register 0x247 is used for status queries in many reference codes. Many people debugging initialization find that no matter how they configure it, readback is always 0x80. In such cases, my first reaction is not to look up this register's function, but to check the correctness of the SPI communication itself.

The first possibility is an addressing error. If you're using 10-bit address mode but the copied code was written for 16-bit address mode, then the 0x247 address is wrong from the command byte onward. What you're actually reading may be another register's value, and 0x80 happens to be that register's power-on default. The verification method is simple: switch to a register with a known default value, such as the chip ID register, and see whether the ID read matches expectations. If even the ID is wrong, it's basically an SPI address mode mismatch.

The second possibility is that the read/write direction is reversed. The command word's bit7 — write is 1, read is 0 — must never be reversed. If the direction is reversed, you're actually writing 0x80 to register 0x247, and readback naturally still returns 0x80. This error is extremely subtle, because the functional manifestation is simply "reading a fixed value" — you're unlikely to immediately think of the direction bit.

The third possibility is that this register is read-only, and your attempt to write it is simply ignored by the chip, so it always returns the power-on default. In this case, check the register table in UG-671 to confirm the register attributes. Don't crudely assume "readback doesn't match expectations means the chip is broken" — first go back to the datasheet to confirm the read attributes.

The fourth possibility is a timing issue. If CS isn't strictly pulled low and high per frame, data gets parsed as an incorrect frame, and the readback byte is a random value that happens to stabilize at 0x80. This situation is the most subtle and requires a logic analyzer to capture waveforms for confirmation. I'll elaborate in the debugging methods section below.

4. Complete Zynq PS-Side Code: From SPI Driver to Register Read/Write Functions

4.1 PS-Side SPI Driver Initialization

To initialize SPI on the Zynq PS side, use the Xilinx SDK's XSpi driver. The core steps are: look up the device configuration, initialize, set to Master mode, disable interrupts (to avoid interruption during debugging), then configure GPIO chip select. The code can be copied directly — just modify the pin numbers and SPI device ID.

#include "xspi.h"
#include "xgpiops.h"
#include "sleep.h"

#define AD9361_SPI_DEVICE_ID   XPAR_XSPI_0_DEVICE_ID
#define AD9361_SPI_CS_PIN      55      /* Adjust based on your MIO or EMIO pin */
#define AD9361_SPI_WRITE_CMD   0x80
#define AD9361_SPI_READ_CMD    0x00

static XSpi      SpiInst;
static XGpioPs   GpioInst;

int ad9361_spi_init(void)
{
    XSpi_Config     *spi_cfg;
    XGpioPs_Config  *gpio_cfg;

    /* GPIO initialization: control chip select */
    gpio_cfg = XGpioPs_LookupConfig(XPAR_XGPIOPS_0_DEVICE_ID);
    if (!gpio_cfg) return XST_FAILURE;
    XGpioPs_CfgInitialize(&GpioInst, gpio_cfg, gpio_cfg->BaseAddr);
    XGpioPs_SetDirectionPin(&GpioInst, AD9361_SPI_CS_PIN, 1);
    XGpioPs_SetOutputEnablePin(&GpioInst, AD9361_SPI_CS_PIN, 1);
    XGpioPs_WritePin(&GpioInst, AD9361_SPI_CS_PIN, 1); /* CS high = not selected */

    /* SPI controller initialization */
    spi_cfg = XSpi_LookupConfig(AD9361_SPI_DEVICE_ID);
    if (!spi_cfg) return XST_FAILURE;
    XSpi_CfgInitialize(&SpiInst, spi_cfg, spi_cfg->BaseAddress);
    XSpi_SetOptions(&SpiInst, XSP_MASTER_OPTION | XSP_MANUAL_SS_OPTION);
    XSpi_Start(&SpiInst);
    XSpi_IntrGlobalDisable(&SpiInst);

    return XST_SUCCESS;
}

Several points require special attention. First, the XSP_MANUAL_SS_OPTION option is important — it indicates that chip select is controlled by software rather than hardware automatically. If you don't add this option, XSpi will automatically pull CS high and low according to its internal chip-select logic, conflicting with your own GPIO-controlled CS. Second, to be safe, initialize CS to high (not selected) to prevent accidental triggering at power-up. Third, start with an SPI baud rate of 1 MHz, and gradually increase after communication is verified. The AD9361's SPI can run up to tens of MHz, but with poor board routing, high speeds are prone to errors. Verify communication at low speed first, then increase — this is the most basic and most effective debugging strategy.

4.2 Register Read/Write Function Encapsulation and Fault Tolerance

The read/write functions are the foundation of the entire AD9361 driver. I encapsulated three functions: low-level transfer, single-byte read, and single-byte write. Note that the code below is written for 16-bit address mode — the command word's lower 6 bits hold the address's upper bits, and the second byte directly holds the address's lower 8 bits. If you haven't switched to 16-bit mode on your board, change the second byte to (reg_addr & 0x3F) << 2.

static int ad9361_spi_transfer(uint8_t *tx, uint8_t *rx, int len)
{
    int status;
    XGpioPs_WritePin(&GpioInst, AD9361_SPI_CS_PIN, 0);  /* Pull CS low */
    status = XSpi_Transfer(&SpiInst, tx, rx, len);
    while (XSpi_IsBusy(&SpiInst));                      /* Wait for transfer complete */
    XGpioPs_WritePin(&GpioInst, AD9361_SPI_CS_PIN, 1);  /* Pull CS high */
    return status;
}

uint8_t ad9361_spi_read(uint16_t reg_addr)
{
    uint8_t tx[3] = {0};
    uint8_t rx[3] = {0};

    tx[0] = AD9361_SPI_READ_CMD | ((reg_addr >> 8) & 0x3F);
    tx[1] = reg_addr & 0xFF;
    tx[2] = 0x00;

    ad9361_spi_transfer(tx, rx, 3);
    return rx[2];
}

int ad9361_spi_write(uint16_t reg_addr, uint8_t value)
{
    uint8_t tx[3] = {0};
    uint8_t rx[3] = {0};

    tx[0] = AD9361_SPI_WRITE_CMD | ((reg_addr >> 8) & 0x3F);
    tx[1] = reg_addr & 0xFF;
    tx[2] = value;

    return ad9361_spi_transfer(tx, rx, 3);
}

Several details in the read/write functions are worth mentioning. First, during a read operation, the third byte sends 0x00, and rx[2] is used as the return value — this is determined by SPI's full-duplex nature: the master must send clocks for the slave to send data back on SDO. Second, after a write operation, immediate readback is not recommended, especially for PLL and calibration-related registers — give the chip some processing time, and preferably add a short delay like usleep(5) in the code. Third, all SPI functions should have timeout protection to prevent XSpi_IsBusy from hanging. The code above omits timeouts for clarity, but production code must include them.

4.3 A Reusable Initialization Sequence

The initialization sequence should not stuff all registers into one function. Instead, break it into sub-functions by functional module, each with a readback verification. The following macro is a powerful troubleshooting tool:

#define CHECK_REG(addr, expected) do { \
    uint8_t val = ad9361_spi_read(addr); \
    if (val != (expected)) { \
        printf("REG 0x%03X check fail: expect 0x%02X, got 0x%02X\n", \
               (addr), (expected), val); \
        return -1; \
    } \
} while (0)

The initialization main flow is roughly as follows:

int ad9361_init(void)
{
    /* 1. Power-on stabilization */
    usleep(20000);

    /* 2. SPI soft reset */
    ad9361_spi_write(0x0A0, 0x01);
    usleep(20000);

    /* 3. Configure SPI mode, switch to 16-bit address mode */
    ad9361_spi_write(0x0A2, 0x20);

    /* 4. Configure reference clock (example: based on actual board reference frequency) */
    ad9361_spi_write(0x0B0, 0x00);
    ad9361_spi_write(0x0B1, 0x00);
    ad9361_spi_write(0x0B2, 0x27);

    /* 5. Configure BBPLL */
    /* Actually requires a long sequence of 0x0A4-0x0AB registers, configured per your sample rate */

    /* 6. Configure RX/TX LO frequency */
    /* Example: set RX LO to 2.4 GHz, TX LO to 2.4 GHz */
    /* Must convert to divider values based on PLL calculation table, then write to corresponding registers */

    /* 7. Wait for PLL lock */
    if (!ad9361_wait_pll_lock(500)) {
        printf("RX PLL not locked!\n");
        return -1;
    }

    /* 8. Configure baseband filters and data interface */
    /* 9. Trigger calibration and wait for completion */
    /* 10. Configure AGC, gain tables, and other runtime parameters */

    return 0;
}

The register values in steps 4 through 6 must be calculated based on your board's reference clock frequency and desired LO frequency — they cannot be copied directly. This is also the most time-consuming part of AD9361 configuration. If your reference clock isn't 40 MHz but something else, the value of register 0x0B2 will be completely different. My recommendation is to use ADI's official AD936x Evaluation Software — it can generate a set of initialization register values based on the reference clock and LO frequency you enter. Pull them into your code, run them once, and verify with readback checks.

5. Debugging Tools and Quick Localization: Logic Analyzer, Waveforms, and Readback Verification

5.1 Using a Logic Analyzer to Capture SPI Waveforms

For debugging AD9361's SPI, a logic analyzer is an essential tool. Sampling rate should be at least 100 MHz, with four channels connected: CS, SCLK, SDI, SDO. Set the trigger condition to CS falling edge so every transfer is fully captured. After capturing, focus on three things.

First, whether the number of SCLK pulses during CS low is exactly 24 (3 bytes × 8 bits). If fewer than 24, the transfer was interrupted and the slave's parsed frame is incomplete; if more than 24, extra clocks were sent, which may cause an extra byte to be read or the slave state machine to become confused. Second, whether the first byte's bit7 is the correct read/write direction: read should be 0, write should be 1. Third, whether the data byte matches the value written in the code, especially the address byte. Once these three things are verified, you can basically determine whether there's a problem at the SPI layer.

Real-world case: I once discovered a problem using this method: the logic analyzer captured 25 SCLK pulses, one more than expected. Investigation revealed that the length parameter passed to XSpi_Transfer in the code was wrong — a one-byte array was passed with a length of 3, causing the SPI controller to send one extra clock. This kind of problem is hard to spot by reading code, but the logic analyzer reveals the truth immediately from the waveform.

5.2 Three Methods for Register Readback Verification

Readback verification is the only reliable means of ensuring registers are actually written. I commonly use three methods.

The first is immediate readback after write, comparing the written and read-back values. This method is simplest and suitable for ordinary configuration registers, but has some pitfalls: some registers perform internal processing after writing, and the readback value may not equal the written value (e.g., some status bits are cleared); some calibration registers require a delay before the new value can be read back. If readback doesn't match, check the register table to confirm attributes first — don't immediately assume an SPI problem.

The second is continuous address readback. When you've configured a block of consecutive registers, don't read them one by one — use multi-byte continuous read and compare against the expected array. This method verifies a large block of register writes at once, with high efficiency. When implementing, note that the AD9361's multi-byte read address auto-increment must start from the correct starting address — an incorrect starting address causes the entire block of data to be misaligned.

The third is periodic polling of a status register. For example, periodically read the PLL lock status after startup to confirm the chip hasn't been erroneously reset by SPI noise. This method is suitable for long-term stability verification. When building products, I wrote a watchdog function that polls all key status registers every 10 seconds, printing logs and recording the scene if anomalies are found — helping to localize intermittent problems.

5.3 Common SPI Fault Waveform Signatures

You need to recognize several "faces" of fault waveforms — seeing them lets you quickly judge the problem direction.

Waveform Signature Possible Cause Handling
SCLK idle high CPOL set incorrectly Check SPI controller configuration, change to CPOL=0
Glitches during CS low CS trace too long or insufficient drive strength Shorten trace, add series resistor, add pull-up
Data bit order reversed LSB-first mistakenly set Check 0x0A2 register configuration
Readback always 0x00 SDO pin cold solder joint or direction config error Check hardware connection, confirm SDO isn't occupied
Readback always 0xFF SDO pull-up resistor too large or drive conflict Check external pull-up, measure level with multimeter
One extra SCLK pulse SPI transfer length parameter error Verify code length parameter matches array byte count
Tip: I recommend memorizing these waveform signatures before debugging — it can cut troubleshooting time in half. Rather than fumbling with a multimeter, a logic analyzer paired with a signature table is the most efficient path to diagnosis.

6. Pitfall Avoidance Checklist: Print This and Tape It to Your Desk

Finally, here's a checklist distilled from debugging several AD9361 boards, suitable for printing and taping to your workstation. Every row is a real pitfall I've stepped in or verified after someone else did.

Symptom First Check Second Check Third Check
Register write has no effect SPI address mode match (10-bit/16-bit) Chip select timing correct Register is read-only
Readback value fixed and unchanging Read/write direction bit reversed Address spelling error Chip erroneously reset by SPI noise
RX PLL won't lock Reference clock ready VCO calibration result Charge pump current setting
cp ovrg high LO frequency out of range Charge pump current too high/low Loop filter parameters
BBPLL won't lock Reference clock divider ratio Supply stability CLK_OUT pin waveform
Data garbled SCLK polarity/phase Data bit order Baud rate too high
Final thought: After working with the AD9361 for so long, the one thing I most want to share is this: before writing the initialization sequence, hang a logic analyzer on the SPI bus first. All those strange phenomena that look like "register problems" — nine out of ten ultimately trace back to the SPI layer. This is the most valuable lesson I learned after debugging my first AD9361 board. Once the hardware link is working, the remaining register configuration is just a matter of patience and time.

7. Quick Project Validation

If you want to shorten development cycles and accelerate project validation, explore our SDR product portfolio:



Previous post

Leave a comment