Step to UEFI (306)JPEG 图片转PPM 程序

这次带来的是一个 Jpeg图片Decode 工具,和之前的显示 Jpeg图片工具类似。相关代码来自 https://github.com/xbarin02/jpeg 项目。修正了一些 UEFI 下的编译问题,主要有:

  1. EDK2 没有提供 roundf()/sqrtf()/cosf()函数,解决方法是通过下面的调用内部的其他函数。
float sqrtf(float x) {
    return sqrt(x);
}

float cosf(float x) {
    return cos(x);
}

float roundf(float x) {
    if (x >= 0.0f) {
        return (float)(INT32)(x + 0.5f);
    } else {
        return (float)(INT32)(x - 0.5f);
    }
}
  1. 源代码中设用了 PRIu16/SCNu16/PRIu8 这样的宏定义,编译时会出现错误。对应的都使用手工方式进行了修改。

最终,完整的主程序decoder.c:

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <assert.h>
#include "common.h"
#include "io.h"
#include "huffman.h"
#include "coeffs.h"
#include "imgproc.h"
#include "frame.h"
#include  <inttypes.h>

const char *Pq_to_str[] = {
	[0] = "8-bit",
	[1] = "16-bit"
};

/* B.2.4.1 Quantization table-specification syntax */
int parse_qtable(FILE *stream, struct context *context)
{
	int err;
	uint8_t Pq, Tq;
	struct qtable *qtable;

	assert(context != NULL);

	err = read_nibbles(stream, &Pq, &Tq);
	RETURN_IF(err);

	if (Tq >= 4) {
		/* invalid value */
		return RET_FAILURE_FILE_UNSUPPORTED;
	}

	assert(Tq < 4);
	assert(Pq < 2);

	//printf("Pq = %" PRIu8 " (%s), Tq = %" PRIu8 " (QT identifier)\n", Pq, Pq_to_str[Pq], Tq);

	qtable = &context->qtable[Tq];

	/* precision */
	qtable->Pq = Pq;

	for (int i = 0; i < 64; ++i) {
		if (Pq == 0) {
			uint8_t byte;
			err = read_byte(stream, &byte);
			RETURN_IF(err);
			qtable->Q[zigzag[i]] = (uint16_t)byte;
		} else {
			uint16_t word;
			err = read_word(stream, &word);
			RETURN_IF(err);
			qtable->Q[zigzag[i]] = word;
		}
	}

	for (int y = 0; y < 8; ++y) {
		for (int x = 0; x < 8; ++x) {
			//printf("%3" PRIu16 " ", qtable->Q[y * 8 + x]);
		}
		printf("\n");
	}

	return RET_SUCCESS;
}

int parse_frame_header(FILE *stream, struct context *context)
{
	int err;
	/* Sample precision */
	uint8_t P;
	/* Number of lines, Number of samples per line */
	uint16_t Y, X;
	/* Number of image components in frame */
	uint8_t Nf;

	assert(context != NULL);

	err = read_byte(stream, &P);
	RETURN_IF(err);
	err = read_word(stream, &Y);
	RETURN_IF(err);
	err = read_word(stream, &X);
	RETURN_IF(err);
	err = read_byte(stream, &Nf);
	RETURN_IF(err);

	assert(X > 0);
	assert(Nf > 0);

	//printf("P = %" PRIu8 " (Sample precision), Y = %" PRIu16 ", X = %" PRIu16 ", Nf = %" PRIu8 " (Number of image components)\n", P, Y, X, Nf);

	/* precision */
	context->P = P;

	context->Y = Y;
	context->X = X;

	/* components */
	context->Nf = Nf;

	uint8_t max_H = 0, max_V = 0;

	for (int i = 0; i < Nf; ++i) {
		uint8_t C;
		uint8_t H, V;
		uint8_t Tq;

		err = read_byte(stream, &C);
		RETURN_IF(err);
		err = read_nibbles(stream, &H, &V);
		RETURN_IF(err);
		err = read_byte(stream, &Tq);
		RETURN_IF(err);

		//printf("C = %" PRIu8 " (Component identifier), H = %" PRIu8 ", V = %" PRIu8 ", Tq = %" PRIu8 " (QT identifier)\n", C, H, V, Tq);

		context->component[C].H = H;
		context->component[C].V = V;
		context->component[C].Tq = Tq;

		max_H = (H > max_H) ? H : max_H;
		max_V = (V > max_V) ? V : max_V;
	}

	context->max_H = max_H;
	context->max_V = max_V;

	err = compute_no_blocks_and_alloc_buffers(context);
	RETURN_IF(err);

	return RET_SUCCESS;
}

