| 
 1987年4月2日, IBM 推出了 PS/2,这是Personal System/2 Model 80 (IBM 8580)的缩写。在这个电脑上引入了VGA(VideoGraphics Array)接口,它成为模拟信号的电脑显示标准。 VGA接口共有15针,分成3排,每排5个孔,显卡上应用最为广泛的接口类型,绝大多数显卡都带有此种接口。它传输红、绿、蓝模拟信号以及同步信号(水平和垂直信号),从块头巨大的CRT显示器时代开始,VGA接口就被使用,并且一直沿用至今。 VGA 公头     简单的说,工作原理是: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 long  gmtOffset_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[i] == 0x1B && vt_animation[i + 1] == 0x5B && vt_animation[i + 2] == 0x48) {
 -       struct tm timeinfo;
 -       // 取得当前时间
 -       getLocalTime(&timeinfo);
 - 
 -       char buf[60];
 -       // 年月日
 -       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[i]);
 -       Terminal.write(vt_animation[i + 1]);
 -       Terminal.write(vt_animation[i + 2]);
 -       // 跳过这个命令
 -       i = i + 3;
 -     }
 -     Terminal.write(vt_animation[i]);
 -     i++;
 -   }
 - }
 
  复制代码
 这是在HP 显示器上测试的结果   
参考:  
 
 |