OLED显示屏
OLED应用于各种大小显示需求, 大到电视屏幕, 小到微型智能穿戴设备的显示器都是它的用武之地. 在各种照明条件下它都能熠熠生辉, 且消耗的电流很小!
模块名为OLED 0.96 IIC 128x64,0.96指的是屏幕的显示尺寸0.96inch, 128×64指的是屏幕的分辨率为128×64, 而IIC指的是该模块使用IIC协议进行通讯, (关于Arduino-IIC协议可参考Arduino-Wire)
以下是OLED 0.96 12864屏幕的基本介绍
- 高分辨率:128×64(和12864同分辨率,高PPI)
- 超大可视角度:大于160°(显示屏中可视角度最大的一种屏幕)
- 超低功耗:正常显示0.06w(远低于TFT显示屏)
- 宽电压供电(3V~5V),兼容3.3V和5V电平逻辑,无需电平转换芯片
- IIC接口只需2个IO轻松点亮
- 工作温度范围为工业级(-20℃~70℃)
- 军工级工艺标准,长期稳定工作
- 提供丰富的多平台例程,提供底层驱动技术支持
- 黄蓝、白、蓝三种颜色显示方案可选

请大家在屏幕建立一个坐标系的概念,因为在程序里,位置都是以坐标的形式去定位的,面向屏幕,以屏幕左上角为坐标原点,横向向右是X轴,竖向向下是Y轴
参考资料:OLED,正在快速发展的新技术
有机发光二极管(英语:Organic Light-Emitting Diode,缩写:OLED)又称有机电激发光显示(英语:Organic Electroluminescence Display,缩写:OELD)、有机发光半导体,OLED技术最早于1950年代和1960年代由法国人和美国人研究,其后由美国柯达及英国剑桥大学加以演进,日本SONY及韩国三星和LG等公司于21世纪开始量产。
OLED(有机发光二极管)与TFT-LCD(薄膜晶体管液晶显示器)为不同类型的产品,前者具有自发光性、广视角、高对比、低耗电、高反应速率、全彩化及制程简单等优点,但相对的在大面板价格、技术选择性 、寿命、分辨率、色彩还原方面便无法与后者匹敌,有机发光二极管显示器可分单色、多彩及全彩等种类,而其中以全彩制作技术最为困难。
越来越多的手机使用OLED屏幕
实例8:点亮OLED屏幕
【实验说明】
使用OLED屏幕、面包板连接线,完成OLED屏幕的点亮,并运行一个示例程序
【材料准备】
OLED屏幕模块、面包板、UNO板、面包线连接线
【马上行动】
1.硬件连接
- VCC:电源正极(接5V电源)
- GND:电源负极(接地)
- SCL:IIC时钟信号线,接A5
- SDA:IIC数据信号线,接A4
VCC接到开发板的5V电源引脚上,GND接到开发板GND引脚上,SCL和SDA需要根据不同的开发板引脚定义来接线,该OLED模块的IIC地址为0x3C
2.拓展库下载与安装

我们使用Adafruit_SSD1306库来更加快捷高效的实现Arduino控制OLED, 在使用这个库时,需要依赖Adafruit-GFX-Library库才能使其正常工作,因可以通过Arduino自带的库管理器来安装,在安装时,如果出现以下情况, 点击Install all即可。