const char *Tc_to_str[] = {
	[0] = "DC",
	[1] = "AC"
};

int parse_huffman_tables(FILE *stream, struct context *context)
{
	int err;
	uint8_t Tc, Th;

	assert(context != NULL);

	err = read_nibbles(stream, &Tc, &Th);
	RETURN_IF(err);

	if (Tc >= 2) {
		return RET_FAILURE_FILE_UNSUPPORTED;
	}

	assert(Tc < 2);

	//printf("Tc = %" PRIu8 " (%s table) Th = %" PRIu8 " (HT identifier)\n", Tc, Tc_to_str[Tc], Th);

	struct htable *htable = &context->htable[Tc][Th];

	for (int i = 0; i < 16; ++i) {
		err = read_byte(stream, &htable->L[i]);
		RETURN_IF(err);
	}

	for (int i = 0; i < 16; ++i) {
		uint8_t L = htable->L[i];

		for (int l = 0; l < L; ++l) {
			err = read_byte(stream, &htable->V[i][l]);
			RETURN_IF(err);
		}
	}

	/* Annex C */
	struct hcode *hcode = &context->hcode[Tc][Th];

	err = conv_htable_to_hcode(htable, hcode);
	RETURN_IF(err);

	return RET_SUCCESS;
}

struct scan {
	uint8_t Ns;
	uint8_t Cs[256];

	/* useful to remove differential DC coding
	 *
	 * At the beginning of the scan and at the beginning of each restart interval, the prediction for the DC coefficient prediction
	 * is initialized to 0. */
	struct int_block *last_block[256];
};

int parse_scan_header(FILE *stream, struct context *context, struct scan *scan)
{
	int err;
	/* Number of image components in scan */
	uint8_t Ns;

	err = read_byte(stream, &Ns);
	RETURN_IF(err);

	//printf("Ns = %" PRIu8 " (Number of image components in scan)\n", Ns);

	assert(scan != NULL);

	scan->Ns = Ns;

	for (int j = 0; j < Ns; ++j) {
		uint8_t Cs;
		uint8_t Td, Ta;

		err = read_byte(stream, &Cs);
		RETURN_IF(err);
		err = read_nibbles(stream, &Td, &Ta);
		RETURN_IF(err);

		//printf("Cs%i = %" PRIu8 " (Component identifier), Td%i = %" PRIu8 " (DC HT identifier), Ta%i = %" PRIu8 " (AC HT identifier)\n", j, Cs, j, Td, j, Ta);

		scan->Cs[j] = Cs;

		context->component[Cs].Td = Td;
		context->component[Cs].Ta = Ta;
	}

	uint8_t Ss;
	uint8_t Se;
	uint8_t Ah, Al;

	err = read_byte(stream, &Ss);
	RETURN_IF(err);
	err = read_byte(stream, &Se);
	RETURN_IF(err);
	err = read_nibbles(stream, &Ah, &Al);
	RETURN_IF(err);

	if (Ss != 0 || Se != 63) {
		return RET_FAILURE_FILE_UNSUPPORTED;
	}

	assert(Ss == 0);
	assert(Se == 63);
	//printf("Ss = %" PRIu8 " (the first DCT coefficient), Se = %" PRIu8 " (the last DCT coefficient)\n", Ss, Se);

	if (Ah != 0 || Al != 0) {
		return RET_FAILURE_FILE_UNSUPPORTED;
	}

	assert(Ah == 0);
	assert(Al == 0);
	//printf("Ah = %" PRIu8 " (bit position high), Al = %" PRIu8 " (bit position low)\n", Ah, Al);

	context->mblocks = 0;

	return RET_SUCCESS;
}

