设计一个简单的文件传输协议

1.文件头设计

TypeSeqLengthChecksum
2 Byte2 Bytes2 Bytes2 Bytes

2.包设计

a.文件信息包

TypeSeqLengthChecksumProtocol VersionFileName LengthFileSizeFileName
TYPE_FILE_INFO00 00包括文件头的总长度2 Bytes协议版本 1 byte1 Byte4 BytesN Bytes

b.数据包

TypeSeqLengthChecksumPayLoad
TYPE_DATANN MM包括文件头的总长度4 BytesXX Byte

c. 确认包

TypeSeqLengthChecksum
TYPE_ACKNN MM 收到的前面包序号包括文件头的总长度4 Bytes

d.结束包

 SeqLengthChecksum
TYPE_ENDNN MM包括文件头的总长度4 Bytes

为了验证上述协议,编写一个 C 代码,在 VS2019 中编译成功,使用串口通讯。理论上串口是流通讯,并不是基于包的通讯。但是实际上我们使用的USB 转串口模块之类的,存在一个缓冲区的问题,特别是对于速度很高的情况(例如,6Mbps),收下来的数据都是先缓存在设备内部的,如果取走的速度比进入的速度快就会有数据丢失的问题。这种情况下,通讯包使用缓冲区一样大小的尺寸效率最高。

用于测试的C代码:

编译后的 exe

使用方法:

发送:

FileTransferProtocolTest.exe -s com17 FileTransferProtocolTest.pdb

接收:

FileTransferProtocolTest.exe -r com16

Step to UEFI (308)UEFI Shell 下的 PPM转JPEG 程序

经过大量的测试和研究,终于找到一个可以工作的代码,能够将PPM转为JPEG 格式(通过前面的文章,我们也知道PPM和BMP 区别不大)。

项目来自 https://github.com/schiermike/jpeg-encoder/tree/master ,只有一个文件,能将PPM 转为 JPG 格式。

代码很长,这里就不列出了,有兴趣的可以直接下载研究。

需要注意的是:

1.只能除了宽度是16整数倍的图片(实际使用中,通常的处理方法是先填充到16的整数倍,处理完成之后再裁剪)

2.代码颜色上应该还有一些问题,估计是颜色排列错误

处理后的结果是

3.测试 Lenna图片

处理之后的结果

4.代码对于 PPM 格式有一些要求,文件头的参数需要用 0x0A 来进行分割,如果换成 0x0D 之类的会报错。

完整的代码和测试数据下载:

力科的 USB  抓包工具的小Bug

最近在Ch32V307上实现 USB Camera 的功能,使用之前的一个设计作为参考。结果在照抄的描述符的时候时偶然发现力科的USB T3 存在一个小Bug。

问题的描述为:描述符解析时,部分值不会反映的 Hex Value中:

最典型的是下面这个 GUID, 解析之后只有部分值,其余部分被丢掉了,如果你认为这个Field 的值为 0x32315659 只有4字节,会导致后续的描述符完全错位:

上面这种相对明显,因为 明确知道GUID应该是 16Bytes,但是下面这个就比较隐蔽,如果只将 Hex  Value拷贝出来,会导致对应的结构体会不够 12 Bytes。

因此,使用工具 Dump USB设备描述符,然后编写自己的描述符时,务必数一下最后的描述符长度。

ESP32S3+MSM261S4030H0+HT513 扬声器

去年使用ESP32S3+MSM261S4030H0+HT513 做了一个扬声器的项目。具体硬件设计可以在 https://oshwhub.com/arduinoai-hao-zhe/portable-speake 看到。

在使用的时候,遇到的一个问题就是MSM261S4030H0输出是32Bits(实际只有24Bits)。最近忽然想起来查了一下是否有机会按照 16Bits输出,搜索出来的资料说的是:只要设置 I2S Master按照16Bits 取即可。因为这个输出有效数据只有 24Bits,直接截断取前面的16Bit即可。

根据这个资料编写代码如下,工作正常。

#include <Arduino.h>
#include <Wire.h>
#include <driver/i2s.h>

#define I2S_PORT1 I2S_NUM_0
#define I2S_PORT2 I2S_NUM_1
#define SAMPLE_RATE 16000
#define CHANNEL_FORMAT I2S_CHANNEL_FMT_ONLY_LEFT
//#define BITS_PER_SAMPLE I2S_BITS_PER_SAMPLE_32BIT
#define BITS_PER_SAMPLE I2S_BITS_PER_SAMPLE_16BIT

#define MIC_BCK_PIN 37
#define MIC_WS_PIN 38
#define MIC_DATA_PIN 36

#define SPK_BCK_PIN 15
#define SPK_WS_PIN 6
#define SPK_DATA_PIN 7
#define SPK_MCK_PIN 16

