面包板的 328P Arduino

用面包板来玩 DIP 封装的 328P 是绝对没问题的。需要注意的有如下几点:

1.如果出现可以刷 BootLoader 但是无法正常工作,请检查晶振是否起振(最好使用示波器);

2.如果 5V 工作正常,但是 3.3V 供电后看起来不工作(比如无法控制 Pin13 上的LED,或者串口无输出)。那么请检查 VCC 是否和 AVCC 连接起来。我不知道为什么这两个Pin 不连接的时候,5V 供电能够正常工作但是 3.3V 供电后就不工作。

Arduino 制作 HDMI Audio 测试工具

最近有客户报告:当插入 HDMI 从外接的显示器显示时,有一定概率出现显示正常但是 HDMI Audio 失效的情况。这个问题的概率不高,但是对客户影响严重。为了复制现象,需要不断插拔HDMI来验证,经常是插拔50次之后抓到一次现象,随之而来的是再来50次,更可怕的是据说某些修改之后会出现插拔100次才会出现一次现象。这对于我这样只愿意动脑不愿动手的人来说是一个极大的考验。

这种情况下,必须考虑用机器来实现自动化的测试。思路是淘宝上买一个带有遥控器的HDMI切换器,通过 Arduino 模拟遥控的操作来实现切换到 HDMI 上然后进行检测。于是,选择了下面这货14元+8元的运费。这个切换器有三个输入口,可以对应三台HDMI输出设备;有一个输出口,可以连接一台显示器

接下来要解决的问题如下:
1. 如何进行遥控?实现遥控之后如何保证切换成功(红外线遥控容易受到光线的影响)。
2. 如何检测当前是Audio 是否正常。
经过研究,第一个问题可以使用下面这个模块来解决:
红外解码模块 编码模块 红外无线通信 接收发射串口通信模块

因为这是发射和接收一体的模块,刚好可以用来解码HDMI切换器发射器的数据,解码之后发射同样的内容就能实现遥控的操作。遥控板上有3个按键,分别对应HDMI 的三个端口。经过实验遥控板按键发送数据如下:
HDMI IN1: 0xF1 0x80 0x7F 0x02 0xA1
HDMI IN2: 0xA1 0xF1 0x80 0x7F 0x04
HDMI IN3: 0xA1 0xF1 0x80 0x7F 0x06
接下来还有一个问题,如何保证切换正常。比如,当前是 HDMI1 输出,我切花发送切换HDMI2命令之后需要确认切换是否成功(红外遥控受到环境影响较大)。经过研究,可以使用切换器上的LED来判断,每一个输入端口都有一个对应的LED用来表示当前选中的输入端口。只需要读取LED电平即可得知。此外,我们再使用额外的LED来标记当前的输入 HDMI Port。
最终,硬件连接如下:


完整代码:

const int LED1=14;
const int LED2=16;
const int LED3=10;

void setup()
{
  pinMode(LED1,OUTPUT);
  pinMode(LED2,OUTPUT);
  pinMode(LED3,OUTPUT);
  digitalWrite(LED1,LOW);
  digitalWrite(LED2,LOW);
  digitalWrite(LED3,LOW);

  pinMode(A1,INPUT_PULLUP);
  pinMode(A2,INPUT_PULLUP);
  pinMode(A3,INPUT_PULLUP);
    
    Serial.begin(9600);
    Serial1.begin(9600);    
    

}

void loop()
{
  char  c=' ';
  int   retry;
    while (Serial.available() > 0)  
    {
        c=Serial.read();
        if (c=='1') { //Command for Switch to HDMI1
            retry=3;
            while (retry>0) {
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x02);
              Serial1.write(0xA1);
              delay(200);
              if (analogRead(A1)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go1");
            }
        }
        if (c=='2') {//Command for Switch to HDMI2
            retry=3;
            while (retry>0) {
              Serial1.write(0xA1);
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x04);
              delay(200);
              if (analogRead(A2)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go2");
            } 
        }
        if (c=='3') {//Command for Switch to HDMI3
            retry=3;
            while (retry>0) {
              Serial1.write(0xA1);
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x06);
              delay(200);
              if (analogRead(A3)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go3");
              }
        } //if (c=='3')
    }
    
   if (analogRead(A1)<20) {digitalWrite(LED1,HIGH);}
   else {digitalWrite(LED1,LOW);}
   if (analogRead(A2)<20) {digitalWrite(LED2,HIGH);}
   else {digitalWrite(LED2,LOW);}
   if (analogRead(A3)<20) {digitalWrite(LED3,HIGH);}
   else {digitalWrite(LED3,LOW);}        
}

 

为了配合这个设备,我们需要编写上位机的代码, 上位机是运行在被测试机器上。主要的逻辑就是:发送切换命令切换到其他HDMI Port,之后再发送命令切换回来,然后检查音频是否正常,如果不正常就停止。如果正常就继续运行。软件界面如下:

代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;

using System.Runtime.InteropServices;

namespace WindowsFormsApplication8
{
    public partial class Form1 : Form
    {   //当前切换到 HDMI[x]
        int current=1;
        //切换次数
        int loops = 0;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //如果当前测试还没有开始,那么开始测试,否则停止
            if (button1.Text == "Start") { button1.Text = "Stop"; }
            else { button1.Text = "Start"; timer1.Enabled = false;  return; }

