此次的SPI協議是基於mini2440開發板上SPI控制器,比較上一篇的區別就是使用了控制器,OLED的復位腳一定也要接在2440的復位腳上面,而不用定義一個復位腳給高低電平。
上一篇文章已經介紹了OLED的一些數據信息,和SPI協議的分析。
這裏的區別就是控制器的使用,2440上面的SPI控制器也給出了很詳細的使用說明。我們要做的事情如下所示:
1. 第一步修改GPIO的設置,上一章節是GPIO模式,因此引腳的使用是output,這裏我們使用控制器,就需要將引腳設置爲SPI模式,因此GPIO初始化的時候需要修改,代碼貼在後面。
2. 第二步就是進行SPI控制器的初始化。初始化的流程2440的手冊也寫得很清楚。
*1. Set Baud Rate Prescaler Register (SPPREn).
*2. Set SPCONn to configure properly the SPI module.
*3. Write data 0xFF to SPTDATn 10 times in order to initialize MMC or SD card.
*4. Set a GPIO pin, which acts as nSS, low to activate the MMC or SD card.
*5. Tx data ¡ Check the status of Transfer Ready flag (REDY=1), and then write data to SPTDATn.
*6. Rx data(1): SPCONn’s TAGD bit disable = normal mode
*7. ¡ write 0xFF to SPTDATn, then confirm REDY to set, and then read data from Read Buffer.
*8. Rx data(2): SPCONn’s TAGD bit enable = Tx Auto Garbage Data mode
*9. ¡ confirm REDY to set, and then read data from Read Buffer (then automatically start to transfer).
*10. Set a GPIO pin, which acts as nSS, high to deactivate the MMC or SD card
首先設置波特率,那麼查看OLED的手冊,可以看見OLED的最小時鐘是100ns,
就是最大10MHZ。在2440的波特率設置:
*計算方法:Baud rate = PCLK / 2 / (Prescaler value + 1)
*我們設置的PCLK=50M(1/2/4) Baud rate = 50M / 2 / (1.5 + 1)=10M
*Prescaler value取整數2,Baud rate = PCLK / 2 / (2 + 1)=8.3MHZ
然後就是進行SPI控制器的初始化。這裏說明一下,OLED接的是SPI0的引腳上面,所以要對SPCON0進行初始化。SPCON0的對應位的含義:
* [6:5] : 00, polling mode 查詢模式
* [4] : 1 = enable 使能時鐘
* [3] : 1 = master SPI做master
* [2] : 0 CPOL表示時鐘初始電平,0爲低電平,1爲高電平
* [1] : 0 = format A CPHA表示相位,即第一個還是第二個時鐘沿採樣數據
* [0] : 0 = normal mode
* 在SPI協議中,有兩個值來確定SPI的模式。 CPOL和CPHA兩個確定了哪種SPI模式;
初始化完成後進行,讀寫數據函數的修改,因爲現在讀寫數據是由控制器來控制,我們看說明,可以知道;
Tx data ¡ Check the status of Transfer Ready flag (REDY=1), and then write data to SPTDATn.
就是說要等待REDY信號爲1,將要寫的數據放到buffer(SPTDAT0)裏面就自己完成了數據的發送。
Rx data(1): SPCONn’s TAGD bit disable = normal mode(初始化的時候已經完成)
¡ write 0xFF to SPTDATn, then confirm REDY to set, and then read data from Read Buffer.
就是說首先寫0xff到SPTDAT0,然後等待REDY信號爲1,就可以獲取數據。
剩下的與GPIO操作完全一致,很簡單的完成了SPI控制器的使用。當然,SPI控制器簡化了很多時序上面的操作,所以瞭解並實際使用一下是很重要的。
接下來貼上所有代碼。
初始化代碼:
/*this is SPI controller
*use SPI OLED
*
**/
#define GPECON (*(volatile unsigned long *)0x56000040)//GPIOE控制寄存器
#define GPEDAT (*(volatile unsigned long *)0x56000044)//GPIOE數據寄存器
#define GPGCON (*(volatile unsigned long *)0x56000060)//GPIOG控制寄存器
#define GPGDAT (*(volatile unsigned long *)0x56000064)//GPIOG控制寄存器
#define GPFCON (*(volatile unsigned long *)0x56000050)//GPIOB控制寄存器
#define GPFDAT (*(volatile unsigned long *)0x56000054)//GPIOG控制寄存器
#define SPCON0 (*(volatile unsigned long *)0x59000000)//SPI0控制寄存器
#define SPCON1 (*(volatile unsigned long *)0x59000020)//SPI1控制寄存器
#define SPSTA0 (*(volatile unsigned long *)0x59000004)//SPI0狀態寄存器
#define SPSTA0 (*(volatile unsigned long *)0x59000024)//SPI0狀態寄存器
#define SPPIN0 (*(volatile unsigned long *)0x59000008)//SPI0片選寄存器
#define SPPIN1 (*(volatile unsigned long *)0x59000028)//SPI1片選寄存器
#define SPPRE0 (*(volatile unsigned long *)0x5900000C)//SPI0時鐘寄存器
#define SPPRE1 (*(volatile unsigned long *)0x5900002C)//SPI1時鐘寄存器
#define SPTDAT0 (*(volatile unsigned long *)0x59000010)//SPI0發數據寄存器
#define SPTDAT1 (*(volatile unsigned long *)0x59000030)//SPI1發數據寄存器
#define SPRDAT0 (*(volatile unsigned long *)0x59000014)//SPI0收數據寄存器
#define SPRDAT1 (*(volatile unsigned long *)0x59000034)//SPI0收數據寄存器
/*使用SPI控制器來實現SPI協議*/
static void SPI_GPIO_Init(void)
{
/*將GPIO引腳設置爲SPI功能引腳,不再是輸入輸出,這點要與GPIO進行區分
*還有就是前面做裸機的時候已經驗證了,SPIMOSI引腳第26位GPIO對應的寄存器值是GPE12,而不是手冊上面寫的GPG6*/
/*由原理圖可以知道
*SPIMISO /GPE11 OLED只輸入不輸出,此腳不用
*SPIMOSI /GPE12 OLED的輸入引腳
*SPICLK /GPE13 OLED的時鐘引腳
*OLED_CSn /GPG1 OLED的片選信號 ,CPU output
*DC /GPF3 OLED的數據/命令引腳,CPU output
*OLED_CSn,DC對應2440引出來的GPIO的第17,12腳
*2440的電源和地分別是2,3腳
*復位腳是第4腳,直接連接/
/* GPG1 OLED_CSn output */
GPGCON &= ~(3<<(1*2)); //清零
GPGCON |= (1<<(1*2)); //置位OLED_CSn
/*
* GPF3 OLED_DC output
* GPE11 SPIMISO input
* GPE12 SPIMOSI output
* GPE13 SPICLK output
*/
GPFCON &= ~(3<<(3*2)); //清零
GPFCON |= (1<<(3*2)); //置位OLED_DC
GPECON &= ~((3<<(12*2)) | (3<<(13*2))); //清零
GPECON |= ((2<<(12*2)) | (2<<(13*2))); //置位爲功能引腳
}
static void SPI_CONTROLLER_Init(void)
{
/*1. Set Baud Rate Prescaler Register (SPPREn).
*2. Set SPCONn to configure properly the SPI module.
*3. Write data 0xFF to SPTDATn 10 times in order to initialize MMC or SD card.
*4. Set a GPIO pin, which acts as nSS, low to activate the MMC or SD card.
*5. Tx data ¡ Check the status of Transfer Ready flag (REDY=1), and then write data to SPTDATn.
*6. Rx data(1): SPCONn's TAGD bit disable = normal mode
*7. ¡ write 0xFF to SPTDATn, then confirm REDY to set, and then read data from Read Buffer.
*8. Rx data(2): SPCONn's TAGD bit enable = Tx Auto Garbage Data mode
*9. ¡ confirm REDY to set, and then read data from Read Buffer (then automatically start to transfer).
*10. Set a GPIO pin, which acts as nSS, high to deactivate the MMC or SD card*/
/*1.設置波特率,OLED的最小時鐘是100ns,就是最大10MHZ
*計算方法:Baud rate = PCLK / 2 / (Prescaler value + 1)
*我們設置的PCLK=50M(1/2/4) Baud rate = 50M / 2 / (1.5 + 1)=10M
*Prescaler value取整數2,Baud rate = PCLK / 2 / (2 + 1)=8.3MHZ*/
SPPRE0 = 2;
SPPRE1 = 2;
/*2. 設置SPI控制寄存器
* [6:5] : 00, polling mode 查詢模式
* [4] : 1 = enable 使能時鐘
* [3] : 1 = master SPI做master
* [2] : 0 CPOL表示時鐘初始電平,0爲低電平,1爲高電平
* [1] : 0 = format A CPHA表示相位,即第一個還是第二個時鐘沿採樣數據
* 在SPI協議中,有兩個值來確定SPI的模式。 CPOL和CPHA兩個確定了哪種SPI模式;
* CPOL CPHA 模式 含義
* 0 0 0 初始電平爲低電平,在第一個時鐘沿採樣數據(上升沿)
* 0 1 1 初始電平爲低電平,在第二個時鐘沿採樣數據
* 1 0 2 初始電平爲高電平,在第一個時鐘沿採樣數據
* 1 1 3 初始電平爲高電平,在第二個時鐘沿採樣數據(上升沿)
* [0] : 0 = normal mode
*/
SPCON0 &= ~((3<<4)| (3<<3));
SPCON0 |= ((1<<4)| (1<<3)); //設置爲master模式,使能時鐘信號
SPCON1 &= ~((3<<4)| (3<<3));
SPCON1 |= ((1<<4)| (1<<3));
}
void SPIInit(void)
{
/*初始化2440的SPI引腳*/
SPI_GPIO_Init();
SPI_CONTROLLER_Init();
}
OLED的實現代碼(注意這裏只是讀寫函數變化了,其餘的CLK等函數不需要就刪除):
#include "oledfont.h"
#include"gpio_spi.h"
#include"oledfont.h"
#include"led_on.h"
#define GPECON (*(volatile unsigned long *)0x56000040)//GPIOE控制寄存器
#define GPEDAT (*(volatile unsigned long *)0x56000044)//GPIOE數據寄存器
#define GPGCON (*(volatile unsigned long *)0x56000060)//GPIOG控制寄存器
#define GPGDAT (*(volatile unsigned long *)0x56000064)//GPIOG控制寄存器
#define GPFCON (*(volatile unsigned long *)0x56000050)//GPIOB控制寄存器
#define GPFDAT (*(volatile unsigned long *)0x56000054)//GPIOG控制寄存器
#define SPCON0 (*(volatile unsigned long *)0x59000000)//SPI0控制寄存器
#define SPCON1 (*(volatile unsigned long *)0x59000020)//SPI1控制寄存器
#define SPSTA0 (*(volatile unsigned long *)0x59000004)//SPI0狀態寄存器
#define SPSTA0 (*(volatile unsigned long *)0x59000024)//SPI0狀態寄存器
#define SPPIN0 (*(volatile unsigned long *)0x59000008)//SPI0片選寄存器
#define SPPIN1 (*(volatile unsigned long *)0x59000028)//SPI1片選寄存器
#define SPPRE0 (*(volatile unsigned long *)0x5900000C)//SPI0時鐘寄存器
#define SPPRE1 (*(volatile unsigned long *)0x5900002C)//SPI1時鐘寄存器
#define SPTDAT0 (*(volatile unsigned long *)0x59000010)//SPI0發數據寄存器
#define SPTDAT1 (*(volatile unsigned long *)0x59000030)//SPI1發數據寄存器
#define SPRDAT0 (*(volatile unsigned long *)0x59000014)//SPI0收數據寄存器
#define SPRDAT1 (*(volatile unsigned long *)0x59000034)//SPI0收數據寄存器
/*將GPIO引腳設置爲SPI功能引腳,不再是輸入輸出,這點要與GPIO進行區分
*還有就是前面做裸機的時候已經驗證了,SPIMOSI引腳第26位GPIO對應的寄存器值是GPE12,而不是手冊上面寫的GPG6*/
/*由原理圖可以知道
*SPIMISO /GPE11 OLED只輸入不輸出,此腳不用
*SPIMOSI /GPE12 OLED的輸入引腳
*SPICLK /GPE13 OLED的時鐘引腳
*OLED_CSn /GPG1 OLED的片選信號 ,CPU output
*DC /GPF3 OLED的數據/命令引腳,CPU output
*OLED_CSn,DC對應2440引出來的GPIO的第17,12腳
*2440的電源和地分別是2,3腳
*復位腳是第4腳,直接連接*/
/**********************************************************/
/**********下面是SPI控制器需要用到的函數*******************/
/**********************************************************/
/*
*設置SPI片選信號
*1--未選中,0--選中;
*/
static void OLED_Set_CS(char val)
{
if (val)
GPGDAT |= (1<<1); /*高電平--未選中OLED*/
else
GPGDAT &= ~(1<<1); /*低電平--選中OLED*/
}
/*
*設置SPI發到OLED的數據/命令通道;
*1--數據通道;0--命令通道;
*/
static void OLED_Set_DC(char val)
{
if(val)
GPFDAT |= (1<<3); /*1--數據通道*/
else
GPFDAT &= ~(1<<3); /*0--命令通道*/
}
/*2440向OLED發送8bit的數據/命令*/
void SPISendByte(unsigned char dat)
{
/*Tx data ¡ Check the status of Transfer Ready flag (REDY=1), and then write data to SPTDATn.*/
while(!(SPSTA0 & 1))/*0 = Not ready*/
{
;
}
SPTDAT0 = dat;
}
/*2440讀取OLED的8bit的數據/命令*/
unsigned char SPIRecvByte(void)
{
/*Rx data(1): SPCONn's TAGD bit disable = normal mode
*¡ write 0xFF to SPTDATn, then confirm REDY to set, and then read data from Read Buffer.*/
SPTDAT0 = 0xff;
while (!(SPSTA0 & 1))
{
;
}
return SPRDAT0;
}
/************************************************/
/************************************************/
/************************************************/
/*這個函數體現了SPI時序的問題,注意仔細看。
*2440通過SPI引腳將數據/命令發到OLED;
*2440向OLED寫入一個字節;
*dat:要寫入的數據/命令;
*val:數據/命令標誌 0--表示命令;1--表示數據;
*/
void OLEDWriteCmd(unsigned char dat, unsigned char val)
{
if(val)
{
OLED_Set_DC(1); /*data*/
OLED_Set_CS(0); /*選中CS,由OLED的時序可以看到,CS拉低爲選中*/
/*選中之後,進行數據的發送*/
SPISendByte(dat);
OLED_Set_CS(1); /*取消選中CS*/
OLED_Set_DC(1); /*data*/
}
else
{
OLED_Set_DC(0); /*cmd*/
OLED_Set_CS(0); /*選中CS,由OLED的時序可以看到,CS拉低爲選中*/
/*選中之後,進行命令的發送*/
SPISendByte(dat);
OLED_Set_CS(1); /*取消選中CS*/
OLED_Set_DC(1); /*data*/
}
}
/****************************************/
/****************************************/
/****************************************/
/****OLED功能函數--顯示/清除*************/
/****************************************/
/****************************************/
/****************************************/
#if 0
static void OLEDSetPageAddrMode(void)
{
OLEDWriteCmd(0x20,0);
OLEDWriteCmd(0x02,0);
}
void OLEDSetPos(int x, int y)
{
OLEDWriteCmd(0xb0+y,0);
OLEDWriteCmd(((x&0xf0)>>4)|0x10,0);
OLEDWriteCmd((x&0x0f)|0x01,0);
}
//開啓OLED顯示
void OLEDDisplay_On(void)
{
OLEDWriteCmd(0X8D,0); //SET DCDC命令
OLEDWriteCmd(0X14,0); //DCDC ON
OLEDWriteCmd(0XAF,0); //DISPLAY ON
}
//關閉OLED顯示
void OLEDDisplay_Off(void)
{
OLEDWriteCmd(0X8D,0); //SET DCDC命令
OLEDWriteCmd(0X10,0); //DCDC OFF
OLEDWriteCmd(0XAE,0); //DISPLAY OFF
}
//清屏函數,清完屏,整個屏幕是黑色的!和沒點亮一樣!!!
void OLEDClear(void)
{
unsigned char i,n;
for(i=0;i<8;i++)
{
OLEDWriteCmd (0xb0+i,0); //設置頁地址(0~7)
OLEDWriteCmd (0x00,0); //設置顯示位置—列低地址
OLEDWriteCmd (0x10,0); //設置顯示位置—列高地址
for(n=0;n<128;n++)OLEDWriteCmd(0,1);
} //更新顯示
}
//在指定位置顯示一個字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白顯示;1,正常顯示
//size:選擇字體 16/12
void OLEDShowChar(int x,int y,char chr)
{
unsigned char c=0,i=0;
c=chr-'A';//得到偏移後的值
//printf("c = %d\n",c);
const unsigned char *dots = F8X16[c];
if(x>Max_Column-1){x=0;y=y+2;}
if(SIZE ==16)
{
OLEDSetPos(x,y);
for(i=0;i<8;i++)
OLEDWriteCmd(dots[i],1);
OLEDSetPos(x,y+1);
for(i=0;i<8;i++)
OLEDWriteCmd(dots[i+8],1);
}
}
/* page: 0-7
* col : 0-127
* 字符: 8x16象素
*/
void OLEDShowString(int x,int y,char *chr)
{
unsigned char j=0;
while (chr[j]!='\0')
{ OLEDShowChar(x,y,chr[j]);
x+=8;
if(x>127){x=0;y+=2;}
j++;
}
}
#endif
static void OLEDSetPageAddrMode(void)
{
OLEDWriteCmd(0x20,0);
OLEDWriteCmd(0x02,0);
}
static void OLEDSetPos(int page, int col)
{
OLEDWriteCmd(0xB0 + page,0); /* page address */
OLEDWriteCmd(col & 0xf,0); /* Lower Column Start Address */
OLEDWriteCmd(0x10 + (col >> 4),0); /* Lower Higher Start Address */
}
/*清楚屏幕的函數,就是往每個像素點寫0,點亮一個像素點就寫1*/
void OLEDClear(void)
{
int page, i;
for (page = 0; page < 8; page ++)
{
OLEDSetPos(page, 0);
for (i = 0; i < 128; i++)
OLEDWriteCmd(0,1);
}
}
/* page: 0-7
* col : 0-127
* 字符: 8x16象素
* 數據是佔8列16行的,在頁地址模式下,page爲8行,因此先寫前一頁的8個col,
* 再寫下一頁的8個col的數據
*/
void OLEDPutChar(int page, int col, char c)
{
int i = 0;
/* 得到字模 */
const unsigned char *dots = F8X16[c - ' '];
/* 發給OLED */
OLEDSetPos(page, col);
/* 發出8字節數據 */
for (i = 0; i < 8; i++)
OLEDWriteCmd(dots[i],1);
OLEDSetPos(page+1, col);
/* 發出8字節數據 */
for (i = 0; i < 8; i++)
OLEDWriteCmd(dots[i+8],1);
}
/* page: 0-7
* col : 0-127
* 字符: 8x16象素
*/
void OLEDPrint(int page, int col, char *str)
{
int i = 0;
while (str[i])
{
OLEDPutChar(page, col, str[i]);
col += 8;
if (col > 127)
{
col = 0;
page += 2;
}
i++;
}
}
/* 顯示漢字的函數
* 漢字佔16列16行的,在頁地址模式下,page爲8行,因此先寫前一頁的16個col,
* 再寫下一頁的16個col的數據
*/
void OLED_ShowCHinese(int page,int col,int no)
{
int t,adder=0;
OLEDSetPos(page,col);
for(t=0;t<16;t++)
{
OLEDWriteCmd(Chinese[2*no][t],1);
adder+=1;
}
OLEDSetPos(page+1,col);
for(t=0;t<16;t++)
{
OLEDWriteCmd(Chinese[2*no+1][t],1);
adder+=1;
}
}
/**********************/
void OLEDInit(void)
{
delay(1000);
#if 1
/* 向OLED發命令以初始化 */
OLEDWriteCmd(0xAE,0); /*display off*/
OLEDWriteCmd(0x00,0); /*set lower column address*/
OLEDWriteCmd(0x10,0); /*set higher column address*/
OLEDWriteCmd(0x40,0); /*set display start line*/
OLEDWriteCmd(0xB0,0); /*set page address*/
OLEDWriteCmd(0x81,0); /*contract control*/
OLEDWriteCmd(0xcf,0); /*128*/
OLEDWriteCmd(0xA1,0); /*set segment remap*/
OLEDWriteCmd(0xA6,0); /*normal / reverse*/
OLEDWriteCmd(0xA8,0); /*multiplex ratio*/
OLEDWriteCmd(0x3F,0); /*duty = 1/64*/
OLEDWriteCmd(0xC8,0); /*Com scan direction*/
OLEDWriteCmd(0xD3,0); /*set display offset*/
OLEDWriteCmd(0x00,0);
OLEDWriteCmd(0xD5,0); /*set osc division*/
OLEDWriteCmd(0x80,0);
OLEDWriteCmd(0xD9,0); /*set pre-charge period*/
OLEDWriteCmd(0xf1,0);
OLEDWriteCmd(0xDA,0); /*set COM pins*/
OLEDWriteCmd(0x12,0);
OLEDWriteCmd(0xdb,0); /*set vcomh*/
OLEDWriteCmd(0x30,0);
OLEDWriteCmd(0x8d,0); /*set charge pump enable*/
OLEDWriteCmd(0x14,0);
OLEDSetPageAddrMode(); //設置頁地址模式
OLEDClear();
OLEDSetPos(0,0);
OLEDWriteCmd(0xAF,0); /*display ON*/
#endif
}