给麦步手表写一个获取比特币行情的表盘

我对手表有着深厚的感情,每当人生遇到坎坷时,或者愤怒不已的时候,我都会想象和安慰自己“我去年买了块表”。刚有taobao的时候,我买了一块卡西欧的太阳能手表,时至今日我还记得价格是 258元。十多年过去了,这块表的卖家已经消失,我从来没有给他更换过电池,这块手表依然行走完好。
image001

古人云“穷玩车,富玩表”。为了体验一下富人的感觉,最近入手了一块麦步智能手表。对于这块手表,最重要的是可以写程序。于是我就尝试给他编写程序,听上去又在重复“屌丝玩电脑”。

image002

首先通读API,了解这个手表的原理。简单的说,这块手表通过蓝牙连接,从而实现WIFI通讯。
image003

目标是编写一个获取当前比特币行情。huobi 网提供了行情API, http://api.huobi.com/staticmarket/ticker_btc_json.js 可以显示实时行情。例如:一次返回数据如下

{“time”:”1467817886″,”ticker”:{“open”:4479,”vol”:604962.2546,”symbol”:”btccny”,”last”:4522.79,”buy”:4522.79,”sell”:4523.03,”high”:4612,”low”:4467.64} }。 其中的 last 就是当前的价格。

配合麦步手表提供的显示股票行情的代码,编写自己的程序如下:

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

 

#include "maibu_sdk.h"

#include "maibu_res.h"

 

 

/*Web通讯ID*/

static uint32_t g_comm_id_web = 0;

 

/*Web request地址*/

#define DATA_WEB     "http://api.huobi.com/staticmarket/ticker_btc_json.js"

 

/* 时间项 */

#define DATE_TEXT_POS_X                      2

#define DATE_TEXT_POS_Y                      6

#define DATE_TEXT_SIZE_W                     70

#define DATE_TEXT_SIZE_H                     14

 

#define TIME_TEXT_POS_X                      90

#define TIME_TEXT_POS_Y                      6

#define TIME_TEXT_SIZE_W                     36

#define TIME_TEXT_SIZE_H                     14

 

static const char weekday[7][11] =

{

    {"周日"},

    {"周一"},

    {"周二"},

    {"周三"},

    {"周四"},

    {"周五"},

    {"周六"}

};

 

 

/*窗口ID*/

static int32_t g_window_id = -1;

 

//数据提示

static int32_t g_layer_id_text = -1;

//数据内容

static int32_t g_layer_id_data = -1;

 

//时间的句柄

static int32_t g_layer_id_time   = -1;

//日期的句柄

static int32_t g_layer_id_date   = -1;

 

//整个窗体句柄

static P_Window h_window;

 

void data_request_web()

{

     /*拼接url请求地址, 注意url的缓存大小*/

     char url[200] = "";   

     sprintf(url, "%s", DATA_WEB);              

 

     /*

               拼接过滤参数,即只接受和过滤参数匹配的返回值

               个人感觉这里的过滤可能是让手机做的,就是这里通知手机:json中的数据除了我制定的其他都过滤掉

     */

 

     /*发送一次*/

     g_comm_id_web = maibu_comm_request_web(url, "last", 0);

}

 

void add_text_layer(P_Window p_window, int32_t *p_layer_id, char *p_str, GRect *p_frame, enum GAlign align, int8_t font, enum GColor color)

{

    LayerText text_cfg = {p_str, *p_frame, align, font, 0};

    P_Layer layer = app_layer_create_text(&text_cfg);

    app_layer_set_bg_color(layer, color);

 

    P_Layer old_layer = app_window_get_layer_by_id(p_window, *p_layer_id);

    if(old_layer)

    {

        *p_layer_id = app_window_replace_layer(p_window, old_layer, layer);

    }

    else

    {

        *p_layer_id = app_window_add_layer(p_window, layer);

    }

}

 

static void add_time_bar(P_Window p_window)