            //检查选中的 HDMI 数量
            int counter = 0;
            for (int i= 0; i < 3; i++) {
                if (checkedListBox1.GetItemChecked(i)) { counter++; }
            }
            //如果只有1个HDMI 被选中,提示出错
            if (counter < 2) { System.Windows.Forms.MessageBox.Show("Please choose at least 2 HDMI"); return; }
            if (serialPort1.IsOpen) { serialPort1.Close(); }
            //打开选中的 COMx
            serialPort1.PortName = comboBox1.SelectedItem.ToString();
            serialPort1.Open();
            textBox1.AppendText("Open " + comboBox1.SelectedItem.ToString()+"\n");
            //1秒之内开始测试
            timer1.Interval = 1000;
            timer1.Enabled = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //设置可以选择的串口号
            comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
            comboBox1.SelectedIndex = 0;
            for (int i = 0; i < checkedListBox1.Items.Count; i++) {
                checkedListBox1.SetItemChecked(i,true);
            }

            //输出当前的串口设备名称
            ManagementObjectSearcher searcher =  new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PnPEntity");
            foreach (ManagementObject queryObj in searcher.Get())
            {
                if (queryObj["Caption"] != null)
                    if (queryObj["Caption"].ToString().Contains("(COM"))
                    {
                        //Console.WriteLine(queryObj["Caption"]);
                        textBox1.AppendText(queryObj["Caption"].ToString()+"\n");
                    }
            }
            searcher.Dispose();

        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serialPort1.IsOpen) {
                textBox1.AppendText("close " + comboBox1.SelectedItem.ToString());
                serialPort1.Close();
            };
        }


        // native calls
        [DllImport("winmm.dll")]
        public static extern int waveOutGetNumDevs();

        private void timer1_Tick(object sender, EventArgs e)
        {
            //如果当前是 HDMI2
            if (current == 2) {
                //检查设备是否有 Audio
                int inum = waveOutGetNumDevs();
                if (inum == int.Parse(maskedTextBox1.Text))  //发现错误
                {
                    textBox1.AppendText("Audio failed");
                    timer1.Enabled = false;
                    return;
                }
            }
            //20秒后触发下一次切换
            if (timer1.Interval == 1000) { timer1.Interval = 20000; }
            //尝试下一个HDMI
            current = (current + 1) % 3+1;
            //检查下一个是否被选中
            while (checkedListBox1.GetItemChecked(current-1)==false) {
                current = (current + 1) % 3+1;
            }
            if (current == 2) { loops++; }
            //切换到下一个可用的HDMI设备
            textBox1.AppendText("Switch to " + current.ToString()+"\n");
            textBox1.AppendText("Loops " + loops.ToString() + "\n");
            serialPort1.Write(current.ToString());
        }

        private void label1_Click(object sender, EventArgs e)
        {

        }
    }


}

 

编译 Windows 下的 EDAX

EDAX 是一款很厉害的黑白棋引擎,项目地址是https://github.com/abulmo/edax-reversi
除了引擎,它提供了一个文本的简单界面,用户能够方便的进行测试。本文介绍一下如何实现使用 VS2015编译出一个能够工作的EXE文件。

1. 在https://github.com/abulmo/edax-reversi/releases 页面有如下下载链接。Edax-linux.7z 是 Linux 版本的可执行程序;edax-ms-windows 是 Windows版本的可行性程序;eval 是开局库,运行的时候必须放在可执行文件同一个目录中;最后是不同压缩格式的两个Source Code。

对我们来说,需要下载 Source Code.zip

2. 对于下载的代码,options.c 文件需要进行修改,其中定义了const char *(boolean[2]) = {“false”, “true”}; 这样的变量,变量名称 Boolean 不妥,我的解决方法是修改为const char *(booleanx[2]) = {“false”, “true”}; 然后将这个文件中的所有 boolean 都修改为 booleanx。

3. 编译命令(我没有搞清楚如何使用 nmake来编译这个项目)
cl /I”..\include” /O2 /GL /fp:fast /favor:INTEL64 /arch:AVX /D “NDEBUG” /D “inline=__inline” /D “__func__=__FUNCTION__” /D “USE_MSVC_X64” /D “POPCOUNT” /MT all.c ws2_32.lib /FewEdax-x64.exe /link /ltcg:pgi /machine:x64 /VERSION:4.4

4. 下载 eval.dat,解压到 data 目录下就可以运行测试了

一站式获得系统信息的工具 Intel SSU

很多时候,为了分析研究现象,我们需要收集一些信息,比如:当前Windows版本,BIOS 版本或者 ME 版本等等。 Intel SSU 就是为此设计的。这个工具介绍如下:

Intel® System Support Utility. Intel® System Support Utility (Intel® SSU) is a standalone utility that performs a detailed scan and report of your computer system information and devices. Intel SSU produces an output file that can be saved, viewed, and shared by the user.

可以在下面的链接下载到:

https://downloadcenter.intel.com/download/25293/Intel-System-Support-Utility-for-Windows-

