Python 重新加载代码

当你使用  import YourFile.py 之后YourFile.py就会加载到内存中,即使你修改了本地的文件,再次调用也会是修改之前的代码。因此,这里需要用 reload 来强制重新加载。使用一个例子进行说明,首先编写一个简单的代码:

def Test():

print(“Test Script from labz”)

下面的步骤1导入上面的代码,然后运行 Test() 函数。接下来修改上面的代码,在输出的字符串末尾加入字符 2.步骤2中再次Import 然后运行,可以看到没有效果:

    之后从 imp 中 import reload 函数(我的 python 是 3.8 , 这里同 2.X 的版本有差别)。再次 reload 代码之后可以看到结果有变化。

总结:可以通过 from imp import reload, 之后每次 reload 你需要的文件即可刷新。

Step to UEFI (227)VS2019 EDK202008 下的 Libc 编译

LibC 是 EDK2 的一个库,提供了标准 C 库的支持。这个库在 VS2015 + EDK202008 的组合下工作正常,但是在 VS2019 下就会遇到一些问题,本文将介绍如何解决之。完成的目标是:完整编译 LibC 提供的 AppPkg 。LibC 可以在 https://github.com/tianocore/edk2-libc 下载到,打开之后内容如下:

Libc 解压之后文件

解压三个目标到 EDK202008 后即可开始编译。

使用 “x86 Native Tools Command Prompt for VS2019”

VS2019 x86 窗口

命令

  1. edksetup
  2. build -a X64 -p AppPkg\AppPkg.dsc -t VS2019  (特别注意, VS2019 必须大写)

之后就遇到了第一个错误:

“Socket.c
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(573): error C2220: the following warning is treated as an error
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(573): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(1337): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(1480): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(1892): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(2754): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(2974): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\Hostx86\x64\cl.exe"' : return code '0x2'
Stop.”

解决方法是:在 StdLib.inc 末尾添加下面的内容:

# Temporarily restrict compiler warnings to those produced by VS2012.
  # Code that fails when these flags are removed will have to be rewritten
  # in order to pass.  This may be as simple as renaming an object, but may
  # require more significant changes.
    MSFT:*_VS2015_*_CC_FLAGS          = /Wv:11
    MSFT:*_VS2015x86_*_CC_FLAGS       = /Wv:11
    MSFT:*_VS2015xASL_*_CC_FLAGS      = /Wv:11
    MSFT:*_VS2015x86xASL_*_CC_FLAGS   = /Wv:11

#LABZ_Start
  # Temporarily restrict compiler warnings to those produced by VS2012.
  # Code that fails when these flags are removed will have to be rewritten
  # in order to pass.  This may be as simple as renaming an object, but may
  # require more significant changes.
    MSFT:*_VS2019_*_CC_FLAGS          = /Wv:11
    MSFT:*_VS2019x86_*_CC_FLAGS       = /Wv:11
    MSFT:*_VS2019xASL_*_CC_FLAGS      = /Wv:11
    MSFT:*_VS2019x86xASL_*_CC_FLAGS   = /Wv:11
#LABZ_End

再次编译会遇到下面这个这个错误:

        "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\Hostx86\x64\link.exe" /OUT:c:\buildbs\edk202008\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\Sockets\OobTx\OobTx\DEBUG\OobTx.dll /NOLOGO /NODEFAULTLIB /IGNORE:4001 /IGNORE:4281 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:_ModuleEntryPoint /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG /WHOLEARCHIVE  @c:\buildbs\edk202008\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\Sockets\OobTx\OobTx\OUTPUT\static_library_files.lst
LibGdtoa.lib(ldtoa.obj) : error LNK2001: unresolved external symbol __fpclassifyd
BsdSocketLib.lib(res_send.obj) : error LNK2001: unresolved external symbol inet_ntoa
BsdSocketLib.lib(res_mkupdate.obj) : error LNK2001: unresolved external symbol inet_aton
BsdSocketLib.lib(ns_print.obj) : error LNK2001: unresolved external symbol inet_ntop
BsdSocketLib.lib(getnetbyht.obj) : error LNK2001: unresolved external symbol inet_network
c:\buildbs\edk202008\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\Sockets\OobTx\OobTx\DEBUG\OobTx.dll : fatal error LNK1120: 5 unresolved externals
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\Hostx86\x64\link.exe"' : return code '0x460'
Stop.
build.py...
 : error 7000: Failed to execute command
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\Hostx86\x86\nmake.exe /nologo tbuild [c:\buildbs\edk202008\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\Sockets\OobTx\OobTx]


build.py...
 : error 7000: Failed to execute command
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\Hostx86\x86\nmake.exe /nologo tbuild [c:\buildbs\edk202008\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\Sockets\DataSink\DataSink]


