作者:ARM混迹江湖
[摘自]http://blog.tianya.cn/blogger/post_show.asp?BlogID=648077&PostID=9434859
Linux下所有的设备都是以文件的形式来操作的。设备软件分为两部分:应用程序和驱动程序,其中,应用程序负责打开设备并对它发出读写指令,驱动程序接受指令并完成读写的过程。
本文以AD5310用于AP/BG为例,讲述其应用和驱动的编写过程。
1. 硬件电路
其中GPIO为CPU管脚,当J2上有Power Meter接上时,BNC_EN为低,程序开始从系统中获取RSSI,换算成相应的值以SPI时序送给AD5310,AD5310把它转化为相应电压,在Power Meter上显示。
2. 驱动实现
2.1 代码编写
驱动的编写主要是编写操作函数,例如读写等,因此驱动程序的结构比较单一,其主要的不同是操作函数的实现过程。
AD5310驱动的实现代码即说明如下:
#ifndef __KERNEL__ //判断驱动是编译进内核还是编译成模块
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include … //包含相关头文件
#ifndef __MOD_INC_USE_COUNT
#define AH_MOD_INC_USE_COUNT(_m) \
if (!try_module_get(_m)) { \
printk(KERN_WARNING "try_module_get failed\n"); \
return NULL; \
}
#define AH_MOD_DEC_USE_COUNT(_m) module_put(_m)
#else
#define AH_MOD_INC_USE_COUNT(_m) MOD_INC_USE_COUNT
#define AH_MOD_DEC_USE_COUNT(_m) MOD_DEC_USE_COUNT
#endif
… //此处声明函数
struct file_operations spi_fops= //告诉系统应用程序的某个命令由谁来执行
{ //file_operation 是系统定义的结构体
.owner = THIS_MODULE,
.open = AD5310_open,
.release = AD5310_release,
.write = AD5310_write,
.read = AD5310_read,
};
char spi_name[]="AD5310"; //device name
static int gmajor = 254; //device ID
/*************************************/
static int __init spidrv_init_module(void) //register device
{
int retv; //register_chrdev()是linux系统注册函数
retv=register_chrdev(gmajor,spi_name,&spi_fops);
if(retv<0)
{
printk("Register Fail!\n");
return retv;
}
printk("SPI device OK!\n");
return 0;
}
static void __exit spidrv_cleanup(void) //unregister device
{
int retv;
retv=unregister_chrdev(gmajor,spi_name);
if(retv<0)
{
printk("UnRegister Fail!\n");
return;
}
printk("SPIDRV:GOOD-bye!\n");
}
static int AD5310_open(struct inode *inode,struct file *file)
{ //打开文件时设置操作标记
AH_MOD_INC_USE_COUNT(THIS_MODULE);
return 0;
}
static int AD5310_release(struct inode *inode,struct file *file)
{
AH_MOD_DEC_USE_COUNT(THIS_MODULE);
return 0;
}
/*******************************************************************
*function:init GPIO
*description:GPIO7 use for DAC input,GPIO9 use for CS,GPIO12 use as
*input,GPIO14 use as clock source
*******************************************************************/
void GPIO_init(void)
{
*IXP4XX_GPIO_GPOER = *IXP4XX_GPIO_GPOER & 0xBD7F;//GPIO7,GPIO9,GPIO14 ,output
}
/*************************************
*read data from input GPIO12
*************************************/
static ssize_t AD5310_read(struct file *fp, char __user *buf, size_t len, loff_t *loff)
{
int input;
printk("read the status of GPIO12\n");
*IXP4XX_GPIO_GPOER = *IXP4XX_GPIO_GPOER | 0x1000;//GPIO12,input
Delay(5);
input = *IXP4XX_GPIO_GPINR; //get the status of GPIO12 pin
input = input >> 12;
input = input & 0x01;
*buf = input;
return(1);
}
/*********************************************************************
*write data to output GPIO9
*AD5310 input register contents:
* DB15(MSB) ........................DB0(LSB)
* x x PD1 PD0 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 x x
*
*PD1 PD0 = 0 0 normal operation
*PD1 PD0 = 0 1 1K resister to GND power down modes
*PD1 PD0 = 1 0 100K to GND power down modes
*PD1 PD0 = 1 1 Three-State power down modes
********************************************************************/
static int AD5310_write(struct file *fp,char *buf,int len,loff_t *loff)
{
int i;
int temp;
int spibuf;
char *p;
int writedata;
p=buf; //get the data ,buf[1] low buf[0] high
writedata=*p;
p++;
writedata = (writedata << 8) + *p;
if(writedata > 1023 || writedata < 0)
{
printk("Err:the data must be 0~1023");
return -1;
}
spibuf=0xC003; //set AD5310 operation mode as normal operation
temp=writedata;
temp= temp << 2;
spibuf += temp; //the data packet needed to send to AD5310
GPIO_init();
Delay(100);
*IXP4XX_GPIO_GPOUTR=*IXP4XX_GPIO_GPOUTR & 0xFDFF;//set GPIO9 low
*IXP4XX_GPIO_GPOUTR=*IXP4XX_GPIO_GPOUTR | 0x4000;//set GPIO14 high
for(i=0;i<16;i++) //start to send the data
{
*IXP4XX_GPIO_GPOUTR=*IXP4XX_GPIO_GPOUTR | 0x4000;//set GPIO14 high
if ((spibuf & 0x8000) == 0)
{
*IXP4XX_GPIO_GPOUTR=*IXP4XX_GPIO_GPOUTR & 0xFF7F; //GPIO7 output 0
}
else
{
*IXP4XX_GPIO_GPOUTR=*IXP4XX_GPIO_GPOUTR | 0x80; //GPIO7 output 1
}
spibuf = spibuf <<1;
*IXP4XX_GPIO_GPOUTR=*IXP4XX_GPIO_GPOUTR & 0xBFFF; //set GPIO14 low
}
*IXP4XX_GPIO_GPOUTR=*IXP4XX_GPIO_GPOUTR | 0x200;//set GPIO9 high
*IXP4XX_GPIO_GPOUTR=*IXP4XX_GPIO_GPOUTR | 0x4000;//set GPIO14 high
return 2;
}
/**************************************************************/
module_init(spidrv_init_module);
module_exit(spidrv_cleanup);
/*end of spidrv.c*/
2.2 驱动编译
在linux2.6内核下,驱动须编译成 .ko 的形式,即需要两个编译阶段,第一步生成 .o 文件,第二步生成 .ko 文件
AD5310驱动的Makefile编写如下:
PWD := $(shell pwd) //指向当前目录
TOPDIR=$(PWD)/../
Tool := $(TOPDIR)/tools/buildroot/build_armeb/bin/ //交叉编译工具的路径
KERNELDIR=$(TOPDIR)/src/kernels/linux-2.6.13.2/ //系统内核路径
EXTRA_CFLAGS += -D__KERNEL__ -DMODULE -I$(KERNELDIR)/include -Wall//环境变量
ARC :=arm //CPU类型(平台类型)
CROSS_CC :=$(Tool)armeb-linux- //gcc名称
obj-m += spidrv.o //目标文件的源文件
default: //默认执行的命令及参量
make ARCH=$(ARC) CROSS_COMPILE=$(CROSS_CC) -C $(KERNELDIR) SUBDIRS=$(PWD) modules
clean: //执行清除命令时清除的文件类型
rm -rf *.o *.ko *mod.c
2.3 驱动加载
把生成的 .ko 文件放到板子上的某个路径下如/lib/modules,执行如下命令:
#insmod /lib/modules/spidrv.ko > /dev/null
#mknod /dev/AD5310 c 254 1
此时若驱动正常工作,就会输出打印信息 SPI device OK! ,并且在/dev 目录下可以看到 AD5310 这个设备。
3. 应用实现
Linux下应用程序以文件的形式来操作设备,其最基本的操作顺序为
Open() -> read()/write() -> close()
RSSI应用实现的代码如下:
#define __KERNEL__
#include … //包含需要的头文件
#define … //定义相关变量
/*******************************************************************
*get the RSSI value
*******************************************************************/
int GetRSSIvalue(void)
{
int8_t rssivalue;
const char *ifname;
char aa[5] = {"ath0"};
ifname = aa;
u_char buf[24*1024];
struct iwreq iwr;
u_char *cp;
int s, len;
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0)
err(1, "socket(SOCK_DRAGM)");
memset(&iwr, 0, sizeof(iwr));
strncpy(iwr.ifr_name, ifname, sizeof(iwr.ifr_name));
iwr.u.data.pointer = (void *) buf;
iwr.u.data.length = sizeof(buf);
if (ioctl(s, IEEE80211_IOCTL_STA_INFO, &iwr) < 0)
errx(1, "unable to get station information");
len = iwr.u.data.length;
if (len < sizeof(struct ieee80211req_sta_info))
return;
cp = buf;
do {
struct ieee80211req_sta_info *si;
u_char *vp;
si = (struct ieee80211req_sta_info *) cp;
vp = (u_int8_t *)(si+1);
rssivalue = si->isi_rssi;
cp += si->isi_len, len -= si->isi_len;
} while (len >= sizeof(struct ieee80211req_sta_info));
return(rssivalue);
}
/*******************************************************************
*main function
*******************************************************************/
int main(int argc,char **argv)
{
int rssivalue;
int senddata;
int RSSIprecision;
char inputstate[2];
char databuf[2];
const int maxrssi=66;
const int minrssi=2;
int fd;
int size;
if((fd=open("/dev/AD5310",O_RDWR))==-1) //打开设备文件
{
perror("device open fail\n");
exit(1)
}
RSSIprecision=1023/maxrssi;
Delay(5);
while(1)
{
size = read(fd,inputstate,1,0); //读取GPIO12状态,查看是否有Meter接入
if(inputstate[0]==1) //no meter,high
{
printf("RSSI not enabled!\n");
printf("Please mount RSSI meter!\n");
Delay(1000);
}
else //有Meter接入
{
printf("mount RSSI meter OK!");
rssivalue = GetRSSIvalue(); //从系统中获得RSSI的值
printf("RSSI:%4d",rssivalue);
if(rssivalue > maxrssi)
{ //over load,display max vcc
senddata = 1023;
}
else if(rssivalue <= minrssi)
{
senddata = 2; // output is too low
}
else
{
senddata = RSSIprecision *rssivalue; //calculate the data needed to send
}
databuf[1]=senddata;
databuf[0]= senddata >> 8;
write(fd,databuf,2,0); //把数据发送给AD5310
printf("databuf[1]=%d\n",databuf[1]);
printf("databuf[0]=%d\n",databuf[0]);
sleep(1); //延时
}
}
}
/**************************************************************/
/*end of RSSI_AD5310*/
另外,在编写真正的应用程序之前,为了保证驱动程序的正确性,一般会先写一些小的测试应用程序来测试驱动,在驱动正确的情况下才会编写测试我们需要的应用程序。