和技嘉的板子搏斗了一天

前年配了一个台式机,16G 内存 ,Intel I7 4790S的 CPU。当时装的是 GigaByte 的 G1 Sniper b5的板子。我的要求就是:稳定。 CPU 和 内存都是降频运行的。最近感觉硬盘不太稳定,又买了一个新的硬盘,不料安装系统给我折腾的够呛。

装系统需要重启,大约是因为我做的启动盘格式有问题,一直没有办法进去安装盘,我只得Reset重启。没想到这样几次之后,主板就开始抽风:启动,能看到光标,然后关机一直反复这个动作。折腾了很久都没有搞定。最后网上搜索到这块主板是双BIOS(Dual BIOS/Dual SPI),关机状态下长按Power Button开机,开机之后继续按住可以进入Recovery Mode…….此外还可以短接主板上一个 SPI NOR 的 Pin 1 和 Pin 8 强制进入,但是动硬件的话难免风险,按 Button 更简单稳妥一些。

经过无数次折腾,终于进入了这个 Mode,主板从另外一个 SPI NOR 读取出来数据覆盖到主 SPI 上才恢复。

看起来 GigaByte 主板在设计或者说测试阶段应该没有发现:当无法启动Leagcy OS后,主动关机会导致 BIOS 进入某种特殊状态的 Bug。万幸 Dual BIOS 功能做的还不错。

最后经过无数次的努力终于把系统装上,告一段落。

Step to UEFI (83) BlockIo Protocol

“因为硬盘是一种块设备,所以每个硬盘设备(硬盘设备包括分区设备)控制器都安装有一个 BlockIo 实例,一个 BlockIo2实例。BlockIo 提供了访问设备的阻塞函数,BlockIo2提供了访问设备的异步函数”【参考1】

blk2

blk1

这里提供一个枚举BlockIo,然后显示每一个 Media 属性的例子:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include <Protocol/BlockIo.h>

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS				Status;
    UINTN					HandleCount,HandleIndex;
    EFI_HANDLE              *BlockControllerHandles = NULL;	
	EFI_BLOCK_IO_PROTOCOL   *BlockIo;
	
	//找到全部有 BlockIo Protocol 的Device
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiBlockIoProtocolGuid,
            NULL,
            &HandleCount,
            &BlockControllerHandles);  

   if (!EFI_ERROR(Status)) {
        //逐个打开 
        for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
            /*打开EFI_BLOCK_IO_PROTOCOL  */ 
            Status = gBS->HandleProtocol(
                    BlockControllerHandles[HandleIndex],
                    &gEfiBlockIoProtocolGuid,
                    (VOID**)&BlockIo);
			//显示信息		
			Print(L"-->[Device]<--:%d\n",HandleIndex);
			Print(L"MediaId       :%0x\n",BlockIo->Media->MediaId);
    		Print(L"RemovableMedia:%0x\n",BlockIo->Media->RemovableMedia);
    		Print(L"MediaPresent  :%0x\n",BlockIo->Media->MediaPresent);
    		Print(L"ReadOnly      :%0x\n",BlockIo->Media->ReadOnly); 
    		Print(L"WriteCaching  :%0x\n",BlockIo->Media->WriteCaching);
    		Print(L"BlockSize     :%0x\n",BlockIo->Media->BlockSize);
    		Print(L"IoAlign       :%0x\n",BlockIo->Media->IoAlign);
    		Print(L"LastBlock     :%0x\n",BlockIo->Media->LastBlock);
    		Print(L"LogicalPartition :%0x\n",BlockIo->Media->LogicalPartition);
    		Print(L"LowestAlignedLba :%0x\n",BlockIo->Media->LowestAlignedLba);
    		Print(L"LogicalBlocksPerPhysicalBlock   : %0x\n",
			BlockIo->Media->LogicalBlocksPerPhysicalBlock);
    		Print(L"OptimalTransferLengthGranularity: %0x\n",
			BlockIo->Media->OptimalTransferLengthGranularity);
        }	//for (HandleIndex = 0;
		
        gBS->FreePool(BlockControllerHandles);
    }			
  return EFI_SUCCESS;
}

 

在 Nt32 虚拟机中运行的结果

blockioa

完整的代码和程序下载:

BIOTest

参考
1. UEFI 原理与编程 P139

一个计算时间的批处理

有时候,我们需要简单的测量一下某个程序经过的时间,经过搜索在网上【参考1】,找到下面的批处理可以完成这个要求。

