使用GPIO实现SPI协议操作OLED

所用的芯片是三星平台的s3c2440,所用的OLED屏是深圳金逸晨电子的128*64屏,模组是晶门科技的SSD1306
简单分析一下使用GPIO实现SPI协议操作OLED的硬件原理
由于OLED只是单纯的信息显示设备,因而只需要往其中写入数据,而不需要从其中读取数据
所以我们只需要用GPIO来模拟三条线,分别为片选线(CS) 、时钟线(SPISCLK)和DO(设备输出)
程序的框架是什么呢?
对于主设备来说,需要配置相应的GPIO管脚,操控管脚的电位,让这些管脚根据SPI协议来发送信息给外接从设备
对于从设备,这里就是OLED显示屏,首先配置。。。让它可以读取到主设备传来的信息,然后对这些信息进行处理。具体的处理方式就是根据根据主设备传来的地址信息和数据信息,在OLED的显存的相应地址上写入相应的数据信息。
然后在主函数中执行就可以了
在这里插入图片描述
可以发现上面管脚所接的线除了刚才所说的三根线以外,还有OLED_DC FLASH_CSn,前者是OLED用来确认当前信号是地址D还是命令C
DC引脚为低电平,则说明DO上传输的是CMD,若为高电平,则说明DO上传输的是data;后者是FLASH的片选引脚。

配置GPIO (SPI_GPIO_Init)
首先是配置相应的GPIO管脚为输入或者输出模式
GPF1 GPG2、GPG4、GPG6、GPG7需要配置为输出引脚,GPG5配置为输入引脚
除了要配置CONFIG寄存器,对于两个片选引脚还要配置DATA寄存器,两个片选引脚都需要设置为输出高电平,才能保证他们被选中,所以要往DATA寄存器中的对应位中写入1.

然后就需要操控三根线的电位来模式SPI协议SPISendByte(SPISendByte函数)。
根据协议,一次性可以传输8字节的数据,可以通过模拟时钟电位先低后高,然后在低变高时,将数据写到DO线上,这样就实现了一个字节的传输,然后用8次循环实现一次传输。由于SPI传输也遵循MSB,所以我们每次传输最高的第8,每传输一次将数据右移一位且,巧妙地实现了

gpio_spi.c

#include "s3c2440.h"

static void SPI_GPIO_Init(void)
{
	 /* GPF1 OLED_CSn output */
    GPFCON &= ~(3<<(1*2));
    GPFCON |= (1<<(1*2));
    GPFDAT |= (1<<1);

    /* GPG2 FLASH_CSn output
    * GPG4 OLED_DC   output
    * GPG5 SPIMISO   input
    * GPG6 SPIMOSI   output
    * 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)) | (1<<(6*2)) | (1<<(7*2)));
    GPGDAT |= (1<<2); 
	
}

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);
}

void SPISendByte(unsigned char val)
{
	/*模式信息传输时,要时刻把SPI的时序图放在心里,在这里选择了mode 1*/
	int i;
	for(i = 0; i < 8; i++)
	{
		SPI_Set_CLK(0);
		SPI_Set_DO(val & 0x80);
		SPI_Set_CLK(1);
		val << 1;
	}
}

void SPIInit(void)
{
	SPI_GPIO_Init();
}

然后是实现OLED文件,在这个文件里是要将GPIO上传来的包含着地址和数据的信息,根据地址在OLED显存上的具体位置,写入传过来的具体信息。(表述好像不是很清除)
所以我们需要认识OLED,尤其是它的显存布局和如何向显存中写入数据
OLED显存有三种地址模式,这里选择页地址模式,芯片手册上的介绍如下
在这里插入图片描述
页地址模式是把每一8行划分为1页(实际上其他模式也是这样),这种模式下的写显存方式是先写第0页的第0列,然后写第0页的第1列,当写到第0页的第127行时,开始写第1页。
关于这款OLED的特性不做详述,不过在读其芯片手册时,注意一个小细节,它的芯片手册会在前面列出一个command table,包含这个命令的名称和命令是如何执行的,然后在后面逐个介绍这些命令,同时在介绍的标题上有索引。
OLED.c