{

    /* 添加时间图层 */

    uint8_t text_buf[40];

    struct date_time t;

    app_service_get_datetime(&t);

 

    memset(text_buf, 0, sizeof(text_buf));

    sprintf((char *)text_buf, "%s", (char *)&weekday[t.wday][0]);

    sprintf(&text_buf[6], "%02d-%02d", t.mon, t.mday);

 

    GRect frame;

    frame.origin.x = DATE_TEXT_POS_X;

    frame.origin.y = DATE_TEXT_POS_Y;

    frame.size.h   = DATE_TEXT_SIZE_H;

    frame.size.w   = DATE_TEXT_SIZE_W;

 

    add_text_layer(p_window, &g_layer_id_date, (char*)text_buf, &frame, GAlignLeft, U_ASCII_ARIAL_14, GColorWhite);

 

    frame.origin.x = TIME_TEXT_POS_X;

    frame.origin.y = TIME_TEXT_POS_Y;

    frame.size.h   = TIME_TEXT_SIZE_H;

    frame.size.w   = TIME_TEXT_SIZE_W;

 

    memset(text_buf, 0, sizeof(text_buf));

    sprintf(text_buf, "%02d:%02d", t.hour, t.min);

 

    add_text_layer(p_window, &g_layer_id_time, (char*)text_buf, &frame, GAlignLeft, U_ASCII_ARIAL_14, GColorWhite);

}

 

P_Window init_btc_window()

{

     static P_Window p_window;

    

     p_window = app_window_create();

     if (NULL == p_window)

     {

               return NULL;

     }

 

    /* 添加表盘背景 */

    GRect frame = {{0, 0}, {128, 128}};

    GBitmap bitmap;

 

    res_get_user_bitmap(BMP_STOCK_BG, &bitmap);

    LayerBitmap layer_bitmap = {bitmap, frame, GAlignCenter};

    P_Layer layer = app_layer_create_bitmap(&layer_bitmap);

    app_window_add_layer(p_window, layer);

 

    /* 添加时间栏 */

    add_time_bar(p_window);

 

     /*加入你的代码 begin*/     

 

     /*添加数据提示信息*/

     GRect frame_main = {{0, 30}, {16, 128}};

    add_text_layer(p_window, &g_layer_id_text, "BTC", &frame_main, GAlignCenter, U_ASCII_ARIAL_14, GColorWhite);

    

     /*添加数据*/

     GRect frame_data = {{0, 60}, {16, 128}};

    add_text_layer(p_window, &g_layer_id_data, "waiting", &frame_data, GAlignCenter, U_ASCII_ARIAL_14, GColorWhite);

    

     return p_window;

 

}

 

void data_comm_result_callback(enum ECommResult result, uint32_t comm_id, void *context)

{

 

     /*如果上一次请求WEB通讯失败,并且通讯ID相同,则重新发送*/

     if ((result == ECommResultFail) && (comm_id == g_comm_id_web))

     {

               data_request_web();

     }

    

}

 

static void web_recv_callback(const uint8_t *buff, uint16_t size)

{

    char stock_gid[10];

     char i;

 

    maibu_get_json_str(buff, "last", stock_gid, 10);

 

     for (i=0;i<10;i++)

     {

     if (stock_gid[i]=='}')

               {

                         stock_gid[i]=0;

               }

     }

 

     /*添加数据*/

     GRect frame_data = {{0, 60}, {16, 128}};

    add_text_layer(h_window, &g_layer_id_data, stock_gid, &frame_data, GAlignCenter, U_ASCII_ARIAL_14, GColorWhite);

    

     app_window_update(h_window);

}

 

static void watch_time_change_callback(enum SysEventType type, void *context)

