嵌入式Linux——SPI总线(1):2440裸机GPIO模拟SPI驱动OLED

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/W1107101310/article/details/80294596

简介:

    本文主要讲解使用2440裸机的GPIO模拟SPI来控制OLED显示我们想显示的字符串。而我使用了两种方法来实现SPI控制OLED,一种是使用4线的SPI,即MOSI,CLK,DC,CS。而另一种是使用3线的SPI,即MOSI,CLK,CS。

所用开发板:JZ2440 V3

所用OLED 屏幕:韦东山老师淘宝所用屏幕以及另一种淘宝所用屏幕。

所用OLED 驱动芯片:SSD1306

声明:

    本文主要是看完韦东山老师的视频后所写的模拟SPI控制OLED的程序,同时我也想将我看完SSD1306芯片手册后对原有的4线SPI程序进行修改以实现3线的spi。希望我的文章会对你有所帮助,谢谢。

4线SPI介绍:

    我们先介绍老师在视频中所讲解的4线SPI,即MOSI,CLK,DC,CS。我想很多人可能会问:我们怎么知道或者怎么判断我们的OLED是4线spi驱动还是3线spi驱动?

    那我们就要通过OLED驱动芯片SSD1306的芯片手册来判断了,我们看芯片手册中关于总线接口选择的列表


    从上图看BS0,BS1,BS2这三个引脚就是用来确定OLED总线接口的。BS0,BS1,BS2都为0时为4线的SPI接口,而当BS0为1而BS1和BS2为0时为3线SPI接口。而我们前面介绍的用i2c驱动OLED则是BS0,BS1,BS2分别为0,1,0时。现在我们看我们的SSD1306芯片是如何接的:


    从上图可以看出,BS0,BS1,BS2都为0,所以我们是4线的SPI接口。

    而现在我想说明的一点是,我们是用GPIO模拟SPI来控制OLED显示我们要显示的字符串,所以我们本文的重点应当是在SSD1306芯片手册如何控制操作OLED,而不是将重点放到2440中对于GPIO端口的控制。

    因此我们看SSD1306芯片手册中关于4线SPI的部分,而这四线主要为数据输出线(MOSI,这里的输入输出是相对于主机而言的),串行时钟(CLK),片选(CS#,其中#表示低电平有效)以及数据/命令引脚(D/C#)。而对于SSD1306芯片,写命令与写数据则是由数据/命令引脚(D/C#)控制。当该引脚为高电平时为写数据,当为低电平时为写命令。


    而在2440中我们用于模拟SPI的引脚为:

    其中SSD1306中引脚与2440中引脚的对应关系为:

SPIMOSI GPG6
SPICLK GPG7
OLED_CSn GPF1
OLED_DC GPG4

    而对于4线的spi,他的时序图为:


    下面我们将参考这个时序图写出对GPIO的操作来实现对SPI的模拟。

    首先我们要对我们所用到的GPIO进行初始化

static void spi_gpio_init(void)
{
	/* GPF1 OLED_CS0 output */
	GPFCON &= ~(0x3<<(1*2));
	GPFCON |= (0x1<<(1*2));
	GPFDAT |= (0x1<<1);      /* 将OLED片选拉为高电平 */

	/* GPG2 FLASH_CS0 output
	*  GPG4 OLED_D/C  output
	*  GPG5 SPI_MISO  input
	*  GPG6 SPI_MOSI  output
	*  GPG7 SPI_CLK   output
	*/
	GPGCON &= ~((0x3<<(2*2)) | (0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)) | (0x3<<(7*2)));
	GPGCON |= ((0x1<<(2*2)) | (0x1<<(4*2)) | (0x1<<(6*2)) | (0x1<<(7*2)));
	GPGDAT |= (0x1<<2);       /* 将FLASH片选拉为高电平 */
}


    初始化完GPIO后,我们就可以使用这些端口来模拟SPI了。我们从上面的时序图可以看出当DC引脚为低电平时输出命令。而CS片选为低电平表示选中该芯片,这时我们就可以对芯片进行操作了。而当片选升为高电平时,取消选中,这时停止对芯片的操作。所以我们的写命令程序为:

void spi_oled_write_cmd(unsigned char cmd)
{
	spi_set_DC(0);   
	spi_set_CS(0);
	spi_send_byte(cmd); 
	spi_set_CS(1);
	spi_set_DC(0);
}

    上面程序大体描述了怎么写命令,而具体写的细节要对应下图:


    从图中我们可以知道三点:

