Linux驱动-SPI-MCP2515 写函数(收发操作)
Linux驱动-SPI-MCP2515 写函数(收发操作)
文章目录
前言
在上个学习内容Linux-SPI-位修改指令-屏蔽字节-修改CANINE寄存器的值-修改工作模式中对 mcp2515 的工作模式进行了修改, 从配置模式修改为了环回模式, 而这里学习将会继续对 mcp2515 的驱动程序进行完善, 加入 mcp2515 的写函数和读函数, 从而实现数据的发送和接收
这里写函数是什么呢? 其实就是文件系统操作里面对外开放的接口:

那对外释放的接口,我们这里实验最终要做什么呢? 暂且抛开CAN功能,我们利用SPI协议对CAN 进行收发操作, 这里的发就是发给CAN。
- 那为什么要进行收发操作呢,其实就是模拟收发数据操作,不用借助CAN 输出。
- 为什么切换到回环模式,回环才能收发呀。

一、参考文档
RK3568驱动指南|第十六篇 SPI-第192章 mcp2515驱动编写:完善write和read函数
二、使用手册分析
发送缓冲器-寄存器
MCP2515 有三个发送缓冲器, 每个发送缓冲器都具有 14 字节的内存空间, 每个发送缓冲器的控制由 TXBnCTRL 寄存器管理, 该寄存器决定了何时发送报文以及发送时的报文状态

SRAM 理解
SRAM:静态随机存储器(Static‑RAM),就是芯片内部一小块高速内存。
- SRAM 和寄存器是什么关系?
MCP2515 把内部这一块 SRAM,映射成 SPI 可以访问的寄存器地址。
你通过 SPI 写寄存器0x31~0x3D(TXB0 缓冲区),本质就是在读写 MCP2515 内部的 SRAM - SRAM 和 Flash(闪存)区别(非常关键)
| 类型 | SRAM (MCP2515 内部内存) | Flash (闪存) |
|---|---|---|
| 断电 | 断电数据全部丢失 | 断电数据保留 |
| 读写速度 | 极快 | 慢 |
| MCP2515 用途 | 临时存发送 / 接收 CAN 报文 | MCP2515没有 Flash,上电所有寄存器都是默认值 |
流程步骤分析
直接看数据手册: 总体上其实有三个步骤的,下面会具体分析的。

操作的寄存器分析梳理
1、设置缓冲器优先级

如图分析寄存器,所以实际我们需要如何写这个逻辑呢?
#define TXB0CTRL 0x30 //发送缓冲器控制寄存器地址
mcp2515_change_regbit(TXB0CTRL, 0x03, 0x03);
// MCP2515修改寄存器位函数
void mcp2515_change_regbit(char reg, char mask, char value) {
int ret;
char write_buf[] = { 0x05, reg, mask, value }; // SPI写缓冲区,用于发送修改寄存器位命令
ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // 发送SPI写命令
if (ret < 0) {
printk("mcp2515_change_regbit error\n");
}
}
2、从用户空间复制数据到内核缓冲区
我们的发送操作是写在write 方法里面作为对应用端提供的接口操作的,所以 具体方法如下:获取用户空间数据到内核
// 写设备操作函数
ssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) {
......
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;
}
......
return size;
}
3、发送缓冲器数据到内核
再看一下使用手册里面的发送缓冲器要求,就是说还要继续发送13 个字节数据出去,发送字节数据出去其实就是要写到寄存器里面去,既然写值那么一定要知道寄存器地址吧。

所以还是先看看寄存器具体啥情况,看地址:





分析:发送缓冲器控制寄存器 TXBnCTRL 为发送缓冲器的第一个字节, 接下来的 5 个字节用来装载标准和扩展标识符以及其他报文仲裁信息。 最后的 8 个字节用于装载等待发送报文的 8 个可能的数据字节, 这 13 个字节数据由用户空间所传递, 且地址是连续的。
也就是说地址连续的,那么写寄存器地址就简单了。
// 写设备操作函数
ssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) {
char w_kbuf[13] = {0};
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;
}
// 将数据写入MCP2515寄存器
for (i = 0; i < sizeof(w_kbuf); i++) {
mcp2515_write_reg(0x31 + i, w_kbuf[i]);
}
.....
4、启动发送
继续看使用手册

继续看寄存器

所以 发送一次寄存器数据如下:
#define TXB0CTRL 0x30 //发送缓冲器控制寄存器地址
// 设置TXB0CTRL寄存器的部分位,启动发送
mcp2515_change_regbit(TXB0CTRL, 0x08, 0x08); //采用位发送数据到寄存器
// MCP2515修改寄存器位函数
void mcp2515_change_regbit(char reg, char mask, char value) {
int ret;
char write_buf[] = { 0x05, reg, mask, value }; // SPI写缓冲区,用于发送修改寄存器位命令
ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // 发送SPI写命令
if (ret < 0) {
printk("mcp2515_change_regbit error\n");
}
}
5、验证是否发送成功-等待发送完毕
继续看操作使用手册如下:所以检查寄存器CANINTF寄存器中TXnIF 位置是否为1 即可。

继续查看寄存器 CANINTF:地址ox2C 判断的TXnIF 是bit2

所以检查寄存器CANINTF 对应的位是不是1,如果是1 代表成功。
#define CANINTF 0x2c
// 等待发送完成
while (!(mcp2515_read_reg(CANINTF) & (1 << 2)));
综合代码编写如下:
// 写设备操作函数
ssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) {
char w_kbuf[13] = {0};
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;
}
// 将数据写入MCP2515寄存器
for (i = 0; i < sizeof(w_kbuf); i++) {
mcp2515_write_reg(0x31 + i, w_kbuf[i]);
}
// 设置TXB0CTRL寄存器的部分位,启动发送
mcp2515_change_regbit(TXB0CTRL, 0x08, 0x08);
// 等待发送完成
while (!(mcp2515_read_reg(CANINTF) & (1 << 2)));
......
return size;
}
6、清除发送完成标志
为什么要进行这步操作,继续看操作手册,如上的寄存器CANINTF

也就是说,我已经验证了启动发送报文成功了,接下来就是要复位操作一次,是MCU 清零,所以需要MCU端 代码实现而不是外设自动复位。
所以要个这个寄存器复位操作很简单了,还是找寄存器地址、复位操作的值、屏蔽字节即可:
mcp2515_change_regbit(CANINTF, 0x04, 0x00);
7、完整发送代码如下:
// 写设备操作函数
ssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) {
char w_kbuf[13] = {0};
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;
}
// 将数据写入MCP2515寄存器
for (i = 0; i < sizeof(w_kbuf); i++) {
mcp2515_write_reg(0x31 + i, w_kbuf[i]);
}
// 设置TXB0CTRL寄存器的部分位,启动发送
mcp2515_change_regbit(TXB0CTRL, 0x08, 0x08);
// 等待发送完成
while (!(mcp2515_read_reg(CANINTF) & (1 << 2)));
// 清除发送完成标志
mcp2515_change_regbit(CANINTF, 0x04, 0x00);
return size;
}
总结
- 写入操作是提供给外部的接口,实际操作的是SPI向CAN 发的操作
- 整个过程完全是根据操作手册来的,读懂、理解、根据操作手册进行编程 、业务实现 需要自己学会看 看得懂才行。
更多推荐

所有评论(0)