@echo off
set ns=0
rem Show start time
set time1=%time%
echo Start time is %time1%
call :time2sec %time1%
set t1=%ns%


rem Do what you want


rem Show end time
set time2=%time%
echo End time is ?%time2%
call :time2sec %time2%
set t2=%ns%
rem Calculate
set /a tdiff=%t2% - %t1%
echo diff %time1% from %time2% = %tdiff% seconds.
goto :eof

:time2sec
rem CDonvert time to seconds. Save to ns
set tt=%1
set hh=%tt:~0,2%
set mm=%tt:~3,2%
set ss=%tt:~6,2%
set /a ns=(%hh%*60+%mm%)*60+%ss%


goto :eof

 

比如,我用这个工具来测试Build BIOS 的时间:

CLean 之后,build 要花费5分钟
clean

再次编译,build只需要不到一分钟
noclean

参考:
1.http://zhidao.baidu.com/link?url=dL8oq_ik4tX5St4YkwQk4vz8HjYGIeFa6ybUs21BI9h6VdSdx7B7BpqdaiVIsNr8rKNlYoRJZ4rIGpxwRfCB3a

Step to UEFI (82) NT32Pkg的Debug Message

最近在查看EDKII代码的时候忽然有一个奇怪的想法:在运行模拟器的时候(Build Run),我们可以在编译窗口看到很多输出的Debug 信息,那么我们是否可以在自己编写的Application中输出这样的信息?

最直接的想法是在 Application中调用 gWinNt ,但是如果要用这个东西,需要定义很多文件头,最麻烦的是这些头文件最后都要使用 Windows.h ,编译的时候总是无法通过。另外的方法是,在 Application 中调用诸如 WinNtThunkDxe 或者 WinNtSerialIoDxe 这样的Protocol,但是在编译使同样会遇到有上面的问题。我尝试了很多次都没有成功,最后只好放弃。

换一种思路,我们可以在NT32Pkg中留下可供调用的函数,然后在 Application 中Call这个函数。

最简单的功能就是用于系统重启的 gST->Reset 。经过查找,模拟器中实现这个功能的代码在 \Nt32Pkg\ResetRuntimeDxe\reset.c 。头定义如下

VOID
EFIAPI
WinNtResetSystem (
  IN EFI_RESET_TYPE   ResetType,
  IN EFI_STATUS       ResetStatus,
  IN UINTN            DataSize,
  IN VOID             *ResetData OPTIONAL
  )
/*++

Routine Description:

  TODO: Add function description

Arguments:

  ResetType   - TODO: add argument description
  ResetStatus - TODO: add argument description
  DataSize    - TODO: add argument description
  ResetData   - TODO: add argument description

Returns:

  EFI_SUCCESS - TODO: Add description for return value

--*/

 

实现功能的代码只有一行 gWinNt->ExitProcess (0)。只要把这些语句注释掉,替换为我们的输出代码即可。参考 \Nt32Pkg\Library\PeiNt32OemHookStatusCodeLib\Nt32OemHookStatusCodeLib.c
,对编译窗口输出的语句如下:

 //
  // Callout to standard output.
  //
  mWinNt->WriteFile (
            mStdOut,
            Buffer,
            (DWORD)CharCount,
            (LPDWORD)&CharCount,
            NULL
            );

 

搬过来,写成下面的形式:

VOID
EFIAPI
WinNtResetSystem (
  IN EFI_RESET_TYPE   ResetType,
  IN EFI_STATUS       ResetStatus,
  IN UINTN            DataSize,
  IN VOID             *ResetData OPTIONAL
  )
/*++

Routine Description:

  TODO: Add function description

Arguments:

  ResetType   - TODO: add argument description
  ResetStatus - TODO: add argument description
  DataSize    - TODO: add argument description
  ResetData   - TODO: add argument description

Returns:

  EFI_SUCCESS - TODO: Add description for return value

--*/
{
  CHAR8			 *R="www.lab-z.com \n\r";	
  UINTN           CharCount=AsciiStrLen(R);
  
  //
  // Cache of standard output handle .
  //
	HANDLE                      mStdOut;
  
  //
  // Cache standard output handle.
  //
  mStdOut = gWinNt->GetStdHandle (STD_OUTPUT_HANDLE);  
  
  //
  // Callout to standard output.
  //
  gWinNt->WriteFile (
            mStdOut,
            R,
            (DWORD)CharCount,
            (LPDWORD)&CharCount,
            NULL
            );
			
  //
  // BUGBUG Need to kill all console windows later
  //
  //
  // Discard ResetType, always return 0 as exit code
  //
  //gWinNt->ExitProcess (0);

  //
  // Should never go here
  //
  //ASSERT (FALSE);
}

 

