SPI簡介
SPI是串行外設接口(Serial Peripheral Interface)的縮寫。SPI,是一種高速的,全雙工,同步的通信總線,並且在芯片的管腳上只佔用四根線,節約了芯片的管腳,同時爲PCB的佈局上節省空間,提供方便,正是出於這種簡單易用的特性,如今越來越多的芯片集成了這種通信協議,比如AT91RM9200。
簡單的說,就是SPI是一種告訴的協議,相當於IIC,只是IIC是兩根線(時鐘線SCL,數據地址線SDA)而SPI是四根線(SCK時鐘信號,DO輸出線,DI輸入線,CSn片選信號),同樣可以掛在多個SPI高速設備。
數據傳輸
數據傳輸由CPOL,CPHA兩個控制器決定,CPOL決定電平啓示狀態是低電平還是高電平,CPHA決定在第一個沿採樣還是第二個沿採樣
通過SPI協議實現對OLED屏幕的顯示
OLED顯示原理
屏幕分辨率是12864,如同LCD一樣,我們需要一個顯存來存放數據,這個顯存在內存中開闢,一個字節八位,分別表示該列元素的亮(1)或者滅(0)
按168的大小在屏幕上顯示,一行爲一個page,在光標移動到底8列時,光標歸爲0,page+1,實現第二個page的寫操作
通過GPIO模擬SPI的數據收發
原理圖中使用到GPG3567和GPF1,所以需要設置S3C2440相應的管腳的輸入輸出模式,除開GPG5爲輸入模式,其餘管腳都是輸出模式。
static void SPI_GPIO_INIT(void)
{
/*GPF1 OLED_CSn OUTPUT*/ /*主機是輸出模式*/
GPFCON &= ~(3<<2);
GPFCON |= (1<<2);
GPFDAT |= (1<<1); /*因爲接了OLED 和 FLASH 如果同時爲0 會產生衝突*/
/*GPG2 FLASH_CSn OUTPUT*/
/*GPG4 OLED_DC OUTPUT*/
/*GPG5 SPIMISO INPUT*/ /*MI:MASTER INPUT*/
/*GPG6 SPIMOSI OUTPUT*/
/*GPG7 SPICLK OUTPUT*/
GPGCON &= ~((3<<4)|(3<<8)|(3<<12)|(3<<14)|(3<<10));
GPGCON |= ((1<<4)|(1<<8)|(1<<12)|(1<<14));
GPGDAT |=(1<<2); /*因爲接了OLED 和 FLASH 如果同時爲0 會產生衝突*/
/*CSn低電平有效*/
/*都不片選,使其在使用時裁片選*/
}
由於芯片上接入了FLASH和oled的片選信號,所以在GPIO的初始化時,需要將兩個片選信號都拉高,避免默認狀態衝突。
OLED初始化
void oledinit(void)
{
/*向OLED發命令初始化*/
/*SPEC UG手冊提供的初始化命令*/
OLEDWriteCmd(0xAE); /*display off*/
OLEDWriteCmd(0x00); /*set lower column address*/
OLEDWriteCmd(0x10); /*set higher column address*/
OLEDWriteCmd(0x40); /*set display start line*/
OLEDWriteCmd(0xB0); /*set page address*/
OLEDWriteCmd(0x81); /*contract control*/
OLEDWriteCmd(0x66); /*128*/
OLEDWriteCmd(0xA1); /*set segment remap*/
OLEDWriteCmd(0xA6); /*normal / reverse*/
OLEDWriteCmd(0xA8); /*multiplex ratio*/
OLEDWriteCmd(0x3F); /*duty = 1/64*/
OLEDWriteCmd(0xC8); /*Com scan direction*/
OLEDWriteCmd(0xD3); /*set display offset*/
OLEDWriteCmd(0x00);
OLEDWriteCmd(0xD5); /*set osc division*/
OLEDWriteCmd(0x80);
OLEDWriteCmd(0xD9); /*set pre-charge period*/
OLEDWriteCmd(0x1f);
OLEDWriteCmd(0xDA); /*set COM pins*/
OLEDWriteCmd(0x12);
OLEDWriteCmd(0xdb); /*set vcomh*/
OLEDWriteCmd(0x30);
OLEDWriteCmd(0x8d); /*set charge pump enable*/
OLEDWriteCmd(0x14);
OLEDPAGEADDRMODE();
OLEDCLEAR();
OLEDWriteCmd(0xAF); /*display ON*/
}
在OLED初始中實現對地址採用Page address mode
static void OLEDPAGEADDRMODE(void)
{
OLEDWriteCmd(0x20);
OLEDWriteCmd(0x02);
}
清屏幕
static void OLEDCLEAR(void)
{
int page,col,i;
for(page = 0; page <8;page++)
{
OLEDSETPOS(page,0);
for(i=0;i<128;i++)
{
OLEDWriteDAT(0);
}
}
}
地址/數據模式
OLED_DC:高電平表示數據,低電平表示數據。
/*命令*/
static void OLEDWriteCmd(unsigned int cmd)
{
OLED_SET_DC(0); /*COMMAND*/
OLED_SET_CS(0); /*選中OLED*/
SPISENDBYTE(cmd);
OLED_SET_CS(1); /*取消選中OLED*/
OLED_SET_DC(1); /*COMMAND*/
}
/*地址*/
static void OLEDWriteDAT(unsigned int dat)
{
OLED_SET_DC(1); /*data*/
OLED_SET_CS(0); /*選中OLED*/
SPISENDBYTE(dat);
OLED_SET_CS(1); /*取消選中OLED*/
OLED_SET_DC(1); /*data*/
}
根據傳入0/1設置相應的GPIO口輸出高低電平
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);
}
}
數據傳輸
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;
}
}
時鐘設置
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);
}
}
程序到這裏已經實現了CPU的數據傳出,接下來需要將數據收到,以及打印到OLED的相應位置
void oledprint(int page,int col,char *str)
{
int i = 0;
while(str[i])
{
OLEDPUTCHAR(page,col,str[i]);
col += 8;
if(col > 127)
{
page += 2;
col = 0;
}
i++;
}
}
一個字節佔8列,16行(16*8)
void OLEDPUTCHAR(int page,int col,char c)
{
int i=0;
/*字摸*/
const unsigned char *dots = oled_asc2_8x16[c - ' '];
/*發給OLED*/
OLEDSETPOS(page,col);
/*發出8字節數據*/
for(i=0;i<8;i++)
{
OLEDWriteDAT(dots[i]);
}
OLEDSETPOS(page+1,col);
/*發出8字節數據*/
for(i=0;i<8;i++)
{
OLEDWriteDAT(dots[i+8]); /*原因是字符數組二維數組是16個爲一組,前8個是上一排的,後8個是下一排*/
}
}
static void OLEDSETPOS(int page, int col)
{
OLEDWriteCmd(0xb0 + page); /*page address*/
OLEDWriteCmd(col & 0xf); /*Set Lower Column Start Address for Page Addressing Mode*/
OLEDWriteCmd(0x10 +(col>>4)); /*Set Higher Column Start Address for Page Addressing Mode*/
}
通過SPI協議實現對FLASH的操作
讀廠家ID、設備ID
首先發送0x90,然後發出3個字節的0(是一個24位的數據,所以需要分割爲3個字節來發送),然後GPG5就有數據讀入。
void SPIflashreadID(int *pMID,int *pDID)
{
SPI_FLASH_set_cs(0); /*選中SPIflash*/
SPISENDBYTE(0X90);
SPIFlashSendAddr(0); /*當發完這些指令和地址是,GPGDAT5上就有數據傳入*/
*pMID = SPIRecvByte();
*pDID = SPIRecvByte();
SPI_FLASH_set_cs(1); /*取消選中SPIflash*/
}
static void SPIFlashSendAddr(unsigned int addr)
{
SPISENDBYTE(addr>>16); /*地址由24位組成,先發高高8位*/
SPISENDBYTE((addr>>8)&0xff); /*地址由24位組成,再發高8位*/
SPISENDBYTE(addr&0xff); /*地址由24位組成,最後發低8位*/
}
static void SPI_FLASH_set_cs(char val)
{
if(val)
{
GPGDAT |= (1<<2); /*不選中*/
}
else
{
GPGDAT &= ~(1<<2); /*選中*/
}
}
/*主機傳入*/
static char SPI_SET_DI(void)
{
/*需要判定GPGDAT第5位是什麼數據*/
if(GPGDAT &(1<<5))
{
return 1;
}
else
{
return 0;
}
}
unsigned char SPIRecvByte(void)
{
int i;
unsigned char val = 0; /*用於存放本字節的東西*/
for(i=0;i<8;i++)
{
val <<= 1;
SPI_SET_CLK(0);
if(SPI_SET_DI())
{
val |= 1; /*數據爲1就或1,否則就是0,前面左移已經產生了,所以不用賦值*/
}
SPI_SET_CLK(1);
}
return val;
}
對FLASH進行讀寫
對FLASH的寫操作,需先進進行寫使能,取出狀態寄存器的保護,然後擦除,然後寫入
寫使能:
寫保護:
static void SPIFlashWriteEnable(int enable)
{
if (enable)
{
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x06);
SPI_FLASH_set_cs(1);
}
else
{
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x04);
SPI_FLASH_set_cs(1);
}
}
讀狀態寄存器
發送0x05命令,接下來就可以讀數據了
static unsigned char SPIFlashReadStatusReg1(void)
{
unsigned char val;
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x05);
val = SPIRecvByte();
SPI_FLASH_set_cs(1);
return val;
}
static unsigned char SPIFlashReadStatusReg2(void)
{
unsigned char val;
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x35);
val = SPIRecvByte();
SPI_FLASH_set_cs(1);
return val;
}
寫狀態寄存器
static void SPIFlashWriteStatusReg(unsigned char reg1, unsigned char reg2)
{
SPIFlashWriteEnable(1);
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x01);
SPISENDBYTE(reg1);
SPISENDBYTE(reg2);
SPI_FLASH_set_cs(1);
SPIFlashWaitWhenBusy();
}
根據狀態寄存器1的第0位可以判定是寫數據已經完成。1表示正在進行,0表示已經完成
static void SPIFlashWaitWhenBusy(void)
{
while (SPIFlashReadStatusReg1() & 1);
}
去除對狀態寄存器的保護
將SRP0,SRP1置0就能去除對狀態寄存器的保護
static void SPIFlashClearProtectForStatusReg(void)
{
unsigned char reg1, reg2;
reg1 = SPIFlashReadStatusReg1();
reg2 = SPIFlashReadStatusReg2();
reg1 &= ~(1<<7);
reg2 &= ~(1<<0);
SPIFlashWriteStatusReg(reg1, reg2);
}
去除對數據域的保護
將BP2,BP1,BP0三位清0就可以去除保護
static void SPIFlashClearProtectForData(void)
{
/* cmp=0,bp2,1,0=0b000 */
unsigned char reg1, reg2;
reg1 = SPIFlashReadStatusReg1();
reg2 = SPIFlashReadStatusReg2();
reg1 &= ~(7<<2);
reg2 &= ~(1<<6);
SPIFlashWriteStatusReg(reg1, reg2);
}
擦除
擦除4k大小的區域
void SPIFlashEraseSector(unsigned int addr)
{
SPIFlashWriteEnable(1);
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x20);
SPIFlashSendAddr(addr);
SPI_FLASH_set_cs(1);
SPIFlashWaitWhenBusy();
}
燒寫
/* program */
void SPIFlashProgram(unsigned int addr, unsigned char *buf, int len)
{
int i;
SPIFlashWriteEnable(1);
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x02);
SPIFlashSendAddr(addr);
for (i = 0; i < len; i++)
SPISENDBYTE(buf[i]);
SPI_FLASH_set_cs(1);
SPIFlashWaitWhenBusy();
}
讀數據
void SPIFlashRead(unsigned int addr, unsigned char *buf, int len)
{
int i;
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x03);
SPIFlashSendAddr(addr);
for (i = 0; i < len; i++)
buf[i] = SPIRecvByte();
SPI_FLASH_set_cs(1);
}
使用SPI控制器取代GPIO模擬
同樣是使用相同的管家,只是不再手動操作相應的GPIO管腳,實現0/1的輸出,而是操作SPI控制器,讓控制器自動進行相應的數據收發。
SPI控制器初始化
將GPGCON中的456位設置爲SPI模式,其餘不變
static void SPI_GPIO_Init(void)
{
/*GPF1 OLED_CSn OUTPUT*/ /*主機是輸出模式*/
GPFCON &= ~(3<<(1*2));
GPFCON |= (1<<(1*2));
GPFDAT |= (1<<1); /*因爲接了OLED 和 FLASH 如果同時爲0 會產生衝突*/
/*GPG2 FLASH_CSn OUTPUT*/
/*GPG4 OLED_DC OUTPUT*/
/*GPG5 SPIMISO INPUT*/ /*MI:MASTER INPUT*/
/*GPG6 SPIMOSI OUTPUT*/ /*這裏所有的輸入輸出都是站在主機CPU的角度談的*/
/*GPG7 SPICLK OUTPUT*/
GPGCON &= ~((3<<(2*2)) | (3<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));
GPGCON |= ((1<<(2*2)) | (1<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));
GPGDAT |= (1<<2); /*因爲接了OLED 和 FLASH 如果同時爲0 會產生衝突*/
/*CSn低電平有效*/
/*都不片選,使其在使用時裁片選*/
}
SPI控制器初始化,設置相應的時鐘,因爲是自動傳輸,所以時鐘(也就是波特率)需要預先設置。
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);
}
收發數據只需要將數據寫給SPSTA1寄存器就可以
由於使用的是SPIMISO1,所以使用的是第二控制器
void SPISENDBYTE(unsigned char val)
{
while (!(SPSTA1 & 1));
SPTDAT1 = val;
}
//判定SPSTA1第一位十位準備就緒,然後寫數據
unsigned char SPIRecvByte(void)
{
SPTDAT1 = 0xff;
while (!(SPSTA1 & 1));
return SPRDAT1;
}
首先將0xff寫入SPTDAT1 ,然後判定SPSTA1第一位十位準備就緒,然後讀數據
特別注意:這裏的數據寄存器(SPRDATn)是單字符寄存器,所以在地址聲明時需要定義爲單字節。