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

来源:互联网 发布:js控制div显示和隐藏 编辑:程序博客网 时间:2024/06/05 14:11

这里分享一个基于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)#endifstatic 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();elseclr_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);}elsereturn -EFAULT;if(gpio_request(AT91_PIN_PC22, "ad7491") == 0){gpio_direction_input(AT91_PIN_PC22);}elsereturn -EFAULT;if(gpio_request(AT91_PIN_PC24, "ad7492") == 0){gpio_direction_output(AT91_PIN_PC24, 1);}elsereturn -EFAULT;if(gpio_request(AT91_PIN_PC25, "ad7493") == 0){gpio_direction_output(AT91_PIN_PC25, 1);}elsereturn -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函数在我的内核也使用失败了,就是不

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

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

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















0 0
原创粉丝点击