特别注意:字符串是 Ascii 定义的,末尾必须是 \r\n ,否则不会立即显示。

最后运行结果,只要在模拟器中输入 reset 即可看到字符串,正常的动作应该是退出模拟器。

nt32a

nt32b

因为我修改掉了退出的方法,每次需要在编译窗口用 ctrl+c来结束模拟器了。

这里只是一个Demo,最好还是重新在EDKII代码中定义一个用来直接输出的接口。

介绍一个MP3模块

之前入手了一个 MP3 播放模块(名称是 Arduino TTL串口语音模块 Mini Voice MP3语音音乐播放器),自带 SD 卡槽还有小喇叭。

image002

image004

image006

指令:
1.播放 play,0001,$
2.播放/暂停 pap,$,pla,$
3.停止播放 stop,$
4.上一曲 previous,$
5.下一曲 next,$
6.音量加 vol+,$
7.音量减 vol-,$
8.音量大小 vol,A,$ (从 vol,1,$到vol,F,$ 十六个级别)
9复位模块 reset,$
10.波特率设置 baud,9600,$ (支持 1200,2400,4800,7200,9600,14400,19200,38400,57600,115200 十种)
测试程序:

boolean pause=true;

void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
  while (!Serial) 
    {;}
}

void loop() {
  Serial.println("(0) Play 0001");
  Serial.println("(1) Play 0002");
  Serial.println("(2) Play/Pause");
  Serial.println("(3) Stop");
  Serial.println("(4) Previuos");  
  Serial.println("(5) Next"); 
  Serial.println("(6) Volume +");  
  Serial.println("(7) Volume -");  
  Serial.println("(9) Reset");  
  Serial.println("");

  while (!Serial.available()) {;}

  switch (Serial.parseInt())
  {
      case 0:  Serial1.print("play,0001,$");      
      case 1:  Serial1.print("play,0002,$"); break;
      case 2:  if (pause==true) {
                    Serial1.print("pla,$");
                   }
               else {
                    Serial1.print("pap,$");
                   }
               pause=!pause;     
                break;
      case 3:  Serial1.print("stop,$");break;
      case 4:  Serial1.print("previous,$");break;
      case 5:  Serial1.print("next,$");break;
      case 6:  Serial1.print("vol+,$");break;
      case 7:  Serial1.print("vol-,$");break;
      case 9:  Serial1.print("reset,$");break;  
      default: Serial1.println("Menu item does not exist.");
  }
}

 

运行结果

image008

另外,模块上面带有USB接口,然后可以将 SD 卡插在上面,用USB线直接连接到电脑上即可充当读卡器。测试中我将 MP3分别命名为 0001.MP3 0002.MP3 0003.MP3。
image010

参考:
1. https://item.taobao.com/item.htm?spm=a1z09.2.0.0.iedIEI&id=45599999495&_u=ckf8s90790 Arduino TTL串口语音模块 Mini Voice MP3语音音乐播放器

Compiler Warning C4131

在尝试编译老旧的代码时,你可能遇到 C4131 Warning, 给出来的提示是 “uses old-style declarator”。 这是因为你的代码使用了旧式的生命方式

比如:

// C4131.c
// compile with: /W4 /c
void addrec( name, id ) // C4131 expected
char *name;
int id;
{ }

 

这个定义会导致上面的 warning。修改为下面的即可

 void addrec( char *name, int id )
 { }

 

更多信息可以在
https://msdn.microsoft.com/en-us/library/b92s55e9(v=vs.90).aspx 看到

Debug Message 标记工具

对于庞大的代码,串口 Message 提供了最好的追踪方法。但是很多时候,我们的代码会有重复的文件,在对照阅读的时候就会非常麻烦。一种方法是之前文章介绍过的使用 __FILE__ 的方法,唯一的问题是这个宏经常导致代码在Debug 模式下爆掉(很大原因是因为Debug模式下这个宏会加入路径信息)。因此,编写这个工具,具体的原理是:打开所有的 C 文件,查找 DEBUG宏,然后在它的字符串起始处加入 “(序号)”这样的标记。序号是由 0-9和A-Z组成的。更改代码之后,再次编译,串口输出的内容就有序号+原先的字符串,能够很方便的分析出来源。