{

    /*时间更改,分变化*/

    if (type == SysEventTypeTimeChange)

    {

        uint8_t text_buf[40];

        struct date_time t;

        app_service_get_datetime(&t);

 

        memset(text_buf, 0, sizeof(text_buf));

        sprintf((char *)text_buf, "%s", (char *)&weekday[t.wday][0]);

        sprintf(&text_buf[6], "%02d-%02d", t.mon, t.mday);

 

        GRect frame;

        frame.origin.x = DATE_TEXT_POS_X;

        frame.origin.y = DATE_TEXT_POS_Y;

        frame.size.h   = DATE_TEXT_SIZE_H;

        frame.size.w   = DATE_TEXT_SIZE_W;

 

        add_text_layer(h_window, &g_layer_id_date, (char*)text_buf, &frame, GAlignLeft, U_ASCII_ARIAL_14, GColorWhite);

 

        frame.origin.x = TIME_TEXT_POS_X;

        frame.origin.y = TIME_TEXT_POS_Y;

        frame.size.h   = TIME_TEXT_SIZE_H;

        frame.size.w   = TIME_TEXT_SIZE_W;

 

        memset(text_buf, 0, sizeof(text_buf));

        sprintf(text_buf, "%02d:%02d", t.hour, t.min);

 

        add_text_layer(h_window, &g_layer_id_time, (char*)text_buf, &frame, GAlignLeft, U_ASCII_ARIAL_14, GColorWhite);

 

        app_window_update(h_window);

    }

}

 

static void data_timer_callback(date_time_t tick_time, uint32_t millis, void *context)

{

    data_request_web();

}

 

int main()

{

 

     /*创建消息列表窗口*/

     h_window = init_btc_window();

 

     //订阅时间改变事件

    maibu_service_sys_event_subscribe(watch_time_change_callback);

    

     /*放入窗口栈显示*/

     g_window_id = app_window_stack_push(h_window);

 

     data_request_web();

 

     /*注册通讯结果回调*/

     maibu_comm_register_result_callback(data_comm_result_callback);

 

     //注册网络请求回调函数

    maibu_comm_register_web_callback(web_recv_callback);

 

    //聚合数据源每隔10s更新数据

    app_window_timer_subscribe(h_window, 10000, data_timer_callback, (void *)h_window);

    

     return 0;

}

 

编译之后上传到手表上就OK了。

看起来工作正常,美中不足就是屏幕有点小,如果能像美国队长那块一样大就完美了。

image004

这块手表的一大重要特性就是采用电纸屏幕,就是那种 Kindle 的屏幕材质,不改变内容无需耗电,这样待机可以很长时间(标称21天)。说道这里,我想起来一个很老的苏联笑话:

有一个人在机场等六点的飞机,可是他忘记了带手表,于是他想找

个人问问。这时,他看见一个人提着两个巨大的手提箱吃力的走过来,手腕上戴

着一块异常漂亮的一看就知道是高科技产品的手表。

  “请问,几点了?”他问道。

  “哪个国家的时间?”那人反问。

  “哦?”他的好奇心来了,“你都知道哪些国家的时间呢?”

  “所有的国家,”那人回答道。

  “哇!那可真是一块好手表呀!”

  “还不止这些呢,这块表还有GPS卫星系统,可以随时收发电子邮件、

传真,这个彩色的屏幕可以收看NTSC制式的电视节目!”那人给他

演示,果真如此!

“哦!太棒了,我真想拥有一块这样的手表,您……您可以把它卖给我吗?”

“说实话,我已经烦透了这块表了,这样吧,900美元,如何?”

  他马上掏出支票簿,写了900美元给那人,“成交!”

 “好的,现在,它是你的了。”那人如释重负,把手表交给他,“这个是你

的手表”,然后指着地上的两个大箱子说,“这两个是电池!”

无数的科学家和工程师在不断的努力改造着我们的生活。

好玩的电子仪器(1)

本系列文章将会介绍一些电子工程师用到的“奇怪”设备。
对于我这样的Firmware工程师来说,硬件充满了神秘,而对于硬件工程师来说,射频更加神秘。
这几天组里的WIFI工程师在摆弄一台机器,好奇之下,我和他聊了一下,他介绍说这是一台无线信号分析器。能够捕获和分析无线信号。
image002

好奇之下,我第二天拿了一个玩具的遥控器给他请他帮忙分析。
image004

