在ARM Linux下使用GPIO模擬SPI時序詳解

Author:楊正  Data2016.1.1  Mail[email protected]

一、       概述

SPI是英文SerialPeripheral Interface的縮寫,顧名思義就是串行外圍設備接口。SPI是一種高速、全雙工、同步通信總線,標準的SPI有4個引腳,常用於單片機和EEPROM、FLASH、實時時鐘、數字信號處理等器件的通信。SPI通信原理要比I2C簡單,它主要是主從方式通信,這種通信方式通常只有一個主機和一個或多個從機,標準的SPI是4根線,分別是SSEL(片選)、SCLK(時鐘,也寫作SCK)、MOSI(主機輸入,從機輸出)和MISO(主機輸入,從機輸出)。

SSEL:從設備片選使能信號。譬如從設備是低電平使能的話,當拉低這個引腳,從設備就會被選中,主機和這個被選中的從設備通信。

SCLK:時鐘信號,即主機產生。

MOSI:主機給從機發送指令或者數據的通道。

MISO:主機讀取從機的狀態或者數據的通道。

 

在某些情況下,也可以用3根或者2根線的SPI進行通信。譬如,主機只給從機發送指令,從機不需要回複數據的時候,MISO就可以不要;而主機只讀從機的數據,不需要給從機發送數據的時候,MOSI就可以不要;當一個主機與一個從機通信時,從機的片選有時可以固定爲有效電平而一直處於使能狀態,那麼SSEL可以不要,此時如果主機只給從機發數據,那麼SSEL和MISO都可以不要;如果主機只讀取從機發送來的數據,那麼SSEL和MOSI都可以不要。當然一般情況下說的SPI都指的是標準的SPI,有4根線進行通信。

 

 

二、  時序分析

衆所周知,SPI時序有4種模式,在講解4種模式之前先學習兩個單詞,即PolarityPhase,這兩個單詞分別是SPI的時鐘極性(Polarity)和相位(Phase),最常見的寫法CPOLCPHA,也有其它的一些寫法,如:

(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity =(時鐘)極性
(2) CKPHA (Clock Phase)   = CPHA = PHA = Phase =
(時鐘)相位

 

那麼時鐘極性是什麼概念呢?SPI通信過程分爲空閒狀態和通信狀態,空閒狀態是指SCLK在數據發送之前和發送完之後的狀態,通信狀態當然就是指發送數據的狀態。CPOL=1,那麼空閒狀態SCLK爲高電平,反之,CPOL=0,則空閒狀態SCLK爲低電平。

 

SPI是一個環形的總線結構,主要是在SCLK的控制下,兩個雙向移位寄存器進行數據交換。那麼主機和從機在進行交換數據的時候就設計到一個問題,即主機在什麼時刻輸出到MOSI上而從機在什麼時刻採樣這個數據,或者從機什麼時刻輸出到MISO上而主機什麼時刻採樣這個數據。同步通信的一個特點就是所有數據的變化和採樣都是伴隨着時鐘沿進行的,也就是說數據總是在時鐘的邊沿附近變化或被採樣,而一個完整的時鐘週期必定包含了一個上升沿和一個下降沿,這是週期的定義所決定的,只是這兩個沿的先後並無規定。又因爲數據從產生到它穩定是需要一定的時間,那麼如果主機在上升沿輸出數據到MOSI,從機就只能在下降沿去採樣這個數據了。反之,如果一方在下降沿輸出數據,那麼另一方就必須在上升沿採樣這個數據。那麼由誰來決定上升沿採樣還是下降沿採樣,上升沿輸出還是下降沿輸出呢?

 

CPHA=1時,表示數據的輸出在一個時鐘週期的第一個沿上,至於這個沿是上升沿還是下降沿,就得由CPOL來決定。CPOL=1那就是下降沿,反之就是上升沿。那麼數據採樣自然就是第二個沿上了。

CPHA=0時,表示數據採樣在一個時鐘週期的第一個沿上,至於這個沿是上升沿還是下降沿,就得由CPOL來決定。CPOL=1那就是下降沿,反之就是上升沿。那麼數據輸出自然就是第二個沿上了。

 

通過以上的學習,SPI的4中模式其實已經浮現出來了,下面對此做一總結:

Mode0

CPOL=0CPHA=0

Mode1

CPOL=0CPHA=1

Mode2

CPOL=1CPHA=0

Mode3

CPOL=1CPHA=1

 

圖形比文字更容易看懂,所以SPI4種模式的時序圖如下:


再進一步說明模式1SPI處於使能狀態,而且發送FIFO內有有效數據時,設置SSEL信號爲低,表示開始傳輸數據。來自Slave的數據立刻發送到Master的接收數據線MISO。半個SCLK時鐘週期之後,有效的Master數據傳輸到MOSI。此時MasterSlave數據都已經有效,SCLK管腳在接下來的半個SCLK時鐘週期之後變爲高電平。數據在SCLK時鐘的上升沿被捕獲(採樣),在時鐘的下降沿被傳送(輸出),傳輸完成後SSEL恢復爲高電平。

 

再進一步說明模式2。當SPI爲使能狀態,而且發送FIFO內有有效數據時,設置SSEL信號爲低表示開始傳輸數據。半個SCLK時鐘週期之後,MasterSlave的有效數據分別在各自的傳輸線上有效。同時,SCLK從第一個上升沿開始有效。數據在SCLK時鐘的下降沿被捕獲,在時鐘的上升沿被傳送,傳輸完成後SSEL恢復爲高電平。

 

再進一步的說明模式3。當SPI爲使能狀態,而且發送FIFO內有有效數據時,設置SSEL信號爲低表示開始傳輸數據。此時Slave的數據立刻發送到Master的接收數據線MISO。半個SCLK週期之後,Master的有效數據傳送到MOSI。再過半個SCLK時鐘週期之後,SCLK管腳設置爲低。這表示數據在SCLK時鐘的下降沿被捕獲,在SCLK時鐘的上升沿被傳送,傳輸完成後SSEL恢復爲高電平。

 

再進一步的說明模式4。當SPI爲使能狀態,而且發送FIFO內有有效數據時,設置SSEL信號爲低表示開始傳輸數據。半個SCLK時鐘週期後,MasterSlave數據在各自的傳輸線上有效。同時,時鐘SCLK1個下降沿開始有效。數據在SCLK時鐘的上升沿被捕獲,在時鐘的下降沿被傳送,傳輸完成後SSEL恢復爲高電平。

 

 

 

三、  SPI訪問寄存器模式

 

常見的SPI訪問寄存器模式有3種,分別是SingleAccess(單次訪問)、Burst Access(突發訪問)、FIFO Access(FIFO訪問)。

 

SingleAccess:通過寫訪問發送一個地址字節後跟一個數據字節,或者發送一個地址字節,再通過讀訪問接收一個數據字節。發送幀時SSEL爲低,發送完最後一個字節後SSEL變爲高。如下圖,將0x0A寫到寄存器0x02上,再從該寄存器上讀其值。


W + addr. 0x02 Data: 0x0A                      R + addr. 0x02 Data:

注意:SSEL第一次爲低電平使能時,MISO變爲高電平,然後立即變爲低電平,這表示從設備芯片已經裝備好(Thefirst time CSn goes low, MISO goes high and then low again immediately,indicating that the chip is ready.)。


Single Byte Access (Write and Read)

 

 

BurstAccess:一個地址字節後跟多個數據字節。在每個數據字節之間地址字節會在內部自動增加,並且片選信號SSEL在發送幀時爲低電平,在每個數據字節之間也保持低電平,當發送完最後一個字節後變爲高電平。如:

BYTE xdata regValues[] = {1,2,3};

halSpiWriteBurstReg(0x00, regValues,sizeof(regValues));

halSpiReadReg(0x00);

halSpiReadReg(0x01);

halSpiReadReg(0x02);


BurstWrite Followed by Single Read

 

 

FIFO Access:如果地址字節對應的是FIFO地址,那麼隨後的數據字節將針對FIFO。該地址字節沒有自動遞增而被存儲,並且不需要在每個數據字節之間發送該地址。片選信號SSEL在發送幀時爲低電平,在每個數據字節之間也保持低電平,當發送完最後一個字節後變爲高電平。

 

特別說明不同的SPI從設備地址字節的內容不一定相同,需要查看相關手冊,才能知道地址字節的構成是什麼樣的。例如:RFM69H這個模塊SPI的地址字節的組成如下:

The first byteis the address byte. It is made of:

wnr bit, whichis 1 for write access and 0 for read access

7 bits of address, MSB first

 

又如CCXX00的地址字節組成如下:

The R/W bit in the address header controlsif the register should be written or read, and the burst bit controls if it

is a single access or a burst access.

R/W

Burst

A5

A4

A3

A2

A1

A0

:其實地址位只有5位,最高位爲讀寫位,1爲讀,0爲寫;後面一位是burstAccess的使能位,當爲1時使能。

 

 

四、  例程解析

特別注意:在主設備這邊配置SPI接口時鐘的時候一定要弄清楚從設備的時鐘要求,因爲主設備這邊的時鐘極性和相位都是以從設備爲基準的。因此在時鐘極性的配置上一定要搞清楚從設備是在時鐘的上升沿還是下降沿接收數據,是在時鐘的下降沿還是上升沿輸出數據。但要注意的是,由於主設備的SDO連接從設備的SDI,從設備的SDO連接主設備的SDI,從設備SDI接收的數據是主設備的SDO發送過來的,主設備SDI接收的數據是從設備SDO發送過來的,所以主設備這邊SPI時鐘極性的配置(即SDO的配置)跟從設備的SDI接收數據的極性是相反的,跟從設備SDO發送數據的極性是相同的。

 

通過手冊得知(MOSI is generated by the master on the falling edge ofSCK and is sampled by the slave (i.e. this SPI interface) on the rising edge ofSCK. MISO is generated by the slave on the falling edge of SCK.A transferalways starts by the NSS pin going low. MISO is high impedance when NSS ishigh.),本例程使用的從設備的SPI工作在Mode1,即CPOL= 0 and CPHA = 0,且SSEL低電平使能,當SSEL爲高電平時,MISO爲高。故主機的MOSI在空閒狀態時爲高。

/*********************************************************************************
 *     Copyright:  (C) 2015 YangZheng<[email protected]> 
 *                  All rights reserved.
 *
 *      Filename:  dev_rfm69h.c
 *   Description:  This file
 *                
 *       Version:  1.0.0(12/28/2015~)
 *        Author:  Yang Zheng<[email protected]>
 *     ChangeLog:  1, Release initialversion on "12/28/2015 07:46:07 PM"
 *                
 ********************************************************************************/
#include<linux/kernel.h>
#include<linux/version.h>
#include<linux/module.h>
#include<linux/types.h>
#include<linux/errno.h>
#include<linux/fcntl.h>
#include<linux/mm.h>
#include<linux/proc_fs.h>
#include<linux/fs.h>
#include<linux/slab.h>
#include<linux/init.h>
#include<asm/uaccess.h>
#include<asm/io.h>
#include <asm/system.h>
#include<linux/miscdevice.h>
#include<linux/delay.h>
#include<linux/sched.h>
 
#include<linux/proc_fs.h>
#include<linux/poll.h>
 
#include<asm/bitops.h>
#include<asm/uaccess.h>
#include<asm/irq.h>
 
#include<linux/moduleparam.h>
#include <linux/ioport.h>
#include<linux/interrupt.h>
#include<linux/cdev.h>
#include<linux/semaphore.h>
#include<linux/wait.h>
 
#define IOCONFIG4_4                                 IO_ADDRESS(0x200f0000+ 0x054)          //複用管腳gpio4_4
#define IOCONFIG4_5                                 IO_ADDRESS(0x200f0000+ 0x050)          //複用管腳gpio4_5
#define IOCONFIG4_6                                 IO_ADDRESS(0x200f0000+ 0x04c)          //複用管腳gpio4_6
#define IOCONFIG4_7                                 IO_ADDRESS(0x200f0000+ 0x048)          //複用管腳gpio4_7
 
#define GPIO4_BASE                                    0x20180000                        //gpio基地址
#define GPIO4_SIZE                                     0x10000             // 64KB
#define GPIO4_DIR                                      IO_ADDRESS(GPIO4_BASE + 0x400)     //gpio4方向寄存器
#defineGPIO_SPI_CS_REG                         IO_ADDRESS(GPIO4_BASE+ 0x40)          //gpio4_4數據寄存器
#defineGPIO4_SPI_SCK_REG                    IO_ADDRESS(GPIO4_BASE + 0x80)          //gpio4_5數據寄存器
#defineGPIO4_SPI_MOSI_REG                 IO_ADDRESS(GPIO4_BASE+ 0x100)     //gpio4_6數據寄存器
#defineGPIO4_SPI_MISO_REG                 IO_ADDRESS(GPIO4_BASE+ 0x200)     //gpio4_7數據寄存器
#define GPIO4_4                                           (1<< 4)   //gpio4_4
#define GPIO4_5                                           (1<< 5)   //gpio4_5
#define GPIO4_6                                           (1<< 6)   //gpio4_6
#define GPIO4_7                                           (1<< 7)   //gpio4_7
 
#define DATA_LENGTH                                21
 
#define DRV_AUTHOR                                "Yang Zheng<[email protected]>"
#define DRV_DESC                                       "spi driver"                                                                                
#define DRV_VERSION                                "v0.0.1"
 
#if 0
#defineDBG(x...)   printk(x)
#define DBG_PRINT
#else
#defineDBG(x...)   do {} while (0)
#endif
 
staticDECLARE_WAIT_QUEUE_HEAD(spi_waitq);
unsigned char                                       read_tmp_data[DATA_LENGTH] = {0};
void __iomem                                       *reg_ssp_base_va;
 
typedef unsignedchar                       byte;
typedef unsignedshort                      word;
 
static int                                                read_flag = 0;
 
 
/******************************************************************************
**函數名稱:Set_nCS
**函數功能:禁用片選
**輸入參數:無
**輸出參數:無
**注意:   高電平爲禁用片選,低電平爲使能片選
******************************************************************************/
void Set_nCS(void)
{
         unsigned char regvalue;
 
         writel(0, IOCONFIG4_4);   //gpio模式
 
         regvalue = readl(GPIO4_DIR);  //設置gpio輸出
         regvalue |= GPIO4_4;
         writel(regvalue, GPIO4_DIR);
 
        
         regvalue = readl(GPIO_SPI_CS_REG); 
         regvalue |= GPIO4_4;
         writel(regvalue, GPIO_SPI_CS_REG);  //禁用片選
}
 
 
/******************************************************************************
**函數名稱:Clr_nCS
**函數功能:使能片選
**輸入參數:無
**輸出參數:無
**注意:   高電平爲禁用片選,低電平爲使能片選
******************************************************************************/
void Clr_nCS(void)
{
         unsigned char regvalue;
 
         writel(0, IOCONFIG4_4);   //gpio模式
 
         regvalue = readl(GPIO4_DIR);  //設置gpio輸出
         regvalue |= GPIO4_4;
         writel(regvalue, GPIO4_DIR);
 
         regvalue = readl(GPIO_SPI_CS_REG); 
         regvalue &= ~GPIO4_4;
         writel(0, GPIO_SPI_CS_REG);  //使能片選
 
}
 
 
/******************************************************************************
**函數名稱:Set_SCK
**函數功能:SCK爲高電平
**輸入參數:無
**輸出參數:無
**注意:    
******************************************************************************/
void Set_SCK(void)
{
         unsigned char regvalue;
 
         writel(0, IOCONFIG4_5);   //gpio模式
 
         regvalue = readl(GPIO4_DIR);  //設置gpio輸出
         regvalue |= GPIO4_5;
         writel(regvalue, GPIO4_DIR);
 
         regvalue =readl(GPIO4_SPI_SCK_REG);  //設置gpio輸出高電平
         regvalue |= GPIO4_5;
         writel(regvalue, GPIO4_SPI_SCK_REG);
}
 
 
/******************************************************************************
**函數名稱:Set_SCK
**函數功能:SCK爲高電平
**輸入參數:無
**輸出參數:無
**注意:    
******************************************************************************/
void Clr_SCK(void)
{
         unsigned char regvalue;
 
         writel(0, IOCONFIG4_5);   //gpio模式
 
         regvalue = readl(GPIO4_DIR);  //設置gpio輸出
         regvalue |= GPIO4_5;
         writel(regvalue, GPIO4_DIR);
 
         regvalue =readl(GPIO4_SPI_SCK_REG);  //設置gpio輸出低電平
         regvalue &= ~GPIO4_5;
         writel(regvalue,GPIO4_SPI_SCK_REG); 
 
}
 
 
/******************************************************************************
**函數名稱:Set_MOSI
**函數功能:MOSI爲高電平
**輸入參數:無
**輸出參數:無
**注意:    
******************************************************************************/
void Set_MOSI(void)
{
         unsigned char regvalue;
 
         writel(0, IOCONFIG4_6);   //gpio模式
 
         regvalue = readl(GPIO4_DIR);  //設置gpio輸出
         regvalue |= GPIO4_6;
         writel(regvalue, GPIO4_DIR);
 
         regvalue = readl(GPIO4_DIR);  //設置gpio輸出高電平
         regvalue |= GPIO4_6;
         writel(regvalue, GPIO4_SPI_MOSI_REG);
 
}
 
 
/******************************************************************************
**函數名稱:Set_MOSI
**函數功能:MOSI爲低電平
**輸入參數:無
**輸出參數:無
**注意:    
******************************************************************************/
void Clr_MOSI(void)
{
         unsigned char regvalue;
 
         writel(0, IOCONFIG4_6);   //gpio模式
 
         regvalue = readl(GPIO4_DIR);  //設置gpio輸出
         regvalue |= GPIO4_6;
         writel(regvalue, GPIO4_DIR);
 
         regvalue = readl(GPIO4_DIR);  //設置gpio輸出低電平
         regvalue &= ~GPIO4_6;
         writel(regvalue, GPIO4_SPI_MOSI_REG);
 
}
 
/******************************************************************************
**函數名稱:MISO_H
**函數功能:讀取MISO_H的值
**輸入參數:無
**輸出參數:無
**注意:    
******************************************************************************/
unsigned charMISO_H(void)
{
         unsigned char regvalue;
 
         writel(0, IOCONFIG4_7);   //gpio模式
 
         regvalue = readl(GPIO4_DIR);  //設置gpio輸入
         regvalue &= ~GPIO4_7;
         writel(regvalue, GPIO4_DIR);
 
         regvalue =readl(GPIO4_SPI_MISO_REG);  //讀取輸入值
         //DBG("regvalue = %x\n",regvalue);
 
         return regvalue;
}
 
/******************************************************************************
**函數名稱:SPICmd8bit
**函數功能:SPI寫入參數8bit
**輸入參數:WrPara
**輸出參數:無
**注意:   保留nCS輸出Low
******************************************************************************/
voidSPICmd8bit(byte WrPara)
{
         byte bitcnt;     
         Clr_nCS();
         Clr_SCK();
 
         for(bitcnt = 8; bitcnt != 0; bitcnt--)
         {
                   Clr_SCK();
                   if(WrPara&0x80)
                   {
                            Set_MOSI();
                   }
                   else
                   {
                            Clr_MOSI();
                   }
                   Set_SCK();
                   WrPara <<= 1;
         }
         Clr_SCK();
         Set_MOSI();
 //Set_nCS();            //*此處不關閉nCS,使用連續模式*
}
 
/******************************************************************************
**函數名稱:SPIRead8bitt
**函數功能:SPI讀取參數8bit
**輸入參數:讀取8bit數據——RdPara
**輸出參數:無
**注意:   保留nCS輸出Low
******************************************************************************/
byte SPIRead8bit(void)
{
         byte RdPara = 0;
         byte bitcnt;
          
         Clr_nCS();
         Set_MOSI();               //讀FIFO,維持SDI爲H 
 
         for(bitcnt=8; bitcnt!=0; bitcnt--)
         {
                   Clr_SCK();
                   RdPara <<= 1;
                   Set_SCK();
                   if(MISO_H())
                   {
                            RdPara |= 0x01;
                   }
                   else
                   {
                            ;
                   }
         }
 
         Clr_SCK();
 
         //Set_nCS();               //*此處不關閉nCS,使用連續模式*
         //DBG("RdPara = %x\n",RdPara);
         return(RdPara);
}
 
/******************************************************************************
**函數名稱:SPIRead
**函數功能:SPI讀取一個地址數據
**輸入參數:adr
**輸出參數:無
******************************************************************************/
byte SPIRead(byteadr)
{
         byte tmp;         
 
         SPICmd8bit(adr);               //發送要讀取的地址
         tmp = SPIRead8bit();    //讀取數據    
         Set_nCS();
 
         return(tmp);
}
 
/******************************************************************************
**函數名稱:SPIWrite
**函數功能:SPI寫入一個16數據(高8位地址,低8位數據)
**輸入參數:WrPara
**輸出參數:無
******************************************************************************/
void SPIWrite(wordWrPara)                                                                    
{                                                      
         byte bitcnt;   
          
         Clr_SCK();                           //注意SCK先清0,保持低
         Clr_nCS();
          
         WrPara |= 0x8000;           //寫數據高位置1
          
         for(bitcnt=16; bitcnt!=0; bitcnt--)
         {
                   Clr_SCK();
                   if(WrPara&0x8000)
                   {
                            Set_MOSI();
                   }
                   else
                   {
                            Clr_MOSI();
                   }
 
                   Set_SCK();
                   WrPara <<= 1;
         }
          
         Clr_SCK();
         Set_MOSI();
         Set_nCS();
}         
 
/******************************************************************************
**函數名稱:SPIBurstRead
**函數功能:SPI連續讀取模式
**輸入參數:adr——讀取地址
**          ptr——存儲數據指針
**          length 讀取長度
**輸出參數:無,數據存在ptr中
******************************************************************************/
voidSPIBurstRead(byte adr, byte *ptr, byte length)
{
         byte i;
         if(length<=1)                       //讀取長度必須大於1
         {
                   return;
         }
        else
        {
                 Clr_SCK();                          //注意SCK先清0,保持低
                 Clr_nCS();
                 SPICmd8bit(adr);               //讀取地址
                 for(i=0;i<length;i++)
                   {
                         ptr[i]= SPIRead8bit();
                   }
                 Set_nCS();        
         }
}
 
/******************************************************************************
**函數名稱:SPIBurstWrite
**函數功能:SPI連續寫入模式
**輸入參數:adr——寫入地址
**          ptr——存儲數據指針
**          length 寫入長度
**輸出參數:無
******************************************************************************/
voidBurstWrite(byte adr, byte *ptr, byte length)
{
         byte i;
 
         if(length<=1)                       //讀取長度不爲0或1
         {
                   return;
         }
         else 
         {      
                   Clr_SCK();                           //注意SCK先清0,保持低
                   Clr_nCS();                                             
                   SPICmd8bit(adr|0x80);    //連續寫
                   for(i=0;i<length;i++)
                   {
                            SPICmd8bit(ptr[i]);
                   }
                   Set_nCS();        
         }
}
 
longspi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
         ……
 
         switch(cmd)
         {
                   case SSP_READ_ALT:
                             …...
                            val = *(unsigned int*)arg;
                            addr = (unsignedchar)(val&0xff);
                            data =SPIRead(addr);
                            ……
                            *(unsigned int *)arg= (unsigned int)(data&0x0000ff);
                            break;
 
 
                   case SSP_READ_BURST:
                            {
                                     unsignedchar          addr = 0x00;
 
                                     ……
 
                                     /*讀取數據 */
                                     SPIBurstRead(addr,read_tmp_data, DATA_LENGTH);
 
                                     …...
 
                                     break;
                            }
 
                   case SSP_WRITE_ALT:
                            data = 0;
                            val  = *(unsigned int *)arg;
                            tmp = (unsignedshort)((val&0xff0000)>>8);      
                            tmp |= (unsignedshort)((val&0x0000ff)>>0);
                            ……
                            SPIWrite(tmp);
                            break; 
 
                   case SSP_WRITE_BURST:
                            res =copy_from_user(tmp_str, (unsigned char *)arg, sizeof(tmp_str));
                            if (res != 0)
                            {
                                     printk("copydata from the user space error\n");
                            }
                           
                            ……
                           
                            BurstWrite(addr,data, DATA_LENGTH);
                                                                                                      
                            break;
 
                   default:
                            {
                                     printk("Nosuch spi command %#x!\n", cmd);
                                     return -1;
                            }
         }
         return 0;
}
 
