十:mini2440使用GPIO模拟SPI总线协议实现OLED的显示

此次的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列;
汉字数据是16
16的就是标准的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字节数据 */
    
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值