总线学习3--SPI番外(TODO)

这次主要看看编程实现,在编程上对于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;
}
相关推荐
ThsPool3 小时前
【遥感学习整理 02】ENVI遥感图像处理基础:从数据读取到遥感信息产品
图像处理·人工智能·学习
0566464 小时前
Python康复训练——常用标准库
开发语言·python·学习
0566464 小时前
Python康复训练——控制流与函数
开发语言·python·学习
早睡早起身体好1236 小时前
为什么 Network 中会出现两个同名请求?——CORS 预检请求学习笔记
网络·笔记·学习·网络安全
菩提树下的打坐6 小时前
测试左移的更深一层:用 DDD 战术设计反推测试边界
学习
casual~7 小时前
模逆元计算方法详解:扩展欧几里得算法与费马小定理
学习·算法·逆元
@Mike@7 小时前
06-数据库学习笔记(存储模型与数据压缩)
数据库·笔记·学习
我的xiaodoujiao8 小时前
快速学习Python基础知识详细图文教程14--模块
开发语言·python·学习·测试工具
wdfk_prog8 小时前
嵌入式面试真题学习笔记系列
笔记·学习·面试