void OLED_Set_DC(char val)
{
	if(val)
		GPGDAT &= (1 << 4);
	else
		GPGDAT |= ~(1 << 4);
}

void OLED_Set_CS(char val)
{
	if(val)
		GPFDAT &= (1 << 1);
	else
		GPFDAT |= ~(1 << 1);
}

void OLEDWriteCmd(unsigned char cmd )
{
	/*设置DC管脚低电位*/
	OLED_Set_DC(0);
	/*片选引脚选中当前OLED*/
	OLED_Set_CS(0);
	
	/*传输信息*/
	SPISendByte(cmd);

	OLED_Set_CS(1);
	OLED_Set_DC(1);
}

void OLEDWriteDat(unsigned char dat)
{
	/*设置DC管脚高电位*/
	OLED_Set_DC(1);
	/*片选引脚选中当前OLED*/
	OLED_Set_CS(0);
	
	/*传输信息*/
	SPISendByte(dat);

	OLED_Set_CS(1);
	OLED_Set_DC(1);
}

static void OLEDSetPageAddrMode(void)
{
    OLEDWriteCmd(0x20);
    OLEDWriteCmd(0x02);
}

static void OLEDSetPos(int page, int col)
{
    OLEDWriteCmd(0xB0 + page); /* page address */

	/*设置col的addr时,需要分两次传输*/
    OLEDWriteCmd(col & 0xf);   
    OLEDWriteCmd(0x10 + (col >> 4));  
}


static void OLEDClear(void)
{
    int page, i;
    for (page = 0; page < 8; page ++)
    {
        OLEDSetPos(page, 0);
        for (i = 0; i < 128; i++)
            OLEDWriteDat(0);
    }
}
void OLEDInit(void)

    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); 

    OLEDSetPageAddrMode();

    OLEDClear();
    
    OLEDWriteCmd(0xAF); /*display ON*/    
}

void OLEDPutChar(int page, int col, char c)
{
	int i;
	/* 点阵源码是一个二级指针,所以我们只需要根据ASCI码找到对应的
	 * 一级数组,并把一级数组名称作为指针传递给dots
	 */
	const unsigned char *dots = oled_asc2_8x16[c - ' '];
	
	/* 根据刚才的分析,OLED对应的显存中每个字符存放在两页一列中
	 * 再依据页地址模式,我们需要先写第一页然后写第二页
	 */
	 OLEDSetPos(page, col);
	 if(i = 0; i < 8; i++)
	 	OLEDWriteDat(dots[i]);

	 OLEDSetPos(page+1, col)
	 if(i = 0; i < 8; i++)
	 	OLEDWriteDat(dots[i+8]);
	 	
}

void OLEDPrint(int page, int col, char * str)
{
	int i;

	while(str[i])
	{
		OLEDPutChar(page, col, str[i]);
		col += 8;
		i++;
		if(col > 127)
		{
			col = 0;
			page += 2;
		}
	}
}

main.c

#include "gpio_spi.h"
#include "oled.h"

void main(void)
{
	SPIInit();
	OLEDInit();
	OLEDPrint(0, 0, "hello world! go for it");
}

简单说明一下我所遇到的一些问题
第一次测试发现没有任何输出,原因是在SPI_GPIO_Init,除了要配置输入和输出,对于片选管脚所对应的GPIO,我们还要配置data寄存器,让对应的片选引脚输出高电平。所以在一开始的时候,外接的OLED设备和OLED flash根本就没有被2440所选中
第二次测试发现有输出,但是是一堆乱码,出现这种情况有两种原因,一种写点阵的函数有问题,第二种是点阵本身有问题,结果发现是原先所用的font文件是16 *8,而我们这里是8 *16.

由于OLED属于输出设备,2440对其只有写操作,而没有读操作,所以上面只是构建了写函数,现在增加读函数以拓展思维,参考如下

unsigned char SPIRecvByte(void)
{
	int i;
	unsigned char val = 0;

	for(i = 0; i < 8; i++)
	{
		val <<= 1;
		SPI_Set_CLK(0);
		if(SPI_GET_DI())
			val |= 1;
		SPI_Set_CLK(1);
	}

	return val;
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值