ARM与FPGA通讯
1 硬件接口介绍
FPGA与RK通信的接口有三种,分别为IIC、FSPI、PCIE。原理图如下所示:

2 IIC通信
在IIC通信中,FPGA作为从机,其设备地址为0x66。
目前代码中定义了三个寄存器地址为0x00、0x01、0x02。其中0x02为只读寄存器
对于0x00寄存器可以测试读写数据是否正确,例如写1,读出来也是1即通信正常。
i2cdetect -r -y -a 0

可以看到I2C总线上有个0x66的地址。
i2cset -r -y -a 0 0x66 0x00 0x40

执行完该命令后,就会往0x00寄存器写入0x40。
此时输入命令 i2cdump -f -y -a 0 0x66

该命令会列出0x66所有寄存器的值,可以看到寄存器0x00的值已经为刚才写入的0x40。
i2cget -f -y -a 0 0x66 0x00
读取寄存器0x00的值,可以看到读出了0x40,和写入的值一致。
0x01寄存器可以控制led的状态,输入以下命令:
i2cset -f -y -a 0 0x66 0x01 0x01
此时底板的LED点亮。
输入以下命令:
i2cset -f -y -a 0 0x66 0x01 0x00
此时底板的LED熄灭。
i2cset -f -y -a 0 0x66 0x01 0x03
此时底板两个LED均点亮。
因为代码中是将该寄存器的低2bit赋值给LED,所以只有在低2bit为1时才能点亮LED。至此,IIC读写通信正常。
3 FSPI通信
FPGA与RK之间通过FSPI进行交互,FPGA会有个RAM用来缓存数据,实现读写回环。
首先加载驱动:
insmod smdt_spi_controller.ko
之后输入命令: lsmod

出现上方红框的字样则表示驱动加载成功。
目录切换道smdt_spi_rw的可执行程序的文件夹下,之后输入命令:
./smdt_spi_rw -d /dev/spidev4.0 -s 50000000 -OH -m 3 -S 1024 -c 1

Byte error rate为0则表示通信正常,数据读写无误。
其中 -s 为设置通信频率,50MHZ。
-m 表示选择模式:1为单线模式,2为双线模式,3为四线模式。
-s 表示选择的传输大小,这里是1024,由于FPGA的ram深度设置为2048,因此一旦传输超过2048的话会出现byte error。
-c 表示传输次数,1表示传输1次。
4 PCIE通信
相关文件路径
smdt_fpga_dma_memcpy_demo项目目录路径:
01-开发资料(百度云盘) -> 05-开发资料 -> 01-Linux系统 -> linux_demo -> smdt_fpga_dma_memcpy_demo
【基于上面项目路径】
FPGA固化文件路径: fpga_sfc -> dram_pcie_pg2l50h.sfc
smdt_fpga_dma_memcpy_demo可执行文件路径: bin -> smdt_dma_memcpy_demo
4.1 固化FPGA程序
FPGA端需要先固化PCIe的工程,因为RK在上电期间只会识别一次PCIe设备,所以要确保在RK启动起来前FPGA已经加载好PCIe的程序,否则会导致识别失败。
1)连接好FGPA的USB下载器。
注意
将USB插入电脑的USB口后下载器亮红灯,板子上电后下载器亮黄灯。

2)打开FPGA固化的软件。
3)给开发板上电,然后点击软件中下图箭头指向的图标。

4)识别到设备会会跳出以下界面,关闭弹出窗口。

5)将鼠标移到芯片处,点击鼠标右键,然后选择下图箭头所指选项。

6)在弹出窗口中,选择我们所需要的 dram_pcie_pg2l50h.sfc,然后点击【Open】。
注意
sfc文件存放路径不要出现中文路径,否则会产生报错无法打开。


7)右键【Outer Flash】,选择下图箭头所指选项【Program】,就会将程序固化。

8)等待固化完成...
9)Console显示以下信息表示固化完成。

4.2 运行PCIE通信Demo
1)重启开发板。
2)在调试工具中输入下面指令,切换adb。
echo 1 > /sys/devices/platform/fe8a0000.usb2-phy/otg_mode
echo 2 > /sys/devices/platform/fe8a0000.usb2-phy/otg_mode
usbdevice start实操效果如下:

