SPI简介
SPI 是Motorola 公司推出的一种同步串行接口技术,是一种高速、全双工的同步通信总线。SPI 以主从方式工作,通常是有一个主设备和一个或多个从设备,一般SPI 需要4 根线,但是也可以使用三根线(单向传输)
这四根线如下:
①、CS/SS
,Slave Select/Chip Select,这个是片选信号线,用于选择需要进行通信的从设备
。
I2C 主机是通过发送从机设备地址来选择需要进行通信的从机设备的,SPI 主机不需要发送从机
设备,直接将相应的从机设备片选信号拉低即可。
②、SCK
,Serial Clock,串行时钟,和I2C 的SCL 一样,为SPI 通信提供时钟
。
③、MOSI/SDO
,Master Out Slave In/Serial Data Output,简称主出从入信号线,这根数据线
只能用于主机向从机发送数据
,也就是主机输出,从机输入。
④、MISO/SDI,Master In Slave Out/Serial Data Input,简称主入从出信号线,这根数据线只
能用户从机向主机发送数据
,也就是主机输入,从机输出。
四种工作形式:通过串行时钟极性(CPOL)和相位(CPHA)的搭配
含义:CPOL:极性、CPHA:相位
如1:CPOL=1:高、CPHA=1:第二个相位。
高->低->高:第一个高->低为上升沿,第二个低->高·下降沿
·
2:CPOL=1:高、CPHA=0:第一个相位。
高->低:第一个高->低为上升沿
①、CPOL=1,串行时钟空闲状态为高电平
,CPHA=1,串行时钟的第二个跳变沿下降沿
采集数据。
②、CPOL=1,串行时钟空闲状态为高电平
,CPHA=0,串行时钟的第一个跳变沿下降沿
采集数据。
③、CPOL=0,串行时钟空闲状态为低电平
,CPHA=1,串行时钟的第二个跳变沿下降沿
采集数据。
④、CPOL=0,串行时钟空闲状态为低电平
,CPHA=0,串行时钟的第一个跳变沿上升沿
采集数据。
SPI驱动
spi_imx.c
先找到spi_imx_driver, 这是imx在linux内核基础上写的
static struct platform_driver spi_imx_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = spi_imx_dt_ids,//设备树中匹配
.pm = IMX_SPI_PM,
},
.id_table = spi_imx_devtype,//无设备树匹配
.probe = spi_imx_probe,
.remove = spi_imx_remove,
};
spi_imx_probe
static int spi_imx_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *of_id =
of_match_device(spi_imx_dt_ids, &pdev->dev);
struct spi_imx_master *mxc_platform_info =
dev_get_platdata(&pdev->dev);
struct spi_master *master;
struct spi_imx_data *spi_imx;
struct resource *res;
int i, ret, num_cs, irq;
if (!np && !mxc_platform_info) {
dev_err(&pdev->dev, "can't get the platform data\n");
return -EINVAL;
}
ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs);
if (ret < 0) {
if (mxc_platform_info)
num_cs = mxc_platform_info->num_chipselect;
else
return ret;
}
master = spi_alloc_master(&pdev->dev,
sizeof(struct spi_imx_data) + sizeof(int) * num_cs);
if (!master)
return -ENOMEM;
platform_set_drvdata(pdev, master);
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
master->bus_num = pdev->id;
master->num_chipselect = num_cs;
spi_imx = spi_master_get_devdata(master);
spi_imx->bitbang.master = master;
for (i = 0; i < master->num_chipselect; i++) {
int cs_gpio = of_get_named_gpio(np, "cs-gpios", i);
if (!gpio_is_valid(cs_gpio) && mxc_platform_info)
cs_gpio = mxc_platform_info->chipselect[i];
spi_imx->chipselect[i] = cs_gpio;
if (!gpio_is_valid(cs_gpio))
continue;
ret = devm_gpio_request(&pdev->dev, spi_imx->chipselect[i],
DRIVER_NAME);
if (ret) {
dev_err(&pdev->dev, "can't get cs gpios\n");
goto out_master_put;
}
}
这上部分都是platform_device 的基本操作
下部分出现bitbang为关键,bitbang的作用是GPIO口模仿SPI模式,其他的为驱动正常操作
SPI发送:
spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
spi_imx->rx = spi_imx_buf_rx_u8;
spi_imx->tx = spi_imx_buf_tx_u8;
spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
bitbang下的txrx_bufs,函数为 spi_imx_transfer
spi_imx_transfer
->spi_imx_pio_transfer
->spi_imx_push->spi_imx
->tx(spi_imx);
SPI接收
中断函数接收数据
spi_imx_isr
->spi_imx->rx(spi_imx);
bitbang启动
spi_bitbang_start(struct spi_bitbang *bitbang)
master->transfer_one_message = spi_bitbang_transfer_one;
spi_bitbang_transfer_one
->bitbang->txrx_bufs(spi, t)=spi_imx_transfer
->->tx(spi_imx)=spi_imx_buf_tx_u8
SPI设备驱动
发送
定义一个spi_transfer
对下面3个成员赋值
struct spi_transfer t[] = {
{
.tx_buf = tx_buff,
.rx_buf = rx_buff,
.len = len,
};
定义一个 spi_message
spi_message_init(&spi_message);//初始化
spi_message_add_tail(&spi_transfer , &spi_message);//将spi_transfer 打包进spi_message
选择设备,选择发送方式(同步,异步)
同步 :spi_sync(spi_device, &spi_message);
异步:spi_async(spi_device, &spi_message);
读和写只需要一个spi_transfer 就行了
读
tx_buf 放寄存器地址
rx_buf 写要存放信息的地址
t->len =1(发送的数据:寄存器大小)+len(读取的数据:此表示接收信息的大小)
unsigned char txdata[1];
struct spi_message m;
struct spi_transfer *t;
/* 一共发送len+1个字节的数据,第一个字节为
寄存器首地址,一共要读取len个字节长度的数据,*/
txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */
t->tx_buf = txdata; /* 要发送的数据 */
t->rx_buf = rxdata; /* 要读取的数据 */
t->len = len+1; /* t->len=发送的长度+读取的长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
SPI选片都是软选,发送数据前的片选信号拉低都是在发送一段spi_message 前拉低,发完就拉高
,现在操作不像裸机
写
txdata =sizeof(char)+len,寄存器地址+要写入的内容
unsigned char *txdata;
struct spi_message m;
struct spi_transfer *t;
txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
/* 一共发送len+1个字节的数据,第一个字节为
寄存器首地址,len为要写入的寄存器的集合,*/
*txdata = reg & ~0x80; /* 写数据的时候首寄存器地址bit8要清零 */
memcpy(txdata+1, buf, len); /* 把len个寄存器拷贝到txdata里,等待发送 */
t->tx_buf = txdata; /* 要发送的数据 */
t->len = len+1; /* t->len=发送的长度+读取的长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
读和写的 t->len = len+1; 这个1都是寄存器地址长度一个字节,len的长度为tx_buf+rx_buf ,tx_buf为寄存器地址