分别搜索:
Adafruit_GFX
Adafruit_SSD1306
3.编译示例程序
/**********************************************************************程序名称/Program name : words_display团队/Team : 太极创客团队 / Taichi-Maker (www.taichi-maker.com)作者/Author : Dapenson日期/Date(YYYYMMDD) : 2020/07/01程序目的/Purpose :使用OLED0.96 IIC 12864显示文字-----------------------------------------------------------------------修订历史/Revision History日期/Date 作者/Author 参考号/Ref 修订说明/Revision Description2021/03/22 金陵中学 1.0 金陵中学Arduino选修课使用-----------------------------------------------------------------------其它说明:***********************************************************************/// 引入IIC通讯所需的Wire库文件#include <Wire.h>// 引入驱动OLED0.96所需的库#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>#define SCREEN_WIDTH 128 // 设置OLED宽度,单位:像素#define SCREEN_HEIGHT 64 // 设置OLED高度,单位:像素// 自定义重置引脚,虽然教程未使用,但却是Adafruit_SSD1306库文件所必需的#define OLED_RESET 4Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);void setup(){// 初始化Wire库// Wire.begin();// 初始化OLED并设置其IIC地址为 0x3Cdisplay.begin(SSD1306_SWITCHCAPVCC, 0x3C);}void loop(){words_display();display.display();}void words_display(){// 清除屏幕display.clearDisplay();// 设置字体颜色,白色可见display.setTextColor(WHITE);//设置字体大小display.setTextSize(1.5);//设置光标位置display.setCursor(0, 0);display.print("JinLing High School");display.setCursor(0, 20);display.print("time: ");//打印自开发板重置以来的秒数:display.print(millis() / 1000);display.print(" s");display.setCursor(0, 40);display.print("Author: ");display.print("Your Name");}
汉字的显示需要对文字进行取模操作,紧接着使用drawBitmap()函数对取模生成的数组进行显示
实例9:OLED显示汉字
【实验说明】
使用OLED屏幕、面包板连接线,完成OLED屏幕的点亮,并运行一个贪吃蛇示例程序
【材料准备】
OLED屏幕模块、面包板、UNO板、面包线连接线
【马上行动】
1.操作取模软件
1)切换到字符模式
取模软件下载地址(内网可用):
http://192.168.80.131:8889/wl/?id=HlKb2tisNWZjpHHyMQVgdXtZahfxaMu4
OLED0.96文字取模Arduino
2)在菜单栏区设置字体和尺寸选择
3)字模选项设置,设置之后点击确定按钮
4)输入字符,点击生成子模, 生成之后需要对生成的数据进行变量赋值和加工,具体格式参考示例程序
2.编写程序
hans_display.ino
/**********************************************************************程序名称/Program name : hans_display团队/Team : 太极创客团队 / Taichi-Maker (www.taichi-maker.com)作者/Author : Dapenson日期/Date(YYYYMMDD) : 2020/07/01程序目的/Purpose :使用OLED0.96 IIC 12864显示汉字-----------------------------------------------------------------------修订历史/Revision History日期/Date 作者/Author 参考号/Ref 修订说明/Revision Description2021/03/22 金陵中学 1.0 金陵中学Arduino选修课使用-----------------------------------------------------------------------其它说明:***********************************************************************/// 引入IIC通讯所需的Wire库文件// 教程参考http://www.taichi-maker.com/homepage/reference-index/arduino-library-index/wire-library/#include <Wire.h>#include "text.h"//新建的子模文本// 引入驱动OLED0.96所需的库#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>#define SCREEN_WIDTH 128 // 设置OLED宽度,单位:像素#define SCREEN_HEIGHT 64 // 设置OLED高度,单位:像素// 自定义重置引脚,虽然教程未使用,但却是Adafruit_SSD1306库文件所必需的#define OLED_RESET 4Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);void setup(){// 初始化OLED并设置其IIC地址为 0x3Cdisplay.begin(SSD1306_SWITCHCAPVCC, 0x3C);}void loop(){hans_display_0();hans_display_1();}void hans_display_0(void){// 显示之前清屏display.clearDisplay();// 显示文字 (左上角x坐标,左上角y坐标, 图形数组, 图形宽度像素点, 图形高度像素点, 设置颜色)display.drawBitmap(20 * 1, 16, hans_jin, 16, 16, 1);display.drawBitmap(20 * 2, 16, hans_ling, 16, 16, 1);display.drawBitmap(20 * 3, 16, hans_zhong, 16, 16, 1);display.drawBitmap(20 * 4, 16, hans_xue, 16, 16, 1);//显示图形display.display();delay(2000);}void hans_display_1(void){// 显示之前清屏display.clearDisplay();// 显示文字 (左上角x坐标,右上角y坐标, 图形数组, 图形宽度像素点, 图形高度像素点, 设置颜色)display.drawBitmap(20 * 1, 16, hans_jin1, 16, 16, 1);display.drawBitmap(20 * 2, 16, hans_ling1, 16, 16, 1);display.drawBitmap(20 * 3, 16, hans_zhong1, 16, 16, 1);display.drawBitmap(20 * 4, 16, hans_xue1, 16, 16, 1);//显示图形display.display();delay(2000);}
新建一个标签,并重命名为text.h

