小白入门:RK3576 SAI 与 FPGA 通信开发教程
小白入门:RK3576 SAI 与 FPGA 通信开发教程
用 SAI 做通用串行数据通信(而非音频),是 SAI 接口的典型“非音频”应用场景!RK3576 的 SAI 本质是“可编程串行同步通信控制器”,只要 FPGA 端按 SAI 的时序规则收发数据,就能实现稳定的双向数据传输。本教程会从“原理适配→硬件连接→设备树配置→代码开发→调试验证”一步步教你,全程避开音频相关冗余内容,聚焦“SAI 作为通用串口”的核心用法。
一、先搞懂核心:SAI 与 FPGA 通信的底层逻辑
1. 为什么 SAI 能和 FPGA 通信?
SAI 的本质是同步串行通信接口(时钟+数据+帧同步),和 SPI/I2C 类似,只是时序规则不同。对 FPGA 来说,只要解析/生成 SAI 的时序,就能和 RK3576 交换数据,核心优势:
- 高带宽:最高支持 384kHz 帧频率 × 32bit 位宽 = 12.288Mbps(比普通 UART 快得多);
- 同步性好:基于时钟同步(BCLK),无丢包风险,适合FPGA这类对时序敏感的设备;
- 多通道:TDM 模式下可同时传输多路数据(比如一路传指令、一路传数据)。
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 = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>; // 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 <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>
// 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. 硬件层面验证(优先做)
- 用示波器/逻辑分析仪抓信号:
- 先看
SAI_BCLK:是否为8MHz(频率稳定); - 再看
SAI_LRCK:是否按32bit周期翻转(每32个BCLK翻转一次); - 最后看
SAI_TXD:是否在BCLK上升沿变化,数据是否自增(和代码中一致)。
- 先看
- 若信号异常:
- 检查设备树引脚配置(是否真的设为SAI功能,而非GPIO);
- 检查硬件接线(共地是否接好,电平是否匹配)。
2. 软件层面验证
- 检查SAI驱动加载:
dmesg | grep sai→ 看到“registered SAI1”即可; - 检查寄存器映射:运行代码后无“mmap failed”,说明寄存器映射成功;
- 临时停止发送:
ps -ef | grep sai_fpga_tx→ kill 进程即可。
3. FPGA端验证
- 查看
rx_valid信号:每32个BCLK周期置位一次,说明帧同步正常; - 查看
rx_data:是否为连续自增的数值(0x12345678→0x12345679→…),说明数据接收正确。
六、常见问题排查(新手必看)
| 问题现象 | 大概率原因 | 解决方法 |
|---|---|---|
| SAI无时钟输出(BCLK/LRCK) | 设备树中SAI未启用,或寄存器配置错误 | 1. 确认设备树 status = "okay";2. 检查代码中 SAI_CTRL0 的“启用位”是否置1 |
| FPGA接收数据乱码 | 位序/时钟沿配置错误 | 1. 确认SAI是“MSB先传”;2. FPGA在BCLK下降沿采样(和RK3576的发送沿相反) |
| 数据丢包 | DMA未启用,或发送速率过快 | 1. 设备树中必须配置DMA通道;2. 降低应用层发送速率(增大usleep值) |
| 寄存器映射失败 | 权限不足,或地址错误 | 1. 用sudo运行代码;2. 核对SAI1基地址(TRM中确认是0xFE470000) |
七、进阶优化(新手学会基础后再做)
- 双向通信:在上述代码基础上,添加
SAI_RX_DATA寄存器读取逻辑,实现FPGA→RK3576数据传输; - 多通道传输:启用TDM模式,把LRCK分成多个子通道(比如前16bit传指令,后16bit传数据);
- 中断方式收发:不用轮询寄存器,通过SAI中断(数据接收完成/发送空)触发数据处理,降低CPU占用;
- 更高带宽:提高BCLK频率(比如到16MHz),或增加帧长度(比如64bit),提升传输速率。
总结
- RK3576 SAI 与 FPGA 通信的核心是抛弃音频协议,把SAI当作“同步串行控制器”,只需关注时钟、帧同步、数据三个信号的时序;
- 新手优先用“SAI主模式+32bit帧长+MSB先传”,FPGA端按BCLK/LRCK时序解析即可,无需复杂配置;
- 问题排查优先查“硬件时序”(示波器抓信号),再查“寄存器配置”,驱动无需修改。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)