// HT513 音量
uint16_t Volume;
#define TOLENCE 16
#define HT513_ADDR_L 0x6c
/**
   @brief  ht513写寄存器
   @param  addr 寄存器地址
   @param  val 要写的值
   @retval None
*/
void HT513_WriteOneByte(uint8_t addr, uint8_t val)
{
  Wire.beginTransmission(HT513_ADDR_L);
  Wire.write(addr);
  Wire.write(val);
  int ack = Wire.endTransmission(true);
  Serial.print("Ack ");
  Serial.println(ack, HEX);
}

/**
   @brief  ht513读寄存器
   @param  addr 寄存器地址
   @retval 读取到的寄存器值
*/
uint8_t HT513_ReadOneByte(uint8_t addr)
{
  uint8_t temp = 0;

  Wire.beginTransmission(HT513_ADDR_L);
  Wire.write(addr);
  Wire.endTransmission(false);
  uint8_t bytesReceived = 0;
  bytesReceived = Wire.requestFrom(HT513_ADDR_L, (uint8_t)1, true);
  if (bytesReceived == 1) {
    temp = Wire.read();
  }
  else {
    Serial.println("Read Error ");
  }

  return temp;
}

void setup() {
  Serial.begin(115200);

  delay(2000);

  i2s_config_t MIC_i2sConfig = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = BITS_PER_SAMPLE,
    .channel_format = CHANNEL_FORMAT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags = 0,
    .dma_buf_count = 8,
    .dma_buf_len = 64,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0
  };

  i2s_pin_config_t MIC_pinConfig = {
    .bck_io_num = MIC_BCK_PIN,
    .ws_io_num = MIC_WS_PIN,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = MIC_DATA_PIN
  };

  i2s_driver_install(I2S_PORT1, &MIC_i2sConfig, 0, NULL);
  i2s_set_pin(I2S_PORT1, &MIC_pinConfig);

  i2s_config_t SPK_i2sConfig = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = BITS_PER_SAMPLE,
    .channel_format = CHANNEL_FORMAT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags = 0,
    .dma_buf_count = 8,
    .dma_buf_len = 64,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0
  };

  i2s_pin_config_t SPK_pinConfig = {
    .mck_io_num = SPK_MCK_PIN, 
    .bck_io_num = SPK_BCK_PIN,
    .ws_io_num = SPK_WS_PIN,
    .data_out_num = SPK_DATA_PIN,
    .data_in_num = I2S_PIN_NO_CHANGE
  };

  i2s_driver_install(I2S_PORT2, &SPK_i2sConfig, 0, NULL);
  i2s_set_pin(I2S_PORT2, &SPK_pinConfig);

  analogReadResolution(9);
  Wire.begin(18, 17);
  //  设置 SD 为LOW
  HT513_WriteOneByte(0x12, 0b11110000);
  // 设置数据格式为 I2S, 16Bits
  HT513_WriteOneByte(0x13, 0b00110000);

  // 读取音量设置
  Volume = analogRead(3);
  uint8_t Vol = (Volume, 0, 511, 0x07, 0xff);
  HT513_WriteOneByte(0x16, Vol);
  HT513_WriteOneByte(0x15, Vol);
  Serial.println(Volume, HEX);

  // 调整声道
  HT513_WriteOneByte(0x17, 0b10110000);

  Serial.println("++++++++++++++++");
  //  设置 SD 为HIGH
  HT513_WriteOneByte(0x12, 0b11110100);
  uint8_t Value = HT513_ReadOneByte(0x12);
  Serial.println(Value, HEX);
  Value = HT513_ReadOneByte(0x13);
  Serial.println(Value, HEX);
  Value = HT513_ReadOneByte(0x16);
  Serial.println(Value, HEX);
  Value = HT513_ReadOneByte(0x17);
  Serial.println(Value, HEX);
Serial.print(" starting");
 // dsp.beginAEC(256, 1024, 16000); // Initialize AEC with frame size, filter length, and sample rate
 // dsp.enableAEC(true);            // Enable AEC
}

int8_t mic[512], speaker[521], out[512];
  
void loop() {
  size_t bytesRead = 0;
  
  i2s_read(I2S_PORT1, &mic, sizeof(mic), &bytesRead, portMAX_DELAY);
  i2s_write(I2S_PORT2, &mic, sizeof(mic), &bytesRead, portMAX_DELAY);

  if (abs(analogRead(3) - Volume) > TOLENCE) {
    // 读取音量设置
    Volume = analogRead(3);
    //  设置 SD 为LOW
    HT513_WriteOneByte(0x12, 0b11110000);
    uint8_t Vol = map(Volume, 0, 511, 0x07, 0xff);
    HT513_WriteOneByte(0x16, Vol);
    //  设置 SD 为HIGH
    HT513_WriteOneByte(0x12, 0b11110100);
    Serial.print(analogRead(3), HEX);
    Serial.print("  ");
    Serial.print(Volume, HEX);
    Serial.print("  ");
    Serial.println(Vol, HEX);
  }
}

