硬件IIC控制OLED屏幕

继上篇速成MSP432后,笔者开始调试使用IIC通信控制的OLED显示屏时,但遇到了极大的阻碍(被中断相关问题困扰),而在网络上发现几乎所有的实现代码都是采用GPIO模拟IIC的方案,所以笔者认为很有必要分享一下这篇开发经历

认识IIC通信

我们分为两个部分来学习:IIC标准和具体的在MSP432上的IIC实现

IIC标准

下图是IIC的总线拓扑,可以看出只有两根信号线:数据(SDA)和时钟(SCL)。可以有任意个设备挂在这对总线上,同时注意到有两个电阻上拉到Vcc,这是因为IIC的IO端口是开漏输出的(见下面具体实现),所以这也带来了IIC的两大优越特性:

  • 线与:同一线上所有设备都推高才为高电平,任一设备拉低就必为低
  • 热拔插:可以带电拔、插总线上的IIC设备

IIC总线拓扑

一次IIC通信的帧结构如下所示,总线上主机发出一个起始信号(’S’);然后发7位从机(Slave)地址和一位读写控制位(0写1读),接着主机释放数据线,等待从机应答;若收到应答,则继续发数据,并且每一字节数据后面都跟一个从机应答;通信最后,主机发停止信号。

注意:应答是相对的!若是主机发数据,则由从机发应答;若是从机发数据(相当于主机读数据),则由主机发应答信号。

IIC协议

那么上面说的几大信号:起始、应答、停止是什么意思呢?这就要用到IIC总线线与的特性并同时观察下图所示的IIC信号:

  • 释放总线:线与的特性就是所有设备都拉高,在总线上才体现高电平;所以释放总线就是所有的设备都给高信号,这时总线表现高电平,为空闲状态
  • 起始信号:在总线空闲情况下(都高电平),数据线(SDA)拉低
  • 应答信号:发送完一字节后,发送机释放总线,从机拉低
  • 结束信号:在时钟线高电平时,数据线发送一个上升沿。发送后总线回到空闲状态

这里注意观察到,在时钟线高电平时,下降沿表示起始、上升沿表示结束;所以在传输数据时,时钟线高电平时读数据,时钟线低电平时改变数据线电平

IIC信号

上面的IIC协议中从机地址为7位,在一些特殊的设备中,升级版的IIC设备支持10位地址,通信协议结构如下图:

10位地址

MSP432的IIC

认识完了IIC的通信,现在我们学习一波MSP432中IIC实例的硬件实现原理,下图为MSP432的IIC外设架构图:

MSP432IIC架构

底层还是很好理解的,就是两个移位器:

  • 接收移位器:输入为SDA,SCL为触发时钟源,收其8位了就放进接收缓存寄存器
  • 发送移位器:从发送缓存寄存器拿数据,SCL为触发时钟逐位输出到SDA

注意到发送移位器(Transmit Shift Register)的输出是通过一个开漏的MOS输出到SDA脚的,而接收移位器(Receive Shift Register)直接从SDA拿数据进逻辑门。所以,我们重新绘制IIC的多设备拓扑图,如下:

IIC多设备

至此,相信你对IIC总线理解已经非常深刻了,开始实战环节了!

上述选用MSP432的实现原理是为了方便理解,实际上理解后所有芯片都是通用的

OLED是啥

OLED显示屏(点亮后)就是下面这个样子,其实和普通的显示屏看起来也没啥区别。但是从物理底层上来说,OLED全称:有机电激光显示二极管(Organic Light-Emitting Diode),发光是驱动后单像素点自发光的现象,无需背光板(其他光源)。

OLED显示屏

当然,OLED其实只是个发光点阵,并不能直接被单片机使用IIC控制,所以一般通常是经过专门控制芯片驱动的,笔者买的这个模块是集成的SH1106方案,具体参数如下:

SH1106

这个片子的架构也很简单(虽然看起来很多),内部有一个132x64的RAM直接映射为OLED屏幕点阵数据,然后左上角是辅助电源,下半部分是通信接口,右上部分是OLED驱动。