他先是询问了一下大概的频率,我说不清楚可能是 315M 的。他就设定扫描100-400M 的频率范围,我一边拨动开关让遥控器工作起来,设备就开始扫描和记录这个范围内的无线信号。很快,我们就在 108Mhz 上看到了信号。之后他又设定了 0-200Mhz作为扫描范围。结果发现,真正的信号是 54Mhz,刚才看到的108Mhz只是一个倍频。结果就是下面这个图片。

image006

后来偶然间我把遥控器翻了过来,上面贴着标签,赫然写着“54Mhz”,看起来这个遥控器频率确实是 54Mhz。
之后我又和他聊了一下,我问他是否有可能用这个设备分析出具体的信号,然后我可以用其他发射器做一个遥控器。他说无法做到,因为这个设备只能用来扫描载波的频率,而实际上有着多种多样的调制方式。比如,我们可以用这个设备来看到2.4G 鼠标和USB3.0 在频率段上冲突,但是具体的冲突方式是无法看到的。
我们实验室是开放的,在刚才的测试过程中还乱入了一个100Mhz 的信号(箭头所示)。真正的分析应该是在铁皮包裹的无线电屏蔽室完成的。比如,之前我在的一家公司,产线生产出来的蓝牙模块要到这样屏蔽室来完成最后的调整和测试。
image008

最后再补充一个八卦的故事,讲讲这个屏蔽室的故事。前面说了,我之前工作的一家公司为了测试蓝牙,弄了个屏蔽室。这个房间很小,建在公司一个大办公室的中间的,10个平方,或者说30个立方更合适,估计有厨房或者厕所那么大。这种专业性很强的事情只能找专业的公司做,台湾人也有相互扶助的传统,找了另外一家台湾公司。建好了之后,承建方发现最大的问题是收不上来钱,每次要钱财务总是说等研发验收或者托词主管不在。

貌似拖欠款项是我之前那家公司的传统,原因是拖欠的货款就是自己的利润,打个比方:拖欠1万,存在余额宝,每天就是八毛多,老板可以多吃一个鸡蛋。如果拖欠10万,那么老板的早餐就有着落了。我在的时候遇到过几次元件供应商因为收不上钱而停止供货的事情。老板就会压着研发换物料。据说之前还发生过逢年过节,供应商开着卡车堵大门的事情,只是我没有碰到过,想必也很热闹。

后来,承建屏蔽室的哥们怒了,直接打电话给财务,说我叔叔是“立委”的 xxx,如果我明天还看不到钱,我就召开记者发布会让你们上《苹果日报》。财务接电话的不是台湾人,琢磨半天“立痿”是什么毛病。后来和他老板汇报了一下,老板急忙查了一下,真有这么个人,也甭管是三叔二叔还是表叔,自己理亏,还是把钱还了吧。第二天,财务主管主动打电话过去主动道歉,跪舔一番,欠款如数奉上,这个事情就了结了。

再后来,还是这个屏蔽室,测试蓝牙,买了一套软件能够自动扫频率和调教模块。结果又是欠对方钱,双方扯皮,只是对方早有防备,软件中加了一个什么逻辑锁,试用期结束后不能自动测试。老板灵机一动,找一批操作工三班倒,简单培训后就上岗,用设备纯手工测试,节省环保还智能……….

有诗为证:

小小公司真奇妙,
老板看着似土豪。
怎料长欠不愿还,
欲想追债只能闹。

自动解压ZIP的批处理

比如,我在一个目录下有大量的 zip 文件,我想解压之,就可以使用下面的批处理

for %%i in (*.zip) do 7z x %%i -o%%~ni

其中:

for %%i in (*.zip) 是枚举目录下的全部 zip 文件名

7z x %%i 是按照完整路径解压的意思

-o%%~ni 是解压到文件名相同的目录的意思

比如,我有一个zip文件名称是 labz.zip 那么运行之后,会生成一个 labz 的目录,然后将文件内容解压进去

Pin13 上LED亮度减半的问题

网上有朋友提了一个问题“arduino怎么把13号引脚的亮度减小一半”。自然而然的想法是用PWM做,查一下资料,非常不幸 Uno的Pin13不是PWM。

pin13

