AD9361纯逻辑FPGA驱动,单音信号收发例程,可动态配置9361,verilog代码,Vivado 2019.1工程。

搞AD9361纯FPGA驱动这事说难不难,但没点骚操作还真容易踩坑。咱今天不整那些虚的理论,直接撸起袖子干代码。先看收发链路的核心结构——这玩意儿就是个数据泵,得在FPGA里用Verilog硬怼出来。

收发通道的数据接口得跟紧AD9361的时钟节奏,上代码:

always @(posedge rx_clk or posedge reset) begin
    if(reset) begin
        rx_data_buffer <= 12'd0;
    end else begin
        rx_data_buffer <= {rx_data_Q, rx_data_I}; // I/Q拼成24bit
    end
end

这里有个骚操作,把12bit的I/Q信号拼成24bit总线,后面接FIFO的时候能省一半带宽。注意rx_clk是从AD9361过来的随路时钟,时序不对齐分分钟丢数据。

动态配置这块必须上状态机,不然SPI时序搞死人。看这个配置状态机的骨架:

parameter IDLE = 3'd0;
parameter WRITE_CMD = 3'd1;
parameter WAIT_ACK = 3'd2;

always @(posedge spi_clk) begin
    case(state)
        IDLE: 
            if(config_en) begin
                spi_csn <= 1'b0;
                spi_mosi <= cmd_buffer[23];
                bit_cnt <= 5'd23;
                state <= WRITE_CMD;
            end
        WRITE_CMD:
            if(bit_cnt > 0) begin
                spi_mosi <= cmd_buffer[bit_cnt-1];
                bit_cnt <= bit_cnt - 1;
            end else begin
                state <= WAIT_ACK;
            end
        // ...其他状态
    endcase
end

注意cmd_buffer里塞的是[读写标志+7位地址+16位数据]的组合包。调试时用ILA抓这个信号,能看到配置参数像坐过山车一样在变化。

单音发生器模块是验证收发的关键,直接上DDS相位累加:

reg [31:0] phase_acc;
always @(posedge tx_clk) begin
    phase_acc <= phase_acc + freq_ctrl_word;
    tx_data_I <= $signed(2047 * $sin(phase_acc[31:24] * 2 * $PI / 256));
    tx_data_Q <= $signed(2047 * $cos(phase_acc[31:24] * 2 * $PI / 256)); 
end

这里用相位截断法省资源,实际测试发现相位噪声完全在可接受范围。freqctrlword直接关联到前面SPI配置的频率寄存器,改个数值就能切频率。

工程里最坑的是Vivado的时钟约束,必须给AD9361的data clock加上creategeneratedclock约束,不然时序报告全飘红。记得在XDC文件里补上:

create_generated_clock -name rx_clk \
    -source [get_pins ad9361_if/inst/clk] \
    -divide_by 1 \
    [get_ports rx_clk]

实测发现不这么搞的话,数据有效窗口会偏移超过1ns,直接导致收不到有效数据。

调通之后上SignalTap抓波形,能看到I/Q数据像心电图一样规律跳动。这时候拿频谱仪对着射频口,能扫到干净的单音信号——这时候千万别激动,先检查是不是本振泄露,把数字直流校正寄存器配置好再说。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