一、oled屏幕
分辨率: 128 * 64
尺寸:0.96英寸
驱动芯片:ssd1306
电压:3.3~5V
功耗:0.06w
寿命:16000h
显示:双色(黄、蓝)
字库:无内置字库(软件控制显示)
通信: 4线spi/i2c(默认spi,使用i2c需调整相应屏后电阻)
# 针脚 GND ------- 接地 VCC ------- 3.3 ~ 5V D0 ------- 时钟线 D1 ------- 数据线 RES ------- 复位(低电平有效) DC ------- spi 通信 data/command 选择;i2c通信时设置i2c地址 cs ------- 片选(spi通信,低电平有效)
|
SPI通信
SPI(Serial Peripheral Interface) 串行外设接口。 以主从方式进行工作,一台master主机可以连接多台slave从机,通过CS片选可以指定选中的从机。通常为4线或3线(单向传输数据)
# 4wire SPI SCLK ------ 时钟 MOSI ------ Master Out Slave Input MISO ------ Maset Input Slave Out CS ------ 片选
|
二、连接raspberrypi
1.启用spi
找到advanced options 下的spi,选择启用
2.连接
三、点亮
在github上查找相关ssd1306的相关js驱动,找到一个可用的驱动,为防止丢失,已Fork https://github.com/yancoding/ssd1306-spi
index.js
const rpio = require("rpio");
class SSD1306 { constructor({ width, height, resetPin, dcPin, spiChip, rpio }) { this.EXTERNAL_VCC = 0x1 this.SWITCH_CAP_VCC = 0x2
this.SET_LOW_COLUMN = 0x00 this.SET_HIGH_COLUMN = 0x10 this.SET_MEMORY_MODE = 0x20 this.SET_COL_ADDRESS = 0x21 this.SET_PAGE_ADDRESS = 0x22 this.RIGHT_HORIZ_SCROLL = 0x26 this.LEFT_HORIZ_SCROLL = 0x27 this.VERT_AND_RIGHT_HORIZ_SCROLL = 0x29 this.VERT_AND_LEFT_HORIZ_SCROLL = 0x2A this.DEACTIVATE_SCROLL = 0x2E this.ACTIVATE_SCROLL = 0x2F this.SET_START_LINE = 0x40 this.SET_CONTRAST = 0x81 this.CHARGE_PUMP = 0x8D this.SEG_REMAP = 0xA0 this.SET_VERT_SCROLL_AREA = 0xA3 this.DISPLAY_ALL_ON_RESUME = 0xA4 this.DISPLAY_ALL_ON = 0xA5 this.NORMAL_DISPLAY = 0xA6 this.INVERT_DISPLAY = 0xA7 this.DISPLAY_OFF = 0xAE this.DISPLAY_ON = 0xAF this.COM_SCAN_INC = 0xC0 this.COM_SCAN_DEC = 0xC8 this.SET_DISPLAY_OFFSET = 0xD3 this.SET_COM_PINS = 0xDA this.SET_VCOM_DETECT = 0xDB this.SET_DISPLAY_CLOCK_DIV = 0xD5 this.SET_PRECHARGE = 0xD9 this.SET_MULTIPLEX = 0xA8
this.MEMORY_MODE_HORIZ = 0x00 this.MEMORY_MODE_VERT = 0x01 this.MEMORY_MODE_PAGE = 0x02
let options = { gpiomem: false, mapping: "gpio", ...rpio };
this._screenWidth = width; this._screenHeight = height; this._resetPin = resetPin; this._dcPin = dcPin; this._spiChip = spiChip;
this._rpioOptions = options;
this._screenBuffer = Buffer.alloc((width * height) / 8).fill(0x00); }
init() { rpio.init(this._rpioOptions); rpio.spiBegin(); rpio.spiChipSelect(this._spiChip); rpio.spiSetClockDivider(128);
rpio.open(this._resetPin, rpio.OUTPUT, rpio.HIGH); rpio.open(this._dcPin, rpio.OUTPUT, rpio.LOW);
rpio.msleep(0.01); this.reset(); this.command(Buffer.from([this.DISPLAY_OFF])); this.command(Buffer.from([this.SET_DISPLAY_CLOCK_DIV, 0x80]));
if (this._screenHeight === 64) { this.command(Buffer.from([this.SET_MULTIPLEX, 0x3F])); this.command(Buffer.from([this.SET_COM_PINS, 0x12])); } else { this.command(Buffer.from([this.SET_MULTIPLEX, 0x1F])); this.command(Buffer.from([this.SET_COM_PINS, 0x02])); }
this.command(Buffer.from([this.SET_DISPLAY_OFFSET, 0x00])); this.command(Buffer.from([this.SET_START_LINE | 0x00])); this.command(Buffer.from([this.CHARGE_PUMP, 0x14])); this.command(Buffer.from([this.SET_MEMORY_MODE, 0x00])); this.command(Buffer.from([this.SEG_REMAP | 0x01])); this.command(Buffer.from([this.COM_SCAN_DEC])); this.command(Buffer.from([this.SET_CONTRAST, 0x8f])); this.command(Buffer.from([this.SET_PRECHARGE, 0xf1])); this.command(Buffer.from([this.SET_VCOM_DETECT, 0x40])); this.command(Buffer.from([this.DISPLAY_ALL_ON_RESUME])); this.command(Buffer.from([this.NORMAL_DISPLAY])); this.command(Buffer.from([this.DISPLAY_ON])); }
clearDisplay() { this._screenBuffer.fill(0); }
invertDisplay() { this.command(Buffer.from([this.INVERT_DISPLAY])); }
normalDisplay() { this.command(Buffer.from([this.NORMAL_DISPLAY])); }
draw() { this.command(Buffer.from([this.SET_MEMORY_MODE, this.MEMORY_MODE_HORIZ])); this.command(Buffer.from([this.SET_PAGE_ADDRESS, 0x00, 0x07])); this.command(Buffer.from([this.SET_COL_ADDRESS, 0x00, 0x7f])); this.command(Buffer.from([this.DISPLAY_ON])); this.data(this._screenBuffer); }
drawPixel(x, y, on) { if (x < 0 || x > this._screenWidth - 1 || y < 0 || y > this._screenHeight - 1) { return; }
let page = Math.floor(y / 8); let offset = y % 8;
if (on) { this._screenBuffer[page * this._screenWidth + x] |= (0x1 << offset); } else { this._screenBuffer[page * this._screenWidth + x] &= ((0x1 << offset) ^ 0xff); } }
reset() { rpio.write(this._resetPin, rpio.LOW); rpio.msleep(10); rpio.write(this._resetPin, rpio.HIGH); }
command(buffer) { rpio.spiWrite(buffer, buffer.length); }
data(buffer) { rpio.write(this._dcPin, rpio.HIGH);
rpio.spiWrite(buffer, buffer.length);
rpio.write(this._dcPin, rpio.LOW); }
end() { rpio.spiEnd(); rpio.close(this._resetPin, rpio.PIN_RESET); rpio.close(this._dcPin, rpio.PIN_RESET); } }
module.exports = SSD1306;
|
使用该驱动
const SSD1306 = require('ssd1306') const rpio = require("rpio")
const ssd1306 = new SSD1306({ width: 128, height: 64, resetPin: 24, dcPin: 23, spiChip: 0, rpio, })
ssd1306.init()
for (let i = 1; i < 128; i++) { for (let j = 1; j < 64; j++) { ssd1306.drawPixel(i, j, true) ssd1306.draw() } }
|
如果可以依次点亮屏幕上的每一点,说明oled一切正常,可以尝试做些其他的事情了
四、一个像素点是如何点亮的?
其实oled与大多数的点阵屏幕点亮原理都很类似,具体还要看下使用手册
oled 水平共有64个共阴极,那么如果在某一像素点上加上高电平,即可点亮该点
128 * 64 共有64行,128列,64行在数据层分为8页,在第一页,也就是128 * 8的区域,如果在第一列写入的数据为0xff,即二进制的 11111111
,则会点亮第一页第一列的8个像素点,上为高位D0,下为低位D7。
所以屏幕的显存为128 * 8 byte,通过ssd1306的指令写入显存,即可显示我们要展示的内容
传统做法是对常用的字符生成不通大小的点阵数据,也就是所谓的字库,生成方式称为字体取模,这种方式有一定的局限性,我们可以考虑采用另一种方式来显示我们想要的任何图形
五、借助canvas绘图
oled屏幕的尺寸是固定的128 * 64尺寸,这点非常类似canvas的画布,我们因此可以将屏幕当成canvas的画布
首先创建一个屏幕大小的画布, 然后借助canvas的api可以绘制出我们想要的图案,再取出画布上的像素数据,转换为相应的数据格式,写入oled的显存,即可在屏幕上展示画布上的图案
注意这里由于屏幕每个点只有两个状态,亮或暗,因此我们在画布上绘制时,只使用黑色绘制即可
const rpio = require("rpio"); const { createCanvas, loadImage } = require('canvas')
class SSD1306 { constructor({ width, height, resetPin, dcPin, spiChip, rpio }) { this.EXTERNAL_VCC = 0x1 this.SWITCH_CAP_VCC = 0x2
this.SET_LOW_COLUMN = 0x00 this.SET_HIGH_COLUMN = 0x10 this.SET_MEMORY_MODE = 0x20 this.SET_COL_ADDRESS = 0x21 this.SET_PAGE_ADDRESS = 0x22 this.RIGHT_HORIZ_SCROLL = 0x26 this.LEFT_HORIZ_SCROLL = 0x27 this.VERT_AND_RIGHT_HORIZ_SCROLL = 0x29 this.VERT_AND_LEFT_HORIZ_SCROLL = 0x2A this.DEACTIVATE_SCROLL = 0x2E this.ACTIVATE_SCROLL = 0x2F this.SET_START_LINE = 0x40 this.SET_CONTRAST = 0x81 this.CHARGE_PUMP = 0x8D this.SEG_REMAP = 0xA0 this.SET_VERT_SCROLL_AREA = 0xA3 this.DISPLAY_ALL_ON_RESUME = 0xA4 this.DISPLAY_ALL_ON = 0xA5 this.NORMAL_DISPLAY = 0xA6 this.INVERT_DISPLAY = 0xA7 this.DISPLAY_OFF = 0xAE this.DISPLAY_ON = 0xAF this.COM_SCAN_INC = 0xC0 this.COM_SCAN_DEC = 0xC8 this.SET_DISPLAY_OFFSET = 0xD3 this.SET_COM_PINS = 0xDA this.SET_VCOM_DETECT = 0xDB this.SET_DISPLAY_CLOCK_DIV = 0xD5 this.SET_PRECHARGE = 0xD9 this.SET_MULTIPLEX = 0xA8
this.MEMORY_MODE_HORIZ = 0x00 this.MEMORY_MODE_VERT = 0x01 this.MEMORY_MODE_PAGE = 0x02
let options = { gpiomem: false, mapping: "gpio", ...rpio };
this._screenWidth = width; this._screenHeight = height; this._resetPin = resetPin; this._dcPin = dcPin; this._spiChip = spiChip;
this._rpioOptions = options;
this._screenBuffer = Buffer.alloc((width * height) / 8).fill(0x00);
this._ctx = createCanvas(128, 64).getContext('2d') }
init() { this._ctx.fillStyle = 'rgba(255, 255, 255, 1)' this._ctx.fillRect(0, 0, 128, 64)
rpio.init(this._rpioOptions); rpio.spiBegin(); rpio.spiChipSelect(this._spiChip); rpio.spiSetClockDivider(128);
rpio.open(this._resetPin, rpio.OUTPUT, rpio.HIGH); rpio.open(this._dcPin, rpio.OUTPUT, rpio.LOW);
rpio.msleep(0.01); this.reset(); this.command(Buffer.from([this.DISPLAY_OFF])); this.command(Buffer.from([this.SET_DISPLAY_CLOCK_DIV, 0x80]));
if (this._screenHeight === 64) { this.command(Buffer.from([this.SET_MULTIPLEX, 0x3F])); this.command(Buffer.from([this.SET_COM_PINS, 0x12])); } else { this.command(Buffer.from([this.SET_MULTIPLEX, 0x1F])); this.command(Buffer.from([this.SET_COM_PINS, 0x02])); }
this.command(Buffer.from([this.SET_DISPLAY_OFFSET, 0x00])); this.command(Buffer.from([this.SET_START_LINE | 0x00])); this.command(Buffer.from([this.CHARGE_PUMP, 0x14])); this.command(Buffer.from([this.SET_MEMORY_MODE, 0x00])); this.command(Buffer.from([this.SEG_REMAP | 0x01])); this.command(Buffer.from([this.COM_SCAN_DEC])); this.command(Buffer.from([this.SET_CONTRAST, 0x8f])); this.command(Buffer.from([this.SET_PRECHARGE, 0xf1])); this.command(Buffer.from([this.SET_VCOM_DETECT, 0x40])); this.command(Buffer.from([this.DISPLAY_ALL_ON_RESUME])); this.command(Buffer.from([this.NORMAL_DISPLAY])); this.command(Buffer.from([this.DISPLAY_ON])); }
getContext(contextID) { if (contextID === '2d') { return this._ctx } else { throw new Error('getConText(contextID)参数错误') } } clearDisplay() { this._screenBuffer.fill(0); }
invertDisplay() { this.command(Buffer.from([this.INVERT_DISPLAY])); }
normalDisplay() { this.command(Buffer.from([this.NORMAL_DISPLAY])); }
draw() { this.command(Buffer.from([this.SET_MEMORY_MODE, this.MEMORY_MODE_HORIZ])); this.command(Buffer.from([this.SET_PAGE_ADDRESS, 0x00, 0x07])); this.command(Buffer.from([this.SET_COL_ADDRESS, 0x00, 0x7f])); this.command(Buffer.from([this.DISPLAY_ON])); this.data(this._screenBuffer); }
drawPixel(x, y, on) { if (x < 0 || x > this._screenWidth - 1 || y < 0 || y > this._screenHeight - 1) { return; }
let page = Math.floor(y / 8); let offset = y % 8;
if (on) { this._screenBuffer[page * this._screenWidth + x] |= (0x1 << offset); } else { this._screenBuffer[page * this._screenWidth + x] &= ((0x1 << offset) ^ 0xff); } }
reset() { rpio.write(this._resetPin, rpio.LOW); rpio.msleep(10); rpio.write(this._resetPin, rpio.HIGH); }
command(buffer) { rpio.spiWrite(buffer, buffer.length); }
data(buffer) { rpio.write(this._dcPin, rpio.HIGH);
rpio.spiWrite(buffer, buffer.length);
rpio.write(this._dcPin, rpio.LOW); }
end() { rpio.spiEnd(); rpio.close(this._resetPin, rpio.PIN_RESET); rpio.close(this._dcPin, rpio.PIN_RESET); }
scroll() { this.command(Buffer.from([this.RIGHT_HORIZ_SCROLL])); this.command(Buffer.from([this.ACTIVATE_SCROLL])); this.data(this._screenBuffer); }
drawToDisplay() { let pixelArray = [] for (let page = 0; page < 8; page++) { for (let col = 0; col < 128; col++) { const imageData = this._ctx.getImageData(col, page * 8, 1, 8) let byte = 0x00 for (let i = 0, j = 0; i < imageData.data.length; i += 4, j++) { const r = imageData.data[i] const g = imageData.data[i + 1] const b = imageData.data[i + 2] const a = imageData.data[i + 3] const average = 0.21 * r + 0.72 * g + 0.07 * b const bit = average < 128 ? 1 : 0 byte += bit << j } pixelArray.push(byte) } } this._screenBuffer = Buffer.from(pixelArray) this.draw() } }
module.exports = SSD1306;
|
尝试绘制文字
// 引入
const SSD1306 = require('ssd1306')
const rpio = require("rpio")
// 创建实例
const ssd1306 = new SSD1306({
width: 128,
height: 64,
resetPin: 24,
dcPin: 23,
spiChip: 0,
rpio,
})
// 初始化
ssd1306.init()
// 获取canvas 2d 上下文
const ctx = ssd1306.getContext('2d')
ctx.strokeStyle = '#000'
ctx.strokeRect(0, 0, 128, 64)
ctx.font = '18px arial'
ctx.fillStyle = 'rgba(0, 0, 0, 1)'
ctx.textAlign = 'center'
ctx.fillText(`Hello World`, 64, 32)
ssd1306.drawToDisplay()