build.py...
 : error F002: Failed to build module
        c:\buildbs\edk202008\AppPkg\Applications\Sockets\OobTx\OobTx.inf [X64, VS2019, DEBUG]

解决方法是修改Conf/tools_def.txt 文件,注释掉一行。对于/WHOLEARCHIVE 这个参数的解释,可以在【参考1】看到,似乎是要求编译过程中把所有的可能的 OBJ 都编译一次?

*_VS2019_*_MAKE_FLAGS      = /nologo
*_VS2019_*_SLINK_FLAGS     = /NOLOGO /LTCG
*_VS2019_*_APP_FLAGS       = /nologo /E /TC
*_VS2019_*_PP_FLAGS        = /nologo /E /TC /FIAutoGen.h
*_VS2019_*_VFRPP_FLAGS     = /nologo /E /TC /DVFRCOMPILE /FI$(MODULE_NAME)StrDefs.h
#LABZ *_VS2019_*_DLINK2_FLAGS    = /WHOLEARCHIVE
*_VS2019_*_ASM16_PATH      = DEF(VS2019_BIN_IA32)\ml.exe
*_VS2019_*_DEPS_FLAGS      = DEF(MSFT_DEPS_FLAGS)
##################
# ASL definitions
##################

再次编译,就可以正常通过得到结果:

编译结果

修改后的 Libc 可以在这里下载

链接: https://pan.baidu.com/s/1xCjiZ6_Shsa0tWOdRCjGrg 提取码: k6n2

感觉目前 EDK2 对于 VS2019 的支持还是有问题的,对于有编译需求的朋友,我依然建议使用 VS2015。

参考:

/WHOLEARCHIVE (Include All Library Object Files)

Force the linker to include all object files in the static library in the linked executable.

Syntax

/WHOLEARCHIVE
/WHOLEARCHIVE:library

Arguments

library
An optional pathname to a static library. The linker includes every object file from this library.

Remarks

The /WHOLEARCHIVE option forces the linker to include every object file from either a specified static library, or if no library is specified, from all static libraries specified to the LINK command. To specify the /WHOLEARCHIVE option for multiple libraries, you can use more than one /WHOLEARCHIVE switch on the linker command line. By default, the linker includes object files in the linked output only if they export symbols referenced by other object files in the executable. The /WHOLEARCHIVE option makes the linker treat all object files archived in a static library as if they were specified individually on the linker command line.

The /WHOLEARCHIVE option can be used to re-export all the symbols from a static library. This allows you to make sure that all of your library code, resources, and metadata are included when you create a component from more than one static library. If you see warning LNK4264 when you create a static library that contains Windows Runtime components for export, use the /WHOLEARCHIVE option when linking that library into another component or app.

The /WHOLEARCHIVE option was introduced in Visual Studio 2015 Update 2.

C# SerialPort 发送8Bits的问题

最近在使用 SerialPort 类发送串口数据,惊奇的发现PC端程序发送 0xFF 对面(ESP32)只能收到 0x7F。但是用串口工具是可以正常发送的。于是针对这个问题进行研究。Baidu上说是因为奇偶校验导致的,我设置了无校验现象依旧。

查看 SerialPort 类发现它有一个 Encoding 属性,默认是ASCIIEncoding,一般来说,正经的 ASCII 是 0-128 就是0-0x7F,因此,直觉上这个问题是它导致的。最终在下面的页面搜索到了答案:

http://www.innovatic.dk/knowledg/SerialCOM/SerialCOM.htm

By default, strings and char’s are transmitted (not coded) as 7-bit ASCII where all characters above 127 are illegal and will be replaced with “?” (code 63) – even though you have defined 8 data bits! Therefore, you cannot use a string or char related method to send or read binary information (0-255) unless you change the encoding to something, which include all values up to 255 like:

   YourSerialPort.Encoding = System.Text.Encoding.GetEncoding(28591)  ' or
   YourSerialPort.Encoding = System.Text.Encoding.GetEncoding(1252)

于是按照上面的方法修改代码问题就得以解决。

Step to UEFI (226)EDK2 生成 ROM 最后的打包动作(上)

编译产生 BIOS ROM的最后一个步骤是将 FV 打包,那么这个操作是在哪里?带着这个问题,以 OVMF 的 PKG 为例进行研究。

我们平时编译使用的是 BaseTools\Bin\Win32下面的 build.exe ,这个文件是使用 Python 转 EXE工具生成的。如果想研究 Build.exe 具体的代码,可以将 build.exe 改名或者删除,然后保证当前 path 中含有BaseTools\BinWrappers\WindowsLike 路径,这样调用 Built 时会直接使用目录下的 Build.bat,该批处理内容如下:

@setlocal
@set ToolName=%~n0%
@%PYTHON_HOME%\python.exe %BASE_TOOLS_PATH%\Source\Python\%ToolName%\%ToolName%.py %*

如果你调用 build就会使用 Python.exe 来执行BaseTools\Source\Python\build\build.py。特别提醒,我实验发现 Python38 会出现如下的错误:

except BaseException, X:
^
SyntaxError: invalid syntax

修正方法是更换为 Python27 的版本(他们可以共存)。另外,这个问题也可能是我使用的 EDK2 版本的问题(我实验环境是EDK2201911).

代码入口在 main中,可以通过加入下面2条语句做一个标记:

print("www.lab-z.com\n")
subprocess.call("pause",shell=True)

添加在代码中入口位置

## Tool entrance method
#
# This method mainly dispatch specific methods per the command line options.
# If no error found, return zero value so the caller of this tool can know
# if it's executed successfully or not.
#
#   @retval 0     Tool was successful
#   @retval 1     Tool failed
#
def Main():

测试结果:

这里证明了上述代码就是程序入口

代码中MyBuild.Launch()是具体执行 build 动作的地方:

        MyBuild = Build(Target, Workspace, Option)
        GlobalData.gCommandLineDefines['ARCH'] = ' '.join(MyBuild.ArchList)
        if not (MyBuild.LaunchPrebuildFlag and os.path.exists(MyBuild.PlatformBuildPath)):
            MyBuild.Launch()

实验确定对应代码:

    ## Launch the module or platform build
    #
    def Launch(self):
        if not self.ModuleFile:
            if not self.SpawnMode or self.Target not in ["", "all"]:
                self.SpawnMode = False
                self._BuildPlatform()
            else:
                self._MultiThreadBuildPlatform()
            self.CreateGuidedSectionToolsFile()
        else:
            self.SpawnMode = False
            self._BuildModule()

self._MultiThreadBuildPlatform() 这里会完成所有的动作.这个函数在同一个文件中:

    ## Build a platform in multi-thread mode
    #
    def _MultiThreadBuildPlatform(self):
        SaveFileOnChange(self.PlatformBuildPath, '# DO NOT EDIT \n# FILE auto-generated\n', False)

我们的目标是找到最后将 FV 打包的过程,在代码中下面的位置加入 Pause 可以正常停下来,所以确定这个是我们目标动作的位置。

                    if self.Fdf:
    		print("www.lab-z.com\n")
subprocess.call("pause",shell=True)

                        #
                        # Generate FD image if there's a FDF file found
                        #
                        GenFdsStart = time.time()
                        LaunchCommand(Wa.GenFdsCommand, os.getcwd())

                        #
                        # Create MAP file for all platform FVs after GenFds.
                        #
                        self._CollectFvMapBuffer(MapBuffer, Wa, ModuleList)
                        self.GenFdsTime += int(round((time.time() - GenFdsStart)))
                    #
                    # Save MAP buffer into MAP file.
                    #
                    self._SaveMapFile(MapBuffer, Wa)

代码中加入下面两句输出具体调用的命令:

                        print("[%s]\n" % Wa.GenFdsCommand)
                        print("[%s]\n" % os.getcwd())
                        subprocess.call("pause",shell=True)

测试,在编译环境下输入下面的 command, 可以实现生成 OVMF.fd:

GenFds -f c:\buildbs\i2c\OvmfPkg\OvmfPkgX64.fdf --conf=c:\buildbs\i2c\conf -o c:\buildbs\i2c\Build\OvmfX64\DEBUG_VS2015x86 -t VS2015x86 -b DEBUG -p c:\buildbs\i2c\OvmfPkg\OvmfPkgX64.dsc -a X64  -D "EFI_SOURCE=c:\\buildbs\\i2c\\edkcompatibilitypkg"  -D "EDK_SOURCE=c:\\buildbs\\i2c\\edkcompatibilitypkg"  -D "TOOL_CHAIN_TAG=VS2015x86"  -D "TOOLCHAIN=VS2015x86"  -D "TARGET=DEBUG"  -D "FAMILY=MSFT"  -D "WORKSPACE=c:\\buildbs\\i2c"  -D "EDK_TOOLS_PATH=c:\\buildbs\\i2c\\basetools"  -D "ARCH=X64"  -D "ECP_SOURCE=c:\\buildbs\\i2c\\edkcompatibilitypkg"

结论:build 过程中的最后一段是调用 GenFds 来生成 BIOS 的 FD (或者叫做 ROM)文件。

推荐一个十六进制编辑器 HXD

之前我一直使用 WinHex作为十六进制编译器,但是WinHex 是收费的,经过研究找到了 HxD 这款软件,它是免费的十六进制编辑软件,主页在 https://mh-nexus.de/en/hxd/  经过一段时间的使用(主要是用于 ROM 文件的分析),感觉挺好用,有需要的朋友可以试试。

HXD 界面

它提供了如下功能:

  • Available as a portable and installable edition
  • RAM-Editor
    • To edit the main memory
    • Memory sections are tagged with data-folds
  • Disk-Editor (Hard disks, floppy disks, ZIP-disks, USB flash drives, CDs, …)
    • RAW reading and writing of disks and drives
    • for Win9x, WinNT and higher
  • Instant opening regardless of file-size
    • Up to 8EB; opening and editing is very fast
  • Liberal but safe file sharing with other programs
  • Flexible and fast searching/replacing for several data types
    • Data types: text (including Unicode), hex-values, integers and floats
    • Search direction: Forward, Backwards, All (starting from the beginning)
  • File compare (simple)
  • View data in Ansi, DOS, EBCDIC and Macintosh character sets
  • Checksum-Generator: Checksum, CRCs, Custom CRC, SHA-1, SHA-512, MD5, …
  • Exporting of data to several formats
    • Source code (Pascal, C, Java, C#, VB.NET)
    • Formatted output (plain text, HTML, Richtext, TeX)
    • Hex files (Intel HEX, Motorola S-record)
  • Insertion of byte patterns
  • File tools
    • File shredder for safe file deletion
    • Splitting or concatenating of files
  • Basic data analysis (statistics)
    • Graphical representation of the byte/character distribution
    • Helps to identify the data type of a selection
  • Byte grouping
    • 1, 2, 4, 8 or 16 bytes packed together into one column
  • “Hex only” or “text only”-modes
  • Progress-window for lengthy operations
    • Shows the remaining time
    • Button to cancel
  • Modified data is highlighted
  • Unlimited undo
  • “Find updates…”-function
  • Easy to use and modern interface
  • Goto address
  • Printing
  • Overwrite or insert mode
  • Cut, copy, paste insert, paste write
  • Clipboard support for other hex editors
    • Visual Studio/Visual C++, WinHex, HexWorkshop, …
  • Bookmarks
    • Ctrl+Shift+Number (0-9) sets a bookmark
    • Ctrl+Number (0-9) goes to a bookmark
  • Navigating to nibbles with Ctrl+Left or Ctrl+Right
  • Flicker free display and fast drawing

Step to UEFI (225)CSME版本检查工具

CSME 和BIOS 并没有多少关系,但是因为同属 Firmware,所以出了问题都会首先找BIOS工程师。对于大多数情况下,升级 CSME 都能解决问题。因此,BIOS工程师遇到的问题是:当前 CSME 是什么版本?通常的解决方法是进入BIOS Setup查看,更专业一点是在 Shell 或者Windows 下运行Intel提供的 MEInfo这个工具。但是对于单独的Binary 就无能为力了。

最近发现了一个开源工具,能够完成这个目标。项目地址是 https://github.com/platomav/MEAnalyzer

项目发布了2个版本: Python的源代码版本和转换为 EXE 的版本。对于大多数用户,个人推荐EXE版本,理由是:省事。

首先展示一下 EXE版本的使用,直接将要查看的Binary 推拽到EXE 上即可:

试验文件

运行结果:

查看到的 CSME 版本
查看到的其他Firmware 版本

Python 版本的下载之后文件如下:

运行方法是打开 CMD 窗口后输入 mea.py + 文件名,运行结果和EXE版本的相同。但是这个代码需要3个额外的库 colorama ,crccheck和PLTable,可以在 https://pypi.org/ 找到。我在附件中提供了直接下载。Colorama 和crccheck是以 WHL 格式提供的。安装方法是将下载好的 WHL 文件放在 Python 安装后的 Scripts 目录下,然后使用 pip install 命令安装,示例如下:

如果发生 pip3无法运行的情况,推荐卸载 Python 之后重新安装,安装时务必选中 PIP选项。

对于PLTable安装方法是解压后CMD窗口中使用 python setup.py install 进行安装:

本文提到的文件可以在这里下载,推荐有兴趣的朋友试试

链接: https://pan.baidu.com/s/1GaDeaRWZU-hpwAnUx4mT5w 提取码: 3rz3

Rowhammer Attacks

最近看资料了解到的一种内存的攻击方式,效果是能够翻转被攻击的内存区域 Bits,如果这个 Bits 正好是用于检查权限之类的,用这种方法就能获得更高的权限。

原理:RAM的制造精度越来越高,部件在物理层面上越来越小。既能在一块芯片上集成更大的内存容量,又能让各个内存单元之间不发生电磁干扰是非常难以做到的。这一情况使得对内存的单个区域进行的读写有可能干扰到邻近的区域,导致电流流入或流出邻近的内存单元。如果反复进行大量读写,有可能改变邻近内存单元的内容,使得0变成1,或者1变成0。这种现象被称为比特翻转(bitflipping),可被利用获取更高的权限。【参考1】

快速读取上图中的黄色 Row ,可能会导致紫色的Row 出现 bits 反转。【参考2】

当然,从原理上来看触发Rowhammer漏洞非常简单,但成功地利用该漏洞有点难,因为内存中大多数的位是与攻击目标无关的,而这些无关位的翻转可能会导致内存破坏。想要成功地利用Rowhammer,攻击者必须能够诱使系统加载目标内存页到DRAM中与攻击者所有的物理内存行邻接的行中。【参考3】

在 MemTest86 的测试中,如果有遇到写入和读取有1Bit差别的failure,可以尝试在Setep 中打开 Enable RH Prevention 这个功能。

参考:

1.http://xilinx.eetrend.com/d6-xilinx/news/2016-10/10624.html 【DDR未解之谜】Row Hammer这么严重,知道如何解决吗?

2.https://www.zhihu.com/question/28666294 Row Hammer漏洞会有多大实质性影响?

3.https://blog.csdn.net/u013806583/article/details/53103714

开源的 Windows ACPI Table 查看工具

最近发现一个开源的 Windows 下查看 ACPI table 的工具 firmwaretables 在下面的链接可以下载到:

https://github.com/vurdalakov/firmwaretables

Lists, extracts and decodes system firmware tables. Usage: firmwaretables <-list | -all | -save | -decode <table type> <table id> [filename]> [-silent] Commands: -l – list available system firmware tables -a – save all system firmware tables to files -s – save specific system firmware table to file -d – decode specific system firmware table Options: -silent – no error messages are shown; check exit code Exit codes: 0 – operation succeeded 1 – operation failed -1 – invalid command line syntax

TinkerNode 制作斐波那契时钟

斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368…….. 这个数列从第3项开始,每一项都等于前两项之和: f(n)=f(n-1)+f(n-2)  , n>2, f(1)=1,f(2)=1。

这样的数列非常简单,但是有着很多有趣的特性。比如:而且当n趋向于无穷大时,前一项与后一项的比值越来越逼近黄金分割0.618(或者说后一项与前一项的比值小数部分越来越逼近 0.618)。

可以看到越到后面,的比值越接近黄金比。

人类和禽兽的一个很大的区别在于对颜色的识别。比如,相对于人类来说,狗狗可以称为色弱。宠物眼科医师已经得出结论,狗所看到的颜色与红绿色盲的人有点相似。狗的眼睛有蓝色和绿色的光感受体,但没有红色的光感受体。因此,狗无法轻易区分黄色,绿色和红色,但是他们可以轻松识别不同深浅的蓝色、紫色和灰色。颜色仅仅是狗感知环境的因素之一。亮度,对比度,尤其是物体运动,都是非常重要的因素,并且这些因素更能帮助狗狗来感知它们周围的环境。【参考1】

狗狗看到的颜色:

人类看到的颜色:

据说人眼对于颜色的敏感是千万年进化而来的结果。比如,准确通过识别书上红色的成熟果实,能够大大提升采集工作的效率…….沿着这种理论,女性应该能分辨出更多的红色,而显示确实如此。譬如,在我眼中都是红色的口红,在老婆眼中可能是朱红 (vermeil),粉红 (pink),梅红(plum),玫瑰红(rose),桃红(peach blossom),樱桃红 ( cherry),桔红(reddish orange),石榴红(garnet),枣红(purplish red),莲红(lotus red),浅莲红(fuchsia pink),豉豆红(bean red)…….

将斐波那契数列和颜色结合在一起,就有了斐波那契时钟,这是国外众筹网站上的一个项目:

想读出这个时钟的时间需要一点技巧:钟表面有5个区域,每个区域代表着不同的权值,比如最小的方块表示1 最大的表示5:

表面上有4种颜色:红色,黄色,蓝色和白色。其中白色表示0. 其他的取值是由其所在区域的权值决定的。具体时间的计算如下

小时数 = 红色数值 + 蓝色数值

分钟数 = (绿色数值 + 蓝色数值) x 5

表示范围就是从 00:00到12:55. 同样的时间可能由不同的方式进行表示。比如:12:55 可以由下面2种方式来表示

红色=1  蓝色=1+2+3+5=11 绿色=0

因此,时间计算方法如下:

小时数 = 1+11=12

分钟数 = (0 + 11) x 5=55

同样的,下面这样也表示 12:55

再来一个复杂的,比如表面如下:

红色=1 绿色=2 蓝色=3+5=8

小时数 = 1+8=9

分钟数 = (2 + 8) x 5=50

所以上面表示的是 9:50 (另外,这个时间总共有6种表示方法)

从表示方法上也能看出来时间是以5分钟为单位的。无法表示注诸如  12:01 这样的时间,只有 12:00或者 12:05 这样。

00:00到 12:55一共有 152个时间,有些时间有很多个表示方法总共有992个方案。

上面介绍了最原始的斐波那契时钟, 小的表面并不能满足我的要求,因此,我设计了一个使用 TinkerNode(ESP32)发出 VGA信号的方案,这样可以将带有 VGA 的显示器变成时钟。电路上非常简单,最主要的是 VGA 接口上的 R,G,B 3个Pin和 Vsync 和 Hsync 2个Pin。

PCB 设计如下:

和之前的设计一样,硬件只是整个作品的一小部分,绝大多数工作是软件来完成的。

需要解决的第一个问题是时间的表示,为此,我编写了一个C语言代码将所有的可能时间拆分表示, 最终结果在 clockdata.h 中。比如,当前时间是  12:55 。首先要在 TimeToIndex [] 中查找(12*12+55/5=155), 其中  TimeToIndex [155] 定义如下:

//155  12:55  990

{2,990}

意思是: 12:55 有2种表示方法,在 TimeToSectionColor [] 这个表格中990 开始处:

//155  12:55  990

{SECRED  ,SECBLUE ,SECBLUE ,SECBLUE ,SECBLUE },

{SECBLUE ,SECRED  ,SECBLUE ,SECBLUE ,SECBLUE }

第一种方法, Section 0-4 颜色分别为 RED,BLUE,BLUE,BLUE,BLUE。 第二种方法,Section 0-4 颜色分别为 BLUE,RED,BLUE,BLUE,BLUE。根据不同区域,将对应区域设定设定为指定的颜色即可。

接下来是时间的获取, TinkerNode 支持物联网,可以轻松的获得,在 DFRobot_NBIOT\examples\LocalTime\CalLocalTime_NB 有这样的例子(此外,对于 TinkerNode开发板还可以通过 GPS/ WIFI 来实现自动获得)。完整代码如下:

// TinkerNode Quectel BC20 模块支持
#include "DFRobot_BC20.h"

// VGA 支持库
#include <ESP32Lib.h>
#include <Ressources/Font8x8.h>

// 计算后的的时间对颜色表示方法
#include "clockdata.h"

DFRobot_BC20 myBC20;

// VGA 显示引脚
const int redPin = 22;
const int greenPin = 21;
const int bluePin = 19;
const int vsyncPin = 18;
const int hsyncPin = 23;

const int TIMEX= 560;
const int TIMEY= 450;

// 3 Bit VGA 支持
VGA3BitI vga;

void setup()
{
  Serial.begin(115200);
  while(!myBC20.powerOn()){
    delay(1000);
    Serial.print(".");
  }
  Serial.println("BC20 started!");
  while(!myBC20.checkNBCard()){
    Serial.println("Please insert the NB SIM card !");
    delay(1000);
  }
  Serial.println("Waitting for access ...");
  while(myBC20.getGATT() == 0){
    Serial.print(".");
    delay(1000);
  }
  Serial.println("Waiting for NB time...");
  // 通过 BC20 获得当前时间
  while(myBC20.getCLK()){
    if(sCLK.Year > 2000){
      break;
    }
    Serial.print(".");
    delay(1000);
  }
  Serial.println();
  Serial.println("Configure local time");
  // 将获得的时间设置给ESP32
  configLocalTime(sCLK.Year,sCLK.Month,sCLK.Day,sCLK.Hour,sCLK.Minute,sCLK.Second);
Serial.printf("sdata %d-%02d-02d %02d:%02d:%02d\n",sCLK.Year,sCLK.Month,sCLK.Day,sCLK.Hour,sCLK.Minute,sCLK.Second);
  // 显示模式为  640x480 (主要是内存限制)
	vga.init(vga.MODE640x480, redPin, greenPin, bluePin, hsyncPin, vsyncPin);
	// 使用 8x8 字库
	vga.setFont(Font8x8);
	// 背景为白色
	vga.clear(vga.RGB(0xffffff));
  /*
	// 这里可以再屏幕上显示一下当前内存剩余情况
	// 设置文字颜色
	vga.setTextColor(vga.RGB(0));
  // 设置屏幕输出位置
  vga.setCursor(0, 20);
  //show the remaining memory
  vga.print("free memory: ");
  vga.print((int)heap_caps_get_free_size(MALLOC_CAP_DEFAULT));
  */
}

// 颜色索引到颜色值转换函数
int SectionColor(int ColorIndex) {
  if (ColorIndex==SECRED) {return vga.RGB(0xFF);}
  if (ColorIndex==SECGREEN) {return vga.RGB(0xFF00);}
  if (ColorIndex==SECBLUE) {return vga.RGB(0xFF0000);}  
  return vga.RGB(0xFFFFFF);
}

// 记录上一个秒数
int LastSecond=0xFF;

// 记录上一个颜色
int LastTimeIndex=0xFFFF;

void loop()
{
  int TimeIndex;

  // 取得当前时间
  tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;
  }
  AnalysisTime(&timeinfo);
  Serial.printf("%02d:%02d:%02d\n",timeinfo.tm_hour,timeinfo.tm_min,timeinfo.tm_sec);
  // 如果秒发生变化
  if (timeinfo.tm_sec!=LastSecond) {
      // 串口输出当前时间
      //Serial.printf("%02d:%02d:%02d\n",timeinfo.tm_hour,timeinfo.tm_min,timeinfo.tm_sec);
      // 擦除之前屏幕上绘制的时间
      vga.fillRect(TIMEX,TIMEY,64,64,vga.RGB(0xffffff));
      // 设置文字颜色
      vga.setTextColor(vga.RGB(0));
      vga.setCursor(TIMEX,TIMEY);
      // 如果小时只有1位,那么前面用 0 填充      
      if (timeinfo.tm_hour<10) {vga.print("0");}
      vga.print(timeinfo.tm_hour);
      vga.print(":");
      // 如果分钟只有1位,那么前面用 0 填充
      if (timeinfo.tm_min<10) {vga.print("0");}
      vga.print(timeinfo.tm_min);
      vga.print(":");
      // 如果秒只有1位,那么前面用 0 填充
      if (timeinfo.tm_sec<10) {vga.print("0");}
      vga.print(timeinfo.tm_sec);
      LastSecond=timeinfo.tm_sec;
      
      // 时间表示范围是从 0:0 到 12:55, 所以对于12点特别处理
      if (timeinfo.tm_hour!=12) {TimeIndex=(timeinfo.tm_hour % 12)*12+(timeinfo.tm_min/5);}
      
      // 只有当需要改变屏幕显示的时候才重新绘制
      if (LastTimeIndex!=TimeIndex) {
        // 
        Serial.print("New time index:");
        Serial.printf("%d  [%d %d]\n",TimeIndex,TimeToIndex[TimeIndex].start,TimeToIndex[TimeIndex].number);
        
        // 生成下一个时间的颜色,对于有多个可能性的事件,随机选择一个
        int PoolIndex=TimeToIndex[TimeIndex].start+random(TimeToIndex[TimeIndex].number);
        Serial.print("Pool index:");
        Serial.println(PoolIndex);
        Serial.printf("Pool [%d %d %d %d %d]\n",
                TimeToSectionColor[PoolIndex].timepool[0],
                TimeToSectionColor[PoolIndex].timepool[1],
                TimeToSectionColor[PoolIndex].timepool[2],
                TimeToSectionColor[PoolIndex].timepool[3],
                TimeToSectionColor[PoolIndex].timepool[4]
        );
        
        // 使用圆形表示时间
        if (TimeToSectionColor[PoolIndex].timepool[0]==SECNONE) {
              vga.fillCircle(80,80,80,vga.RGB(0xFFFF));
              vga.fillCircle(80,80,75,SectionColor(TimeToSectionColor[PoolIndex].timepool[0]));
          }else{  
              vga.fillCircle(80,80,80,SectionColor(TimeToSectionColor[PoolIndex].timepool[0]));
        } 
        if (TimeToSectionColor[PoolIndex].timepool[1]==SECNONE) {
              vga.fillCircle(200,40,40,vga.RGB(0xFFFF));
              vga.fillCircle(200,40,35,SectionColor(TimeToSectionColor[PoolIndex].timepool[1]));
        }else {
              vga.fillCircle(200,40,40,SectionColor(TimeToSectionColor[PoolIndex].timepool[1]));
        }
        if (TimeToSectionColor[PoolIndex].timepool[2]==SECNONE) {
              vga.fillCircle(200,120,40,vga.RGB(0xFFFF));
              vga.fillCircle(200,120,35,SectionColor(TimeToSectionColor[PoolIndex].timepool[2]));
        }else {
              vga.fillCircle(200,120,40,SectionColor(TimeToSectionColor[PoolIndex].timepool[2]));
        }
        if (TimeToSectionColor[PoolIndex].timepool[3]==SECNONE) {
              vga.fillCircle(120,280,120,vga.RGB(0xFFFF));
              vga.fillCircle(120,280,115,SectionColor(TimeToSectionColor[PoolIndex].timepool[3]));
        }else {
              vga.fillCircle(120,280,120,SectionColor(TimeToSectionColor[PoolIndex].timepool[3]));
        }
        if (TimeToSectionColor[PoolIndex].timepool[4]==SECNONE) {
              vga.fillCircle(440,200,200,vga.RGB(0xFFFF));
              vga.fillCircle(440,200,195,SectionColor(TimeToSectionColor[PoolIndex].timepool[4]));
        }else {
              vga.fillCircle(440,200,200,SectionColor(TimeToSectionColor[PoolIndex].timepool[4]));
        }
        

        /*
        // 使用正方形表示时间
        vga.fillRect(0,0,160,160,SectionColor(TimeToSectionColor[PoolIndex].timepool[0]));
        vga.fillRect(160,0,80,80,SectionColor(TimeToSectionColor[PoolIndex].timepool[1]));
        vga.fillRect(160,80,80,80,SectionColor(TimeToSectionColor[PoolIndex].timepool[2]));
        vga.fillRect(0,160,240,240,SectionColor(TimeToSectionColor[PoolIndex].timepool[3]));
        vga.fillRect(240,0,400,400,SectionColor(TimeToSectionColor[PoolIndex].timepool[4]));
        */
        
        /*
        // 这里可以预览一下8种颜色
        vga.fillRect(0,0,80,80,  vga.RGB(0x000000));
        vga.fillRect(80,0,80,80, vga.RGB(0x0000FF));
        vga.fillRect(160,0,80,80,vga.RGB(0x00FF00));
        vga.fillRect(240,0,80,80,vga.RGB(0xFF0000));
        vga.fillRect(320,0,80,80,vga.RGB(0xFF00FF));
        vga.fillRect(400,0,80,80,vga.RGB(0xFFFFFF));
        vga.fillRect(480,0,80,80,vga.RGB(0x00FFFF));
        vga.fillRect(560,0,80,80,vga.RGB(0xFFFF00));
        */

        LastTimeIndex=TimeIndex;
      }
   } 
   delay(900);  
}