这个软件无需安装,下载后执行即可:

点击 Scan 后可以得到下面的结果


可以切换到更详细的信息栏

有兴趣的朋友可以试试。

libpng warning: iCCP: known incorrect sRGB profile

今天我在编译一个 C# Console 的代码时候忽然出现了“libpng warning: iCCP: known incorrect sRGB profile”一堆的 Warning。因为前几天编译运行的时候还没有遇到过这样的事情。研究了很久。网上的资料都是说 libpng 是一个 Png 的解码库,但是我的代码没有Png 相关的任何内容。

最终,我写了一个只有一行 Console.WriteLine() 输出的程序,运行之后出现了同样的现象我才确定不是因为我的代码导致了问题。

搜索的时候看到一篇文章提到可能和输入法相关。于是我去控制面板中根据时间排序惊奇的发现 QQ拼音输入法 排在第一位,然后想起来今天手欠,看到输入法提示选择了升级。卸载之后再次运行,世界终于清静了。

很早之前遇到过这样的事情,那次是搜狗输入法导致我的 Delphi 无法调试编译好的EXE文件,更加让人觉得崩溃。半年之后才发现是搜狗导致的,于是果断卸载之。没想到今天又遇到这样的情况。

ps: 更让人吃惊的是,现在的 QQ 输入法就是搜狗做的“
2013年09月16日,腾讯4.48亿美元注资搜狗,宣布将搜搜和QQ输入法业务与搜狗现有业务进行合并。 ”看起来他们已经整合的差不多了。

Step to UEFI (168)Setup 中替换字符串

以前设置BIOS的门槛挺高,至少对于不熟悉英文的人进入满屏英文的Setup界面很容易变成丈二和尚———摸不着头脑。但是随着时代的进步,汉化Setup界面并不复杂,在 EDK2 上已经加入了多语言支持功能。但是,某些情况下,Setup中的选项不是来自BIOS Source Code 而是第三方的UEFI ROM,遇到这样的情况,经常会出现一片中文夹杂了一行英文的情况。为了更详细的描述这样的情况,用下面的例子来进行说明。在UDK2017中,有一个 RAM Disk 功能,这个功能在之前的系列文章中有介绍过。默认情况下,NT32Pkg 没有加载这个功能,但是编译过程中会生成 RamDiskDxe.efi。实验如下:


1. 先进入 Setup ,可以看到目前是没有相关选项的

2. 进入 Shell 之后,加载 RamDiskDxe.efi

3. 再进入 Setup,可以看到已经出现了 Ram Disk 的选项

这里,实验的目标就是在不修改 RamDiskDxe.efi 的情况下,当语言选择为法语时,修改 Ram Disk 的名称。
首先研究一下 Setup 的选项,经过研究发现在\MdeModulePkg\Library\UefiHiiLib\HiiString.c中有一个HiiGetString的函数。从注释可以看出,这个函数负责返回 Hii Package中的字符串,因此,我们在其中添加代码,当发现语言设置为法语时,并且取得 Ram Disk 相关的String后,用我们定义的 String 替换掉正常的返回值从而实现“翻译”的动作。

/**
  Retrieves a string from a string package in a specific language.  If the language
  is not specified, then a string from a string package in the current platform 
  language is retrieved.  If the string can not be retrieved using the specified 
  language or the current platform language, then the string is retrieved from 
  the string package in the first language the string package supports.  The 
  returned string is allocated using AllocatePool().  The caller is responsible 
  for freeing the allocated buffer using FreePool().
  
  If HiiHandle is NULL, then ASSERT().
  If StringId is 0, then ASSET.

  @param[in]  HiiHandle  A handle that was previously registered in the HII Database.
  @param[in]  StringId   The identifier of the string to retrieved from the string 
                         package associated with HiiHandle.
  @param[in]  Language   The language of the string to retrieve.  If this parameter 
                         is NULL, then the current platform language is used.  The 
                         format of Language must follow the language format assumed 
                         the HII Database.

  @retval NULL   The string specified by StringId is not present in the string package.
  @retval Other  The string was returned.

**/
EFI_STRING
EFIAPI
HiiGetString (
  IN EFI_HII_HANDLE  HiiHandle,
  IN EFI_STRING_ID   StringId,
  IN CONST CHAR8     *Language  OPTIONAL
  )

最终代码如下:

  //
  // Return the Null-terminated Unicode string
  //
  //LABZ_Start
  if (String!=NULL) {
          DEBUG((DEBUG_INFO, "Get [%s]\n",String));
          if (StrCmp(String,L"Choisir la Langue")==0) { //这里判断一下语言是否已经选择为法语
                  DEBUG((DEBUG_INFO, "Let's speek French\n"));
                  FrenchX=TRUE; //做一个标记
                }
          //只有在切换为法语以及发现需要替换的字符串时再下手
          if ((FrenchX)&&(StrCmp(String,L"RAM Disk Configuration")==0)) {
                  DEBUG((DEBUG_INFO, "Replace item string\n"));
                  //"RAM Disk Configuration"
                  //"Jia zhuang fayu       "
                  StrCpy(String,L"Jia zhuang fayu       ");
                }      
          }
  //LABZ_End

