使用 Johnny-Five 的 Led.Digits 打造七段数码管数字时钟
【免费下载链接】johnny-fiveJavaScript Robotics and IoT programming framework, developed at Bocoup.项目地址: https://gitcode.com/gh_mirrors/jo/johnny-five
导读
本文围绕 Johnny-Five(JavaScript Robotics and IoT 编程框架)官方示例文档 docs/led-digits-clock.md 展开,讲解如何利用Led.Digits驱动七段数码管(7 Segment Digits)在 Arduino、Intel Galileo Gen 2 等平台上实现一个每秒刷新的数字时钟。读完本文,你将掌握Led.Digits的初始化方式、print()与digit()的底层工作机制、时间字符串的格式化技巧,以及如何将同一段代码移植到不同控制器平台。
示例概述:一块会走的数字钟
原文档给出了一个非常直观的示例:使用七段数码管和 Intel Galileo Gen 2 打造一台时钟,并且明确指出该示例可以轻松移植到任何 Johnny-Five 支持的平台(例如 Arduino Uno)。核心思路如下:
- 通过
Led.Digits封装数码管硬件,指定data、cs、clock三个引脚; - 在
board.on("ready")之后,用setInterval每秒调用一次digits.print(time()); time()函数借助 moment.js 生成形如02.25.54 P的格式化字符串,并做一次正则替换,得到适合单字母上下午(meridiem)的显示内容。
在仓库中,该示例存在三个版本,可直接对照阅读:
- eg/led-digits-clock.js:默认 Arduino 平台版本;
- eg/led-digits-clock-arduino.js:与默认版本等价;
- eg/led-digits-clock-galileo.js:使用
galileo-io作为 IO 层的 Galileo 版本。
运行 Galileo 版本的方式(与文档一致):
node eg/led-digits-clock-galileo.js运行普通 Arduino 版本则执行:
node eg/led-digits-clock.js接线图与硬件准备
原文档提供了两种接线的 Fritzing 图,均以仓库根目录为起点:
- Galileo 接线图:docs/breadboard/led-digits-clock-galileo.png,对应 Fritzing 源文件 docs/breadboard/led-digits-clock-galileo.fzz;
- Arduino 接线图:docs/breadboard/led-digits-clock-arduino.png,对应 Fritzing 源文件 docs/breadboard/led-digits-clock-arduino.fzz。
接线要点(对应源码中的引脚配置):数码管/驱动板的data、cs、clock三根信号线分别接到控制器的 2、3、4 号数字引脚(本例配置),公共端与电源按驱动板手册连接。仓库中还有基于 HT16K33 等 I2C 控制器的变体接线图,如 docs/breadboard/led-digits-clock-HT16K33.png 和 docs/breadboard/led-digits-clock-dual.png,可作为后续移植参考。
完整代码与逐段讲解
const moment = require("moment"); const { Board, Led } = require("johnny-five"); const Galileo = require("galileo-io"); const board = new Board({ io: new Galileo() }); board.on("ready", () => { var digits = new Led.Digits({ pins: { data: 2, cs: 3, clock: 4, } }); setInterval(() => digits.print(time()), 1000); }); function time() { /* The desired display looks something like these examples: 02.25.54 P 12.30.00 A moment doesn't have an option for a single letter meridiem (nor should it, that would be silly), so we need to manipulate the string a bit to so that it the string matches our desired display. */ return moment().format("hh.mm.ssA") .replace(/([AP])M/, " $1"); }1. 依赖与平台抽象
const moment = require("moment"); const { Board, Led } = require("johnny-five"); const Galileo = require("galileo-io");moment:时间格式化库,负责生成hh.mm.ssA形式的时间字符串;johnny-five:提供Board(板卡抽象)与Led(LED 家族,含Led.Digits);galileo-io:Galileo 平台的 IO 适配层。若使用 Arduino,则省略这一行,直接new Board()即可(见 eg/led-digits-clock-arduino.js)。
这种"Board + io 注入"的结构正是 Johnny-Five 的平台可移植性来源:代码逻辑不变,只换 IO 层就能换硬件平台。
2. 初始化数码管
const digits = new Led.Digits({ pins: { data: 2, cs: 3, clock: 4, } });Led.Digits是 lib/led/digits.js 中定义的一个极简类,它继承自LedControl并强制options.isMatrix = false:
class Digits extends LedControl { constructor(options) { options.isMatrix = false; super(options); } static get CHARS() { return LedControl.DIGIT_CHARS; } }LedControl(见 lib/led/ledcontrol.js)支持两种控制器:
- 默认控制器(MAX7219/MAX7221 类 SPI 驱动):通过
pins指定data、clock、cs(chip select,也接受别名latch)三个数字引脚。源码中初始化时会把这几个引脚全部设为OUTPUT模式(见 lib/led/ledcontrol.js),并按 MAX7219 的寄存器顺序发送BRIGHTNESS、SCANLIMIT、SHUTDOWN、DISPLAYTEST等配置指令; - HT16K33 控制器(I2C 驱动):通过
address/addresses与dims配置,适用于带 I2C 接口的数码管背板。
pins既可以是对象{ data, clock, cs },也可以是数组[data, clock, cs]。其它可用选项还包括devices(级联的驱动芯片数量)、digits(每块芯片的位数,默认 8)、colon(设备是否内置冒号)等。
3. 每秒刷新一次
setInterval(() => digits.print(time()), 1000);每 1000ms 生成一次当前时间字符串并调用digits.print()。print()是LedControl的核心显示方法(见 lib/led/ledcontrol.js),它会用正则/([0-9A-Za-z][.]|[0-9A-Za-z:]|[\s])/g把字符串拆成"字符单元"(支持带小数点字符、冒号、空白),然后逐个调用digit()写入对应位置。若设备配置了colon: true,print()还会在超过两个字符且第 3 个字符不是:或空格时自动插入空格占位,让内置冒号的数码管更自然地居中显示。
digit()(见 lib/led/ledcontrol.js)则负责把单个字符映射成段码:优先查LedControl.DIGIT_CHARS字模表,查不到时回退为Math.abs(Number(character));若字符后带.,则与小数点段码0x80做按位或。
4. 时间格式化与字符串处理
return moment().format("hh.mm.ssA") .replace(/([AP])M/, " $1");moment().format("hh.mm.ssA"):输出 12 小时制时间,如02.25.54PM。其中hh是 12 小时制(01-12),mm是分钟,ss是秒,A是 AM/PM 大写字母;.replace(/([AP])M/, " $1"):把AM/PM压缩成单个字母A/P,并在前面补一个空格,使最终显示为02.25.54 P/12.30.00 A,与七段数码管的显示习惯一致。
小数点在这里起到分隔时、分、秒的作用(., 段码为0x80),这正是print()正则允许"数字 + 小数点"单元的原因。
显示字模与字符支持
Led.Digits.CHARS暴露了完整的数字字模表(见 lib/led/led-chars.js),它是LedControl.DIGIT_CHARS的静态别名,支持:
- 数字:
0–9(例如"8": 0x7F,全段点亮); - 标点与分隔符:空格
0x00、小数点.0x80、冒号:0xFF、感叹号!0xB0; - 大小写字母:
A–Z、a–z(七段近似显示)。
如果你要显示自定义字符,可以基于这张表扩展,并把对应的 8 位段码直接传给digit(addr, position, chr),或直接传数字段码。
移植到其它平台与控制器
原文档强调"这个示例可以轻松移植到任何其它受支持平台",移植路径有两种:
- 更换 IO 层(同一块数码管):Galileo 用
new Board({ io: new Galileo() }),Arduino 用new Board(),Raspberry Pi 可换成raspi-io,Tessel 可换成tessel-io等,业务代码不变; - 更换驱动控制器(同一套代码):若数码管背板是 HT16K33(I2C),将
pins换成address(如0x70)与dims,其余print()/digit()调用方式一致;该控制器的初始化、blink、brightness、clear等实现在 lib/led/ledcontrol.js 的Controllers.HT16K33中,可据此了解 I2C 版本的行为差异。
常见问题与注意事项
- 必须等
board.on("ready")再操作:所有 IO 初始化都在 ready 事件之后进行,否则引脚与控制器尚未就绪; - 字符数量不要超过位数:默认
digits: 8,本示例显示 8 个字符(hh.mm.ss P恰好 8 个单元);超出位数的字符会被忽略; time()始终返回固定长度字符串:moment().format("hh.mm.ssA")中hh/mm/ss均会补零,保证 12 小时制下字符串长度稳定,避免因数字位数变化导致显示错位;- SPI 引脚需要支持数字输出:
data、clock、cs必须使用数字引脚,且注意电平与驱动板兼容。
深入阅读
- 数码管实现类:lib/led/digits.js
- 底层控制器(MAX7219/HT16K33 双控制器实现):lib/led/ledcontrol.js
- 字模表(含数字、冒号、小数点与字母):lib/led/led-chars.js
- 示例源码:eg/led-digits-clock.js、eg/led-digits-clock-galileo.js
- 相关教程文档:docs/led-digits-clock-dual.md(双模块时钟)、docs/led-digits-clock-HT16K33.md(HT16K33 版)
结合本文的源码剖析,你可以在任意 Johnny-Five 支持的平台上快速复现这台数码管时钟,并将Led.Digits的print()能力扩展到倒计时器、秒表、计数器等更多场景。
【免费下载链接】johnny-fiveJavaScript Robotics and IoT programming framework, developed at Bocoup.项目地址: https://gitcode.com/gh_mirrors/jo/johnny-five
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考