小白入门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. 关键配置避开音频相关只保留通信核心配置项推荐值新手作用工作模式主模式MasterRK3576 生成 BCLK/LRCKFPGA 只需要被动响应减少同步问题协议类型I2S 或 TDMI2S 时序最简单优先选TDM 适合多通道帧长度LRCK32bit/帧每帧传输 32bit 数据可自定义比如 16/8bitBCLK 频率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若只需单向通信可悬空GNDGND必须共地否则时钟/数据会有干扰导致数据错误关键提醒无需接 MCLKMCLK 是给音频 CODEC 的时钟通用通信不需要若传输距离超过 10cm建议在信号线上串 22Ω 电阻减少反射RK3576 的 SAI 引脚是 1.8V 电平若 FPGA 是 3.3V需加电平转换芯片比如 TXS0108避免烧引脚。三、核心步骤 1设备树配置关键避开音频仅启用SAI硬件不用配置任何音频相关节点比如sound/CODEC只需启用 SAI 控制器、配置引脚和基础参数示例如下以 SAI1 为例1. 启用 SAI1 控制器禁用音频相关sai1: saife470000 { 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文件名makeARCHarm64CROSS_COMPILEaarch64-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 发送自定义数据比如 0x12345678FPGA 端按时序接收即可。#includestdio.h#includestdlib.h#includefcntl.h#includeunistd.h#includesys/mman.h#includestdint.h// SAI1寄存器基地址从TRM查RK3576 SAI1基地址是0xFE470000#defineSAI1_BASE_ADDR0xFE470000#defineSAI_REG_SIZE0x1000// SAI寄存器偏移查TRMSerial Audio Interface章节#defineSAI_CTRL00x00// 控制寄存器0模式、时钟、位宽#defineSAI_CTRL10x04// 控制寄存器1帧长度、同步模式#defineSAI_TX_DATA0x20// 发送数据寄存器#defineSAI_STATUS0x10// 状态寄存器判断发送完成intmain(){intfd;uint8_t*sai_regs;uint32_tdata0x12345678;// 要发送给FPGA的数据32bit// 1. 打开/dev/mem用于映射物理寄存器fdopen(/dev/mem,O_RDWR|O_SYNC);if(fd0){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_regsMAP_FAILED){perror(mmap failed);close(fd);return-1;}// 3. 配置SAI1为通用串行通信模式核心// 3.1 复位SAI1先清0*(volatileuint32_t*)(sai_regsSAI_CTRL0)0x00000000;usleep(1000);// 3.2 配置核心参数按TRM寄存器说明// CTRL0主模式 32bit位宽 BCLK上升沿发送 MSB先传*(volatileuint32_t*)(sai_regsSAI_CTRL0)(10)|// 启用SAI(11)|// 主模式RK3576生成BCLK/LRCK(03)|// 数据位宽32bitTRM中查对应值(18)|// MSB先传(110);// BCLK上升沿发送数据// CTRL1帧长度32bit LRCK帧同步*(volatileuint32_t*)(sai_regsSAI_CTRL1)(310)|// 帧长度32bit0~31共32位(016);// LRCK作为帧同步// 4. 循环发送数据到FPGAprintf(SAI1 start send data to FPGA...\n);while(1){// 等待发送寄存器为空while(*(volatileuint32_t*)(sai_regsSAI_STATUS)(10));// 写入要发送的数据32bit*(volatileuint32_t*)(sai_regsSAI_TX_DATA)data;// 数据自增方便FPGA验证data;usleep(1000);// 控制发送速率可根据需求调整}// 5. 释放资源实际不会执行仅示例munmap(sai_regs,SAI_REG_SIZE);close(fd);return0;}3. 代码编译与运行# 交叉编译针对RK3576的arm64架构aarch64-linux-gnu-gcc sai_fpga_tx.c-osai_fpga_tx# 传到开发板后运行需要root权限chmodx sai_fpga_txsudo./sai_fpga_tx4. 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 6d0; rx_valid 1b0; end else begin // 按位接收数据MSB先传 rx_data[31 - bit_cnt] SAI_TXD; bit_cnt bit_cnt 1b1; // 32bit接收完成置位有效标志 if (bit_cnt 6d31) begin rx_valid 1b1; 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 okay2. 检查代码中SAI_CTRL0的“启用位”是否置1FPGA接收数据乱码位序/时钟沿配置错误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时序解析即可无需复杂配置问题排查优先查“硬件时序”示波器抓信号再查“寄存器配置”驱动无需修改。