需要特别注意的是:返回值是放在 String 中的,它是由HiiGetString()函数进行空间分配,然后由调用者负责分配。因此,我们需要在 String 中修改,而不能简单的用另外一个变量来赋值给 String,否则在运行时会出现不可预料的错误。

重新实验,加载 RamDiskDxe.efi ,再选择法语后运行结果:

在 Log 中我们可以看到下面的 Debug Message:
1.发现语言选项切换为法语

2.发现选项,替换之。

上面的实验都是在 UDK2017 NT32中进行的。完整的代码和 RamDiskDxe.efi 可以在这里下载:

额外说两句,法国是非常有名的老牌资本主义国家,譬如每个人耳熟能详的名言“知识就是力量,法国就是培根”。

不过从目前的新闻来看,这个国家正在一步步走向衰落。
纵观历史,我们所处的时代正是国运上升的时期,也许是我们的幸运吧。希望通过我们的努力能让我们的后辈有着更多的选择。

Teensy 打造USB纯数字麦克风

很多人喜欢在文章中引用名人名言来证明自己的观点,但是大多数名人名言都是有前提条件的,甚至前后段意义完全相反。比方说:打算宽恕别人的时候可以说“以德报怨”是中华民族的传统美德;打算复仇的时候就可以完整的引用“以德报怨,何以报德”。又比方说:在作文中我们用了无数次的“天才就是1%的灵感加上99%的汗水”;但是爱迪生要表达的是“天才就是1%的灵感加上99%的汗水,但那1%的灵感是最重要的,甚至比那99%的汗水都要重要”。所以,在我看来,这样的名人名言除了凑凑字数别无大用。与之相反,理科生的世界中,各种定理和名言充满了简洁和力量。比如:奈奎斯特教导我们说“要从抽样信号中无失真地恢复原信号,抽样频率应大于2倍信号最高频率。 抽样频率小于2倍频谱最高频率时,信号的频谱有混叠。 抽样频率大于2倍频谱最高频率时,信号的频谱无混叠。”

这句话可以指导我们对音频进行采样,语音信号频率范围是:300Hz~3.4kHz,因此,电话系统的 8000Hz 采样率对于传递语音信息已经足够了。

要想让电脑处理音频,先要用声能到电能的元件进行转换,再经过放大之后达到一定幅度才能进行采样(否则只有100mv不适合采样),可以看到在这个过程中会引入需要经过很多步骤而每一个步骤都会引入不确定性,因此,很长一段时间以内,音频处理都算得上是玄学。

最近我接触到了数字麦克风(MEMS micphone),和传统的相比数字麦克风输出的是数字信号,而不是通常的模拟信号。产品的基本形态有方形的,也有圆形的,形状与通常的驻极体传声器类似。基本结构为:一个换能器,用于产生代表声信号的模拟信号(通常由驻极体振膜及其背电极来完成的);和大于1的单比特∑-Δ调制器模数变换器,按照过抽样速率并以∑-Δ调制比特流的形式从模拟信号中产生数据输出信号。

数字麦克风的最大优点是抗干扰能力强,无需像传统传声器那样内置高频滤波电容、滤波器电路。数字麦克风因其固有的特点,不会受到那些来自电脑、网络、射频际磁场信号源的干扰、影响,因此在接入的时候,无需采用屏蔽线,可以有效地利用相关产品的有限空间。【参考1】

这次使用的是SPH0645LM4H (SPH0645) 数字麦克风模块

特别注意,有孔的一面是正面,需要面对音源。

硬件连接方式

Teensy 3.2     SPH0645LM4H

        GND             SEL

        D23              LRCL

         D13             DOUT

         D9                     BCLK

         GND                     GND

   3.3V               3V 

       

https://www.pjrc.com/teensy/gui/index.html?info=AudioInputI2S# 可以直接使用图形方式设定连接

可以用这样的方式直接生成需要的文件头部。最终的源代码:

#include &lt;Audio.h>

#include &lt;Wire.h>

#include &lt;SPI.h>

#include &lt;SD.h>

#include &lt;SerialFlash.h>

 

// GUItool: begin automatically generated code

AudioInputI2S            i2s1;           //xy=223,84

AudioOutputUSB           usb1;           //xy=435,172

AudioConnection          patchCord1(i2s1, 0, usb1, 0);

// GUItool: end automatically generated code

 

void setup() {

  // put your setup code here, to run once:

   AudioMemory(80);

}

 

void loop() {

}

除了上面提到的数字麦克风的优点之外,它还有如下缺点:

1.     对于处理器有要求。比如 Teensy 3.X 能够支持这款麦克风,最主要是因为它上面有对应的处理单元。而像 Arduino Uno,并没有这样的硬件基础,因此不能直接使用;

2.     价格昂贵。本次试验用到的模块价格为 48元。单纯的数字麦克风数字麦克风模块也要十几块;

另外,现在绝大多数的笔记本都采用这种麦克风作为内置麦克风。相信未来,这种麦克风会有更大的用途。

参考:

1. https://baike.baidu.com/item/%E6%95%B0%E5%AD%97%E9%BA%A6%E5%85%8B%E9%A3%8E/6847775