我们只能采用模拟PWM的方式来做。首先想到的是直接在 Timer0 的ISR (在\hardware\arduino\avr\cores\arduino\wiring.c)上加入内容。但是实验结果非常糟糕,肉眼能够看到LED在闪烁。于是,需要自己设置一个快速的中断,然后在里面写拉高低的代码。
最终找到tsaiwn 编写的代码【参考1】。直接在上面修改:
设定的亮度有十级别,0是最亮,9是最暗。

// 控制 LED 亮滅, 每秒閃爍 5000 次: 亮 0.0001 秒滅 0.0001 秒 …
// Prescaler 用 64
volatile int ggyy = 1; // 使用這當 Flag 給 ISR 使用 !
int ledPin =13;

unsigned char myPWM=0;
unsigned char myPCounter=0;
/// For Prescaler == 64
/// 1 秒 / (16 000 000 / 64) = 1/250000 = 0.000004 sec / per cycle
/// 0.1 sec / 0.000004 sec -1 = 25000 -1 = 24999
/// 0.0001 sec / 0.000004 sec -1 = 25 -1 = 24
const int myTOP = 24; // 0.0001 sec when Prescaler == 64
///// Interrupt Service Routine for TIMER1 CTC on OCR1A as TOP
/// 注意以下名稱是有意義的, 不可亂改 !
ISR(TIMER1_COMPA_vect)
{
//digitalWrite(ledPin, ggyy); // ggyy 是 0 或 1
//ggyy = 1 – ggyy; // 給下次進入 ISR 用
myPCounter++;
if ((myPCounter % 10) >= myPWM)
{
digitalWrite(ledPin, HIGH);
}
else
{
digitalWrite(ledPin, LOW);
}

}
void setup( ) {
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW); // turn Off the LED
cli(); // 禁止中斷
TCCR1A = 0;
TCCR1B = (1<

【入门指南】图解如何使用 USBTinyIsp

每一个Arduino 的玩家都应该准备一个 ISP 下载器,有了它能让你的 Uno 玩出很多花样。这里就介绍一下如何使用 USBTinyISP。本文将介绍使用这款工具的三种刷写方式:

  • 第三方软件刷写 328P BootLoader的方法
  • Arduino IDE 刷写 328P BootLoader的方法
  • Arduino IDE 刷写328P上面程序的方法

首先,你要有一个USBTinyISP烧录器。下面的是购买自淘宝的 OCROBOT 出品,价格是 45元,属于较贵的。
image001

这款需要安装驱动才能工作,淘宝很多出售的号称不需要驱动程序,我也曾经入手过一个,一直无法使用。因此,建议购买之前要和卖家沟通好,索要驱动,特别的如果卖家要求关闭数字签名才能安装驱动也请不要购买。这样做会让你的系统暴露在风险之中,另外,64位Windows由于本身的特性无法关闭数字签名功能。

插入之后安装驱动,驱动来自【参考1】。安装之后是下面这样(特别提醒,如果插入之后出现无法获得USB Descriptor 的提示,那么请更换一个USB端口,产生这个问题的原因可能是某些USB3.0端口和这个卡上的控制芯片有兼容问题)

image002

image004

image003

image005

Arduino UNO 上有2个烧录口,一个是给 USB 转串口芯片使用的,一个是给 328P 使用的。通常我们需要烧录的是328P 【参考】。也就是下图中的橘色标记口。
image006

直接将USBTinyISP的头插入这个位置即可。需要注意的是:

  1. 建议切断Uno上的外部供电之后进行烧写;
  2. 插入是有方向的,插入之后无论正反,板子上的灯都会亮。但是反插无需担心烧毁

至此,硬件部分准备完毕,下面就是如何使用软件进行烧写了。

  • 第三方软件Avrdudess刷写 328P BootLoader的方法

运行 Avrdudess,先选择 USBTinyISP

image007
然后使用右侧Detect 按钮进行芯片识别。正常情况下,主芯片应该是328P,USB转串口芯片应该是16U2或者8U2如果没有识别,应该是插线反了导致的,请调换方向重新插入
image008
最后,选择要烧写的文件,再使用GO 按钮即可进行烧录
image009
特别提醒:不要调整右侧 Fuses 选项。

二.      Arduino IDE 刷写 328P BootLoader的方法

  1. Tools->Board->选择你的板子型号,比如:Uno
  2. Tools->Programmer->USBTinyISP 选择烧写器的型号

image0103.Tools->BurnBootloader 直接烧写 328P 的Bootloader

image011

烧写成功的话,会有下面的提示信息

image012

  • Arduino IDE 刷写328P上面程序的方法

某些情况下,我们需要直接烧写328P上面的程序,方法如下:

  1. IDE中保证你的代码能够正常编译通过
  2. IDE中选择板子型号和烧写器型号和上面介绍的步骤一样
  3. 使用File->Upload Using Programmer

image013
运行成功后有下面类似提示

image014

本文首发 http://www.arduino.cn/thread-21619-1-1.html

文章中提到的工具和驱动也可以在上面的网址找到

参考:

  1. https://learn.adafruit.com/usbtinyisp/drivers 其实国内大多数 Arduino 产品都是仿造国外的而已,软件和Firmware一直是软肋。
  2. https://www.lab-z.com/ardfim/

Step to UEFI (94)Source Level Debug

直接进行 Source Level的 Debug 对于学习过是非常有用,本文就介绍一下如何在 NT32Pkg的模拟环境下进行 Source Level Debug 的方法。使用的环境是 Windows7 + UDK2015+VS2013。调试的目标是 UDK2015 AppPkg 自带的 Hello。

在 Hello.INF 中加入下面的编译参数:

[BuildOptions]
MSFT:DEBUG_*_IA32_DLINK_FLAGS = /EXPORT:InitializeDriver=$(IMAGE_ENTRY_POINT) /ALIGN:4096 /FILEALIGN:4096 /SUBSYSTEM:CONSOLE

 

此外,在代码中加入_asm int 3 ,当编译器遇到这个命令时,会自动停止准备调试

INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
  _asm int 3  
  Print(L"Hello there fellow Programmer.\n");
  Print(L"Welcome to the world of EDK II.\n");

  return(0);
}

 