完整代码下载:

最终,提到颜色,不由得让人想起乐嘉老师的“色彩心理学”,他的学说完全可以作为检验一个人常识和逻辑水平的试金石。

本文首发 https://mc.dfrobot.com.cn/thread-307842-1-1.html

参考:

1. 作者:汪小喵 https://www.zhihu.com/question/22005284/answer/64313689

工作视频在B站可以看到

https://www.bilibili.com/video/BV1df4y1Y7rR

对应的电路图和 PCB

AMD BIOS 工作机会

THE ROLE:
Provide consultation to internal and external customers regarding AMD features and programming requirements.
AMD’s environment is fast paced, results oriented and built upon a legion of forward-thinking people with a passion for winning technology.
This is an extraordinary opportunity to work in an outstanding company like AMD – Join us!

THE PERSON:
Has knowledge with ACPI, USB, PCIE, SATA and other PC industry standard. Has good communication skills and be able to work both independently and in a team.

KEY RESPONSIBILITIES:
Design, develop, and debug BIOS (System Software) for internal/external systems and platforms that use AMD APU/CPUParticipant in day-to-day BIOS development work using PC assembly and C languages; will need to interact with internal organizations, BIOS vendors, and customers.Comfortable working with PC hardware and platform issuesMust have strong system debugging skills.The following are typical tasks that the engineer will be responsible for:Design BIOS features required by AMD APU/CPU;Develop BIOS features for AMD new platforms crafted in house or externally; sustain existing BIOS; debug BIOS and system related issues;Assist APU/CPU validation, platform validation, and debug engineers to develop/debug system and silicon issues; 


PREFERRED EXPERIENCE:
Strong Knowledge about ACPI, USB, PCIE, SATA and other PC industry standardGood at X86 assembly and C languageFamiliar with at least one BIOS code base (AMI, Insyde or Phoenix BIOS).Strong communication skillsExperience in BIOS related tools development is a plus.Fluent in both written and spoken English.Fixes code for firmware (IC embedded code) application.Involves collaboration on or assuming the consultative or leadership responsibilities for a specific project or for product development initiatives.Is required to deal with internal groups on behalf of the group or project.Has accountability for results in a particular area of work.May have limited accountability for a small number of engineers related to projects (2-5), (e.g., be involved in any of the following activities: interview and selection, day-to-day technical supervision or mentoring). 


ACADEMIC CREDENTIALS:
Bachelor or Master, major in EE, CS or related area 
LOCATION:
 上海浦东环科路669


有兴趣的朋友可以联系 Eddy.Gu@amd.com