使用VirtualBox 进行UEFI 网络实验

最近研究了一下 UEFI 下的网络编程。需要解决的首要问题是:如何进行相关实验。对于我们来说最便捷的莫过于使用虚拟机,《UEFI 原理与编程》推荐的是使用 NT32 环境加SnpNt32Io 来完成模拟。但是我这边实验一直不成功,可能是Winpcap 安装不正确导致的。最终经过研究比较,确定了使用 VirtualBox作为实验对象。

第一个目标是要讲实验用到的驱动传入到虚拟机中。我们创建虚拟硬盘然后将需要内容 COPY到其中。

1. 打开 Disk Management,使用 Create VHD 功能

2. 创建一个 500MB,Fixed Size (可能是出于兼容性原因,这里建议用 Fixed Size)

3. 创建之后需要分区和格式化,Windows只能格式化成 NTFS 格式,这里需要使用 DiskGenius来完成。直接创建一个500MB大小的 ESP分区。然后文件系统需要为 FAT32。保存分区后格式化。

4. 上述操作结束之后,可以在 Windows 中对刚出现的盘符进行操作,COPY进需要的文件。比如:这里放入 Intel 网卡驱动和 UDK2018的WebServer 的UEFI Applicaiton

5. 再到 Disk Management 中 Detach VHD

至此,已经完成虚拟机硬盘的创建,并且放入了必须的文件。下面就是在 VirtualBox中验证的过程。

6. 在 VirtualBox 中新建一个虚拟机,一定要 Enable EFI ,此外,要将刚才制作的 VHD 文件挂上去。

上面的设置完成之后就可以进行网络方面的测试了。有几个基本的命令:
1. ifconfig –l eth0 //查看第一个网卡的配置信息
2. ifconfig –s eth0 DHCP //设置使用 DHCP 取得IP 地址,因为使用 Bridge 的网卡设定,新创建好的虚拟机和HOST主机一样是“暴露”在网络上的,我的 HOST 主机是通过 DHCP 获得IP的,因此虚拟机同样可以从 DHCP 上获得IP

上述完成之后,运行 WebServer.efi ,运行之后这个EFI Application会监听网络 Port 80,等待 HTTP 请求。在 Host 机器中,使用 Chrome 访问虚拟机的IP 80端口,结果如下:

就是说访问到了虚拟机提供的 WebServer 。
额外的话:
1. 除了 VirtualBox , QEMU 也是一个很好的选择。同时 QEMU 能够运行编译出来的BIOS Image 相对来说更胜一筹。但是我在设置网络的时候,QEMU 一直没能实现 Bridge ,只有 NAT,可能是因为我的HOST 是 Windows 导致的。只有NAT这对于使用来说显示颇多限制,因此,最终选择 VirtualBox 来实现模拟环境。
2. DiskGenius注册版可以直接编辑VHD 文件,挂载之后可以直接对其写入文件。普通免费版没有这个功能。
3. VHD 还可以使用ImDisk 直接挂载到 Windows 上然后写入数据。官方网站 http://www.ltr-data.se/opencode.html/
4. 实验发现 VirtualBox 的UEFI BIOS无法识别 IDE 的光驱,但是 SATA 的可以。用制作 ISO 的方法直接传递EFI Application 也是一个好方法。

20210531 补充

1.虚拟机中网络设置如下

2.实验用到的编译好的 WebServer.efi 和网卡驱动下载

Step to UEFI (167)Shell 下的二维码显示

二维码是一种非常方便的对手机输入的方式,现在的日常生活中二维码几乎随处可见,无论是手机支付还是网页分享都能看到它的身影。最近研究了一下如何在生成二维码,找到【参考1】提供的一个C语言库。经过简单调试即可在 Shell 下跑起来。
实例代码如下:

#include &lt;stdio.h>
#include &lt;string.h>
#include "qr_encode.h"

int main(void)
{
	int side, i, j, a, ecclevel;
	uint8_t bitdata[QR_MAX_BITDATA];
	char str[2048];

	printf("ECC Level [LMQH]: ");
	if (!fgets(str, sizeof(str), stdin)) {
		return 1;
	}
	switch (str[0]) {
	case 'l':
	case 'L':
		ecclevel = QR_LEVEL_L;
		break;
	case 'm':
	case 'M':
	default:
		ecclevel = QR_LEVEL_M;
		break;
	case 'q':
	case 'Q':
		ecclevel = QR_LEVEL_Q;
		break;
	case 'h':
	case 'H':
		ecclevel = QR_LEVEL_H;
		break;
	}

	printf("Enter string: ");
	if (!fgets(str, sizeof(str), stdin)) {
		return 1;
	}
	// remove newline
	if (str[strlen(str) - 1] == '\n') {
		str[strlen(str) - 1] = 0;
	}

	side = qr_encode(ecclevel, 0, str, 0, bitdata);

	printf("side: %d\n", side);

	for (i = 0; i &lt; side + 2; i++) printf("██");
	printf("\n");
	for (i = 0; i &lt; side; i++) {
		printf("██");
		for (j = 0; j &lt; side; j++) {
			a = i * side + j;
			printf((bitdata[a / 8] &amp; (1 &lt;&lt; (7 - a % 8))) ? "  " : "██");
		}
		printf("██");
		printf("\n");
	}
	for (i = 0; i &lt; side + 2; i++) printf("██");
	printf("\n");

	return 0;
}