上述修改完成后,正常编译

Build –a IA32 –p AppPkg\AppPkg.dsc

编译完成之后,在NT32 模拟环境中运行 hello.efi, 会出现下面的错误信息,选择使用 VS2013 调试
image001

然后就可以看到带着源码的调试界面了
image003

我试验了一下,感觉还不错。

一般情况下BIOS工程师不会进行源码级别的调试,最直接的原因是:这样的调试过于复杂,必须额外的硬件支持,要么需要 AMI Debugger ,要么需要 XDP 之类的高级工具,单纯的准备就需要花费很多时间。

自从有了UEFI 的架构,串口 Debug 信息是 BIOS 工程师最忠实的伙伴。

【注释】:本文根据《UEFI 原理与编程》一书 P59 3.4 调试 UEFI 写成。只是这本书上给出来的方法我试验并不好用,疑惑之间搜索到了该书作者在他的Blog上提到这个问题,试验之后完全没有问题:

http://www.cppblog.com/djxzh/archive/2015/02/08/209766.html
“书中讲到了如何利用_asm int 3 调试代码。
_asm int 3需要配合Nt32Pkg使用。也就是说通过Nt32Pkg编译出的.efi文件才能够调试。
如果你带_asm int 3语句的工程是通过非Nt32Pkg编译出来的(例如AppPkg),在SecMain模拟器中调试会导致断点停在Image.c文件如下代码中
Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);
在模拟器控制台会输出
WARNING: No source level debug
表明SecMain在加载你的模块时没有成功加载调试符号。
解决方案
在.inf文件中添加如下代码
[BuildOptions]
MSFT:DEBUG_*_IA32_DLINK_FLAGS = /EXPORT:InitializeDriver=$(IMAGE_ENTRY_POINT) /ALIGN:4096 /FILEALIGN:4096 /SUBSYSTEM:CONSOLE

VS2013 编译的问题

最近安装了一个 VS2013进行 UEFI 的编译,碰到了其怪的错误:

“C:\Program Files\Microsoft Visual Studio 12.0\Vc\bin\link.exe” /out:”c:\udk2015\Build\NT32IA32\DEBUG_VS2013\IA32\SecMain.exe” /base:0x10000000 /pdb:”c:\udk2015\Build\NT32IA32\DEBUG_VS2013\IA32\SecMain.pdb” /LIBPATH:”C:\Program Files\Microsoft Visual Studio 12.0\VC\\Lib” /LIBPATH:”C:\Program Files\Windows Kits\8.1\Lib\winv6.3\um\x86″ /NOLOGO /SUBSYSTEM:CONSOLE /NODEFAULTLIB /IGNORE:4086 /MAP /OPT:REF /DEBUG /MACHINE:I386 /LTCG Kernel32.lib MSVCRTD.lib Gdi32.lib User32.lib Winmm.lib Advapi32.lib /EXPORT:InitializeDriver=_ModuleEntryPoint /BASE:0x10000 /ALIGN:4096 /FILEALIGN:4096 /SUBSYSTEM:CONSOLE @c:\udk2015\Build\NT32IA32\DEBUG_VS2013\IA32\Nt32Pkg\Sec\SecMain\OUTPUT\static_library_files.lst

SecMain.lib(WinNtThunk.obj) : error LNK2001: unresolved external symbol _SetCons
oleCursorInfo@8
c:\udk2015\Build\NT32IA32\DEBUG_VS2013\IA32\SecMain.exe : fatal error LNK1120: 1
51 unresolved externals

build…
: error 7000: Failed to execute command
C:\Program Files\Microsoft Visual Studio 12.0\Vc\bin\nmake.exe /nologo t
build [c:\udk2\015\Build\NT32IA32\DEBUG_VS2013\IA32\Nt32Pkg\Sec\SecMain]

仔细研究发现是虚拟机无法找到一些 Windows SDK 的内容。后来经过研究发现错误的原因是因为我使用了错误 VS2013 的控制台。我想编译 X64的UEFI 代码,但是进入了 x86(IA32)的控制台。

tt

正确的做法是:

如果你要编译x64 的代码,那么需要进入上面红色框中的命令行模式。然后,Conf\Target.txt 中的TOOL_CHAIN_TAG 必须是VS2013;

如果你要编译IA32的代码,那么需要进入上面绿色框中的命令行模式。然后,Conf\Target.txt 中的TOOL_CHAIN_TAG 必须是VS2013x86;

否则编译器无法找到对应的SDK 文件。

简单代码打造 Arduino 整蛊设备

如果你使用的是Intel 集成显卡的话,安装好驱动之后可以使用键盘上的 Ctrl + Alt+方向键旋转屏幕。根据这个功能,我们可以尝试制作一个整蛊的硬件。原理上来说就是使用 Leonardo模拟按下这个组合键,发送给系统来实现转动。当然为了防止你的朋友们变成下面这个样子,代码使用随机转动:
image001

代码非常简单

#define KEY_LEFT_CTRL		0x80
#define KEY_LEFT_ALT		0x82
#define KEY_UP_ARROW		0xDA
#define KEY_DOWN_ARROW		0xD9
#define KEY_LEFT_ARROW		0xD8
#define KEY_RIGHT_ARROW		0xD7

void setup() {
  // put your setup code here, to run once:
  Keyboard.begin();
}

void loop() {

  delay(5000);
  
  // CTRL-ALT-LEFT
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press(KEY_LEFT_ALT);
  
  switch(random(3)){
    case 0:
      Keyboard.press(KEY_LEFT_ARROW);    
      break;
    case 1:
      Keyboard.press(KEY_RIGHT_ARROW);        
      break;
    case 2:
      Keyboard.press(KEY_UP_ARROW);        
      break;
    case 3:
      Keyboard.press(KEY_DOWN_ARROW);        
      break;
  }
  Keyboard.releaseAll();

}

 

运行之后的效果:

image003

image002