Step to UEFI (307)UEFI 下使用PRIu8 以及类似宏

上次的代码中碰到了C语言中的 PRIu8 ,这次专门研究了一下如何在代码中直接使用。

<inttypes.h> 是 C 标准库提供的头文件,其中有定义(例如EDK2 中的StdLib\Include\X64\machine\int_fmtio.h文件):

#define	PRIu8		"u"	/* uint8_t		*/
#define	PRIu16		"u"	/* uint16_t		*/
#define	PRIu32		"u"	/* uint32_t		*/

它们C99 标准引入的格式说明符宏。可以通过下面的这种方法进行使用:

printf("Value: %" PRIu16 "\n", value);

在 C 语言中,当你编写多个字符串字面量(例如使用双引号括起来的字符串)在代码中相邻放置时,编译器会自动将它们连接起来形成一个单独的字符串,这被称作C 语言的字符串字面量的自动连接,所以上述代码展开后就是:

printf("Value: %u \n", value);

在EDK2 中编译一个简单的代码进行验证:

#include &lt;stdio.h>
#include &lt;stdlib.h>
#include &lt;string.h>
#include &lt;inttypes.h> 
#include &lt;Uefi.h>
#include &lt;Library/UefiLib.h>

#define LABZTest "LABZ"
int main(int argc, char *argv[]) {
    printf("Value: " LABZTest "\n");
    return 0;
}

模拟器中运行结果如下:

上述试验说明 VC2019 完全支持字符串字面量的自动连接。

所以理论上只要引用了inttypes.h 就可以使用PRIu8 这种宏定义,但是还是会遇到编译期的错误。经过研究发现 \StdLib\Include\inttypes.h 中相关引用是被注释掉的。下图中左侧是原始的代码,右侧是修改后的。完全无法理解原始代码要去掉这个引用,经过这样的修改即可正常编译和执行:

完整的测试代码:

#include <stdio.h>
#include <inttypes.h> 
#include <Uefi.h>
#include <Library/UefiLib.h>

int main(int argc, char *argv[]) {

    printf("Value: %" PRIu8 "\n",1234);

    return 0;
}

模拟器中测试,得到了期望的结果:

完整的代码下载:

NSPGD1M006DT04 测试模块

一、项目介绍

本项目使用表压传感器NSPGD1M006DT04,制作了一个模块,能够获得当前的气压。

很多年前,如果需要制作“吹气熄灯项目”,通常只能使用声音传感器,使用获取声音的方法来判断是否进行了吹气,显而易见这种方法要受到诸多影响。现在可以使用NSPGD1M006DT04 这种传感器来来完成。

二、项目设计思路

1、芯片选型

NSPGD1M006DT04是表压传感器。表压传感器(Gauge Pressure Sensor)是一种测量相对压力的传感器,即相对于大气压力的压力值。相对的还有绝压传感器(Absolute Pressure Sensor)是一种测量相对于真空或绝对零点的压力的传感器。与表压传感器不同,绝压传感器不仅测量被测介质的压力,还考虑到环境中的气压。绝压传感器的工作原理与表压传感器类似,但其参考点是绝对真空或绝对零点而不是大气压力。

2、测试主控选型

为了方便进行测试,选择使用 Arduino 来作为主控,它可以方便的实现 I2C 通讯,同时我们还会设计一个 Arduino 的库,方便日后的使用。

3、原理图绘制

作为模块,非常简单,严格按照Datasheet设计。下图是 DataSheet 中给出的参考:

据此设计的模块电路图:

4、PCB绘制与制板

5、焊接与调试

为了便于使用,焊接了 2.54mm 排母,方便插拔测试。

三、开发过程中遇到的问题与解决方式

为了方便的进行调试,我使用了一款国产开源逻辑分析仪:章鱼哥出品的 PX logic 逻辑分析仪

这款逻辑分析仪主打高速采集,性能指标​如下:

•最大32通道逻辑分析仪​

•buff模式最大支持8ch@1G,总带宽8G,存储深度4Gbits​

•usb3.0下stream最大2ch@1G,总带宽2G,存储深度1024Gbits​

•采样usb3.0进行传输,兼容usb2.0​

•输入采样阈值0-6v可调​

•开源解码协议库200+,使用python进行开发,可自行开发编写

硬件核心部分采用国产器件:

  • USB3.0 Phy 采用南京沁恒微的WCH569W
  • FPGA采用国产FPGA 紫光同创PGL22G

