关于SPI
,不同的芯片具体通信方式可能会不大一样,所以要具体问题具体分析,下面是最近做
LCD
时碰到的两个模拟
SPI
协议的代码,芯片通信方式不同,代码也就不同了
SPI的工作原理不多说,网上一大把。
1.一款夏普的屏,
hx8363A,
和
host
的接法是三线的
SPI
写:
读:
如上图,发命令D/CX
是
0
,如果是参数
D/CX
就是
1.
下面是相关代码
点击(
此处
)折叠或打开
-
#define SET_LSCE_LOW SET_GPIO_OUT
(
LSCE_GPIO_PIN
,
0
)
-
#define SET_LSCE_HIGH SET_GPIO_OUT
(
LSCE_GPIO_PIN
,
1
)
-
#define SET_LSCK_LOW SET_GPIO_OUT
(
LSCK_GPIO_PIN
,
0
)
-
#define SET_LSCK_HIGH SET_GPIO_OUT
(
LSCK_GPIO_PIN
,
1
)
-
#define SET_LSDA_LOW SET_GPIO_OUT
(
LSDA_GPIO_PIN
,
0
)
-
#define SET_LSDA_HIGH SET_GPIO_OUT
(
LSDA_GPIO_PIN
,
1
)
-
-
static void start_lcd
(
void
)
-
{
-
SET_LSCE_LOW
;
-
}
-
-
static void stop_lcd
(
void
)
-
{
-
SET_LSCE_HIGH
;
-
}
-
-
static void write_lcd_datas
(
unsigned
int
data
,
unsigned
int
len
)
-
{
-
int
i
;
-
-
for
(
i
=
len
–
1
;
i
>
=
0
;
i
–
–
)
-
{
-
SET_LSCK_LOW
;
-
if
(
data
&
(
1
<
<
i
)
)
-
SET_LSDA_HIGH
;
-
else
-
SET_LSDA_LOW
;
-
UDELAY
(
1
)
;
-
SET_LSCK_HIGH
;
-
UDELAY
(
1
)
;
-
}
-
}
-
-
static void write_lcd_cmd
(
unsigned char cmd
)
-
{
-
unsigned
int
x
;
-
UDELAY
(
1
)
;
-
x
=
0x0000
|
cmd
;
-
write_lcd_datas
(
x
,
9
)
;
-
}
-
-
static void write_lcd_data
(
unsigned char data
)
-
{
-
unsigned
int
x
;
-
UDELAY
(
1
)
;
-
x
=
0x0100
|
data
;
-
write_lcd_datas
(
x
,
9
)
;
-
}
-
-
static void lcd_recv_data
(
)
-
{
-
int
i
;
-
for
(
i
=
0
;
i
<
8
;
i
+
+
)
-
{
-
SET_LSCK_LOW
;
-
UDELAY
(
1
)
;
-
SET_LSCK_HIGH
;
-
UDELAY
(
1
)
;
-
}
-
}
读写的时候加上
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
,一个是标志信息,一个是数据
下面为相关的代码
点击(
此处
)折叠或打开
-
static void write_lcd_datas
(
unsigned
int
data
,
unsigned
int
len
)
-
{
-
int
i
;
-
for
(
i
=
len
–
1
;
i
>
=
0
;
i
–
–
)
-
{
-
SET_LSCK_LOW
;
-
if
(
data
&
(
1
<
<
i
)
)
-
SET_LSDA_HIGH
;
-
else
-
SET_LSDA_LOW
;
-
UDELAY
(
1
)
;
-
SET_LSCK_HIGH
;
-
UDELAY
(
1
)
;
-
}
-
}
-
static void write_lcd_cmd
(
unsigned char highbyte
,
unsigned char lowbyte
)
-
{
-
unsigned
int
x
;
-
start_lcd
(
)
;
-
UDELAY
(
1
)
;
-
write_lcd_datas
(
0x20
,
8
)
;
/
/
发标志位
-
-
UDELAY
(
1
)
;
-
write_lcd_datas
(
highbyte
,
8
)
;
/
/
发命令高位
-
stop_lcd
(
)
;
-
-
start_lcd
(
)
;
-
UDELAY
(
1
)
;
-
write_lcd_datas
(
0x00
,
8
)
;
/
/
发标志位
-
-
UDELAY
(
1
)
;
-
write_lcd_datas
(
lowbyte
,
8
)
;
/
/
发命令低位
-
stop_lcd
(
)
;
-
}
-
static void write_lcd_data
(
unsigned char data
)
-
{
-
unsigned
int
x
;
-
start_lcd
(
)
;
-
UDELAY
(
1
)
;
-
write_lcd_datas
(
0x40
,
8
)
;
/
/
发标志位
-
-
UDELAY
(
1
)
;
-
write_lcd_datas
(
data
,
8
)
;
/
/
发参数低位
-
stop_lcd
(
)
;
-
}
-
static __inline void send_ctrl_cmd
(
unsigned
int
cmd1
,
unsigned
int
cmd2
)
-
{
-
write_lcd_cmd
(
(
cmd1
&
0xFF
)
,
(
cmd2
&
0xFF
)
)
;
-
}
-
-
static __inline void send_data_cmd
(
unsigned
int
data
)
-
{
-
write_lcd_data
(
(
data
&
0xFF
)
)
;
-
}
写寄存器的时候直接这样写就行了
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
种传输模式:
点击(
此处
)折叠或打开
-
开发者可根据具体设备使用的是哪种模式来实现之,我们项目种的这块LCD的模式为CPOL
=
1
,
CPHA
=
1
.
-
具体实现如下:
-
#define SPI_DATA GPIO0
-
#define SPI_CLK GPIO1
-
void spi_write
(
char data
)
-
{
-
int8 i
=
7
;
-
uint8 mask
[
]
=
{
0x01
,
0x02
,
0x04
,
0x08
,
0x10
,
0x20
,
0x40
,
0x80
}
;
-
for
(
;
i
>
=
0
;
i
–
–
)
{
-
gpio_out
(
SPI_CLK
,
GPIO_LOW_VALUE
)
;
-
gpio_out
(
SPI_DATA
,
(
(
data
&
mask
[
i
]
)
>
>
i
)
)
;
-
spi_delay
(
10
)
;
-
gpio_out
(
SPI_CLK
,
GPIO_HIGH_VALUE
)
;
-
spi_delay
(
10
)
;
-
}
-
}
实际上模拟
SPI
是很简单的事情,只要对照
SPI
传输模式的时序图来模拟就行了。需要注意的是一定要有个等待时间,以使数据在数据线上稳定下来,并使设备端有时间取数据。刚开始调试的时候可以适当把等待时间延长一点,当调通了
SPI
后在降下等待时间。
我写的等待时间如下:
#define spi_delay(delay)
\
{ \
register uint32 i = 0; \
while(i < delay) { \
__asm{ \
NOP; \
NOP; \
NOP; \
NOP; \
}; \
i -= 4; \
} \
}
呵呵,整个过程就是这样简单。