SH1106架构

这个片子支持很多种和主机通信的接口:8080、SPI和我们选用的IIC。在IIC模式下,片子的配置如下,笔者选用的模块已经把各个引脚电平都拉好了,对外引出了SCL和SDA两个通信脚和电源脚。

SH1106IIC

这个片子的IIC读写协议倒是略有特色,为了区分设置命令和显示数据,加了控制字节(control byte)。下图描述的算是很清楚了,如果发送控制字节0x00意思就是后面发一串(Co=0)的命令(DC=0);如果控制字节是0xc0意思就是后面发一个数据(DC=1)并有后续控制字节(Co=1)。

SH1106协议

然后就是显存RAM和屏幕的映射关系与读写方式,如下图所示,这个132x64的点阵被分为了8个页(Page),每个页包括132x8bit数据,也就是相当于把屏幕分为了8行进行操作。

这样设计的好处在下面很快就会见到🎈使用命令设置页内的段顺序等,可以很容易实现屏幕滚动等操作而不进行很多计算

SH1106RAM

接下来,就让我们看看SH1106这个片子到底支持哪些控制命令。下表列出了手册中说明的所有命令,其中:列地址设置、启动显示、页地址设置命令是让屏幕显示内容的3个基础命令,设置显示起始行命令可以很容易实现滚动显示。每个命令具体的用途和位信息参见下表

SH1106命令

SH1106命令

控制OLED

接下来就到了实战调试的时候了,我们先把IIC发数据的代码测试好,然后对OLED(SH1106)进行操作

MSP432中断实现IIC连发

Ti官方发布的IIC接口有:

  • 初始化(主机模式):I2C_initMaster
  • 设置目标从机地址:I2C_setSlaveAddress
  • 设置通信模式:I2C_setMode
  • 模块启动:I2C_enableModule
  • 开始多字节数据发送:I2C_masterSendMultiByteStart
  • 发送下一个字节数据:I2C_masterSendMultiByteNext
  • 结束发送:I2C_masterSendMultiByteStop

NOTE:笔者在实际代码中,每个接口的调用前都加了MAP_前缀,这样是为了让编译器自动选择是调用函数版本的接口还是432内部ROM程序的接口(更快更轻量)。读者可以尝试打开(Ti驱动库)头文件rom_map.h查看详细实现

使用上述接口的情况下,我们可以编写如下的IIC连发函数,共有2个接口(初始化、发送)和1个硬件中断函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include "BSP.h"

#define OLED_IIC_ADDRESS 0x3c // OLED屏幕地址

/**
* @brief IIC配置常量
*/
static const eUSCI_I2C_MasterConfig i2cConfig =
{
EUSCI_B_I2C_CLOCKSOURCE_SMCLK, // SMCLK Clock Source
48000000, // SMCLK = 48MHz
EUSCI_B_I2C_SET_DATA_RATE_400KBPS, // Desired I2C Clock of 400khz
0, // No byte counter threshold
EUSCI_B_I2C_NO_AUTO_STOP // No Autostop
};

static uint32_t _OLED_IIC_sending = 0; // 剩余要发送的数据量
static uint8_t * _OLED_IIC_sendingPtr = 0; // 数据指针

/**
* @brief 初始化IIC设备
* @note
* @author 江榕煜
* @param None
* @ret None
*/
void IIC4OLED_init(void)
{
// P1.6 映射为IIC.SDA
// P1.7 映射为IIC.SCL
MAP_GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,
GPIO_PIN6 + GPIO_PIN7, GPIO_PRIMARY_MODULE_FUNCTION);

// IIC外设参数初始化配置
MAP_I2C_initMaster(EUSCI_B0_BASE, &i2cConfig);

// 设置(目标通信)从机地址
MAP_I2C_setSlaveAddress(EUSCI_B0_BASE, OLED_IIC_ADDRESS);
// 发送数据模式
MAP_I2C_setMode(EUSCI_B0_BASE, EUSCI_B_I2C_TRANSMIT_MODE);

