共计 7175 个字符,预计需要花费 18 分钟才能阅读完成。
前言
之前看了雪碧大佬的将 React 渲染到嵌入式液晶屏觉得很有意思,React 能被渲染到嵌入式液晶屏, 那 Vue 是不是也能够呢?所以本文咱们要做的就是:
如题目所示, 就是将 Vue 渲染到嵌入式液晶屏。这里应用的液晶屏是 0.96 寸大 128×64 分辨率的 SSD1306。要将 Vue 渲染到液晶屏, 咱们还须要一个桥梁, 它必须具备管制液晶屏及运行代码的能力。而树莓派的硬件对接能力和可编程性人造就具备这个条件。最初一个问题来了, 咱们用什么技术来实现呢?
这里我抉择了 Node.js。起因:
- Atwood 定律:“任何能够应用 JavaScript 来编写的利用,最终会由 JavaScript 编写。”????
- 驱动硬件我大 Node.js 一行
npm install
走天下。????
这个乏味的实际可拆分为这几个步骤:
- 在 Node.js 运行 Vue
- 树莓派连贯屏幕芯片
- Node.js 驱动硬件
Talk is cheap,Let’s Go!!!
跨端渲染
无论是 基于 React 的 React Native 声称的「Learn Once, Write Anywhere」, 还是基于 Vue 的 Weex 声称的「Write Once, Run Everywhere」口号, 实质上强调的都是它们跨端渲染的能力。那什么是跨端渲染呢?
React: ReactNative Taro …
Vue: Weex UniApp …
各种形形色色的前端框架纷纷袭来, 前端工程师们纷纷埋怨学不动了~
老板们看到纷纷笑嘻嘻, App 单, 前端分, 小程序单, 前端吞,PC/H5, 前端昏。skr~
这些跨平台框架原理其实都大同小异, 选定 Vue/React 作为 DSL, 以这个 DSL 框架为规范在各端别离编译, 在运行时, 各端应用各自的渲染引擎 (Render Engines) 进行渲染, 底层渲染引擎中不用关怀下层 DSL 的语法和更新策略,只须要解决 JS Framework 中对立定义的节点构造和渲染指令。也正是因为这一渲染层的形象, 使得跨平台 / 框架成为了可能。
Vue 和 React 当初都实现了自定义渲染器, 上面咱们简略介绍一下:
React Reconciler
React16 采纳新的 Reconciler, 外部采纳了 Fiber 的架构。react-reconciler 模块正是基于 v16 的新 Reconciler 实现, 它提供了创立 React 自定义渲染器的能力.
const Reconciler = require("react-reconciler");
const HostConfig = {
// You'll need to implement some methods here.
// See below for more information and examples.
};
const MyRenderer = Reconciler(HostConfig);
const RendererPublicAPI = {render(element, container, callback) {// Call MyRenderer.updateContainer() to schedule changes on the roots.
// See ReactDOM, React Native, or React ART for practical examples.
},
};
module.exports = RendererPublicAPI;
Vue createRenderer
vue3 提供了 createRender API, 让咱们创立自定义渲染器。
createRenderer 函数承受两个泛型参数:HostNode 和 HostElement,对应于宿主环境中的 节点 和 元素 类型。
自定义渲染器能够传入特定于平台的类型,如下所示:
import {createRenderer} from 'vue'
const {render, createApp} = createRenderer<Node, Element>({
patchProp,
...nodeOps
})
在 Node.js 上运行 Vue
SFC To JS
<template>
<text x="0" y="0">Hello Vue</text>
<text x="0" y="20">{{time}}</text>
<text x="0" y="40">Hi SSD3306</text>
</template>
<script>
import {defineComponent, ref, toRefs, onMounted} from "vue";
import dayjs from "dayjs";
export default defineComponent({setup() {const time = ref(dayjs().format("hh:mm:ss"));
onMounted(() => {setInterval(() => {time.value = dayjs().format("hh:mm:ss");
}, 800);
});
return {
...toRefs({time,}),
};
},
});
</script>
要将 Vue 渲染到液晶屏, 咱们首先须要让 Vue 能运行在 Node.js 上, 然而下面这个 SFC 是没方法被 Node.js 辨认的, 它只是 vue 的编程标准, 是一种方言。所以咱们须要做的是先将 SFC 转为 js。这里我应用 Rollup 打包将 SFC 转为 JS(相干配置这里就不啰嗦了, 贴个传送门)。到了这一步,Node.js 就能胜利运行打包后的 js 代码了, 这还不够, 这时候 Vue 组件的状态更新是没方法同步到 Node.js 的。
Create Custom Renderer
组件状态更新咱们须要告诉 Node.js 更新并渲染液晶屏内容, 咱们须要创立自定义的 ” 更新策略 ”。这里就须要用到了咱们后面提到的自定义渲染器:createRenderer API。上面咱们简略介绍下咱们相干应用:
// index.js
// 自定义渲染器
import {createApp} from "./renderer.js";
// 组件
import App from "./App.vue";
// 容器
function getContainer() {// ...}
// 创立渲染器, 将组件挂载到容器上
createApp(App).mount(getContainer());
// renderer.js
import {createRenderer} from "vue";
// 定义渲染器, 传入自定义 nodeOps
const render = createRenderer({
// 创立元素
createElement(type) {},
// 插入元素
insert(el, parent) {},
// props 更新
patchProp(el, key, preValue, nextValue) {},
// 设置元素文本
setElementText(node, text) {},
// 以下疏忽, 有趣味的童鞋可自行理解
remove(el) {},
createText(type) {},
parentNode(node) {},
nextSibling(nide) {},});
export function createApp(root) {return render.createApp(root);
}
vue 渲染器默认实现了 Web 平台 DOM 编程接口, 将 Virtual DOM 渲染为实在 DOM。然而这个渲染器只能运行在浏览器中, 不具备跨平台能力。所以咱们必须重写 nodeOps 相干钩子函数, 实现对应宿主环境元素的增删改查操作。接下来咱们定义一个适配器, 来实现相干逻辑。
Adapter
在实现前, 咱们先来理一下咱们要实现的逻辑:
- 创立元素实例 (create)
- 将元素实例插入容器, 由容器进行治理 (insert)
- 状态扭转时, 告诉容器进行更新 (update)
// adapter.js
// 文本元素
export class Text {constructor(parent) {// 提供一个父节点用于寻址调用更新 (后面提到状态更新由容器进行)
this.parent = parent;
}
// 元素绘制, 这里须要实现文本元素渲染逻辑
draw(text) {console.log(text);
}
}
// 适配器
export class Adapter {constructor() {
// 装载容器
this.children = [];}
// 装载子元素
append(child) {this.children.push(child);
}
// 元素状态更新
update(node, text) {
// 找到指标渲染进行绘制
const target = this.children.find((child) => child === node);
target.draw(text);
}
clear() {}
}
// 容器 === 适配器实例
export function getContainer() {return new Adapter();
}
好了, 根本的适配器曾经实现了, 接下来咱们来实现渲染器。
Renderer Abstract
import {createRenderer} from "vue";
import {Text} from "./adapter";
let uninitialized = [];
const render = createRenderer({
// 创立元素, 实例化 Text
createElement(type) {switch (type) {
case "text":
return new Text();}
},
// 插入元素, 调用适配器办法进行装载对立治理
insert(el, parent) {if (el instanceof Text) {
el.parent = parent;
parent.append(el);
uninitialized.map(({node, text}) => el.parent.update(node, text));
}
return el;
},
// props 更新
patchProp(el, key, preValue, nextValue) {el[key] = nextValue;
},
// 文本更新, 从新绘制
setElementText(node, text) {if (node.parent) {console.log(text);
node.parent.clear(node);
node.parent.update(node, text);
} else {uninitialized.push({ node, text});
}
},
remove(el) {},
createText(type) {},
parentNode(node) {},
nextSibling(nide) {},});
export function createApp(root) {return render.createApp(root);
}
树莓派连贯屏幕芯片
SSD1306 OLED
OLED,即有机发光二极管(Organic Light Emitting Diode)。是一种液晶显示屏。而 SSD1306 就是一种 OLED 驱动芯片。ssd1306 自身反对多种总线驱动形式:6800/8080 并口、SPI 及 IIC 接口方式。这里咱们抉择 IIC 接口方式进行通信, 理由很简略: 1. 接线简略不便(两根线就能够驱动 OLED) 2. 轮子好找 … 毛病就是 IIC 传输数据效率太慢了, 刷新率只有 10FPS 不到。而 SPI 刷新率最大能达到 2200FPS。
硬件接线
IIC 仅须要 4 根线就能够,其中 2 根是电源,另外 2 根是 SDA 和 SCL。咱们应用 IIC-1 接口。上面是树莓派的 GPIO 引脚图。
留神:请肯定以屏幕的理论引脚编号为准。
- 屏幕 VCC 接树莓派 1 号引脚。- 3.3v 电源
- 屏幕 GND 接树莓派 9 号引脚。- 地线
- 屏幕 SDA 接树莓派 3 号引脚。- IIC 通信中为数据管脚
- 屏幕 SCL 接树莓派 5 号引脚。- IIC 通信中为时钟管脚
树莓派启用 I2C
1. 装置工具包
sudo apt-get install -y i2c-tools
2. 启用 I2C
- sudo raspi-config
- 抉择 Interfacing Options
- Enable I2C
3. 查看设施挂载状态
i2c-tools 提供的 i2cdetect 命令能够查看挂载设施
sudo i2cdetect -y 1
Node.js 驱动硬件
Node.js Lib
咱们先来看几个 Node.js 库, 看完你会不得不感叹~任何能够应用 JavaScript 来编写的利用, 最 ….
johnny-five
Johnnt-Five 是一个反对 JavaScript 语言编程的机器人和 IOT 开发平台, 基于 Firmata 协定。Firmata 是计算机软件和微控制器之间的一种通信协议。应用它, 咱们能够很简略的架起树莓派和屏幕芯片之间的桥梁。
raspi-io
Raspi IO 是一个为 Johnny-Five Node.js 机器人平台提供的 I/O 插件,该插件使 Johnny-Five 可能管制一个 Raspberry Pi 上的硬件。
oled-font-5×7
5×7 oled 字体库, 将字符转为 16 进制编码, 让 oled 程序可能辨认。用于绘制文字。
oled-js
???? 兼容 johnny-five 的 oled 反对库 (johnny-five 自身并不反对 oled), 提供了操作 oled 的 API。
驱动程序实现
// oled.js
const five = require("johnny-five");
const Raspi = require("raspi-io").RaspiIO;
const font = require("oled-font-5x7");
const Oled = require("oled-js");
const OPTS = {
width: 128, // 分辨率 0.96 寸 ssd1306 128*64
height: 64, // 分辨率
address: 0x3c, // 管制输出地址,ssd1306 默认为 0x3c
};
class OledService {constructor() {this.oled = null;}
/**
* 初始化: 创立一个 Oled 实例
* 创立后, 咱们就能够通过操作 Oled 实例来管制屏幕了
*/
init() {
const board = new five.Board({io: new Raspi(),
});
// 监听程序退出, 敞开屏幕
board.on("exit", () => {this.oled && this.remove();
});
return new Promise((resolve, reject) => {board.on("ready", () => {
// Raspberry Pi connect SSD 1306
this.oled = new Oled(board, five, OPTS);
// 关上屏幕显示
this.oled.turnOnDisplay();
resolve();});
});
}
// 绘制文字
drawText({text, x, y}) {
// 重置光标地位
this.oled.setCursor(+x, +y);
// 绘制文字
this.oled.writeString(font, 2, text, 1, true, 2);
}
clear({x, y}) {this.oled.setCursor(+x, +y);
}
// 刷新屏幕
update() {this.oled.update();
}
remove() {
// 敞开显示
this.oled.turnOffDisplay();
this.oled = null;
}
}
export function oledService() {return new OledService();
}
接下来, 咱们就能够在适配器中调用 oled 程序渲染屏幕了~
// index.js
import {createApp} from "./renderer.js";
import {getContainer} from "./adapter";
import {oledService} from "./oled";
import App from "./App.vue";
const oledIns = oledService();
oledIns.init().then(() => {createApp(App).mount(getContainer(oledIns));
});
// adapter.js
export class Text {constructor(parent) {this.parent = parent;}
draw(ints, opts) {ints.drawText(opts);
ints.update();}
}
export class Adapter {constructor(oledIns) {this.children = [];
this.oled = oledIns;
}
append(child) {this.children.push(child);
}
update(node, text) {const target = this.children.find((child) => child === node);
target.draw(this.oled, {
text,
x: node.x,
y: node.y,
});
}
clear(opts) {this.oled.clear(opts);
}
}
export function getContainer(oledIns) {return new Adapter(oledIns);
}
到这一步, 就能够胜利点亮屏幕啦, 来看看成果~
成果展现
参考
将 React 渲染到嵌入式液晶屏
在树莓派上应用 SSD1306 OLED 屏幕
结语
残缺代码已上传到 Github, 如果你感觉这个实际对你有启发 / 帮忙, 点个 star 吧~
Vue 曾经胜利渲染到嵌入式液晶屏了, 那下一步是不是能够思考接个摇杆写个贪吃蛇游戏了~哈哈哈, 这很 ”Javascript”。
“ 浏览式 ” 的学习使我犯困, 所以我更偏向通过一些乏味的实际排汇常识。如果你和我一样爱折腾, 欢送关注~