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函数在我的内核也使用失败了,就是不
能使,该怎么解决这个问题呢?
对代码的注释部分如有不解之处请留言交流,欢迎赐教。
这是我的第一篇博客,谢谢。
- Linux3.6.9下AD7490的SPI通信驱动设计
- linux3.2下adt7320的spi驱动编写
- AD7490 SPI DRIVER
- Linux3.5下的蜂鸣器驱动测试
- spi驱动(基于linux3.4.2)
- Linux下的spi驱动
- Linux3.5下的PWM蜂鸣器驱动测试 (2)
- S3c2410-SPI驱动设计
- Linux SPI驱动设计
- linux下SPI驱动的补充
- Linux下的SPI总线驱动
- Linux下SPI驱动的分析
- linux下的SPI模块驱动使用
- linux下SPI驱动
- linux下SPI驱动
- Linux SPI 子系统 驱动设计
- 两单片机通过 SPI 通信的软件协议设计概述
- jz2440ARM开发板下添加Linux3.4.2的wm8976音频模块驱动
- 开放一些常见功能的工具类代码
- ACdream原创群赛(18)题解6题
- Python 批量获取Google+用户动态 (分页)
- 在ubuntu14.04上安装sogoupinyin
- SRAM与SDRAM的区别
- Linux3.6.9下AD7490的SPI通信驱动设计
- 博客开张小记
- 动态表单实现客户端二次过滤及字段汇总统计
- leetcode 之 Scramble String
- js获取浏览器基本信息
- 一个不错的国外在线代理网站,可以上youtobe,google等
- Java基础(1)
- Android LCD(一):LCD基本原理篇
- 关于免费网游的道具消费设计探讨