19_SPI的使用

介绍SPI读写rc522内部的reg

Posted by Jerry Chen on August 12, 2019

了解内容

SPI知识

SPI是4线的:SDI数据输入、SDO数据输出、SCLK时钟、CS片选。只有1根数据线也可以单向通信。

Linux SPI知识

和想象中不同的是我们不用根据时序图写spi的硬件操作函数,这部分已经被硬件实现控制层完成了。设备驱动(driver 驱动层)的工作仅是根据统一的spi设备操作函数完成配置和应用程序调用接口。

Linux中查看spi总线下的设备:

1
ls /sys/bus/spi/devices/

Linux中查看spi总线下的”spi2.0”设备的名称:

1
cat /sys/bus/spi/devices/spi2.0/modalias/

开始

在下图上方的GPIO引脚区域有SPI2的4根引脚引出,这就是目标设备。

对应的在编译环境找到spi总线下编译文件中存在rc522.o,这就是目标文件。该模块是rfid模块。

值得注意的是原理图中can转wifi模块占用了spi2_clk管脚,故这两个驱动不能同时被编译到内核。

SPI_RC522(rfid模块)路径:“Device Drivers”–>“SPI support”–>“RC522 Module driver support”

CAN_MCP251X(can转wifi模块)路径:“Networking support”–>“CAN bus subsystem support”–>“CAN Device Drivers”–>“Platform CAN drivers with Netlink support”–>“Microchip MCP251x SPI CAN controllers”

首先在内核中保留SPI_RC522(下面改写。为什么不重写?因为几个GPIO可作为spi也可作为i2c,定义选择的宏和这个宏有关,简单起见就不重新写),但取消CAN_MCP251X。

紧接着改写平台文件mach-itop4412.c中设备信息,改写rc522平台设备信息中modalias为”my_rc522”即可:

这个数组会被spi_register_board_info()函数使用,用于注册spi中的设备信息。

上面关于片选GPIO的结构体不用更改,如下:

然后在drivers/spi/rc522.c源码目录下修改Makefile不编译rc522.o驱动:

上述完成后,重新编译内核镜像再次烧录

这时可以看到cat /sys/bus/spi/devices/spi2.0/modalias/中打印的是”my_rc522”:

接下来完成my_rc522的驱动即可。

一些函数

  1. 注册i2c驱动函数spi_register_driver,在include/linux/spi.h中有定义。可类比platform_driver_register函数,性质相同。

    1
    
    int spi_register_driver(struct spi_driver *driver);
    

    传参driver,其中包含了I2C设备的名称、probe、remove等接口信息。

  2. spi设置数据函数spi_async

    1
    
    int spi_async(struct spi_device *spi, struct spi_message *message);
    
    • spi参数,即probe函数中的设备结构体参数。 - message参数,是一个spi消息。传入前需要对其初始化和添加节点。
  3. spi消息队列的操作

    初始化spi消息队列函数spi_message_init

    1
    
    static void spi_message_init(struct spi_message *m);
    

    在spi消息队列中添加节点函数spi_message_add_tail

    1
    
    static void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
    

    关于spi_transfer一般定义如下:

    spi是每次传输一字节,Linux将它封装了一下,使用了队列和链表进行传输。

    比如创建好如下的网络结构后,选择第一列的spi_message使用spi_async进行传输,会依次该列所有的tx_buf/rx_buf发出和接收(spi_transfer中len可指定的这块发出/接收的长度)。

  4. 注销spi驱动函数spi_unregister_driver

    1
    
    spi_unregister_driver(struct spi_driver *driver);
    

其他

线程同步的一些函数:

DECLARE_COMPLETION_ONSTACK(done);

wait_for_completion(&done);

整理

spi发送函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int send_demofunc(unsigned char *buffer, int len)
{
	int status;
	struct spi_transfer	t = {
		.tx_buf		= buffer,
		.len		= len,
	};
	struct spi_message	m;
	DECLARE_COMPLETION_ONSTACK(done);
	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	
	m.complete = NULL;
	m.context = &done;
	
	//printk("spi_async send begin!\n");
	status = spi_async(my_spi,&m);
	if(status == 0){
		wait_for_completion(&done);
		status = m.status;
		if (status == 0)
			status = m.actual_length;
	}
	return status;
}

spi接收函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int recv_demofunc(unsigned char *buffer, int len)
{
	int status;
	struct spi_transfer	t = {
		.rx_buf		= buffer,
		.len		= len,
	};
	struct spi_message	m;
	DECLARE_COMPLETION_ONSTACK(done);
	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	
	m.complete = NULL;
	m.context = &done;
	
	//printk("spi_async recv begin!\n");
	status = spi_async(my_spi,&m);
	if(status == 0){
		wait_for_completion(&done);
		status = m.status;
		if (status == 0)
			status = m.actual_length;
	}
	return status;
}

例程

驱动例程

spi_demo.c

spidev.h 头文件浏览:点这

spidev_test.h 头文件浏览:点这

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/gpio.h> //已经包含gpio-exynos4.h
#include <plat/gpio-cfg.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/completion.h>