// 使能IIC实例
MAP_I2C_enableModule(EUSCI_B0_BASE);

// 清楚中断标志位,防止误促发中断
MAP_I2C_clearInterruptFlag(EUSCI_B0_BASE,
EUSCI_B_I2C_TRANSMIT_INTERRUPT0 + EUSCI_B_I2C_NAK_INTERRUPT);
// 使能中断
MAP_I2C_enableInterrupt(EUSCI_B0_BASE,
EUSCI_B_I2C_TRANSMIT_INTERRUPT0 + EUSCI_B_I2C_NAK_INTERRUPT);
MAP_Interrupt_enableInterrupt(INT_EUSCIB0);
}

/**
* @brief 发送数据给OLED
* @note 使用中断连发数据
* 但是会阻塞等待上一次调用
* @author 江榕煜
* @param
* dataPtr(uint8_t *) 发送数据内存地址
* dataSize(uint32_t) 发送数据大小
* @ret None
*/
void IIC4OLED_send(uint8_t * dataPtr, uint32_t dataSize)
{
// 确保IIC是空闲的
// while (MAP_I2C_masterIsStopSent(EUSCI_B0_BASE) == EUSCI_B_I2C_SENDING_STOP);
while(_OLED_IIC_sending);

if(dataSize)
{
// 中断续发变量初始化
_OLED_IIC_sending = dataSize - 1;
_OLED_IIC_sendingPtr = dataPtr + 1;

// 启动多字节数据发送
MAP_I2C_masterSendMultiByteStart(EUSCI_B0_BASE, dataPtr[0]);
}
}

/**
* @brief IIC中断处理函数
*/
void EUSCIB0_IRQHandler(void)
{
// 中断状态
uint_fast16_t status;
status = MAP_I2C_getEnabledInterruptStatus(EUSCI_B0_BASE);

// 没接收到应答(ACK)信号
if (status & EUSCI_B_I2C_NAK_INTERRUPT)
{
// 清除中断
MAP_I2C_clearInterruptFlag(EUSCI_B0_BASE,EUSCI_B_I2C_NAK_INTERRUPT);

/* ---处理--- */
}

// 接收中断
if (status & EUSCI_B_I2C_RECEIVE_INTERRUPT0)
{
// 清除中断
MAP_I2C_clearInterruptFlag(EUSCI_B0_BASE,EUSCI_B_I2C_RECEIVE_INTERRUPT0);
// 失能中断
MAP_I2C_disableInterrupt(EUSCI_B0_BASE,
EUSCI_B_I2C_RECEIVE_INTERRUPT0);
}

// 发送完成中断
if(status & EUSCI_B_I2C_TRANSMIT_INTERRUPT0)
{
// 清除中断
MAP_I2C_clearInterruptFlag(EUSCI_B0_BASE,EUSCI_B_I2C_TRANSMIT_INTERRUPT0);

/* ---发送完成中断处理--- */
if(_OLED_IIC_sending == 0) // 发完了
{
_OLED_IIC_sendingPtr = 0; // 指针处理
MAP_I2C_masterSendMultiByteStop(EUSCI_B0_BASE); // 发送IIC结束信号
}
else if(_OLED_IIC_sending == 1) // 还有一个就发完了
{
MAP_I2C_masterSendMultiByteFinish(EUSCI_B0_BASE, // 发送完下一个信号自动跟上结束信号
*_OLED_IIC_sendingPtr);

_OLED_IIC_sendingPtr = 0; // 指针处理
_OLED_IIC_sending = 0;
}
else { // 还有很多数据待发
MAP_I2C_masterSendMultiByteNext(EUSCI_B0_BASE, // 放一个数据到发送寄存器
*_OLED_IIC_sendingPtr);
_OLED_IIC_sendingPtr++; // 指针迭代
_OLED_IIC_sending--; // 计数器自减
}
}

// 发送结束中断
if (status & EUSCI_B_I2C_STOP_INTERRUPT)
{
MAP_I2C_clearInterruptFlag(EUSCI_B0_BASE,EUSCI_B_I2C_STOP_INTERRUPT);
}
}

