2026年2月更新,Step to UEFI 文章索引:
Step to UEFI (305)UEFI Shell 下显示 PPM 图片的程序
PPM是Portable Pixmap Format的缩写,这是一种非常冷门儿的图片格式。它带有一个简单的 ASCII头,内部就是最原始的使用 RGB 值表示的每一个点的数据。例如,打开bell_206.ppm【参考1】,基本上可以猜到 P6 标记了图片的存储方式,258*792 是图片尺寸。

更详细的介绍可以在【参考2】看到:
每个图像文件的开头都通过2个字节「magic number」来表明文件格式的类型(PBM, PGM, PPM),以及编码方式(ASCII 或 Binary),magic number分别为P1、P2、P3、P4、P5、P6。
| Magic Number | Type | Encoding |
| P1 | Bitmap | ASCII |
| P2 | Graymap | ASCII |
| P3 | Pixmap | ASCII |
| P4 | Bitmap | Binary |
| P5 | Graymap | Binary |
| P6 | Pixmap | Binary |
ASCII格式适合人类阅读理解,可以用文本编辑器打开,读取对应图像的数据(比如PPM格式的RGB值)。 Binary格式适合机器阅读,按照二进制形式,顺序存储图像信息,不用空格分隔,所以图像处理起来更有效率,占用空间容量更少(由于缺少空格)。
下面着重讲解PPM格式:
PPM图像格式分为两部分,分别为头部分和图像数据部分。
头部分:由3部分组成,通过换行或空格进行分割,一般PPM的标准是空格。
第1部分:P3或P6,指明PPM的编码格式,
第2部分:图像的宽度和高度,通过ASCII表示,
第3部分:最大像素值,0-255字节表示。
在这三部分中,可能会有注释。注释以#开头,例如:# CREATOR: GIMP PNM Filter Version 1.1。
图像数据部分:
ASCII格式:按RGB的顺序排列,RGB中间用空格隔开,图片每一行用回车隔开。
Binary格式:PPM用24bits代表每一个像素,红绿蓝分别占用8bits。
这样看起来编写一个在 UEFI Shell 下显示 PPM 文件的代码并不复杂,最终的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/GraphicsOutput.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
// PPM 图像结构
typedef struct {
int width;
int height;
int maxval;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *pixels;
} PPM_IMAGE;
// 跳过注释和空白
void skip_comments(FILE *fp) {
int c;
while ((c = fgetc(fp)) != EOF) {
if (c == '#') {
// 跳过整行注释
while ((c = fgetc(fp)) != EOF && c != '\n');
} else if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
// 非空白字符,放回去
ungetc(c, fp);
break;
}
}
}
// 读取 PPM 文件
PPM_IMAGE* load_ppm(const char *filename) {
FILE *fp;
PPM_IMAGE *img;
char magic[3];
int i, r, g, b;
printf("Loading PPM file: %s\n", filename);
fp = fopen(filename, "rb");
if (!fp) {
printf("Error: Cannot open file %s\n", filename);
return NULL;
}
// 分配图像结构
img = (PPM_IMAGE*)malloc(sizeof(PPM_IMAGE));
if (!img) {
printf("Error: Memory allocation failed\n");
fclose(fp);
return NULL;
}
// 读取魔数
if (fread(magic, 1, 2, fp) != 2) {
printf("Error: Cannot read magic number\n");
free(img);
fclose(fp);
return NULL;
}
magic[2] = '\0';
if (strcmp(magic, "P3") != 0 && strcmp(magic, "P6") != 0) {
printf("Error: Unsupported format %s (only P3 and P6 supported)\n", magic);
free(img);
fclose(fp);
return NULL;
}
printf("PPM Format: %s\n", magic);
// 跳过空白和注释
skip_comments(fp);
// 读取宽度、高度、最大值
if (fscanf(fp, "%d", &img->width) != 1 ||
fscanf(fp, "%d", &img->height) != 1 ||
fscanf(fp, "%d", &img->maxval) != 1) {
printf("Error: Cannot read image dimensions\n");
free(img);
fclose(fp);
return NULL;
}
printf("Dimensions: %d x %d, Max value: %d\n",
img->width, img->height, img->maxval);
if (img->width <= 0 || img->height <= 0 || img->maxval <= 0) {
printf("Error: Invalid image parameters\n");
free(img);
fclose(fp);
return NULL;
}
// 跳过最后的空白字符
fgetc(fp);
// 分配像素数据
img->pixels = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL*)malloc(
img->width * img->height * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
if (!img->pixels) {
printf("Error: Cannot allocate pixel buffer\n");
free(img);
fclose(fp);
return NULL;
}
// 读取像素数据
if (strcmp(magic, "P3") == 0) {
// P3 格式 (ASCII)
printf("Reading P3 format...\n");
for (i = 0; i < img->width * img->height; i++) {
if (fscanf(fp, "%d %d %d", &r, &g, &b) != 3) {
printf("Error: Cannot read pixel data at position %d\n", i);
free(img->pixels);
free(img);
fclose(fp);
return NULL;
}
// 转换为 0-255 范围并设置像素 (BGR 格式)
img->pixels[i].Red = (UINT8)((b * 255) / img->maxval);
img->pixels[i].Green = (UINT8)((g * 255) / img->maxval);
img->pixels[i].Blue = (UINT8)((r * 255) / img->maxval);
img->pixels[i].Reserved = 0;
}
} else {
// P6 格式 (Binary)
printf("Reading P6 format...\n");
for (i = 0; i < img->width * img->height; i++) {
unsigned char rgb[3];
if (fread(rgb, 1, 3, fp) != 3) {
printf("Error: Cannot read binary pixel data at position %d\n", i);
free(img->pixels);
free(img);
fclose(fp);
return NULL;
}
// 转换为 0-255 范围并设置像素 (BGR 格式)
img->pixels[i].Red = (UINT8)((rgb[0] * 255) / img->maxval);
img->pixels[i].Green = (UINT8)((rgb[1] * 255) / img->maxval);
img->pixels[i].Blue = (UINT8)((rgb[2] * 255) / img->maxval);
img->pixels[i].Reserved = 0;
}
}
fclose(fp);
printf("PPM file loaded successfully!\n");
return img;
}
// 释放图像内存
void free_ppm(PPM_IMAGE *img) {
if (img) {
if (img->pixels) {
free(img->pixels);
}
free(img);
}
}
// 显示图像
EFI_STATUS display_image(PPM_IMAGE *img, int x, int y) {
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
if (!img || !img->pixels) {
return EFI_INVALID_PARAMETER;
}
Status = gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid, NULL, (VOID**)&gop);
if (EFI_ERROR(Status)) {
printf("Error: Cannot locate Graphics Output Protocol\n");
return Status;
}
Status = gop->Blt(gop, img->pixels, EfiBltBufferToVideo,
0, 0, 0, 0, img->width, img->height, 0);
if (EFI_ERROR(Status)) {
printf("Error: Failed to display image\n");
Print(L"%r\n\r",Status);
} else {
printf("Image displayed successfully!\n");
}
return Status;
}
// 主函数
int main(int argc, char *argv[]) {
PPM_IMAGE *img;
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
const char *filename = "test.ppm"; // 默认文件名
int center_x, center_y;
printf("Simple PPM Viewer for UEFI\n");
printf("==========================\n\n");
// 如果提供了命令行参数,使用指定的文件名
if (argc > 1) {
filename = argv[1];
}
// 检查图形输出协议
Status = gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid, NULL, (VOID**)&gop);
if (EFI_ERROR(Status)) {
printf("Error: Graphics Output Protocol not available\n");
return 1;
}
printf("Screen Resolution: %d x %d\n",
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution);
// 加载 PPM 文件
img = load_ppm(filename);
if (!img) {
printf("Failed to load PPM file\n");
return 1;
}
// 检查图像是否适合屏幕
if (img->width > (int)gop->Mode->Info->HorizontalResolution ||
img->height > (int)gop->Mode->Info->VerticalResolution) {
printf("Warning: Image (%dx%d) is larger than screen (%dx%d)\n",
img->width, img->height,
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution);
}
// 计算居中位置
center_x = ((int)gop->Mode->Info->HorizontalResolution - img->width) / 2;
center_y = ((int)gop->Mode->Info->VerticalResolution - img->height) / 2;
if (center_x < 0) center_x = 0;
if (center_y < 0) center_y = 0;
// 显示图像
Status = display_image(img, center_x, center_y);
if (EFI_ERROR(Status)) {
printf("Failed to display image\n");
free_ppm(img);
return 1;
}
// 清理资源
free_ppm(img);
printf("PPM Viewer finished.\n");
return 0;
}
在模拟器中运行结果如下:

完整的代码下载:
参考:
1. https://people.sc.fsu.edu/~jburkardt/data/ppmb/ppmb.html PPMB Files Portable Pixel Map (binary)
2. https://blog.csdn.net/qq_41598072/article/details/81129203 PPM图片格式及其C读写代码
使用 Ch32V307 实现一个 USB YUV 摄像头
这里介绍如何使用 Ch32V307 实现一个 USB YUV 摄像头。这次的实验是基于WCH官方Ch32v307开发板进行的。实现一个 160*120 分辨率的YUV 摄像头,使用 307 的 USBFS 来实现。
- 参考的对象是之前的一个使用 32U4 制作的摄像头【参考1】,因此描述符部分是完全照抄的。实现描述符之后插入设备就能看到设备管理器中“长出来”我们自定义的摄像头了,但是还无法工作。
- 项目是根据WCH官方的 307 USB FS 键盘鼠标例程修改而来。设定串口输出波特率是 6 000 000 bps。如有需要,可以直接和原版对比。
USART_Printf_Init( 6000000 );
USB FS 是下图4的接口,串口输出在下图7位置的 TXD

3.实现下面的命令应答之后,设备即可发送数据
GET_INFO SU_INPUT_SELECT_CONTROL

GET_INFO PU_BRIGHTNESS_CONTROL

GET_MIN ProcessingUnit

GET_MAX ProcessingUnit

GET_RES processingUnit

GET_DEF ProcessingUnit

GET_CUR ProcessingUnit

SET_CUR ProcessingUnit (主机对设备发数据)

GET_CUR Video_Streaming (主机打开摄像头软件之后才有)

GET_MAX VideoStreaming

GET_MIN VideoStreaming

SET_CUR VideoStreaming

4.接下来构建数据进行发送。需要特别注意的是:原版使用 Endpoint1 进行发送,但是307 的 USBFS EndPoint1 最大只能发送64 Bytes.设置为 256 会使得 EndPoint 的 Length 重置为 0。设计上307 的 FS 只有 EndPoint3 支持最大为 1023 Bytes的ISO包。因此,我们修改代码,改为EndPoint3,每一个包的长度仍然是 256Bytes.
5.发送的数据实际上是有一个头的,比如: 02 给出了头的长度是 2 Bytes, 0x80 是一个切换值,用于表示数据结束。比如,这一帧图像数据开头都是 02 80 ,那么下一帧的数据开头都是 02 81, 下下一帧就会是 02 80。接收端用这个跳变来判断一帧是否结束。

6.因为有这个头的存在,所以有效的数据长度是EndPoint 的长度是 256-2=254 字节。我们一帧的数据量计算方式如下:
以最常用的 YUV420 格式(如YV12)为例:
- Y分量:160×120 = 19,200 字节
- U分量:160×120×1/4 = 4,800 字节(色度下采样)
- V分量:160×120×1/4 = 4,800 字节
- 总大小 = 19,200 + 4,800 + 4,800 = 28,800 字节(约28.1KB)
那么需要发送 28800/254=113个 余98Bytes。
简单起见,我们设定 Y=U=V=从0-255固定值,循环变化。发送 113次 256Bytes(带2Bytes头),再多发送一次 98Bytes。具体是在下面准备好每一个USB 包的数据进行发送。
int counter = 0;
uint8_t color=0;
void USBFS_Endp_ZSend (void) {
if (USBFS_Endp_Busy[3] == 0) { // 可以发送
if (counter == 113) {
USBFSD_UEP_TLEN (3) = 100;
} else USBFSD_UEP_TLEN (3) = 256;
if (counter == 0) {
for (int i=2;i<256;i++) {
USBFS_EP3_Buf[i]=color;
}
color++;
if (USBFS_EP3_Buf[1] == 0x80) {
USBFS_EP3_Buf[1] = 0x81;
} else {
USBFS_EP3_Buf[1] = 0x80;
}
}
printf ("%d %x %x %x\r\n", counter, USBFSD_UEP_TLEN (3), USBFS_EP3_Buf[1], USBFS_EP3_Buf[2]);
USBFSD_UEP_TX_CTRL (3) = (USBFSD_UEP_TX_CTRL (3) & ~USBFS_UEP_T_RES_MASK) | USBFS_UEP_T_RES_NONE;
USBFS_Endp_Busy[3] = 1;
if (counter == 113) {
counter=0;
} else counter++;
}
}
工作的测试视频:
这次只是一个简单的测试,相比 MJPEG 摄像头,YUV 的分辨率不会太高(没有压缩数据量太大),但是YUV 的显示内容完全可以通过简单的计算来得到。后面我会探索更多的玩法。
完整的代码:
参考:
1. https://www.lab-z.com/adca/ Arduino Leonardo 自带的“显示屏
Step to UEFI (304)UEFI Shell 下显示JPEG 图片
前面介绍过 OK_JPG 解码库,这次尝试吧代码移植到 UEFI 下。
整体架构和之前介绍的 Windows 下的几乎相同,ok_jpg_read() 即可完成读取和解码。
需要特别注意的是UEFI Graphics Output Protocol 使用的颜色格式通常是 BGR 而不是常见的 RGB 格式,因此,解码之后需要一个函数来调整每个点的表示方法,完整代码如下:
#include <Protocol/GraphicsOutput.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <stdio.h>
#include <stdlib.h>
#include "ok_jpg.h"
EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
extern EFI_BOOT_SERVICES *gBS;
// 批量转换图像数据
VOID ConvertRgbToBgr(
IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL *ImageData,
IN UINTN PixelCount
) {
UINTN i;
UINT8 temp;
for (i = 0; i < PixelCount; i++) {
// 交换 Red 和 Blue 分量
temp = ImageData[i].Red;
ImageData[i].Red = ImageData[i].Blue;
ImageData[i].Blue = temp;
// Green 和 Reserved 保持不变
}
}
int main() {
EFI_STATUS Status;
Status = gBS->LocateProtocol(
&GraphicsOutputProtocolGuid,
NULL,
(VOID **) &GraphicsOutput);
if (EFI_ERROR(Status))
{
GraphicsOutput = NULL;
printf("Loading Graphics_Output_Protocol error!\n");
return EFI_SUCCESS;
}
printf("start\n");
FILE *file = fopen("my_image.jpg", "rb");
printf("%d\n",*file);
ok_jpg image = ok_jpg_read(file, OK_JPG_COLOR_FORMAT_RGBA);
fclose(file);
if (image.data) {
printf("Got image! Size: %li x %li\n", (long)image.width, (long)image.height);
// 颜色顺序需要调整一下
ConvertRgbToBgr((EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) image.data,image.width*image.height);
//显示
GraphicsOutput->Blt(
GraphicsOutput,
(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) image.data,
EfiBltBufferToVideo,
0, 0,
0, 0,
image.width, image.height, 0);
free(image.data);
}
return 0;
}
INF如下:
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = jpgDe
FILE_GUID = 4ea97c46-2026-0417-b442-747010f3ce5f
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 0.1
ENTRY_POINT = ShellCEntryLib
#
# VALID_ARCHITECTURES = IA32 X64
#
[Sources]
JpegDecoder.c
ok_jpg.c
[Packages]
StdLib/StdLib.dec
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
MdeModulePkg/MdeModulePkg.dec
[LibraryClasses]
LibC
LibStdio
DevShell
[BuildOptions]
MSFT:*_*_*_CC_FLAGS = /wd4244
在模拟器中运行结果如下:

完整的代码如下:
参考:
1. https://www.lab-z.com/stu246/ 显示 JPEG 图片的 DXE 驱动
入手一个 Type-C 切换器
淘宝入手一个 Type-C 切换器,牌子是淇睿通。拿到收之后,实验了2台显示器(一个HDMI,一个 TypeC),3根Type-C 线(1根是同事正在用的),2台都无法正常点亮。



咨询了客服,经过一番折腾,还是没有点亮只好退货了。
使用 Type-C 显示器的时候,显示器上的摄像头是OK 的, 估计 USB 部分通的,但是 DP 部分信号出问题了。感觉这种东西应该还是有点技术含量的。
一个简单的JPEG解码库
最近在研究JPEG格式的编码(Encode)问题,偶然发现了一个好用的开源解码库(Decode)。项目地址:
https://github.com/brackeen/ok-file-formats
看起来比较简单,于是进行一些研究。
首先编写一个 console 读取指定jpeg 的尺寸:
#include <stdio.h>
#include <stdlib.h>
#include "ok_jpg.h"
#pragma warning(disable:4996)
int main() {
FILE* file = fopen("my_image.jpg", "rb");
ok_jpg image = ok_jpg_read(file, OK_JPG_COLOR_FORMAT_RGBA);
fclose(file);
if (image.data) {
printf("Got image! Size: %li x %li\n", (long)image.width, (long)image.height);
free(image.data);
}
return 0;
}
可以正常读取。
接下来试验一下复杂的,将结果显示在窗口中:
#include <windows.h>
#include <iostream>
#include <cstdint>
#include <memory>
#include <stdio.h>
#include <stdlib.h>
#include "ok_jpg.h"
#pragma warning(disable:4996)
// 添加这行来链接必要的库
#pragma comment(lib, "msimg32.lib")
// 全局变量
HWND g_hWnd = NULL;
const wchar_t* CLASS_NAME = L"RGBAImageWindow";
// 图像数据结构
struct ImageData {
uint8_t* data;
int width;
int height;
int channels; // 4 for RGBA
};
ImageData g_imageData = { nullptr, 0, 0, 4 };
// 创建测试用的 RGBA 图像数据
void CreateTestRGBAImage() {
FILE* file = fopen("./my_image.jpg", "rb");
ok_jpg image = ok_jpg_read(file, OK_JPG_COLOR_FORMAT_RGBA);
fclose(file);
g_imageData.width = image.width;
g_imageData.height = image.height;
g_imageData.channels = 4;
g_imageData.data = image.data;
}
// 将 RGBA 数据转换为 Windows 位图并绘制
void DrawRGBAImage(HDC hdc, const ImageData& imageData, int destX, int destY, int destWidth, int destHeight) {
if (!imageData.data || imageData.width <= 0 || imageData.height <= 0) {
return;
}
// 创建 DIB (Device Independent Bitmap) 信息
BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = imageData.width;
bmi.bmiHeader.biHeight = -imageData.height; // 负值表示从上到下
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; // RGBA = 32 bits
bmi.bmiHeader.biCompression = BI_RGB;
// 创建兼容的内存 DC
HDC memDC = CreateCompatibleDC(hdc);
void* pBits = nullptr;
HBITMAP hBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0);
if (hBitmap && pBits) {
// 将 RGBA 数据复制到位图 (注意:Windows 使用 BGRA 格式)
uint8_t* destData = static_cast<uint8_t*>(pBits);
for (int i = 0; i < imageData.width * imageData.height; i++) {
int srcIndex = i * 4;
int destIndex = i * 4;
destData[destIndex + 0] = imageData.data[srcIndex + 2]; // B
destData[destIndex + 1] = imageData.data[srcIndex + 1]; // G
destData[destIndex + 2] = imageData.data[srcIndex + 0]; // R
destData[destIndex + 3] = imageData.data[srcIndex + 3]; // A
}
// 选择位图到内存 DC
HBITMAP hOldBitmap = static_cast<HBITMAP>(SelectObject(memDC, hBitmap));
// 使用 AlphaBlend 支持透明度
BLENDFUNCTION blendFunc = {};
blendFunc.BlendOp = AC_SRC_OVER;
blendFunc.BlendFlags = 0;
blendFunc.SourceConstantAlpha = 255;
blendFunc.AlphaFormat = AC_SRC_ALPHA;
// 绘制到目标 DC,支持缩放
AlphaBlend(hdc, destX, destY, destWidth, destHeight,
memDC, 0, 0, imageData.width, imageData.height, blendFunc);
// 清理资源
SelectObject(memDC, hOldBitmap);
DeleteObject(hBitmap);
}
DeleteDC(memDC);
}
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 获取窗口客户区大小
RECT rect;
GetClientRect(hwnd, &rect);
// 设置背景为黑色
FillRect(hdc, &rect, static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
// 绘制 RGBA 图像,居中显示
if (g_imageData.data) {
int windowWidth = rect.right - rect.left;
int windowHeight = rect.bottom - rect.top;
// 计算缩放比例以适应窗口
float scaleX = static_cast<float>(windowWidth) / g_imageData.width;
float scaleY = static_cast<float>(windowHeight) / g_imageData.height;
float scale = min(scaleX, scaleY) * 0.8f; // 留一些边距
int scaledWidth = static_cast<int>(g_imageData.width * scale);
int scaledHeight = static_cast<int>(g_imageData.height * scale);
int x = (windowWidth - scaledWidth) / 2;
int y = (windowHeight - scaledHeight) / 2;
DrawRGBAImage(hdc, g_imageData, x, y, scaledWidth, scaledHeight);
}
// 绘制信息文本
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(255, 255, 255));
wchar_t info[256];
swprintf_s(info, L"RGBA Image: %dx%d pixels", g_imageData.width, g_imageData.height);
RECT textRect = rect;
textRect.top = rect.bottom - 30;
DrawText(hdc, info, -1, &textRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
EndPaint(hwnd, &ps);
return 0;
}
case WM_KEYDOWN:
switch (wParam) {
case VK_ESCAPE:
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
case VK_SPACE:
// 空格键重新生成图像
if (g_imageData.data) {
delete[] g_imageData.data;
}
CreateTestRGBAImage();
InvalidateRect(hwnd, NULL, TRUE);
break;
}
return 0;
case WM_SIZE:
// 窗口大小改变时重绘
InvalidateRect(hwnd, NULL, TRUE);
return 0;
case WM_CLOSE:
DestroyWindow(hwnd);
return 0;
case WM_DESTROY:
// 清理图像数据
if (g_imageData.data) {
delete[] g_imageData.data;
g_imageData.data = nullptr;
}
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
// 注册窗口类
bool RegisterWindowClass()
{
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
return RegisterClass(&wc) != 0;
}
// 创建窗口
HWND CreateAppWindow()
{
return CreateWindowEx(
0,
CLASS_NAME,
L"RGBA 图像显示窗口",
WS_OVERLAPPEDWINDOW,
100, 100,
800, 600,
NULL, NULL,
GetModuleHandle(NULL),
NULL
);
}
// 消息循环
void MessageLoop()
{
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// 从外部数据加载 RGBA 图像的函数
void LoadRGBAImage(uint8_t* data, int width, int height) {
// 清理旧数据
if (g_imageData.data) {
delete[] g_imageData.data;
}
g_imageData.width = width;
g_imageData.height = height;
g_imageData.channels = 4;
// 复制数据
size_t dataSize = width * height * 4;
g_imageData.data = new uint8_t[dataSize];
memcpy(g_imageData.data, data, dataSize);
// 刷新窗口
if (g_hWnd) {
InvalidateRect(g_hWnd, NULL, TRUE);
}
}
int main()
{
SetConsoleOutputCP(CP_UTF8);
std::wcout << L"=== RGBA 图像显示程序 ===" << std::endl;
std::wcout << L"正在创建窗口..." << std::endl;
if (!RegisterWindowClass())
{
std::wcout << L"❌ 注册窗口类失败!" << std::endl;
system("pause");
return -1;
}
g_hWnd = CreateAppWindow();
if (g_hWnd == NULL)
{
std::wcout << L"❌ 创建窗口失败!" << std::endl;
system("pause");
return -1;
}
// 创建测试图像数据
CreateTestRGBAImage();
ShowWindow(g_hWnd, SW_SHOW);
UpdateWindow(g_hWnd);
std::wcout << L"✅ 窗口创建成功!" << std::endl;
std::wcout << L"💡 提示:" << std::endl;
std::wcout << L" - ESC 键退出程序" << std::endl;
std::wcout << L" - 空格键重新生成图像" << std::endl;
std::wcout << L" - 关闭窗口退出程序" << std::endl;
MessageLoop();
std::wcout << L"程序已退出。" << std::endl;
return 0;
}
运行结果:

这个库不单单提供JPEG解码功能,完整的列表如下:
| Library | Description |
|---|---|
| ok_png | Reads PNG files. Supports Apple’s proprietary CgBI chunk. Tested against the PngSuite. |
| ok_jpg | Reads JPEG files. Baseline and progressive formats. Interprets EXIF orientation tags. No CMYK support. |
| ok_wav | Reads WAV and CAF files. PCM, u-law, a-law, and ADPCM formats. |
| ok_fnt | Reads AngelCode BMFont files. Binary format from AngelCode Bitmap Font Generator v1.10 or newer. |
| ok_csv | Reads Comma-Separated Values files. |
| ok_mo | Reads gettext MO files. |
完整的库下载:
有需要的朋友可以试试。
unresolved external symbol guard_check_icall$fo$ 问题
最近在调试一个程序,在设置了 Multi-threaded (/MT) 之后,编译时出现下午的错误
Error LNK2001 unresolved external symbol guard_check_icall$fo$
根据搜索结果,这个可能和 SDK 有关系,于是下载最新的 26100 的 SDK(26100.8038.260310-1641.ge_release_svc_im_WindowsSDK)安装后问题就解决了。
ESP32 Arduino HTTPS 的研究
在物联网获得数据的过程中,HTTP 和 HTTPS 还是有蛮大区别的。
- 可以使用下面的Curl 命令测试
curl -X POST https://httpbin.org/post -H "Content-Type: application/json" -d '{"name": "test", "value": "123"}'
返回值
{
"args": {},
"data": "{name: test, value: 123}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "24",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "curl/8.7.0",
"X-Amzn-Trace-Id": "Root=1-69ddf67f-75016f90629ad2556d1387f1"
},
"json": null,
"origin": "117.143.53.223",
"url": "https://httpbin.org/post"
}
2.编写 ESP32 Arduino 代码
#include <WiFi.h>
#include <WiFiClientSecure.h>
// 设置 Wi-Fi 凭据,
const char* ssid = "你的 WIFI 名称";
const char* password = "对应的密码";
// 目标服务器信息
const char* server = "httpbin.org"; // 域名
const int port = 443; // HTTPS 默认端口
const char* path = "/post"; // 请求路径
// 创建安全客户端对象
WiFiClientSecure client;
void setup() {
Serial.begin(115200);
delay(1000);
// 连接 Wi-Fi
Serial.println("正在连接 Wi-Fi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi 连接成功!");
Serial.print("IP 地址: ");
Serial.println(WiFi.localIP());
// 设置 HTTPS 客户端(跳过证书验证,仅用于测试)
client.setInsecure(); // 注意:生产环境中应使用证书验证
// 发送 POST 请求
sendPostRequest();
}
void loop() {
// 主循环为空,仅执行一次请求
}
void sendPostRequest() {
Serial.println("正在连接服务器...");
if (!client.connect(server, port)) {
Serial.println("连接服务器失败!");
return;
}
Serial.println("服务器连接成功!");
// 构建 POST 请求数据(JSON 格式)
String postData = "{\"name\":\"test\",\"value\":\"123\"}";
// 构建 HTTP 请求头
String request = "POST " + String(path) + " HTTP/1.1\r\n";
request += "Host: " + String(server) + "\r\n";
request += "Content-Type: application/json\r\n";
request += "Content-Length: " + String(postData.length()) + "\r\n";
request += "Connection: close\r\n\r\n"; // 关闭连接
request += postData;
// 发送请求
Serial.println("发送 POST 请求...");
client.print(request);
Serial.println("请求已发送!");
// 等待并读取响应
Serial.println("服务器响应:");
unsigned long timeout = millis();
while (client.connected() || client.available()) {
if (client.available()) {
String line = client.readStringUntil('\n');
Serial.println(line);
}
// 超时处理
if (millis() - timeout > 5000) {
Serial.println("响应超时!");
break;
}
}
// 断开连接
client.stop();
Serial.println("连接已关闭。");
}
运行结果串口输出如下:
请求已发送!
服务器响应:
HTTP/1.1 200 OK
Date: Tue, 14 Apr 2026 08:21:05 GMT
Content-Type: application/json
Content-Length: 412
Connection: close
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"args": {},
"data": "{\"name\":\"test\",\"value\":\"123\"}",
"files": {},
"form": {},
"headers": {
"Content-Length": "29",
"Content-Type": "application/json",
"Host": "httpbin.org",
"X-Amzn-Trace-Id": "Root=1-69ddf8f1-007fb8cc07ce50765cddb2f1"
},
"json": {
"name": "test",
"value": "123"
},
"origin": "117.143.53.223",
"url": "https://httpbin.org/post"
}
[ 7009][E][ssl_client.cp
另外一个例子,看起来更容易理解一些:
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
// WiFi凭据
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";
void setup() {
Serial.begin(115200);
// 连接WiFi
WiFi.begin(ssid, password);
Serial.print("连接WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi连接成功!");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
// 发送HTTPS POST请求
sendHttpsPostRequest();
}
void loop() {
// 空循环
}
void sendHttpsPostRequest() {
// 创建WiFiClientSecure对象用于HTTPS
WiFiClientSecure *client = new WiFiClientSecure;
if(client) {
// 设置为不验证SSL证书(用于测试,生产环境建议验证证书)
client->setInsecure();
// 或者如果你想验证证书,可以设置根CA证书
// client->setCACert(rootCACertificate);
HTTPClient https;
Serial.println("[HTTPS] 开始连接...");
if (https.begin(*client, "https://httpbin.org/post")) {
Serial.println("[HTTPS] 连接成功");
// 设置HTTP头
https.addHeader("Content-Type", "application/json");
https.addHeader("Token", "abc");
// 准备JSON数据
String jsonPayload = "{\"cityCode\":\"310100\",\"isGetStopArrive\":\"1\",\"lon\":\"121.3747863\",\"lat\":\"31.11027359\",\"limit\":20,\"offset\":0,\"coordinateType\":1}";
Serial.println("发送HTTPS POST请求到: https://httpbin.org/post");
Serial.println("请求头: Token=abc");
Serial.println("请求体: " + jsonPayload);
// 发送POST请求
int httpResponseCode = https.POST(jsonPayload);
if (httpResponseCode > 0) {
String response = https.getString();
Serial.println("HTTPS响应代码: " + String(httpResponseCode));
Serial.println("响应内容:");
Serial.println(response);
} else {
Serial.println("HTTPS请求失败");
Serial.println("错误代码: " + String(httpResponseCode));
Serial.println("错误信息: " + https.errorToString(httpResponseCode));
}
// 关闭连接
https.end();
} else {
Serial.println("[HTTPS] 无法连接");
}
// 删除客户端对象
delete client;
} else {
Serial.println("无法创建WiFiClientSecure客户端");
}
}
Step to UEFI (303)更改 Memmap
之前提到过,UEFI 上没有 E820 ,而是通过gBS->GetMemoryMap() 来获得当前内存分配情况,这里给出了一个驱动代码,可以在UEFI Shell 下加载,他会替换之前的GetMemoryMap() 函数,基于之前的返回值增加一个条目:0xFED00000-0xFED1FFFF ,报告为 available 。
HookMemoryMapDxe.c
/** @file
Memory Map Hook DXE Driver
Hooks GetMemoryMap to add a fake memory region
**/
#include <Uefi.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
// Hook Protocol GUID
#define MEMORY_MAP_HOOK_PROTOCOL_GUID \
{ 0x87654321, 0x4321, 0x8765, { 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90 } }
EFI_GUID gMemoryMapHookProtocolGuid = MEMORY_MAP_HOOK_PROTOCOL_GUID;
// Hook数据结构
typedef struct {
UINT32 Signature;
EFI_GET_MEMORY_MAP OriginalGetMemoryMap;
EFI_PHYSICAL_ADDRESS FakeMemoryStart;
UINT64 FakeMemoryPages;
BOOLEAN HookEnabled;
} MEMORY_MAP_HOOK_DATA;
#define HOOK_SIGNATURE SIGNATURE_32('H','M','A','P')
// 全局变量
STATIC MEMORY_MAP_HOOK_DATA *gHookData = NULL;
STATIC EFI_HANDLE gProtocolHandle = NULL;
STATIC EFI_EVENT gExitBootServicesEvent = NULL;
/**
Hooked GetMemoryMap function
**/
EFI_STATUS
EFIAPI
HookedGetMemoryMap (
IN OUT UINTN *MemoryMapSize,
IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap,
OUT UINTN *MapKey,
OUT UINTN *DescriptorSize,
OUT UINT32 *DescriptorVersion
)
{
EFI_STATUS Status;
UINTN OriginalMapSize;
EFI_MEMORY_DESCRIPTOR *OriginalMap;
UINTN NewMapSize;
UINTN EntryCount;
UINTN Index;
BOOLEAN NeedToAddEntry = TRUE;
// 检查Hook是否启用
if (gHookData == NULL || !gHookData->HookEnabled || gHookData->OriginalGetMemoryMap == NULL) {
return EFI_UNSUPPORTED;
}
// 如果MemoryMap为NULL,只是查询大小
if (MemoryMap == NULL) {
Status = gHookData->OriginalGetMemoryMap(MemoryMapSize, MemoryMap, MapKey, DescriptorSize, DescriptorVersion);
if (Status == EFI_BUFFER_TOO_SMALL) {
// 为新增的条目预留空间
*MemoryMapSize += *DescriptorSize;
}
return Status;
}
// 分配临时缓冲区
OriginalMapSize = *MemoryMapSize + *DescriptorSize;
OriginalMap = AllocatePool(OriginalMapSize);
if (OriginalMap == NULL) {
return EFI_OUT_OF_RESOURCES;
}
// 调用原始的GetMemoryMap
Status = gHookData->OriginalGetMemoryMap(&OriginalMapSize, OriginalMap, MapKey, DescriptorSize, DescriptorVersion);
if (EFI_ERROR(Status)) {
FreePool(OriginalMap);
return Status;
}
EntryCount = OriginalMapSize / *DescriptorSize;
// 检查是否已经存在重叠的内存区域
for (Index = 0; Index < EntryCount; Index++) {
EFI_MEMORY_DESCRIPTOR *Entry = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)OriginalMap + Index * (*DescriptorSize));
EFI_PHYSICAL_ADDRESS EntryStart = Entry->PhysicalStart;
EFI_PHYSICAL_ADDRESS EntryEnd = EntryStart + (Entry->NumberOfPages * EFI_PAGE_SIZE) - 1;
// 检查是否与新条目重叠
if ((gHookData->FakeMemoryStart >= EntryStart && gHookData->FakeMemoryStart <= EntryEnd) ||
(gHookData->FakeMemoryStart + (gHookData->FakeMemoryPages * EFI_PAGE_SIZE) - 1 >= EntryStart &&
gHookData->FakeMemoryStart + (gHookData->FakeMemoryPages * EFI_PAGE_SIZE) - 1 <= EntryEnd)) {
NeedToAddEntry = FALSE;
break;
}
}
// 计算新的内存映射大小
if (NeedToAddEntry) {
NewMapSize = OriginalMapSize + *DescriptorSize;
} else {
NewMapSize = OriginalMapSize;
}
// 检查提供的缓冲区是否足够大
if (*MemoryMapSize < NewMapSize) {
*MemoryMapSize = NewMapSize;
FreePool(OriginalMap);
return EFI_BUFFER_TOO_SMALL;
}
// 复制原始内存映射
CopyMem(MemoryMap, OriginalMap, OriginalMapSize);
// 如果需要,添加新的内存条目
if (NeedToAddEntry) {
EFI_MEMORY_DESCRIPTOR *NewEntry = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)MemoryMap + OriginalMapSize);
NewEntry->Type = EfiConventionalMemory; // Available memory
NewEntry->PhysicalStart = gHookData->FakeMemoryStart;
NewEntry->VirtualStart = 0;
NewEntry->NumberOfPages = gHookData->FakeMemoryPages;
NewEntry->Attribute = EFI_MEMORY_WB; // Write-back cacheable
}
*MemoryMapSize = NewMapSize;
FreePool(OriginalMap);
return EFI_SUCCESS;
}
/**
ExitBootServices event handler
**/
VOID
EFIAPI
ExitBootServicesNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
// 在ExitBootServices之前恢复原始函数指针
if (gHookData != NULL && gHookData->HookEnabled && gHookData->OriginalGetMemoryMap != NULL) {
gBS->GetMemoryMap = gHookData->OriginalGetMemoryMap;
// 更新CRC32
gBS->Hdr.CRC32 = 0;
gBS->CalculateCrc32((UINT8 *)gBS, gBS->Hdr.HeaderSize, &gBS->Hdr.CRC32);
gHookData->HookEnabled = FALSE;
}
}
/**
Driver entry point
@param ImageHandle The image handle
@param SystemTable The system table
@retval EFI_SUCCESS Driver loaded successfully
**/
EFI_STATUS
EFIAPI
HookMemoryMapDxeEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
DEBUG((DEBUG_INFO, "HookMemoryMapDxe: Driver starting...\n"));
// 分配Hook数据结构
gHookData = AllocateRuntimeZeroPool(sizeof(MEMORY_MAP_HOOK_DATA));
if (gHookData == NULL) {
DEBUG((DEBUG_ERROR, "HookMemoryMapDxe: Failed to allocate hook data\n"));
return EFI_OUT_OF_RESOURCES;
}
// 初始化Hook数据
gHookData->Signature = HOOK_SIGNATURE;
gHookData->OriginalGetMemoryMap = gBS->GetMemoryMap;
gHookData->FakeMemoryStart = 0xFED00000; // 0xFED00000-0xFED1FFFF
gHookData->FakeMemoryPages = 0x20000 / EFI_PAGE_SIZE; // 128KB = 32 pages
gHookData->HookEnabled = FALSE;
// 安装Protocol (用于标识和查找)
Status = gBS->InstallProtocolInterface(
&gProtocolHandle,
&gMemoryMapHookProtocolGuid,
EFI_NATIVE_INTERFACE,
gHookData
);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "HookMemoryMapDxe: Failed to install protocol: %r\n", Status));
FreePool(gHookData);
gHookData = NULL;
return Status;
}
// 创建ExitBootServices事件
Status = gBS->CreateEvent(
EVT_SIGNAL_EXIT_BOOT_SERVICES,
TPL_NOTIFY,
ExitBootServicesNotify,
NULL,
&gExitBootServicesEvent
);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_WARN, "HookMemoryMapDxe: Failed to create ExitBootServices event: %r\n", Status));
// 不是致命错误,继续执行
}
// 安装Hook
gBS->GetMemoryMap = HookedGetMemoryMap;
// 更新CRC32
gBS->Hdr.CRC32 = 0;
gBS->CalculateCrc32((UINT8 *)gBS, gBS->Hdr.HeaderSize, &gBS->Hdr.CRC32);
// 启用Hook
gHookData->HookEnabled = TRUE;
DEBUG((DEBUG_INFO, "HookMemoryMapDxe: Driver loaded successfully\n"));
DEBUG((DEBUG_INFO, "HookMemoryMapDxe: Hook installed and enabled\n"));
DEBUG((DEBUG_INFO, "HookMemoryMapDxe: Fake memory region: 0x%lx-0x%lx\n",
gHookData->FakeMemoryStart,
gHookData->FakeMemoryStart + (gHookData->FakeMemoryPages * EFI_PAGE_SIZE) - 1));
return EFI_SUCCESS;
}
HookMemoryMapDxe.inf
这个代码需要放置在MdeModulePkg下面,然后在MdeModulePkg.dsc添加:
[PcdsDynamicExDefault]
gEfiMdeModulePkgTokenSpaceGuid.PcdRecoveryFileName|L"FVMAIN.FV"
[Components]
MdeModulePkg/HookMemoryMapDxe/HookMemoryMapDxe.inf
MdeModulePkg/Application/HelloWorld/HelloWorld.inf
MdeModulePkg/Application/DumpDynPcd/DumpDynPcd.inf
MdeModulePkg/Application/MemoryProfileInfo/MemoryProfileInfo.inf
之后,使用下面的命令进行编译即可:
build -a X64 -p MdeModulePkg/MdeModulePkg.dsc -t VS2019
完整的代码下载:
使用时,只需要在 UEFI Shell 下 load HookMemoryMapDxe.efi 即可,之后可以使用 memmap 命令看到改动结果。
特别注意:这里只是一个Demo ,实际使用时需要根据你的需求进行修改。
pbatard 的轻量级UEFI 编译环境
pbatard 在GitHub 上提供了一个轻量级UEFI 编译环境(https://github.com/pbatard/uefi-simple),用户可以直接在 Visual Studio 中直接创建项目,然后使用 GNU-EFI 来进行编译。
下载之后可以看到项目提供了一个 VC 的工程文件作为例子,可以使用 VS2019 直接打开。

打开之后编译会发生错误,因为这个项目是 VS2022 的。修改的方法是在项目中搜索所有的 V143 字样,修改为 V142
比如,uefi-simple.vs\msvc\uefi-simple.vcxproj 中的下面<PlatformToolset> 中给出的
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
另外还要修改项目的属性中, Platform Toolset 为 v142

修改之后就可以直接 Build 出来需要的 EFI 文件了(main.c)

在模拟器中运行结果如下:

修改后的,能直接在 VS2019 下编译的可以在这里下载
DS3231 中断引脚问题
仍然使用之前提到的模块 https://www.lab-z.com/ds3231165/ 。这个模块已经有内部上拉了,因此中断 Pin 无需再额外上拉。
这次遇到的问题是中断经常触发,打开Alarm1 后就立即触发。经过研究发现这是因为没有清除A1F 导致的,之前触发过Alarm1 中断后这个Flag 就会一直处于 1(触发状态)。当再次Enable Alarm1 后,就会继续拉低 Interrupt

解决方法是在关闭 Alarm 1的时候多做一个动作:
// 特别注意必须用下面函数清除 Interrupt Flag 否则只要 Enable Alarm 就会触发
myRTC.checkIfAlarm(1);
// 关闭 Alarm1
myRTC.turnOffAlarm(1);
完整的测试代码如下:
#include <Wire.h>
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <DS3231.h>
DS3231 myRTC;
RTClib CurrentTime;
WiFiMulti wifiMulti;
JsonDocument doc;
const char* ssid = "CMCC-TSR6739";
const char* password = "!!1783az";
void showdate() {
bool century;
bool h12Flag;
bool pmFlag;
Serial.print(myRTC.getYear(), DEC);
Serial.print("-");
Serial.print(myRTC.getMonth(century), DEC);
Serial.print("-");
Serial.print(myRTC.getDate(), DEC);
Serial.print(" ");
Serial.print(myRTC.getDoW(), DEC);
Serial.print(" ");
Serial.print(myRTC.getHour(h12Flag, pmFlag), DEC); //24-hr
Serial.print(":");
Serial.print(myRTC.getMinute(), DEC);
Serial.print(":");
Serial.println(myRTC.getSecond(), DEC);
}
void setup() {
wifiMulti.addAP(ssid, password);
Serial.begin(115200);
Serial.println("Start");
Wire.begin();
delay(100);
myRTC.setClockMode(false); // set to 24h
pinMode(14, INPUT_PULLUP);
}
// 设置 seconds 秒后的闹钟
void SetAlart(uint32_t seconds) {
DateTime Current = CurrentTime.now();
printf("%d\n", Current.unixtime());
printf("%d %d %d %d %d %d \n",
Current.year(), Current.month(), Current.day(),
Current.hour(), Current.minute(), Current.second());
Current = DateTime(Current.unixtime() + seconds);
printf("%d\n", Current.unixtime());
printf("%d %d %d %d %d %d \n",
Current.year(), Current.month(), Current.day(),
Current.hour(), Current.minute(), Current.second());
// 1分钟后触发
myRTC.setA1Time(Current.day(), Current.hour(), Current.minute(), Current.second(), 0, false, false, false);
// myRTC.setA1Time(Current.day(), Current.hour(), Current.minute(), Current.second()+20, 0, false, false, false);
// 打开闹钟
myRTC.turnOnAlarm(1);
}
uint8_t geti2c(uint8_t reg) {
Wire.beginTransmission(0x68);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(0x68, 1);
return Wire.read();
}
unsigned long char_to_uint32(const char* t) {
unsigned long result = 0;
for (int i = 0; i < 14; i++) {
if (t[i] >= '0' && t[i] <= '9') {
result = result * 10 + (t[i] - '0');
}
}
result = result + 8 * 3600;
return result;
}
void loop() {
while (Serial.available()) {
char c = Serial.read();
if (c == '1') {
// 从互联网取得当前时间
Serial.print("测试:从互联网获得当前时间, 并且设置给 RTC:");
// 从互联网取得时间
if ((wifiMulti.run() == WL_CONNECTED)) {
HTTPClient http;
//http.begin("http://quan.suning.com/getSysTime.do"); //HTTP
http.begin("http://api.k780.com/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json");
int httpCode = http.GET();
// httpCode will be negative on error
if (httpCode > 0) {
String payload = http.getString();
// 解析JSON数据
DeserializationError error = deserializeJson(doc, payload);
Serial.println(doc["result"]["timestamp"].as<const char*>());
myRTC.adjust(char_to_uint32(doc["result"]["timestamp"].as<const char*>()));
} else {
Serial.println("Http 访问出错");
}
http.end();
} else {
Serial.println("WIFI 未连接");
}
Serial.print("测试结束");
}
if (c == '2') {
// 显示当前时间
showdate();
}
if (c == '3') {
// 设置 20秒后触发中断,对应引脚会变低
SetAlart(20);
}
if (c == '4') {
// 关闭闹钟
Serial.println("TurnOff Alarm");
// 特别注意必须用下面函数清除 Interrupt Flag 否则只要 Enable Alarm 就会触发
myRTC.checkIfAlarm(1);
// 关闭 Alarm1
myRTC.turnOffAlarm(1);
}
if (c == '5') {
// 检查闹钟状态
if (myRTC.checkAlarmEnabled(1)) {
Serial.println("闹钟 Enabled");
if (myRTC.checkIfAlarm(1, false)) {
Serial.println("闹钟已触发");
} else {
Serial.println("闹钟未触发");
}
} else {
Serial.println("闹钟 Disabled");
}
}
if (c == '6') {
// 输出当前闹钟设定的时间
byte alarmDay, alarmHour, alarmMinute, alarmSecond, alarmBits;
bool alarmDy, alarmH12Flag, alarmPmFlag;
// Display Alarm 1 information
myRTC.getA1Time(alarmDay, alarmHour, alarmMinute, alarmSecond, alarmBits, alarmDy, alarmH12Flag, alarmPmFlag);
Serial.print(alarmDay, DEC);
if (alarmDy) {
Serial.print(" DoW");
} else {
Serial.print(" Date");
}
Serial.print(' ');
Serial.print(alarmHour, DEC);
Serial.print(' ');
Serial.print(alarmMinute, DEC);
Serial.print(' ');
Serial.print(alarmSecond, DEC);
Serial.print(' ');
if (alarmH12Flag) {
if (alarmPmFlag) {
Serial.print("pm ");
} else {
Serial.print("am ");
}
}
Serial.printf("alarmBits:%x %x \n", alarmBits, alarmDy);
Serial.println(' ');
}
if (c == '7') {
// 寄存器检测
printf("0x07: %d\n", geti2c(0x07));
printf("0x08: %d\n", geti2c(0x08));
printf("0x09: %d\n", geti2c(0x09));
printf("0x0A: %d\n", geti2c(0x0A));
printf("0x0E: %d\n", geti2c(0x0E));
printf("0x0F: %d\n", geti2c(0x0F));
}
}
}