Linux3.6.9下AD7490的SPI通信驱动设计

这里分享一个基于Linux的SPI总线驱动,芯片是AD7490,用IO模拟的时序,O(∩_∩)O谢谢奋斗,接下来会有同样芯片的驱动用SPI控制器实现和用LINUX的SPI子系统实现,敬请期待。下面进入大家最喜欢的讲解环节。

平台

   1.内核版本 Linux3.6.9

    2.CPU ATMEL SAMA5D34(可以用所以的AT91系列内核函数)

    3.板子是自己做的,加了一个AD7490 AD转换器。

        对AD7490这款芯片的介绍(CPU和内核版本的介绍见官网吧):16通道逐次逼近型12位AD转换器,基于SPI总线通信,最快转换速度1MSPS,工作电压2.7V-5.25V。转换过程和数据获得由片选信号线和串行时钟信号控制。输入信号在片选CS的下降沿被采样,并且转换也是从此刻开始,转换时间由时钟频率决定,需要16个时钟周期的转换时间。转换结果一16位输出,其中包含4为地址位表示通道的编号,12位表示数据。

管脚使用情况:CS <--------> PC25         SCLK <--------> PC24            MOSI <--------> PC23          MISO <--------> PC22

  控制寄存器初始化时每一位的值如下:

WRITE_BIT = 1;

SEQ = 0;

ADD3---ADD0 = 0;

PM1 = PM0 = 1;

SHADOW = 0;

WEAK = 0;

RANGE = 0;

CODING = 1;

因此我们对控制寄存器的赋值应该是0X8310,这是我阅读AD7490数据手册后得到的每个位的值,我用的是AD7490的普通模式,如果大家有其他的需求,可以自行阅读芯片手册进行必要的改动,也可与本人讨论以发挥它的更多功能。

代码:

   

#include <linux/module.h>

#include <linux/kernel.h>

<span style="font-size:24px;">#include <linux/init.h>	
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/device.h> 
#include <linux/errno.h>
#include <linux/delay.h>
#include <asm/delay.h> 
#include <mach/gpio.h>
#include <linux/gpio.h>

//#define AD7490_DEBUG
#ifdef  AD7490_DEBUG
#define ad7490_dbg(fmt, arg...) 	printk(KERN_WARNING fmt, ##arg)
#else
#define ad7490_dbg(fmt, arg...) 	printk(KERN_DEBUG fmt, ##arg)
#endif

static int ad7490_minor = 0;
static struct class *ad7490_class;	
static int ad7490_major = 0;

#define  set_clk()					__gpio_set_value(AT91_PIN_PC24, 1)
#define  clr_clk()					__gpio_set_value(AT91_PIN_PC24, 0)
#define  set_mosi()					__gpio_set_value(AT91_PIN_PC23, 1)
#define  clr_mosi()					__gpio_set_value(AT91_PIN_PC23, 0)
#define  set_cs()					__gpio_set_value(AT91_PIN_PC25, 1)
#define  clr_cs()					__gpio_set_value(AT91_PIN_PC25, 0)
#define  delay()					udelay(10)               //10us延时
#define  cmd_start_normal_mod       0x8310
#define  cmd_to_ad7490(x)           ((cmd_start_normal_mod) | ((x) << 10))

static void write_ad7490(unsigned short cmd)
{
	int i = 16;
//	ad7490_dbg("%s\n", __FUNCTION__);  //上升沿写
	clr_cs();
	for(i = 15; i >= 0; i--)
	{
		set_clk();
		delay();
		if(cmd & (1 << i))
			set_mosi();
		else
			clr_mosi();
		delay();
		clr_clk();
		delay();
	}
	set_cs();
	set_clk();
}

static void give_clk(void)
{
	int i = 0;
	clr_cs();
	for(i = 15; i >= 0; i--)
	{
		set_clk();
		delay();
		clr_clk();
		delay();
	}
	set_cs();
	set_clk();
}