只要没有玄学问题,上代码读者可以直接抄走用,没有任何问题。不依赖开发环境只使用了Ti官方的接口。

OLED显示字符

现在我们回到OLED的手册上来,建议读者先通读SH1106的数据手册,大概对每个命令都有一定的了解。笔者将这个片子的所有命令做成了一个头文件OLED_SH1106.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* OLED_SH1106.h - The C head file of the OLED Screen(SH1106) driver
* NOTE: This file is based on C99
* Up to now,this file only has instructions.
*
* Copyright (c) 2020-2021, FOSH Project
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes mail
* 2021-04-30 江榕煜 first version 2373180028@qq.com
**/
#ifndef __OLED_SH1106_H__
#define __OLED_SH1106_H__

#include "stdint.h"

/* --- SH1106芯片内部指令集定义 --- */

#define SetColAddrLow_SH1106 0x00 // 后四位使用逻辑或|修改
#define SetColAddrHigh_SH1106 0x10 // 后四位使用逻辑或|修改

#define SetDCDCVol_SH1106 0x30 // 后两位使用逻辑或|修改

#define SetDispStarLine_SH1106 0x40 // 后6位使用逻辑或|修改

#define SetContrast_SH1106 0x8100 // 双字节结构:后一字节数据

#define SegRightRot_SH1106 0xa0 // 段地址右旋循环
#define SegLeftRot_SH1106 0xa1 // 段地址左旋循环

#define EntireDispOff_SH1106 0xa4 // 普通显示(禁用全显示测试功能)
#define EntireDispOn_SH1106 0xa5 // 全显示(测试功能)

#define ReverseDispOff_SH1106 0xa6 // 普通显示(不使用反色)
#define ReverseDispOn_SH1106 0xa7 // 反色显示

#define MultRatio_SH1106 0xa800 // 双字节结构:后6位使用逻辑或|修改

#define DisableDCDC_SH1106 0xad8a // 失能DCDC
#define EnableDCDC_SH1106 0xad8b // 使能DCDC

#define DispOff_SH1106 0xae // 关闭显示
#define DispOn_SH1106 0xaf // 启动显示

#define SetPageAddr_SH1106 0xb0 // 后4位使用逻辑或|修改

#define ScanDirPos_SH1106 0xc0 // 顺序扫描
#define ScanDirAnt_SH1106 0xc8 // 逆序扫描

#define SetDispOffset_SH1106 0xd300 // 双字节结构:后6位使用逻辑或|修改

#define SetDivAndFreq_SH1106 0xd5 // 双字节结构:4+4分别为分频率和振荡器频率
#define SetchargePeriod_SH1106 0xd9 // 双字节结构:4+4分别为放电周期和预充电周期

#define ComSigPadSeqt_SH1106 0xda02 // 引脚顺序
#define ComSigPadAltn_SH1106 0xda0a // 引脚逆序

#define SetVCOM_SH1106 0xdb00 // 双字节结构:后一字节数据

#define ReadModWrite_SH1106 0Xe0 // 启动“读写”组合模式,即一次读写指针跳一次
#define END_SH1106 0Xee // 结束“读写”组合模式
#define NOP_SH1106 0xe3 // 无操作命令

#define CommandNotTail_SH1106 0x80 // 发送一个指令,还有其他内容
#define DataNotTail_SH1106 0xc0 // 发送一个数据,还有其他内容
#define CommandAll_SH1106 0x00 // 接下来发送一堆命令
#define DataAll_SH1106 0x40 // 接下来发送一堆数据

/* --- 用户接口定义 ---*/

extern const unsigned char OLED_display_on[5];

extern const unsigned char OLED_display_off[4];

extern const unsigned char OLED_Front_Jiang[16*4+1];

extern const unsigned char OLED_Number1234567890[10][16*4];

extern const unsigned char OLED_Chinese[][32*4];

extern const unsigned char OLED_English[][16*4];
#endif

