江濤帶你玩0.96-OLED之實戰stm32的RTC時鐘(上)

目錄

1.前言

2.準備工作

3.關於OLED驅動,模擬SPI和硬件SPI

4.關於SPI和IIC的驅動的異同點

5.開始驅動OLED,使用STM32-CubeMX生成代碼

6.顯示函數

7.改造顯示函數

 


 

1.前言

因爲疫情原因,宅在家無聊,翻到了之前的一些STM32的開發板,還有兩塊0.96的OLED屏,於是想着再來折騰下電子,雖然我已經有5年時間沒碰過電子了(畢業後本人就轉行Android和Java開發了),但是還是覺得搞電子比較有趣,回憶從前玩單片機都是拿來主義,很少從代碼層去深究實現原理,很多屏幕的驅動都是抄現成的代碼,有時候不好用了又不知道怎麼辦,所以奉勸學電子的後來人,如果打算在這條路上走下去,還是得搞清楚原理,才能以不變應萬變,成爲真正的大神。而這篇博文呢,只是爲了給自己一個警醒,凡是切勿淺嘗輒止,要深入研究,同時也給驅動OLED同樣有困惑的人一些經驗借鑑。好了,廢話不多說了。

 

2.準備工作

工慾善其事必先利其器,所以先把需要用到的工具啥的準備好。

需要安裝的軟件 : Keil5(V-5.25.2.0)     STM32-CubeMX(V-5.6.0)

需要準備的硬件 : STM32系列開發板(本人使用的是STM32F103C8T6),OLED屏幕(4線SPI),ST-Link燒錄器

軟件下載放上分享鏈接:

鏈接:https://pan.baidu.com/s/1MgRisNjq3rcqYgmM6Jmpiw 
提取碼:vkmn

關於環境搭建,大家可以自行百度,我上面分享的也不一定是最新的軟件版本,stm32cubeMX的安裝需要java支持。

例如安裝教程:

STM32CubeMx入門系列教程----安裝教程(適用萌新)

關於KEIL的安裝強調一點:安裝keil的時候,破解軟件運行的時候要使用右鍵以管理員身份運行,同樣keil也要使用右鍵管理員方式運行,然後複製CID破解。如果不是管理員身份運行,破解的時候會出現如下的報錯

You are not logged in as an "Administrator" !!!!

同樣也給出瞭解決方法 Solution:用管理員賬號登陸或者Run as Administrator(以管理員方式運行)
 

3.關於OLED驅動,模擬SPI和硬件SPI

本文驅動OLED使用的是4線SPI方式驅動,當然如果你的外設IO口有限,也可以選擇IIC方式驅動。我這裏先就SPI驅動來談論,下面放上兩段代碼對比。

//向SSD1306寫入一個字節。
//dat:要寫入的數據/命令
//cmd:數據/命令標誌 0,表示命令;1,表示數據;
void OLED_WR_Byte(u8 dat ,u8 cmd)
{	
	if(cmd)
	  OLED_DC_Set(); // 控制腳拉高,寫數據
	else 
	  OLED_DC_Clr(); // 控制腳拉低,寫命令

	// 硬件SPI寫法
	HAL_SPI_Transmit(&hspi2,&dat,sizeof(dat),1000); // SPI寫數據或者命令
	OLED_DC_Set(); 
} 
//向SSD1306寫入一個字節。
//dat:要寫入的數據/命令
//cmd:數據/命令標誌 0,表示命令;1,表示數據;
void OLED_WR_Byte(u8 dat ,u8 cmd)
{	
	u8 i;	
	// 根據傳入的CMD,判斷是寫數據還是寫命令
	if(cmd)
	  OLED_DC_Set(); // 控制腳拉高,寫數據
	else 
	  OLED_DC_Clr(); // 控制腳拉低,寫命令

    // 模擬SPI數據傳輸寫法
	OLED_CS_Clr(); 									// 片選拉低,開始寫數據或命令
	for(i=0;i<8;i++)								// 一字節有8bit,所以需要循環8次
	{			  
		OLED_SCLK_Clr(); 							// 時鐘置低
		dat&0x80 ? OLED_SDIN_Set():OLED_SDIN_Clr(); // 與位判斷,最終數據腳寫0或1就是根據這個
		OLED_SCLK_Set(); 							// 時鐘拉高
		dat<<=1;       								// 數據或命令左移一位  
	}				 		  
	OLED_CS_Set(); 									// 片選拉高,結束寫數據或命令

	OLED_DC_Set();  
} 

上面第一段代碼是硬件SPI的寫法,下面一段是模擬SPI的寫法。可以看到硬件SPI驅動的寫法相較模擬SPI少了很多。我放個圖上來對比下二者的寫法

 

模擬SPI和硬件SPI這兩部分實現的功能其實是一模一樣的,都是有序的將數據傳輸到OLED驅動芯片SSD1306上。而使用STM32的硬件SPI,我們可以少關注CS拉低開始寫入,開關時鐘SCLK,8bit依次判斷寫0或1,然後CS拉高禁止寫入的整個過程。其實懂了這個之後對於之後移植這個驅動就很簡單了。下面我們對照着代碼來分析下SSD1306數據手冊中的時序圖

對照着手冊再回過頭來看代碼,發現是能夠一一對應的。 

 

4.IIC的驅動OLED的例子

用IIC來驅動OLED相比複雜度更高,因爲只用到了時鐘和數據兩根線,所以代碼上也會複雜一點,我找一段IIC的驅動代碼貼上來給大家品鑑下。

void IIC_Start()
{
	OLED_SCLK_Set() ;
	OLED_SDIN_Set();
	OLED_SDIN_Clr();
	OLED_SCLK_Clr();
}

void IIC_Stop()
{
	OLED_SCLK_Set() ;
	OLED_SDIN_Clr();
	OLED_SDIN_Set();
}

void IIC_Wait_Ack()
{
	OLED_SCLK_Set() ;
	OLED_SCLK_Clr();
}
/**********************************************
// IIC Write byte
**********************************************/
void Write_IIC_Byte(unsigned char IIC_Byte)
{
	unsigned char i;
	unsigned char m,da;
	da=IIC_Byte;
	OLED_SCLK_Clr();
	for(i=0;i<8;i++)		
	{
		m=da;
		m=m&0x80;
		if(m==0x80)
			OLED_SDIN_Set();
		else 
			OLED_SDIN_Clr();
		da=da<<1;
		OLED_SCLK_Set();
		OLED_SCLK_Clr();
	}
}
/**********************************************
// IIC Write Command
**********************************************/
void Write_IIC_Command(unsigned char IIC_Command)
{
   IIC_Start();
   Write_IIC_Byte(0x78);            //Slave address,SA0=0
	IIC_Wait_Ack();	
   Write_IIC_Byte(0x00);			//write command
	IIC_Wait_Ack();	
   Write_IIC_Byte(IIC_Command); 
	IIC_Wait_Ack();	
   IIC_Stop();
}
/**********************************************
// IIC Write Data
**********************************************/
void Write_IIC_Data(unsigned char IIC_Data)
{
   IIC_Start();
   Write_IIC_Byte(0x78);			//D/C#=0; R/W#=0
	IIC_Wait_Ack();	
   Write_IIC_Byte(0x40);			//write data
	IIC_Wait_Ack();	
   Write_IIC_Byte(IIC_Data);
	IIC_Wait_Ack();	
   IIC_Stop();
}

/**********************************************
// OLED寫數據或命令 cmd=1:數據,cmd=0:命令
**********************************************/
void OLED_WR_Byte(unsigned dat,unsigned cmd)
{
	if(cmd)
		Write_IIC_Data(dat);
	else 
		Write_IIC_Command(dat);
}

因爲IIC的通訊方式和SPI的通訊協議的不同,導致二者在驅動OLED的時候有差異,我們主要看下核心函數

void Write_IIC_Byte(unsigned char IIC_Byte)其實這個函數的實現也是數據和時鐘腳在工作,高低交替,8bit數據在一位一位的傳輸。跟SPI驅動所不同的是,IIC寫命令和寫數據不再有DC腳和CS腳來控制,而是全由時鐘腳和數據腳來控制,另外增加了數據傳輸的等待操作和增加了從地址質量操作,所以代碼寫法上相對更復雜些。傳輸速率上,SPI和比IIC的快,對IIC感興趣的同學可以對照着手冊自行研究,我這裏就不深入了。

5.開始驅動OLED,使用STM32-CubeMX生成代碼

關於stm32-cubeMX的操作步驟,我這裏截了長圖,大家可以看下,主要是配置GPIO口和SW下載調試口,時鐘保持默認是哦給你內部時鐘就可以了。

具體需要注意的事項我已經在圖上做了標註,其他有疑問的地方大家可以自行查閱資料。

記得生成的代碼,KEIL打開後,需要先全部rebuild一遍!!!

記得生成的代碼,KEIL打開後,需要先全部rebuild一遍!!!

記得生成的代碼,KEIL打開後,需要先全部rebuild一遍!!!

看下KEIL的注意事項

按要求添加好自己的.h文件和.c文件,我這裏添加兩個文件。然後貼上代碼

 下面是 oled.h 

/***************************************
** OLED 頭文件
** 作者:菜鳥江濤
** 時間:2020-03-17 
** CSDN: https://blog.csdn.net/u010898329
**************************************/
#ifndef __OLED_H
#define __OLED_H

// 由於會使用到GPIO口,所以引入相關的頭文件
#include "main.h"

// 常量定義
#define u8         unsigned char  // 將unsigned char 定義簡寫
#define OLED_CMD   1  // 寫命令
#define OLED_DATA  0  // 寫數據

// 端口高低操縱定義
#define OLED_RST_H HAL_GPIO_WritePin(OLED_RST_GPIO_Port,OLED_RST_Pin,GPIO_PIN_SET)    // 復位腳高
#define OLED_RST_L HAL_GPIO_WritePin(OLED_RST_GPIO_Port,OLED_RST_Pin,GPIO_PIN_RESET)  // 復位腳低

// 對應有些OLED模塊的絲印是 D0
#define OLED_SCK_H HAL_GPIO_WritePin(OLED_SCK_GPIO_Port,OLED_SCK_Pin,GPIO_PIN_SET)    // 時鐘腳高
#define OLED_SCK_L HAL_GPIO_WritePin(OLED_SCK_GPIO_Port,OLED_SCK_Pin,GPIO_PIN_RESET)  // 時鐘腳低

// 對應有些OLED模塊的絲印是 D1
#define OLED_SDA_H HAL_GPIO_WritePin(OLED_SDA_GPIO_Port,OLED_SDA_Pin,GPIO_PIN_SET)    // 數據腳高
#define OLED_SDA_L HAL_GPIO_WritePin(OLED_SDA_GPIO_Port,OLED_SDA_Pin,GPIO_PIN_RESET)  // 數據腳低

#define OLED_CS_H HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin,GPIO_PIN_SET)  	    // 片選腳高
#define OLED_CS_L HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin,GPIO_PIN_RESET)     // 片選腳低

#define OLED_DC_H HAL_GPIO_WritePin(OLED_DC_GPIO_Port,OLED_DC_Pin,GPIO_PIN_SET)  			// 控制腳高
#define OLED_DC_L HAL_GPIO_WritePin(OLED_DC_GPIO_Port,OLED_DC_Pin,GPIO_PIN_RESET)  		// 控制腳低

// 函數定義
// OLED的基礎操作函數
void OLED_Write_Byte(u8 data , u8 cmd);
void OLED_Init(void);
void OLED_On(void);
void OLED_Off(void);
void OLCD_Set_Pos(u8 x, u8 y);

// OLED的顯示操作函數
void OLED_Clear(void);
void OLED_Show_Char(u8 x, u8 y , u8 chr);

#endif

接着是 oled.c

#include "oled.h"
#include "ascii.h"

/****************
** 向OLED寫一個字節的數據或是命令
** cmd: 1=命令,0=數據
** data: 待寫入的字節(命令/數據)
*******************/
void OLED_Write_Byte( u8 data , u8 cmd )
{
    u8 i=0;

    // 這裏直接使用三目運算符,不使用if/else寫法,看起來簡潔些
    cmd ? OLED_DC_L : OLED_DC_H ; 				// 寫命令DC輸出低,寫數據DC輸出高

    // 模擬SPI數據傳輸寫法
    OLED_CS_L; 														// 片選拉低,開始寫數據或命令
    for(i=0; i<8; i++)											// 一字節有8bit,所以需要循環8次
    {
        OLED_SCK_L; 												// 時鐘置低
        data&0x80 ? OLED_SDA_H:OLED_SDA_L; 	// 與位判斷,最終數據腳寫0或1就是根據這個
        OLED_SCK_H; 												// 時鐘拉高
        data<<=1;       										// 數據或命令左移一位
    }
    OLED_CS_H; 														// 片選拉高,結束寫數據或命令

    OLED_DC_H;														// 控制腳拉高,置成寫數據狀態
}


/*******************
** OLED初始化
********************/
void OLED_Init(void)
{
    // 製造一個先低後高的電平變換,達到復位的效果
    OLED_RST_L;
    HAL_Delay(500);
    OLED_RST_H;

    OLED_Write_Byte(0xAE,OLED_CMD);//--turn off oled panel
    OLED_Write_Byte(0x02,OLED_CMD);//---set low column address
    OLED_Write_Byte(0x10,OLED_CMD);//---set high column address
    OLED_Write_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    OLED_Write_Byte(0x81,OLED_CMD);//--set contrast control register
    OLED_Write_Byte(0xff,OLED_CMD);// Set SEG Output Current Brightness
    OLED_Write_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
    OLED_Write_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
    OLED_Write_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
    OLED_Write_Byte(0x3f,OLED_CMD);//--1/64 duty
    OLED_Write_Byte(0xD3,OLED_CMD);//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
    OLED_Write_Byte(0x00,OLED_CMD);//-not offset
    OLED_Write_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
    OLED_Write_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
    OLED_Write_Byte(0xD9,OLED_CMD);//--set pre-charge period
    OLED_Write_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    OLED_Write_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
    OLED_Write_Byte(0x12,OLED_CMD);
    OLED_Write_Byte(0xDB,OLED_CMD);//--set vcomh
    OLED_Write_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
    OLED_Write_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
    OLED_Write_Byte(0x02,OLED_CMD);//
    OLED_Write_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
    OLED_Write_Byte(0x14,OLED_CMD);//--set(0x10) disable
    OLED_Write_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
    OLED_Write_Byte(0xA6,OLED_CMD);// 開啓正常顯示 (0xA6 = 正常 / 0xA7 = 反顯)
    OLED_Write_Byte(0xAF,OLED_CMD);//--turn on oled panel

}

/******************
**  開啓OLED顯示
*******************/
void OLED_On(void)
{
    OLED_Write_Byte(0X8D,OLED_CMD);  //SET DCDC命令
    OLED_Write_Byte(0X14,OLED_CMD);  //DCDC ON
    OLED_Write_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}

/******************
**  關閉OLED顯示
*******************/
void OLED_Off(void)
{
    OLED_Write_Byte(0X8D,OLED_CMD);  //SET DCDC命令
    OLED_Write_Byte(0X10,OLED_CMD);  //DCDC OFF
    OLED_Write_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}


/****************************
** 設置座標點
** x: 行座標 0~127
** y: 頁座標 0~7
*****************************/
void OLCD_Set_Pos(u8 x, u8 y)
{
    OLED_Write_Byte(0xb0+y,OLED_CMD);
    OLED_Write_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
    OLED_Write_Byte((x&0x0f)|0x01,OLED_CMD);
}

/********************
** OLED清屏操作
*********************/
void OLED_Clear(void)
{
    u8 i,n;
    for(i=0; i<8; i++)
    {
        OLED_Write_Byte (0xb0+i,OLED_CMD);    //設置頁地址(0~7)
        OLED_Write_Byte (0x00,OLED_CMD);      //設置顯示位置—列低地址
        OLED_Write_Byte (0x10,OLED_CMD);      //設置顯示位置—列高地址
        for(n=0; n<128; n++)OLED_Write_Byte(0,OLED_DATA); // 每一列都置零
    }
}

/**************************************
** 顯示一個字符
** x: 列座標位置
** y: 頁座標位置
** chr: 待顯示的字符
***************************************/
void OLED_Show_Char(u8 x, u8 y , u8 chr)
{
    u8 i=0 ,c=0;
    c = chr - ' '; // 從空字符串算起,得到偏移後的值

    OLCD_Set_Pos(x,y); // 設置顯示位置
    for(i=0; i<6; i++)
    {
        OLED_Write_Byte(F6x8[c][i],OLED_DATA);
    }
}


還有字體文件 ascii.h

#ifndef __ASCII_H
#define __ASCII_H

//常用ASCII表
//偏移量32
//ASCII字符集
const unsigned char  F6x8[][6] =		
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// sp
0x00, 0x00, 0x00, 0x2f, 0x00, 0x00,// !
0x00, 0x00, 0x07, 0x00, 0x07, 0x00,// "
0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14,// #
0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12,// $
0x00, 0x62, 0x64, 0x08, 0x13, 0x23,// %
0x00, 0x36, 0x49, 0x55, 0x22, 0x50,// &
0x00, 0x00, 0x05, 0x03, 0x00, 0x00,// '
0x00, 0x00, 0x1c, 0x22, 0x41, 0x00,// (
0x00, 0x00, 0x41, 0x22, 0x1c, 0x00,// )
0x00, 0x14, 0x08, 0x3E, 0x08, 0x14,// *
0x00, 0x08, 0x08, 0x3E, 0x08, 0x08,// +
0x00, 0x00, 0x00, 0xA0, 0x60, 0x00,// ,
0x00, 0x08, 0x08, 0x08, 0x08, 0x08,// -
0x00, 0x00, 0x60, 0x60, 0x00, 0x00,// .
0x00, 0x20, 0x10, 0x08, 0x04, 0x02,// /
0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E,// 0
0x00, 0x00, 0x42, 0x7F, 0x40, 0x00,// 1
0x00, 0x42, 0x61, 0x51, 0x49, 0x46,// 2
0x00, 0x21, 0x41, 0x45, 0x4B, 0x31,// 3
0x00, 0x18, 0x14, 0x12, 0x7F, 0x10,// 4
0x00, 0x27, 0x45, 0x45, 0x45, 0x39,// 5
0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30,// 6
0x00, 0x01, 0x71, 0x09, 0x05, 0x03,// 7
0x00, 0x36, 0x49, 0x49, 0x49, 0x36,// 8
0x00, 0x06, 0x49, 0x49, 0x29, 0x1E,// 9
0x00, 0x00, 0x36, 0x36, 0x00, 0x00,// :
0x00, 0x00, 0x56, 0x36, 0x00, 0x00,// ;
0x00, 0x08, 0x14, 0x22, 0x41, 0x00,// <
0x00, 0x14, 0x14, 0x14, 0x14, 0x14,// =
0x00, 0x00, 0x41, 0x22, 0x14, 0x08,// >
0x00, 0x02, 0x01, 0x51, 0x09, 0x06,// ?
0x00, 0x32, 0x49, 0x59, 0x51, 0x3E,// @
0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C,// A
0x00, 0x7F, 0x49, 0x49, 0x49, 0x36,// B
0x00, 0x3E, 0x41, 0x41, 0x41, 0x22,// C
0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C,// D
0x00, 0x7F, 0x49, 0x49, 0x49, 0x41,// E
0x00, 0x7F, 0x09, 0x09, 0x09, 0x01,// F
0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A,// G
0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F,// H
0x00, 0x00, 0x41, 0x7F, 0x41, 0x00,// I
0x00, 0x20, 0x40, 0x41, 0x3F, 0x01,// J
0x00, 0x7F, 0x08, 0x14, 0x22, 0x41,// K
0x00, 0x7F, 0x40, 0x40, 0x40, 0x40,// L
0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F,// M
0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F,// N
0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E,// O
0x00, 0x7F, 0x09, 0x09, 0x09, 0x06,// P
0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E,// Q
0x00, 0x7F, 0x09, 0x19, 0x29, 0x46,// R
0x00, 0x46, 0x49, 0x49, 0x49, 0x31,// S
0x00, 0x01, 0x01, 0x7F, 0x01, 0x01,// T
0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F,// U
0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F,// V
0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F,// W
0x00, 0x63, 0x14, 0x08, 0x14, 0x63,// X
0x00, 0x07, 0x08, 0x70, 0x08, 0x07,// Y
0x00, 0x61, 0x51, 0x49, 0x45, 0x43,// Z
0x00, 0x00, 0x7F, 0x41, 0x41, 0x00,// [
0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55,// 55
0x00, 0x00, 0x41, 0x41, 0x7F, 0x00,// ]
0x00, 0x04, 0x02, 0x01, 0x02, 0x04,// ^
0x00, 0x40, 0x40, 0x40, 0x40, 0x40,// _
0x00, 0x00, 0x01, 0x02, 0x04, 0x00,// '
0x00, 0x20, 0x54, 0x54, 0x54, 0x78,// a
0x00, 0x7F, 0x48, 0x44, 0x44, 0x38,// b
0x00, 0x38, 0x44, 0x44, 0x44, 0x20,// c
0x00, 0x38, 0x44, 0x44, 0x48, 0x7F,// d
0x00, 0x38, 0x54, 0x54, 0x54, 0x18,// e
0x00, 0x08, 0x7E, 0x09, 0x01, 0x02,// f
0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C,// g
0x00, 0x7F, 0x08, 0x04, 0x04, 0x78,// h
0x00, 0x00, 0x44, 0x7D, 0x40, 0x00,// i
0x00, 0x40, 0x80, 0x84, 0x7D, 0x00,// j
0x00, 0x7F, 0x10, 0x28, 0x44, 0x00,// k
0x00, 0x00, 0x41, 0x7F, 0x40, 0x00,// l
0x00, 0x7C, 0x04, 0x18, 0x04, 0x78,// m
0x00, 0x7C, 0x08, 0x04, 0x04, 0x78,// n
0x00, 0x38, 0x44, 0x44, 0x44, 0x38,// o
0x00, 0xFC, 0x24, 0x24, 0x24, 0x18,// p
0x00, 0x18, 0x24, 0x24, 0x18, 0xFC,// q
0x00, 0x7C, 0x08, 0x04, 0x04, 0x08,// r
0x00, 0x48, 0x54, 0x54, 0x54, 0x20,// s
0x00, 0x04, 0x3F, 0x44, 0x40, 0x20,// t
0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C,// u
0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C,// v
0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C,// w
0x00, 0x44, 0x28, 0x10, 0x28, 0x44,// x
0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C,// y
0x00, 0x44, 0x64, 0x54, 0x4C, 0x44,// z
0x14, 0x14, 0x14, 0x14, 0x14, 0x14,// horiz lines
};

#endif

字體有三種,後面會完善三種字體的顯示函數,目前的代碼我先僅僅展示如何寫一個 6*8 的字體到 OLED上,主要用到了OLED_Show_Char(u8 x , u8 y , u8 chr);  看下main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2020 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "oled.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */
	OLED_Init();
    OLED_Clear();
	OLED_Show_Char(0,0,'E');
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{ 
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

注意我自己代碼放的位置

看下顯示效果,我手頭的是黃藍雙色的屏幕。

至此,屏幕的驅動就基本大功告成了, 後面我們接着學習更爲複雜的顯示函數,其實都是基於這個函數來改造的。

6.常規顯示函數

6.0 字符顯示拓展,顯示三種不同大小的字符 

/**************************************
** 顯示一個字符
** x: 列座標位置
** y: 頁座標位置
** chr: 待顯示的字符
***************************************/
void OLED_Show_Char(u8 x, u8 y , u8 chr , enum Font_Size size)
{
    u8 i=0  ,c=0;
    c = chr - ' '; // 從空字符串算起,得到偏移後的值

    switch(size)
    {
    case SMALL:
        OLCD_Set_Pos(x,y); // 設置顯示位置
        for(i=0; i< size ; i++)
            OLED_Write_Byte(F6x8[c][i],OLED_DATA);
        break;
    case MEDIA:
        OLCD_Set_Pos(x,y); // 設置顯示位置
        for(i=0; i< 6 ; i++)
            OLED_Write_Byte(F12X6[c][i],OLED_DATA);
        OLCD_Set_Pos(x,y+1); // 設置顯示位置
        for(i=6; i< 12 ; i++)
            OLED_Write_Byte(F12X6[c][i],OLED_DATA);
        break;
    case BIG:
        OLCD_Set_Pos(x,y); // 設置顯示位置
        for(i=0; i< 8 ; i++)
            OLED_Write_Byte(F16x8[c][i],OLED_DATA);
        OLCD_Set_Pos(x,y+1); // 設置顯示位置
        for(i=8; i< 16 ; i++)
            OLED_Write_Byte(F16x8[c][i],OLED_DATA);
        break;
    }
}

這裏我準備了三種字體大小,定義了一個字體的枚舉類,使用這個函數的時候需要傳入枚舉值,例如

	OLED_Show_Char(0,0,'A',SMALL);
	OLED_Show_Char(12,0,'A',MEDIA);
	OLED_Show_Char(24,0,'A',BIG);
	OLED_Show_String(0,2,"123ABC",SMALL);
	OLED_Show_String(0,3,"123ABC",MEDIA);
	OLED_Show_String(0,5,"123ABC",BIG);

6.1 字符串顯示函數

/****************************************
** 顯示字符串
** x: 起始列座標
** y: 起始頁
** chr: 待顯示的字符串
*****************************************/
void OLED_Show_String(u8 x, u8 y , u8 *chr)
{
    u8 i = 0;
    while(chr[i] != '\0') {
        OLED_Show_Char(x,y,chr[i]);
        x+=6; // 字體寬高 6*8
        if(x>120)    // 如果大於了極限值 128
        {
            x = 0 ;  //  列從零開始
            y += 1;  //  另起一頁
        }
        i ++ ;    // 循環繼續,直到字符串都寫完了爲止
    }
}

注意,上面顯示的字符串的寬高是6*8 ,而屏幕的像素點是128*64 。 上面有個大於120的判斷,是因爲如果啓示橫座標是120,那麼整個字體顯示完整就到了到 126  , 如果繼續執行就會出現 126+6 = 132 , 超過了最大限制,會出現重疊,所以會以120作爲判斷標準。後面會將此處的設置爲動態判斷,因爲肯定會出現寬度不是6個像素的字體,這個留着到後面分析

6.2 改造後的字符串顯示函數,支持傳入字體枚舉值

/****************************************
** 顯示字符串
** x: 起始列座標
** y: 起始頁
** chr: 待顯示的字符串
*****************************************/
void OLED_Show_String(u8 x, u8 y , u8 *chr , enum Font_Size size)
{
    u8 i = 0;
    u8 page =  (size>8 ? size >> 3 : 1 );  // 字體大小決定翻幾頁
    u8 len = (size>8 ? size >>1 : size ) ;   // 字體寬度,最後結果對應了字體表中的每行長度索引
    u8 limit = MAX_LEN/len - 1 ;
    while(chr[i] != '\0') {
        OLED_Show_Char(x,y,chr[i],size);
        x+=len; // 字體寬高 6*8
        if(x>(len*limit-1))    // 如果大於了極限值 128
        {
            x = 0 ;  //  列從零開始
            y += page;  //  另起一頁
        }
        i ++ ;    // 循環繼續,直到字符串都寫完了爲止
    }
}

這裏有個常量定義,MAX_LEN = 128,定義的是屏幕寬度的最大像素點。

如果字體高度大於8像素,肯定是要翻頁的,我這裏做了判斷,page = size/8 或者 page=1(至少一頁) ,limit爲一行能最多能顯示的字符個數,len*limit -1 就是最後一個字符開始的位置,跟上面的字符顯示函數做了對應了,這裏改成了動態了。

看下三個的顯示效果,我都顯示在了一個屏幕上了

 如果大家有其他的字體需要,可以自行去使用取模軟件生成ASCII碼,放上ASCII如下

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

6.2 自定義數字顯示

自定義數字顯示我們需要用到取模軟件,取模軟件一併在分享文件中有。軟件取模時候的配置如下

這裏的取模方式使用 逐列 或者 列行 都可以,只要是右側那個取模演示中是從上往下填充的方式都可以實現。具體是什麼原因呢,還得看SSD1306的數據手冊。

自定義的數字,我這裏使用三種字體大小,分別是  寬x高:12*24,16*32,20*40三種字體,下面貼上我的字體頭文件 number.h

#ifndef __NUMBER_H
#define __NUMBER_H

//寬x高:12*24
const unsigned char N24x12[][12]={	  
	

{0x00,0x00,0xC0,0xE0,0x30,0x10,0x10,0x30,0xE0,0xC0,0x00,0x00},
{0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00},
{0x00,0x00,0x03,0x07,0x0C,0x08,0x08,0x0C,0x07,0x03,0x00,0x00},/*"0",0*/

{0x00,0x00,0x20,0x20,0x20,0xE0,0xF0,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x08,0x08,0x08,0x0F,0x0F,0x08,0x08,0x08,0x00,0x00},/*"1",1*/

{0x00,0xC0,0xA0,0x10,0x10,0x10,0x10,0x30,0xE0,0xC0,0x00,0x00},
{0x00,0x01,0x01,0x00,0x80,0x60,0x30,0x18,0x0F,0x03,0x00,0x00},
{0x00,0x0C,0x0E,0x09,0x08,0x08,0x08,0x08,0x08,0x0F,0x00,0x00},/*"2",2*/

{0x00,0xE0,0xE0,0x10,0x10,0x10,0x30,0xE0,0xC0,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x08,0x08,0x0C,0x17,0xF3,0xC0,0x00,0x00},
{0x00,0x07,0x07,0x08,0x08,0x08,0x08,0x0C,0x07,0x03,0x00,0x00},/*"3",3*/

{0x00,0x00,0x00,0x00,0x00,0x80,0x40,0xF0,0xF0,0x00,0x00,0x00},
{0x00,0x60,0x50,0x4C,0x42,0x41,0x40,0xFF,0xFF,0x40,0x40,0x40},
{0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x0F,0x0F,0x08,0x08,0x00},/*"4",4*/

{0x00,0x00,0xF0,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00},
{0x00,0x80,0x9F,0x08,0x04,0x04,0x04,0x0C,0xF8,0xF0,0x00,0x00},
{0x00,0x03,0x05,0x08,0x08,0x08,0x08,0x0E,0x07,0x01,0x00,0x00},/*"5",5*/

{0x00,0x00,0xC0,0xE0,0x20,0x10,0x10,0x10,0x70,0x60,0x00,0x00},
{0x00,0xFE,0xFF,0x10,0x08,0x04,0x04,0x04,0x0C,0xF8,0xF0,0x00},
{0x00,0x00,0x03,0x06,0x0C,0x08,0x08,0x08,0x04,0x07,0x01,0x00},/*"6",6*/

{0x00,0x00,0xE0,0x30,0x10,0x10,0x10,0x10,0x90,0x70,0x30,0x00},
{0x00,0x00,0x00,0x00,0x00,0x80,0xF0,0x0C,0x03,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x0F,0x0F,0x00,0x00,0x00,0x00,0x00},/*"7",7*/

{0x00,0xC0,0xE0,0x30,0x10,0x10,0x10,0x10,0x30,0xE0,0xC0,0x00},
{0x00,0xC1,0xE7,0x36,0x0C,0x08,0x18,0x18,0x34,0xE7,0xC1,0x00},
{0x00,0x03,0x07,0x04,0x08,0x08,0x08,0x08,0x0C,0x07,0x03,0x00},/*"8",8*/

{0x00,0x80,0xE0,0x60,0x10,0x10,0x10,0x10,0x60,0xC0,0x00,0x00},
{0x00,0x0F,0x1F,0x30,0x20,0x20,0x20,0x10,0x88,0xFF,0x7F,0x00},
{0x00,0x00,0x06,0x0E,0x08,0x08,0x08,0x04,0x07,0x01,0x00,0x00},/*"9",9*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x0E,0x0E,0x0E,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x0E,0x0E,0x0E,0x00,0x00,0x00,0x00},/*":",10*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",11*/

};

//寬x高:16*32
const unsigned char N32x16[][16]={	  
{0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x30,0x10,0x10,0x70,0xE0,0xC0,0x80,0x00,0x00},
{0x00,0x00,0xFC,0xFF,0xFF,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0xFF,0xFC,0x00},
{0x00,0x00,0x0F,0x7F,0xFF,0xF0,0x80,0x00,0x00,0x00,0x80,0xE0,0xFF,0x7F,0x0F,0x00},
{0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x02,0x02,0x03,0x01,0x00,0x00,0x00,0x00},/*"0",0*/

{0x00,0x00,0x00,0x40,0x40,0x40,0x40,0xE0,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x02,0x02,0x02,0x03,0x03,0x03,0x03,0x03,0x02,0x02,0x02,0x00,0x00},/*"1",1*/

{0x00,0x00,0x80,0xC0,0x60,0x30,0x10,0x10,0x10,0x10,0x30,0x70,0xE0,0xE0,0x80,0x00},
{0x00,0x00,0x07,0x07,0x06,0x00,0x00,0x00,0x00,0x80,0xC0,0xF0,0x7F,0x3F,0x0F,0x00},
{0x00,0x00,0x80,0xC0,0x60,0x30,0x1C,0x0E,0x07,0x03,0x01,0x00,0x80,0xF0,0xF0,0x00},
{0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00},/*"2",2*/

{0x00,0x00,0xC0,0xE0,0xE0,0x30,0x10,0x10,0x10,0x30,0x70,0xE0,0xC0,0x80,0x00,0x00},
{0x00,0x00,0x03,0x03,0x03,0x00,0x40,0x40,0x40,0xE0,0xF0,0xBF,0x9F,0x0F,0x00,0x00},
{0x00,0x00,0xF0,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xFF,0xFF,0x3C,0x00},
{0x00,0x00,0x00,0x01,0x01,0x03,0x02,0x02,0x02,0x02,0x03,0x01,0x01,0x00,0x00,0x00},/*"3",3*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xF0,0xF0,0xF0,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x80,0xE0,0x78,0x1C,0x07,0x03,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00},
{0x00,0x0C,0x0F,0x0B,0x09,0x08,0x08,0x08,0x08,0xFF,0xFF,0xFF,0x08,0x08,0x08,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x02,0x02,0x03,0x03,0x03,0x02,0x02,0x02,0x00},/*"4",4*/

{0x00,0x00,0x00,0xC0,0xF0,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x00},
{0x00,0x00,0x00,0xFF,0xFF,0x60,0x30,0x10,0x10,0x10,0x30,0x70,0xE0,0xC0,0x00,0x00},
{0x00,0x00,0xF0,0xF0,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xFF,0xFF,0x3F,0x00},
{0x00,0x00,0x00,0x01,0x01,0x03,0x02,0x02,0x02,0x02,0x03,0x01,0x01,0x00,0x00,0x00},/*"5",5*/

{0x00,0x00,0x00,0x00,0xC0,0xE0,0x60,0x30,0x10,0x10,0x10,0xF0,0xE0,0xE0,0x00,0x00},
{0x00,0x00,0xF8,0xFF,0xFF,0xE1,0x30,0x30,0x10,0x10,0x30,0x70,0xE0,0xC0,0x00,0x00},
{0x00,0x00,0x1F,0x7F,0xFF,0xE0,0x80,0x00,0x00,0x00,0x00,0x80,0xFF,0xFF,0x3F,0x00},
{0x00,0x00,0x00,0x00,0x01,0x01,0x03,0x03,0x02,0x02,0x03,0x03,0x01,0x00,0x00,0x00},/*"6",6*/

{0x00,0x00,0x80,0xF0,0xF0,0x30,0x30,0x30,0x30,0x30,0x30,0xB0,0xF0,0x70,0x30,0x00},
{0x00,0x00,0x03,0x03,0x00,0x00,0x00,0x00,0xC0,0xF8,0x3E,0x07,0x01,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"7",7*/

{0x00,0x00,0x80,0xC0,0xE0,0x70,0x30,0x10,0x10,0x10,0x30,0x70,0xE0,0xC0,0x80,0x00},
{0x00,0x00,0x0F,0x9F,0xBF,0xFC,0x70,0x70,0xE0,0xC0,0xE0,0xB0,0x9F,0x1F,0x07,0x00},
{0x00,0x7C,0xFF,0xFF,0x83,0x00,0x00,0x00,0x00,0x01,0x03,0x87,0xFF,0xFF,0x7C,0x00},
{0x00,0x00,0x00,0x01,0x01,0x03,0x02,0x02,0x02,0x02,0x03,0x01,0x01,0x00,0x00,0x00},/*"8",8*/

{0x00,0x00,0xC0,0xE0,0xE0,0x30,0x10,0x10,0x10,0x10,0x30,0xE0,0xC0,0x80,0x00,0x00},
{0x00,0x7F,0xFF,0xFF,0xC0,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xFF,0xFF,0xFE,0x00},
{0x00,0x00,0xC0,0xC1,0xC3,0x03,0x02,0x02,0x02,0x83,0xC1,0xF9,0x7F,0x1F,0x07,0x00},
{0x00,0x00,0x01,0x01,0x03,0x02,0x02,0x02,0x03,0x03,0x01,0x00,0x00,0x00,0x00,0x00},/*"9",9*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x78,0x78,0x78,0x30,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xC0,0xC0,0x80,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x03,0x01,0x00,0x00,0x00,0x00,0x00},/*":",10*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",11*/

};

const unsigned char N40x20[][20]={
{0x00,0x00,0x00,0x00,0x80,0xC0,0xE0,0xE0,0x70,0x30,0x10,0x10,0x30,0xE0,0xE0,0xC0,0x80,0x00,0x00,0x00},
{0x00,0x00,0xF0,0xFE,0xFF,0xFF,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xFF,0xFF,0xFE,0xF0,0x00},
{0x00,0x00,0x7F,0xFF,0xFF,0xFF,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x7F,0x00},
{0x00,0x00,0x00,0x03,0x0F,0x1F,0x3F,0x38,0x60,0x60,0x40,0x40,0x60,0x38,0x3F,0x1F,0x0F,0x03,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"0",0*/

{0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0xC0,0xE0,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x60,0x7F,0x7F,0x7F,0x7F,0x60,0x40,0x40,0x40,0x40,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"1",1*/

{0x00,0x00,0x00,0xC0,0xC0,0x60,0x20,0x30,0x10,0x10,0x10,0x30,0x30,0xF0,0xE0,0xE0,0xC0,0x00,0x00,0x00},
{0x00,0x00,0x1F,0x1F,0x1F,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xFF,0xFF,0x7F,0x1F,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1E,0x0F,0x07,0x03,0x01,0x00,0x00,0x00,0x00},
{0x00,0x00,0x70,0x78,0x7C,0x6F,0x67,0x63,0x60,0x60,0x60,0x60,0x60,0x60,0x70,0x78,0x7E,0x0E,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"2",2*/

{0x00,0x00,0x80,0xC0,0xE0,0x60,0x30,0x10,0x10,0x10,0x30,0x30,0xF0,0xE0,0xE0,0xC0,0x00,0x00,0x00,0x00},
{0x00,0x00,0x07,0x0F,0x0F,0x0E,0x00,0x00,0x00,0x00,0x80,0x80,0xE0,0xFF,0x7F,0x3F,0x1F,0x00,0x00,0x00},
{0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x01,0x01,0x01,0x03,0x07,0x0E,0xFE,0xFC,0xF8,0xE0,0x00,0x00},
{0x00,0x00,0x0F,0x1F,0x3F,0x37,0x60,0x40,0x40,0x40,0x40,0x60,0x70,0x38,0x3F,0x1F,0x0F,0x03,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"3",3*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xE0,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xE0,0x78,0x1C,0x0F,0x03,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00},
{0x00,0x40,0x60,0x78,0x5C,0x4F,0x43,0x40,0x40,0x40,0x40,0x40,0xFF,0xFF,0xFF,0x40,0x40,0x40,0x40,0x40},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x40,0x7F,0x7F,0x7F,0x60,0x40,0x40,0x40,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"4",4*/

{0x00,0x00,0x00,0x00,0xF0,0xF0,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x00,0x00},
{0x00,0x00,0x00,0xF8,0xFF,0x07,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x80,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x87,0x87,0x87,0x01,0x01,0x00,0x00,0x00,0x00,0x01,0x07,0xFF,0xFF,0xFE,0xF8,0x00,0x00},
{0x00,0x00,0x07,0x1F,0x3F,0x23,0x60,0x40,0x40,0x40,0x40,0x60,0x70,0x3C,0x3F,0x1F,0x0F,0x03,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"5",5*/

{0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xE0,0x60,0x30,0x10,0x10,0x10,0x10,0xF0,0xF0,0xE0,0xC0,0x00,0x00},
{0x00,0x00,0xE0,0xFC,0xFF,0xFF,0x07,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC1,0x81,0x81,0x01,0x00,0x00},
{0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x07,0x03,0x01,0x00,0x00,0x00,0x01,0x01,0x07,0xFF,0xFF,0xFE,0xF8,0x00},
{0x00,0x00,0x00,0x07,0x0F,0x1F,0x3E,0x38,0x60,0x60,0x40,0x40,0x40,0x60,0x38,0x3F,0x1F,0x0F,0x03,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"6",6*/

{0x00,0x00,0x00,0x80,0xF0,0xF0,0x70,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0xF0,0xF0,0x70,0x30,0x00},
{0x00,0x00,0x00,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xF0,0x3C,0x0F,0x07,0x01,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0xFC,0xFF,0x0F,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x7F,0x7F,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"7",7*/

{0x00,0x00,0x00,0x00,0xC0,0xE0,0xE0,0xF0,0x30,0x10,0x10,0x10,0x10,0x30,0xF0,0xE0,0xE0,0xC0,0x00,0x00},
{0x00,0x00,0x00,0x1F,0x7F,0xFF,0xFF,0xF0,0xE0,0xC0,0x80,0x80,0x00,0x80,0xE0,0xFF,0x7F,0x3F,0x1F,0x00},
{0x00,0x00,0xE0,0xF0,0xF8,0xFC,0x1E,0x07,0x03,0x03,0x07,0x07,0x0F,0x1F,0x3F,0xFC,0xFC,0xF0,0xE0,0x00},
{0x00,0x00,0x07,0x0F,0x1F,0x3F,0x38,0x60,0x60,0x40,0x40,0x40,0x40,0x60,0x38,0x3F,0x1F,0x0F,0x07,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"8",8*/

{0x00,0x00,0x00,0x80,0xC0,0xE0,0xE0,0x70,0x30,0x10,0x10,0x10,0x30,0x70,0xE0,0xC0,0x80,0x00,0x00,0x00},
{0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xFF,0xFF,0xFF,0xF8,0x00},
{0x00,0x00,0x01,0x07,0x0F,0x0F,0x1F,0x1C,0x1C,0x18,0x18,0x18,0x0C,0x0E,0xC7,0xFF,0xFF,0xFF,0x1F,0x00},
{0x00,0x00,0x00,0x1C,0x3C,0x7C,0x7C,0x40,0x40,0x40,0x60,0x60,0x38,0x3C,0x1F,0x0F,0x07,0x01,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"9",9*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xE0,0xE0,0xE0,0xE0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x03,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x7C,0x7C,0x7C,0x7C,0x38,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*":",10*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",11*/
};

#endif

這個是用取模軟件自己生成的,大家也可以自己動手生成自己的一份。看下數字顯示函數。

/*********************************************************
** 自定義數字顯示函數
** x: 列起始位置 0~(127-len) 取值從0到最後一個字符的起始位置
** y: 頁起始位置 0~7
** num: 待顯示的數字 0~9
** size: 字體大小枚舉值
*********************************************************/
void OLED_Show_Num(u8 x, u8 y , u8 num , enum Num_Size size)
{
    u8 i,j ;
    u8 len = size >> 1 ;  // 字體寬度,這裏直接處以2,因爲字體寬度正好是高度的一半
    u8 page = size >> 3 ; // 這裏處以8直接用移位操作,高度大於8就要翻頁

    for(i = 0 ; i < page ; i ++)
    {
        OLCD_Set_Pos(x,y+i); // 設置顯示位置
        switch(size)
        {
        case SMALL_NUM:
            for(j=0; j<len; j++)
                OLED_Write_Byte(N24x12[num*page + i][j],OLED_DATA);  // 寫數據
            break;
        case MEDIA_NUM:
            for(j=0; j<len; j++)
                OLED_Write_Byte(N32x16[num*page + i][j],OLED_DATA);  // 寫數據
            break;
        case BIG_NUM:
            for(j=0; j<len; j++)
                OLED_Write_Byte(N40x20[num*page + i][j],OLED_DATA);  // 寫數據
            break;
        }
    }
}

下面是oled.h中的枚舉定義。

// 定義字體枚舉,值爲字體的高度
enum Font_Size {SMALL = 6 , MEDIA = 12 , BIG = 16};
// 定義數字枚舉,值爲數字的高度
enum Num_Size {SMALL_NUM = 24 , MEDIA_NUM = 32 , BIG_NUM = 40};

看下自定義數字的顯示效果

如果想要其他風格或者大小不同的字體,大家可以自己去取模生成。 

6.3 自定義漢字顯示

/*********************************************************
** 自定義漢字顯示函數
** x: 列起始位置 0~(127-len) 取值從0到最後一個字符的起始位置
** y: 頁起始位置 0~7
** index: 待顯示的漢字序號
** size: 字體大小枚舉值
*********************************************************/
void OLED_Show_Chinese(u8 x, u8 y , u8 index , enum Zh_Size size)
{
    u8 i,j ;
    u8 len = size  ;  // 字體寬度,等於字體高度
    u8 page = size >> 3 ; // 這裏處以8直接用移位操作,高度大於8就要翻頁

    for(i = 0 ; i < page ; i ++)
    {
        OLCD_Set_Pos(x,y+i); // 設置顯示位置
        switch(size)
        {
        case ZH16:
            for(j=0; j<len; j++)
                OLED_Write_Byte(ZH16x16[index*page + i][j],OLED_DATA);  // 寫數據
            break;
        case ZH32:
            for(j=0; j<len; j++)
                OLED_Write_Byte(ZH32x32[index*page + i][j],OLED_DATA);  // 寫數據
            break;
        }
    }
}

自定義漢字的顯示其實和上面的顯示數字類似,不過是數字的寬高是1:2,漢字的寬高是1:1,就只有這裏有區別而已,我只取16*16的字體和32*32的字體給大家演示下。

6.4 自定義圖標顯示。

放上我的圖標頭文件

#ifndef __ICON_H
#define __ICON_H

//寬x高:16*16
const unsigned char ICON16[][16]={	  
	
{0xC0,0xF0,0x10,0xD0,0xD0,0xD0,0x10,0xD0,0xD0,0xD0,0x10,0xD0,0xD0,0xD0,0xF0,0x00},
{0x07,0x1F,0x10,0x17,0x17,0x17,0x10,0x17,0x17,0x17,0x10,0x17,0x17,0x17,0x1F,0x00},/*"電池",0*/

{0x08,0x18,0x28,0x48,0x88,0x48,0x28,0x18,0x08,0x00,0x00,0x00,0xE0,0x00,0xF8,0x00},
{0x00,0x00,0x40,0x40,0x7F,0x40,0x40,0x00,0x7C,0x00,0x7F,0x00,0x7F,0x00,0x7F,0x00},/*"信號",1*/

{0x00,0x00,0x00,0x08,0x10,0x20,0x40,0xFE,0x86,0xC4,0x68,0x30,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x10,0x08,0x04,0x02,0x7F,0x61,0x23,0x16,0x0C,0x00,0x00,0x00,0x00},/*"藍牙",2*/

};

#endif

這些圖標是我在取模軟件中用圖像模式,用鼠標一個一個點畫出來的,可能有些醜,大家其實也可以自己畫。至於圖標的顯示函數,把上面的字符顯示的函數改一改就可以用了

** 自定義圖標顯示
** x: 列起始位置 0~(127-len) 取值從0到最後一個字符的起始位置
** y: 頁起始位置 0~7
** index: 待顯示的圖標序號,圖標大小爲16*16
*********************************************************/
void OLED_Show_Icon(u8 x, u8 y , u8 index)
{
    u8 i,j ;
    u8 len = 16  ;
    u8 page = 2 ;

    for(i = 0 ; i < page ; i ++)
    {
        OLCD_Set_Pos(x,y+i); // 設置顯示位置
        for(j=0; j<len; j++)
            OLED_Write_Byte(ICON16[index*page + i][j],OLED_DATA);  // 寫數據
    }

}

看下效果 

7.功能顯示函數

7.1 開關屏幕

/******************
**  開啓OLED顯示
*******************/
void OLED_On(void)
{
    OLED_Write_Byte(0X8D,OLED_CMD);  //SET DCDC命令
    OLED_Write_Byte(0X14,OLED_CMD);  //DCDC ON
    OLED_Write_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}

/******************
**  關閉OLED顯示
*******************/
void OLED_Off(void)
{
    OLED_Write_Byte(0X8D,OLED_CMD);  //SET DCDC命令
    OLED_Write_Byte(0X10,OLED_CMD);  //DCDC OFF
    OLED_Write_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}

 開關屏幕其實是根據SSD1306手冊中的寫命令來,這兩個函數可以直接拷貝使用。

7.2 正常顯示和反顯

  • 全屏幕反顯,使用一條寫命令語句就可以了
OLED_Write_Byte(0xA6,OLED_CMD);// 開啓正常顯示 (0xA6 = 正常 / 0xA7 = 反顯)
  • 字符本身反顯,其實也很簡單,將待寫入的數據取反操作後寫入
/*********************************************************
** 自定義漢字顯示函數
** x: 列起始位置 0~(127-len) 取值從0到最後一個字符的起始位置
** y: 頁起始位置 0~7
** index: 待顯示的漢字序號
** size: 字體大小枚舉值
** reverse: 是否反顯 1=是,0=否
*********************************************************/
void OLED_Show_Chinese(u8 x, u8 y , u8 index , enum Zh_Size size , u8 reverse)
{
    u8 i,j ;
    u8 len = size  ;  // 字體寬度,等於字體高度
    u8 page = size >> 3 ; // 這裏處以8直接用移位操作,高度大於8就要翻頁

    for(i = 0 ; i < page ; i ++)
    {
        OLCD_Set_Pos(x,y+i); // 設置顯示位置
        switch(size)
        {
        case ZH16:
            for(j=0; j<len; j++)
                reverse?OLED_Write_Byte(~ZH16x16[index*page + i][j],OLED_DATA):OLED_Write_Byte(ZH16x16[index*page + i][j],OLED_DATA);  // 寫數據
            break;
        case ZH32:
            for(j=0; j<len; j++)
                reverse?OLED_Write_Byte(~ZH32x32[index*page + i][j],OLED_DATA):OLED_Write_Byte(ZH32x32[index*page + i][j],OLED_DATA);  // 寫數據
            break;
        }
    }
}

我改造了下漢字顯示的函數,增加了是否反顯的標誌位 ,用來控制是否反顯。

7.3 屏幕滾動

/************************************************************
** 滾動顯示某一頁
** start: 開始頁 0 - 7
** end  : 結束頁 0 - 7
** dirct:方向枚舉,left向左,right向右
**************************************************************/
void OLED_Scroll_Display(u8 start,u8 end,enum DIRECTION dirct)
{
	if(start > 7 || end > 7) return; // 頁碼超出
	OLED_Write_Byte(0x2E ,OLED_CMD); // 關閉滾動
	OLED_Write_Byte(dirct,OLED_CMD); // 向左/右滾動
	OLED_Write_Byte(0x00 ,OLED_CMD); // 空制令
	OLED_Write_Byte(start,OLED_CMD); // 起始頁
	OLED_Write_Byte(0x0F ,OLED_CMD); // 滾動間隔,0=不滾動,值越大,滾動越快
	OLED_Write_Byte(end  ,OLED_CMD); // 結束頁
	OLED_Write_Byte(0x00 ,OLED_CMD); // 空指令
	OLED_Write_Byte(0xFF ,OLED_CMD); // 空指令,加兩條空指令,不然不會生效
	OLED_Write_Byte(0x2F ,OLED_CMD); // 開啓滾動
}

方向枚舉的定義如下

// 滾動方向枚舉
enum DIRECTION { LEFT = 0x27 , RIGHT = 0x26};

 看下演示效果。

 

如果有感興趣的可以加羣交流,以上的代碼我會在羣裏分享。QQ羣:1087285029

或者掃碼

 

 

 

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