特别注意:某些情况下可能会导致源文件的错误,这是目前分析的手法决定的。不过从我的实践来看,EDK代码不会有问题,某些工程文件可能会有1到2处标记错误,手动修改去掉即可。

这是我在某个BIOS工程上直接实验的结果:

dm

可执行程序和完整代码下载

DebugMark

MPR121 触摸传感器模块

MPR121 是一款触摸传感器芯片,原理是通过检测电容变化来判断当前是否有触摸(接近)。

主要电器特性如下【参考1】:
1. 工作电压1.71-3.6v(芯片工作电压)
2. 通讯接口为 I2C
3. 12个检测端口
4. 带有1个 IRQ端口

我是在Taobao购买的模块,价格是16元【参考3】。经过搜索,这是仿sparkfun的,更多资料可以在【参考2】看到。

模块长得下面这样,有一个降压芯片,看起来可以直接使用 5V 供电。上面有12个口,可以接12个触摸按钮。IRQ 的作用是发出中断通知上面有触摸。ADD是芯片的I2C地址选择,接GND VDD SDA或者 SCL 地址分别是 0x5A 0x5B 0x5C 和 0x5D【来自参考4】。

image001

图片来自【参考2】

下面的代码可以正常工作(卖家的例程,有一些修改可以在 1.6.0 上编译通过)

#include "mpr121.h"
#include <Wire.h>

#define SENSORS       13
#define TOU_THRESH    0x1F
#define REL_THRESH    0x1A
#define PROX_THRESH   0x3f
#define PREL_THRESH   0x3c

// variables: capacitive sensing
bool touchStates[SENSORS];    // holds the current touch/prox state of all sensors
bool activeSensors[SENSORS] = {1,1,1,1,1,1,1,1,1,1,1,1,1}; // holds which sensors are active (0=inactive, 1=active)
bool newData = false;         // flag that is set to true when new data is available from capacitive sensor
int irqpin = 2;               // pin that connects to notifies when data is available from capacitive sensor

void setup(){

  // attach interrupt to pin - interrupt 1 is on pin 2 of the arduino (confusing I know)
  attachInterrupt(0, dataAvailable, FALLING);

  // set-up the Serial and I2C/Wire connections
  Serial.begin(9600);
  Wire.begin();

  // set the registers on the capacitive sensing IC
  setupCapacitiveRegisters();

}

void loop(){
  readCapacitiveSensor();
}

/**
 * dataAvailable Callback method that runs whenever new data becomes available on from the capacitive sensor. 
 *   This method was attached to the interrupt on pin 2, and is called whenever that pins goes low.
 */
void dataAvailable() {
  newData = true;
}

/**
 * readCapacitiveSensor Reads the capacitive sensor values from the MP121 IC. It makes a request to
 *   the sensor chip via the I2C/Wire connection, and then parses the sensor values which are stored on
 *   the first 13 bits of the 16-bit response msg.
 */
void readCapacitiveSensor(){
  if(newData){    
            Serial.println("yes");      
    //read the touch state from the MPR121
    Wire.requestFrom(0x5A,2); 
    byte tLSB = Wire.read();
    byte tMSB = Wire.read();
    uint16_t touched = ((tMSB << 8) | tLSB); //16bits that make up the touch states

    for (int i = 0; i < SENSORS; i++){  // Check what electrodes were pressed
      if (activeSensors[i] == 0) continue;
      char sensor_id [] = {'\0','\0','\0'};
      switch (i) {
        case 12:
          sensor_id[0] = 'P';
          break;
        default:
          if (i < 10) {
            sensor_id[0] = char( i+48 );
          } 
          else if (i < 12) {
            sensor_id[0] = char('1');
            sensor_id[1] = char( ( i % 10 ) + 48 );
          } 
      }
      if (sensor_id != '\0') {
        // read the humidity level

        // if current sensor was touched (check appropriate bit on touched var)
        if(touched & (1<<i)){      
          // if current pin was not previously touched send a serial message
          if(touchStates[i] == 0){          
            Serial.print(sensor_id);        
            Serial.print(":");
            Serial.println("1");
          } 
          touchStates[i] = 1;      
        } else {
          // if current pin was just touched send serial message
          if(touchStates[i] == 1){
            Serial.print(sensor_id);
            Serial.print(":");
            Serial.println("0");
          }
          touchStates[i] = 0;
        }        
      }
    }
    newData = false;
  }
}

