十一:mini2440上面使用SPI控制器實現OLED顯示

此次的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
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章