static int read_ad7490(void) //下降沿读
{
	unsigned int i = 0;
	int data_miso = 0;

	clr_cs();
	delay();
	data_miso |= __gpio_get_value(AT91_PIN_PC22);
	for(i = 0; i < 15; i++)
	{
		data_miso = data_miso << 1;
		set_clk();
		delay();
		clr_clk();
		delay();
		data_miso |= __gpio_get_value(AT91_PIN_PC22);
	}
	set_cs();
	set_clk();
	return data_miso;
}

static void init_ad7490(void)
{
	int i = 0, j = 0;
	ad7490_dbg("%s\n", __FUNCTION__);
	for(j = 0; j < 2; j++)
	{
		set_mosi();
		clr_cs();
		for(i = 0; i < 16; i++)
		{
			clr_clk();
			delay();
			set_clk();
			delay();
		}
		set_cs();
		delay();
	}
	clr_mosi();
	write_ad7490(cmd_start_normal_mod);
}

static int init_spi(void)
{
	if(gpio_request(AT91_PIN_PC23, "ad7490") == 0){
		gpio_direction_output(AT91_PIN_PC23, 0);
	}else
		return -EFAULT;
		
	if(gpio_request(AT91_PIN_PC22, "ad7491") == 0){
		gpio_direction_input(AT91_PIN_PC22);
	}else
		return -EFAULT;
		
	if(gpio_request(AT91_PIN_PC24, "ad7492") == 0){
		gpio_direction_output(AT91_PIN_PC24, 1);
	}else
		return -EFAULT;

	if(gpio_request(AT91_PIN_PC25, "ad7493") == 0){
		gpio_direction_output(AT91_PIN_PC25, 1);
	}else
		return -EFAULT;
	return 0;
}

static int ad7490_open(struct inode *inode, struct file *filp)
{	
	int ret = 0;	
	ad7490_dbg("%s\n", __FUNCTION__);
	//init_ad7490();				
	return ret;
}

ssize_t ad7490_read(struct file *file, char __user *buf, size_t count, loff_t *offp)
{
	int ret, len;
    int spi_miso_data = 0;
	int channel_true = 0;
	int channel_to_usr = 0;
	len = min(count, sizeof(spi_miso_data));

	spi_miso_data = read_ad7490();

	//ad7490_dbg("spi_miso_data = 0x%x\n", spi_miso_data);
	channel_true = ((0xf << 12) & spi_miso_data) >> 12;
	channel_to_usr = 15 - channel_true;
	//ad7490_dbg("channel_to_usr = %d\n", channel_to_usr);

	spi_miso_data = (spi_miso_data & 0xfff) | (channel_to_usr << 12);
	

	if(copy_to_user(buf, &spi_miso_data, len) != 0)
	{		
		ret = -EFAULT;	
		goto cp_err;	
	}	
	return len;
cp_err:	
	return ret;
}

ssize_t ad7490_write(struct file *file, const char __user *buf, size_t count, loff_t *offp)
{
	unsigned int usr_cmd;
	unsigned int cmd;
	unsigned long len = min(count, sizeof(usr_cmd));	
	int ret;	

	if(copy_from_user(&usr_cmd, buf, len) != 0)
	{		
		ret = -EFAULT;		
		goto cp_err;	
	}	

	//ad7490_dbg("usr_cmd = %d\n", usr_cmd);
	cmd = cmd_to_ad7490(15 - usr_cmd);
	ad7490_dbg("cmd = 0x%x\n", cmd);
	write_ad7490(cmd);

	give_clk();
	
	return len;	
cp_err:	
	return ret;	
}

static struct file_operations ad7490_fops = {	
	owner:		THIS_MODULE,	
	open:		ad7490_open,
	read:       ad7490_read,
	write:      ad7490_write,
};