/* read MCU */
int read_macroblock(struct bits *bits, struct context *context, struct scan *scan)
{
	int err;

	assert(scan != NULL);
	assert(context != NULL);

	size_t seq_no = context->mblocks;

	if (scan->Ns == 0) {
		/* nothing to do */
		return RET_FAILURE_NO_MORE_DATA;
	} else if (scan->Ns == 1) {
		/* A.2.2 Non-interleaved order (Ns = 1) */
		assert(scan->Ns == 1);

		uint8_t Cs = scan->Cs[0];

		uint8_t H = context->component[Cs].H;
		uint8_t V = context->component[Cs].V;

		size_t blocks_in_mb = H * V;

		for (size_t w = 0; w < blocks_in_mb; ++w) {
			size_t block_x = (blocks_in_mb * seq_no + w) % context->component[Cs].b_x;
			size_t block_y = (blocks_in_mb * seq_no + w) / context->component[Cs].b_x;

			size_t block_seq = block_y * context->component[Cs].b_x + block_x;

			struct int_block *int_block = &context->component[Cs].int_buffer[block_seq];

			/* read block */
			err = read_block(bits, context, Cs, int_block);
			RETURN_IF(err);

			if (scan->last_block[Cs] != NULL) {
				int_block->c[0] += scan->last_block[Cs]->c[0];
			}

			scan->last_block[Cs] = int_block;
		}
	} else {
		assert(scan->Ns > 1);

		if (context->m_x == 0) {
			/* missing SOF before SOS? */
			return RET_FAILURE_FILE_UNSUPPORTED;
		}

		assert(context->m_x != 0);

		size_t x = seq_no % context->m_x;
		size_t y = seq_no / context->m_x;

// 		printf("[DEBUG] reading macroblock... x=%zu y=%zu\n", x, y);

		/* for each component */
		for (int j = 0; j < scan->Ns; ++j) {
			uint8_t Cs = scan->Cs[j];
			uint8_t H = context->component[Cs].H;
			uint8_t V = context->component[Cs].V;

// 			printf("[DEBUG] reading component %" PRIu8 " blocks @ x=%zu y=%zu\n", Cs, x * H, y * V);

			/* for each 8x8 block */
			for (int v = 0; v < V; ++v) {
				for (int h = 0; h < H; ++h) {
					size_t block_x = x * H + h;
					size_t block_y = y * V + v;

					assert(block_x < context->component[Cs].b_x);

					size_t block_seq = block_y * context->component[Cs].b_x + block_x;

// 					printf("[DEBUG] reading component %" PRIu8 " blocks @ x=%zu y=%zu out of X=%zu Y=%zu\n", Cs, x * H + h, y * V + v, context->component[Cs].b_x, context->component[Cs].b_y);
// 					printf("[DEBUG] reading component %" PRIu8 " block# %zu out of %zu\n", Cs, block_seq, context->component[Cs].b_x * context->component[Cs].b_y);

					struct int_block *int_block = &context->component[Cs].int_buffer[block_seq];

					/* past the end of data? */
					if (block_seq >= context->component[Cs].b_x * context->component[Cs].b_y) {
						int_block = NULL;
					}

					/* read block */
					err = read_block(bits, context, Cs, int_block);
					RETURN_IF(err);

					/* remove differential DC coding */
					if (scan->last_block[Cs] != NULL) {
						int_block->c[0] += scan->last_block[Cs]->c[0];
					}

					scan->last_block[Cs] = int_block;
				}
			}
		}
	}

	return RET_SUCCESS;
}

int read_ecs(FILE *stream, struct context *context, struct scan *scan)
{
	int err;
	struct bits bits;

	init_bits(&bits, stream);

	for (int i = 0; i < 256; ++i) {
		scan->last_block[i] = NULL;
	}

	/* loop over macroblocks */
	do {
		err = read_macroblock(&bits, context, scan);
		if (err == RET_FAILURE_NO_MORE_DATA)
			goto end;
		RETURN_IF(err);
		context->mblocks++;
	} while (1);

end:
	printf("Processed: %zu macroblocks\n", context->mblocks);

	return RET_SUCCESS;
}

int parse_restart_interval(FILE *stream, struct context *context)
{
	int err;
	uint16_t Ri;

	err = read_word(stream, &Ri);
	RETURN_IF(err);

	context->Ri = Ri;

	return RET_SUCCESS;
}

