FireBeetle 做一个 VGA 时钟
1987年4月2日, IBM 推出了 PS/2,这是Personal System/2 Model 80 (IBM 8580)的缩写。在这个电脑上引入了VGA(VideoGraphics Array)接口,它成为模拟信号的电脑显示标准。VGA接口共有15针,分成3排,每排5个孔,显卡上应用最为广泛的接口类型,绝大多数显卡都带有此种接口。它传输红、绿、蓝模拟信号以及同步信号(水平和垂直信号),从块头巨大的CRT显示器时代开始,VGA接口就被使用,并且一直沿用至今。VGA 公头标号名称描述标号名称描述
1RED视频红色分量9KEY保留
2GREEN视频绿色分量10SGND同步信号地
3BLUE视频蓝色分量11NC保留
4NC保留12Bi-Diorectional DataSDA
5GND地13HSYNC行同步信号
6RGND红色地14VSYNC场同步信号
7GGND绿色地15Data ClockSCL
8BGND蓝色地
简单的说,工作原理是:RG B 三个Pin 给出每一个点的颜色组成信息,显示器采样后即按照给出值显示,当显示完一行后,行同步信号通知显示器换行,如此进行,当一帧显示完成后场同步信号通知显示器这一帧结束了,请从最上面再进行显示。对于我们来说,只要单片机足够快就可以模拟出 VGA 信号从而达到显示的目的。这次制作一个 VGA 转接板,配合 FireBeetle 在显示器上显示当前时间。首先,本项目基于开源图形库FabGL【参考1】 ,它是设计给ESP32的图形库。它实现了多个显示驱动程序,例如VGA接口的显示器以及I2C和SPI 接口的液晶屏)。此外FabGL还可以从PS/2键盘和鼠标获取输入,方便实现简单的交互。硬件设计上 GPIO21/22 用作红色信号输出;GPIO18/19用作绿色信号输出;GPIO4/5用作蓝色信号输出;GPIO23用于 HSync;GPIO15用于VSync(定义在vga16controller.h)。每一种颜色使用2个电阻构成简单的 DAC 电路,因此可以显示 2^6=64种颜色。最终给FireBeetle设计了一个 VGA Shield 如下:此外,引出所有的IO方便日后扩展其他功能。
此外,还有一个I2S的 DAC输出,为日后音频输出预留
除了 FireBeetle提供电力,板子上还有一个 USB接口,可以直接将USB插入此处供电。
PCB 布线如下:
3D预览如下:
制作好的 PCB 如下:
用于颜色显示的电阻是必须的,其他的没有焊接。安装之后的样子:接下来编写代码,基本原理是使用 FabGL 的终端(Terminal,相当于一个 ASCII 字符显示器),在上面使用 ASCII绘制转动的地球;此外,通过 WIFI 获得阿里NTP服务器提供的日期时间信息,一起输出到VGA接口上显示在屏幕上。
#include "fabgl.h"
#include "vtanimations.h"
#include <WiFi.h>
// VGA 显示
fabgl::VGA16Controller DisplayController;
fabgl::Terminal Terminal;
const char *ssid = "labz_001665"; //网络名称
const char *password = "12345678"; //网络密码
// 使用阿里的 NTP 获得当前时间
const char* ntpServer = "ntp.aliyun.com";
// 时区修正,我们在东八区
const longgmtOffset_sec = 8 * 60 * 60;
// 夏令时修正
const int daylightOffset_sec = 0;
void setup() {
struct tm timeinfo;
Serial.begin(115200);
// Connect to Wi-Fi
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
delay(2000);
Serial.println("Connect to NTP server");
// 从 NTP Server 获得时间
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// 如果没有成功获取,那么重启 ESP32
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time, retry");
delay(5000);
ESP.restart();
}
Serial.println("Got time");
// 取得时间后即可断开 WIFI
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
DisplayController.begin();
// 设定分辨率
DisplayController.setResolution(VGA_640x480_60Hz);
// 创建 Terminal
Terminal.begin(&DisplayController);
Terminal.enableCursor(true);
// 背景为黑,文字绿色
Terminal.write("\e[40;92m");
// 请屏幕
Terminal.write("\e[2J");
Terminal.write("\e[20h");
//Terminal.write("\e[92m");
// 关闭光标
Terminal.enableCursor(false);
}
void loop() {
int i = 0;
while (i < sizeof(vt_animation) - 4) {
// 如果当前要发送回归第一行的命令,就输出当前时间
if (vt_animation == 0x1B && vt_animation == 0x5B && vt_animation == 0x48) {
struct tm timeinfo;
// 取得当前时间
getLocalTime(&timeinfo);
char buf;
// 年月日
sprintf(buf, "\e[10;56H%d/%02d/%02d", timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday);
Serial.println(buf);
Terminal.write(buf);
//小时分钟秒
sprintf(buf, "\e[14;56H%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
Serial.println(buf);
Terminal.write(buf);
// 输出回归第一行的命令
Terminal.write(vt_animation);
Terminal.write(vt_animation);
Terminal.write(vt_animation);
// 跳过这个命令
i = i + 3;
}
Terminal.write(vt_animation);
i++;
}
}
这是在HP 显示器上测试的结果
参考:1. https://github.com/fdivitto/FabGL/
源代码
PCB和电路图
工作的视频可以在 B站看到
https://www.bilibili.com/video/BV12u411Z7vG/
页:
[1]