1. 数据在上升沿写入

2. 先传输高位

3. 共传输8位数据

    所以我们根据这三点我们写出其对应的操作函数为:

void spi_send_byte(unsigned char val)
{
	int i;

	for(i=0;i<8;i++){
		spi_set_clk(0);
		spi_set_DO(val & 0x80);
		spi_set_clk(1);
		val <<= 1;
	}
}

    而在上面程序中所用到的spi_set_clk函数,spi_set_DO函数,spi_set_CS函数以及spi_set_DC函数则是对GPIO寄存器的操作,程序为:

static void spi_set_clk(char val)
{
	if(val)
		GPGDAT |= (0x1<<7);
	else
		GPGDAT &= ~(0x1<<7);
}

static void spi_set_DO(char val)
{
	if(val)
		GPGDAT |= (0x1<<6);
	else
		GPGDAT &= ~(0x1<<6);
}


static void spi_set_CS(char val)
{
	if(val)
		GPFDAT |= (0x1<<1);
	else
		GPFDAT &= ~(0x1<<1);
}

static void spi_set_DC(char val)
{
	if(val)
		GPGDAT |= (0x1<<4);
	else
		GPGDAT &= ~(0x1<<4);
}

    上面我们介绍了当DC为低电平发送命令的情况,我们现在讲解当DC为高电平时发送数据的情况,其实与发送命令唯一不同的一点就是这时DC变为了高电平,而其他的都不变,所以程序为:

void spi_oled_write_dat(unsigned char dat)
{
	spi_set_DC(1);
	spi_set_CS(0);
	spi_send_byte(dat);
	spi_set_CS(1);
	spi_set_DC(1);
}

    上面就是我们用GPIO来模拟SPI的操作,而下面我们将运用这些函数来实现对OLED的控制。首先我们同样要初始化OLED,这里我们就要用到spi_oled_write_cmd函数来向OLED发送命令了:

void oled_init(void)
{
	spi_oled_write_cmd(0xae);//--display off
	spi_oled_write_cmd(0x00);//---set low column address
	spi_oled_write_cmd(0x10);//---set high column address
	spi_oled_write_cmd(0x40);//--set start line address  
	spi_oled_write_cmd(0xb0);//--set page address
	spi_oled_write_cmd(0x81); // contract control
	spi_oled_write_cmd(0xff);//--128   
	spi_oled_write_cmd(0xa1);//set segment remap 
	spi_oled_write_cmd(0xa6);//--normal / reverse
	spi_oled_write_cmd(0xa8);//--set multiplex ratio(1 to 64)
	spi_oled_write_cmd(0x3f);//--1/32 duty
	spi_oled_write_cmd(0xc8);//com scan direction
	spi_oled_write_cmd(0xd3);//-set display offset
	spi_oled_write_cmd(0x00);//

	spi_oled_write_cmd(0xd5);//set osc division
	spi_oled_write_cmd(0x80);//
         
	spi_oled_write_cmd(0xd8);//set area color mode off
	spi_oled_write_cmd(0x05);//
            
	spi_oled_write_cmd(0xd9);//set pre-charge period
	spi_oled_write_cmd(0xf1);//
            
	spi_oled_write_cmd(0xda);//set com pin configuartion
	spi_oled_write_cmd(0x12);//
          
	spi_oled_write_cmd(0xdb);//set vcomh
	spi_oled_write_cmd(0x30);//
          
	spi_oled_write_cmd(0x8d);//set charge pump enable
	spi_oled_write_cmd(0x14);//
	
	spi_oled_write_cmd(0xaf);//--turn on oled panel

}
    其中对于OLED的命令操作就要你自己去看SSD1306芯片手册,我就不在这里介绍了。我们继续看程序,我们知道我们的目的是: 在OLED上显示我们要输入的字符串。所以我们这里要先获得 所有字符的字库
