動(dòng)與頻率控制字換算:從寄存器配置到FPGA移植)
簡介面向FPGA及嵌入式開發(fā)者的AD9914高性能DDS驅(qū)動(dòng)資源包主要解決直接數(shù)字頻率合成器在工程中的快速配置與控制問題適用于通信、測試測量、雷達(dá)等需要高精度頻率生成的場景。包內(nèi)共24個(gè)文件以C源文件、頭文件、Keil工程文件uvproj/uvopt及編譯輸出hex/axf/map為主附帶匯編啟動(dòng)文件與鏈接腳本等可完整支持在ADuC7026平臺(tái)上的編譯、燒錄與調(diào)試。壓縮包僅59KB輕量易用。已有447人學(xué)習(xí)瀏覽。資源結(jié)合AD9914內(nèi)置12位DAC、3.5GSPS采樣率等特點(diǎn)給出驅(qū)動(dòng)初始化、頻率/幅度/相位參數(shù)配置以及SPI或并行接口控制的示例代碼便于開發(fā)者理解寄存器操作與同步時(shí)序并快速移植到自有FPGA或MCU項(xiàng)目中縮短高頻信號(hào)源系統(tǒng)的開發(fā)周期。1. 一塊 3.5 GSPS 的 DDS 芯片一份能直接跑的 AD9914 驅(qū)動(dòng)工程射頻調(diào)試臺(tái)前最繞不開的一類器件就是 DDS 信號(hào)發(fā)生器。AD9914 是 Analog Devices 的高性能直接數(shù)字頻率合成器3.5 GSPS 采樣率配 12 位 DAC頻率、相位、幅度都能用數(shù)字字精確控制跳頻通信、雷達(dá)激勵(lì)、測試測量里經(jīng)常見到它。這份 ExampleCode_DDS_AD9914 壓縮包是一套完整的 ADuC7026ADI 的 ARM7 內(nèi)核 MCUKeil 工程SPI 初始化、寄存器配置、FTW 頻率字換算都寫在 ad9914test.c 里工程里連 hex 和 axf 都編譯好了屬于拿到就能開干的那類代碼。做 FPGA 驅(qū)動(dòng)的人同樣值得下——AD9914 的 SPI 時(shí)序和主控平臺(tái)無關(guān)把 C 驅(qū)動(dòng)里的寄存器序列搬到 Verilog 狀態(tài)機(jī)里就能用。適合正在調(diào) AD9914 寄存器、或者打算用 FPGA 控制 DDS 輸出頻率的開發(fā)者。2. AD9914 寄存器體系與頻率控制字換算把 MHz 變成十六進(jìn)制之前先看懂這兩張表2.1 DDS 核心相位累加器與 32 位頻率控制字DDS 內(nèi)部繞不開的是一個(gè)相位累加器。每個(gè) SYSCLK 周期累加器加上一個(gè)步進(jìn)值這個(gè)步進(jìn)就是頻率控制字 FTW。當(dāng)累加器溢出回繞輸出就完成一個(gè)周期。所以輸出頻率和 FTW 是嚴(yán)格的線性關(guān)系f_out (FTW × f_SYSCLK) / 2^32AD9914 的 SYSCLK 最高 3.5 GHzFTW 是 32 位寄存器。這個(gè)公式的工程含義是頻率分辨率固定等于 f_SYSCLK / 2^32約 0.815 Hz與輸出頻率無關(guān)。你不需要查表不需要線性化校準(zhǔn)直接把目標(biāo)頻率代入公式就能算出 FTW。反過來如果發(fā)現(xiàn)輸出頻率對(duì)不上第一步就是檢驗(yàn) FTW 是不是按當(dāng)前實(shí)際 SYSCLK 算的——很多人栽在參考時(shí)鐘不是標(biāo)稱 3.5 GHz 上。FTW 決定頻率POW0 是 16 位相位偏移字相位分辨率 360/65536 ≈ 0.0055 度ACW0 是 12 位幅度控制字配合 12 位 DAC 使用。相參體制、多片同步時(shí)需要設(shè)置 POW。頻率切換能有多快取決于更新 FTW 的方式直接寫 FTW0 后由 IO_UPDATE 鎖存或者用 Profile 模式預(yù)先在 8 個(gè) profile 里存好不同的 FTW/POW/ACW切換時(shí)只拉 Profile0-2 引腳不碰 SPI切換時(shí)間只受 SYSCLK 周期限制這部分在第 6 章展開。2.2 關(guān)鍵寄存器地址與 SPI 指令字節(jié)AD9914 的寄存器空間用 6 位地址編址調(diào)試最常用的幾個(gè)地址列在這張表里。地址寄存器長度用途0x00CFR132 位SPI 模式、LSB first、SDO 使能等全局配置0x01CFR232 位SYNC_CLK 輸出、IO_UPDATE 行為0x02CFR332 位參考時(shí)鐘倍頻、Profile 相關(guān)配置0x03CFR432 位輔助控制位0x04FTW032 位頻率控制字主輸出通道0x05POW016 位相位偏移字0x06ACW012 位幅度控制字SPI 每次傳輸?shù)母袷绞? 個(gè)指令字節(jié) 若干數(shù)據(jù)字節(jié)。指令字節(jié) bit7 是讀寫標(biāo)志0 寫 1 讀bit6:1 是 6 位寄存器地址bit0 恒為 0。數(shù)據(jù)字節(jié)數(shù)由目標(biāo)寄存器長度決定比如 CFR1 和 FTW0 都是 4 字節(jié)POW0 是 2 字節(jié)全部 MSB first。這個(gè)位域定義在不同版本數(shù)據(jù)手冊里的表達(dá)方式可能略有差異但實(shí)際線上時(shí)序就是先送指令字節(jié)再送數(shù)據(jù)CSB 在整個(gè)傳輸期間拉低。提示拿到芯片后第一件事不是寫代碼而是確認(rèn)手頭數(shù)據(jù)手冊版本對(duì)應(yīng)的寄存器表和讀時(shí)序圖壓縮包里 ad9914test.c 的地址宏可以作為交叉驗(yàn)證的參考。2.3 三個(gè)頻率計(jì)算實(shí)例與一段可復(fù)現(xiàn)的換算代碼假設(shè) SYSCLK 3.5 GHzFTW 公式代入后得到下面的結(jié)果。目標(biāo)頻率FTW 整數(shù)實(shí)際輸出誤差1 GHz1227133513約 0.1 Hz100 MHz122713351小于 0.5 Hz25 MHz30683338小于 0.5 Hz后端如果用 C 寫直接乘 2^32 會(huì)溢出 32 位整型我一般先用 double 算再截?cái)喑?uint32_t反算一遍確認(rèn)誤差。#include stdio.h #include stdint.h int main(void) { double sysclk 3.5e9; /* AD9914 最高 SYSCLK */ double f_target 1e9; /* 目標(biāo)頻率單位 Hz */ uint32_t ftw; /* 用 double 算出理論 FTW再轉(zhuǎn) 32 位整型 */ ftw (uint32_t)((f_target / sysclk) * 4294967296.0); printf(FTW 0x%08X (%u)\n, ftw, ftw); /* 反算實(shí)際輸出頻率確認(rèn)舍入誤差 */ double f_real (double)ftw * sysclk / 4294967296.0; printf(f_out %.3f Hz\n, f_real); return 0; }這段代碼的邏輯先算比例 f_target / sysclk再乘 2^32得到的就是理論上應(yīng)該寫進(jìn) FTW0 的整數(shù)。反算那一步很關(guān)鍵它能直接告訴你舍入帶來的誤差量級(jí)避免把錯(cuò)誤的控制字燒進(jìn)芯片。參數(shù)上注意 sysclk 一定要用真實(shí)值比如外部晶振 100 MHz 配合片內(nèi) 35 倍頻那 sysclk 才是 3.5 GHz如果 PLL 沒使能實(shí)際只有 100 MHz同樣的 FTW 輸出會(huì)差 35 倍這是后面避坑章節(jié)的高頻事故。3. 解包 ExampleCode_DDS_AD9914Keil 工程結(jié)構(gòu)、SPI 初始化與寄存器寫入主流程3.1 壓縮包里的工程結(jié)構(gòu)哪些文件有用哪些是中間產(chǎn)物解壓后第一眼會(huì)看到一堆后綴各異、看起來亂糟糟的文件。別被嚇到我把它們按用途分一下類。文件作用是否需要關(guān)注AD9914Test.uvprojKeil μVision 4 工程文件是雙擊打開它AD9914Test.c主程序SPI 初始化與 AD9914 配置是核心代碼ADuC7026.hADuC7026 外設(shè)寄存器定義頭文件是查 SPI/GPIO 寄存器時(shí)用ADuC702x.sARM 啟動(dòng)文件時(shí)鐘與異常向量一般不用改irq_arm.cARM 中斷處理工程里中斷服務(wù)入口看需求關(guān)中斷可忽略AD9914Test.hex / .axf編譯產(chǎn)物是可直接燒錄AD9914Test.map / .lst鏈接映射與反匯編排錯(cuò)時(shí)用ad9914test.o / .crf / .d / .plg編譯器中間文件忽略真正值得反復(fù)看的是 AD9914Test.c 和 ADuC7026.h。.uvproj 用 Keil 打開后直接 F7 編譯就能過工程里帶的 hex 是已經(jīng)編譯好的用 ULINK 或者 J-Link 燒進(jìn) ADuC7026 的 Flash 就能跑。ADuC7026 是 3.3V IOAD9914 的數(shù)字 IO 也是 3.3V 域兩者可以直連不需要電平轉(zhuǎn)換這給調(diào)試省了很多事。3.2 SPI 底層接口一個(gè)字節(jié)收發(fā)函數(shù)打天下ADuC7026 的 SPI 外設(shè)寄存器操作屬于平臺(tái)相關(guān)代碼。為了讓你在別的 MCU 或 FPGA 上也能復(fù)用我把底層抽象成一個(gè)字節(jié)收發(fā)函數(shù)上層的寄存器讀寫邏輯全部平臺(tái)無關(guān)。/* spi_platform.h * 平臺(tái)相關(guān)層只暴露一個(gè)字節(jié)收發(fā)函數(shù)。 * ADuC7026 上的實(shí)現(xiàn)是操作 SPITX/SPIRX/SPISTA 寄存器 * 換成 STM32、GD32 或 FPGA 時(shí)替換成各自的 SPI 收發(fā)即可。 */ static uint8_t spi_write_read_byte(uint8_t tx) { /* ADuC7026 參考實(shí)現(xiàn) * 1) SPIEN 1 使能 SPI * 2) SPITX tx啟動(dòng)傳輸 * 3) 等待 SPISTA 的 SPIF 置位 * 4) return SPIRX; * 具體寄存器名以 ADuC7026.h 為準(zhǔn) */ return 0xFF; /* 占位 */ }這段代碼的意義在于把一個(gè)字節(jié)怎么過 SPI 總線和AD9914 的寄存器協(xié)議解耦。你只需要保證 spi_write_read_byte 能發(fā)一個(gè)字節(jié)并讀回一個(gè)字節(jié)其他所有邏輯都不依賴具體平臺(tái)。參數(shù)上注意ADuC7026 的 SPI 主模式建議先跑 1 MHz 驗(yàn)證時(shí)序確認(rèn)通信穩(wěn)定后再往上提AD9914 的 SCLK 上限很高但 MCU 這邊往往受限于外設(shè)分頻。3.3 寫寄存器函數(shù)指令字節(jié)拼裝與 IO_UPDATE 鎖存核心寫函數(shù)如下它拼裝指令字節(jié)、按大端序發(fā)送數(shù)據(jù)最后拉一次 IO_UPDATE 把數(shù)據(jù)鎖存進(jìn) AD9914 內(nèi)部寄存器。/* ad9914_drv.h 平臺(tái)無關(guān)的 AD9914 驅(qū)動(dòng)核心 */ #define AD9914_CFR1 0x00 #define AD9914_CFR2 0x01 #define AD9914_CFR3 0x02 #define AD9914_FTW0 0x04 #define AD9914_POW0 0x05 #define AD9914_ACW0 0x06 /* 寫寄存器instr 的 bit70 表示寫bit6:1 放 6 位地址bit0 恒 0 */ void ad9914_write(uint8_t addr, uint8_t len, uint32_t data) { uint8_t instr (addr 1) 0x7E; /* 地址放入 bit6:1 */ uint8_t buf[4]; int i; buf[0] instr; /* 大端序最高字節(jié)在前 */ for (i 0; i len; i) buf[1 i] (data (8 * (len - 1 - i))) 0xFF; cs_low(); for (i 0; i 1 len; i) spi_write_read_byte(buf[i]); cs_high(); /* IO_UPDATE 上升沿把數(shù)據(jù)鎖存進(jìn)內(nèi)部寄存器 */ io_update_pulse(); }指令字節(jié)的拼裝邏輯是 (addr 1) 0x7Eaddr 是 6 位寄存器地址左移一位放進(jìn) bit6:1bit7 保持 0 就是寫操作bit0 恒為 0。數(shù)據(jù)按大端發(fā)送先送最高字節(jié)因?yàn)?AD9914 的 SPI 是 MSB first。io_update_pulse 一般用 GPIO 拉高再拉低脈沖寬度我給 100 ns 以上工程里用兩個(gè) GPIO 翻轉(zhuǎn)就行。需要注意的是CFR 寄存器其實(shí)在 SPI 傳輸完成后立即生效而 FTW0、POW0、ACW0 必須等 IO_UPDATE 上升沿才鎖存。上面的函數(shù)統(tǒng)一在每次寫完后發(fā)脈沖省事且不會(huì)漏。3.4 初始化序列與一個(gè) 1 GHz 輸出示例下面這段初始化代碼可以直接套用FTW0 的值就是第 2 章算出來的 1227133513。void ad9914_init(void) { /* CFR1: SPI 模式、SDO 使能等 * 具體位含義對(duì)照數(shù)據(jù)手冊 CFR1 描述逐位核對(duì) */ ad9914_write(AD9914_CFR1, 4, 0x00001000); ad9914_write(AD9914_CFR2, 4, 0x00000042); ad9914_write(AD9914_CFR3, 4, 0x00000000); /* 設(shè)置 1 GHz 輸出FTW0 1227133513 0x49249249 */ ad9914_write(AD9914_FTW0, 4, 1227133513UL); } int main(void) { spi_master_init(); /* ADuC7026 SPI 主模式速率先跑 1 MHz */ ad9914_init(); while (1) { /* 這里放測試序列 * 改 FTW、觸發(fā) IO_UPDATE、讀回校驗(yàn) */ } }spi_master_init 在 ADuC7026 工程里已經(jīng)寫好無非是配置 SPI 引腳復(fù)用、主從模式、時(shí)鐘極性和相位。CFR1 的值 0x00001000 是我在測試板上常用的配置不同板子的 PLL 使能、SDO 使能位不一樣拿到工程后對(duì)照數(shù)據(jù)手冊 CFR1 的位定義核對(duì)一遍再上電。整套流程走通后頻譜儀上應(yīng)該能看到 1 GHz 的譜線。3.5 讀回校驗(yàn)SPI 半雙工必須處理的引腳方向配置完寄存器最好讀回來確認(rèn)。AD9914 支持 SPI 讀操作指令字節(jié) bit7 置 1 即可。uint32_t ad9914_read(uint8_t addr, uint8_t len) { uint8_t instr ((addr 1) 0x7E) | 0x80; /* bit71 表示讀 */ uint32_t data 0; int i; cs_low(); spi_write_read_byte(instr); /* AD9914 在讀指令后直接輸出數(shù)據(jù)主機(jī)繼續(xù)供時(shí)鐘 */ for (i 0; i len; i) data (data 8) | spi_write_read_byte(0x00); cs_high(); return data; }讀操作要特別注意ADuC7026 的 SPI 如果配置成半雙工單線模式SDIO 在發(fā)完指令字節(jié)后必須從輸出切到輸入否則讀回來的全是發(fā)送數(shù)據(jù)本身。我在第 5 章會(huì)把讀回全 0 或全 1這個(gè)坑單獨(dú)拿出來講。函數(shù)里 data 不斷左移再或上新字節(jié)實(shí)現(xiàn)大端拼接和寫操作的字節(jié)順序一致。4. 把驅(qū)動(dòng)移植到 FPGAVerilog 狀態(tài)機(jī) SPI 控制器與多片同步設(shè)計(jì)4.1 FPGA 側(cè)引腳規(guī)劃先定接口再寫邏輯FPGA 驅(qū)動(dòng) AD9914 和 MCU 驅(qū)動(dòng)最大的區(qū)別是SPI 時(shí)序要自己用狀態(tài)機(jī)造出來好處是 SCLK 速度、脈沖寬度完全可控。先規(guī)劃好引腳再動(dòng)手寫代碼。AD9914 引腳方向FPGA 側(cè)說明SCLK輸出SPI 時(shí)鐘建議先跑 10 MHzSDIO雙向?qū)憰r(shí)輸出讀時(shí)切為輸入CSB輸出片選低有效整個(gè)傳輸期間保持低IO_UPDATE輸出寄存器鎖存脈沖寫 FTW/POW 后給一個(gè)上升沿SYNC_CLK輸入AD9914 輸出多片同步時(shí)作為參考PROFILE0-2輸出Profile 快速切換不用時(shí)接地SYNC_CLK 的作用容易被忽略。它是 AD9914 輸出的同步時(shí)鐘信號(hào)多片級(jí)聯(lián)時(shí)必須把主片的 SYNC_CLK 接到從片讓所有芯片的 IO_UPDATE 對(duì)齊。單片的場合可以不管它但如果系統(tǒng)里有好幾片 AD9914 做相參輸出SYNC_CLK 就是那個(gè)讓相位對(duì)齊的關(guān)鍵信號(hào)。4.2 Verilog SPI 狀態(tài)機(jī)一個(gè)可直接例化的模塊下面這個(gè)狀態(tài)機(jī)模塊按 bit 發(fā)送指令字節(jié)加數(shù)據(jù)一次性傳完。SCLK 空閑為低數(shù)據(jù)在 SCLK 低電平期間切換上升沿穩(wěn)定送入 AD9914。/* ad9914_spi_master.v * 參數(shù) CLK_DIV 決定 SCLK 頻率sclk clk / (2*CLK_DIV) * 時(shí)序SCLK 空閑低數(shù)據(jù)在低電平期間切換上升沿被 AD9914 采樣 */ module ad9914_spi_master #( parameter CLK_DIV 4 )( input wire clk, input wire rst_n, input wire start, /* 上升沿啟動(dòng)一次傳輸 */ input wire [7:0] instr, /* 指令字節(jié) */ input wire [31:0] data, /* 數(shù)據(jù)大端在前 */ input wire [1:0] data_len, /* 數(shù)據(jù)字節(jié)數(shù)01字節(jié)34字節(jié) */ output reg sclk, output reg cs_n, output reg sdo, output reg busy, input wire sdi /* 讀回寫模式可不接 */ ); localparam IDLE 2d0, PRE_BIT 2d1, BIT_HIGH 2d2, POST_BIT 2d3; reg [1:0] state; reg [7:0] bit_cnt; reg [4:0] total_bits; reg [31:0] shift_reg; reg [15:0] div_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; cs_n 1b1; sclk 1b0; busy 1b0; end else begin case (state) IDLE: begin if (start) begin cs_n 1b0; /* 總位數(shù) 8 位指令 N 字節(jié)數(shù)據(jù) * 8 */ total_bits 8 ({2b00, data_len} 1) * 8; shift_reg {instr, data}; bit_cnt 0; div_cnt 0; busy 1b1; state PRE_BIT; end end PRE_BIT: begin /* SCLK 低電平期間把 sdo 切到當(dāng)前最高位 */ sdo shift_reg[31]; if (div_cnt CLK_DIV) begin sclk 1b1; div_cnt 0; state BIT_HIGH; end else begin div_cnt div_cnt 1; end end BIT_HIGH: begin /* 上升沿期間數(shù)據(jù)保持穩(wěn)定供 AD9914 采樣 */ if (div_cnt CLK_DIV) begin sclk 1b0; shift_reg {shift_reg[30:0], 1b0}; bit_cnt bit_cnt 1; div_cnt 0; state POST_BIT; end else begin div_cnt div_cnt 1; end end POST_BIT: begin if (bit_cnt total_bits - 1) begin cs_n 1b1; busy 1b0; state IDLE; end else begin state PRE_BIT; end end default: state IDLE; endcase end end endmodule這個(gè)狀態(tài)機(jī)的核心思想是每個(gè) bit 分成三段PRE_BIT 里 SCLK 為低、sdo 切到當(dāng)前最高位BIT_HIGH 里 SCLK 拉高POST_BIT 里切到下一 bit 并繼續(xù)。total_bits 由指令字節(jié)位數(shù)和數(shù)據(jù)字節(jié)數(shù)算出數(shù)據(jù)長度通過 data_len 參數(shù)控制2 位可以編碼 1 到 4 字節(jié)。需要讀回時(shí)在 BIT_HIGH 末尾用 sdi 采樣進(jìn)一個(gè)移位寄存器即可寫模式下 sdi 懸空或接 0。例化這個(gè)模塊寫一次 FTW0 的代碼如下。/* 例化并觸發(fā)一次 FTW0 寫操作地址 0x04數(shù)據(jù) 0x492492491 GHz */ ad9914_spi_master #(.CLK_DIV(4)) u_spi ( .clk (clk_50m), .rst_n (rst_n), .start (spi_start), .instr (8h08), /* 地址 0x04 左移一位bit70 寫 */ .data (32h49249249), .data_len (2d3), /* 4 字節(jié) */ .sclk (ad9914_sclk), .cs_n (ad9914_cs_n), .sdo (ad9914_sdio), .busy (spi_busy), .sdi (1b0) );注意 instr 傳的是 8h08也就是 0x04 左移一位的結(jié)果和 C 驅(qū)動(dòng)里 (addr 1) 0x7E 完全對(duì)應(yīng)。寫 FTW 這類需要鎖存的寄存器等 busy 拉低后再給 IO_UPDATE 脈沖幅值 100 ns 以上。SPI 傳輸完成后用一句 reg 翻轉(zhuǎn)生成 IO_UPDATE檢測 busy 下降沿延時(shí)幾個(gè)時(shí)鐘拉高再拉低。4.3 MCU 驅(qū)動(dòng)和 FPGA 驅(qū)動(dòng)的差異三個(gè)必須調(diào)整的地方第一SCLK 可以跑得更高。ADuC7026 的 SPI 外設(shè)分頻有限通常跑到幾 MHz 就到頭了FPGA 這邊 CLK_DIV 參數(shù)一調(diào)就能上 20 MHz配置 8 個(gè) profile 的時(shí)間明顯縮短。第二讀回時(shí)序。半雙工模式下 SDIO 需要三態(tài)控制發(fā)完指令字節(jié)后把 sdo 置為高阻然后從 sdi 采樣MCU 的 SPI 外設(shè)一般自動(dòng)處理方向切換FPGA 要自己寫一個(gè) oe 信號(hào)。第三多片同步。FPGA 天然適合處理 SYNC_CLK 和 IO_UPDATE 對(duì)齊這件事把所有 AD9914 的 IO_UPDATE 約束到同一個(gè)時(shí)序路徑比 MCU 用 GPIO 逐個(gè)拉要可靠得多。5. 避坑清單AD9914 配置翻車最常見的五個(gè)原因5.1 寄存器寫了沒反應(yīng)輸出還是上一個(gè)頻率現(xiàn)象SPI 波形看起來是對(duì)的CSB 拉低、指令字節(jié)和數(shù)據(jù)字節(jié)都發(fā)了但頻譜儀上輸出頻率紋絲不動(dòng)。原因FTW0、POW0、ACW0 這類寄存器必須等 IO_UPDATE 上升沿才鎖存只寫完 SPI 不會(huì)生效。我見過不少第一次調(diào) AD9914 的人在這里卡了兩三天波形上什么都對(duì)就是忘了 IO_UPDATE 這根線。解決每次寫完這三個(gè)寄存器后拉一個(gè) IO_UPDATE 脈沖寬度給足 100 ns 以上?;蛘吲渲?CFR2 里的自動(dòng)鎖存模式讓寄存器在 SPI 傳輸完成后自動(dòng)生效省掉外部引腳但我不建議新手這么干調(diào)試時(shí)少一個(gè)可控變量就多一分困惑。5.2 輸出頻率和設(shè)定值差一個(gè)固定倍數(shù)現(xiàn)象設(shè) 1 GHz實(shí)測 350 MHz設(shè) 100 MHz實(shí)測 35 MHz比例和標(biāo)稱 SYSCLK 與真實(shí) SYSCLK 的比值一致。原因這是頻率控制字公式里 SYSCLK 用錯(cuò)了。外部晶振 100 MHz片內(nèi) PLL 沒使能或者倍頻系數(shù)不對(duì)真實(shí) SYSCLK 只有 100 MHz計(jì)算 FTW 時(shí)卻按 3.5 GHz 代進(jìn)去輸出自然差了 35 倍。解決上電后用示波器或頻譜儀先測 REFCLK 頻率再確認(rèn) CFR3 里倍頻配置的最終 SYSCLK。我一般會(huì)在初始化代碼里加一個(gè)調(diào)試宏把當(dāng)前 SYSCLK 值打印出來和 FTW 一起核對(duì)避免這種低級(jí)錯(cuò)誤燒掉半天時(shí)間。5.3 SPI 讀回全是 0xFF 或者全 0現(xiàn)象寫進(jìn)去的值讀不回來讀 CFR1 得到 0xFFFFFFFF或者全是 0。原因半雙工模式下 SDIO 引腳方向沒切換。指令字節(jié)發(fā)送階段 SDIO 是輸出進(jìn)入數(shù)據(jù)階段必須切成輸入否則讀到的還是驅(qū)動(dòng)電平。另外部分 ADI 的 DDS 讀操作需要在指令字節(jié)后補(bǔ)一個(gè) dummy 時(shí)鐘周期時(shí)序圖沒仔細(xì)看就會(huì)踩到。解決發(fā)完指令字節(jié)后把 SDIO 置為高阻再繼續(xù)供時(shí)鐘對(duì)照數(shù)據(jù)手冊讀時(shí)序圖確認(rèn)是否要 dummy 周期。FPGA 里就是那個(gè) oe 信號(hào)翻轉(zhuǎn)的時(shí)機(jī)問題MCU 里則是 SPI 外設(shè)的接收使能配置逐條排查。5.4 輸出雜散大SFDR 遠(yuǎn)低于手冊值現(xiàn)象頻譜儀上除了主頻還有一堆雜散譜線SFDR 掉到 40 dBc 以下手冊上明明寫的是 50 dBc 起步。原因先分兩類看。單根對(duì)稱雜散多半是 DDS 本身的雜散跟頻率控制字的值有關(guān)換個(gè) FTW 位置雜散跟著變寬帶抬高的底噪則指向電源去耦不足或 SYSCLK 抖動(dòng)過大。解決電源引腳全部加 0.1 uF 和 1 uF 去耦電容評(píng)估板上的濾波網(wǎng)絡(luò)別省REFCLK 用低抖動(dòng)晶振別用電平毛刺大的 FPGA 時(shí)鐘直接懟上去輸出端加一個(gè)合適的低通濾波器把 DAC 鏡像頻率壓下去。5.5 多片同步時(shí)相位對(duì)不齊現(xiàn)象兩片 AD9914 配同樣的 FTW 和 POW輸出的相位差卻不固定每次上電都不一樣。原因多片之間 SYNC_CLK 沒有對(duì)齊IO_UPDATE 到達(dá)各片的時(shí)間不同步。AD9914 的 SYNC_CLK 輸出必須作為所有片的公共參考IO_UPDATE 要在同一時(shí)刻鎖存。解決把主片 SYNC_CLK 接到所有從片的 SYNC_CLK 輸入IO_UPDATE 由 FPGA 統(tǒng)一產(chǎn)生并約束到同一時(shí)鐘域。FPGA 里對(duì) SYNC_CLK 做相位對(duì)齊確保 IO_UPDATE 的上升沿落在所有片的安全窗口內(nèi)。6. 上電驗(yàn)證與 Profile 快速切換把 3.5 GSPS 的跳頻速度真正用起來把工程燒進(jìn)去之后別急著改需求先做一套固定的上電驗(yàn)證序列。第一步寫 FTW0 100 MHz 對(duì)應(yīng)的 122713351讀回校驗(yàn)第二步切換成 1 GHz 的 0x49249249看頻譜儀譜線是否跳過去第三步檢查 SFDR確認(rèn)電源和參考時(shí)鐘的底子沒問題。這套序列我每次接新的 DDS 板子都會(huì)強(qiáng)制走一遍能過濾掉八成以上的低級(jí)問題。Profile 模式才是 AD9914 在跳頻場景里的殺手锏。把兩三個(gè)常用頻點(diǎn)預(yù)先算好 FTW初始化時(shí)寫進(jìn)對(duì)應(yīng)的 profile 寄存器運(yùn)行過程中只改 PROFILE0-2 引腳電平不碰 SPI跳頻時(shí)間就從微秒級(jí)壓縮到納秒級(jí)。傳統(tǒng)方式寫 FTW 再拉 IO_UPDATE受限于 SPI 時(shí)鐘和軟件開銷跳頻間隔很難壓下去Profile 切換繞開了這條鏈路直接把頻率字的讀取交給硬件引腳。我習(xí)慣把起始頻率放 PROFILE 0跳變目標(biāo)頻率放 PROFILE 1初始化時(shí)一次寫完調(diào)試時(shí)用邏輯分析儀抓 PROFILE 引腳和輸出包絡(luò)的對(duì)應(yīng)關(guān)系。曾經(jīng)有一回我圖省事直接改 FTW0 做跳頻結(jié)果跳頻時(shí)間不達(dá)標(biāo)折騰了兩天才意識(shí)到該用 Profile從那以后每次設(shè)計(jì) DDS 跳頻鏈路都會(huì)把 Profile 切換放在第一位。硬件上的坑已經(jīng)幫你趟過一遍寄存器地址、指令字節(jié)位域、IO_UPDATE 時(shí)序這三處最容易翻車按上面這套流程走基本能一次點(diǎn)亮。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取