linux下的spi子系统
概念通信模式可以分为单工、半双工和全双工单工通信指信号只在一个方向上传输仅 能发送或接收而半双工通信指信号可以在俩个方向上传输但某一个时刻只允许发送或接收而全双工通信指数据同时在俩个方向上传输而 SPI 只需要4根信号线(SCLK、MOSI、MISO、CS)即可完成主从设备之间的数据交换实现全双 工通信。减少了引脚数,简化了接线通信原理:本质就是进行数据交换就是你发给我一个数据我发给你一个数据这样比如你只想读数据你可以发OxFF这样得到的是他给你的数据你只想写从机发给你0xFF这样时钟和相位:在SPI的通信之前需要先确定时钟信号的默认状态以及时钟信号的采样时间这两个参数 由CPOL(时钟极性ClockPolarity)和 CPHA(时钟相位ClockPhase)来确定1.CPOL(时钟极性ClockPolarity) CPOL 定义了时钟信号的默认状态(即空闲状态)。 CPOL 0 时,表示时钟信号在空闲状态下为低电平(0)。 CPOL 1 时,表示时钟信号在空闲状态下为高电平(1)。 2.CPHA(时钟相位ClockPhase) CPHA 定义了数据信号相对于时钟信号的采样时间。 CPHA0 时,表示数据在时钟的第一个边沿(上升或下降)被采样。 CPHA1 时,表示数据在时钟的第二个边沿(上升或下降)被采样。RK3568的SPI介绍·支持4个SPI控制器 ·1个控制器支持1个片选输出其余3个控制器各支持2个片选输出 ·支持主机模式和从机模式,可通过软件进行配置切换SPI子系统框架三个层的主要作用和I2C一样修改设备树:spi0 { status okay; pinctrl-0 spi0m1_cs0 spi0m1_pins; pinctrl-1 spi0m1_cs0 spi0m1_pins_hs; mcp2515: mcp25150 { compatible my-mcp2515; //表示使用硬件片选0频率 //如果软件片选的话 //cs-gpios; reg 0; /* SPI片选号0表示使用SPI0的CS0对应设备树的片选引脚 */ //spi-cpha; /* SPI时钟相位配置不写表示CPHA0第一个边沿采样数据 */ //spi-cpol; /* SPI时钟极性配置不写表示CPOL0空闲时时钟为低电平 */ //spi-lsb-first; /* 数据传输格式不写表示MSB先行高位先发送MCP2515必须用这个 */ //spi-cs-high; /* 片选有效电平不写表示低电平有效MCP2515默认低电平有效 */ reg 0; spi-max-frequency 10000000; status okay; }; };SPI的驱动代码:#include linux/init.h #include linux/module.h #include linux/spi/spi.h // MCP2515设备初始化函数 static int mcp2515_probe(struct spi_device *spi) { printk(This is mcp2515 probe\n); return 0; } // MCP2515设备移除函数 static int mcp2515_remove(struct spi_device *spi) { return 0; } // MCP2515设备匹配表,用于设备树匹配 static const struct of_device_id mcp2515_of_match_table[] { { .compatible my-mcp2515 }, { } }; // MCP2515设备ID匹配表,用于总线匹配 static const struct spi_device_id mcp2515_id_table[] { { mcp2515, 0 }, { } }; // MCP2515 SPI驱动结构体 static struct spi_driver spi_mcp2515 { .probe mcp2515_probe, // 探测函数 .remove mcp2515_remove, // 移除函数 .driver { .name mcp2515, // 驱动名称 .owner THIS_MODULE, // 所属模块 .of_match_table mcp2515_of_match_table, // 设备树匹配表 }, .id_table mcp2515_id_table, // 设备ID匹配表 }; // 驱动初始化函数 static int __init mcp2515_init(void) { int ret; // 注册SPI驱动 ret spi_register_driver(spi_mcp2515); return ret; } // 驱动退出函数 static void __exit mcp2515_exit(void) { // 注销SPI驱动 spi_unregister_driver(spi_mcp2515); } module_init(mcp2515_init); module_exit(mcp2515_exit); MODULE_LICENSE(GPL);注册字符设备代码:(和之前一样可以不看)#include linux/init.h #include linux/module.h #include linux/spi/spi.h #include linux/cdev.h #include linux/fs.h dev_t dev_num; // 设备号 struct cdev mcp2515_cdev; // 字符设备对象 struct class *mcp2515_class; // 设备类 struct device *mcp2515_dev; // 设备节点 struct spi_device *spi_global; // 保存spi设备 // 打开设备 int mcp2515_open(struct inode *inode, struct file *file) { return 0; } // 读设备 ssize_t mcp2515_read(struct file *file, char __user *buf, size_t size, loff_t *offset) { return 0; } // 写设备 ssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { return 0; } // 关闭设备 int mcp2515_release(struct inode *inode, struct file *file) { return 0; } // 文件操作集合 struct file_operations fops { .open mcp2515_open, .read mcp2515_read, .write mcp2515_write, .release mcp2515_release, }; // 设备树匹配成功后进入probe int mcp2515_probe(struct spi_device *spi) { spi_global spi; // 保存spi设备 // 1. 申请设备号 alloc_chrdev_region(dev_num, 0, 1, mcp2515); // 2. 初始化字符设备 cdev_init(mcp2515_cdev, fops); mcp2515_cdev.owner THIS_MODULE; cdev_add(mcp2515_cdev, dev_num, 1); // 3. 创建类和设备节点 /dev/mcp2515 mcp2515_class class_create(THIS_MODULE, mcp2515); mcp2515_dev device_create(mcp2515_class, NULL, dev_num, NULL, mcp2515); printk(mcp2515 probe ok\n); return 0; } // 移除设备 int mcp2515_remove(struct spi_device *spi) { // 销毁字符设备 device_destroy(mcp2515_class, dev_num); class_destroy(mcp2515_class); cdev_del(mcp2515_cdev); unregister_chrdev_region(dev_num, 1); return 0; } // 设备树匹配表 static const struct of_device_id mcp2515_match[] { { .compatible my-mcp2515 }, {}, }; // spi驱动id表 static const struct spi_device_id mcp2515_id[] { { mcp2515, 0 }, {}, }; // spi驱动结构体 struct spi_driver mcp2515_spi_driver { .probe mcp2515_probe, .remove mcp2515_remove, .driver { .name mcp2515, .of_match_table mcp2515_match, }, .id_table mcp2515_id, }; // 驱动入口 static int __init mcp2515_init(void) { spi_register_driver(mcp2515_spi_driver); return 0; } // 驱动出口 static void __exit mcp2515_exit(void) { spi_unregister_driver(mcp2515_spi_driver); } module_init(mcp2515_init); module_exit(mcp2515_exit); MODULE_LICENSE(GPL);SPI的API/* SPI同步写函数只发送数据不接收数据阻塞式传输 * spi : 指向SPI设备的指针指定与哪个设备通信 * buf : 发送数据的缓冲区存放要写入的数据 * len : 要发送的数据长度单位字节 * 返回值: 0成功负数失败 */ static inline int spi_write(struct spi_device *spi, const void *buf, size_t len); /* SPI同步读函数只接收数据不发送数据阻塞式传输 * spi : 指向SPI设备的指针 * buf : 接收数据的缓冲区存放读取到的数据 * len : 要读取的数据长度单位字节 * 返回值: 0成功负数失败 */ static inline int spi_read(struct spi_device *spi, void *buf, size_t len);MCP2515复位函数MCP2515外设通信必须先发指令再发数据比如0XC0复位0X02写寄存器// MCP2515芯片复位函数 void mcp2515_reset(void) { int ret; char write_buf[] {0xc0}; // 复位指令 0xC0 ret spi_write(spi_dev, write_buf, sizeof(write_buf)); // 发送复位命令 if (ret 0) { printk(spi_write is error\n); // 打印错误信息 } }读写寄存器函数:/* SPI 先写后读函数先发送数据再读取数据一条SPI总线完成 * spi : SPI设备指针 * tx_buf : 发送数据缓冲区 * tx_len : 发送数据长度字节 * rx_buf : 接收数据缓冲区 * rx_len : 接收数据长度字节 * 返回值 : 0成功负数失败 */ static inline int spi_write_then_read(struct spi_device *spi, const void *tx_buf, unsigned int tx_len, void *rx_buf, unsigned int rx_len);/* 读取MCP2515寄存器的值 * reg : 要读取的寄存器地址 * 返回值: 读取到的寄存器数据 */ char mcp2515_read_reg(char reg) { char write_buf[] {0x03, reg}; // 读指令0x03 寄存器地址 char read_buf; // 存放读取到的数据 int ret; // 先发送指令地址再读取1字节数据 ret spi_write_then_read(spi_dev, write_buf, sizeof(write_buf), read_buf, sizeof(read_buf)); if (ret 0) { printk(spi_write_then_read error\n); return ret; } return read_buf; // 返回读到的数据 }部分代码如下:其他与上面一样probe函数加上// MCP2515 设备探测函数设备树匹配成功后执行 int mcp2515_probe(struct spi_device *spi) { mcp2515_reset(); // 复位MCP2515设备 value mcp2515_read_reg(0x0e); // 读取寄存器0x07的值 printk(value is %x\n, value); // 打印读取到的寄存器值 return 0; // 返回成功 }写寄存器函数:// MCP2515写寄存器函数向指定寄存器写入数据 void mcp2515_write_reg(char reg, char value) { int ret; // 写指令0x02 寄存器地址 要写入的值 char write_buf[] {0x02, reg, value}; // 通过SPI发送完整数据 ret spi_write(spi_dev, write_buf, sizeof(write_buf)); if(ret 0){ printk(mcp2515_write_reg error\n); } }// MCP2515修改寄存器位函数位修改指令0x05 // reg: 要修改的寄存器地址 // mask: 位屏蔽字决定哪些位可被修改 // value: 要写入的位数据 void mcp2515_change_regbit(char reg, char mask, char value) { int ret; // 写缓冲区位修改指令0x05 寄存器地址 屏蔽码 数据 char write_buf[] {0x05, reg, mask, value}; // 发送SPI数据 ret spi_write(spi_dev, write_buf, sizeof(write_buf)); if(ret 0){ printk(mcp2515_change_regbit error\n); } }部分代码如下:其他与上面一样probe函数加上//eg:0xe0 → 二进制 11100000表示只改最高 3 位其他位不动 //0x40 → 二进制 01000000表示把那 3 位改成 010 // MCP2515 设备探测函数设备树匹配成功后执行 int mcp2515_probe(struct spi_device *spi) { // 配置 MCP2515 寄存器CAN 波特率 模式 中断 接收 mcp2515_write_reg(CNF1, 0x01); // 配置波特率寄存器1 mcp2515_write_reg(CNF2, 0xb1); // 配置波特率寄存器2 mcp2515_write_reg(CNF3, 0x05); // 配置波特率寄存器3 mcp2515_write_reg(RXB0CTRL, 0x60); // 配置接收缓冲器0 mcp2515_write_reg(CANINTE, 0x05); // 打开接收中断使能 // 修改 CANCTRL 寄存器的特定位设置模式 mcp2515_change_regbit(CANCTRL, 0xe0, 0x40); // 读取寄存器并打印验证配置是否成功 value mcp2515_read_reg(0x0e); printk(value is %x\n, value); }设备节点的read,write函数write函数/* * 函数名称mcp2515_write * 功能应用层 write 时触发用于 MCP2515 发送一帧 CAN 数据 * 参数 * file : 文件结构体 * buf : 用户空间数据缓冲区 * size : 要写入的数据长度 * offset : 文件偏移地址 * 返回值成功返回写入长度失败返回负数 */ size_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { char w_kbuf[13] {0}; // 内核缓冲区存储CAN帧数据 int ret; int i; // 配置发送控制器TXB0CTRL使能发送相关位 mcp2515_change_regbit(TXB0CTRL, 0x03, 0x03); // 从用户空间拷贝数据到内核空间 ret copy_from_user(w_kbuf, buf, size); if(ret) { printk(copy_from_user w_kbuf is error\n); return -1; } // 将13字节CAN帧数据依次写入MCP2515发送寄存器地址从0x31开始 for(i 0; i sizeof(w_kbuf); i) { mcp2515_write_reg(0x31 i, w_kbuf[i]); } // 启动发送将TXB0CTRL的请求发送位拉高 mcp2515_change_regbit(TXB0CTRL, 0x08, 0x08); // 等待发送完成查询CANINTF的发送完成标志位 while(!(mcp2515_read_reg(CANINTF) (1 2))); // 清除发送完成标志位 mcp2515_change_regbit(CANINTF, 0x04, 0x00); return size; // 返回成功写入的长度 }read函数#define CANINTF 0x2c // 读设备操作函数从MCP2515读取一帧CAN数据到用户缓冲区 ssize_t mcp2515_read(struct file *file, char __user *buf, size_t size, loff_t *offset) { char r_kbuf[13] {0}; // 内核缓冲区存储从MCP2515读取的一帧CAN数据 int i; int ret; // 等待接收完成检测CANINTF的bit0接收中断标志 while(!(mcp2515_read_reg(CANINTF) (1 0))); // 从MCP2515接收缓冲区 RXB0 读取13字节数据 for(i 0; i sizeof(r_kbuf); i){ r_kbuf[i] mcp2515_read_reg(0x61 i); } // 清除接收完成标志位 mcp2515_change_regbit(CANINTF, 0x01, 0x00); // 将内核数据复制到用户空间 ret copy_to_user(buf, r_kbuf, size); if(ret){ printk(copy_to_user r_kbuf is error\n); return -1; } return 0; }测试APP#include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h // 主函数程序入口点 int main(int argc, char *argv[]) { int fd; // 文件描述符 int i; // 循环变量 // 写缓冲区13字节的CAN数据帧 char w_buf[13] {0x66, 0x08, 0x22, 0x33, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; // 读缓冲区存放读取到的CAN数据 char r_buf[13] {0}; // 打开设备 /dev/mcp2515读写方式打开 fd open(/dev/mcp2515, O_RDWR); if (fd 0) { printf(open /dev/mcp2515 error\n); return -1; } // 发送数据到CAN驱动 write(fd, w_buf, sizeof(w_buf)); // 读取CAN接收到的数据 read(fd, r_buf, sizeof(r_buf)); // 打印读取到的数据 for (i 0; i 13; i) { printf(r_buf[%d] is %x\n, i, r_buf[i]); } // 关闭设备 close(fd); return 0; }SPI设备驱动的使用,生成设备节点(修改compatitle)修改设备树(就是compatitle)spi0 { status okay; pinctrl-0 spi0m1_cs0 spi0m1_pins; pinctrl-1 spi0m1_cs0 spi0m1_pins_hs; mcp2515: mcp25150 { compatible rockchip,spidev; reg 0; spi-max-frequency 10000000; status okay; }; };spidev_test 工具使用make cc你的交叉工具链路径 LD你的连接路径写入和读取数据spidev_test-D /dev/spidevX.Y-s 1000000-b 8-d 1000-H-p hello这条命令会向 SPI 设备写入字符串 hello并以十六进制模式显示设备的响应数据。-b 8 指定每个字的位数为 8-d 1000 设置 1000 微秒的延迟。回环测试没问题就可以挂载外设了./spidev_test -D /dev/spidev0.0 -l -v应用程序如何使用spi首先肯定也是ioctl命令来交互数据一些宏定义如下/* 读取 / 写入 SPI 模式SPI_MODE_0 ~ SPI_MODE_3限制为 8 位 */ #define SPI_IOC_RD_MODE _IOR(SPI_IOC_MAGIC, 1, __u8) // 读取 SPI 模式 #define SPI_IOC_WR_MODE _IOW(SPI_IOC_MAGIC, 1, __u8) // 写入 SPI 模式 /* 读取 / 写入 SPI 位顺序 */ #define SPI_IOC_RD_LSB_FIRST _IOR(SPI_IOC_MAGIC, 2, __u8) // 读取 SPI 低位优先 #define SPI_IOC_WR_LSB_FIRST _IOW(SPI_IOC_MAGIC, 2, __u8) // 写入 SPI 低位优先 /* 读取 / 写入 SPI 设备字长1 ~ N */ #define SPI_IOC_RD_BITS_PER_WORD _IOR(SPI_IOC_MAGIC, 3, __u8) // 读取 SPI 每字位数 #define SPI_IOC_WR_BITS_PER_WORD _IOW(SPI_IOC_MAGIC, 3, __u8) // 写入 SPI 每字位数 /* 读取 / 写入 SPI 设备默认最大速度Hz */ #define SPI_IOC_RD_MAX_SPEED_HZ _IOR(SPI_IOC_MAGIC, 4, __u32) // 读取 SPI 最大速度Hz #define SPI_IOC_WR_MAX_SPEED_HZ _IOW(SPI_IOC_MAGIC, 4, __u32) // 写入 SPI 最大速度Hz /* 读取 / 写入 SPI 模式字段 */ #define SPI_IOC_RD_MODE32 _IOR(SPI_IOC_MAGIC, 5, __u32) // 读取 SPI 模式32 位 #define SPI_IOC_WR_MODE32 _IOW(SPI_IOC_MAGIC, 5, __u32) // 写入 SPI 模式32 位用户代码如下都是固定代码别utransfer和配置模式等都是不变的代码#includestdio.h #includesys/ioctl.h #includelinux/spi/spidev.h #includesys/types.h #includesys/stat.h #includefcntl.h #includestring.h #define RESET 0xc0 // 复位命令 #define CANSTAT 0x0e // CAN 状态寄存器地址 #define READ 0x03 // 读命令 #define CANCTRL 0x0f // CAN 控制寄存器地址 #define WRITE 0x02 // 写命令 int fd; // SPI 设备文件描述符 int mode SPI_MODE_0; // SPI 模式 int bits 8; // 每字比特数 int speed 10000000; // 最大 SPI 总线速度Hz int delay; // 延迟时间微秒 /* * 初始化 SPI 通信 * 返回 0 表示成功-1 表示失败 */ int spi_init(void) { int ret; // 打开 SPI 设备文件 fd open(/dev/spidev0.0, O_RDWR); /* 设置 SPI 模式 */ ret ioctl(fd, SPI_IOC_WR_MODE32, mode); ret ioctl(fd, SPI_IOC_RD_MODE32, mode); /* 设置每字比特数 */ ret ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, bits); ret ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, bits); /* 设置最大传输速度 */ ret ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, speed); ret ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, speed); printf(SPI 模式: 0x%x\n, mode); printf(每字比特数: %d\n, bits); printf(最大速度: %d Hz (%d KHz)\n, speed, speed / 1000); return 0; } /* * 执行 SPI 数据传输 * 参数 * fd - SPI 设备文件描述符 * tx - 发送缓冲区 * rx - 接收缓冲区 * len - 数据长度 * 返回 0 表示成功-1 表示失败 */ int transfer(int fd, char *tx, char *rx, int len) { int ret; struct spi_ioc_transfer tr { .tx_buf (unsigned long)tx, .rx_buf (unsigned long)rx, .len len, .delay_usecs delay, .speed_hz speed, .bits_per_word bits, }; ret ioctl(fd, SPI_IOC_MESSAGE(1), tr); if (ret 1) { printf(无法发送 SPI 消息\n); return -1; } return 0; } int main(int argc, char *argv[]) { char reset_cmd[1] {RESET}; // 复位命令数组 char rd_canstat[2] {READ, CANSTAT}; // 读 CAN 状态寄存器 char canstat[3] {0}; // 存储 CAN 状态的缓冲区 char wr_canctrl[3] {WRITE, CANCTRL, 0x00}; // 写 CAN 控制寄存器 // 初始化 SPI 通信 spi_init(); // 1. 发送复位命令 transfer(fd, reset_cmd, NULL, sizeof(reset_cmd)); // 2. 读取 CAN 状态 transfer(fd, rd_canstat, canstat, sizeof(canstat)); printf(CAN 状态为: %x\n, canstat[2]); // 清空缓冲区 memset(canstat, 0, sizeof(canstat)); // 3. 写入 CAN 控制寄存器 transfer(fd, wr_canctrl, NULL, sizeof(wr_canctrl)); // 4. 再次读取 CAN 状态 transfer(fd, rd_canstat, canstat, sizeof(canstat)); printf(CAN 状态为: %x\n, canstat[2]); close(fd); return 0; }软件模拟SPI首先开启内核驱动修改设备树compatitle改成下面这个目的是匹配驱动spi5: spigpiol { compatible spi-gpio; #address-cells 1; sck-gpio gpio0 RK_PB0 GPIO_ACTIVE_LOW; miso-gpio gpio1 RK_PB0 GPIO_ACTIVE_LOW; mosi-gpio gpio1 RK_PB1 GPIO_ACTIVE_LOW; cs-gpios gpio1 RK_PB2 GPIO_ACTIVE_LOW; num-chipselects 1; pinctrl-names default; pinctrl-0 spi5_gpios; status disabled; };pinctrl添加下面节点:spi5_gpios: gpios { rockchip,pins 0 RK_PB0 0 pcfg_pull_none, 1 RK_PB0 0 pcfg_pull_none, 1 RK_PB1 0 pcfg_pull_none, 1 RK_PB2 0 pcfg_pull_none; };spi下面挂载设备spi5 { status okay; mcp2515: mcp25150 { compatible rockchip,spidev; reg 0; spi-max-frequency 10000000; status okay; }; };然后回环测试即可移植MCP2515驱动1:首先把驱动编译内核2修改设备树描述硬件信息spi0 { status okay; pinctrl-0 spi0m1_cs0 spi0m1_pins; pinctrl-1 spi0m1_cs0 spi0m1_pins_hs; mcp2515: mcp25150 { compatible microchip,mcp2515; reg 0; spi-max-frequency 10000000; //下面是移植别人驱动要添加的引脚配置 pinctrl-names default; pinctrl-0 mcp2515_int; interrupt-parent gpio0; interrupts RK_PB0 IRQ_TYPE_EDGE_FALLING; clocks clk8m; status okay; }; clk8m: clk8m { compatible fixed-clock; #clock-cells 0; clock-frequency 8000000; }; };pinctrl添加节点:mcp2515-gpio { mcp2515_int: mcp2515-int { rockchip,pins 0 RK_PB0 RK_FUNC_GPIO pcfg_pull_none; }; };
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2475893.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!