Linux驱动-SPI-mcp2515 复位函数
Linux驱动-SPI-mcp2515 复位函数
文章目录
前言
这里只简单写一个复位函数,为什么要写复位函数是通过外设数据手册来的,所以要先搞懂数据手册,通过数据手册来分析驱动业务流程,然后一步一步进行外设的驱动的业务 无外乎读写外设寄存器进行控制。
一、参考文档
Linux驱动-初步认识SPI
Linux驱动-SPI外设代码框架编写
Linux驱动-SPI-框架中添加字符设备
二、读懂原理图和操作手册
原理图分析
Can 模块
mcp2515 模块有很多功能的,spi 转can,spi 转485. 如下 spi 如何转换的呢? 就是通过ic 芯片进行转换了一次而已。

我们直接看一下spi 转can 的原理图。

看U1 部分,左边不就是SPI 相关的内容吗? pin 脚标记的16 、15/14/13/12 这些就是SPI 的四根线,右边看 RXCAN 、TXCAN 输出的就是can 信号。
目标:就是你要看懂原理图,这个太基本了。 或者通过相关资料来读懂原理图。
这里有特别容易忽略的问题,就是IC 的原理图和实际的物理要对应上,千万不要搞错。

GPIO/CAN/485 Interfcae 原理图
如如下红丝标记的J1 , 原理图要和实际的对上,这样才能正确接线。

数据手册分析
收据手册本身要读懂,要搞明白机制,每一块的内容都特别重要,下面初步看一下目录有哪些内容,本身就是一个说明使用书:

器件概述

工作模式

所以驱动连接外设后,在驱动第一步就是复位 进入配置模式。
SPI接口
这里特别重要,数据手册里面都说了支持0,0 和 1,1 两种模式,所以在实际设备树配置的时候要配置好,不然无法通信。


同时要关注一下数据手册里面有误发送数据介绍说明,默认优先高位。

