# SPI 协议笔记 (基于 AD/DA 芯片 XPT2046) 坑惨我了

# 被坑经历

我买的 51 似乎是新版的 上面的 ADDA 芯片不是视频里面说的 PCF8591 视频里面是以 PCF8591 来讲解的 而那个芯片正好也是走的 IIC 协议 所以视频里面改一改通讯地址就能用了 我就一直抱着我的单片机学啊学啊 一直都不行 在网上找了很多例程都没办法用 (因为都是找的 PCF8591 的代码 当然用不了) 所以这个问题就被搁置了 之后后面有一天我无意中看到了我的单片机上面的 AD/DA 芯片特别小 感觉有些不对劲 拿出来和同学的一比较 才发现自己的那个芯片是 XPT2046 走的是 SPI 协议 这才能继续进行下去 (51 单片机结束的一个工程需要利用到 AD 转换 必须得用到 ADDA)

# SPI 协议

和 IIC 协议类似 SPI 协议也是用于不同模块之间的通讯 但是 SPI 用了四根线 分别是 SCLK CS MOSI (master output slave input) 和 MISO (master input slave output)

其中 SCLK 为时钟节拍 其电平的变化使得每一位的数据被传送出去 CS 为使能端 当 CS 为低电平时 SCLK 才能起作用 MOSI 和 MISO 分别对应着主发从收和主收从发 默认情况下 SCLK 空闲状态为低电平 此时将数据送给 MOSI (或 MISO) 再将 SCLK 电平拉高 此时数据便发出去了 此外 SPI 由高位到低位的顺序传送数据

其实在 SPI 协议中 SCLK 空闲时的电平、在第几个跳变沿发送数据都是可以自定义的 就我目前看来 如果不是特殊项目对数据传输的时效性有很高要求的话 使用默认情况就可以满足要求了

在 SPI 协议中 时钟极性 (CPOL) 就是用来定义 SCLK 空闲时候的电平的
当 SPOL = 0 时 SCLK 空闲时的电平为 0
当 SPOL = 1 时 SCLK 空闲时的电平为 1

时钟相位 (CPHA) 是用来定义采取第几个跳变沿时的数据
当 CHPA = 0 时 采用第一个跳变沿时候的数据
当 CHPA= 1 时 采用第二个跳变沿时候的数据

由 CHPA 和 CPOL 的变化 也就产生了四种模式

模式CPOLCHPA功能
Mode 000SCLK 低电平空闲 且在第一个跳变沿时 (上升沿) 采集数据
Mode 101SCLK 低电平空闲 且在第二个跳变沿时 (下降沿) 采集数据
Mode 210SCLK 高电平空闲 且在第一个跳变沿时 (下降沿) 采集数据
Mode 311SCLK 高电平空闲 且在第二个跳变沿时 (上升沿) 采集数据

img

因为 SPI 采用了两根数据线 所以使得收发数据都变得很容易 以下为 XPT2046 的通讯过程

  1. 使能端 (CS) 电平降低
  2. 主发从收:发送从机 (XPT2046) 地址
  3. 从发主收:主机接收来自从机的一字节的数据
  4. 使能端 (CS) 电平升高

与 DHT11 的通讯比起来 SPI 不用做数据校验 与 IIC 协议比起来 SPI 不用响应应答信号 在编写过程中非常方便 但是相对来说还是缺少了数据的稳定性 因为它并未考虑到主机未接收时的措施

# 完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/***********DIN 对应 MOSI, DOUT 对应 MISO**********/

void SPI_write(uchar dat)
{
uchar i;

for (i = 0; i < 8; i++)
{
DCLK = 0;
if (dat & 0x80)
{
DIN = 1;
}

else
{
DIN = 0;
}

DCLK = 1;
dat <<= 1;
}
}

int SPI_read()
{
int dat;
uchar i;

for (i = 0; i < 12; i++)
{
DCLK = 1;
dat <<= 1;
if (DOUT)
{
dat |= 0x01;
}

else
{
dat |= 0;
}

DCLK = 0;
}

return dat;
}

int readDat(uchar cmd)
{
int DAT;
CS = 0;
SPI_write(cmd);
DCLK = 0;
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
DAT = SPI_read();
CS = 1;
return DAT;
}