这次主要看看编程实现,在编程上对于SPI,有一个概念很重要,因为是全双工,所以RX有没有意义取决于slave。SPI没有单独read。永远同时收发。
1 SPIDEV_TEST
最基本用法
bash
./spidev_test -D /dev/spidev0.0
输出
bash
spi mode: 0
bits per word: 8
max speed: 500000 Hz
TX | FF FF FF FF
RX | 00 00 00 00
发送指定数据
bash
./spidev_test -D /dev/spidev0.0 -p "\x9f\x00\x00" -v
接收
bash
TX:
9f 00 00
RX:
ef 40 18
2 应用层
一般来说是基于spidev来编程,所有的接口都是IOCTL。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#define SPI_DEV "/dev/spidev14.0"
#define SPI_MODE 0
#define SPI_BITS 8
#define SPI_SPEED 1000000
static int spi_fd = -1;
// SPI单次传输通用函数
static int spi_xfer(uint8_t *tx, uint8_t *rx, uint32_t len)
{
struct spi_ioc_transfer xfer = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = len,
.speed_hz = SPI_SPEED,
.bits_per_word = SPI_BITS,
.delay_usecs = 10,
};
return ioctl(spi, SPI_IOC_MESSAGE(1), &xfer);
}
int spi_init(void)
{
spi_fd = open(SPI_DEV, O_RDWR);
if (spi_fd < 0) {
perror("open spi fail");
return -1;
}
uint8_t mode = SPI_MODE;
uint8_t bits = SPI_BITS;
uint32_t speed = SPI_SPEED;
ioctl(spi_fd, SPI_IOC_WR_MODE, &mode);
ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
return 0;
}
int main(void)
{
if (spi_init() < 0)
return -1;
// 第一包:0xF0 00 00 00 00,只发送,丢弃接收数据
uint8_t tx1[] = {0xF0,0x00,0x00,0x00,0x00};
uint8_t dump_rx[5] = {0};
spi_xfer(tx1, dump_rx, sizeof(tx1));
// 第二包:0xF1 00 00 00 00,发送同时读取设备返回数据
uint8_t tx2[] = {0xF1,0x00,0x00,0x00,0x00};
uint8_t rx_data[5] = {0};
spi_xfer(tx2, rx_data, sizeof(tx2));
printf("F1指令返回数据:");
for(int i = 0; i < 5; i++) {
printf("0x%02X ", rx_data[i]);
}
printf("\n");
close(spi_fd);
return 0;
}
3 驱动层
Linux标准SPI的API
| 函数原型 | 功能说明 |
|---|---|
void spi_message_init(struct spi_message *m); |
初始化一个 SPI 事务 message,清空链表 |
void spi_message_add_tail(struct spi_transfer *xfer, struct spi_message *m); |
把一段收发 transfer 挂载到 message 尾部,支持多段连续传输 |
int spi_sync(struct spi_device *spi, struct spi_message *m); |
同步阻塞传输,等待整包收发完成才返回;线程上下文可用,中断禁止调用 |
int spi_write(struct spi_device *spi, const void *buf, size_t len); |
简化单发,只发不收(rx=NULL) |
int spi_read(struct spi_device *spi, void *buf, size_t len); |
简化只读,tx=NULL,发空时钟读数据 |
int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx); |
先发一段,再收一段(完美适配你 F0/F1 两段指令需求) |
| 函数原型 | 功能说明 |
|---|---|
int spi_async(struct spi_device *spi, struct spi_message *m); |
异步发起传输,立即返回,完成后调用 complete 回调 |
void spi_message_complete_callback(struct spi_message *m); |
传输完成回调函数原型 |
和上面效果一样的程序。
cpp
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
// 私有设备数据
struct fp_dev {
struct spi_device *spi;
u8 tx_buf[8];
u8 rx_buf[8];
};
// SPI单次收发封装
static int fp_spi_xfer(struct fp_dev *priv, u8 *tx, u8 *rx, size_t len)
{
struct spi_message msg;
struct spi_transfer xfer = {
.tx_buf = tx,
.rx_buf = rx,
.len = len,
.delay_usecs = 10,
};
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
return spi_sync(priv->spi, &msg);
}
// 业务:先发F0指令,再发F1读返回数据
static int fp_read_data(struct fp_dev *priv)
{
int ret;
u8 tx1[] = {0xF0, 0x00, 0x00, 0x00, 0x00};
u8 dump[5];
// 第一帧 F0,丢弃接收
ret = fp_spi_xfer(priv, tx1, dump, sizeof(tx1));
if (ret)
return ret;
udelay(50); // 硬件时序间隔
// 第二帧 F1,读取返回
u8 tx2[] = {0xF1, 0x00, 0x00, 0x00, 0x00};
ret = fp_spi_xfer(priv, tx2, priv->rx_buf, sizeof(tx2));
return ret;
}
// probe:DTS compatible匹配后执行
static int fp_probe(struct spi_device *spi)
{
struct fp_dev *priv;
priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->spi = spi;
spi_set_drvdata(spi, priv);
dev_info(&spi->dev, "Finger SPI driver probed\n");
return 0;
}
static int fp_remove(struct spi_device *spi)
{
dev_info(&spi->dev, "Finger SPI removed\n");
return 0;
}
// DTS匹配表
static const struct of_device_id fp_of_match[] = {
{ .compatible = "vendor,finger-spi" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, fp_of_match);
static struct spi_driver fp_spi_driver = {
.probe = fp_probe,
.remove = fp_remove,
.driver = {
.name = "fp-spi-driver",
.of_match_table = fp_of_match,
},
};
module_spi_driver(fp_spi_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Linux SPI peripheral minimal driver");
如果需要连续发送不拉CS,可以换成这个。
cpp
// 一次性连续发送F0 + F1,CS全程保持低电平
static int fp_continuous_xfer(struct fp_dev *priv)
{
struct spi_message msg;
struct spi_transfer xfers[2];
int ret;
u8 tx1[] = {0xF0,0,0,0,0};
u8 dump1[5];
u8 tx2[] = {0xF1,0,0,0,0};
u8 rx2[5];
memset(xfers, 0, sizeof(xfers));
// 第一段 F0
xfers[0].tx_buf = tx1;
xfers[0].rx_buf = dump1;
xfers[0].len = 5;
xfers[0].cs_change = 0; // 传输完不释放CS
// 第二段 F1
xfers[1].tx_buf = tx2;
xfers[1].rx_buf = rx2;
xfers[1].len = 5;
xfers[1].cs_change = 1; // 最后一帧释放CS
spi_message_init(&msg);
spi_message_add_tail(&xfers[0], &msg);
spi_message_add_tail(&xfers[1], &msg);
ret = spi_sync(priv->spi, &msg);
if (!ret)
memcpy(priv->rx_buf, rx2, 5);
return ret;
}