其中的 Buffer 模式意思是:内部自带了缓冲,可以将采集的信号直接传输存储在内部,存储多少数据取决于当前采样数量居和板载内存的大小;Stream的意思是随时将当前采样到的数据发送到电脑上,理论上只要你的电脑性能足够,可以不停的进行采样。

在动手之前首先在面包板上动手实验(NSPGD1M006DT04这种封装非常友好,便于实验)。这里无需额外的上拉I2C 即可工作

设置采样20秒,采样率5Mhz(I2C 400KHz, 相当于10倍采样率)

Buffer 模式,2V 阈值,只要2个通道即可抓取

解码器选择 I2C

指定0 通道是 SCL, 1通道是 SDA

接下来就可以抓到结果了:

四、实现结果展示

五、关键代码及说明

读取的代码数据需要和 Datasheet 上的 Demo 完成相同:

float NSPGD1M006DT04::readPressure() {
  uint32_t PData;

  _i2cPort->beginTransmission(_i2cAddress);
  _i2cPort->write(0x30);
  _i2cPort->write(0x0A);
  _i2cPort->endTransmission(true);

  int number = 0;
  while (number < 50) {
    number++;
    delay(1);
    //NSPGD1M006DT04_Read_Byte(0x30,Reg30);
    _i2cPort->beginTransmission(_i2cAddress);
    _i2cPort->write(0x30);
    _i2cPort->endTransmission(false);


    _i2cPort->requestFrom(_i2cAddress, 1, true);
    if (_i2cPort->read() == 0x02) {

      //NSPGD1M006DT04_Read_3Byte(0x06,PData);
      _i2cPort->beginTransmission(_i2cAddress);
      _i2cPort->write(0x06);
      _i2cPort->endTransmission(false);
      _i2cPort->requestFrom(_i2cAddress, 3, true);
      PData = _i2cPort->read();
      PData = (PData << 8) | _i2cPort->read();
      PData = (PData << 8) | _i2cPort->read();

      if (PData > 8388607) {
        PData = PData - 16777216;
      }

      return (((float)PData) / 8388607 - 0.1) / 0.13333;
    }
  }

  return 0.0;
}

取得的数值以  float 返回给调用者。

六、难点以及解决方法

在调试过程中,我发现根据文档要求,发送指令之后,一直会出现 I2C 的 NA ,无法取得设备的数据。搜索资料也无果,无奈之下继续研究Datasheet, 忽然发现它给出的例子是多次操作的,下面这一块发送 0x0A 之后,发0x30 然后尝试50次(之前上学的时候,英文会有改错的题目,其中一种不为人知的方法是将错误通过排版的方法,放在换行或者换页的地方,看到这里忽然让我想起来被这种题目支配的恐惧)

于是,我也修改代码多次读取,然后问题就解决了。总结下来之前碰到问题的原因是:

  1. 为了便于调试,只尝试一次;
  2. 代码中间隔2秒才进行一次尝试;
  3. 我逻辑分析仪设置采样20秒,通常我也只会观察20秒;

修改库函数,多尝试问题就解决了。

==================================================

本项目是参考电子森林的活动项目,原始链接在 https://www.eetree.cn/project/4719

给 7Z 增加解压后打开目录的功能

修改 7Zip CPP\7zip\UI\GUI\ExtractGUI.cpp ExtractGUI() 函数,在末尾增加一段代码:

  extracter.ArchivePaths = &archivePaths;
  extracter.ArchivePathsFull = &archivePathsFull;
  extracter.WildcardCensor = &wildcardCensor;
  extracter.Options = &options;
  #ifndef Z7_SFX
  extracter.HashBundle = hb;
  #endif

  extracter.IconID = IDI_ICON;

  RINOK(extracter.Create(title, hwndParent))
  messageWasDisplayed = extracter.ThreadFinishedOK && extracter.MessagesDisplayed;
  
  //LAB-Z_Debug_Start
  if (extracter.Result == S_OK && !options.TestMode && !options.OutputDir.IsEmpty())  
  {  
    FString outputDir = options.OutputDir;  
    NName::NormalizeDirPathPrefix(outputDir);  
      
    ShellExecute(NULL, L"explore", fs2us(outputDir), NULL, NULL, SW_SHOWNORMAL);  
  }
  //LAB-Z_Debug_End

  return extracter.Result;
}

重新编译,之后将编译生成的 7zG.exe 拷贝覆盖到之前的 7z 安装目录下,使用右键解压的时候就能自动打开解压目录了。

修改后的代码:

基于26.01 源代码修改后编译生成的 7zG.exe:

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 即可看到转换后的结果:

有兴趣的朋友可以试试。

完整的代码如下: