Step to UEFI (96)Build 参数介绍

最近在认真研读《UEFI 原理与编程》,强烈推荐这本书,希望深入学习UEFI 的朋友都应该购买一本反复阅读。

《UEFI 原理与编程》第二章提到了build 命令的常用参数,我认真考察了一下。在 CMD窗口下运行 build –h 可以获得支持的命令参数列表:

–version 显示build的版本然后退出
-h, –help 显示帮助信息然后退出
-a TARGETARCH, –arch=TARGETARCH 指定编译目标,可选的有IA32, X64, IPF, ARM, AARCH64 或者 EBC。这个参数会覆盖 target.txt中指定的TARGET_ARCH。我们经常使用的 –a IA32 编译出来的 Application,更换为 –a X64即可编译出X64平台下可以用的 Application。
  -p PLATFORMFILE, –platform=PLATFORMFILE

 

 

指令要编译的 package。这个参数会覆盖 target.txt中指定的 ACTIVE_PLATFORM。
  -m MODULEFILE, –module=MODULEFILE 编译指定的模块。就是编译指定 INF文件。
  -b BUILDTARGET, –buildtarget=BUILDTARGET 指定编译为 RELASE版本还是 DEBUG版本。这个参数会覆盖 target.txt中指定的TARGET。
  -t TOOLCHAIN, –tagname=TOOLCHAIN

 

指定使用的工具集。这个参数会覆盖 target.txt中的TOOL_CHAIN_TAG 定义.

 

 

-x SKUID, –sku-id=SKUID

 

 

使用的给出的SKU ID来进行编译,覆盖DSC文件中的SKUID_IDENTIFIER (没用过。检查UDK2015 Source Code这个参数定义为 DEFAULT)
  -n THREADNUMBER 指定编译中使用的线程数量。理论上说线程越多,编译速度越快。这个参数会覆盖target.txt中的MAX_CONCURRENT_THREAD_NUMBER
  -f FDFFILE, –fdf=FDFFILE

 

指定 FDF的名称。这个参数会覆盖DSC文件中指定的FDF文件名。
  -r ROMIMAGE, –rom-image=ROMIMAGE 指定生成的 FD文件名称。这个名称必须是FDF文件中[FD] 节中存在的文件名。
  -i FVIMAGE, –fv-image=FVIMAGE

file.

指定生成的FV文件名称。这个名称必须是FDF文件中[FV] 节中存在的名称。
  -C CAPNAME, –capsule-image=CAPNAME

 

 

指定生成的Capsule文件名称。这个名称必须是FDF文件中[Capsule] 节中存在的名称。Capsule 是一种专门用来升级的UEFI定义的格式。比如:有对应的BIOS Capsule 文件,在启动过程中你把对应的文件放在U盘上,BIOS会自动识别并且加载然后升级自身,这样的好处是刷新环境稳定,避免操作系统的影响,避免被破解的风险。坏处是实际操中挺麻烦,需要准备FAT32格式的U盘。
  -u, –skip-autogen 跳过 Autogen 的过程进行编译
  -e, –re-parse 只做分析和生成编译需要的准备工作,不做实际编译,相当于只做AutoGen。
  -c, –case-insensitive 不检查 INF文件中的大小写
  -w, –warning-as-error 将 Warning视作 Error
-j LOGFILE, –log=LOGFILE

 

将编译过程中输出到屏幕上的信息同样输入到文件中。如果信息太多,无法看到期望的出错信息不妨试试这个参数直接查看Log文件。
  -s, –silent 沉默模式执行NMAKE。
  -q, –quiet           Disable all messages except FATAL ERRORS.

 

 

关闭除了致命错误之外的全部信息显示。感觉和上面的 –s没有差别。
-v, –verbose

 

打开 verbose模式(完整信息),包括 library实例,dependency表达式,warning信息等等。
  -d DEBUG, –debug=DEBUG

 

 

看起来是控制编译期的Debug信息输出。
  -D MACROS, –define=MACROS

 

定义宏
-y REPORTFILE, –report-file=REPORTFILE

Create/overwrite the report to the specified filename.

 

创建/覆盖指定的报告。这个功能非常有用,可以生成一个 report文件,其中包括了项目定义的 PCD变量等等的信息。这对于查看一些编译期的参数非常有用。
  -Y REPORTTYPE, –report-type=REPORTTYPE

 

 

创建/覆盖指定内容的报告。内容范围在[PCD, LIBRARY, FLASH, DEPEX, BUILD_FLAGS, FIXED_ADDRESS, EXECUTION_ORDER]。
  -F FLAG, –flag=FLAG  Specify the specific option to parse EDK UNI file.

Must be one of: [-c, -s]. -c is for EDK framework UNI

file, and -s is for EDK UEFI UNI file. This option can

also be specified by setting *_*_*_BUILD_FLAGS in

[BuildOptions] section of platform DSC. If they are

both specified, this value will override the setting

in [BuildOptions] section of platform DSC.

 

 

指定处理uni文件的方法,可选项是[-c,-s]。-c是用来处理EDK架构的UNI;-s用来处理EDK架构的 UNI。我不清楚具体有什么差别,如果你了解不妨告诉我
-N, –no-cache 关闭缓存机制。在EDK编译过程中是要生成一个小型的数据库文件的。我之前就遇到过杀毒软件会误杀这个数据库导致无法正常编译的情况。
–conf=CONFDIRECTORY 指定 Conf 的目录。
  –check-usage

 

检查 INF中提到的Source文件是否存在。只是检查而已,没有其他动作。
  –ignore-sources      Focus to a binary build and ignore all source files 不使用源程序,强制使用编译生成的二进制文件继续编译。

 

上述是通过简单实验和个人理解进行翻译的,如果有不妥当欢迎直接评论中指出或者给我写邮件。万分感谢!

 

 

2019年2月12日 补充。上面提到的 -Y 参数查看 PCD 设定的例子(UDK2018):

build -x X64 -Y PCD -y zz.txt

运行结束后在根目录下可以看到 zz.txt。需要注意的是:

1.建议先编译一次,确定能跑过再继续;

2.zz.txt 是编译结束的时候才生成的。

Arduino 使用固态继电器的例子

平时我们使用的继电器都是下面这种,内部是有机械结构来控制通断的。在工作的时候我们会听到“咔”的声响。
image002
如果你是持续的操作或者是在安静的环境下使用,这种声音就会非常恼人。此时可以考虑使用固态继电器,长相如下:
image004

注意,固态继电器有直流和交流的差别,上面这样一个差不多11元,比继电器模块贵一些(如果你的继电器模块功能比较全,有光耦之类的其实二者价格相差不多)。
上面图片显示的就是我入手的,控制端输入3-32V直流,被控制端可以控制5-60V直流。
具体线路连接如下:
image006

#define ControlPin 8
void setup()
{
    Serial.begin(9600);
    pinMode(ControlPin,OUTPUT);      //该端口需要选择有#号标识的数字口
    digitalWrite(ControlPin,LOW);
}

void loop()
{
  char  c;

    while (Serial.available() > 0)  
    {
        c=Serial.read();
        if (']'==c) 
          {
            digitalWrite(ControlPin,HIGH);
          }
        if ('['==c) 
          {
            digitalWrite(ControlPin,LOW);
          }

    }
}

 

最后放一下工作的照片
image007
总结【参考2】固态继电器的优缺点
1、固态继电器的优点
(1)高寿命,高可靠:SSR没有机械零部件,有固体器件完成触点功能,由于没有运动的零部件,因此能在高冲击,振动的环境下工作,由于组成固态继电器的元器件的固有特性,决定了固态继电器的寿命长,可靠性高。
(2)灵敏度高,控制功率小,电磁兼容性好:固态继电器的输入电压范围较宽,驱动功率低,可与大多数逻辑集成电路兼容不需加缓冲器或驱动器。
(3)快速转换:固态继电器因为采用固体其间,所以切换速度可从几毫秒至几微妙。
(4)电磁干扰笑:固态继电器没有输入”线圈”,没有触点燃弧和回跳,因而减少了电磁干扰。大多数交流输出固态继电器是一个零电压开关,在零电压处导通,零电流处关断,减少了电流波形的突然中断,从而减少了开关瞬态效应。
(5)无噪音

2、固态继电器的缺点
(1)导通后的管压降大,可控硅或双相控硅的正向降压可达1~2V,大功率晶体管的饱和压浆液灾1~2V之间,一般功率场效应管的导通电祖也较机械触点的接触电阻大。
(2)半导体器件关断后仍可有数微安至数毫安的漏电流,因此不能实现理想的电隔离。
(3)由于管压降大,导通后的功耗和发热量也大,大功率固态继电器的体积远远大于同容量的电磁继电器,成本也较高。
(4)电子元器件的温度特性和电子线路的抗干扰能力较差,耐辐射能力也较差,如不采取有效措施,则工作可靠性低。
(5)固态继电器对过载有较大的敏感性,必须用快速熔断器或RC阻尼电路对其进行过在保护。固态继电器的负载与环境温度明显有关,温度升高,负载能力将迅速下降。

另外,一般的继电器输出有3个Pin分别是:地,常开和常闭。固态继电器只有2个Pin,购买的时候就需要确定是常开的还是常闭。
参考:
1. http://www.lab-z.com/mos%E6%8E%A7%E5%88%B6%E5%B0%8F%E7%81%AF%E6%B3%A1%E7%9A%84%E5%AE%9E%E9%AA%8C/ MOS控制小灯泡的实验
2. http://www.elecfans.com/yuanqijian/jidianqi/20121030295690.html 固态继电器简介及优缺点

Step to UEFI (95)又一个截图软件

之前我写过一个UEFI 截图软件,功能有限,最近在网上看到了一个开源的截图软件【参考1】,支持热键,抓图结果会被转化为 Png格式,自动存在FSx:下面。于是,下载编译实验之。

代码中lodepng.h 和qsort.c 中对于size_t 的定义有些问题,看起来他是想使用VS默认的定义,但是不知道为什么我在 UDK2015 + Vs2013 下面编译会有问题,重新定义一下,编译就OK了。

编译好的代码无法在NT32模拟环境下运行。下面是在平板电脑的 Shell 中运行的结果,使用 Load 命令加载之后就可以使用 ctrl+alt+F12截屏:

image001

image002

之后在Intel  Kabylake HDK上测试

image003

进入 RU 之后还可以正常工作

image004

除了一般的Shell下截图,还可以先进入 Shell 加载之后再退出到Setup中,同样的热键截图

image005

修改之后的源代码在这里:

CrScreenshotDxe

参考:

1.项目地址在 https://github.com/LongSoft/CrScreenshotDxe

2023年5月6日

编译后的 X64 版本EFI程序可以在这里下载

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

我对手表有着深厚的感情,每当人生遇到坎坷时,或者愤怒不已的时候,我都会想象和安慰自己“我去年买了块表”。刚有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