SPI协议参考:
SPI协议及其工作原理浅析
http://bbs.chinaunix.net/thread-1916003-1-1.html
SPI总线协议及SPI时序图详解
http://blog.163.com/sunshine_linting/blog/static/44893323201181482335951
一、概述
SPI, Serial Perripheral Interface, 串行外围设备接口, 是 Motorola 公司推出的一种同步串行接口技术. SPI 总线在物理上是通过接在外围设备微控制器(PICmicro) 上面的微处理控制单元 (MCU) 上叫作同步串行端口(Synchronous Serial Port) 的模块(Module)来实现的, 它允许 MCU 以全双工的同步串行方式, 与各种外围设备进行高速数据通信.
SPI 主要应用在 EEPROM, Flash, 实时时钟(RTC), 数模转换器(ADC), 数字信号处理器(DSP) 以及数字信号解码器之间. 它在芯片中只占用四根管脚 (Pin) 用来控制以及数据传输, 节约了芯片的 pin 数目, 同时为 PCB 在布局上节省了空间. 正是出于这种简单易用的特性, 现在越来越多的芯片上都集成了 SPI技术.
二、 特点
1. 采用主-从模式(Master-Slave) 的控制方式
SPI 规定了两个 SPI 设备之间通信必须由主设备 (Master) 来控制次设备 (Slave). 一个 Master 设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI 协议还规定 Slave 设备的Clock 由 Master 设备通过 SCK 管脚提供给 Slave 设备, Slave 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave 设备不能正常工作.
2. 采用同步方式(Synchronous)传输数据
Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的.
3. 数据交换(Data Exchanges)
SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 "发送者(Transmitter)" 或者 "接收者(Receiver)".在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了.
一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access). 所以, Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上.
在数据传输的过程中, 每次接收到的数据必须在下一次数据传输之前被采样. 如果之前接收到的数据没有被读取, 那么这些已经接收完成的数据将有可能会被丢弃, 导致 SPI 物理模块最终失效. 因此, 在程序中一般都会在 SPI 传输完数据后, 去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的.
三、 工作机制
1. 概述
通常情况下, 我们只需要对上图所描述的四个管脚(pin) 进行编程即可控制整个 SPI 设备之间的数据通信:
SCK :Serial Clock主要的作用是 Master 设备往 Slave 设备传输时钟信号, 控制数据交换的时机以及速率;
SS/CS:Slave Select/Chip Select, 用于 Master 设备片选 Slave 设备, 使被选中的 Slave 设备能够被 Master 设备所访问;
SDO/MOSI: Serial Data Output/Master Out Slave In, 在 Master 上面也被称为 Tx-Channel, 作为数据的出口, 主要用于 SPI 设备发送数据;
SDI/MISO: Serial Data Input/Master In Slave Out, 在 Master 上面也被称为 Rx-Channel, 作为数据的入口, 主要用于SPI 设备接收数据;
SPI 设备在进行通信的过程中, Master 设备和 Slave 设备之间会产生一个数据链路回环(Data Loop), 就像上图所画的那样, 通过 SDO 和 SDI 管脚, SSPSR 控制数据移入移出 SSPBUF, Controller 确定 SPI 总线的通信模式, SCK 传输时钟信号.
2.工作模式
CPOL位表示初始时钟极性:
CPOL = 0表示时钟初始为低电平,所以开始的第一(leading)边缘是上升沿,第二边缘(trailing)是下降沿。
CPOL = 1时的时钟启动为高电平,所以第一(leading)的边缘是下降沿。
CPHA的指示用于采样数据的时钟相位(注意是采样数据,采样是指的将数据线上的数据所存起来)
CPHA = 0时表示边沿超前
CPHA = 1表示边沿滞后
以 S3C2440 为例:
![](https://i-blog.csdnimg.cn/blog_migrate/ac991b28018816555fe9cbced63aafe3.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/cb4c3a0705ad166776652f297b4c9843.jpeg)
模式1:CPOL = 0 CPHA = 1 主机从机的数据均是在上升沿改变,下降沿锁存
模式2:CPOL = 1 CPHA = 0 主机从机的数据均是在上升沿改变,下降沿锁存
模式3:CPOL = 0 CPHA = 1 主机从机的数据均是在下降沿改变,上升沿锁存
以模式0为例,看看具体时序图:
![](https://i-blog.csdnimg.cn/blog_migrate/eff922b944bd63414ba9293b77264f95.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/0ebf642b25ea419068708a7111724095.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/5b45cfaeaaf15cb4ff3a04079ccaefec.jpeg)
显然,主机和从机都是在上升沿锁存数据,下降沿改变数据。
四、裸机程序
在使用spi控制器的情况下,裸机程序十分简单,以 S3C2440 为例,简单说明一下。
1、配置引脚
1. 设置片选输出低电平
2. 配置 GPE11 SPIMISO 、GPE12 SPIMOSI 、GPE13 SPICLK 为特殊功能引脚
2、配置控制寄存器
SPCONn[6-5]: SPI模式选择,查询 中断 DMA等
SPCONn[4] : 时钟使能
SPCONn[3] : 主机模式、从机模式
SPCONn[2] : CPOL
SPCONn[1] : CPHA
SPCONn[0] : 接收数据是是否自动发送,这里选普通模式,我们自己发送就好
3、发送数据
1、检查状态寄存器 传输是否可用 SPSTAn[0] 1表示可用,0表示正在发送或者接收
2、将数据写入 SPTDATn
4、接收数据
1、检查状态寄存器 传输是否可用 SPSTAn[0] 1表示可用,0表示正在发送或者接收
2、写数据 0xff 到 SPTDATn ,这就是普通模式的要求,接收的同时发送,0xff发送完了,数据也就接收完了
3、检查状态寄存器 传输是否可用 SPSTAn[0] 1表示可用,0表示正在发送或者接收
4、从 SPRDATn 取数据
-
static void SPIFlash_Set_CS(char val)
-
{
-
if (val)
-
GPGDAT |= ( 1<< 10);
-
else
-
GPGDAT &= ~( 1<< 10);
-
}
-
static void SPI_GPIO_Init(void)
-
{
-
/* GPG1 OLED_CSn output
-
* GPG10 FLASH_CSn output
-
*/
-
GPGCON &= ~(( 3<<( 1* 2)) | ( 3<<( 10* 2)));
-
GPGCON |= ( 1<<( 1* 2)) | ( 1<<( 10* 2));
-
GPGDAT |= ( 1<< 1) | ( 1<< 10);
-
-
/*
-
* GPF3 OLED_DC output
-
* GPE11 SPIMISO
-
* GPE12 SPIMOSI
-
* GPE13 SPICLK
-
*/
-
GPFCON &= ~( 3<<( 3* 2));
-
GPFCON |= ( 1<<( 3* 2));
-
-
GPECON &= ~(( 3<<( 11* 2)) | ( 3<<( 12* 2)) | ( 3<<( 13* 2)));
-
GPECON |= (( 2<<( 11* 2)) | ( 2<<( 12* 2)) | ( 2<<( 13* 2)));
-
}
-
-
-
void SPISendByte(unsigned char val)
-
{
-
while (!(SPSTA0 & 1));
-
SPTDAT0 = val;
-
}
-
-
unsigned char SPIRecvByte(void)
-
{
-
SPTDAT0 = 0xff;
-
while (!(SPSTA0 & 1));
-
return SPRDAT0;
-
}
-
-
-
static void SPIControllerInit(void)
-
{
-
/* OLED : 100ns, 10MHz
-
* FLASH : 104MHz
-
* 取10MHz
-
* 10 = 50 / 2 / (Prescaler value + 1)
-
* Prescaler value = 1.5 = 2
-
* Baud rate = 50/2/3=8.3MHz
-
*/
-
SPPRE0 = 2;
-
SPPRE1 = 2;
-
-
/* [6:5] : 00, polling mode
-
* [4] : 1 = enable
-
* [3] : 1 = master
-
* [2] : 0
-
* [1] : 0 = format A
-
* [0] : 0 = normal mode
-
*/
-
SPCON0 = ( 1<< 4) | ( 1<< 3);
-
SPCON1 = ( 1<< 4) | ( 1<< 3);
-
-
}
-
-
void SPIInit(void)
-
{
-
/* 初始化引脚 */
-
SPI_GPIO_Init();
-
-
SPIControllerInit();
-
}
实际的SPI通信中,是由片选函数、字节读、字节写三部分组合而成~例如:
-
void SPIFlashRead(unsigned int addr, unsigned char *buf, int len)
-
{
-
int i;
-
-
SPIFlash_Set_CS( 0);
-
SPISendByte( 0x03);
-
SPISendByte(addr >> 16);
-
SPISendByte(addr >> 8);
-
SPISendByte(addr & 0xff);
-
for (i = 0; i < len; i++)
-
buf[i] = SPIRecvByte();
-
-
SPIFlash_Set_CS( 1);
-
}
上一篇文章中,分析了 master 和 spi 设备驱动的框架,我们知道 master 是在 atmel_spi_probe 函数中分配、设置、注册。其实在哪不重要,主要是设置的那些东西,我们再来看一下都设置了哪些东西
master = spi_alloc_master(&pdev->dev, sizeof *as);
/* 设置 master */
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; // 所支持的模式
master->bus_num = pdev->id; // 控制器编号,用于分辨外围spi设备是连接在哪一个控制器上
master->num_chipselect = 4; // 片选最大值+1,spi设备的片选值要小于它
master->setup = atmel_spi_setup; // 一个控制器上可能接有多个spi设备,它们的频率和模式是不一样的,用于设备之间切换时设置这些信息。
master->transfer = atmel_spi_transfer; // 最重要的发送函数
ret = request_irq(irq, atmel_spi_interrupt, 0, dev_name(&pdev->dev), master);
master->transfer 函数,就跟I2c里的adapter->xxx_i2c_xfer函数一样,是最底层的发送接收函数,在spi中,设备驱动层调用到master层transfer函数的过程是这样的spi_sync(spi, &m)->spi_async(spi,&m)->master->transfer(spi, &m),也就是说,设备驱动层传递给我们一个spi_device 和 一个 spi_message ,还有一点值得注意的。
-
int spi_sync(struct spi_device *spi, struct spi_message *message)
-
{
-
DECLARE_COMPLETION_ONSTACK(done);
-
int status;
-
-
message->complete = spi_complete;
-
message->context = &done;
-
status = spi_async(spi, message);
-
if (status == 0) {
-
wait_for_completion(&done);
-
status = message->status;
-
}
-
message->context = NULL;
-
return status;
-
}
在 spi_new_device 我们根据 Board_list 里的信息创建了 spi_device,它含有一下信息
-
struct spi_device *spi_new_device(struct spi_master *master,
-
struct spi_board_info *chip)
-
{
-
struct spi_device *proxy;
-
int status;
-
-
proxy = spi_alloc_device(master);
-
-
proxy->chip_select = chip->chip_select;
-
proxy->max_speed_hz = chip->max_speed_hz;
-
proxy->mode = chip->mode;
-
proxy->irq = chip->irq;
-
strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
-
proxy->dev.platform_data = ( void *) chip->platform_data;
-
proxy->controller_data = chip->controller_data;
-
proxy->controller_state = NULL;
-
-
status = spi_add_device(proxy);
-
-
return proxy;
-
}
那么,我们master->transfer函数需要做什么?
1、传输前,根据 spi_device 里的信息,对 master 进行设置(调用master->setup),因为一个spi控制器可能接有多个spi设备,它们的频率 模式可能是不一样的
2、取出 spi_message 里的 spi_transfer 进行寄存器操作,发送或接收,这个过程和 I2C 的传输过程是类似的。
下面开始分析代码,先看setup函数。代码参考:韦东山老师的spi教程
-
static int s3c2440_spi_setup(struct spi_device *spi)
-
{
-
struct s3c_spi_info *info;
-
struct clk *clk;
-
-
int cpol = 0;
-
int cpha = 0;
-
-
int div;
-
-
info = spi_master_get_devdata(spi->master);
-
clk = clk_get( NULL, "plck");
-
-
/* 设置传输模式 : mode
-
* 传输频率 : max_speed_hz
-
* bits_per_word : 不用管
-
*/
-
-
/* spi_mode: CPOL,CPHA组成一个值,0,1,2,3
-
*/
-
-
if (spi->mode & 1)
-
{
-
cpha = 1;
-
}
-
if (spi->mode & 2)
-
{
-
cpol = 1;
-
}
-
-
/* 写入SPCON0,1 */
-
/* [6:5] : 01, polling mode
-
* [4] : 1 = enable
-
* [3] : 1 = master
-
* [2] : CPOL
-
* [1] : CPHA
-
* [0] : 0 = normal mode
-
*/
-
writeb(( 1<< 5) | ( 1<< 4) | ( 1<< 3) | (cpol << 2) | (cpha << 1), info->reg_base + S3C2410_SPCON);
-
-
/*
-
* Baud rate = PCLK / 2 / (Prescaler value + 1)
-
* Prescaler value = PCLK/(2*Baud rate) - 1
-
* Prescaler Value : 0,1,...,255
-
*/
-
div = DIV_ROUND_UP(clk_get_rate(clk), spi->max_speed_hz * 2) - 1;
-
clk_put(clk);
-
-
if (div > 255)
-
div = 255;
-
-
writeb(div, info->reg_base + S3C2410_SPPRE);
-
-
return 0;
-
}
下面来看transfer函数
思路:
1、首先一个 spi_message 对应一个 片选过程,我们要先片选
2、master->setup
3、取出 spi_message 中的 第一个 spi_transfer
如果是读:跟裸机一样,先写0xff,等待中断
如果是写:那么将 spi_transfer->tx_buf[0]里的数据写到寄存器,等待休眠
何时唤醒?
在中断服务程序里,判断当前 spi_transfer 的发送接收完毕了,唤醒,取出下一个 spi_transfer ,如果有所得 spi_transfer 传输完了,则唤醒设备驱动层进程
中断服务程序:
读:先把上次读的取出来,判断是否还有数据要读?如果有则继续发送0xff,等待中断,没有则唤醒,进入下一个spi_transfer
写:判断当前 spi_transfer 的发送是否完成,没有完成则把下一个Buf里的数据发送或接收。完成了,则唤醒,进入下一个spi_transfer。
附上韦东山老师完整源码:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* 构造注册spi_master */
-
-
static struct spi_master *spi0_controller;
-
static struct spi_master *spi1_controller;
-
-
struct s3c_spi_info {
-
int irq;
-
unsigned int reg_base;
-
struct completion done;
-
struct spi_transfer *cur_t;
-
int cur_cnt;
-
struct platform_device *pdev;
-
};
-
-
static int s3c2440_spi_setup(struct spi_device *spi)
-
{
-
struct s3c_spi_info *info;
-
struct clk *clk;
-
-
int cpol = 0;
-
int cpha = 0;
-
-
int div;
-
-
info = spi_master_get_devdata(spi->master);
-
clk = clk_get( NULL, "plck");
-
-
/* 设置传输模式 : mode
-
* 传输频率 : max_speed_hz
-
* bits_per_word : 不用管
-
*/
-
-
/* spi_mode: CPOL,CPHA组成一个值,0,1,2,3
-
*/
-
-
if (spi->mode & 1)
-
{
-
cpha = 1;
-
}
-
if (spi->mode & 2)
-
{
-
cpol = 1;
-
}
-
-
/* 写入SPCON0,1 */
-
/* [6:5] : 01, polling mode
-
* [4] : 1 = enable
-
* [3] : 1 = master
-
* [2] : CPOL
-
* [1] : CPHA
-
* [0] : 0 = normal mode
-
*/
-
writeb(( 1<< 5) | ( 1<< 4) | ( 1<< 3) | (cpol << 2) | (cpha << 1), info->reg_base + S3C2410_SPCON);
-
-
/*
-
* Baud rate = PCLK / 2 / (Prescaler value + 1)
-
* Prescaler value = PCLK/(2*Baud rate) - 1
-
* Prescaler Value : 0,1,...,255
-
*/
-
div = DIV_ROUND_UP(clk_get_rate(clk), spi->max_speed_hz * 2) - 1;
-
clk_put(clk);
-
-
if (div > 255)
-
div = 255;
-
-
writeb(div, info->reg_base + S3C2410_SPPRE);
-
-
return 0;
-
}
-
-
static int s3c2440_spi_transfer(struct spi_device *spi, struct spi_message *mesg)
-
{
-
struct spi_master *master = spi->master;
-
struct s3c_spi_info *info;
-
struct spi_transfer *t = NULL;
-
-
info = spi_master_get_devdata(master);
-
-
/* 1. 选中芯片 */
-
s3c2410_gpio_setpin(spi->chip_select, 0); /* 默认为低电平选中 */
-
-
/* 2. 发数据 */
-
-
/* 2.1 发送第1个spi_transfer之前setup */
-
master->setup(spi);
-
-
/* 2.2 从spi_message中逐个取出spi_transfer,执行它 */
-
list_for_each_entry (t, &mesg->transfers, transfer_list) {
-
/* 处理这个spi_transfer */
-
info-> cur_t = t;
-
info->cur_cnt = 0;
-
init_completion(&info->done);
-
-
/* 如果该spi_transfer的speed_hz或bits_per_word
-
* 不是0, 则调用setup来设置
-
*/
-
-
if (t->tx_buf)
-
{
-
/* 发送 */
-
writeb((( unsigned char *)t->tx_buf)[ 0], info->reg_base + S3C2410_SPTDAT);
-
-
/* 它会触发中断 */
-
-
/* 休眠 等待当前transfer 发送完毕 */
-
wait_for_completion(&info->done);
-
}
-
else if(t->rx_buf)
-
{
-
/* 接收 */
-
writeb( 0xff, info->reg_base + S3C2410_SPTDAT);
-
/* 它会触发中断 */
-
-
/* 休眠 等待当前 transfer 接收完毕 */
-
wait_for_completion(&info->done);
-
}
-
-
/* 如果该spi_transfer的cs_change为1
-
* 则取消片选
-
*/
-
}
-
-
-
/* 2.3 唤醒等待的进程 */
-
mesg->status = 0;
-
mesg->complete(mesg->context);
-
-
/* 3. 取消片选 */
-
s3c2410_gpio_setpin(spi->chip_select, 1); /* 默认为低电平选中 */
-
-
return 0;
-
}
-
-
static irqreturn_t s3c2440_spi_irq(int irq, void *dev_id)
-
{
-
struct spi_master *master = (struct spi_master *)dev_id;
-
struct s3c_spi_info *info = spi_master_get_devdata(master);
-
struct spi_transfer *t = info->cur_t;
-
-
if (!t)
-
{
-
/* 误触发 */
-
return IRQ_HANDLED;
-
}
-
-
/* 处理 spi_transfer */
-
-
if (t->tx_buf) /* 是发送 */
-
{
-
info->cur_cnt++;
-
-
if (info->cur_cnt < t->len) /* 没发完? */
-
writeb((( unsigned char *)t->tx_buf)[info->cur_cnt], info->reg_base + S3C2410_SPTDAT);
-
else
-
complete(&info->done); /* 唤醒 */
-
}
-
else /* 接收 */
-
{
-
/* 读/存数据 */
-
(( unsigned char *)t->rx_buf)[info->cur_cnt] = readb(info->reg_base + S3C2410_SPRDAT);
-
info->cur_cnt++;
-
-
if (info->cur_cnt < t->len) /* 没收完? */
-
writeb( 0xff, info->reg_base + S3C2410_SPTDAT);
-
else
-
complete(&info->done); /* 唤醒 */
-
}
-
return IRQ_HANDLED;
-
}
-
-
static void s3c2440_spi_controler_init(int which)
-
{
-
struct clk *clk = clk_get(NULL, "spi");
-
-
/* 使能spi controller 0/1的时钟 */
-
clk_enable(clk);
-
clk_put(clk);
-
-
/* GPIO */
-
if (which == 0)
-
{
-
/* SPI controller 0 */
-
/*
-
* GPE11 SPIMISO
-
* GPE12 SPIMOSI
-
* GPE13 SPICLK
-
*/
-
s3c2410_gpio_cfgpin(S3C2410_GPE( 11), S3C2410_GPE11_SPIMISO0);
-
s3c2410_gpio_cfgpin(S3C2410_GPE( 12), S3C2410_GPE12_SPIMOSI0);
-
s3c2410_gpio_cfgpin(S3C2410_GPE( 13), S3C2410_GPE13_SPICLK0);
-
-
}
-
else if (which == 1)
-
{
-
/* SPI controller 1 */
-
/*
-
* GPG5 SPIMISO
-
* GPG6 SPIMOSI
-
* GPG7 SPICLK
-
*/
-
s3c2410_gpio_cfgpin(S3C2410_GPG( 5), S3C2410_GPG5_SPIMISO1);
-
s3c2410_gpio_cfgpin(S3C2410_GPG( 6), S3C2410_GPG6_SPIMOSI1);
-
s3c2410_gpio_cfgpin(S3C2410_GPG( 7), S3C2410_GPG7_SPICLK1);
-
-
/* 使能spi controller 1的时钟 */
-
}
-
}
-
-
static struct spi_master *create_spi_master_s3c2440(int bus_num, unsigned int reg_base_phy, int irq)
-
{
-
int ret;
-
struct spi_master *master;
-
struct s3c_spi_info *info;
-
struct platform_device *pdev;
-
-
-
pdev = platform_device_alloc( "s3c2440_spi", bus_num);
-
platform_device_add(pdev);
-
-
master = spi_alloc_master(&pdev->dev, sizeof(struct s3c_spi_info));
-
master->bus_num = bus_num;
-
master->num_chipselect = 0xffff;
-
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
-
-
master->setup = s3c2440_spi_setup;
-
master->transfer = s3c2440_spi_transfer;
-
-
info = spi_master_get_devdata(master);
-
info->reg_base = ( unsigned int)ioremap(reg_base_phy, 0x18);
-
info->irq = irq;
-
info->pdev = pdev;
-
-
/* 硬件初始化 */
-
s3c2440_spi_controler_init(bus_num);
-
-
ret = request_irq(irq, s3c2440_spi_irq, 0, "s3c2440_spi", master);
-
-
spi_register_master(master);
-
-
return master;
-
}
-
-
static void destroy_spi_master_s3c2440(struct spi_master *master)
-
{
-
struct s3c_spi_info *info = spi_master_get_devdata(master);;
-
-
spi_unregister_master(master);
-
platform_device_del(info->pdev);
-
platform_device_put(info->pdev);
-
free_irq(info->irq, master);
-
iounmap(( void *)info->reg_base);
-
//kfree(master);
-
}
-
-
static int spi_s3c2440_init(void)
-
{
-
spi0_controller = create_spi_master_s3c2440( 0, 0x59000000, IRQ_SPI0);
-
spi1_controller = create_spi_master_s3c2440( 1, 0x59000020, IRQ_SPI1);
-
-
return 0;
-
}
-
-
static void spi_s3c2440_exit(void)
-
{
-
destroy_spi_master_s3c2440(spi0_controller);
-
destroy_spi_master_s3c2440(spi1_controller);
-
}
-
-
module_init(spi_s3c2440_init);
-
module_exit(spi_s3c2440_exit);
-
MODULE_DESCRIPTION( "SPI Controller Driver");
-
MODULE_AUTHOR( "weidongshan@qq.com,www.100ask.net");
-
MODULE_LICENSE( "GPL");