运行之后会要求你选择容错程度,容错性越高面积和复杂度会越高。然后要求你输入需要编码的字符串,之后就生成 ASCII 组成的二维码了。编译后的 EFI 可以在 NT32或者实体机上运行,下面就是在 KBL-R HDK 上运行的结果(有兴趣的朋友可以直接用手机扫描一下):

完整的代码和库下载:

参考:
1. https://github.com/trezor/trezor-qrenc

保险中的“不可抗辩条款”

最近偶然看到了“不可抗辩条款”,好奇心驱使之下对其进行了一番研究。有兴趣的朋友可以多读几遍这篇文章,有机会能够以此判断对你推销保险的人是否专业。

先说说背景:保险公司和美国大学一样,普遍实行“宽进严出”。意思是买保险的时候审核不严格,承诺也很多如同结婚之前的男人一样;等你需要用的时候就严格了,国内很常见的是“非常抱歉,您没有按照约定出现问题,所以无法赔偿”。

国外保险公司也一样,这样就导致正常人很难相信保险,也对保险公司产生了严重的负面影响。“1848年英国伦敦寿险公司出售的产品中首次应用了不可抗辩条款。即合同生效一定时期之后,保险公司就不得以投保人误告、漏告等为理由拒绝赔付。这一条款一经推出,就受到了投保人的普遍欢迎,极大地改善了该公司与消费者的关系,为公司赢得了信任。其后该条款被其他公司纷纷仿效,在寿险业得到了极大的推广。1930年,不可抗辩条款首次成为法定条款,由美国纽约州保险监督管理部门在该州保险法例中加以规定,要求所有寿险保单必须包含此条款,以约束保险人的行为,保护保单持有人的利益,防止保险公司不当得利,最终保护整个保险业的健康发展。其后不可抗辩条款通过立法的形式,成为了绝大多数发达国家寿险合同中的一条固定条款”【参考1】

简单的说就是合同生效之后,过了一定的期限,无论你找什么理由,比如之前未能如实告知,进门的时候先迈左腿等等,都必须按照合同约定进行理赔。大致就是这样的意思。

国内2009年《保险法》加入了“不可抗辩条款”(说到这里吐槽一下,我小时候抨击资本主义社会的一条就是:各种法律多如牛毛,各种条款复杂无比,因此,相关问题必须交给专业人士进行处理,充分体现了资本主义法律的虚伪。现在国内法律也有多如各种毛的趋势,这种事情是社会发展的必然结果,和制度没多少关系)。具体条款如下(中华人民共和国保险法(2015年修正)):

第十六条

订立保险合同,保险人就保险标的或者被保险人的有关情况提出询问的,投保人应当如实告知。 投保人故意或者因重大过失未履行前款规定的如实告知义务,足以影响保险人决定是否同意承保或者提高保险费率的,保险人有权解除合同。 前款规定的合同解除权,自保险人知道有解除事由之日起,超过三十日不行使而消灭。自合同成立之日起超过二年的,保险人不得解除合同;发生保险事故的,保险人应当承担赔偿或者给付保险金的责任。 投保人故意不履行如实告知义务的,保险人对于合同解除前发生的保险事故,不承担赔偿或者给付保险金的责任,并不退还保险费。 投保人因重大过失未履行如实告知义务,对保险事故的发生有严重影响的,保险人对于合同解除前发生的保险事故,不承担赔偿或者给付保险金的责任,但应当退还保险费。 保险人在合同订立时已经知道投保人未如实告知的情况的,保险人不得解除合同;发生保险事故的,保险人应当承担赔偿或者给付保险金的责任。 保险事故是指保险合同约定的保险责任范围内的事故。【参考2】

我第一遍看过之后,关注在“自合同成立之日起超过二年的,保险人不得解除合同;发生保险事故的,保险人应当承担赔偿或者给付保险金的责任。”但是后面又说了“投保人因重大过失未履行如实告知义务,对保险事故的发生有严重影响的,保险人对于合同解除前发生的保险事故,不承担赔偿或者给付保险金的责任,但应当退还保险费。”感觉这个操作空间就很大了。

为了正确理解,我去法律裁判文书网搜索案例,虽然我们国家并非依据案例判决,但是这仍然是最权威的解读。

搜索检索条件:全文检索:保险纠纷+不可抗辩

案例1【参考3】

被保险人要求太平洋人寿保险公司日照中心支公司支付理赔。 2010年6月25日签署合同。然后 2017年3月29日,诊断为XX完全性左束支传导阻滞心功能IV级,××症。要求理赔,公司拒绝。理由是:2010年7月12日,原告入住日照市人民医院,××。根据保险合同的约定,原告所患××病情发生在等待期,被告不应承担保险责任,并无息退还原告所缴纳的保费,同时该合同终止。2017年3月29日,原告再次因××入院,后原告向被告申请理赔,被告已经退还原告保费,原告在投保时被告已经向有关说明了保险责任等待期的内容,对等待期条款予以认可,故应驳回原告的诉讼请求。

