IO特性
SPI接口一般使用四条信号线通信:
SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)
MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
SCLK:串行时钟信号,由主设备产生。
CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突
四种模式
Mode0:CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿(准备数据),(发送数据)数据发送是在下降沿。
Mode1:CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
Mode2:CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
Mode3:CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
spi_bus
[图片]
SPI 总线设备驱动模型
设备树处理过程
我们可以下载安卓源码查看:
设备树在devices_tree,驱动在driver目录,
git clone https://android.googlesource.com/kernel/msm
MSM:指的是 Qualcomm 的 Mobile Station Modem 平台
spi_master:是当前spi_dev的父节点
spi 设备树上的设备节点中的信息都会转化为spi_device中的成员值,呈一一对应关系。
解析spi设备树的核心函数为 of_register_spi_devices
在of_register_spi_devices函数中解析设备树
访问 spi_master节点下的每一个子节点
解析子节点中的devices信息
spi_devices配置好后,使用spi_add_devices添加设备
spidev.c文件解析,spidev.c是spi通用的设备驱动文件,一些简单的spi设备可以直接使用spidev.c
spidev.c分析:https://blog.csdn.net/2301_77015050/article/details/132146419?ops_request_misc=&request_id=&biz_id=102&utm_term=spi%E9%A9%B1%E5%8A%A8&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-4-132146419.142v100pc_search_result_base2&spm=1018.2226.3001.4187
使用spidev.c操作spi_dac模块
这个电路图展示了一个使用 TLC5615 数模转换器(DAC)的电路。具体来说,电路图分成了两个主要部分:左边部分是连接到外部 SPI 接口的部分,右边部分是 DAC 输出和相应的指示电路。以下是详细的解释:
左边部分(H2 Header 与 SPI 接口)
H2 是一个 8 引脚的连接器,用于连接到外部 SPI 接口:
- 引脚 1 (5V): 提供5V电源。
- 引脚 2 (GND): 接地。
- 引脚 3 (MOSI): 主设备输出从设备输入,用于发送数据到 DAC(TLC5615)。
- 引脚 4 (MISO): 主设备输入从设备输出,这里未使用。
- 引脚 5 (SCLK): 串行时钟信号。
- 引脚 6 (CS): 片选信号,当低电平时,DAC 被选中。
- 引脚 7、8: 未使用。
C1 和 C2 是电源去耦电容,用于滤除电源噪声:
- C1 (22nF)
- C2 (100nF)
中间部分(TLC5615 数模转换器)
U1 是 TLC5615 数模转换器,它将数字信号转换为模拟信号:
- 引脚 1 (DIN): 数据输入,连接到 MOSI。
- 引脚 2 (SCLK): 串行时钟输入。
- 引脚 3 (CS): 片选输入。
- 引脚 4 (DOUT): 数据输出,这里未使用。
- 引脚 5 (AGND): 模拟地。
- 引脚 6 (REFIN): 参考电压输入。
- 引脚 7 (VDD): 电源电压。
- 引脚 8 (OUT): 模拟信号输出。
右边部分(输出信号处理和指示电路) - 电容 C3 (100nF): 电源去耦电容。
- 电阻 R1 (5.1kΩ): 限流电阻,用于 LED1。
- LED1: 用于显示 DAC 输出状态。
- LM4040(U2): 精密电压参考,用于提供稳定的参考电压(2.048V)。
- 电阻 R2 (10kΩ): 拉低电阻。
- 电阻 R3 (5.1kΩ): 限流电阻,用于 LED2。
- LED2: 用于显示电源状态。
工作原理 - 电源部分: H2 提供的 5V 电源通过 C1 和 C2 去耦之后,供给 TLC5615(U1)和 LM4040(U2)。
- SPI 通信部分: 外部设备通过 H2 的 MOSI, SCLK 和 CS 引脚与 TLC5615 进行 SPI 通信,发送的数字信号经 DAC 转换为模拟信号,从 U1 的 OUT 引脚输出。
- 参考电压部分: LM4040 提供稳定的 2.048V 参考电压给 TLC5615 的 REFIN 引脚,确保 DAC 输出的精度。
- 输出信号处理部分:
- 从 U1 的 OUT 引脚输出的模拟信号通过 R1 限流并驱动 LED1,指示 DAC 的输出状态。
- LED2 通过 R3 限流并一直亮起,指示电路板的电源状态。
总体来说,这个电路实现了通过 SPI 接口控制 DAC 输出模拟信号,并通过 LED1 和 LED2 指示输出状态和电源状态。
- CS为低:
- 在SCLK的上升沿,从DIN采集16位数据,存入上图中的16-Bit Shift Register
- 在CS的上升沿,把16-Bit Shift Register中的10位数据传入10-Bit DAC Register,作为模拟量在OUT引脚输出
注意: - 传输的16位数据中,高4位是无意义的
- 中间10位才被转换为模拟量
- 最低2位必须是0
- 从DOUT出来的是上一次发送的数据
上机实验:
修改设备树,在spi节点下添加子节点:
dac: dac@00 {
compatible = "spidev";
reg = <0>;
spi-max-frequency = <20000000>; //从DAC芯片手册获得
};
编译设备树:
make dtbs 编译设备树
make modules 编译spidev.ko驱动
开启编译选项
make menuconfig
应用层代码:参考tools\spi\spidev_fdx.c
read/write只能单向读写所以要使用ioctl
ioctl的具体实现在spidev.c的spidev_ioctl函数中
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
/* dac_test /dev/spidevB.D <val> */
int main(int argc, char **argv)
{
int fd;
unsigned int val;
struct spi_ioc_transfer xfer[1];
unsigned char tx_buf[2];
unsigned char rx_buf[2];
int status;
if(argc != 3) {
printf("Usage: %s /dev/spidevB.D <val>\n", argv[0]);
return 0;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
val = strtoul(argv[2], NULL, 0);
val <<= 2; /* bit0, bit1 = 0b00 */
val &= 0xFFC; /* only reserve 10 bit */
tx_buf[0] = (val>>8) & 0xff;
tx_buf[1] = val & 0xff;
memset(xfer, 0, sizeof xfer);
xfer[0].len = 2;
xfer[0].tx_buf = (unsigned long) tx_buf;
xfer[0].rx_buf = (unsigned long) rx_buf;
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);//告诉内核我们要发送1个SPI消息。这部分中的1表示我们要发送的消息数量。
if (status < 0) {
printf("SPI_IOC_MESSAGE\n");
return -1;
}
/* print result */
val = (rx_buf[0] <<8)|(rx_buf[1]);
val >>= 2;
printf("pre val = %d\n", val);
return 0;
}
-
unlocked_ioctl 是标准的 ioctl 处理函数,用于在设备文件上执行各种控制操作。它通常用于处理来自用户空间的 ioctl
请求。 -
compat_ioctl 是用于处理 32 位用户空间应用程序在 64 位内核上运行时的兼容性 ioctl 请求。它确保 32
位应用程序能够正确调用 ioctl,并处理 32 位和 64 位之间的数据结构差异