3)打开 cmd 检查是否有 adb 设备。
adb devices
4)将 smdt_dma_memcpy_demo 通过adb push指令传入板卡中。
注意
案例将 smdt_dma_memcpy_demo 放在E盘中,打算传入板卡的data目录下。
adb push E:\smdt_dma_memcpy_demo /data/
5)在板卡中查找是否有该文件,并增加可执行权限,在 cmd 中输入下面指令。
adb shell
cd /data
ls
chmod +x ./smdt_dma_memcpy_demo
ls -al
6)通过指令检查 PCIE 是否已经建链,以及是否产生 input_dev_demo。
lspci -vv
cat /proc/bus/input/devices | grep -i "Name=\"input_dev_demo\"" -A 8 -B 1输入 lspci -vv 输出如下图,能显示出0755:0755且lnkSta状态正常,Speed 5GT/S,Widthx2,则表示PCIe已经建链,同样可以看到板子上的LED灯在快速闪烁。
注意
从下图还以看出 PCIe BAR空间映射到了0xf0200000 大小64KByte

输入 cat /proc/bus/input/devices | grep -i "Name=\"input_dev_demo\"" -A 8 -B 1,输出如下图所示,案例中的 input_dev_demo 设备为 event0。

7)执行下面的指令,运行 PCIE 通信 Demo。
echo 1 > /sys/class/pci_bus/0002:21/device/0002:21:00.0/enable
./smdt_dma_memcpy_demo -a 0xf0200000 -s 20480 -c 1 -d /dev/input/event0
相关信息
读写均没有error则表示DMA交互正常。-a 表示地址-s 表示传输字节数-c 表示传输次数-d 表示设备路径
可以调节s和c来改变DMA传输的数据量。
4.3 源码解析
1️⃣ 打开设备节点

2️⃣ 写测试阶段代码解析(ARM -> FPGA)
相关信息
函数:static int perform_write_test(DMAContext *ctx, uint16_t *write_buf);
流程:ARM准备数据 → DMA设置 → 内存映射 → CPU拷贝 → DMA传输 → 等待完成
- DMA配置

- 建立DMA通道

- 内存映射建立

- CPU数据拷贝及性能测试

启动DMA
通过 ioctl 函数启动 DMA,通过 PCIe 总线将数据搬运到 FPGA DRAM。

传输完成等待
程序等待接收驱动上报的 input 事件。

DMA传输性能指标获取
程序接收驱动上报的 input 事件后,表示 DMA 传输完成,通过 ioctl 函数获取 DMA 搬运数据耗时,并计算 DMA 传输速率。

清理关闭
1、清理内存映射
2、关闭DMA传输并返回状态

3️⃣ 读测试阶段代码解析(FPGA -> ARM)
相关信息
函数:static int perform_read_test(DMAContext *ctx, uint16_t *write_buf, uint16_t *read_buf);
流程:DMA设置 → DMA传输启动 → 等待完成 → 内存映射 → CPU拷贝 → 数据验证
- DMA配置

- 建立DMA通道

启动DMA
通过 ioctl 函数启动 DMA,通过 PCIe 总线将 FPGA DRAM 数据搬运到驱动申请的连续内存空间(位于DDR) 。

- FPGA数据传输等待

- 内存映射建立

数据读取
程序接收驱动上报input事件后,将数据从内核空间读取至用户空间。

数据完整性验证
校验数据是否与写入数据一致。

DMA性能指标获取
通过ioctl函数获取DMA搬运数据耗时,并计算DMA传输速率。

清理关闭
1、清理内存映射
2、关闭DMA传输并返回状态

4.4 ioctl函数
具体实现代码可见内核源码中的smdt_pcie_dma_memcpy.c文件
部分代码如下:

具体文件路径
文件路径:SDK/kernel-6.1/drivers/pci/pcie/smdt_pcie_dma_memcpy.c
4.5 FPGA部分内容详解
PCIe通信测试中关于FPGA部分的内容,请参考:基于PCIe的DMA/PIO控制实验