int spi_open(structinode * inode, struct file * file)
{
         ……
 
         return 0;
}
 
intspi_close(struct inode * inode, struct file * file)
{
         return 0;
}
 
 
static structfile_operations spi_fops = {
         .owner          =THIS_MODULE,
         .unlocked_ioctl        = spi_ioctl,
         .open           =spi_open,
         .release           =spi_close
};
 
 
static structmiscdevice spi_dev = {
         .minor                =MISC_DYNAMIC_MINOR,                                                                                      
         .name               =DEV_NAME,
         .fops                           =&spi_fops,
};
 
static int __initspi_gpio_init(void)
{
         int ret;
 
         ret = misc_register(&spi_dev);
         if (ret < 0)
         {
                   printk("registerspi_gpio device failed!\n");
 
                   return -1;
         }
 
         printk("SPI driver initializesuccessful! .\n");
 
         return 0;
}
 
static void __exitspi_gpio_exit(void)
{
         misc_deregister(&spi_dev);
 
         printk("SPI driver exit!\n");
}
module_init(spi_gpio_init);
module_exit(spi_gpio_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);


 

 

 

參考文檔:

http://wenku.baidu.com/link?url=6RKxmOJX7aw26PXrmaB-RTzLwWYQ4YWfmL6g54FPl7hhvBH5u3W5OuAuTtp_u8XhU1w_29c3l7yktsyMmoNsGWSYbIOsu_xIMmstjiCejiC

 

http://blog.sina.com.cn/s/blog_69b5d2a50101am99.html

 

http://www.eeworld.com.cn/mcu/2015/0319/article_18823.html

 

http://wenku.baidu.com/link?url=KaiyT6TEh0iFd5BVNwa17vzLEhKwDGSTaIIji3TJ5_w4qs9G2RAnqPHXeMdQKBrmh-luZaM3AvIMrUML4Ed30MTjb1s62F_xzU-klppSYcm

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章