GPIO模拟SPI接口代码

  • Post author:
  • Post category:其他


http://wenku.baidu.com/view/b316705077232f60ddcca184.html



关于SPI

,不同的芯片具体通信方式可能会不大一样,所以要具体问题具体分析,下面是最近做


LCD


时碰到的两个模拟


SPI


协议的代码,芯片通信方式不同,代码也就不同了


SPI的工作原理不多说,网上一大把。

1.一款夏普的屏,

hx8363A,



host

的接法是三线的

SPI


写:




读:



如上图,发命令D/CX




0


,如果是参数


D/CX


就是


1.

下面是相关代码


点击(

此处

)折叠或打开

  1. #define SET_LSCE_LOW SET_GPIO_OUT

    (

    LSCE_GPIO_PIN

    ,

    0

    )


  2. #define SET_LSCE_HIGH SET_GPIO_OUT

    (

    LSCE_GPIO_PIN

    ,

    1

    )


  3. #define SET_LSCK_LOW SET_GPIO_OUT

    (

    LSCK_GPIO_PIN

    ,

    0

    )


  4. #define SET_LSCK_HIGH SET_GPIO_OUT

    (

    LSCK_GPIO_PIN

    ,

    1

    )


  5. #define SET_LSDA_LOW SET_GPIO_OUT

    (

    LSDA_GPIO_PIN

    ,

    0

    )


  6. #define SET_LSDA_HIGH SET_GPIO_OUT

    (

    LSDA_GPIO_PIN

    ,

    1

    )



  7. static void start_lcd

    (

    void

    )



  8. {



  9. SET_LSCE_LOW

    ;



  10. }



  11. static void stop_lcd

    (

    void

    )



  12. {



  13. SET_LSCE_HIGH

    ;



  14. }



  15. static void write_lcd_datas

    (

    unsigned

    int

    data

    ,

    unsigned

    int


    len


    )



  16. {




  17. int

    i

    ;




  18. for


    (

    i

    =


    len




    1

    ;

    i

    >


    =

    0

    ;

    i







    )



  19. {



  20. SET_LSCK_LOW

    ;



  21. if


    (

    data

    &


    (

    1

    <


    <

    i

    )


    )


  22. SET_LSDA_HIGH

    ;



  23. else


  24. SET_LSDA_LOW

    ;


  25. UDELAY

    (

    1

    )


    ;


  26. SET_LSCK_HIGH

    ;


  27. UDELAY

    (

    1

    )


    ;



  28. }



  29. }



  30. static void write_lcd_cmd

    (

    unsigned char cmd

    )



  31. {



  32. unsigned

    int

    x

    ;


  33. UDELAY

    (

    1

    )


    ;


  34. x

    =

    0x0000

    |

    cmd

    ;


  35. write_lcd_datas

    (

    x

    ,

    9

    )


    ;



  36. }



  37. static void write_lcd_data

    (

    unsigned char data

    )



  38. {



  39. unsigned

    int

    x

    ;


  40. UDELAY

    (

    1

    )


    ;


  41. x

    =

    0x0100

    |

    data

    ;


  42. write_lcd_datas

    (

    x

    ,

    9

    )


    ;



  43. }



  44. static void lcd_recv_data

    (


    )



  45. {




  46. int

    i

    ;



  47. for


    (

    i

    =

    0

    ;

    i

    <

    8

    ;

    i

    +


    +


    )



  48. {



  49. SET_LSCK_LOW

    ;


  50. UDELAY

    (

    1

    )


    ;


  51. SET_LSCK_HIGH

    ;


  52. UDELAY

    (

    1

    )


    ;



  53. }



  54. }

读写的时候加上

start_lcd





stop_lcd

start_lcd();

send_ctrl_cmd(0xB9);

send_data_cmd(0xFF);

stop_lcd();



二.

1.最近又调了一款屏,

nt35510


,也是用的是


spi


,但是他的寄存器是


16


位的,通信的时候也有些区别





上图为写时序,由于是16

位寄存器,所以地址要分两次发,一次发寄存器高位,一次发低位。每次发两个


byte,


第一个


byte


包含一些标志信息


,


读写


/dcx/


高低位 等,第二个


byte


就是具体的数据


(


这里是寄存器地址的高低位


)

地址发完后再发参数,和上面一样,一个包包含两个

byte


,一个是标志信息,一个是数据

下面为相关的代码



