imx6ull -- SPI

SPI 是 Motorola 公司推出的一种同步串行接口

技术,是一种高速、全双工的同步通信总线, SPI 时钟频率相比 I2C 要高很多,最高可以工作

在上百 MHz。 SPI 以主从方式工作,通常是有一个主设备和一个或多个从设备,一般 SPI 需要

4 根线,但是也可以使用三根线(单向传输)。

框图

结构图如下

CS/SS, Slave Select/Chip Select:选择需要进行通信的从设备,直接将相应的从机片选信号拉低即可。

SCK, Serial Clock,串行时钟,和 I2C 的 SCL 一样,为 SPI 通信提供时钟。

MOSI/SDO, Master Out Slave In/Serial Data Output,简称主出从入信号线,这根数据线

只能用于主机向从机发送数据,也就是主机输出,从机输入。

MISO/SDI, Master In Slave Out/Serial Data Input,简称主入从出信号线,这根数据线只

能用户从机向主机发送数据,也就是主机输入,从机输出。

工作模式

SPI 有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式

①、 CPOL=0,串行时钟空闲状态为低电平。

②、 CPOL=1,串行时钟空闲状态为高电平,

此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。

③、 CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。

④、 CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。

SPI是全双工的,不需要像IIC一样分为读时序和写时序,配置好上面的模式后当通信时钟电平变化产生起始信号后,就开始通信。

工作时序图

CS 片选信号先拉低,选中要通信的从设备,然后通过 MOSI 和 MISO 这两根数据线进行收发数据, MOSI 数据线发出了0XD2 这个数据给从设备,同时从设备也通过 MISO 线给主设备返回了 0X66 这个数据。

6ull里的SPI

spi控制器的核心就是对spi_master的构建(6ull里还有个spi_imx),6ull函数使用transfer_one_message函数

驱动编写

添加pinctrl节点,在iomuxc下添加如下(同上节一样)

屏蔽

在 imx6ull-alientek-emmc.dts 文件最后面加入如下所示内容:

make dtbs编译设备树文件放到tftp下

在/sys/bus/device里看到spi2 就是对应我们挂到的spi3

驱动代码

复制代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm20608reg.h"

#define ICM20608_CNT    1
#define ICM20608_NAME   "icm20608"

struct icm20608_dev{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int major;
    void *private_data;             /* 私有数据 */
    signed int gyro_x_adc; /* 陀螺仪 X 轴原始值 */
    signed int gyro_y_adc; /* 陀螺仪 Y 轴原始值 */
    signed int gyro_z_adc; /* 陀螺仪 Z 轴原始值 */
    signed int accel_x_adc; /* 加速度计 X 轴原始值 */
    signed int accel_y_adc; /* 加速度计 Y 轴原始值 */
    signed int accel_z_adc; /* 加速度计 Z 轴原始值 */
    signed int temp_adc; /* 温度原始值*/
};

static struct icm20608_dev icm20608dev;

/*dev:  icm20608设备*- reg:  要读取的寄存器首地址*- buf:  读取到的数据 - len:  要读取的数据长度*/
static int  icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
    int ret = -1;
    unsigned char txdata[1];
    unsigned char *rxdata;
    struct spi_message m;
    struct spi_transfer *t;
    struct spi_device *spi = (struct spi_device *)dev->private_data;

    t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
    if(!t)
    {
        return -ENOMEM;
    }

    rxdata = kzalloc(sizeof(char)*len, GFP_KERNEL);
    if(!rxdata)
        goto out1;

    txdata[0] = reg | 0x80;
    t->tx_buf = txdata;
    t->rx_buf = rxdata;
    t->len = len+1;
    spi_message_init(&m);
    spi_message_add_tail(t, &m);
    ret =  spi_sync(spi, &m);
    if(ret)
        goto out2;

    memcpy(buf, rxdata+1, len);

out1:
    kfree(t);
out2:
    kfree(rxdata);

    return ret;
}

static int  icm20608_write_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
    int ret = -1;
    unsigned char *txdata;
    struct spi_message m;
    struct spi_transfer *t;
    struct spi_device *spi = (struct spi_device *)dev->private_data;

    t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
    if(!t)
    {
        return -ENOMEM;
    }

    txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
    if(!txdata)
        goto out1;

    *txdata = reg & ~0x80;
    memcpy(txdata+1, buf, len);
    t->tx_buf = txdata;
    t->len = len+1;
    spi_message_init(&m);
    spi_message_add_tail(t, &m);
    ret =  spi_sync(spi, &m);
    if(ret)
        goto out2;

out1:
    kfree(t);
out2:
    kfree(txdata);

    return ret;
}

//读取icm20608指定寄存器值,读取一个寄存器
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
    unsigned char data = 0;
    icm20608_read_regs(dev, reg, &data, 1);
    return data;
}

//向icm20608指定寄存器写入指定的值,写一个寄存器
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
    u8 buf = value;
    icm20608_write_regs(dev, reg, &buf, 1);
}