int parse_comment(FILE *stream, uint16_t len)
{
	if (len < 2) {
		return RET_FAILURE_FILE_UNSUPPORTED;
	}

	assert(len >= 2);

	size_t l = len - 2;

	char *buf = malloc(l + 1);

	if (buf == NULL) {
		return RET_FAILURE_MEMORY_ALLOCATION;
	}

	if (fread(buf, sizeof(char), l, stream) != l) {
		free(buf);
		return RET_FAILURE_FILE_IO;
	}

	buf[l] = 0;

	printf("%s\n", buf);

	free(buf);

	return RET_SUCCESS;
}

int write_image(struct context *context, const char *path)
{
	int err;

	struct frame frame;

	err = frame_create(context, &frame);
	RETURN_IF(err);

	err = frame_to_rgb(&frame);

	if (err) {
		goto end;
	}

	err = write_frame(&frame, path);

end:
	frame_destroy(&frame);

	return err;
}

int epilogue(struct context *context, const char *path)
{
	int err;

	err = dequantize(context);
	RETURN_IF(err);
	err = inverse_dct(context);
	RETURN_IF(err);
	err = conv_blocks_to_frame(context);
	RETURN_IF(err);
	err = write_image(context, path);
	RETURN_IF(err);

	return RET_SUCCESS;
}

int parse_format(FILE *stream, struct context *context, const char *path)
{
	int err;

	struct scan scan;

	// init
	scan.Ns = 0;

	while (1) {
		uint16_t marker;

		err = read_marker(stream, &marker);
		RETURN_IF(err);

		/* An asterisk (*) indicates a marker which stands alone,
		 * that is, which is not the start of a marker segment. */
		switch (marker) {
			uint16_t len;
			long pos;

			/* SOI* Start of image */
			case 0xffd8:
				printf("SOI\n");
				break;
			/* APPn */
			case 0xffe0:
			case 0xffe1:
			case 0xffe2:
			case 0xffe3:
			case 0xffe4:
			case 0xffe5:
			case 0xffe6:
			case 0xffe7:
			case 0xffe8:
			case 0xffeb:
			case 0xffec:
			case 0xffed:
			case 0xffee:
				printf("APP%i\n", marker & 0xf);
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = skip_segment(stream, len);
				RETURN_IF(err);
				break;
			/* DQT Define quantization table(s) */
			case 0xffdb:
				printf("DQT\n");
				pos = ftell(stream);
				err = read_length(stream, &len);
				RETURN_IF(err);
				do {
					err = parse_qtable(stream, context);
					RETURN_IF(err);
				} while (ftell(stream) < pos + len);
				break;
			/* SOF0 Baseline DCT */
			case 0xffc0:
				printf("SOF0\n");
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = parse_frame_header(stream, context);
				RETURN_IF(err);
				break;
			/* SOF1 Extended sequential DCT */
			case 0xffc1:
				printf("SOF1\n");
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = parse_frame_header(stream, context);
				RETURN_IF(err);
				break;
			/* SOF2 Progressive DCT */
			case 0xffc2:
				printf("SOF2\n");
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = parse_frame_header(stream, context);
				RETURN_IF(err);
				fprintf(stderr, "Progressive DCT not supported!\n");
				return RET_FAILURE_FILE_UNSUPPORTED;
			/* SOF3 Lossless (sequential) */
			case 0xffc3:
				printf("SOF3\n");
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = parse_frame_header(stream, context);
				RETURN_IF(err);
				fprintf(stderr, "Lossless JPEG not supported!\n");
				return RET_FAILURE_FILE_UNSUPPORTED;
			/* SOF9 Extended sequential DCT (arithmetic coding) */
			case 0xffc9:
				printf("SOF9\n");
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = parse_frame_header(stream, context);
				RETURN_IF(err);
				fprintf(stderr, "Arithmetic coding not supported!\n");
				return RET_FAILURE_FILE_UNSUPPORTED;
			/* SOF10 Progressive DCT (arithmetic coding) */
			case 0xffca:
				printf("SOF10\n");
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = parse_frame_header(stream, context);
				RETURN_IF(err);
				fprintf(stderr, "Arithmetic coding not supported!\n");
				return RET_FAILURE_FILE_UNSUPPORTED;
			/* DHT Define Huffman table(s) */
			case 0xffc4:
				printf("DHT\n");
				pos = ftell(stream);
				err = read_length(stream, &len);
				RETURN_IF(err);
				/* parse multiple tables in single DHT */
				do {
					err = parse_huffman_tables(stream, context);
					RETURN_IF(err);
				} while (ftell(stream) < pos + len);
				break;
			/* SOS Start of scan */
			case 0xffda:
				printf("SOS\n");
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = parse_scan_header(stream, context, &scan);
				RETURN_IF(err);
				err = read_ecs(stream, context, &scan);
				RETURN_IF(err);
				break;
			/* EOI* End of image */
			case 0xffd9:
				printf("EOI\n");
				pos = ftell(stream);
				fseek(stream, 0, SEEK_END);
				if (ftell(stream) - pos > 0) {
					printf("*** %li bytes of garbage ***\n", ftell(stream) - pos);
				}
				err = epilogue(context, path);
				RETURN_IF(err);
				return RET_SUCCESS;
			/* DRI Define restart interval */
			case 0xffdd:
				printf("DRI\n");
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = parse_restart_interval(stream, context);
				RETURN_IF(err);
				break;
			/* RSTm* Restart with modulo 8 count “m” */
			case 0xffd0:
			case 0xffd1:
			case 0xffd2:
			case 0xffd3:
			case 0xffd4:
			case 0xffd5:
			case 0xffd6:
			case 0xffd7:
				printf("RST%i\n", marker & 0xf);
				err = read_ecs(stream, context, &scan);
				RETURN_IF(err);
				break;
			/* COM Comment */
			case 0xfffe:
				printf("COM\n");
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = parse_comment(stream, len);
				RETURN_IF(err);
				break;
			/* TEM* For temporary private use in arithmetic coding */
			case 0xff01:
				printf("TEM\n");
				break;
			/* DAC Define arithmetic coding conditioning(s) */
			case 0xffcc:
				printf("DAC\n");
				err = read_length(stream, &len);
				RETURN_IF(err);
				err = skip_segment(stream, len);
				RETURN_IF(err);
				break;
			default:
				//fprintf(stderr, "unhandled marker 0x%" PRIx16 "\n", marker);
				return RET_FAILURE_FILE_UNSUPPORTED;
		}
	}
}

