当前位置:首页 > 新闻资讯 > FPGA之家动态 >

RK3576 SAI 与 FPGA 通信开发教程

时间:2026-05-31      来源:FPGA_UCY 关于我们 0

RK3576 SAI与 FPGA 通信开发教程

用 SAI 做通用串行数据通信(而非音频),是 SAI 接口的典型“非音频”应用场景!RK3576 的 SAI 本质是“可编程串行同步通信控制器”,只要 FPGA 端按 SAI 的时序规则收发数据,就能实现稳定的双向数据传输。本教程会从“原理适配→硬件连接→设备树配置→代码开发→调试验证”一步步教你,全程避开音频相关冗余内容,聚焦“SAI 作为通用串口”的核心用法。

一、先搞懂核心:SAI 与 FPGA 通信的底层逻辑1. 为什么 SAI 能和 FPGA 通信?

SAI 的本质是同步串行通信接口(时钟+数据+帧同步),和 SPI/I2C 类似,只是时序规则不同。对 FPGA 来说,只要解析/生成 SAI 的时序,就能和 RK3576 交换数据,核心优势:

2. 核心时序(新手先掌握最简模式:I2S 主模式)

不用管音频协议,只需关注 3 个核心信号的时序规则(RK3576 做主机,FPGA 做从机,最易实现):

信号

方向(RK3576→FPGA)

作用

SAIx_BCLK

输出

位时钟(RK3576 生成,每 1 个 BCLK 对应 1bit 数据传输,频率可配置)

SAIx_LRCK

输出

帧同步时钟(RK3576 生成,每 1 个 LRCK 周期 = 1 帧数据,帧长度可配置)

SAIx_TXD

输出

发送数据(RK3576→FPGA,每bit数据在 BCLK 上升沿/下降沿稳定)

SAIx_RXD

输入

接收数据(FPGA→RK3576,规则和 TXD 一致)

最简时序规则(新手必记):

LRCK 作为“帧起始”信号:LRCK 电平翻转时,开始 1 帧数据传输;BCLK 作为“位同步”信号:每 1bit 数据在 BCLK 的上升沿由发送端输出,下降沿由接收端采样;数据格式:每帧传输 N bit 数据(比如 32bit),无需区分左右声道(把 LRCK 仅当作“帧同步”即可)。3. 关键配置(避开音频相关,只保留通信核心)

配置项

推荐值(新手)

作用

工作模式

主模式(Master)

RK3576 生成 BCLK/LRCK,FPGA 只需要被动响应,减少同步问题

协议类型

I2S 或 TDM

I2S 时序最简单(优先选),TDM 适合多通道

帧长度(LRCK)

32bit/帧

每帧传输 32bit 数据(可自定义,比如 16/8bit)

BCLK 频率

8MHz(示例)

32bit/帧 × 250kHz 帧频率 = 8MHz BCLK(频率可通过寄存器配置)

数据位序

MSB 先传

高位在前,FPGA 解析更简单

二、硬件连接(最简配置,新手必对)

以 RK3576 的 SAI1 为例,和 FPGA 只需要 5 根线(无需音频 CODEC,直接对接):

RK3576 SAI1 引脚

FPGA 引脚

备注

SAI1_BCLK

任意IO(输入)

FPGA 作为从机,接收 RK3576 生成的位时钟

SAI1_LRCK

任意IO(输入)

FPGA 接收帧同步时钟

SAI1_TXD

任意IO(输入)

FPGA 接收 RK3576 发送的数据

SAI1_RXD

任意IO(输出)

FPGA 发送数据到 RK3576(若只需单向通信,可悬空)

GND

GND

必须共地!否则时钟/数据会有干扰,导致数据错误

关键提醒:

无需接 MCLK(MCLK 是给音频 CODEC 的时钟,通用通信不需要);若传输距离超过 10cm,建议在信号线上串 22Ω 电阻(减少反射);RK3576 的 SAI 引脚是 1.8V 电平,若 FPGA 是 3.3V,需加电平转换芯片(比如 TXS0108),避免烧引脚。

三、核心步骤 1:设备树配置(关键!避开音频,仅启用SAI硬件)

不用配置任何音频相关节点(比如 sound/CODEC),只需启用 SAI 控制器、配置引脚和基础参数,示例如下(以 SAI1 为例):

1. 启用 SAI1 控制器(禁用音频相关)

