# 代码
有一些朋友可能需要代码,而不是看我砍大山,先放上完整版的代码。注意 代码是 SPI在方式0,数据在上升沿采样,下降沿发送。
提示:以下是本篇文章正文内容,下面案例仅供参考
#include "reg51.h"
sbit _NSS = P2^0;
sbit _SCK = P2^1;
sbit _MISO = P2^2;
sbit _MOSI = P2^3;
#define NSS(STA) if(STA) _NSS = 1; else _NSS = 0
#define SCK(STA) if(STA) _SCK = 1; else _SCK = 0
#define MOSI(STA) if(STA) _MOSI = 1; else _MOSI = 0
#define READ_MISO _MOSI
unsigned char i;
//发送数据,默认CPHA = CPOL = 0 大端模式,高位先行,一次发送八位 ,返回读取到的数据
unsigned char SP_SendData(unsigned char dat)
{
unsigned char reveDat = 0;
NSS(0);
SCK(0);
for(i = 0; i < 8; i++)
{
if(dat&0x80) MOSI(1);
else MOSI(0);
SCK(1); //SPI模式0,下降沿数据发送
SCK(0);
dat <<= 1;
}
for(i = 0; i < 8; i++)
{
SCK(0);
SCK(1);
reveDat |= READ_MISO ;
reveDat <<= 1;
}
NSS(1);
return reveDat;
}
//数据接收只要发送数据0x00就可以接收到数据了
首先
1、我们的51单片机并没有内置SPI内部外设,所以我们的一切数据的传输需要通过IO口模拟来实现
2、在代码还没开始之前的时候我们需要一点点的SPI通信的基础,知道SPI的基本工作原理和它的时序,只要这两点就足够了(也没其他的了)。
3、SPI的基本工作原理,SPI最初由摩托罗拉公司研发并且流传开来的,关于SPI的历史基本在网上都能搜索得到,这里不再赘述,首先从它按照OSI分层讲,它处于第二层-数据链路层,规定了数据传输的逻辑规则,在物理层它有四个引脚,分别为
引脚 | 功能 |
---|---|
NSS | 从设备选择 |
MISO | 主设备输入,从设备输出 |
MOSI | 主设备输出,从设备输入 |
SCK | 同步时钟 |
注意的是:我们的SPI传输最少可以只用两线便可以传输数据,如果只有一个从设备可省略NSS(接地),如果对方是显示设备,我们还可以省略掉MISO,当然省略NSS前提是主设备和从设备共地才行。
SPI有主设备模式和从设模式,当然了我们的MCU一般选择为主设备。而我们的其他通信外设默认为从设备,这在我们的开发基本是这样,MISO意思只有一个,那就是我们的主设备输入我们从设备输出,方向是相对的MOSI的意思也是一样,然后是我们的NSS,在我们的协议中非常常见,用来表示我们当前表示的是哪一个设备(反过来讲我们的从设备在当只有NSS为低电平的时候才去接收数据)在我们的多设备通信的时候有用的 ,然后是我们的数据传输的时钟。要通过数据的同步。设备在工作中必须要有一个是主设备。
接下来我们来聊聊SPI的基本通信原理,通信的时序,于OSI第二层-数据链路层。在讲这些之前先解决一个问题,那就是我我们的数据上升沿的问题,按照我们数据发送和接收上升沿和第一二个脉冲,我们的SPI模式可以有四种模式。
CPOL:用来表示SCK的有效状态,为1表示SCK高电平为空闲状态,有效状态就是低电平。
CPHA:为0表示在第一个边沿采样,为1则是数据在第二个边沿采样。
方式 | 状态 |
---|---|
方式0 | CPOL = 0 CPHA = 0 |
方式1 | CPOL = 0 CPHA = 1 |
方式2 | CPOL = 1 CPHA = 0 |
方式3 | CPOL = 1 CPHA = 1 |
例如:SPI工 作在方式0:那么SCK的空闲状态是低电平,数据采样在第一个边沿,也就是上升沿,而数据的发送则是在下降沿。
注意理解,这里上升沿和下降沿只是相对的,会随着SCK的空闲状态不同而不同,说白了也就是方式不同。
还有一点就是我们的SCK只能由主设备来产生,还有NSS,所以都应该配置为输出
SPI内部原理图:
SSPSR 是SPI的内部移位寄存器,他的主要工作是在SCK发生变化的时候,往SSPBUF里面移入或者移出数据,每次移动数据的大小由Bus-Width和Channel-Width决定。
该图是我们SPI收发数据的时序图,在我们后面也会提到,通过我们代码,并且在Proteus软件仿真中实现。
介绍完了SPI的理论,我们可以就可用代码来实现了,新建一个工程。
4、新建工程,工程目录结构下
一、程序编写
工程目录结构如下
所有的硬件声明,如下:
sbit _NSS = P2^0;
sbit _SCK = P2^1;
sbit _MISO = P2^2;
sbit _MOSI = P2^3;
sbit kl = P1^7;
kl是我们做测试,为了方便捕捉SPI时序定义的一个按键控制,按键按下发送交换一次数据。CPHA和CPOL,通信的时候主从机的CPHA和CPOL必须一致。否则数据会发生错误。
#define NSS(STA) if(STA) _NSS = 1; else _NSS = 0
#define SCK(STA) if(STA) _SCK = 1; else _SCK = 0
#define MOSI(STA) if(STA) _MOSI = 1; else _MOSI = 0
#define READ_MISO _MOSI
为什么要写成这样,有原因的,方便代码移植,给读者看32的IO控制一般写法就明白了
#define MOUDLE_RST(uo) if(uo) __PORT_A -> BSRR = _nRST_GPIO_Pin; \
else __PORT_A -> BRR = _nRST_GPIO_Pin;
#define MOUDLE_CS(uo) if(uo) __PORT_A -> BSRR = _CS_GPIO_Pin; \
else __PORT_A -> BRR = _CS_GPIO_Pin;
下面我们继续来看代码实现,在51中模拟SPI:
收发代码如下(示例):
//发送数据,默认CPHA = CPOL = 0 大端模式,高位先行,一次发送八位 ,返回读取到的数据
unsigned char SP_SendData(unsigned char dat)
{
unsigned char reveDat = 0;
NSS(0);
SCK(0);
for(i = 0; i < 8; i++)
{
if(dat&0x80) MOSI(1);
else MOSI(0);
SCK(1);
SCK(0);
dat <<= 1;
}
for(i = 0; i < 8; i++)
{
SCK(0);
SCK(1);
reveDat |= READ_MISO ;
reveDat <<= 1;
}
NSS(1);
return reveDat;
}
unsigned char SP_ReveData()
{
return SP_SendData(0x00);
}
首先初始化SPI的工作模式,其实就是赋值CPHA和CPOL,CPOL表示我们始终在空闲的时候的状态,本来是想好的,但是后面没用上,具体是不能传递变量到宏定义,有解决办法,也很简单,后期实现。还有一个注意的地方是我们的发送模式高位在前,用 &0x80来得到最高位,在发送完之后左移一位,下次发送次高位。还有一点十分重要的是,我们51没有硬件SPI支持的Buff 所以可以看到我发送了一位马上有读取一位,如果从机也是模拟SPI的话,我这样做没有错,但是一般支持SPI的会把待发送数据预备到SPI的缓冲区中,等待我们发送完八位数据之后的读取数据时钟,其实我在写的时候也考虑到这些,后来想通了,基本支持SPI的都是硬件SPI所以不需要担心,我们只需要关注从机SPI模式就行了。
二、仿真结果
实验仿真结果:
发送0xA5,收到0xFF,第二个是我读到的数据,因为是一个模拟仿真器,他没有发送过数据,所以读到都是高电平为0xFF,时序不是很标准,但也能说明问题,好的 我们继续。
void main()
{
my_TFTInit();
while(1)
{
Lcd_Clear(BLUE);
Lcd_Clear(YELLOW);
// showImage();
}
}
初始化SPI、初始化TFT,然后在while循环中不断的黄蓝刷屏,现象如下:
现象:证明我们写的SPI通信代码是没有问题的。但是我在实际使用的过程中,发现了一个问题,51的刷新的速度很慢了,肉眼可见的速度在不停的替换每一个像素的颜颜色,大概刷新一屏的时间是半分钟到一分钟左右。
TFT屏是240*320的屏幕。所以我们万不得已不要去刷新整个屏幕。
好的,本篇博文就到这里,如果你有什么问题、疑问,欢迎在下方评论区留言讨论哦。