文件的末尾声明了几个数组,是使用字模软件提取了几个英文字母和中文字符的数据,放在配套的源文件OLED_SH1106.C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include "OLED_SH1106.h"

const unsigned char OLED_display_on[5] = {
CommandAll_SH1106,
SetDCDCVol_SH1106 | 0x02, // 设置DCDC电压
(EnableDCDC_SH1106>>8), // 启动DCDC
EnableDCDC_SH1106&0x0f,
DispOn_SH1106 // 使能显示
};

const unsigned char OLED_display_off[4] = {
CommandAll_SH1106,
(DisableDCDC_SH1106>>8), // 关闭DCDC
DisableDCDC_SH1106&0x0f,
DispOff_SH1106 // 失能显示
};

const unsigned char OLED_Chinese[][32*4] =
{ // 分辨率:32*32
{ //中文:电
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xF0,0xF0,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x0C,0xFC,0xEC,0x0C,0x84,0x84,0xC4,0xC4,0xFF,0x47,0x42,
0x42,0x42,0x02,0x02,0xC3,0xFF,0x7E,0x0E,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x18,0x08,0x08,0x08,0x08,0xFF,0x08,0x08,
0x08,0x0C,0x0C,0x0C,0x07,0x01,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x07,0x0C,
0x0C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x0C,0x0C,0x0F,0x06,0x00,0x00,0x00,0x00
},
{ //中文:压
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,
0xC0,0xC0,0xC0,0x40,0x60,0x60,0x60,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,
0x02,0xFE,0xFE,0x84,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0xC0,0xF0,0x3F,0x07,0x00,0x00,0x00,0x00,0x01,0x01,0x01,
0x01,0xFF,0xFF,0x01,0x01,0x01,0x0C,0x18,0xB8,0x80,0x80,0x80,0x00,0x00,0x00,0x00,
0x00,0x10,0x08,0x04,0x03,0x01,0x00,0x00,0x02,0x02,0x02,0x02,0x02,0x02,0x03,0x03,
0x03,0x03,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00
}
};

const unsigned char OLED_English[][16*4] =
{ // 分辨率:16 X 32
{ //英文冒号
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x0F,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00
},
{ //英文:V
0x40,0xC0,0xC0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xC0,0xC0,0x00,
0x00,0x03,0x3F,0xFF,0xF8,0xC0,0x00,0x00,0x00,0x00,0xE0,0xFE,0xFF,0x0F,0x01,0x00,
0x00,0x00,0x00,0x01,0x1F,0xFF,0xFC,0xC0,0xF0,0xFF,0x3F,0x07,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x0F,0x0F,0x03,0x00,0x00,0x00,0x00,0x00,0x00
}
};