int process_jpeg_stream(FILE *stream, const char *path)
{
	int err;

	struct context *context = malloc(sizeof(struct context));

	if (context == NULL) {
		fprintf(stderr, "malloc failure\n");
		return RET_FAILURE_MEMORY_ALLOCATION;
	}

	err = init_context(context);

	if (err) {
		goto end;
	}

	err = parse_format(stream, context, path);
end:
	free_buffers(context);

	free(context);

	return err;
}

int process_jpeg_file(const char *i_path, const char *o_path)
{
	FILE *stream = fopen(i_path, "r");

	if (stream == NULL) {
		fprintf(stderr, "fopen failure\n");
		return RET_FAILURE_FILE_OPEN;
	}

	int err = process_jpeg_stream(stream, o_path);

	fclose(stream);

	return err;
}

int main(int argc, char *argv[])
{
	const char *i_path = argc > 1 ? argv[1] : "Lenna.jpg";
	const char *o_path = argc > 2 ? argv[2] : NULL;

	int err = process_jpeg_file(i_path, o_path);

	if (err) {
		printf("Failure.\n");
		return 1;
	}

	printf("Success.\n");

	return 0;
}

编译完成后,在模拟器中进行测试。首先运行一下 JPG2PPM.EFI,它会将Lenna.jpg 转为为 Test.ppm。

接下来,运行 ppmdemo.efi output.ppm 即可看到转换后的结果:

有兴趣的朋友可以试试。

完整的代码如下:

USB 调频收音机

之前在使用 RDA5807FP制作收音机的时候【参考1】曾经注意到,芯片提供了 I2S输出。因此,这次同样使用这个芯片制作一个将收到的信号通过USB传输到电脑中的设备。

设计上使用DFRobot出品的FireBeetle 2 Board ESP32-S3-U作为主控。其中的核心芯片是 ESP32 S3 带有 USB Device的功能,我们通过代码将它模拟为USB麦克风,插入电脑后无需驱动即可直接使用。此外,芯片还模拟出一个 USB 串口,用于主机发送例如选台之类的命令。

硬件非常简单,但是细节很多(前后打了三版)。总结下来需要注意的有如下几点:

  1. RDA5807FP 芯片有假货,我最开始买的装上之后只有沙沙声无法收到电台。最后咨询了B站网友“我是电视电视迷”,他推荐我在“深圳市义胜电子网店”购买了,工作正常;
  2. RDA5807FP芯片有两个工作模式:直接工作模式和单片机模式。前者是意思是上电之后芯片会根据 Pin7 和 Pin8 调整频率,Pin15和Pin16 调整音量;后者的意思是上电后需要使用单片机从 I2C 通讯来控制。但是,这个模式切换引脚在 Datasheet上是标记为 GND 的隐藏起来的。
  3. 供电部分使用了LP5907MFX-3.3,主要是为了降低电源噪音;
  4. 对于接收信号影响最大的是天线部分,这次的设计将天线和耳机分开两个接口。二者是可以放在一起的,耳机的地线作为天线使用,效果也非常不错。当使用耳机作为天线的时候,耳机仍然能够正常工作。
  5. PCB 布线需要特别注意:不要在芯片底部走线,会影响效果。

最终电路图如下,可以看到设计上非常简单,左侧是一个2.54mm座子,右侧是 RDA芯片:

PCB设计如下:

最终焊接后的成品如下:

拿到PCB 后,先进行简单的焊接,不要连接FireBeetle板子,将跳线跳到直接工作模式后,从外部引入5V 供电即可在耳机中听到沙沙声,如果没有声音请检查RDA5807FP焊接特别是供电部分。之后,将 SCL 或者SDA 引脚短暂接地,这样能够启动RDA5807FP的自动搜台功能,如果无法搜索到任何频道,请检查天线部分。

接下来进行代码设计。为了实现USB 麦克风,这里使用 CherryUSB 框架,CherryUSB 是一款国产的小而美的、可移植性高的、用于嵌入式系统的 USB 主从协议栈。

1.创建一个 USB 麦克风,对于 CherryUSB 来说,已经准备好了大部分的代码,只需要下面设定描述符之类的动作即可

    usbd_desc_register(busid, cdc_audio_v1_descriptor);
    usbd_add_interface(busid, usbd_audio_init_intf(busid, &amp;intf0, 0x0100, audio_entity_table, 1));
    usbd_add_interface(busid, usbd_audio_init_intf(busid, &amp;intf1, 0x0100, audio_entity_table, 1));
    usbd_add_endpoint(busid, &amp;audio_in_ep);

    usbd_initialize(busid, reg_base, usbd_event_handler);

2.创建一个USB 串口(USB CDC)

   usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &amp;intf2));
    usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &amp;intf3));
    usbd_add_endpoint(busid, &amp;cdc_out_ep);
    usbd_add_endpoint(busid, &amp;cdc_in_ep);

3.初始化RDA5807 (初始化使用 I2S 输出音频之后,仍然能够在耳机输出口听到音频输出,这对于调试帮助很大)。初始化使用 I2S 输出之后仍然能够在耳机插孔中听到接收到的声音,这样对于调试非常有帮助。

void FMInit() {
	i2c_master_init(&amp;bus_handle, &amp;dev_handle);

    rda_reg02 rda2;
    rda2.raw = 0;
    rda2.refined.NEW_METHOD = 0;
    rda2.refined.RDS_EN = 0;
    rda2.refined.CLK_MODE = 0;
    rda2.refined.RCLK_DIRECT_IN = 0;
    rda2.refined.NON_CALIBRATE = 0;
    rda2.refined.MONO = 0;
    rda2.refined.DMUTE = 1;
    rda2.refined.DHIZ = 1;
    rda2.refined.ENABLE = 1;
    rda2.refined.BASS = 1;
    rda2.refined.SEEK = 0;
    writeRegister(0x02, rda2.raw);

    rda_reg05 rda5;
    rda5.raw = 0x00;
    rda5.refined.INT_MODE = 0;
    rda5.refined.LNA_PORT_SEL = 2;
    rda5.refined.LNA_ICSEL_BIT = 0;
    rda5.refined.SEEKTH = 8;
    rda5.refined.VOLUME = 0;
    writeRegister(0x05, rda5.raw);
	vTaskDelay(pdMS_TO_TICKS(500));  // 必须的

ESP_LOGI(TAG, "Reg04: %X",readRegister(0x04));
ESP_LOGI(TAG, "Reg06: %X",readRegister(0x06));

	uint16_t reg04 = readRegister(0x04);
    reg04 |= (1 &lt;&lt; 6); //I2S_ENABLED

    writeRegister(0x04, reg04);
	
	uint16_t reg06 = readRegister(0x06);
    reg06 |= (1 &lt;&lt; 12)|(1&lt;&lt;9); //I2S_MODE == Slave Mode

    writeRegister(0x06, reg06);
	ESP_LOGI(TAG,"Reg06: %X\n", readRegister(0x06));
ESP_LOGI(TAG, "Reg04: %X\n",readRegister(0x04));
ESP_LOGI(TAG, "Reg06: %X\n",readRegister(0x06));
    vTaskDelay(pdMS_TO_TICKS(500));  // 必须的
	
	setFreq(10170); // 设置频率为101.7MHz
}

上面涉及到的寄存器可以在 RDA5807 手册上查到。

4.获得I2S数据的代码,获得数据之后会放置在一个 Ring Buffer 中,这样的设计可以尽量让代码并行处理

void task_func(void *arg)
{
	i2c_mic_rx_data_t rx_data;

	while (1)
	{
		{
			size_t out_len = 0;
			rx_data.buffer = malloc(AUDIO_IN_PACKET);
			rx_data.size = AUDIO_IN_PACKET;

			i2s_channel_read(rx_handle, rx_data.buffer, rx_data.size, &amp;out_len, 10);

            //  将数据放入队列
			if (xQueueSend(s_receive_queue, &amp;rx_data, portMAX_DELAY) != pdTRUE) {
				USB_LOG_RAW("Send2 Err %x\n",rx_data.buffer);
			}
		}
	}

    vTaskDelete(NULL);
}

5.处理USB 串口收到的数据代码。特别注意,收到的数据不可以立即处理,因为需要通过 I2C 对RDA5807FP 发送数据,耗时较长阻塞整个程序。

void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
    USB_LOG_RAW(TAG,"actual out len:%d\r\n", nbytes);
	
	if ((nbytes==0x05)&amp;&amp;(read_buffer[0]=='S')) {
			write_buffer[0]='S';write_buffer[1]='t';write_buffer[2]=read_buffer[1];
			if (read_buffer[1] == '1') {
                i2ccmd=1;
            } else if (read_buffer[1] == '2') {
                i2ccmd=2;
            }  else if (read_buffer[1] == '3') {
                i2ccmd=3;
            }  else if (read_buffer[1] == '4') {
                i2ccmd=4;
            }  else if (read_buffer[1] == '5') {
                i2ccmd=5;
            }  else if (read_buffer[1] == '6') {
                i2ccmd=6;
            }  else if (read_buffer[1] == '7') {
                i2ccmd=7;
            }  else if (read_buffer[1] == '8') {
                i2ccmd=8;
            }  else if (read_buffer[1] == '9') {
                i2ccmd=9;
            }  else if (read_buffer[1] == 'a') {
                i2ccmd=0x0a;
			}  else {
                write_buffer[0]='U';write_buffer[1]='N';write_buffer[2]='W';
            }
			
			cdc_ep_tx_busy_flag = true;
			usbd_ep_start_write(busid, CDC_IN_EP, write_buffer, 3);
	} else {
			// 将接收到的数据复制到发送缓冲区  
			memcpy(write_buffer, read_buffer, nbytes);  
			// 启动数据发送(回环)  
			cdc_ep_tx_busy_flag = true;  
			usbd_ep_start_write(busid, CDC_IN_EP, write_buffer, nbytes);
	}

    /* setup next out ep read transfer */
    usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, 2048);
	
}