text.h
static const unsigned char PROGMEM hans_jin[] = {0x01,0x00,0x01,0x00,0x02,0x80,0x04,0x40,0x08,0x20,0x10,0x10,0x2F,0xE8,0xC1,0x06,0x01,0x00,0x3F,0xF8,0x01,0x00,0x11,0x10,0x09,0x10,0x09,0x20,0xFF,0xFE,0x00,0x00,/*"金",0*/};static const unsigned char PROGMEM hans_ling[] = {0x00,0x20,0x78,0x20,0x49,0xFC,0x50,0x20,0x50,0x20,0x63,0xFE,0x50,0x88,0x49,0x44,0x4A,0x42,0x48,0xF8,0x69,0x88,0x52,0x50,0x40,0x20,0x40,0x50,0x41,0x88,0x46,0x06,/*"陵",1*//* (16 X 16 , 宋体 )*/};static const unsigned char PROGMEM hans_zhong[] = {0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x3F,0xF8,0x21,0x08,0x21,0x08,0x21,0x08,0x21,0x08,0x21,0x08,0x3F,0xF8,0x21,0x08,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,/*"中",2*//* (16 X 16 , 宋体 )*/};static const unsigned char PROGMEM hans_xue[] = {0x22,0x08,0x11,0x08,0x11,0x10,0x00,0x20,0x7F,0xFE,0x40,0x02,0x80,0x04,0x1F,0xE0,0x00,0x40,0x01,0x80,0xFF,0xFE,0x01,0x00,0x01,0x00,0x01,0x00,0x05,0x00,0x02,0x00,/*"学",3*//* (16 X 16 , 宋体 )*/};static const unsigned char PROGMEM hans_jin1[] = {0x00,0x00,0x00,0x00,0x01,0x80,0x06,0x60,0x08,0x10,0x70,0x0C,0x1F,0xFA,0x01,0x00,0x01,0x00,0x3F,0xFC,0x11,0x08,0x11,0x08,0x09,0x10,0x09,0x10,0x09,0x10,0x7F,0xFE,/*"金",0*//* (16 X 16 , 幼圆 )*/};static const unsigned char PROGMEM hans_ling1[] = {0x00,0x00,0x00,0x40,0x78,0x40,0x4F,0xFE,0x48,0x40,0x50,0x40,0x5F,0xFE,0x51,0x88,0x51,0x04,0x4A,0x82,0x4D,0xFC,0x4B,0x04,0x74,0x88,0x48,0x50,0x40,0x70,0x4F,0x8C,/*"陵",1*//* (16 X 16 , 幼圆 )*/};static const unsigned char PROGMEM hans_zhong1[] = {0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x3F,0xFC,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x3F,0xFC,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,/*"中",2*//* (16 X 16 , 幼圆 )*/};static const unsigned char PROGMEM hans_xue1[] = {0x00,0x00,0x00,0x00,0x21,0x08,0x11,0x08,0x08,0x90,0x77,0x6E,0x40,0x02,0x5F,0xF2,0x00,0x20,0x00,0xC0,0x00,0x80,0x7F,0xFE,0x00,0x40,0x00,0x40,0x00,0x40,0x0C,0xC0,/*"学",3*//* (16 X 16 , 幼圆 )*/};
参考资料:汉字的编码
计算机内部处理的信息,都是用二进制代码表示的,汉字也不例外。而二进制代码使用起来是不方便的,于是需要采用信息交换码。但汉字进入计算机,有许多困难,其原因主要有三点:
- 数量庞大:一般认为,汉字总数已超过6万个(包括简化字)。虽有研究者主张规定3000多或4000字作为当代通用汉字,但仍比处理由二三十个字母组成的拼音文字要困难得多。
- 字形复杂:有古体今体,繁体简体,正体异体;而且笔画相差悬殊,少的一笔,多的达36笔,简化后平均为9.8笔。
- 存在大量一音多字和一字多音的现象:汉语音节416个,分声调后为1295个(根据《现代汉语词典》统计,轻声39个未计)。以1万个汉字计算,每个不带调的音节平均超过24个汉字,每个带调音节平均超过7.7个汉字。有的同音同调字多达66个。一字多音现象也很普遍。
因此,我国1981年制定了中华人民共和国国家标准GB2312—80《信息交换用汉字编码字符集—基本集》,即国标码。而区位码是国标码的另一种表现形式,把国标GB2312—80中的汉字、图形符号组成一个94×94的方阵,分为94个“区”,每区包含94个“位”,其中“区”的序号由01至94,“位”的序号也是从01至94。94个区中位置总数=94×94=8836个,其中7445个汉字和图形字符中的每一个占一个位置后,还剩下1391个空位,这1391个位置空下来保留备用。
参考资料:数组
前面生成的十六进制代码组合在一起,成为数组。数组是连续的一组相同类型的内存位置。要引用数组中的特定位置或元素,我们指定数组的名称和数组中特定元素的位置编号。
下图给出了一个名为C的整数数组,它包含11个元素。通过给出数组名称,后面跟特定元素的位置编号:方括号([]),你可以引用这些元素中的任何一个。位置编号更正式地称为下标或索引(该数字指定从数组开始的元素数)。第一个元素具有下标0(零),有时称为零元素。
因此,数组C的元素是C[0],C[1],C[2]等等。数组C中的最高下标是10,其比数组中的元素数少1。数组名遵循与其他变量名相同的约定。