static int __init drv_ad7490_init(void)
{	
	struct device *ad7490_device;	
	int retval;		
	ad7490_dbg("%s\n", __FUNCTION__);
	if(init_spi() < 0){
		printk("gpio_request error\n");
		return -EFAULT;
	}
	
	ad7490_major = register_chrdev(0, "ad7490", &ad7490_fops);
	if(ad7490_major < 0)
	{		
		retval = ad7490_major;		
		goto chrdev_err;	
	}
	ad7490_class = class_create(THIS_MODULE,"ad7490_class");	
	if(IS_ERR(ad7490_class))
	{		
		retval =  PTR_ERR(ad7490_class);	
		goto class_err;	
	}
	ad7490_device = device_create(ad7490_class,NULL, MKDEV(ad7490_major, ad7490_minor), NULL,"ad7490");		
	if(IS_ERR(ad7490_device))
	{		
		retval = PTR_ERR(ad7490_device);		
		goto device_err;	
	}
	return 0;

device_err:
	device_destroy(ad7490_class,MKDEV(ad7490_major, ad7490_minor));
	class_destroy(ad7490_class);
class_err:	
	unregister_chrdev(ad7490_major, "ad7490");
chrdev_err: 
	return retval;
}

static void __exit drv_ad7490_exit(void)
{	
	ad7490_dbg("%s\n", __FUNCTION__);
	unregister_chrdev(ad7490_major, "ad7490");
	device_destroy(ad7490_class,MKDEV(ad7490_major, ad7490_minor));	
	class_destroy(ad7490_class);
	gpio_free(AT91_PIN_PC22);
	gpio_free(AT91_PIN_PC23);
	gpio_free(AT91_PIN_PC24);
	gpio_free(AT91_PIN_PC25);
}


module_init(drv_ad7490_init);
module_exit(drv_ad7490_exit);
MODULE_LICENSE("Dual BSD/GPL");	</span>

问题记录:

1.我用的内核是买开发板时带的内核就是AT91系列的,它支持一系列

的AT91函数,但是在使用AT91_set_gpio_value等一系列AT91函数时

却使用失败,我跟踪失败原因发现是内核中的一个变量gpio_banks在

内核启动阶段没有被赋值,它是一个全局变量,不初始化就是0,在

AT91系列函数中都会调用pin_to_controller,而这个函数又用到这样一

句话likely(pin < gpio_banks), 因为gpio_banks为0,因此此函数返回

NULL(意思就是条件不成立),那么gpio_banks的初始化又在上面地方

呢?在内核版本我也不知道什么版本以上,platform机制的device部分

不在内核中注册,有dtb指定不知道哪里注册了,dtb又是什么呢?我

们为什么要编译下载它呢?这也是导致很多platform_device找不到的

原因。

2.我的这个系统运行一段时间后(超不过15分钟)就会返回垃圾数值,

好像是芯片停止转换了一样,具体表现就是读每个通道的值都差不多

是通一个值,但读到的通道编号是正确的,说明AD转换环节出了问

题,因AD7490的转换过程受我们提供的

CLK控制,它进行AD转换也需要我们提供的时钟,开始时我提供的

时钟频率过快,芯片长时间可能已经崩溃,当我放慢了CLK频率后

,一切正常,它的转换需要16个CLK的转换时间,因此在提供完要

求转换的通道号以后我在底层驱动给了AD转换器16个CLK的转换

时间,这个时间是很短的,这样就省去了上层应用通过估算来确定

软件延时以确保转换完成的时间。只需要写通道编号马上读数值就

可以了。

总结:

1.如果使用IO模拟实现SPI通信的话,上升沿或是下降沿读写数据

其实没有一个定论,我试过,怎么写都能写进去,但读不可以,这

个可能和具体的芯片性能有关系。

2.__gpio_set_value函数是内核带的,可以用于任何GPIO,以后还

是用它吧,很好。

3.但是要想设置SAMA5D34的GPIO为特殊功能A的话就比较尴尬

了,AT91_set_A_periph函数在我的内核也使用失败了,就是不

能使,该怎么解决这个问题呢?

对代码的注释部分如有不解之处请留言交流,欢迎赐教。

这是我的第一篇博客,谢谢。















猜你喜欢

转载自blog.csdn.net/u011994171/article/details/39117791