6.从设备向主机发送音频数据

		//	没有数据,提前接收,准备好数据
		if (0 == rx_data.size)
		{
			if (pdPASS == xQueueReceive(s_receive_queue, &amp;rx_data, 1))
			{
				if (rx_data.size != 64)
				{
					USB_LOG_INFO("Recv %x , size=%d\n", rx_data.buffer, rx_data.size);
				}
				//else USB_LOG_INFO("GD");
			}
			else
			{
				rx_data.size = 0;
			}
		}

        //  空闲循环的时候也要等待,让出CPU跑别的任务
        if (0 == tx_flag)
        {
            stop_count++;
			xSemaphoreTake(sign_tx, 1);
        }
		
		//  等待发送
        if (tx_flag &amp;&amp; rx_data.size)
        {

            loop_count--;

			// 如果当前没有发送
			if (ep_tx_busy_flag != true)
			{
				ep_tx_busy_flag = true;

                xSemaphoreTake(sign_tx, 0);

					// 发送数据到 USB 
				usbd_ep_start_write(0, AUDIO_IN_EP, rx_data.buffer, rx_data.size);

			}
			
                xSemaphoreTake(sign_tx, 10);
                
				while (ep_tx_busy_flag)
				{
					if (tx_flag == false)
					{
						break;
					}
				}
				//	发送完成,释放缓冲区
				rx_data.size = 0;
				free(rx_data.buffer);
			}

整体代码框架是天杀帮忙设计的,可以方便的迁移到其他使用 I2S 通讯的项目中,有兴趣的朋友可以深入研究。

RDA5807FP 是上海锐迪科推出的高集成度国产 FM 收音机调谐器芯片,个人感觉可玩度很高。只需要几个外围元件即可接收播放调频广播;同时还可以通过单片机进行控制。这次更将玩法深入一步,通过USB 和计算机连接在一起。有兴趣的朋友不妨试试。

参考:

1. https://oshwhub.com/zoologist/rda5807fp-shou-yin-ji RDA5807FP收音机

工作的测试视频,测试分作两部分,一个是使用将MIC直接从喇叭播放的网页,另外一个是使用录音机录制 :

代码在 IDF 5.4 环境下编译通过。

源代码下载:

电路是立创EDA 专业版设计的,下载

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 NumberTypeEncoding
P1BitmapASCII
P2GraymapASCII
P3PixmapASCII
P4BitmapBinary
P5GraymapBinary
P6PixmapBinary

编码方式

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 &lt;stdio.h>
#include &lt;stdlib.h>
#include &lt;string.h>
#include &lt;Uefi.h>
#include &lt;Library/UefiLib.h>
#include &lt;Library/BaseMemoryLib.h>
#include &lt;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 &amp;&amp; c != '\n');
        } else if (c != ' ' &amp;&amp; c != '\t' &amp;&amp; c != '\n' &amp;&amp; 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 &amp;&amp; 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", &amp;img->width) != 1 ||
        fscanf(fp, "%d", &amp;img->height) != 1 ||
        fscanf(fp, "%d", &amp;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 &lt;= 0 || img->height &lt;= 0 || img->maxval &lt;= 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 &lt; img->width * img->height; i++) {
            if (fscanf(fp, "%d %d %d", &amp;r, &amp;g, &amp;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 &lt; 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(&amp;gEfiGraphicsOutputProtocolGuid, NULL, (VOID**)&amp;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(&amp;gEfiGraphicsOutputProtocolGuid, NULL, (VOID**)&amp;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 &lt; 0) center_x = 0;
    if (center_y &lt; 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 来实现。

  1. 参考的对象是之前的一个使用 32U4 制作的摄像头【参考1】,因此描述符部分是完全照抄的。实现描述符之后插入设备就能看到设备管理器中“长出来”我们自定义的摄像头了,但是还无法工作。
  2. 项目是根据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解码功能,完整的列表如下:

LibraryDescription
ok_pngReads PNG files. Supports Apple’s proprietary CgBI chunk. Tested against the PngSuite.
ok_jpgReads JPEG files. Baseline and progressive formats. Interprets EXIF orientation tags. No CMYK support.
ok_wavReads WAV and CAF files. PCM, u-law, a-law, and ADPCM formats.
ok_fntReads AngelCode BMFont files. Binary format from AngelCode Bitmap Font Generator v1.10 or newer.
ok_csvReads Comma-Separated Values files.
ok_moReads 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 还是有蛮大区别的。

  1. 可以使用下面的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 ,实际使用时需要根据你的需求进行修改。