#include "spidev_test.h"
#include "spidev.h"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("jerry");

#define DRV_NAME "my_rc522"

static struct spi_device *my_spi;

static int send_demofunc(unsigned char *buffer, int len)
{
	int status;
	struct spi_transfer	t = {
		.tx_buf		= buffer,
		.len		= len,
	};
	struct spi_message	m;
	DECLARE_COMPLETION_ONSTACK(done);
	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	
	m.complete = NULL;
	m.context = &done;
	
	printk("spi_async send begin!\n");
	status = spi_async(my_spi,&m);
	if(status == 0){
		wait_for_completion(&done);
		status = m.status;
		if (status == 0)
			status = m.actual_length;
	}
	return status;
}
static int recv_demofunc(unsigned char *buffer, int len)
{
	int status;
	struct spi_transfer	t = {
		.rx_buf		= buffer,
		.len		= len,
	};
	struct spi_message	m;
	DECLARE_COMPLETION_ONSTACK(done);
	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	
	m.complete = NULL;
	m.context = &done;
	
	printk("spi_async recv begin!\n");
	status = spi_async(my_spi,&m);
	if(status == 0){
		wait_for_completion(&done);
		status = m.status;
		if (status == 0)
			status = m.actual_length;
	}
	return status;
}

static unsigned char ReadRawRC(int addr)
{
	int ret;
	unsigned char  ReData;
	unsigned char  Address;
	
	//bit7:MSB=0;bit0:RFU=0;bit6~1:addr
	Address  = ((unsigned char)addr << 1)&0x7E;
	
	ret = send_demofunc(&Address, 1);
	if (ret < 0)
		printk("spi:SPI Write error\n");

	udelay(100);

	ret = recv_demofunc(&ReData, 1);
	if (ret < 0)
		printk("spi:SPI Read error\n");

	return ReData;
}

static int WriteRawRC(int addr, int data)
{
	int ret;
	unsigned char TxBuf[2];

	//bit7:MSB=0;bit0:RFU=0;bit6~1:addr
	TxBuf[0] = ((unsigned char)addr << 1)&0x7E;
	TxBuf[1] = (unsigned char)data;
	
	ret = send_demofunc(TxBuf, 2);
	if (ret < 0)
		printk("spi:SPI Write error\n");
	
	udelay(10);
	return ret;
}

static void rc522_demo(void)
{
	/*rc522芯片基于spi的上层通信协议demo*/
	char version = 0;

	//write reset cmd,即连续写入两个字节为写寄存器
	WriteRawRC(CommandReg, PCD_RESETPHASE);
	udelay(10);
	WriteRawRC(ModeReg, 0x3D);
	WriteRawRC(TReloadRegL, 30);
	WriteRawRC(TReloadRegH, 0);
	WriteRawRC(TModeReg, 0x8D);
	WriteRawRC(TPrescalerReg, 0x3E);

	//read version reg,即读取一个字节为读寄存器
	version = ReadRawRC(VersionReg);
	printk("Chip Version: 0x%x\n", version);
}

#define RC522_RESET_PIN	EXYNOS4_GPK1(0)

static void my_rc522_reset(void){
	if(gpio_request_one(RC522_RESET_PIN, GPIOF_OUT_INIT_HIGH, "RC522_RESET"))
		pr_err("failed to request GPK1_0 for RC522 reset control\n");

	s3c_gpio_setpull(RC522_RESET_PIN, S3C_GPIO_PULL_UP);
	gpio_set_value(RC522_RESET_PIN, 0);

	mdelay(5);

	gpio_set_value(RC522_RESET_PIN, 1);
	gpio_free(RC522_RESET_PIN);
	printk(KERN_EMERG "%s ok!\n", __FUNCTION__);
}

static int demo_probe(struct spi_device *spi){
	my_rc522_reset();//复位管脚复位rc522芯片
	my_spi = spi;
	rc522_demo(); //rc522的协议测试程序
	printk(KERN_EMERG "demo_probe ok!\n");
	return 0;
}

static int demo_remove(struct spi_device *spi){
	printk(KERN_EMERG "demo_remove ok!\n");
	return 0;
}

static struct spi_driver spi_drv_demo = {
	.driver = {
		.name =	DRV_NAME,
		.owner =	THIS_MODULE,
	},
	.probe =	demo_probe,
	.remove = __devexit_p(demo_remove),
};

static int __init demo_init(void){
	spi_register_driver(&spi_drv_demo);
	return 0;
}
static void __exit demo_exit(void){
	spi_unregister_driver(&spi_drv_demo);
}

//如果写内核中,要使用late_initcall后加载
module_init(demo_init);
module_exit(demo_exit);
测试结果

本文测试结果和测试程序是不完整的,因为需要外接rc522(rfid)硬件模块才能测试具体的读写。所以测试程序仅在probe中实现了读取数据就没有继续实现了,读懂后继续实现很容易。

编写简单Makefile:参考这里

编译生成模块后,拷贝到开发板中:

  • 首先加载模块后正常执行probe函数,但是没该硬件模块所以spi发送阻塞。