sai1: sai@fe470000 {
    compatible = "rockchip,rk3576-sai";  // 仅匹配SAI硬件驱动,不关联音频
    reg = <0x0 0xfe470000 0x0 0x1000>;   // SAI1寄存器基地址(查TRM确认)
    interrupts = ;  // SAI1中断号(查TRM)
    // 时钟配置:仅启用基础时钟,不用音频PLL
    clocks = <&cru PCLK_SAI1>, <&cru SCLK_SAI1>, <&cru HCLK_SAI1>;
    clock-names = "pclk", "sclk", "hclk";
    // DMA配置:必须启用(SAI大量数据传输依赖DMA)
    dmas = <&dmac1 22>, <&dmac1 23>;     // TX/RX DMA通道(查TRM)
    dma-names = "tx", "rx";
    // 引脚复用:绑定SAI1的物理引脚(查开发板引脚手册)
    pinctrl-names = "default";
    pinctrl-0 = <&sai1_bclk>, <&sai1_lrck>, <&sai1_txd>, <&sai1_rxd>;
    status = "okay";  // 启用SAI1
    
    // 自定义属性:告诉驱动这是通用通信(非音频)
    rockchip,sai-mode = "general";       // 自定义字段,后续代码会读取
    rockchip,frame-length = <32>;        // 每帧32bit
    rockchip,bclk-freq = <8000000>;      // BCLK频率8MHz
};
// 引脚复用配置(关键:确保引脚设为SAI功能,而非GPIO)
pinctrl {
    sai1 {
        sai1_bclk: sai1-bclk {
            rockchip,pins = <1 RK_PA1 0 &pcfg_pull_none>;  // 示例引脚,改你自己的
        };
        sai1_lrck: sai1-lrck {
            rockchip,pins = <1 RK_PA2 0 &pcfg_pull_none>;
        };
        sai1_txd: sai1-txd {
            rockchip,pins = <1 RK_PA3 0 &pcfg_pull_none>;
        };
        sai1_rxd: sai1-rxd {
            rockchip,pins = <1 RK_PA4 0 &pcfg_pull_none>;
        };
    };
};

2. 编译烧录设备树

和之前音频场景一样,编译后烧录到开发板:

# 交叉编译设备树(替换为你的DTS文件名)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rk3576-evb.dtb -j8
# 烧录
fastboot flash dtb rk3576-evb.dtb

四、核心步骤 2:驱动层/应用层代码开发(新手先从应用层入手)1. 核心思路(新手优先)

不用修改内核 SAI 驱动!RK3576 的 SAI 驱动已提供寄存器操作接口和DMA 数据传输接口,直接在应用层通过 mmap 映射 SAI 寄存器,或通过 sysfs/dev 节点收发数据即可。

2. 最简应用层代码(SAI 发送数据到 FPGA)

功能:配置 SAI1 为主模式,按 32bit/帧、8MHz BCLK 发送自定义数据(比如 0x12345678),FPGA 端按时序接收即可。