最终法院认定:保险公司没有办法证明是同一个问题一直如此,并且保险公司一直收费,时间这么久,超过2年不可抗辩。判决保险公司需要理赔。

案例2【参考4】:

原告被保险人要求新华人寿保险股份有限公司青岛分公司支付赔偿。 2013年11月16日购买终身重大XX保险。2016年2月19日原告因患左侧卵巢粘液性囊腺癌急需医疗费而向被告递交索赔文件申请理赔,被告拒赔。被告新华人寿保险股份有限公司青岛分公司辩称,第一,原告在投保之前三年已检出左侧卵巢××,原告在此情况下向被告投保,而且是短期保险,明显属于带病投保,隐瞒事实真相,没有尽到如实告知义务,因此被告有权拒绝理赔。第二,原告所患左侧卵巢粘液性囊腺癌并没有达到涉案保险合同约定的理赔标准,被告拒绝赔付符合合同约定,应驳回原告诉讼请求。

最终法院裁定,虽然2011、2012、2013年原告体检时检查出左侧卵巢见囊性无回声,但是投保时仍然选择“否”,已经违反如实告知,但是被告2年内没有提出解除合同,因此判决保险公司需要理赔。

案例3【参考5】:

原告被保险人诉被告百年人寿保险股份有限公司内蒙古分公司赤峰中心支公司保险纠纷。被保险人2014年12月19日买的重大疾病保险。2017年11月21日被保险人因颅内胶质瘤入院治疗。申请赔偿的时候被拒绝。理由是:被保险人2012年11月6日因原告被诊断患有“星形细胞瘤”在赤峰市医院住院并进行手术治疗。保险公司以被保险人投保时未如实告知、系“保险欺诈”为由拒绝理赔并于2018年2月5日作出《理赔决定通知书》整案拒赔。自合同成立后原告已连续缴费3年。

最终法院认定:原告投保时未如实告知,原告具有主观恶意,系恶意骗保的不诚信行为,并违反保险合同法的规定,应赋予被告解除权,且两年不可抗辩期间适用的前提是保险合同成立两年后新发生的保险事故,因此,保险合同成立前已经发生保险事故不适用《保险法》第十六条的规定,故被告不应理赔。但是,因为保险公司在与被保险人签订保险合同时未尽到职责,在被保险人投保时若要求提供体检报告,就能阻止被保险人带病投保,避免此类保险纠纷的发生

最终要求保险公司支付赔偿。

这个案例让我感觉就是晕,逻辑非常混乱的感觉,也许是因为走得简易程序?这个事情还有后续【参考6】,前文的被保险人因病去世,保险公司提起上诉不希望赔偿,然后被保险人的姐姐继承财产。双方无新证据提交。最终法院认定维持原判,保险公司需要赔偿。

案例4【参考7】

这是二审的案例。一审的是被保险人起诉平安人寿襄阳中心支公司拒赔。审理结果是:拒赔有道理。

被保险人 2014年8月11日购买平安人寿襄阳中心支公司销售的人身保险,后来缴纳了三期。但是,被保险人2009年1月就开始患有霍奇金氏淋巴瘤并前后多次住院接受治疗。签订保险时没有如实告知。在合同成立的2年零7个月后,即2017年2月8日至2017年5月24日期间,上诉人先后分别在襄阳中心医院、协和医院住院治疗99天。之后申请理赔被拒绝。

案例5【参考8】

这是上诉的案子,中国太平洋人寿保险股份有限公司周口中心支公司上诉不服一审判决。

大概的案情:2015年6月17日 投保人时佳为其父亲时富根(××)在中国太平洋人寿保险股份有限公司投保,保险合同生效日为2015年6月17日。投保人时佳为时富根在中国太平洋人寿保险股份有限公司周口中心支公司已经连续两年交纳保险费(这句话没明白什么意思)。2017年5月9日,时富根检查出来肝癌。然后申请理赔。中国太平洋人寿保险股份有限公司周口中心支公司于2017年10月13日做出《理赔决定通知书》,以××时富根投保前因原发性肝癌住院,××投保为由,拒不承担给付保险金的义务。另查明,2015年6月17日时富根因病在驻马店中心医院住院8天,被诊断为原发性肝癌。

一审认定:本案双方自2015年6月17日签订保险合同成立并生效以来,已经超过两年,时富根并且连续两年交纳保费。无论时富根是否存在故意或重大过失未履行如实告知义务的情形,均不能成为拒绝给付保险金的理由,故时富根诉请判令支付××保险金150000元,于法有据,法院予以支持。

二审认定,本案的争议焦点为:在投保前时富根已身患××,保险公司应否理赔。