点击(

此处

)折叠或打开

  1. static void write_lcd_datas

    (

    unsigned

    int

    data

    ,

    unsigned

    int


    len


    )



  2. {




  3. int

    i

    ;



  4. for


    (

    i

    =


    len




    1

    ;

    i

    >


    =

    0

    ;

    i







    )



  5. {



  6. SET_LSCK_LOW

    ;



  7. if


    (

    data

    &


    (

    1

    <


    <

    i

    )


    )


  8. SET_LSDA_HIGH

    ;



  9. else


  10. SET_LSDA_LOW

    ;


  11. UDELAY

    (

    1

    )


    ;


  12. SET_LSCK_HIGH

    ;


  13. UDELAY

    (

    1

    )


    ;



  14. }



  15. }


  16. static void write_lcd_cmd

    (

    unsigned char highbyte

    ,

    unsigned char lowbyte

    )



  17. {



  18. unsigned

    int

    x

    ;


  19. start_lcd

    (


    )


    ;


  20. UDELAY

    (

    1

    )


    ;


  21. write_lcd_datas

    (

    0x20

    ,

    8

    )


    ;


    /


    /

    发标志位


  22. UDELAY

    (

    1

    )


    ;


  23. write_lcd_datas

    (

    highbyte

    ,

    8

    )


    ;


    /


    /

    发命令高位

  24. stop_lcd

    (


    )


    ;



  25. start_lcd

    (


    )


    ;


  26. UDELAY

    (

    1

    )


    ;


  27. write_lcd_datas

    (

    0x00

    ,

    8

    )


    ;


    /


    /

    发标志位


  28. UDELAY

    (

    1

    )


    ;


  29. write_lcd_datas

    (

    lowbyte

    ,

    8

    )


    ;


    /


    /

    发命令低位

  30. stop_lcd

    (


    )


    ;



  31. }


  32. static void write_lcd_data

    (

    unsigned char data

    )



  33. {



  34. unsigned

    int

    x

    ;


  35. start_lcd

    (


    )


    ;


  36. UDELAY

    (

    1

    )


    ;


  37. write_lcd_datas

    (

    0x40

    ,

    8

    )


    ;


    /


    /

    发标志位


  38. UDELAY

    (

    1

    )


    ;


  39. write_lcd_datas

    (

    data

    ,

    8

    )


    ;


    /


    /

    发参数低位

  40. stop_lcd

    (


    )


    ;



  41. }


  42. static __inline void send_ctrl_cmd

    (

    unsigned

    int

    cmd1

    ,

    unsigned

    int

    cmd2

    )



  43. {



  44. write_lcd_cmd

    (


    (

    cmd1

    &

    0xFF

    )


    ,


    (

    cmd2

    &

    0xFF

    )


    )


    ;



  45. }



  46. static __inline void send_data_cmd

    (

    unsigned

    int

    data

    )



  47. {



  48. write_lcd_data

    (


    (

    data

    &

    0xFF

    )


    )


    ;



  49. }

写寄存器的时候直接这样写就行了

send_ctrl_cmd(0xFF,0x00);

send_data_cmd(0xAA);






SPI

协议概括


SPI


,是英语

Serial Peripheral interface

的缩写,顾名思义就是串行外围设备接口。是

Motorola

首先在其

MC68HCXX

系列处理器上定义的。

SPI

接口主要应用在

EEPROM



FLASH

,实时时钟,

AD

转换器,还有数字信号处理器和数字信号解码器之间。

SPI

,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为

PCB

的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,比如

AT91RM9200.


SPI


的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少

4

根线,事实上

3

根也可以(单向传输时)。也是所有基于

SPI

的设备共有的,它们是

SDI

(数据输入),

SDO

(数据输出),

SCK

(时钟),

CS

(片选)。




1



SDO –

主设备数据输出,从设备数据输入




2



SDI –

主设备数据输入,从设备数据输出




3



SCLK




时钟信号,由主设备产生




4



CS –

从设备使能信号,由主设备控制


其中

CS

是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效。这就允许在同一总线上连接多个

SPI

设备成为可能。


接下来就负责通讯的

3

根线了。通讯是通过数据交换完成的,这里先要知道

SPI

是串行通讯协议,也就是说数据是一位一位的传输的。这就是

SCK

时钟线存在的原因,由

SCK

提供时钟脉冲,

SDI



SDO

则基于此脉冲完成数据传输。数据输出通过

SDO

线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。这样,在至少

8

次时钟信号的改变(上沿和下沿为一次),就可以完成

