1. TL3562 SPI驱动开发环境搭建
1.1 SDK获取与解压
在开始TL3562开发板的SPI驱动开发前,首要任务是搭建完整的开发环境。RK3562官方SDK通常以压缩包形式提供,我们需要在Linux环境下进行解压和配置。
实际操作步骤:
- 创建专用工作目录:
bash复制mkdir -p ~/rk3562_sdk
- 解压SDK包(假设SDK名为rk3562_linux_sdk.tar.gz):
bash复制tar -zxvf rk3562_linux_sdk.tar.gz -C ~/rk3562_sdk
注意:解压前务必确认磁盘空间足够,建议预留至少50GB空间。我曾遇到过因/tmp分区空间不足导致解压失败的情况,后来改用直接解压到用户目录才解决。
1.2 交叉编译工具链配置
RK3562采用ARM架构,必须使用对应的交叉编译工具链。在SDK目录中通常已经包含预编译的工具链,路径一般为:
code复制~/rk3562_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
建议将工具链路径加入环境变量:
bash复制export PATH=$PATH:~/rk3562_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
验证工具链是否可用:
bash复制aarch64-linux-gnu-gcc --version
2. 设备树(DTS)配置与内核编译
2.1 SPI设备树节点修改
要让TL3562识别SPI接口,必须在内核设备树中正确配置。关键步骤如下:
- 定位设备树文件:
bash复制cd ~/rk3562_sdk/kernel/arch/arm64/boot/dts/rockchip
- 找到目标设备树文件(如tl3562-evm.dts),修改spi0节点:
dts复制&spi0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins>;
#address-cells = <1>;
#size-cells = <0>;
spidev@0 {
compatible = "rockchip,spidev";
reg = <0>;
spi-max-frequency = <50000000>;
};
};
关键点:除了修改status为"okay"外,还需确认pinctrl配置是否正确。我曾遇到过因pinctrl配置缺失导致SPI无法工作的情况,后来通过查阅芯片手册确认了正确的引脚复用配置。
2.2 内核编译与烧录
完成DTS修改后,需要重新编译内核:
- 在SDK根目录执行编译:
bash复制./build.sh kernel
- 编译完成后,生成的boot.img位于:
code复制~/rk3562_sdk/kernel/boot.img
烧录工具使用RKDevTool:
- 开发板进入Loader模式(通常需要按住特定按键上电)
- 连接USB到PC
- 在RKDevTool中选择编译生成的boot.img
- 点击"执行"按钮开始烧录
烧录完成后,通过串口终端登录开发板,验证SPI设备节点:
bash复制ls /dev/spidev*
如果看到/dev/spidev0.0,说明设备树修改成功。
3. SPI测试程序开发与调试
3.1 交叉编译测试程序
编写简单的SPI测试程序spi_test.c:
c复制#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#define SPI_DEV "/dev/spidev0.0"
int main() {
int fd;
char tx_buf[] = "Tronlong_SPI_Data_OK";
char rx_buf[32] = {0};
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx_buf,
.rx_buf = (unsigned long)rx_buf,
.len = sizeof(tx_buf),
.delay_usecs = 0,
.speed_hz = 500000,
.bits_per_word = 8,
};
fd = open(SPI_DEV, O_RDWR);
if (fd < 0) {
perror("open spi device");
return -1;
}
if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
perror("ioctl SPI_IOC_MESSAGE");
close(fd);
return -1;
}
printf("Received: %s\n", rx_buf);
close(fd);
return 0;
}
使用交叉编译器编译:
bash复制aarch64-linux-gnu-gcc spi_test.c -o spi_test
3.2 程序传输与运行
将编译好的程序传输到开发板,推荐几种方式:
- U盘方式:
bash复制# 开发板上操作
mkdir /mnt/usb
mount /dev/sdb1 /mnt/usb
cp /mnt/usb/spi_test /home
- 网络方式(如果网络已配置):
bash复制# 开发板上操作
wget http://192.168.1.100/spi_test
- 串口传输(适合小文件):
bash复制# PC端
sz spi_test
# 开发板端
rz
4. SPI回环测试与验证
4.1 硬件连接准备
进行SPI回环测试前,需要正确连接硬件:
-
查阅TL3562原理图,确认SPI0引脚定义:
- Pin7: SPI0_MOSI
- Pin8: SPI0_MISO
- Pin9: SPI0_CLK
- Pin10: SPI0_CS0
-
使用跳线帽短接MOSI(Pin7)和MISO(Pin8)
安全提示:务必确认引脚定义后再连接,我曾见过因误接电源导致SPI控制器烧毁的案例。建议先用万用表测量确认。
4.2 测试执行与结果验证
运行测试程序:
bash复制./spi_test
预期输出:
code复制Received: Tronlong_SPI_Data_OK
如果看到发送和接收的数据一致,说明:
- SPI驱动工作正常
- 硬件连接正确
- 数据传输完整
5. 常见问题排查指南
5.1 SPI设备节点不存在
可能原因及解决方案:
-
设备树未正确修改:
- 确认spi0节点status="okay"
- 检查pinctrl配置是否正确
-
内核配置未启用SPI驱动:
- 执行make menuconfig
- 确认Device Drivers -> SPI support已启用
-
烧录的镜像不包含修改:
- 确认烧录的是最新编译的boot.img
- 检查编译日志是否有错误
5.2 测试程序运行报错
常见错误及解决方法:
-
"Permission denied":
bash复制chmod 666 /dev/spidev0.0 -
"Invalid argument":
- 检查spi_ioc_transfer结构体参数
- 确认SPI模式设置正确
-
数据收发异常:
- 确认硬件连接可靠
- 检查时钟频率是否过高
- 尝试降低传输速度
5.3 性能优化建议
-
调整SPI时钟频率:
c复制int speed = 1000000; // 1MHz ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); -
使用DMA传输(如果支持):
- 在内核配置中启用SPI_DMA选项
- 修改设备树添加dmas属性
-
双缓冲技术:
- 准备两个缓冲区交替使用
- 实现零等待的连续传输
6. 深入理解SPI驱动架构
6.1 Linux SPI子系统架构
Linux SPI子系统分为三层:
- 核心层(SPI Core):提供核心API和框架
- 控制器驱动(SPI Master):芯片特定的SPI控制器实现
- 设备驱动(SPI Device):具体外设的驱动
TL3562的SPI控制器驱动位于:
code复制drivers/spi/spi-rockchip.c
6.2 SPI数据传输流程
一次完整的SPI传输涉及:
- 应用层:通过ioctl发起传输请求
- VFS层:处理设备文件操作
- SPI核心层:验证参数,准备传输
- 控制器驱动:配置硬件寄存器,启动传输
- 中断处理:传输完成中断处理
6.3 调试技巧
-
启用调试日志:
bash复制echo 8 > /proc/sys/kernel/printk dmesg | grep spi -
使用逻辑分析仪:
- 抓取实际SPI波形
- 验证时钟极性和相位
-
Sysfs调试接口:
bash复制cat /sys/kernel/debug/spi/spi0/registers
7. 进阶开发建议
7.1 编写自定义SPI驱动
当连接特定SPI设备时,可能需要编写专用驱动:
- 定义设备树节点:
dts复制&spi0 {
my_device@0 {
compatible = "my,spi-device";
reg = <0>;
spi-max-frequency = <1000000>;
};
};
- 实现驱动基本结构:
c复制static const struct of_device_id my_spi_dt_ids[] = {
{ .compatible = "my,spi-device" },
{}
};
static struct spi_driver my_spi_driver = {
.driver = {
.name = "my_spi_device",
.of_match_table = my_spi_dt_ids,
},
.probe = my_spi_probe,
.remove = my_spi_remove,
};
7.2 用户态与内核态SPI访问对比
| 特性 | 用户态(spidev) | 内核态驱动 |
|---|---|---|
| 开发难度 | 简单 | 复杂 |
| 性能 | 较低 | 高 |
| 功能完整性 | 有限 | 完整 |
| 适用场景 | 原型验证 | 产品开发 |
| 稳定性 | 一般 | 高 |
7.3 实际项目经验分享
-
时钟配置陷阱:
- RK3562的SPI时钟源来自PLL
- 需要确保PLL已正确配置
- 实测最大稳定时钟约50MHz
-
DMA传输优化:
- 启用CONFIG_SPI_ROCKCHIP_DMA
- 设备树添加dmas和dma-names属性
- 大数据量传输性能提升3-5倍
-
电源管理:
- 空闲时关闭SPI时钟
- 使用runtime PM框架
- 可降低功耗约20mA
通过这个完整的SPI驱动开发流程,我们不仅实现了基本功能,还深入理解了Linux SPI子系统的工作原理。在实际项目中,这些知识可以帮助我们快速定位问题,优化性能,开发出更稳定的SPI设备驱动。