/**
 * setupCapacitiveRegisters Updates all of configurations on the MP121 capacitive sensing IC. This includes
 *   setting levels for all filters, touch and proximity sensing activation and release thresholds, debounce,
 *   and auto-configurations options. At the end it activates all of the electrodes.
 */
void setupCapacitiveRegisters(){

  set_register(0x5A, ELE_CFG, 0x00); 
  
  // Section A - filtering when data is > baseline.
    // touch sensing
    set_register(0x5A, MHD_R, 0x01);
    set_register(0x5A, NHD_R, 0x01);
    set_register(0x5A, NCL_R, 0x00);
    set_register(0x5A, FDL_R, 0x00);

    // prox sensing 
    set_register(0x5A, PROX_MHDR, 0xFF);
    set_register(0x5A, PROX_NHDAR, 0xFF);
    set_register(0x5A, PROX_NCLR, 0x00);
    set_register(0x5A, PROX_FDLR, 0x00);

  // Section B - filtering when data is < baseline.
    // touch sensing
    set_register(0x5A, MHD_F, 0x01);
    set_register(0x5A, NHD_F, 0x01);
    set_register(0x5A, NCL_F, 0xFF);
    set_register(0x5A, FDL_F, 0x02);
  
    // prox sensing
    set_register(0x5A, PROX_MHDF, 0x01);
    set_register(0x5A, PROX_NHDAF, 0x01);
    set_register(0x5A, PROX_NCLF, 0xFF);
    set_register(0x5A, PROX_NDLF, 0xFF);

  // Section C - Sets touch and release thresholds for each electrode
    set_register(0x5A, ELE0_T, TOU_THRESH);
    set_register(0x5A, ELE0_R, REL_THRESH);
   
    set_register(0x5A, ELE1_T, TOU_THRESH);
    set_register(0x5A, ELE1_R, REL_THRESH);
    
    set_register(0x5A, ELE2_T, TOU_THRESH);
    set_register(0x5A, ELE2_R, REL_THRESH);
    
    set_register(0x5A, ELE3_T, TOU_THRESH);
    set_register(0x5A, ELE3_R, REL_THRESH);
    
    set_register(0x5A, ELE4_T, TOU_THRESH);
    set_register(0x5A, ELE4_R, REL_THRESH);
    
    set_register(0x5A, ELE5_T, TOU_THRESH);
    set_register(0x5A, ELE5_R, REL_THRESH);
    
    set_register(0x5A, ELE6_T, TOU_THRESH);
    set_register(0x5A, ELE6_R, REL_THRESH);
    
    set_register(0x5A, ELE7_T, TOU_THRESH);
    set_register(0x5A, ELE7_R, REL_THRESH);
    
    set_register(0x5A, ELE8_T, TOU_THRESH);
    set_register(0x5A, ELE8_R, REL_THRESH);
    
    set_register(0x5A, ELE9_T, TOU_THRESH);
    set_register(0x5A, ELE9_R, REL_THRESH);
    
    set_register(0x5A, ELE10_T, TOU_THRESH);
    set_register(0x5A, ELE10_R, REL_THRESH);
    
    set_register(0x5A, ELE11_T, TOU_THRESH);
    set_register(0x5A, ELE11_R, REL_THRESH);

  // Section D - Set the touch filter Configuration
    set_register(0x5A, FIL_CFG, 0x04);  

  // Section E - Set proximity sensing threshold and release
    set_register(0x5A, PRO_T, PROX_THRESH);   // sets the proximity sensor threshold
    set_register(0x5A, PRO_R, PREL_THRESH);   // sets the proximity sensor release

  // Section F - Set proximity sensor debounce
    set_register(0x59, PROX_DEB, 0x50);  // PROX debounce

  // Section G - Set Auto Config and Auto Reconfig for prox sensing
    set_register(0x5A, ATO_CFGU, 0xC9);  // USL = (Vdd-0.7)/vdd*256 = 0xC9 @3.3V   
    set_register(0x5A, ATO_CFGL, 0x82);  // LSL = 0.65*USL = 0x82 @3.3V
    set_register(0x5A, ATO_CFGT, 0xB5);  // Target = 0.9*USL = 0xB5 @3.3V
    set_register(0x5A, ATO_CFG0, 0x0B);

  // Section H - Start listening to all electrodes and the proximity sensor
    set_register(0x5A, ELE_CFG, 0x3C);
}