8

位数据的传输。


要注意的是,

SCK

信号线只由主设备控制,从设备不能控制信号线。同样,在一个基于

SPI

的设备中,至少有一个主控设备。这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少

8

位数据,而

SPI

允许数据一位一位的传送,甚至允许暂停,因为

SCK

时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。也就是说,主设备通过对

SCK

时钟线的控制可以完成对通讯的控制。

SPI

还是一个数据交换协议:因为

SPI

的数据输入和输出线独立,所以允许同时完成数据的输入和输出。不同的

SPI

设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。


在点对点的通信中,

SPI

接口不需要进行寻址操作,且为全双工通信,显得简单高效。在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比

I2C

系统要稍微复杂一些。


最后,

SPI

接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。


AT91RM9200




SPI

接口主要由

4

个引脚构成:

SPICLK



MOSI



MISO



/SS

,其中

SPICLK

是整个

SPI

总线的公用时钟,

MOSI



MISO

作为主机,从机的输入输出的标志,

MOSI

是主机的输出,从机的输入,

MISO

是主机的输入,从机的输出。

/SS

是从机的标志管脚,在互相通信的两个

SPI

总线的器件,

/SS

管脚的电平低的是从机,相反

/SS

管脚的电平高的是主机。在一个

SPI

通信系统中,必须有主机。

SPI

总线可以配置成单主单从,单主多从,互为主从。


SPI


的片选可以扩充选择

16

个外设

,

这时

PCS

输出

=NPCS,



NPCS0~3



4-16

译码器

,

这个译码器是需要外接

4-16

译码器,译码器的输入为

NPCS0~3

,输出用于

16

个外设的选择。


详细的

SPI

规范可参考

SPI

协议。





GPIO

模拟

SPI

的实现






下面将结合本人项目中的经验来详细描述如何用

GPIO

来模拟

SPI

协议


项目中要求实现一块


LCD





ssd1815br1


的驱动,它与


BB


的通信使用


SPI


协议,由于


BB





SPI


总线已使用完,


因此考虑使用


GPIO


来模拟实现。


GPIO


对应


SPI


引脚的关系如下:




1



SDO – GPIO0





BB



LCD

的数据线)




2



SDI –

无,因为暂时不需要

BB

接收来自

LCD

的数据




3



SCLK


– GPIO1




4



CS –

接地, 使

LCD

一直处于使能状态。


接下来就是要实现

SPI

的协议了,

SPI



4

种传输模式:




点击(

此处

)折叠或打开

  1. 开发者可根据具体设备使用的是哪种模式来实现之,我们项目种的这块LCD的模式为CPOL

    =

    1

    ,

    CPHA

    =

    1

    .


  2. 具体实现如下:

  3. #define SPI_DATA GPIO0

  4. #define SPI_CLK GPIO1

  5. void spi_write

    (

    char data

    )



  6. {



  7. int8 i

    =

    7

    ;


  8. uint8 mask

    [


    ]


    =


    {


    0x01

    ,

    0x02

    ,

    0x04

    ,

    0x08

    ,

    0x10

    ,

    0x20

    ,

    0x40

    ,

    0x80

    }


    ;



  9. for


    (


    ;

    i

    >


    =

    0

    ;

    i







    )


    {



  10. gpio_out

    (

    SPI_CLK

    ,

    GPIO_LOW_VALUE

    )


    ;


  11. gpio_out

    (

    SPI_DATA

    ,


    (


    (

    data

    &

    mask

    [

    i

    ]


    )


    >


    >

    i

    )


    )


    ;


  12. spi_delay

    (

    10

    )


    ;


  13. gpio_out

    (

    SPI_CLK

    ,

    GPIO_HIGH_VALUE

    )


    ;


  14. spi_delay

    (

    10

    )


    ;



  15. }



  16. }


实际上模拟

SPI

是很简单的事情,只要对照

SPI

传输模式的时序图来模拟就行了。需要注意的是一定要有个等待时间,以使数据在数据线上稳定下来,并使设备端有时间取数据。刚开始调试的时候可以适当把等待时间延长一点,当调通了

SPI

后在降下等待时间。






我写的等待时间如下:




#define spi_delay(delay)


\




{ \




register uint32 i = 0; \




while(i < delay) { \




__asm{ \




NOP; \




NOP; \




NOP; \




NOP; \




}; \




i -= 4; \




} \




}


呵呵,整个过程就是这样简单。