Linux的spi驱动是这个项目一个关键点,就靠spi与stm32MCU进行数据传输来操作stm32产生相应动作。
这篇主要是介绍spi驱动的基本实现过程,并没有详细解释源码。
Linux 中SPI驱动代码位于drivers/spi目录。
SPI核心层是Linux的SPI核心部分,提供了核心数据结构的定义、SPI控制器驱动和设备驱动的注册、注销管理等API。其为硬件平台无关层,向下屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口;其向上提供了统一的接口,以便SPI设备驱动通过总线控制器进行数据收发。
在系统开机时,SPI控制器驱动被首先装载。一个控制器驱动用于支持一条特定的SPI总线的读写。一个控制器驱动可以用数据结构struct spi_master来描述。
因此SPI驱动分为两类:
控制器驱动:它们通常内嵌于片上系统处理器,通常既支持主设备,又支持从设备。这些驱动涉及硬件寄存器,可能使用DMA。或它们使用GPIO引脚成为PIO bitbangers。这部分通常会由特定的开发板提供商提供,不用自己写。
协议驱动:它们通过控制器驱动,以SPI连接的方式在主从设备之间传递信息。这部分涉及具体的SPI从设备,通常需要自己编写。
分析一下注册spi驱动:
1、 Platform bus
Platform bus对应的结构是platform_bus_type,这个内核开始就定义好的。我们不需要定义。
2、Platform_device
SPI控制器对应platform_device的定义方式,参看arch/arm/mach-exynos/Dev-spi.c文件,需要在平台文件下注册spi设备信息,其中就会包含使用的是哪一路spi引脚。我的开发板spi都定义在三星写的平台文件中arch/arm/mach-exynos/mach-itop4412.c中
static struct spi_board_info spi2_board_info[] __initdata = {
#if CONFIG_SPI_SPIDEV
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>.modalias = "spidev",
<span style="white-space:pre"> </span>.platform_data = NULL,
<span style="white-space:pre"> </span>.max_speed_hz = 10*1000*1000,
<span style="white-space:pre"> </span>.bus_num = 2,
<span style="white-space:pre"> </span>.chip_select = 0,
<span style="white-space:pre"> </span>.mode = SPI_MODE_0,
<span style="white-space:pre"> </span>.controller_data = &spi2_csi[0],
<span style="white-space:pre"> </span>}
#endif
这里定义了基本信息:
<pre name="code" class="plain" style="widows: 1;">.controller_data = &spi2_csi[0],
中spi2_csi如下
static struct s3c64xx_spi_csinfo spi2_csi[] = {
<span style="white-space:pre"> </span>[0] = {
<span style="white-space:pre"> </span>.line = EXYNOS4_GPC1(2),
<span style="white-space:pre"> </span>.set_level = gpio_set_value,
<span style="white-space:pre"> </span>.fb_delay = 0x1,
<span style="white-space:pre"> </span>},
};
可以看到SPI2片选引脚为GPC1_2
3、Platform_driver
再看platform_driver,参看drivers/spi/spi_s3c64xx.c文件
最后附上我用的测试程序:
1,先将MISO和MOSI进行短接,自发自接来测试spi通信是否正常,因为ioctl操作Linux带的spi驱动是全双工通信,也就是说接受与发送数据同时进行。
2,源码(从网上找的,改下设备节点就能用)
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
static void pabort(const char *s)
{
perror(s);
abort();
}
static const char *device = "/dev/rc522";
static uint8_t mode;
static uint8_t bits = 8;
static uint32_t speed = 700000;
static uint16_t delay;
static void transfer(int fd)
{
int ret;
uint8_t tx[] = { //要发送的数据数组
100,100,100,100,100,100
};
uint8_t rx[ARRAY_SIZE(tx)] = {0, }; //接收的数据数据
struct spi_ioc_transfer tr = { //声明并初始化spi_ioc_transfer结构体
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = ARRAY_SIZE(tx),
.delay_usecs = delay,
.speed_hz = speed,
.bits_per_word = bits,
};
//SPI_IOC_MESSAGE(1)的1表示spi_ioc_transfer的数量
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); //ioctl默认操作,传输数据
if (ret < 1)
pabort("can't send spi message");
for (ret = 0; ret < ARRAY_SIZE(tx); ret++) { //打印接收缓冲区
if (!(ret % 6)) //6个数据为一簇打印
puts("");
printf("%.2X ", rx[ret]);
}
puts("");
}
static void print_usage(const char *prog) //参数错误则打印帮助信息
{
printf("Usage: %s [-DsbdlHOLC3]\n", prog);
puts(" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
" -d --delay delay (usec)\n"
" -b --bpw bits per word \n"
" -l --loop loopback\n"
" -H --cpha clock phase\n"
" -O --cpol clock polarity\n"
" -L --lsb least significant bit first\n"
" -C --cs-high chip select active high\n"
" -3 --3wire SI/SO signals shared\n");
exit(1);
}
static void parse_opts(int argc, char *argv[])
{
while (1) {
static const struct option lopts[] = { //参数命令表
{ "device", 1, 0, 'D' },
{ "speed", 1, 0, 's' },
{ "delay", 1, 0, 'd' },
{ "bpw", 1, 0, 'b' },
{ "loop", 0, 0, 'l' },
{ "cpha", 0, 0, 'H' },
{ "cpol", 0, 0, 'O' },
{ "lsb", 0, 0, 'L' },
{ "cs-high", 0, 0, 'C' },
{ "3wire", 0, 0, '3' },
{ "no-cs", 0, 0, 'N' },
{ "ready", 0, 0, 'R' },
{ NULL, 0, 0, 0 },
};
int c;
c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR", lopts, NULL);
if (c == -1)
break;
switch (c) {
case 'D': //设备名
device = optarg;
break;
case 's': //速率
speed = atoi(optarg);
break;
case 'd': //延时时间
delay = atoi(optarg);
break;
case 'b': //每字含多少位
bits = atoi(optarg);
break;
case 'l': //回送模式
mode |= SPI_LOOP;
break;
case 'H': //时钟相位
mode |= SPI_CPHA;
break;
case 'O': //时钟极性
mode |= SPI_CPOL;
break;
case 'L': //lsb 最低有效位
mode |= SPI_LSB_FIRST;
break;
case 'C': //片选高电平
mode |= SPI_CS_HIGH;
break;
case '3': //3线传输模式
mode |= SPI_3WIRE;
break;
case 'N': //没片选
// mode |= SPI_NO_CS;
break;
case 'R': //从机拉低电平停止数据传输
// mode |= SPI_READY;
break;
default: //错误的参数
print_usage(argv[0]);
break;
}
}
}
int main(int argc, char *argv[])
{
int ret = 0;
int fd;
parse_opts(argc, argv); //解析传递进来的参数
fd = open(device, O_RDWR); //打开设备文件
if (fd < 0)
pabort("can't open device");
/*
* spi mode //设置spi设备模式
*/
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); //写模式
if (ret == -1)
pabort("can't set spi mode");
ret = ioctl(fd, SPI_IOC_RD_MODE, &mode); //读模式
if (ret == -1)
pabort("can't get spi mode");
/*
* bits per word //设置每个字含多少位
*/
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); //写 每个字含多少位
if (ret == -1)
pabort("can't set bits per word");
ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits); //读 每个字含多少位
if (ret == -1)
pabort("can't get bits per word");
/*
* max speed hz //设置速率
*/
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); //写速率
if (ret == -1)
pabort("can't set max speed hz");
ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed); //读速率
if (ret == -1)
pabort("can't get max speed hz");
//打印模式,每字多少位和速率信息
printf("spi mode: %d\n", mode);
printf("bits per word: %d\n", bits);
printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
transfer(fd); //传输测试
close(fd); //关闭设备
return ret;
}
3,通过Linux交叉编译链编译,传到开发板中运行。