const unsigned char OLED_Number1234567890[10][16*4] =
{
{
0x00,0x00,0x00,0x00, 0x80,0xC0,0xC0,0xC0,
0xC0,0xC0,0xC0,0x80, 0x00,0x00,0x00,0x00,
0x00,0xF0,0xFE,0xFF, 0x07,0x01,0x00,0x00,
0x00,0x00,0x03,0x0F, 0xFF,0xFC,0x00,0x00,
0x00,0x1F,0xFF,0xFF, 0xC0,0x00,0x00,0x00,
0x00,0x00,0x00,0xE0, 0xFF,0x7F,0x00,0x00,
0x00,0x00,0x00,0x03, 0x03,0x07,0x06,0x0E,
0x0E,0x06,0x07,0x03, 0x01,0x00,0x00,0x00
/* (16 X 32 , 楷体 )*/
},
{
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0xC0,
0xC0,0xC0,0x00,0x00, 0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x1C, 0x0C,0x06,0x03,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,0x00, 0x00,0x00,0x00,0x07,
0x07,0x07,0x00,0x00, 0x00,0x00,0x00,0x00
/* (16 X 32 , 楷体 )*/
},
{
0x00,0x00,0x00,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x80,0x80,0x00,0x00,0x00,
0x00,0x04,0x07,0x07,0x03,0x01,0x00,0x00,0x00,0x00,0xC1,0xFF,0xFF,0x1E,0x00,0x00,
0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1E,0x0F,0x03,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0x07,0x07,0x07,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x00,0x00
/* (16 X 32 , 楷体 )*/
},
{
0x00,0x00,0x00,0x00,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x80,0x80,0x00,0x00,0x00,
0x00,0x00,0x06,0x0F,0x03,0x01,0x80,0x80,0x80,0xC0,0xC1,0x7F,0x7F,0x1E,0x00,0x00,
0x00,0x40,0xC0,0xE0,0x80,0x00,0x01,0x01,0x01,0x03,0x03,0xDF,0xFE,0xFC,0x00,0x00,
0x00,0x00,0x01,0x03,0x07,0x07,0x06,0x0E,0x0E,0x06,0x07,0x03,0x03,0x00,0x00,0x00
/* (16 X 32 , 楷体 )*/
},
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xC0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xC0,0xE0,0x70,0x3C,0x1E,0x07,0xFF,0xFF,0x00,0x00,0x00,0x00,
0x70,0x78,0x7E,0x7F,0x73,0x71,0x70,0x70,0x70,0x70,0xFF,0xFF,0x70,0x70,0x70,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x00
/* (16 X 32 , 楷体 )*/
},
{
0x00,0x00,0x00,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x00,0x00,0x00,
0x00,0x80,0xF8,0xFF,0xCF,0x60,0x60,0x60,0x60,0xE0,0xC0,0xC0,0x80,0x00,0x00,0x00,
0x00,0xC1,0xE1,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xE7,0xFF,0xFE,0x00,0x00,
0x00,0x01,0x03,0x07,0x06,0x0E,0x0E,0x0E,0x0E,0x07,0x07,0x03,0x01,0x00,0x00,0x00
/* (16 X 32 , 楷体 )*/
},
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xC0,0xF0,0xF8,0xFE,0xCF,0xC7,0xC1,0xC0,0xC0,0x80,0x00,0x00,0x00,
0x00,0x7C,0xFF,0xFF,0x83,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0xFF,0xFE,0x00,
0x00,0x00,0x01,0x03,0x07,0x07,0x06,0x0E,0x0E,0x0E,0x06,0x07,0x03,0x01,0x00,0x00
/* (16 X 32 , 楷体 )*/
},
{
0x00,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xE0,0xF8,0x3E,0x0F,0x03,0x01,0x00,
0x00,0x00,0x00,0x00,0x00,0x80,0xF0,0xFE,0x3F,0x07,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x04,0x07,0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
/* (16 X 32 , 楷体 )*/
},
{
0x00,0x00,0x00,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x80,0x00,0x00,0x00,0x00,
0x00,0x00,0x7F,0x7F,0xE1,0xC0,0x80,0x80,0x80,0xC0,0xE1,0x7F,0x7F,0x1E,0x00,0x00,
0x00,0xFC,0xFE,0xCF,0x07,0x03,0x01,0x01,0x01,0x03,0x03,0x8F,0xFE,0xFC,0x00,0x00,
0x00,0x01,0x03,0x07,0x07,0x06,0x0E,0x0E,0x0E,0x06,0x07,0x07,0x03,0x01,0x00,0x00
/* (16 X 32 , 楷体 )*/
},
{
0x00,0x00,0x00,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x80,0x00,0x00,0x00,0x00,
0x00,0xFE,0xFF,0x87,0x01,0x01,0x00,0x00,0x00,0x01,0x83,0xFF,0xFF,0x7E,0x00,0x00,
0x00,0x01,0x03,0x07,0x07,0x06,0x86,0xE6,0xFE,0x7F,0x1F,0x07,0x03,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x08,0x0E,0x0F,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00
/* (16 X 32 , 楷体 )*/
}
};

注意上面定义了一个常量OLED_display_on,是点亮OLED的命令组合,上电后只要把里面的数据通过IIC发送出去,OLED就亮了。

1
2
// 启动显示
IIC4OLED_send((uint8_t *)OLED_display_on,sizeof(OLED_display_on));