下标必须是整数或整数表达式(使用任何整数类型)。如果程序使用表达式作为下标,则程序评估表达式以确定下标。例如,如果我们假设变量a等于5,变量b等于6,那么语句将数组元素C[11]加2。
下标数组名是一个左值,它可以在赋值的左侧使用,就像非数组变量名一样。
让我们更仔细地检查给定图中的数组C。整个数组的名称是C。它的11个元素被称为C[0]到C[10]。C[0]的值为-45,C[1]的值为6,C[2]的值为0,C[7]的值为62,C[10]的值为78。
要打印数组C的前三个元素中包含的值的总和,我们将写:
Serial.print (C[ 0 ] + C[ 1 ] + C[ 2 ] );
要将C[6]的值除以2并将结果赋值给变量x,我们将写:
x = C[ 6 ] / 2;
声明数组
数组占用内存中的空间。要指定元素的类型和数组所需的元素数量,请使用以下形式的声明:
type arrayName [ arraySize ] ;
编译器保留适当的内存量(回想一下,保留内存的声明更恰当地被称为定义)。arraySize必须是大于零的整数常量。例如,要告诉编译器为整数数组C保留11个元素,请使用声明:
int C[ 12 ]; // C is an array of 12 integers
实例10:按钮+OLED的贪吃蛇小游戏
【实验说明】
使用OLED屏幕、面包板连接线,运行一个小游戏
【材料准备】
OLED屏幕模块、面包板、UNO板、面包线连接线、按钮2只、1k电阻2只
【马上行动】
1.硬件连接
2.编译示例程序
#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>//DISPLAY THINGS#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)#define OLED_ADDRESS 0x3C // I2C address of the display.#define SCREEN_WIDTH 128 // OLED display width, in pixels#define SCREEN_HEIGHT 64 // OLED display height, in pixels//BUTTON THINGS#define LEFT_B_IN A0#define RIGHT_B_IN A1//GAME OPTIONS#define WIN_POINTS 20#define CYCLE_INTERVAL 500#define BUTTON_INTERVAL 400unsigned long previousTime = 0;//---------DISPLAY STUFF---------// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);//Draws a square on the 21x10 board//(A 128x64 board reduced to 126x60, each element is 6x6)//x is between 0 and 20 inclusive//y is between 0 and 9 inclusive//thing: 0 = erase, 1 = snake, 2 = food//Could've used a switch statement here...void drawSquare(byte x, byte y, byte thing){if (thing == 1){display.fillRect(6*x+2,6*y+3,4,4,WHITE);return;}if (thing == 2){display.drawRoundRect(6*x+2,6*y+3,4,4,1,WHITE);return;}display.fillRect(6*x+2,6*y+3,4,4,BLACK);}//---------SNAKE STUFF---------//Coordinate struct//With the size of the game board (21x10), you could technically shrink it to//1 byte, but I don't quite know how to do that yet.typedef struct{byte x;byte y;} coord;//THE SNAKE//#Apparently snake[] took up so much space that it interfered with the OLED//#Keep it a reasonable size.coord snake[100];byte snakeLength = 2;short directions[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};short dirIndex = 0;coord foodCoord;//Initializes the snake with an initial length of 2//and initial direction right.void makeSnake(){snakeLength = 2;snake[0] = {1, (byte) random(0,10)};snake[1] = {0, snake[0].y};drawSquare(snake[0].x,snake[0].y,1);drawSquare(snake[1].x,snake[1].y,1);dirIndex = 0;}//Modify direction according to button pressvoid redirect(){unsigned long tempTime = millis();bool R = false;bool L = false;//Listen for button presseswhile (millis()-tempTime < BUTTON_INTERVAL){if (digitalRead(LEFT_B_IN)){L = true;}if (digitalRead(RIGHT_B_IN)){R = true;}}//Ignore double presses and non pressesif (R == L){return;}//If right, increment direction indexif (R){dirIndex++;if (dirIndex > 3){dirIndex = 0;}return;}//If left, decrement direction indexdirIndex--;if (dirIndex < 0){dirIndex = 3;}}//Moves the snakebool moveSnake(){//Calculate the new coordinatesint x = snake[0].x+directions[dirIndex][0];int y = snake[0].y+directions[dirIndex][1];//If out of bounds, exit and lose.if (x > 20 || x < 0 || y > 9 || y < 0){return 1;}coord newHead = {byte(x),byte(y)};//Draw the new headdrawSquare(newHead.x,newHead.y,1);//Did we land on food? / Does the new head line up with the food location?bool onFood = (newHead.x == foodCoord.x && newHead.y == foodCoord.y);//Shift all the snake coords back to make space for the headfor (int i = snakeLength; i != 0; --i){//If the new head contacts any snake coord, exit and loseif (!onFood && newHead.x == snake[i].x && newHead.y == snake[i].y){return 1;}snake[i] = snake[i-1];}//If nothing wrong, set the new head of the snake.snake[0] = newHead;//If no food, erase tailif (!onFood){drawSquare(snake[snakeLength].x,snake[snakeLength].y,0);}//Else dont erase tail, increment length of snake,//and put a new foodelse{snakeLength++;putFood();}return 0;}//Puts a new piece of food on the game board.void putFood(){bool foodOkay = false;//Make sure the food doesnt fall on top of the snakewhile (!foodOkay){foodCoord = {byte(random(0,21)),byte(random(0,10))};foodOkay = true;for (byte i = 0; i < snakeLength; ++i){if (foodCoord.y == snake[i].y && foodCoord.x == snake[i].x){foodOkay = false;break;}}}drawSquare(foodCoord.x,foodCoord.y,2);}void setup(){//Serial.begin(9600);// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internallyif(!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {//Serial.println(F("Oh no"));for(;;);}//Random numbersrandomSeed(analogRead(7));//Set up the buttons//Left buttonpinMode(LEFT_B_IN, INPUT);//Right buttonpinMode(RIGHT_B_IN, INPUT);//Set up the title screendisplay.clearDisplay();display.setTextSize(3);display.setTextColor(WHITE);display.setCursor(20,5);display.println(F("SNAKE"));display.setTextSize(1);display.setCursor(26,40);display.println(F("Hit L to play"));}//Game loopvoid loop() {display.display();//Wait for user inputwhile (!digitalRead(LEFT_B_IN)){}//GAME SETUP//Set up bordersdisplay.clearDisplay();display.fillRect(0,0,128,2,WHITE);display.fillRect(0,62,128,2,WHITE);display.fillRect(0,0,1,64,WHITE);display.fillRect(127,0,1,64,WHITE);//Make the snake and place the foodmakeSnake();putFood();display.display();bool win = false;delay(800);//Start gamefor(;;){//Every cycleif (millis() - previousTime > CYCLE_INTERVAL){previousTime = millis();//Check for direction changeredirect();//Self contact/Out of bounds conditionif (moveSnake()){break;}if (snakeLength == WIN_POINTS+2){win = true;break;}display.display();}}if (win){display.clearDisplay();display.setTextSize(2);display.setTextColor(WHITE);display.setCursor(0,5);display.println(F("YOU WON :)"));}//Show lose screenelse{//Flash the screendisplay.invertDisplay(true);delay(400);display.invertDisplay(false);delay(400);display.invertDisplay(true);delay(400);display.invertDisplay(false);delay(400);//Loss textdisplay.clearDisplay();display.setTextSize(2);display.setTextColor(WHITE);display.setCursor(0,5);display.println(F("YOU LOST:("));}display.setTextSize(1);display.setCursor(0,30);display.print(F("Donuts Eaten: "));display.print(snakeLength-2);display.println();display.println();display.println(F("Hit L to play again"));}
练习(倒计时红绿灯)
能否给上一节课的红绿灯加上一个OLED显示屏,进行倒计时或者提示词?