//读取ICM20608的数据,读取原始数据,包括三轴陀螺仪
void icm20608_readdata(struct icm20608_dev *dev)
{
    unsigned char data[14] = { 0 };
	icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);

	dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); 
	dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); 
	dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); 
	dev->temp_adc    = (signed short)((data[6] << 8) | data[7]); 
	dev->gyro_x_adc  = (signed short)((data[8] << 8) | data[9]); 
	dev->gyro_y_adc  = (signed short)((data[10] << 8) | data[11]);
	dev->gyro_z_adc  = (signed short)((data[12] << 8) | data[13]);
}

static int icm20608_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &icm20608dev; /* 设置私有数据 */
	return 0;
}

static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	signed int data[7];
	long err = 0;
	struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;

	icm20608_readdata(dev);
	data[0] = dev->gyro_x_adc;
	data[1] = dev->gyro_y_adc;
	data[2] = dev->gyro_z_adc;
	data[3] = dev->accel_x_adc;
	data[4] = dev->accel_y_adc;
	data[5] = dev->accel_z_adc;
	data[6] = dev->temp_adc;
	err = copy_to_user(buf, data, sizeof(data));
	return 0;
}

static int icm20608_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static const struct file_operations icm20608_ops = {
	.owner = THIS_MODULE,
	.open = icm20608_open,
	.read = icm20608_read,
	.release = icm20608_release,
};

void icm20608_reginit(void)
{
	unsigned char value = 0;
	
	icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
	mdelay(50);
	icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
	mdelay(50);

	value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
	printk("ICM20608 ID = %c\r\n", value);	

	icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); 	/* 输出速率是内部采样率					*/
	icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); 	/* 陀螺仪±2000dps量程 				*/
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); 	/* 加速度计±16G量程 					*/
	icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); 		/* 陀螺仪低通滤波BW=20Hz 				*/
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz 			*/
	icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); 	/* 打开加速度计和陀螺仪所有轴 				*/
	icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); 	/* 关闭低功耗 						*/
	icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);		/* 关闭FIFO						*/
}

static int icm20608_probe(struct spi_device *spi)
{
	/* 1、构建设备号 */
	if (icm20608dev.major) {
		icm20608dev.devid = MKDEV(icm20608dev.major, 0);
		register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
	} else {
		alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
		icm20608dev.major = MAJOR(icm20608dev.devid);
	}

	/* 2、注册设备 */
	cdev_init(&icm20608dev.cdev, &icm20608_ops);
	cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);

	/* 3、创建类 */
	icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
	if (IS_ERR(icm20608dev.class)) {
		return PTR_ERR(icm20608dev.class);
	}

	/* 4、创建设备 */
	icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
	if (IS_ERR(icm20608dev.device)) {
		return PTR_ERR(icm20608dev.device);
	}

	/*初始化spi_device */
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);
	icm20608dev.private_data = spi; /* 设置私有数据 */

	/* 初始化ICM20608内部寄存器 */
	icm20608_reginit();		
	return 0;
}

static int icm20608_remove(struct spi_device *spi)
{
	/* 删除设备 */
	cdev_del(&icm20608dev.cdev);
	unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);

	/* 注销掉类和设备 */
	device_destroy(icm20608dev.class, icm20608dev.devid);
	class_destroy(icm20608dev.class);
	return 0;
}

static const struct spi_device_id icm20608_id[] = {
	{"alientek,icm20608", 0},  
	{}
};

static const struct of_device_id icm20608_of_match[] = {
	{ .compatible = "alientek,icm20608" },
	{ /* Sentinel */ }
};

/* SPI驱动结构体 */	
static struct spi_driver icm20608_driver = {
	.probe = icm20608_probe,
	.remove = icm20608_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "icm20608",
		   	.of_match_table = icm20608_of_match, 
		   },
	.id_table = icm20608_id,
};

static int __init icm20608_init(void)
{
	return spi_register_driver(&icm20608_driver);
}

static void __exit icm20608_exit(void)
{
	spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
相关推荐
m0_466607703 分钟前
【STM32CubeMX】ST官网MCU固件库下载及安装
stm32·单片机·嵌入式硬件
Wallace Zhang4 小时前
STM32F103_Bootloader程序开发11 - 实现 App 安全跳转至 Bootloader
stm32·嵌入式硬件·安全
GodKK老神灭4 小时前
STM32 CCR寄存器
stm32·单片机·嵌入式硬件
杰克逊的日记9 天前
MCU编程
单片机·嵌入式硬件
Python小老六9 天前
单片机测ntc热敏电阻的几种方法(软件)
数据库·单片机·嵌入式硬件
HX科技10 天前
STM32给FPGA的外挂FLASH进行升级
stm32·嵌入式硬件·fpga开发·flash·fpga升级
Suagrhaha10 天前
驱动入门的进一步深入
linux·嵌入式硬件·驱动
国科安芯10 天前
基于ASP4644多通道降压技术在电力监测系统中集成应用与发展前景
嵌入式硬件·硬件架构·硬件工程
Li Zi10 天前
STM32 ADC(DMA)双缓冲采集+串口USART(DMA)直接传输12位原始数据到上位机显示并保存WAV格式音频文件 收藏住绝对实用!!!
经验分享·stm32·单片机·嵌入式硬件
进击的程序汪10 天前
触摸屏(典型 I2C + Input 子系统设备)从设备树解析到触摸事件上报
linux·网络·嵌入式硬件