#include 
#include 
#include 
#include 
#include 
#include 
// SAI1寄存器基地址(从TRM查:RK3576 SAI1基地址是0xFE470000)
#define SAI1_BASE_ADDR 0xFE470000
#define SAI_REG_SIZE   0x1000
// SAI寄存器偏移(查TRM:Serial Audio Interface章节)
#define SAI_CTRL0      0x00  // 控制寄存器0(模式、时钟、位宽)
#define SAI_CTRL1      0x04  // 控制寄存器1(帧长度、同步模式)
#define SAI_TX_DATA    0x20  // 发送数据寄存器
#define SAI_STATUS     0x10  // 状态寄存器(判断发送完成)
int main() {
    int fd;
    uint8_t *sai_regs;
    uint32_t data = 0x12345678;  // 要发送给FPGA的数据(32bit)
    // 1. 打开/dev/mem(用于映射物理寄存器)
    fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (fd < 0) {
        perror("open /dev/mem failed");
        return -1;
    }
    // 2. 映射SAI1寄存器到用户空间
    sai_regs = (uint8_t *)mmap(NULL, SAI_REG_SIZE, PROT_READ | PROT_WRITE,
                               MAP_SHARED, fd, SAI1_BASE_ADDR);
    if (sai_regs == MAP_FAILED) {
        perror("mmap failed");
        close(fd);
        return -1;
    }
    // 3. 配置SAI1为通用串行通信模式(核心!)
    // 3.1 复位SAI1(先清0)
    *(volatile uint32_t *)(sai_regs + SAI_CTRL0) = 0x00000000;
    usleep(1000);
    // 3.2 配置核心参数(按TRM寄存器说明)
    // CTRL0:主模式 + 32bit位宽 + BCLK上升沿发送 + MSB先传
    *(volatile uint32_t *)(sai_regs + SAI_CTRL0) = 
        (1 << 0) |   // 启用SAI
        (1 << 1) |   // 主模式(RK3576生成BCLK/LRCK)
        (0 << 3) |   // 数据位宽:32bit(TRM中查对应值)
        (1 << 8) |   // MSB先传
        (1 << 10);   // BCLK上升沿发送数据
    // CTRL1:帧长度32bit + LRCK帧同步
    *(volatile uint32_t *)(sai_regs + SAI_CTRL1) = 
        (31 << 0) |  // 帧长度:32bit(0~31共32位)
        (0 << 16);   // LRCK作为帧同步
    // 4. 循环发送数据到FPGA
    printf("SAI1 start send data to FPGA...\n");
    while (1) {
        // 等待发送寄存器为空
        while (*(volatile uint32_t *)(sai_regs + SAI_STATUS) & (1 << 0));
        
        // 写入要发送的数据(32bit)
        *(volatile uint32_t *)(sai_regs + SAI_TX_DATA) = data;
        
        // 数据自增(方便FPGA验证)
        data++;
        usleep(1000);  // 控制发送速率(可根据需求调整)
    }
    // 5. 释放资源(实际不会执行,仅示例)
    munmap(sai_regs, SAI_REG_SIZE);
    close(fd);
    return 0;
}

3. 代码编译与运行

# 交叉编译(针对RK3576的arm64架构)
aarch64-linux-gnu-gcc sai_fpga_tx.c -o sai_fpga_tx
# 传到开发板后运行(需要root权限)
chmod +x sai_fpga_tx
sudo ./sai_fpga_tx

4. FPGA端最简接收逻辑(Verilog示例)

FPGA 只需按 SAI 时序解析数据即可,核心代码如下(新手可直接用):

module sai_rx (
    input         SAI_BCLK,    // 来自RK3576的位时钟
    input         SAI_LRCK,    // 来自RK3576的帧同步
    input         SAI_TXD,     // 来自RK3576的数据
    output reg [31:0] rx_data, // 解析后的32bit数据
    output reg        rx_valid  // 数据有效标志
);
reg [5:0] bit_cnt;  // 位计数器(0~31)
reg       lrck_prev;
// 帧同步检测:LRCK翻转时重置计数器
always @(posedge SAI_BCLK) begin
    lrck_prev <= SAI_LRCK;
    if (SAI_LRCK != lrck_prev) begin
        bit_cnt <= 6'd0;
        rx_valid <= 1'b0;
    end else begin
        // 按位接收数据(MSB先传)
        rx_data[31 - bit_cnt] <= SAI_TXD;
        bit_cnt <= bit_cnt + 1'b1;
        
        // 32bit接收完成,置位有效标志
        if (bit_cnt == 6'd31) begin
            rx_valid <= 1'b1;
        end
    end
end
endmodule

五、调试验证(新手必做,避免踩坑)1. 硬件层面验证(优先做)2. 软件层面验证3. FPGA端验证六、常见问题排查(新手必看)

问题现象

大概率原因

解决方法

SAI无时钟输出(BCLK/LRCK)

设备树中SAI未启用,或寄存器配置错误

1. 确认设备树 status = "okay";2. 检查代码中 SAI_CTRL0 的“启用位”是否置1

FPGA接收数据乱码

位序/时钟沿配置错误

1. 确认SAI是“MSB先传”;2. FPGA在BCLK下降沿采样(和RK3576的发送沿相反)

数据丢包

DMA未启用,或发送速率过快

1. 设备树中必须配置DMA通道;2. 降低应用层发送速率(增大usleep值)

寄存器映射失败


注明:本内容来源网络,不用于商业使用,禁止转载,如有侵权,请来信到邮箱:429562386ⓐqq.com 或联系本站客服处理,感谢配合!

用户登陆

    未注册用户登录后会自动为您创建账号

提交留言