本案中,双方之间的保险合同自2015年6月17日签订成立并生效以来,已经超过两年,投保人连续两年缴纳保费。××时富根虽为××投保,未向保险公司尽到如实告知义务。但保险合同的解除是保险人拒绝承担保险责任的前提,保险人应当在不可抗辩期间内解除合同。根据《中华人民共和国保险法》第十六条第三款“前款规定的合同解除权,自保险人知道有解除事由之日起,超过三十日不行使而消灭。自合同成立之日起超过二年的,保险人不得解除合同;发生保险事故的,保险人应当承担赔偿或者给付保险金的责任”的规定,保险人解除保险合同应受不可抗辩期间的限制:一是在知道有解除事由之日起三十日内;二是自合同成立之日起二年之内,超过任何一个期间解除权即丧失。在本案所涉保险合同未被解除的情况下,对双方具有约束力,保险公司应当按照本案所涉保险合同的约定承担给付保险金的责任。

最终,法院认为,《中华人民共和国保险》第十六条第三款规定“前款规定的合同解除权,自保险人知道有解除事由之日起,超过三十日不行使而消灭。自合同成立之日起超过二年的,保险人不得解除合同;发生保险事故的,保险人应当承担赔偿或者给付保险金的责任”,该条款虽然是对保险人解除合同的权利加以限制,但并不意味着投保人可以滥用此条款来进行恶意投保并拖延理赔的不诚信行为,因此该条款所规定的两年不可抗辩期间适用的前提是保险合同成立两年后新发生的保险事故。而本案中,上诉人廖启来投保前所患疾病与其提出保险理赔所患疾病系同一疾病,属于保险合同成立前已经发生的保险事故,并非投保后经医院确诊初次发生“重大疾病”,不属于保险合同约定的保险责任,故保险公司不应赔偿。因此上诉的被保险人来此项上诉理由不能成立,本院不予支持。

案例6【参考9】

这是上诉的案件,一审被保险人败诉。

案情如下: 2011年6月,被保险人和中国人寿保险公司签订合同。2015年2月2日,被保险人因重症肌无力到医院住院治疗,并于2015年3月4日以此次住院为由向中国人寿保险公司申请理赔,保险公司发现她在2011年3月投保前因重症肌无力住院。

一审判决:被保险人带病投保的行为违背了当事人在从事民事行为中应当遵循的诚实信用原则,根据《中华人民共和国合同法》第五十四条第二款“一方以欺诈、胁迫的手段或者乘人之危,使对方在违背真实意思的情况下订立的合同,受损害方有权请求人民法院或者仲裁机构变更或撤销”的规定,中国人寿保险公司有权撤销保险合同,且中国人寿保险公司行使撤销权并未超过《中华人民共和国合同法》第五十五规定的一年期限,因此,对中国人寿保险公司撤销保险合同的请求,予以支持。

二审结论:上诉人的上诉理由不成立,本院不予支持。原审判决认定事实清楚,适用法律正确,应予维持。

看过上述案例之后,对于投保之前患病,缴纳保费超过2年,能否使用不可抗辩条款的答案仍然是无法确定。如果你是之前患病距离投保时间比较长,比如大于2年,然后再次患病距离首次投保时间也比较长,比如超过3年,拒绝理赔之后直接起诉胜率会比较大。上述案例中,起诉费用不高,普遍在1-2千,偶尔还能打折,但是整体周期会比较长。

参考:

1. https://baike.baidu.com/item/%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9%E6%9D%A1%E6%AC%BE/2491542?fr=aladdin   不可抗辩条款

2. https://duxiaofa.baidu.com/detail?searchType=statute&from=aladdin_28231&originquery=%E4%BF%9D%E9%99%A9%E6%B3%95&count=182&cid=995d3f3bcf96060c74df1af2f6fce4d8_law中华人民共和国保险法(2015年修正)

3. http://wenshu.court.gov.cn/content/content?DocID=1e96371d-dd87-42ae-8437-a86d0186878a&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9  惠杨与太平人寿保险有限公司日照中心支公司保险纠纷一审民事判决书

4. http://wenshu.court.gov.cn/content/content?DocID=12fa70fd-4d7d-4ba9-8dcc-6149d33ef89c&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9  迟延文与新华人寿保险股份有限公司青岛分公司保险纠纷一审民事判决书

5. http://wenshu.court.gov.cn/content/content?DocID=2bd9419c-39fd-4f74-815d-a9be00ef2eb1&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9  张雪东与百年人寿保险股份有限公司内蒙古分公司赤峰中心支公司保险纠纷一审民事判决书

6. http://wenshu.court.gov.cn/content/content?DocID=3b8867a6-7ba6-452b-b2fb-a9b800e20fc2&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9  百年人寿保险股份有限公司内蒙古分公司赤峰中心支公司与(原审原告张雪东姐姐)张艳秋保险纠纷二审民事判决书

7. http://wenshu.court.gov.cn/content/content?DocID=43adb4a9-d430-499e-a9d7-a95e0164635e&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9 廖启来、中国平安人寿保险股份有限公司襄阳中心支公司保险纠纷二审民事判决书

8. http://wenshu.court.gov.cn/content/content?DocID=f1622c7c-80cf-4f45-a984-a8bf011c7397&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9  中国太平洋人寿保险股份有限公司周口中心支公司、时富根保险纠纷二审民事判决书

9. http://wenshu.court.gov.cn/content/content?DocID=8725b916-016a-4cf5-a11b-f0a2aa19efc9&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9 中国人寿保险股份有限公司青岛市分公司与于元章保险纠纷二审民事判决书