SPI使用介绍

SPI协议介绍:
硬件连接:
SPI Flash和SPI OLED。
两种方式:
用GPIO模拟SPI
用S3C2440的SPI控制器
我们先介绍下SPI协议,硬件框架如下:
SPI使用介绍_第1张图片
SCK:提供时钟
DO:作为数据输出
DI:作为数据输入
CS0/CS1:作为片选
同一时刻只能有一个SPI设备处于工作状态。
假设现在2440传输一个0x56(二进制就是0b0101 0110)数据给SPI Flash,时序如下:
SPI使用介绍_第2张图片

在SPI协议中,有两个值来确定SPI的模式。
CPOL:表示SPICLK的初始电平,0为低电平,1为高电平
CPHA:表示相位,即第一个还是第二个时钟沿采样数据,0为第一个时钟沿,1为第二个时钟沿。
CPOL CPHA 模式 含义
0 0 0 初始电平为低电平,在第一个时钟沿采样数据
0 1 1 初始电平为低电平,在第二个时钟沿采样数据
1 0 2 初始电平为高电平,在第一个时钟沿采样数据
1 1 3 初始电平为高电平,在第二个时钟沿采样数据
我们常用的是模式0和模式3,因为它们都是在上升沿采样数据,不用去在乎时钟的初始电平是什么,只要在上升沿采集数据就行。
极性选什么?格式选什么?通常去参考外接的模块的芯片手册。比如对于OLED,查看它的芯片手册时序部分:
SPI使用介绍_第3张图片
SCLK的初始电平我们并不需要关心,只要保证在上升沿采样数据就行。
使用GPIO实现SPI协议操作OLED
现在开始写代码,使用GPIO实现SPI协议操作。
我们现在想要操作OLED,通过三条线(SCK、DO、CS)与OLED相连,这里没有DI是因为2440只会向OLED传数据而不用接收数据。
我们要用GPIO来实现SOC向OLED写数据,这一层用gpio_spi.c来实现,负责发送数据。
对于OLED,有专门的指令和数据格式,要传输的数据内容,在oled.c这一层来实现,负责组织数据。
因此,我们需要实现以上两个文件。
SPI使用介绍_第4张图片
需要实现的函数:先SPI初始化SPIInt(),再初始化OLEDOLEDInit(),最后再显示OLEDPrint()。
新建一个gpio_spi.c文件,实现SPI初始化SPIInt()
void SPIInit(void){
/* 初始化引脚 /
SPI_GPIO_Init();
}
具体实现SPI_GPIO_Init()。这里使用GPIO实现SPI协议:
GPF1作为OLED片选引脚,设置为输出;
GPG2作为FLASH片选引脚,设置为输出;
GPG4作为OLED的数据(Data)/命令(Command)选择引脚,设置为输出;
GPG5作为SPI的MISO,设置为输入;
GPG6作为SPI的MOSI,设置为输出;
GPG7作为SPI的时钟CLK,设置为输出;
/
用GPIO模拟SPI /static void SPI_GPIO_Init(void){
/
GPF1 OLED_CSn output /
GPFCON &= ~(3<<(1
2));
GPFCON |= (1<<(12));
GPFDAT |= (1<<1);
/
GPG2 FLASH_CSn output
* GPG4 OLED_DC output
* GPG5 SPIMISO input
* GPG6 SPIMOSI output
* GPG7 SPICLK output
/
GPGCON &= ~((3<<(2
2)) | (3<<(42)) | (3<<(52)) | (3<<(62)) | (3<<(72)));
GPGCON |= ((1<<(22)) | (1<<(42)) | (1<<(62)) | (1<<(72)));
GPGDAT |= (1<<2);
}
再新建一个oled.c文件,以实现初始化OLEDOLEDInit(),对于OLED,除了SPI的片选、时钟、数据引脚,还有一个数据/命令切换引脚。
这里的D/C即数据(Data)/命令(Command)选择引脚,它为高电平时,OLED即认为收到的是数据;它为低电平时,OLED即认为收到的是命令。
对于OLED,命令由开启/关闭显示、背光亮度等,具体有什么命令,可以查阅OLED的主控芯片手册
因此,在编写OLEDWriteCmd()时,需要先设置为命令模式:
static void OLEDWriteCmd(unsigned char cmd)
{
OLED_Set_DC(0); /* command /
OLED_Set_CS(0); /
select OLED */

SPISendByte(cmd);

OLED_Set_CS(1); /* de-select OLED /
OLED_Set_DC(1); /
*/
}
即:先设置为命令模式,再片选OLED,再传输命令,再恢复成原来的模式和取消片选。
片选函数和模式切换函数都比较简单,设置为对应的高低电平即可:
static void OLED_Set_DC(char val){
if (val)
GPGDAT |= (1<<4);
else
GPGDAT &= ~(1<<4);
}
static void OLED_Set_CS(char val){
if (val)
GPFDAT |= (1<<1);
else
GPFDAT &= ~(1<<1);
}