/**
 * set_register Sets a register on a device connected via I2C. It accepts the device's address, 
 *   register location, and the register value.
 * @param address The address of the I2C device
 * @param r       The register's address on the I2C device
 * @param v       The new value for the register
 */
void set_register(int address, unsigned char r, unsigned char v){
  Wire.beginTransmission(address);
  Wire.write(r);
  Wire.write(v);
  Wire.endTransmission();
}

 

运行结果:

image004

完整的代码下载

MPR121

参考:
1. http://wenku.baidu.com/link?url=77EtEpflHOBF9LmqwOvIwe5ONZ6I548h4BcBk4Ep1XWVO_RVDj9fycwoku44RENseV48lzvnrnDasY3UAMHsBuuU7yVdxFsAfxq-zbDiEhy MPR121中文数据手册
2. https://learn.sparkfun.com/tutorials/mpr121-hookup-guide
3. https://item.taobao.com/item.htm?spm=a1z09.2.0.0.kpoNRc&id=19968128319&_u=pkf8s90f4c
4. MPR121 DataSheet

===============================================================================
额外说一下这个传感器在 Pro Micro 上的连接方法:

1. Pro Micro 的中断:“The Pro Micro has five external interrupts, which allow you to instantly trigger a function when a pin goes either high or low (or both). If you attach an interrupt to an interrupt-enabled pin, you’ll need to know the specific interrupt that pin triggers: pin 3 maps to interrupt 0, pin 2 is interrupt 1, pin 0 is interrupt 2, pin 1 is interrupt 3, and pin 7 is interrupt 4.”
推荐 使用 Pin7-Interrupt 4
2. Pro Micro Pin2- SDA Pin3-SCL

VC 中的 __FILE__ 宏

VC 中有一个 __FILE__ 宏定义,表示当前文件,比如,下面的程序就可以输出编译期的源文件名。

#include "stdafx.h"
#include "stdio.h"

int _tmain(int argc, _TCHAR* argv[])
{
	printf(__FILE__);
	getchar();
	return 0;
}

 

我们在BIOS中经常看到的 ASSERT 就使用这个宏来准确的输出出现错误的位置。

此外,在 Release 模式下这个宏只包括文件名,Debug 模式下这个宏会输出完整的路径。

debug

release

从屏幕输出的方法

之前,我写过一篇“Arduino 打造自动输入器”【参考1】。文章的应用场景是“某些时候因为一些特殊的原因,使得我们不能直接使用U盘之类的存储设备”。在这样的条件下,我们可以用 Arduino打造一个虚拟键盘,一个字节一个字节的敲入我们期望的程序。有了输入内容的方法,接下来的问题是:如何把需要的内容输出出来。本文给出一个方法:将要输出的文件,转化为二维码通过屏幕输出,用户再用手机对着屏幕进行识别。
大多数人都不希望看着长篇的代码,这里我只是列出来大概的思路,有需要的朋友可以在后面下载到Delphi编写的代码。
具体方法是:将欲传文件切为一定的大小(决定大小的是QR Code的容量【参考2】,屏幕分辨率和你手机的摄像头的分辨率),然后通过程序将十六进制表示的字节转换为字符串,再通过程序生成一张张二维码,之后显示在屏幕上就可以通过手机来识别,识别之后可以发送到邮箱,再用另外的程序把所有的文件“粘”起来就恢复原样了。
下面是一个具体的操作例子。
首先选择文件,这里我们选一个比较小的文件。

image001

程序会将文件转换为 QR_Code,这里我直接生成的是BMP,体积较大。切成了 6 个图片。

image002

image003

然后一边显示,一边用手机拍照(半自动)。

image004

这个照片中展示的是 ZXing 软件进行识别,不过最后我发现小米手机的二维码扫描工具,感觉识别率和容错能力更好。

image005

这里手工粘贴下来(我使用的二维码生成控件有问题,无法生成数字0 ,所以我代码上用 “-” 来替代了所有的 “0”)。
image006

把每个文件的扫描结果单独存放在一个个*.labz文件中,使用替换方法将“-”替换为“0”,再使用另外的Delphi编写的工具把他们转换为十六进制。
image007

替换之后会生成 0001.bin 这样的文件,最终用批处理将他们粘在一起就好了。

image008

最终结果和原始文件没有差别。

image009

完整的工具下载
Demo

File2Image

TxtToBin

参考:
1. http://www.lab-z.com/ardkey/ Arduino 打造自动输入器
2. GBT 18284-2000 快速响应矩阵码