这里分享一个基于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函数在我的内核也使用失败了,就是不
能使,该怎么解决这个问题呢?
对代码的注释部分如有不解之处请留言交流,欢迎赐教。
这是我的第一篇博客,谢谢。