关于按键信息在下面这个文件中可以看到:
\hardware\arduino\sam\cores\arduino\USB\USBAPI.h
弄明白上面的原理,你还可以做一个能够遥控旋转的设备,如果你不想让别人动你的电脑就让他转起来,很快对方就会知难而退的。

Step to UEFI (93)FTDI 串口驱动

FDTI232系列芯片是最好的USB转串口芯片,驱动全,芯片本身很稳定,兼容性也很好。这次介绍一个FTDI USB串口芯片的项目,地址是

https://github.com/tianocore/tianocore.github.io/wiki/Tasks-USB-Serial-Adapter-driver#Real_UEFI_system

项目的简介如下:

“Today there are many inexpensive USB Serial adapters available, and most systems are built with USB ports available. But at the same time, the dedicated Serial port is becoming less common to find available in a system.
A serial port can still be useful for software debugging purposes. (debug trace messages)
It can also be useful in providing a secondary terminal to the UEFI system.
This task would involve writing a USB driver which interfaces with a USB Serial Adapter.
Ideally, this project should enable a driver that will attach to the USB Serial Adapter and produce the SerialIo protocol to enable the UEFI terminal to become available through the USB Serial adapter.”。

简单的说如果你的系统当前有 FTDI的USB串口,那么驱动会帮你生成一个 SerialIO 供你使用。这样你就可以在UEFI 环境中使用 FTDI 的USB 串口进行通讯。
在使用之前,请确定你的USB串口设备ID,例如:我现在使用的USB串口在 Windows下面看到的信息是这样的:
image002

image001

本文并不打算做原理上的分析,只是介绍如何编译和实验。
实验环境是 UDK2015

1.在 C:\EDK\Nt32Pkg\Nt32Pkg.dsc 文件的 [Components] 段中添加下面的内容

 

Nt32Pkg/WinNtBlockIoDxe/WinNtBlockIoDxe.inf
Nt32Pkg/WinNtSerialIoDxe/WinNtSerialIoDxe.inf
Nt32Pkg/WinNtGopDxe/WinNtGopDxe.inf
Nt32Pkg/WinNtSimpleFileSystemDxe/WinNtSimpleFileSystemDxe.inf
MdeModulePkg/Application/HelloWorld/HelloWorld.inf
#LabZ_Start
FtdiUsbSerialDxe/FtdiUsbSerialDxe.inf
#LabZ_End
#
# Network stack drivers
# To test network drivers, need network Io driver(SnpNt32Io.dll), please refer to NETWORK-IO Subproject.
#

特别说明:如果有可能,最好在你当前的项目上重新这样编译一次。比如,你打算在BayTrail上用这个驱动,那么最好再Baytrail的BIOS中重新编译一次,这样最为稳妥。
2.将FtdiUsbSerialDxe目录拷贝到你UDK 的根目录下 例如: C:\EDK\
3.使用 Build 命令编译 NT32,这次是编译为 X64 的驱动。 命令式 Build –a X64
至此,驱动程序已经编译完成。可以在Build目录下找到我们需要的驱动,名称是 FtdiUsbSerialDxe.efi。 接下来我们就可以进行实验。
实验是在实体机上进行的,让一款“酷比魔方”的平板电脑和台式机进行通讯。实验中我使用了2个USB串口设备,还有一段双母头线(因为USB转串口都是公头)。

1. 首先检查一下,当前系统中没有 SerialIo Protocol
image003

2. 加载驱动image004

3. 再次检查会发现系统中多了一个 SerialIO protocol
image005
直接 dh 进行检查会发现多了2个handle 一个是驱动,一个是新增的挂protocol
image006
4. 之后使用我们前面介绍过的串口工具【参考1】
image007
看起来在启动的时候串口上会有一些乱码

在 Windows 端启动串口工具,双方可以进行通讯。
image008

完整的代码下载
FtdiUsbSerialDxe
编译好的X64 驱动下载
FtdiUsbEFI

特别注意:本文提到的代码是在 Windows7 VS2013 UDK2015环境下编译生成。

参考:
1. http://www.lab-z.com/stu91/ Step to UEFI (91) Shell下的串口测试软件