三、设备树配置
我们看看我们之前的设备树基本配置、必备配置: 参考 Linux驱动-SPI外设代码框架编写
&spi0 {
status = "okay";
pinctrl-0 = <&spi0m1_cs0 &spi0m1_pins>;
pinctrl-1 = <&spi0m1_cs0 &spi0m1_pins_hs>;
mcp2515:mcp2515@0 {
compatible = "my-mcp2515";
reg = <0>;
spi-max-frequency = <10000000>;
status = "okay";
};
};
说一下 设备树配置外设时候,针对SPI 关注几个点:
- compatible : 匹配外设驱动
- reg : 片选信号
- spi-max-frequency : 始终频率 也就是传输速度,去手册里面找。(10MHz)
- spi-cpha; spi-cpol; : 极性和相位,外设默认是0,0 支持的就是0,0 或者 1,1 。 就按默认的来匹配,不要配置这两个属性。
- spi-lsb-first; : 低位优先传输,默认是高位优先传输,所有不要配置这个属性。
- spi-cs-high : 片选极性,高电平有效。 mcp2515 默认是低电平有效,所有不要写这个属性。
两点特别注意:
- 你自己要学会看懂操作手册,如果有特别的说明 那么对应的就要在设备树上体现
- 如果没有特别体现就用默认,同时不要写不必要的属性。
四、SPI 复位函数编写
1、复位函数编写
定义一个SPI设备指针
static struct spi_device *g_mcp2515_spi = NULL;
定义复位指令
#define MCP2515_CMD_RESET 0xC0 // 复位指令 1100 0000
SPI设备指针 赋值
在 mcp2515_probe回调函数中赋值
// MCP2515设备初始化函数
static int mcp2515_probe(struct spi_device *spi)
{
int ret;
printk("This is mcp2515 probe\n");
// 保存spi设备指针
g_mcp2515_spi = spi;
// ==========【上电首先执行MCP2515软件复位】==========
ret = mcp2515_hw_reset(spi);
if (ret) {
printk("mcp2515 hw reset fail!\n");
return ret;
}
// =================================================
// 后续可以在这里继续添加寄存器初始化代码
return 0;
}
MCP2515 SPI软件复位函数
/* MCP2515 SPI软件复位函数 */
static int mcp2515_hw_reset(struct spi_device *spi)
{
u8 cmd = MCP2515_CMD_RESET;
int ret;
if (!spi) {
printk("mcp2515 spi dev is NULL!\n");
return -EINVAL;
}
// 发送单字节复位命令
ret = spi_write(spi, &cmd, sizeof(cmd));
if (ret < 0) {
printk("mcp2515 reset cmd send failed, ret:%d\n", ret);
return ret;
}
printk("mcp2515 send reset command(0xC0) success\n");
msleep(10); // 等待MCP2515内部复位完成,参考数据手册要求
return 0;
}
对比两种复位操作-写入函数
如上是一种写操作,写了复位,如下给出另外一种方案:
// MCP2515芯片复位函数
void mcp2515_reset(void){
int ret;
char write_buf[] = {0xc0}; // 复位指令0x11000000即0xc0
ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // 发送复位命令
if(ret < 0){
printk("spi_write is error\n"); // 打印错误信息
}
}
这两种实现方案的底层逻辑其实是一摸一样的,一个是标准写法一个是极简写法,推荐第一套写法。
其实 这里最核心特别注意的一点是如何声明的问题:
char vs u8:
char write_buf[]={0xc0} 在 ARM 平台可能是有符号 char,规范驱动代码统一使用内核<linux/types.h>的u8无符号字节类型,避免符号扩展带来的隐性问题。
2、带复位函数完整驱动编写
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/delay.h> // 新增:msleep 头文件
static dev_t dev_num;
static int major;
static int minor;
static struct cdev mcp2515_cdev;
struct class *mcp2515_class;
struct device *mcp2515_device;
static struct spi_device *g_spi_dev; // 新增:全局保存spi设备指针
/* MCP2515 SPI复位函数 */
static int mcp2515_reset(void)
{
int ret;
uchar cmd = 0xC0; // MCP2515 复位指令 11000000 = 0xC0
if (!g_spi_dev) {
printk("mcp2515_reset: spi dev pointer is NULL!\n");
return -ENODEV;
}
ret = spi_write(g_spi_dev, &cmd, sizeof(cmd));
if (ret < 0) {
printk("mcp2515_reset spi_write failed, ret=%d\n", ret);
return ret;
}
msleep(10); // MCP2515复位后等待芯片稳定(手册建议延时)
printk("mcp2515 send reset cmd success\n");
return 0;
}
/*打开设备函数*/
static int mcp2515_open(struct inode *inode, struct file *file)
{
printk("This is mcp2515_open \n");
return 0;
}
static ssize_t mcp2515_read(struct file *file,char __user *buf, size_t size, loff_t *off)
{
printk("This is mcp2515_cdev read\r\n");
return 0;
}
/*向设备写入数据函数*/
static ssize_t mcp2515_write(struct file *file,const char __user *buf,size_t size,loff_t *off)
{
printk("This is mcp2515_cdev_write\r\n");
return 0;
}
static int mcp2515_release(struct inode *inode, struct file *file)
{
printk("This is chrdev_release\r\n");
return 0;
}
static struct file_operations mcp2515_fops = {
.owner=THIS_MODULE,
.open = mcp2515_open,
.read = mcp2515_read,
.write = mcp2515_write,
.release = mcp2515_release,
};
// MCP2515 设备初始化函数
static int mcp2515_probe (struct spi_device *spi)
{
printk ("This is mcp2515 probe\n");
g_spi_dev = spi; // 保存spi设备指针
mcp2515_reset(); // ✅ 进入probe后执行芯片复位
return 0;
}
// MCP2515 设备移除函数
static int mcp2515_remove (struct spi_device *spi)
{
printk ("This is mcp2515 mcp2515_remove\n");
g_spi_dev = NULL; // 移除时清空全局指针,防止野指针
return 0;
}
// MCP2515 设备匹配表,用于设备树匹配
static const struct of_device_id mcp2515_of_match_table [] = {
{.compatible = "my-mcp2515"},
{ /* sentinel */ }
};
// MCP2515 设备 ID 匹配表,用于总线匹配
static const struct spi_device_id mcp2515_id_table [] = {
{"mcp2515", 0},
{ /* sentinel */ }
};
// 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,
};
// 驱动初始化函数
static int __init mcp2515_init (void)
{
int ret;
ret=alloc_chrdev_region (&dev_num,0,1,"mcp2515");
if(ret < 0){
printk("alloc_chrdev_region is error\n");
goto err_out;
}
printk ("alloc_chrdev_region is ok\n");
major=MAJOR (dev_num);
minor=MINOR (dev_num);
printk ("major is %d\n",major);
printk ("minor is %d\n",minor);
cdev_init (&mcp2515_cdev,&mcp2515_fops);
mcp2515_cdev.owner = THIS_MODULE;
ret= cdev_add(&mcp2515_cdev,dev_num,1);
if(ret < 0 ){
printk("cdev_add is error\n");
goto err_unreg_devnum;
}
printk("cdev_add is ok\n");
mcp2515_class = class_create (THIS_MODULE,"spi_to_can");
if (IS_ERR(mcp2515_class)) {
ret = PTR_ERR(mcp2515_class);
printk("class_create failed!\n");
goto err_cdev_del;
}
mcp2515_device = device_create(mcp2515_class, NULL, dev_num, NULL, "mcp2515");
if (IS_ERR(mcp2515_device)) {
ret = PTR_ERR(mcp2515_device);
printk("device_create failed!\n");
goto err_class_destroy;
}
// 注册 SPI 驱动
ret = spi_register_driver (&spi_mcp2515);
if (ret < 0) {
printk ("spi_register_driver error\n");
goto err_dev_destroy;
}
printk("mcp2515 module load complete\n");
return 0;
// ========== 错误回滚分支(新增,防止资源泄漏oops)==========
err_dev_destroy:
device_destroy(mcp2515_class, dev_num);
err_class_destroy:
class_destroy(mcp2515_class);
err_cdev_del:
cdev_del(&mcp2515_cdev);
err_unreg_devnum:
unregister_chrdev_region(dev_num,1);
err_out:
return ret;
}
// 驱动退出函数
static void __exit mcp2515_exit (void)
{
spi_unregister_driver (&spi_mcp2515);
device_destroy (mcp2515_class,dev_num);
class_destroy (mcp2515_class);
cdev_del (&mcp2515_cdev);
unregister_chrdev_region (dev_num,1);
printk ("module exit \n");
}
module_init(mcp2515_init);
module_exit(mcp2515_exit);
MODULE_LICENSE("GPL");
总结
本篇内容核心知识点:
- 会看使用手册、原理图、通过原理图接线
- 设备树的配置相关技能,必须根据使用手册来针对性配置
- 复位函数,通过spi 的 相关方法
spi_write进行写操作
更多推荐

所有评论(0)