STM32F103ZET6 驅(qū)動 OLED
目錄
-
前言
-
OLED模塊的基本了解
-
OLED驅(qū)動程序的開發(fā)
前言
? 大家好,這是我第一次發(fā)帖,由于,我的技術并不成熟,程序難免有編寫不規(guī)范的地方,希望讀者能夠指正,也希望這篇帖子能夠讓讀者對OLED模塊有個大致的了解。很高興能與大家交流。
OLED模塊的基本了解
OLED模塊的引腳:
?
? 圖片轉(zhuǎn)載自淘寶商家
? 我使用的OLED模塊有以下幾個引腳:
引腳名 | 功能 | 驅(qū)動電壓 | 相連接MCU的端口 |
---|---|---|---|
GND | 接地 | GND | |
VCC | 電源電壓 | 3.3v ~ 5v | 3.3v |
DO | 時鐘線 | 2.2v ~ 5v | SCLK(PA5) |
D1 | 數(shù)據(jù)線 | 2.2v ~ 5v | MOSI(PA7) |
RES | 復位線 | 2.2v ~ 5v | PC5 |
DC | 數(shù)據(jù)/命令控制線 | 2.2v ~ 5v | PC4 |
CS | 片選線 | 2.2v ~ 5v | PA4 |
- RES 為復位線,低電平復位
- 當GPIO向DC端口輸出高電平時,MCU會通過GPIO向D1端口發(fā)送數(shù)據(jù),使其顯示在OLED屏上。當GPIO向DC端口輸出低電平時,MCU會通過GPIO向D1端口發(fā)送命令,通過這些命令,可以配置OLED模塊。
以上是對OLED模塊引腳的簡要了解,在編寫驅(qū)動程序之前,我們還需要了解OLED模塊的尋址方式。
OLED模塊的尋址方式:
OLED有三種尋址模式:
-
水平尋址
-
垂直尋址
-
頁尋址
最常用的尋址模式是頁尋址模式,本驅(qū)動程序所使用的尋址方法也是頁尋址。我會重點介紹頁尋址和水平尋址,簡要地介紹垂直尋址。
在介紹尋址模式之前,我們需要了解一些預備知識:
0.96寸的OLED顯示屏有128列,64行,即128x64,64行被分為8組,每個128x8的區(qū)域被稱為頁,如下圖:
可以看到COM0~COM7,也就是0 ~ 7行從屬于Page0,也就是第0頁,依此類推。
通過這種劃分,可以把整個屏幕劃為8份。
通過這樣的劃分,當我們往某頁某一列中寫入數(shù)據(jù)0x5A時,就能控制哪一列的哪個格子是亮或者滅的,如下圖:
0x5A
亮 | 1 |
---|---|
滅 | 0 |
亮 | 1 |
滅 | 0 |
滅 | 0 |
亮 | 1 |
滅 | 0 |
亮 | 1 |
(注:從頂行到底行,由地位到高位)
?
? 在OLED模塊中,我們會通過指令來設置OLED模塊,假設,我們通過WriteCmd(uint8_t command)函數(shù)向OLED模塊發(fā)送指令。你會發(fā)現(xiàn),有一條就會生效的指令,有多條配合使用才會生效的指令。
void OLED_Init(void) {
// 使能相關的GPIO口 配置SPI外設
OLED_SPI_Init();
OLED_SPI_RES_HIGH;
// 延時200ms
Delay_us(200);
/**************************************** 以下是官方的代碼******************************/
WriteCmd(0xAE); //display off
WriteCmd(0x20); //Set Memory Addressing Mode
WriteCmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
WriteCmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7
WriteCmd(0xc8); //Set COM Output Scan Direction
WriteCmd(0x00); //---set low column address
WriteCmd(0x10); //---set high column address
WriteCmd(0x40); //--set start line address
WriteCmd(0x81); //--set contrast control register
WriteCmd(0xff); //亮度調(diào)節(jié) 0x00~0xff
WriteCmd(0xa1); //--set segment re-map 0 to 127
WriteCmd(0xa6); //--set normal display
WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
WriteCmd(0x3F); //
WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
WriteCmd(0xd3); //-set display offset
WriteCmd(0x00); //-not offset
WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
WriteCmd(0xf0); //--set divide ratio
WriteCmd(0xd9); //--set pre-charge period
WriteCmd(0x22); //
WriteCmd(0xda); //--set com pins hardware configuration
WriteCmd(0x12);
WriteCmd(0xdb); //--set vcomh
WriteCmd(0x20); //0x20,0.77xVcc
WriteCmd(0x8d); //--set DC-DC enable
WriteCmd(0x14); //
WriteCmd(0xaf); //--turn on oled panel
}
以上面官方提供的初始化代碼片段為例子:
WriteCmd(0xAE); //display off 關閉顯示
WriteCmd(0x81); //--set contrast control register
WriteCmd(0xff); //亮度調(diào)節(jié) 0x00~0xff
// 以上兩條命令是設置對比度控制
// 通過0x81命令選擇相應的寄存器,然后,設置范圍0x00 ~ 0xff的亮度大小,控制OLED屏的亮度
如果想熟悉命令,請參考相應的官方手冊。
下面簡要介紹幾條命令:
WriteCmd(0x00); // 設置為水平尋址
WriteCmd(0x01); // 設置為垂直尋址
WriteCmd(0x10); // 設置為頁尋址
WriteCmd(0x8D); // 設置電荷泵
WriteCmd(0x14); // 開啟電荷泵
WriteCmd(0xAF); // OLED喚醒
WriteCmd(0xAE); // 關閉OLED
設置頁地址:
0xby
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
1 | 0 | 1 | 1 | Y3 | Y2 | Y1 | Y0 |
這就是設置頁地址命令的格式,[7:4]位是固定的,而[3:0]位可以通過寫入0000b ~ 0111b,這個值域的二進制數(shù)來指定程序在屏幕顯示時的起始地址。
設置列地址:
關于設置列地址,有兩條指令0x1[x7:x4] 和 0x0[x3:x0]。
高四位的值為1的指令表示的是設置列地址的高四位,高四位的值為0的指令表示設置列地址的第四位。
然后,指令**0x1[x7:x4]的值左移四位,然后與0x0[x3:x0]**的值相加,組成一個8位二進制數(shù),來決定選擇的是0 ~127行中的第幾行為初始地址
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 1 | x7 | x6 | x5 | x4 |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | x3 | x2 | x1 | x0 |
所以下面的一個函數(shù),大家應該看得懂:
// 設置起始坐標
void SetPos(uint8_t x, uint8_t y) {
// 設置頁地址
WriteCmd(0xb0 + y);
// 取列高位
WriteCmd((x & 0xf0)>>4 | 0x10);
// 取列低位
WriteCmd((x & 0x0f) | 0x01);
}
// 以上代碼的意思是,將每個頁地址的起始地址設置為0列
對以上的知識有個基本了解之后,下面將介紹幾種尋址模式:
- 首先是頁尋址
? 如上圖所示,頁尋址的辦法是,橫向?qū)γ啃羞M行讀和寫,當?shù)竭_每行的末尾的時候,會立即返回每行的開頭。
如果到達該行的末尾時,我們不想返回該行的開頭,那么我們就需要坐標的起始值。
- 水平尋址
? 如上圖所示,當完成對一行的遍歷后,頁地址值會自增1,然后,會跳到下一頁進行遍歷,當對0 ~7 頁都遍歷之后,會返回0頁0列。
OLED驅(qū)動程序的開發(fā)
本次實驗使用了STM32F103ZET6最小系統(tǒng)板,面包板,以及一個電源模塊和JLINK OB仿真器,如下圖:
SPI模塊的程序如下:
#ifndef _SPI_H
#define _SPI_H
#include "stm32f10x.h"
// DO SCLK
#define OLED_SPI_DO_Pin GPIO_Pin_5
#define OLED_SPI_DO_Port GPIOA
#define OLED_SPI_DO_CLK RCC_APB2Periph_GPIOA
#define OLED_SPI_DO_CLK_FUN RCC_APB2PeriphClockCmd
// D1 MOSI
#define OLED_SPI_MOSI_Pin GPIO_Pin_7
#define OLED_SPI_MOSI_Port GPIOA
#define OLED_SPI_MOSI_CLK RCC_APB2Periph_GPIOA
#define OLED_SPI_MOSI_CLK_FUN RCC_APB2PeriphClockCmd
// CS NSS
#define OLED_SPI_CS_Pin GPIO_Pin_4
#define OLED_SPI_CS_Port GPIOA
#define OLED_SPI_CS_CLK RCC_APB2Periph_GPIOA
#define OLED_SPI_CS_CLK_FUN RCC_APB2PeriphClockCmd
// DC Data or Cammand 數(shù)據(jù)或者命令選擇
#define OLED_SPI_DC_Pin GPIO_Pin_4
#define OLED_SPI_DC_Port GPIOC
#define OLED_SPI_DC_CLK RCC_APB2Periph_GPIOC
#define OLED_SPI_DC_CLK_FUN RCC_APB2PeriphClockCmd
// RES RESET
#define OLED_SPI_RES_Pin GPIO_Pin_5
#define OLED_SPI_RES_Port GPIOC
#define OLED_SPI_RES_CLK RCC_APB2Periph_GPIOC
#define OLED_SPI_RES_CLK_FUN RCC_APB2PeriphClockCmd
#define OLED_SPI SPI1
#define OLED_SPI_CLK RCC_APB2Periph_SPI1
#define OLED_SPI_CLK_FUN RCC_APB2PeriphClockCmd
#define OLED_SPI_GPIO_CLK_FUN RCC_APB2PeriphClockCmd
// 復位引腳的高低電平的輸出
#define OLED_SPI_RES_HIGH GPIO_SetBits(OLED_SPI_RES_Port, OLED_SPI_RES_Pin)
#define OLED_SPI_RES_LOW GPIO_ResetBits(OLED_SPI_RES_Port, OLED_SPI_RES_Pin)
// DC命令選擇的高低電平的輸出
#define OLED_SPI_DC_HIGH GPIO_SetBits(OLED_SPI_DC_Port, OLED_SPI_DC_Pin)
#define OLED_SPI_DC_LOW GPIO_ResetBits(OLED_SPI_DC_Port, OLED_SPI_DC_Pin)
void OLED_SPI_Init(void);
void OLED_SPI_Write(uint8_t data);
#endif
#include "spi.h"
void OLED_SPI_Init(void) {
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 打開時鐘
OLED_SPI_CLK_FUN(OLED_SPI_CLK, ENABLE);
OLED_SPI_GPIO_CLK_FUN(OLED_SPI_DO_CLK | OLED_SPI_MOSI_CLK | OLED_SPI_CS_CLK | OLED_SPI_DC_CLK
| OLED_SPI_RES_CLK, ENABLE);
// DO SCLK
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = OLED_SPI_DO_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(OLED_SPI_DO_Port, &GPIO_InitStructure);
// D1 MOSI
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = OLED_SPI_MOSI_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(OLED_SPI_MOSI_Port, &GPIO_InitStructure);
// CS NSS
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = OLED_SPI_CS_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(OLED_SPI_CS_Port, &GPIO_InitStructure);
// DC 命令數(shù)據(jù)選擇
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = OLED_SPI_DC_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(OLED_SPI_DC_Port, &GPIO_InitStructure);
// RES RESET 低電平復位
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = OLED_SPI_RES_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(OLED_SPI_RES_Port, &GPIO_InitStructure);
// 初始化 SPI結構體
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 軟件控制
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主機模式
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 大端數(shù)據(jù)先行
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; // 單線發(fā)送
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 數(shù)據(jù)長度為8
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 空閑時為高電平,偶數(shù)邊沿
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
// 初始化SPI
SPI_Init(OLED_SPI, &SPI_InitStructure);
// 使能SPI
SPI_Cmd(OLED_SPI, ENABLE);
}
void OLED_SPI_Write(uint8_t data) {
// 判斷 SPI的 發(fā)送緩沖區(qū)是否為空?
while(SPI_I2S_GetFlagStatus(OLED_SPI, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(OLED_SPI, data);
}
SPI模塊的配置如上面的代碼所示,關于SPI協(xié)議,在本貼中,不會提及,請感興趣的讀者去閱讀STM32的參考手冊。要提及的一點是,SPI_InitStructure.SPI_NSS需要設置成軟件控制,如果有硬件控制,可能OLED無法顯示數(shù)據(jù),我個人認為,可能是如果通過硬件控制,NSS端口可能以極快的頻率在高低電平之間來回切換,導致采集數(shù)據(jù)時,OLED無法被作為從設備選中,這是一家之言,如果有不正確之處,請大家指正。
OLED模塊如下:
#ifndef __OLED_H
#define __OLED_H
#include "stm32f10x.h"
#define OLED_ADDRESS 0x78 //通過調(diào)整0R電阻,屏可以0x78和0x7A兩個地址 -- 默認0x78
#define OLED_CMD 0
#define OLED_DATA 1
void WriteCmd(uint8_t SPI_Command);
void WriteData(uint8_t SPI_Data);
void OLED_Init(void);
void OLED_ON(void);
void OLED_OFF(void);
void SetPos(uint8_t x, uint8_t y);
void OLED_Fill(uint8_t Fill_Data);
void OLED_Clean(void);
void OLED_ShowStr(uint8_t x, uint8_t y, char ch[], uint8_t TextSize);
#endif
#include "spi.h"
#include "oled.h"
#include "bsp_systick.h"
#include "codetab.h"
// 設置oled的顯存
// 存放格式如下
// [0] 0 1 2 3 ... 127
// [1] 0 1 2 3 ... 127
// [2] 0 1 2 3 ... 127
// [3] 0 1 2 3 ... 127
// [4] 0 1 2 3 ... 127
// [5] 0 1 2 3 ... 127
// [6] 0 1 2 3 ... 127
// [7] 0 1 2 3 ... 127
uint8_t OLED_GRAM[128][8];
// 初始化 OLED模塊0
void OLED_Init(void) {
// 使能相關的GPIO口 配置SPI外設
OLED_SPI_Init();
OLED_SPI_RES_HIGH;
// 延時200ms
Delay_us(200);
WriteCmd(0xAE); //display off
WriteCmd(0x20); //Set Memory Addressing Mode
WriteCmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
WriteCmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7
WriteCmd(0xc8); //Set COM Output Scan Direction
WriteCmd(0x00); //---set low column address
WriteCmd(0x10); //---set high column address
WriteCmd(0x40); //--set start line address
WriteCmd(0x81); //--set contrast control register
WriteCmd(0xff); //亮度調(diào)節(jié) 0x00~0xff
WriteCmd(0xa1); //--set segment re-map 0 to 127
WriteCmd(0xa6); //--set normal display
WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
WriteCmd(0x3F); //
WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
WriteCmd(0xd3); //-set display offset
WriteCmd(0x00); //-not offset
WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
WriteCmd(0xf0); //--set divide ratio
WriteCmd(0xd9); //--set pre-charge period
WriteCmd(0x22); //
WriteCmd(0xda); //--set com pins hardware configuration
WriteCmd(0x12);
WriteCmd(0xdb); //--set vcomh
WriteCmd(0x20); //0x20,0.77xVcc
WriteCmd(0x8d); //--set DC-DC enable
WriteCmd(0x14); //
WriteCmd(0xaf); //--turn on oled panel
}
void WriteCmd(uint8_t SPI_Command) {
OLED_SPI_DC_LOW;
OLED_SPI_Write(SPI_Command);
}
void WriteData(uint8_t SPI_Data) {
OLED_SPI_DC_HIGH;
OLED_SPI_Write(SPI_Data);
}
// 打開OLED
void OLED_ON(void) {
WriteCmd(0x8D); // 設置電荷泵
WriteCmd(0x14); // 開啟電荷泵
WriteCmd(0xAF); // OLED喚醒
}
// 關閉OLED
void OLED_OFF(void) {
WriteCmd(0x8D); // 設置電荷泵
WriteCmd(0x10); // 關閉電荷泵
WriteCmd(0xAE); // 關閉OLED
}
// 設置起始坐標
void SetPos(uint8_t x, uint8_t y) {
// 設置頁地址
WriteCmd(0xb0 + y);
// 取列高位
WriteCmd((x & 0xf0)>>4 | 0x10);
// 取列低位
WriteCmd((x & 0x0f) | 0x01);
}
// 全屏填充
void OLED_Fill(uint8_t Fill_Data) {
uint8_t m, n;
// 設置起始地址
for (m = 0; m < 8; m++) {
WriteCmd(0xb0 + m);
WriteCmd(0x00);
WriteCmd(0x10);
}
// 填充數(shù)據(jù)
for (n = 0; n < 128; n++) {
WriteData(Fill_Data);
}
}
// 清屏
void OLED_Clean(void) {
OLED_Fill(0x00);
}
// 顯示字符串
void OLED_ShowStr(uint8_t x, uint8_t y, char ch[], uint8_t TextSize) {
uint8_t c = 0, i =0, j = 0;
switch(TextSize) {
// 模式1:6x8點陣
// 6列 1組
case 1: {
while(ch[j] != '\0') {
c = ch[j] - 32;
if (x > 126) {
x = 0;
y++;
}
SetPos(x, y);
for (i = 0; i < 6; i++) {
WriteData(F6x8[c][i]);
}
x += 6;
j++;
}
}break;
// 模式2: 8x16點陣 兩頁
case 2: {
while(ch[j] != '\0') {
c = ch[j] - 32; // 字符偏移量,字庫上的字體序號和ASCII碼表上相差32
if (x > 120) {
x = 0;
y++;
}
SetPos(x, y);
for (i = 0; i < 8; i++) {
WriteData(F8X16[c*16+i]);
}
SetPos(x,y+1);
for(i=0;i<8;i++) {
WriteData(F8X16[c*16+i+8]);
}
x += 8;
j++;
}
}break;
}
}
對于OLED模塊中的相關函數(shù),重點介紹一下OLED_ShowStr(uint8_t x, uint8_t y, char ch[], uint8_t TextSize),這是官方提供的函數(shù)。
對于OLED_ShowStr函數(shù),它的參數(shù)x, y代表在屏幕中進行寫操作時的起始坐標值,如果我們選擇的是6x8模式,也就是6列,8行來表示一個字符,那么0 ~ 127中,最大能整除6的數(shù)字是126,即x的最大值是125(包括0),當x大于126時,便會開啟下一頁的寫操作,如下:
if (x > 126) {
x = 0;
y++;
}
那么問題來了,如何理解下面的表達式:
c = ch[j] - 32;
比如要想顯示A字母,A字母的ASCII碼值是65,那么65 - 32 = 33,33就對應著字庫(注:字庫文件在codetab.h文件中定義,因為篇幅原因,我就不貼出來了,大家可以通過淘寶廠家提供的百度網(wǎng)盤鏈接找到)中A字母的編號33,那么我們可以通過找到A的編號,從而找到它的編碼,從而在屏幕中打印出來。
以此類推,F(xiàn)8X16是一個數(shù)組,每個元素是一個8位的char類型,字庫中將其分行,每行有16個字符,A在33行,所以,33*16能取到A的第一個編碼值,然后按順序,從1 ~ 16,一列列地將A字母從屏幕上打印出來。
#include "stm32f10x.h"
#include "led.h"
#include "bsp_systick.h"
#include "spi.h"
#include "oled.h"
int main() {
SysTick_init();
LED_Init();
OLED_Init();
OLED_Clean();
OLED_ShowStr(0, 1, "hello world", 1);
OLED_ShowStr(0, 2, "hello world", 2);
while(1) {
}
}
c = ch[j] - 32;
比如要想顯示A字母,A字母的ASCII碼值是65,那么65 - 32 = 33,33就對應著字庫(注:字庫文件在codetab.h文件中定義,因為篇幅原因,我就不貼出來了,大家可以通過淘寶廠家提供的百度網(wǎng)盤鏈接找到)中A字母的編號33,那么我們可以通過找到A的編號,從而找到它的編碼,從而在屏幕中打印出來。文章來源:http://www.zghlxwxcb.cn/news/detail-627896.html
以此類推,F(xiàn)8X16是一個數(shù)組,每個元素是一個8位的char類型,字庫中將其分行,每行有16個字符,A在33行,所以,33*16能取到A的第一個編碼值,然后按順序,從1 ~ 16,一列列地將A字母在屏幕上打印出來。文章來源地址http://www.zghlxwxcb.cn/news/detail-627896.html
#include "stm32f10x.h"
#include "led.h"
#include "bsp_systick.h"
#include "spi.h"
#include "oled.h"
int main() {
SysTick_init();
LED_Init();
OLED_Init();
OLED_Clean();
OLED_ShowStr(0, 1, "hello world", 1);
OLED_ShowStr(0, 2, "hello world", 2);
while(1) {
}
}
到了這里,關于STM32F103ZET6 驅(qū)動 OLED的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!