/****************************************8*16的点阵************************************/
const unsigned char F8X16[][16]=	  
	{
	  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},// 0
	  {0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00},//! 1
	  {0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//" 2
	  {0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00},//# 3
	  {0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00},//$ 4
	  {0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00},//% 5
	  {0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10},//& 6
	  {0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//' 7
	  {0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00},//( 8
	  {0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00},//) 9
	  {0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00},//* 10
	  {0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00},//+ 11
	  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00},//, 12
	  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01},//- 13
	  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00},//. 14
	  {0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00},/// 15
	  {0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00},//0 16
	  {0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//1 17
	  {0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00},//2 18
	  {0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00},//3 19
	  {0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00},//4 20
	  {0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00},//5 21
	  {0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00},//6 22
	  {0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00},//7 23
	  {0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00},//8 24
	  {0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00},//9 25
	  {0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00},//: 26
	  {0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00},//; 27
	  {0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00},//< 28
	  {0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00},//= 29
	  {0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00},//> 30
	  {0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00},//? 31
	  {0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00},//@ 32
	  {0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20},//A 33
	  {0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00},//B 34
	  {0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00},//C 35
	  {0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00},//D 36
	  {0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00},//E 37
	  {0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00},//F 38
	  {0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00},//G 39
	  {0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20},//H 40
	  {0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//I 41
	  {0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00},//J 42
	  {0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00},//K 43
	  {0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00},//L 44
	  {0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00},//M 45
	  {0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00},//N 46
	  {0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00},//O 47
	  {0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00},//P 48
	  {0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00},//Q 49
	  {0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20},//R 50
	  {0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00},//S 51
	  {0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},//T 52
	  {0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},//U 53
	  {0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00},//V 54
	  {0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00},//W 55
	  {0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20},//X 56
	  {0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},//Y 57
	  {0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00},//Z 58
	  {0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00},//[ 59
	  {0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00},//\ 60
	  {0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00},//] 61
	  {0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//^ 62
	  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},//_ 63
	  {0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//` 64
	  {0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20},//a 65
	  {0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00},//b 66
	  {0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00},//c 67
	  {0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20},//d 68
	  {0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00},//e 69
	  {0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//f 70
	  {0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00},//g 71
	  {0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},//h 72
	  {0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//i 73
	  {0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00},//j 74
	  {0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00},//k 75
	  {0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//l 76
	  {0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F},//m 77
	  {0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},//n 78
	  {0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},//o 79
	  {0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00},//p 80
	  {0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80},//q 81
	  {0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00},//r 82
	  {0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00},//s 83
	  {0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00},//t 84
	  {0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20},//u 85
	  {0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00},//v 86
	  {0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00},//w 87
	  {0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00},//x 88
	  {0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00},//y 89
	  {0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00},//z 90
	  {0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40},//{ 91
	  {0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00},//| 92
	  {0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00},//} 93
	  {0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}//~ 94
	};

    我们使用的是8*16的字库。

    这里我们就要对我们的OLED进行一下说明,如下图:


    我们的OLED是一个128*64的屏幕,也就是说他的横向有128个像素点,而他的纵向有64个像素点。而我们要操作OLED其实就是操作OLED的寄存器来向寄存器写值最终实现对这128*64 个像素点的亮与灭控制。而在OLED显存中用一字节的数据表示一纵列的8个像素点,这样我们就可以用128*8个字节来描述这个OLED中像素点。而我们将横向的这128个字节称为一页,所以我们共有8页。

    有了上面对于OLED显存的了解,我们再讲解程序就会好了很多。我们知道我们用到的字库是8*16的字库也就是说我们要用到横向的8个像素点和纵向的16个像素点来描述一个字符,而我们知道纵向8个像素点就是一页,而我们要用16个,那就是两页了。如下图:


    例如:当我们要输入字符A时,要分为两部分即用page0的第一个byte和page1的第一个byte分别描述字符A的上下两部分。下面我们还要介绍另一个重要的参数,那就是字符出现的位置。因为我们要在指定的位置显示特定的字符串。所以我们要先指定要写字符的位置,而指定位置的操作可以通过写命令来实现,他的具体程序为:

void oled_set_pos(int page,int col)
{
	spi_oled_write_cmd(0xb0+page);   /* 确定页位置 */
	spi_oled_write_cmd(col&0x0f);    /* 确定行的低字节位 */
	spi_oled_write_cmd(((col&0xf0)>>4)|0x10);  /* 确定行的高字节位 */	
} 

    确定了字符出现的位置我们就可以从字库中调用我们需要的字符来显示了,所以显示单个字符的代码为:

static void oled_put_char(int page,int col,char c)
{
	int i;
	/* 得到字模 */
	const unsigned char *val = F8X16[c-' '];

	/* 发送字符到OLED的指定位置 */
	oled_set_pos(page,col);

	/* 发送8字节数据 */
	for(i=0;i<8;i++){
		spi_oled_write_dat(val[i]);
	}

	/* 发送字符到OLED下一页的指定位置 */
	oled_set_pos(page+1,col);

	/* 发送8字节数据 */
	for(i=0;i<8;i++){
		spi_oled_write_dat(val[i+8]);
	}
	
}

    上面的函数只能发送单个的字符,而我们想要发送字符串就要调用下面的函数来一个一个的发送这些字符了,同时每当发完一个字符我们都要确认当前的位置是否到达一行的末尾,如果到达末尾我们要跳到下一行。显示字符串的代码为:

void oled_print_string(int page,int col,char *str)
{
	int i = 0;
	while(str[i]){         /* 判断字符是否传完 */
		oled_put_char(page,col,str[i]);
		col += 8;        /* 没传送完一个字符行位置加8 */
		if(col > 127){    /* 到行末尾,跳到下一行 */
			col = 0;
			page += 2;
		}
		i++;
	}
}

    有了上面的知识,我们再来介绍清屏函数就容易多了。其实清屏函数就是将屏幕中的所有的像素点熄灭。也就是向每个像素点写0 。所以清屏函数为:

void oled_clear(void)
{
	int page,col;
	for(page=0;page<8;page++){      /* 遍历每一页 */
		oled_set_pos(page,0);
		for(col=0;col<128;col++){  /* 遍历每一行 */
			spi_oled_write_dat(0);
		}
	}
}
    讲到这里我们就讲完了 4线SPI。而我们接下来就要讲3线SPI。


3线SPI介绍:

    由于韦东山老师的OLED只能支持4线的spi,而我想写一个3线的spi,所有我又自己买了个OLED,同时将他改为了3线的spi,如下图:


    如图中红框所示,当连接R3,R4时表示是4线的spi,而当连接R2,R3时表示是3线的spi。我将原来的R4移到了R2,所以现在他就是一个3线的SPI控制的OLED。我们下面看SSD1306中关于3线spi的介绍。

    对于3线的spi,他的3条线分别为数据输出线(MOSI,这里的输入输出是相对于主机而言的),串行时钟(CLK),片选(CS#,其中#表示低电平有效)。而相对于4线的spi,这里少了D/C#线,那么我们就少了用于区分数据还是命令的线。但是我们一定要让SSD1306芯片知道我们传输的是数据还是命令,要不然就混了。那他的解决办法是什么那?如何用来区分我们发的是数据还是命令那?

    我们看SSD1306的数据手册中3线spi的时序图


   从图上我们看到他一共发送了9位数据,其中最高位就是数据/命令位,用来区分后面发送的8位是数据还是命令。而其他的就与4线的spi一样了。

    我们下面直接看代码,首先还是GPIO引脚初始化

static void spi_3wire_gpio_init(void)
{
	/* GPF1 OLED_CS0 output */
	GPFCON &= ~(0x3<<(1*2));
	GPFCON |= (0x1<<(1*2));
	GPFDAT |= (0x1<<1);

	/* GPG2 FLASH_CS0 output
	*  GPG5 SPI_MISO  input
	*  GPG6 SPI_MOSI  output
	*  GPG7 SPI_CLK   output
	*/
	GPGCON &= ~((0x3<<(2*2)) | (0x3<<(5*2)) | (0x3<<(6*2)) | (0x3<<(7*2)));
	GPGCON |= ((0x1<<(2*2)) | (0x1<<(6*2)) | (0x1<<(7*2)));
	GPGDAT |= (0x1<<2);

}

    与4线spi的GPIO初始化相似,就是少了对DC引脚的初始化。

    接下来我们还是看时序图的上半部分,了解3线spi传输的大致过程:


    从上面看,当片选为低电平时选中芯片,开始对芯片进行操作,而当片选为高电平时取消对芯片的操作。他对应的写数据/命令函数的代码为:

void spi_oled_write_9bit(unsigned char val,int DC)
{
	spi_set_CS(0);
	spi_send_byte(val,DC);
	spi_set_CS(1);
}

    而具体对芯片的操作我们看下图:


    从图中我们可以知道三点:

1. 数据在上升沿写入

2. 先传输高位

3. 共传输9位数据

    而通过上图和上面的三点我们可以写出单字节数据/命令写函数

void spi_send_byte(unsigned char val,int DC)
{
	int i;
	if(DC){  /* 判断发送的是数据还是命令 */
		spi_set_clk(0);
		spi_set_DO(1);
		spi_set_clk(1);
	}else{
		spi_set_clk(0);
		spi_set_DO(0);
		spi_set_clk(1);
	}
        
	for(i=0;i<8;i++){
		spi_set_clk(0);
		spi_set_DO(val & 0x80);
		spi_set_clk(1);
		val <<= 1;
	}
}

    其中,第一步我们先判断发送的是命令还是数据,并相应的拉低或升高DO线,然后再发送8位数据。

    因此我们每一次向SSD1306芯片发送内容时都要标明他是命令还是数据。因此我用宏定义了这两个常量:

#define OLED_CMD 0
#define OLED_DAT 1

    而在函数中我们直接使用宏会更加的方便。

    做好了spi的传输函数,我们就要初始化OLED了。这时,初始化函数变为:

void oled_3wire_init(void)
{
	spi_oled_write_9bit(0xae,OLED_CMD);//--display off
	spi_oled_write_9bit(0x00,OLED_CMD);//---set low column address
	spi_oled_write_9bit(0x10,OLED_CMD);//---set high column address
	spi_oled_write_9bit(0x40,OLED_CMD);//--set start line address  
	spi_oled_write_9bit(0xb0,OLED_CMD);//--set page address
	spi_oled_write_9bit(0x81,OLED_CMD); // contract control
	spi_oled_write_9bit(0xff,OLED_CMD);//--128   
	spi_oled_write_9bit(0xa1,OLED_CMD);//set segment remap 
	spi_oled_write_9bit(0xa6,OLED_CMD);//--normal / reverse
	spi_oled_write_9bit(0xa8,OLED_CMD);//--set multiplex ratio(1 to 64)
	spi_oled_write_9bit(0x3f,OLED_CMD);//--1/32 duty
	spi_oled_write_9bit(0xc8,OLED_CMD);//com scan direction
	spi_oled_write_9bit(0xd3,OLED_CMD);//-set display offset
	spi_oled_write_9bit(0x00,OLED_CMD);//

	spi_oled_write_9bit(0xd5,OLED_CMD);//set osc division
	spi_oled_write_9bit(0x80,OLED_CMD);//

	spi_oled_write_9bit(0xd8,OLED_CMD);//set area color mode off
	spi_oled_write_9bit(0x05,OLED_CMD);//

	spi_oled_write_9bit(0xd9,OLED_CMD);//set pre-charge period
	spi_oled_write_9bit(0xf1,OLED_CMD);//

	spi_oled_write_9bit(0xda,OLED_CMD);//set com pin configuartion
	spi_oled_write_9bit(0x12,OLED_CMD);//

	spi_oled_write_9bit(0xdb,OLED_CMD);//set vcomh
	spi_oled_write_9bit(0x30,OLED_CMD);//

	spi_oled_write_9bit(0x8d,OLED_CMD);//set charge pump enable
	spi_oled_write_9bit(0x14,OLED_CMD);//

	spi_oled_write_9bit(0xaf,OLED_CMD);//--turn on oled panel
}

    而相应的设置位置的函数变为:

void oled_set_pos(int page,int col)
{
	spi_oled_write_9bit(0xb0+page,OLED_CMD);
	spi_oled_write_9bit(col&0x0f,OLED_CMD);
	spi_oled_write_9bit(((col&0xf0)>>4)|0x10,OLED_CMD);	
}

    上面说的函数都是写命令,而在OLED显示字符时就要写数据了:

static void oled_put_char(int page,int col,char c)
{
	int i;
	/* 得到字模 */
	const unsigned char *val = F8X16[c-' '];

	/* 发送OLED */
	oled_set_pos(page,col);

	/* 发送8字节数据 */
	for(i=0;i<8;i++){
		spi_oled_write_9bit(val[i],OLED_DAT);
	}

	/* 发送OLED */
	oled_set_pos(page+1,col);

	/* 发送8字节数据 */
	for(i=0;i<8;i++){
		spi_oled_write_9bit(val[i+8],OLED_DAT);
	}
	
}

    而对于显示字符串的函数为:

void oled_3wire_print_string(int page,int col,char *str)
{
	int i = 0;
	while(str[i]){
		oled_put_char(page,col,str[i]);
		col += 8;
		if(col > 127){
			col = 0;
			page += 2;
		}
		i++;
	}
}
    相对于4线spi,3线的spi的介绍就比较简单了。这主要是因为,4线和3线的spi有很多的相似部分,而我们对应于3线spi,在4线的基础上做一些修改就可以了。所以3线的spi介绍的很简单。而关于他们的详细代码我放到了: 2440裸板spi操作OLED

    最后的效果图为:



猜你喜欢

转载自blog.csdn.net/W1107101310/article/details/80294596