这时候,理论上是可以看到屏幕亮起来,但是是一些乱数据,我们需要往上面写字符或者图像数据。上面的OLED源文件中,我们定义了几个分辨率为16X3232X32的中、英、数字字符,复习一下前面的显存RAM分页的设计特性,所以我们需要将每个字符分不同页不同部分进行写入,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* @brief 发送数据给OLED
* @note 使用中断连发数据
* 但是会阻塞等待上一次调用
* @author 江榕煜
* @param
* dataPtr(uint8_t *) 发送数据内存地址
* dataSize(uint32_t) 发送数据大小
* @ret None
*/
void IIC4OLED_sendRAM(uint8_t * dataPtr, uint32_t dataSize)
{
// 确保IIC是空闲的
// while (MAP_I2C_masterIsStopSent(EUSCI_B0_BASE) == EUSCI_B_I2C_SENDING_STOP);
while(_OLED_IIC_sending);
if(dataSize)
{
// 中断续发变量初始化
_OLED_IIC_sending = dataSize;
_OLED_IIC_sendingPtr = dataPtr;

// 启动多字节数据发送
MAP_I2C_masterSendMultiByteStart(EUSCI_B0_BASE, DataAll_SH1106);

}
}

/**
* @brief 在指定位置写数字
* @note 字体为32*16
* @author 江榕煜
* @param
* posX(uint16_t) X位置
* posY(uint16_t) Y位置
* data(uint8_t *) 字体数据
* @ret
* None
*/
void OLED_writeChar3216(uint16_t posX, uint16_t posY, uint8_t * data)
{
unsigned char _setPos[4]; // CommandAll_SH1106+设置页+设置列高4位+设置列低四位
_setPos[0]= CommandAll_SH1106;
_setPos[1]= (posX & 0x0f) | SetColAddrLow_SH1106;
_setPos[2]= (posX >> 4) &0x0f | SetColAddrHigh_SH1106;
_setPos[3]= (posY / 8) &0x0f | SetPageAddr_SH1106;

IIC4OLED_send(_setPos,4);
IIC4OLED_sendRAM(data,16);

_setPos[3]= _setPos[3] +1;
IIC4OLED_send(_setPos,4);
IIC4OLED_sendRAM(data+16,16);

_setPos[3]= _setPos[3] +1;
IIC4OLED_send(_setPos,4);
IIC4OLED_sendRAM(data+32,16);

_setPos[3]= _setPos[3] +1;
IIC4OLED_send(_setPos,4);
IIC4OLED_sendRAM(data+48,16);
}

/**
* @brief 在指定位置写数字
* @note 字体为32*32
* @author 江榕煜
* @param
* posX(uint16_t) X位置
* posY(uint16_t) Y位置
* data(uint8_t *) 字体数据
* @ret
* None
*/
void OLED_writeChar3232(uint16_t posX, uint16_t posY, uint8_t * data)
{
unsigned char _setPos[4]; // CommandAll_SH1106+设置页+设置列高4位+设置列低四位
_setPos[0]= CommandAll_SH1106;
_setPos[1]= (posX & 0x0f) | SetColAddrLow_SH1106;
_setPos[2]= (posX >> 4) &0x0f | SetColAddrHigh_SH1106;
_setPos[3]= (posY / 8) &0x0f | SetPageAddr_SH1106;

IIC4OLED_send(_setPos,4);
IIC4OLED_sendRAM(data,32);

_setPos[3]= _setPos[3] +1;
IIC4OLED_send(_setPos,4);
IIC4OLED_sendRAM(data+32,32);

_setPos[3]= _setPos[3] +1;
IIC4OLED_send(_setPos,4);
IIC4OLED_sendRAM(data+64,32);

_setPos[3]= _setPos[3] +1;
IIC4OLED_send(_setPos,4);
IIC4OLED_sendRAM(data+96,32);
}

如果读者想要显示图片等,可以根据页关系,修改上代码。

有任何问题欢迎联系笔者交流

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022-2024 RY.J
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信