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 Description
2021/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 4
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup()
{
// 初始化Wire库
// Wire.begin();
// 初始化OLED并设置其IIC地址为 0x3C
display.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 Description
2021/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 4
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup()
{
// 初始化OLED并设置其IIC地址为 0x3C
display.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 400
unsigned 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 press
void redirect()
{
unsigned long tempTime = millis();
bool R = false;
bool L = false;
//Listen for button presses
while (millis()-tempTime < BUTTON_INTERVAL)
{
if (digitalRead(LEFT_B_IN)){L = true;}
if (digitalRead(RIGHT_B_IN)){R = true;}
}
//Ignore double presses and non presses
if (R == L){
return;
}
//If right, increment direction index
if (R){
dirIndex++;
if (dirIndex > 3){dirIndex = 0;}
return;
}
//If left, decrement direction index
dirIndex--;
if (dirIndex < 0){dirIndex = 3;}
}
//Moves the snake
bool moveSnake()
{
//Calculate the new coordinates
int 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 head
drawSquare(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 head
for (int i = snakeLength; i != 0; --i)
{
//If the new head contacts any snake coord, exit and lose
if (!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 tail
if (!onFood)
{
drawSquare(snake[snakeLength].x,snake[snakeLength].y,0);
}
//Else dont erase tail, increment length of snake,
//and put a new food
else
{
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 snake
while (!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 internally
if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
//Serial.println(F("Oh no"));
for(;;);
}
//Random numbers
randomSeed(analogRead(7));
//Set up the buttons
//Left button
pinMode(LEFT_B_IN, INPUT);
//Right button
pinMode(RIGHT_B_IN, INPUT);
//Set up the title screen
display.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 loop
void loop() {
display.display();
//Wait for user input
while (!digitalRead(LEFT_B_IN)){}
//GAME SETUP
//Set up borders
display.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 food
makeSnake();
putFood();
display.display();
bool win = false;
delay(800);
//Start game
for(;;)
{
//Every cycle
if (millis() - previousTime > CYCLE_INTERVAL)
{
previousTime = millis();
//Check for direction change
redirect();
//Self contact/Out of bounds condition
if (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 screen
else
{
//Flash the screen
display.invertDisplay(true);
delay(400);
display.invertDisplay(false);
delay(400);
display.invertDisplay(true);
delay(400);
display.invertDisplay(false);
delay(400);
//Loss text
display.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显示屏,进行倒计时或者提示词?