此次的SPI协议是基于mini2440开发板上的GPIO,并未使用SPI控制器,所以跟单片机一致,但是这里有个问题就是OLED的复位脚一定要接在2440的复位脚上面,而不用定义一个复位脚给高低电平。
1. 介绍一下SPI协议的OLED。
市面上的开发板很少接有SPI设备,但是SPI协议在工作中经常用到。我们开发了SPI模块,上面有SPI Flash和SPI OLED。OLED就是一块显示器。
此次使用的OLED是购买的中景园的0.96寸OLED,所用的驱动 IC 为 SSD1306;其具有内部升压功能;所以在设计的时候不需要再专一设计升压电路;当然了本屏也可以选用外部升压,具体的请详查数据手册。
SSD1306 的每页包含了128 个字节,总共 8 页,这样刚好是 12864 的点阵大小。我们的数据是按照8位来传输的,8位在点阵上面显示是1列8行,就是1列1页的宽度。所以显示的时候就要按照这样的格式进行传输数据。(一列是1bit,一页是8bits)
我们的字符数据是8 * 16的就是标准的8行(1页) * 16列的宽度。所以传输数据的时候要先传第一个8位前8列,再传后8列;
汉字数据是1616的就是标准的16行(2页)宽度。所以传输数据的时候要先传第一个16位,依然是前8列,再传后8列;
这样数据的传输格式处理就完成了。
2. 实物图
3. OLED一共是7个外接引脚,使用SPI时,7个脚的定义如下所示:
我是用mini2440的GPIO来模拟所以引脚的功能要定义一致,然后用杜邦线进行连接。
4.定义的mini2440的引脚功能
因此初始化的时候,将对应的寄存器的该位设置为输出模式,然后往对应的数据寄存器里面写入1和0,则输出高低电平。
5.接下来就是SPI的时序问题,借用韦老师的图
可以看见2440与OLED的连接方式,这里补充了DC,复位,和3.3/5V电压与地。并且DI并不需要,因为OLED不会返回数据。
SCK:提供时钟
DO:作为数据输出
DI:作为数据输入
CS0/CS1:作为片选
程序框架如下所示:
首先初始化,就是初始化作为GPIO控制SPI时序的引脚,代码如下所示:
/*this is gpio moni SPI
*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控制寄存器
/*用GPIO 引脚来模拟SPI*/
static void SPI_GPIO_Init(void)
{
/*将GPIO引脚设置为输出和输入*/
/*由原理图可以知道
*SPIMISO /GPE11 OLED只输入不输出,此脚不用
*SPIMOSI /GPE12 OLED的输入引脚,CPU output
*SPICLK /GPE13 OLED的时钟引脚,CPU output
*OLED_CSn /GPG1 OLED的片选信号,CPU output
*DC /GPF3 OLED的数据/命令引脚,CPU output
*对应2440引出来的GPIO的第25,26,27,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 |= ((1<<(12*2)) | (1<<(13*2))); //置位
}
void SPIInit(void)
{
/*初始化2440的SPI引脚*/
SPI_GPIO_Init();
}
做完初始化之后,就开始实现SPI的时序,代码如下所示:
#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控制寄存器
/*将GPIO引脚设置为输出和输入*/
/*由原理图可以知道
*SPIMISO /GPE11 OLED只输入不输出,此脚不用
*SPIMOSI /GPE12 OLED的输入引脚,CPU output
*SPICLK /GPE13 OLED的时钟引脚,CPU output
*OLED_CSn /GPG1 OLED的片选信号,CPU output
*DC /GPF3 OLED的数据/命令引脚,CPU output
*对应2440引出来的GPIO的第25,26,27,17,12脚
*2440的电源和地分别是2,3脚*/
/**********************************************************/
/**********下面是GPIO模拟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--命令通道*/
}
/*
*设置SPI时钟电平,val;
*高电平--1,低电平--0;
*时钟位在GPIOE的第13位;
*/
static void SPI_Set_CLK(char val)
{
if(val)
GPEDAT |= (1<<13); /*时钟高电平*/
else
GPEDAT &= ~(1<<13); /*时钟低电平*/
}
/******************************/
/*
*2440往SPI数据线上写入1bit的数据(MOSI)
*val的值为0/1
*val为0的时候,就是写低电平
*val为1的时候,就是写高电平
*/
/*设置CPU输出高低电平-MOSI*/
static void SPI_Set_MOSI(char val)
{
if (val)
GPEDAT |= (1<<12);
else
GPEDAT &= ~(1<<12);
}
/*2440向OLED发送8bit的数据/命令*/
void SPISendByte(unsigned char dat)
{
int i;
for (i = 0; i < 8; i++)
{
/*拉低时钟线*/
SPI_Set_CLK(0);
/*往数据线上写入1位数据---最高位*/
SPI_Set_MOSI(dat&0x80);
/*拉高时钟,在时钟的上升沿,OLED读走1bit数据*/
SPI_Set_CLK(1);
dat<<=1;
}
}
/************************************************/
/************************************************/
/************************************************/
/*
*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*/
}
}
SPI时序程序里面实现了发送数据或命令到SPI设备的函数(包括了选中SPI设备,发送之前拉低时钟线,然后往数据线写0或者1,然后拉高时钟线,让SPI设备读走数据,再取消选中SPI设备;),接下来要在SPI时序程序里面补充OLED的显示函数,也就是前面提到的数据格式。
#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 SIZE 16
#define XLevelL 0x02
#define XLevelH 0x10
#define Max_Column 128
#define Max_Row 64
#define Brightness 0xFF
#define X_WIDTH 128
#define Y_WIDTH 64
/*将GPIO引脚设置为输出和输入*/
/*由原理图可以知道
*SPIMISO /GPE11 OLED只输入不输出,此脚不用
*SPIMOSI /GPE12 OLED的输入引脚,CPU output
*SPICLK /GPE13 OLED的时钟引脚,CPU output
*OLED_CSn /GPG1 OLED的片选信号,CPU output
*DC /GPF3 OLED的数据/命令引脚,CPU output
*对应2440引出来的GPIO的第25,26,27,17,12脚
*2440的电源和地分别是2,3脚*/
/**********************************************************/
/**********下面是GPIO模拟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--命令通道*/
}
/*
*设置SPI时钟电平,val;
*高电平--1,低电平--0;
*时钟位在GPIOE的第13位;
*/
static void SPI_Set_CLK(char val)
{
if(val)
GPEDAT |= (1<<13); /*时钟高电平*/
else
GPEDAT &= ~(1<<13); /*时钟低电平*/
}
/******************************/
/*
*2440往SPI数据线上写入1bit的数据(MOSI)
*val的值为0/1
*val为0的时候,就是写低电平
*val为1的时候,就是写高电平
*/
/*设置CPU输出高低电平-MOSI*/
static void SPI_Set_MOSI(char val)
{
if (val)
GPEDAT |= (1<<12);
else
GPEDAT &= ~(1<<12);
}
/*2440向OLED发送8bit的数据/命令*/
void SPISendByte(unsigned char dat)
{
int i;
for (i = 0; i < 8; i++)
{
/*拉低时钟线*/
SPI_Set_CLK(0);
/*往数据线上写入1位数据---最高位*/
SPI_Set_MOSI(dat&0x80);
/*拉高时钟,在时钟的上升沿,OLED读走1bit数据*/
SPI_Set_CLK(1);
dat<<=1;
}
}
/************************************************/
/************************************************/
/************************************************/
/*
*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功能函数--显示/清除*************/
/****************************************/
/****************************************/
/****************************************/
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 */
}
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字节数据 */