还剩下SPISendByte()函数,它属于SPI协议,放在gpio_spi.c里面:
void SPISendByte(unsigned char val){
int i;
for (i = 0; i < 8; i++)
{
SPI_Set_CLK(0);
SPI_Set_DO(val & 0x80);
SPI_Set_CLK(1);
val <<= 1;
}

}
发送数据要满足SPI的时序要求,参考前面:
先设置CLK为低,然后数据引脚输出数据的最高位,然后CLK为高,在CLK这个上升沿中,OLED就读取了一位数据。接着左移一位,将原来的第7位移动到了第8位,重复8次,传输完成。
再完成SPI_Set_CLK()和SPI_Set_DO():
static void SPI_Set_CLK(char val){
if (val)
GPGDAT |= (1<<7);
else
GPGDAT &= ~(1<<7);
}
static void SPI_Set_DO(char val){
if (val)
GPGDAT |= (1<<6);
else
GPGDAT &= ~(1<<6);
}

至此,SPI初始化和OLED初始化就基本完成了,接下来就是OLED显示部分。

使用SPI控制器:
前面我们都是通过GPIO管脚来实现的SPI通信,这节我们使用2440里面的GPIO控制器来实现SPI通信。
前面使用GPIO发送数据时,是手工的控制时钟线、数据线,我们使用SPI控制器的话,只需要
把数据写入寄存器,它就可以帮我自动那些时钟线和数据线,我们继续在上一节的基础上修改,添加一个文件s3c2440_spi.c和s3c2440_spi.h,同时修改Makefile,替换gpio_spi.c为s3c2440_spi.o。
从初始化函数开始,需要管脚初始化和SPI控制器初始化:
void SPIInit(void)
{
/* 初始化引脚 */
SPI_GPIO_Init();

SPIControllerInit();
}
管脚初始化即需要把SPI相关的CLK、MOSI、MISO配置为对应的功能引脚:
static void SPI_GPIO_Init(void){
/* GPF1 OLED_CSn output /
GPFCON &= ~(3<<(1
2));
GPFCON |= (1<<(12));
GPFDAT |= (1<<1);
/
GPG2 FLASH_CSn output
* GPG4 OLED_DC output
* GPG5 SPIMISO
* GPG6 SPIMOSI
* GPG7 SPICLK
/
GPGCON &= ~((3<<(2
2)) | (3<<(42)) | (3<<(52)) | (3<<(62)) | (3<<(72)));
GPGCON |= ((1<<(22)) | (1<<(42)) | (3<<(52)) | (3<<(62)) | (3<<(7*2)));
GPGDAT |= (1<<2);
}

然后是SPI控制器的初始化,控制器的初始化可以参考芯片手册介绍的编程步骤:
SPI使用介绍_第5张图片
首先是设置波特率,要根据外设所能接受的范围来设置,比如查阅OLED的芯片手册得知其时钟最小值为100ns,即最小为10MHz;Flash时钟支持最大104MHz,为了代码简单,就直接取10MHz,根据等式推出寄存器值:
Baud rate = PCLK / 2 / (Prescaler value + 1)
10 = 50 / 2 / (Prescaler value + 1)
Prescaler value = 1.5 = 2
实际的波特率为:50/2/3=8.3MHz
根据参考流程,接下来设置SPI控制寄存器:

[6:5]设置为查询模式: 00 polling mode
[4]设置时钟使能: 1 = enable
[3]设置为主机模式: 1 = master
[2]设置无数据时时钟为低电平: 0
[1]设置工作模式为模式A: 0 = format A
[0]设置发送数据时无需读取数据: 0 = normal mode
static void SPIControllerInit(void){
/* OLED : 100ns, 10MHz
* FLASH : 104MHz
* 取10MHz
* 10 = 50 / 2 / (Prescaler value + 1)
* Prescaler value = 1.5 = 2
* Baud rate = 50/2/3=8.3MHz
*/
SPPRE0 = 2;
SPPRE1 = 2;

/* [6:5] : 00, polling mode
* [4]   : 1 = enable 
* [3]   : 1 = master
* [2]   : 0
* [1]   : 0 = format A
* [0]   : 0 = normal mode
*/
SPCON0 = (1<<4) | (1<<3);
SPCON1 = (1<<4) | (1<<3);

}
发送数据时,先检查状态寄存器,判断发送/接收数据是否准备好了,准备好后就把数据放在寄存器SPTDAT1里,SPI控制器就自己控制时序把数据自动发送出去了。
void SPISendByte(unsigned char val){
while (!(SPSTA1 & 1));
SPTDAT1 = val;
}
接收数据时,先写0xFF到寄存器SPTDAT1,再检查状态寄存器,判断发送/接收数据是否准备好了,准备好后就读取寄存器SPTDAT1,读取出来的就是接收到的数据。
unsigned char SPIRecvByte(void){
SPTDAT1 = 0xff;
while (!(SPSTA1 & 1));
return SPRDAT1;
}

你可能感兴趣的:(裸机开发)