sizeof(int)=?

很长一段时间以来,我理所当然的认为 sizeof(int) 是根据编译目标变化的,比如: x86 下应该为 4 Bytes (32-bits),x64 模式下应该为 8 Bytes(64 bits)。但是近日有朋友遇到了问题,我随手实验,用 printf 输出 sizeof(int) 和 sizeof(int *) 结果令我大吃一惊:

x86 下 sizeof(int) ==4 , sizeof(int *)==4

x64 下 sizeof(int) ==4 , sizeof(int *)==8

就是说, x64 下内存指针变成了8Bytes, 但是 int 仍然是 4bytes。无他,规定如此耳。

所以,在编写 UEFI 代码的时候,尽量不要使用 int 类型,因为你在代码中随手写下的赋值可能会因为编译目标不同而导致令人疑惑的错误。作为 Firmware 工程师,需要时刻了解数据的大小。

参考:

1.https://docs.microsoft.com/en-us/cpp/cpp/data-type-ranges?view=msvc-160

2.https://www.geeksforgeeks.org/difference-between-sizeofint-and-sizeofint-in-c-c/

3.https://docs.microsoft.com/en-us/cpp/cpp/fundamental-types-cpp?view=msvc-160

Sizes of built-in types

Most built-in types have implementation-defined sizes. The following table lists the amount of storage required for built-in types in Microsoft C++. In particular, long is 4 bytes even on 64-bit operating systems.

TypeSize
boolcharchar8_tunsigned charsigned char__int81 byte
char16_t__int16shortunsigned shortwchar_t__wchar_t2 bytes
char32_tfloat__int32intunsigned intlongunsigned long4 bytes
double__int64long doublelong longunsigned long long8 bytes

嘉立创 SMT 指南

相信大多数和我一样的非专业玩家在制作自己的作品时经常会受到焊接方面的困扰。比如,经常因为焊接失误导致芯片报废。曾经有哲人说过:“一个人遇到的99%的问题都可以用钱解决,剩下的1%,需要用更多的钱。”同样的,焊接的问题也可以用钱来解决。这次以设计一块CH558芯片的开发板为例,介绍一下如何在嘉立创直接 SMT。

选择嘉立创的主要原因是:他们有PCB 绘制软件,PCB 制作,SMT焊接和元件商城 。这样涵盖了电子制作的全部过程,可以一站式满足需求,这样在最大程度上避免了生产过程不匹配的问题,尽可能节省了设计者的时间。比如,我曾经碰到做主板时贴片二极管符号和实物方向不匹配的问题,第一次遇到问题时,硬件工程师花了很长时间才确定问题。

第一个目标时是完成电路图和 PCB 的绘制,这个步骤结束后会产生有三个用于生产的文件:

  1. Gerber File (用于制作PCB。电路的设计软件很多,比如 PROTEL, Eagle, Allegro 等等。PCB 生产厂家通常 “不认可”你提交的源文件,但是 Gerber 格式是通用的。)
  2. BOM (元件列表,用于告诉准备SMT的人需要为你的板子准备什么元件。订阅《无线电》杂志的朋友应该看过上面对于波峰焊机的介绍。在SMT准备过程中,工程师就是根据BOM 将需要焊接的元件料盘放置在机器中。)
  3. 坐标文件(告诉贴片机在什么位置贴什么元件。)

前面提到立创有一条龙服务,这里使用的软件是立创EDA。主要优点是支持在线编辑绘制电路图和PCB,这样有网络能打开网页就可以画图,上班摸鱼下班回家后可以继续绘制。缺点主要是:自动布线不太成熟,经常抽风,官方建议手工布线。

绘制电路的过程需要选择元件。诸如USB公头这样的插接件,强烈建议选用商城中的元件,因为这样的元件有对应的元件库,这样能够尽可能避免引脚差异导致的无法使用。

这次绘制的是 CH558的开发板,更具体可以说是将 CH558的封装转成 DIP 封装。电路图很简单,外围元件只有几个电阻,主要工作是将芯片引到排插上,设计如下:

PCB设计如下,特别注意图中的2个USB引脚需要通过差分布线来实现(主要是保证等长):

上述设计完成后即可生成准备用于制造的文件。

在PCB编辑界面上(注意,电路图编辑界面没有这个选项,必须是 PCB编辑界面),选择下面“生成制造文件(Gerber)”;或者在菜单中选择“制造àPCB制版文件(Gerber)”:

在弹出的对话框中使用按钮下载生成的 Gerber package。这样我们就有了 Gerber File有了这样的文件可以提交给 PCB 厂家进行生产。

同样的,可以在菜单—> 制造—>物料清单(BOM)生成物料清单表。

列表如下,使用下面的“导出BOM ”即可生成生产所需 BOM 表

接下来是坐标文件,在同样的位置

我们是单板,在嘉立创生产所以都无须勾选:

选择“导出”后,我们就有了坐标文件。下面就可以提交进行生产了。

有了上面的文件就可以准备生产了,SMT 的目标可以分解为2个小目标:一个是PCB 的制作,另一个是在这个 PCB 上进行SMT。

小目标1:提交嘉立生产PCB

1.1使用嘉立创下单助手(这个软件有网页版和PC版,二者界面是相同的。目前在推广期,建议使用PC版提交生产,有优惠价格)。选择左侧 PCB订单管理à在线下单/计价

1.2 点击 “上传PCB文件” 选中刚才的Gerber Packag-e 文件:

1.3 自动解析,生成预览。可以看到PCB的预览图,建议认真观察过孔位置等等:

1.4 在上面的界面选择“板子数量”,最少5块。建议第一次打选择5块这样最节省。

1.5 续设定参数,如果没有特别要求大部分使用默认值即可。具体尺寸可以在立创 EDA 菜单 ――> 制作 ――> PCB 信息 中看到。目前嘉立创有优惠,用户每个月有两次机会,可以以5元价格制作10x10cm以内的PCB,非常实惠。

如果你是第一次打板子,建议选择“确认生产稿”(多3元),这样可以避免一些问题(比如线距太窄导致PCB 打出来就无法使用这样的问题):

下面的工艺信息中,阻焊颜色决定了 PCB板子的颜色,绿色的打板速度最快,其他颜色加工时间稍微长一点(绿色通常是24小时;我个人比较喜欢红色,承诺交货时间是72小时)。我们打的板子没有拼板(这个可以理解为一个稿件中同时包含了多块板子)。我们打的样板没有拼板(拼版可以理解为一个稿件中同时包含了多块板子,在以面积计算价格的情况下可以节省成本)。

下面这些选项保持默认即可:

我们要求 SMT 因此,这里选择“需要”:

点击后会弹出要求确认,再点需要。

我们这次打的是单片:

选择了 SMT 之后只能选择不需要钢网,另h外也不需要发票

交期根据自己需要选择最快的:

目前的优惠每个用户每个月有两次机会,可以以5元价格制作10x10cm以内的PCB

1.5提交订单,等待审核。建议耐心一点等待审核结果。

1.6上述选择后,在右侧填写地址信息。至此,PCB 的提交已经完成。如果你只是制作PCB 没有 SMT 的要求,在付款之后等待PCB到家即可。

下面是提交 SMT的过程

  1. 审核结束之后,点击确认。会提示继续下 SMT 订单。

2.建议第一次打板选择 2片贴3片不贴

3.接下来继续填写表格,这里需要前面BOM 和 坐标文件,选择文件后即可进行下一步:

4.勾选需要进行贴片的文件,我们这里只贴嘉立创有库存的元件,余下的等板子回来自己手工贴。这里需要注意目前嘉立创无法贴需要手工摆放的元件,比如:USB公头,12MHz晶振这样的东西:

5.接下来会有简单的预览,我们的元件在正面(T层)

6.提示要求确认:

7.继续后需要确认主要芯片的Pin1 ,以及由极性元件的方向。确定后继续

8.最后就是交钱的过程了

经过一段的等待后板子就会飞越千山万水来到你的手中,入下:两片打芯片,三片不打。

板子到手之后即可着手进行检查,可能出现的问题是元件库和实物不符,比如:WS2811 搞错了 Pin1, 这种情况下可以去论坛提出问题,然后会有 PCB 和 EDA 方面的人和你进行接洽。

Vs2015 Source Level Debug WinHost

前面提到过,EDK2 后面更换了模拟器,每次编译的结果是 WinHost.exe。最近偶然发现,之前给出来的调试方法无效【参考1和2】。具体现象是运行之后遇到 DebugBreak会自动退出不会出现VS2015调试界面。经过研究,可以用下面的方法来实现源码调试:

  1. 编译 EmulatorPkg ,然后运行起来 WinHost.exe
  2. 打开 VS2015(需要管理员权限运行),选择 Debug -> Attach to Process
VS2015 Attach to Process

 3.在出现的界面中选择 WinHost.exe

找到当前模拟器的 WinHost.exe 进程

4.在WinHost 模拟器中发出 Break ,比如,使用【参考2】的代码,运行 rim 程序:

在 WinHost.exe 中 Trigger DebugBreak

5.使用 F10 跳过中断,可以看到 Image.c 的代码:

参考:

  1. http://www.lab-z.com/stu204nt32day/
  2. http://www.lab-z.com/stu205/

===============================================

2025年2月,验证了上述方法在 EDK2 202411 + VS2019 上仍然可以使用

推荐一个三极管仿真软件

在使用 Arduino 驱动一些元件工作的时候需要特别留意元件的功耗,如果元件需要的电流超过了 Arduino 引脚的共给能力就需要想办法进行“扩流”。最简单的方法就是使用三极管。但是三极管工作状态计算是一个麻烦的事情,在我上学的时候老师就曾经讲过“数字电路的搞不赢模拟电路”,时至今日,我的模拟电路知识更是早已归还给了老师。好在近日找到一个好用的在线计算工具在 https://www.falstad.com/circuit/e-pnp.html 这对于设计一个三极管驱动电路是最合适不过的:

首先,放置一个我们需要的三极管(PNP,NPN)。然后设定它工作的放大参数。

例如,Import 我们之前设计的电路(在 File 菜单中选择保存和加载你的设计):

通过修改 “Base Voltage” 的电压,可以模拟Arduino GPIO ,可以看到,当Arduino GPIO 为5V 时,8欧电阻(实际上我想放置于此的是蜂鸣器)上的电流为 0.1pa,相当于没有驱动电流:

当Arduino Pin 为0 时,300Ω电阻上电流时 14.126ma, 8欧姆电阻上电流为 (625.4-14.126)ma,此时蜂鸣器可以工作

本文提到的设计,三极管放大倍数为 120 (对应三极管SS8050),电路文件如下:

$ 1 0.000005 89.03963197220334 33 2 50
172 208 176 176 176 0 7 5 5 0 0 0.5 Base Voltage
w 320 160 320 112 1
t 272 176 320 176 0 -1 4.9999999999683995 -3.000089066063083e-11 120
R 320 112 320 80 0 0 40 5 0 0 0.5
172 320 240 320 272 0 7 0 2 0 0 0.5 Collector Voltage
r 224 176 256 176 0 300
w 224 176 208 176 0
w 256 176 272 176 0
r 320 208 320 240 0 8
w 320 192 320 208 0

UEFI下的 NTFS 驱动

最近研究了一下rufus,这是一款Windows 下的启动 U盘制作工具【参考1】。更难能可贵的是该项目是开源的。同时它提供了UEFI 的 NTFS 和 exFat32 分区的驱动。这次我测试了前者。

1.首先检查一下,系统中有2个 Shell 下可见的分区:U盘的一个和硬盘上的启动分区

2.加载 NTFS 驱动

3. map -r 之后可以看到能识别更多的分区

4. fs1 应该是系统的 recovery 分区

5.这是安装 Windows的分区

本文提到的ntfs 和 exFat32 驱动下载

参考:

1.https://rufus.ie/

2.https://github.com/pbatard/rufus/tree/master/res

Virtualbox 安装 Windows 声卡的方法

 VirtualBox 默认情况下 Window 10 安装之后是没有声卡驱动的。经过搜索,可以用下面的方法解决:

1.确定虚拟机设定如下

VirtualBox Audio 设定

2.在这个链接下载驱动程序 https://realtek-download.com/wp-content/uploads/2014/07/6305_vista_win7_pg537.zip

3.安装之后如下,测试可以正常发声

右下角音频正常
设备管理器无 Yellow Bang

参考:

1. https://superuser.com/questions/1106839/audio-not-working-in-win-10-in-virtualbox-virtualbox-5-1-2-guest-additions-m

招聘信息(更新Intel BIOS测试职位 3月31日)

●2022年3月更新一个 Intel BIOS 测试职位,具体要求可以在下面看到

●2022年1月更新 Ampere Computing职位,具体要求可以在下面看到

●2021年11月更新 Intel Shenzhen职位,具体要求可以在下面看到

LCFC 的一些职位,具体可以在以下页面看到

● AMD BIOS 的一个职位,具体要求在以下页面:

http://www.lab-z.com/amd-bios/

● Ampere Computing 的一个职位 ,要求在以下页面可以看到:

Remote 修改 Notepad

在之前的文章中研究过如何修改 Notepad,但是当时的结论是:无法通过 Kernel Debug 的方式调整目标机上的 Application【参考1】。这次实验使用 Kernel Debug 进行同样的操作。实验环境是USB3.0 调试远端 Win 10 环境。

Step1. 建立好 USB 3.0 的连接,然后在被测机上打开 NotePad.exe,另外保证 Symbols 正确加载

Step2. 使用 !process 0 0 列出所有进程:

PROCESS ffffcf0d61835300
    SessionId: 1  Cid: 0b08    Peb: 14fbdcc000  ParentCid: 03b8
    DirBase: 3d3a5f000  ObjectTable: ffff9381ba013d00  HandleCount: 256.
    Image: svchost.exe

PROCESS ffffcf0d65432300
    SessionId: 0  Cid: 0e68    Peb: fa07ff1000  ParentCid: 03b8
    DirBase: 3f5dba000  ObjectTable: ffff9381b739bd40  HandleCount: 224.
    Image: svchost.exe

PROCESS ffffcf0d6a8a0080
    SessionId: 1  Cid: 04a0    Peb: 40c7e7c000  ParentCid: 18ac
    DirBase: 27b9df000  ObjectTable: ffff9381b72cbcc0  HandleCount: 233.
    Image: notepad.exe

Step3. 使用.process /i /p ffffcf0d6a8a0080入侵式调试 Notepad进程,然后再 g 继续。等再次停下时已经在 Notepad进程中了。

.process /i /p

之后的做法和【参考1】提到的本地调试相同

Step4. 在打开窗口函数上下断点 bp notepad!ShowOpenSaveDialog,g之后在 Notepad 中 file -> open就会自动停下来

设置 notepad!ShowOpenSaveDialog 断点

Step5. K 命令查看堆栈

查看断下来之后的堆栈

Step6. ub notepad!InvokeOpenDialog+0xc3 反编译

反编译查看调用处的代码

Step7. 我们修改的目标就是上面的 mov rcx,rsi, 使用 a 命令写入代码

a 命令写入代码

再确认修改成功

再次反编译确定修改成功

Step8. G 命令运行,然后重新打开 Open 菜单,可以发现当前能够在打开对话框的情况下聚焦到Notepad 编辑区。

参考:

1.http://www.lab-z.com/windbgn/

WMIC ProcessorID 取得的 CPUID

最近有人在群里询问是否有在电脑上生成唯一序列号的方法,有一个群友建议他使用下面的命令来获得:

wmic cpu get processorid

但是在我印象中,很久之前,CPU支持过序列号功能,但是被人指责侵犯隐私,所以现在的 CPU完全没有所谓的序列号。为此,做了一番研究,在【参考1】有介绍过这一段历史,在奔腾3中短暂的引入过这个功能,但是后来很快就移除了。

EAX=3: Processor Serial Number[edit]

See also:  Pentium III § Controversy about privacy issues

This returns the processor’s serial number. The processor serial number was introduced on Intel Pentium III, but due to privacy concerns, this feature is no longer implemented on later models (PSN feature bit is always cleared). Transmeta’s Efficeon and Crusoe processors also provide this feature. AMD CPUs however, do not implement this feature in any CPU models.

For Intel Pentium III CPUs, the serial number is returned in EDX:ECX registers. For Transmeta Efficeon CPUs, it is returned in EBX:EAX registers. And for Transmeta Crusoe CPUs, it is returned in EBX register only.

Note that the processor serial number feature must be enabled in the BIOS setting in order to function.

“这样取出来的只是 CPUID 指令运行的返回结果,在【参考2】中有指出:

实际上,获取到的值并不是CPU的编号或者序列号,也并不是唯一的,对此,微软在msdn上有相关说明:

msdn链接:http://msdn.microsoft.com/en-us/library/aa394373(v=vs.85).aspx

msdn上的原文是这样说的:

ProcessorId

Data type: string

Access type: Read-only

Processor information that describes the processor features. For an x86 class CPU, the field format depends on the processor support of the CPUID instruction. If the instruction is supported, the property contains 2 (two) DWORD formatted values. The first is an offset of 08h-0Bh, which is the EAX value that a CPUID instruction returns with input EAX set to 1. The second is an offset of 0Ch-0Fh, which is the EDX value that the instruction returns. Only the first two bytes of the property are significant and contain the contents of the DX register at CPU reset—all others are set to 0 (zero), and the contents are in DWORD format.”

编写一个C 代码,在我工作机器运行 CPUID指令:

#include "stdafx.h"
#include<iostream>

int main()
{

	int32_t deBuf[4];

	__cpuidex(deBuf, 01, 0);
	printf("%.8x%.8x", deBuf[3], deBuf[0]);

	getchar();
    return 0;
}

运行结果:

自己编写的 CPUID指令程序运行结果

这个结果和我是用 WMIC 命令结果是相同的:

WMIC 取得的 processorid 结果

结论:不要使用 WMIC get processorid 取得的CPUID 作为序列号,这样的话会发现同一批机器结果不同。

参考:

  1. https://blog.csdn.net/fudong071234/article/details/49612083
  2. https://www.cnblogs.com/hhh/p/4022128.html

互联网账号共享器(DFRobot论坛首发)

在很久之前,共享一个账号是很简单的事情,只需要告诉对方账号和密码即可。但是随着时代的发展,除了账号密码之外,登陆还需要短信验证。比如目前国内功能最强,应用最广泛的某个网盘,很多资源都可以直接在上面找到免除了搜索之苦,另外和 BT 下载相比,只要保存了资源就不用担心内容不全(试想一下,历经千辛万苦淘到的种子,始终卡在99%远比找不到资源更加抑郁)。接下来的问题就是费用,每月25元的会员费用对于下载狂魔来说并不是问题,但是对于我这样一个月只有一两次下载需求的人来说就显得不那么划算,因此如果能和同学朋友共享一个账号是最好不过的了。唯一的问题是似乎网盘考虑到了这样的场景,经常在登陆时需要通过短信进行身份验证。如果能将验证短信一同分享给同学朋友,那就不存在这样的问题了。本文就介绍如何通过将身份验证短信发送到邮箱中来解决这个问题。

首先选择合适的开发板。这里我选择的是 LilyGo 推出的的T-Call开发板,它主控是 ESP32 板载了Sim800,因此可以用它实现收发短信,拨打电话和连接互联网的功能(ESP32本身可以实现WIFI上网,SIM800可以实现运营商的GPRS通讯功能)。

LilyGo T-Call

ESP32 是通过串口和SIM800 进行通讯的:

T-Call 串口
SIM800L 串口

ESP32 的一个特点是:具有OUTPUT功能的GPIOPin 可以任意定义为其他功能。例如,通过下面的代码指定了IO26/27作为串口使用:

#define MODEM_TX            27
#define MODEM_RX            26
// ESP32 对 SIM800串口
SerialAT.begin(115200,SERIAL_8N1, MODEM_RX, MODEM_TX);


SIIM800L 是通过串口用 AT 命令和 ESP32 打交道的,为了便于测试设计如下的测试代码:

#include <Wire.h>
 
  
 
// Set serial for AT commands (to the module)
 
#define SerialAT  Serial1
 
  
 
  
 
#define MODEM_RST             5
 
#define MODEM_PWRKEY          4
 
#define MODEM_POWER_ON       23
 
#define MODEM_TX             27
 
#define MODEM_RX             26
 
  
 
#define I2C_SDA              21
 
#define I2C_SCL              22
 
#define LED_GPIO             13
 
#define LED_ON               HIGH
 
#define LED_OFF              LOW
 
  
 
#define IP5306_ADDR          0x75
 
#define IP5306_REG_SYS_CTL0  0x00
 
  
 
void setup() {
 
    // put your setup code here, to run once:
 
    Serial.begin(115200);
 
  
 
    // Set GSM module baud rate and UART pins
 
    SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
 
    delay(1000);
 
  
 
    #ifdef MODEM_RST
 
    // Keep reset high
 
    pinMode(MODEM_RST, OUTPUT);
 
    digitalWrite(MODEM_RST, HIGH);
 
#endif
 
  
 
    pinMode(MODEM_PWRKEY, OUTPUT);
 
    pinMode(MODEM_POWER_ON, OUTPUT);
 
  
 
    // Turn on the Modem power first
 
    digitalWrite(MODEM_POWER_ON, HIGH);
 
  
 
    // Pull down PWRKEY for more than 1 second according to manual requirements
 
    digitalWrite(MODEM_PWRKEY, HIGH);
 
    delay(100);
 
    digitalWrite(MODEM_PWRKEY, LOW);
 
    delay(1000);
 
    digitalWrite(MODEM_PWRKEY, HIGH);
 
  
 
    // Initialize the indicator as an output
 
    pinMode(LED_GPIO, OUTPUT);
 
    digitalWrite(LED_GPIO, LED_OFF);
 
    
 
    Serial.println("Starting......");
 
}
 
  
 
void loop() {
 
  if (Serial.available()) {      // If anything comes in Serial (USB),
 
    SerialAT.write(Serial.read());   // read it and send it out Serial1
 
  }
 
  
 
  if (SerialAT.available()) {     // If anything comes in Serial1
 
    Serial.write(SerialAT.read());   // read it and send it out Serial (USB)
 
  }
 
}

这个代码前部分是 T-Call开发板提供的初始化代码,主要是对模块 Reset 之类的,然后在 Loop 中设置了一个USB串口和 SIM800L 互传的代码,就是USB串口的内容接收后转发到SerialAT上,同样的 SerialAT 上的内容也会转发给 USB串口。
烧写之后,可以在串口监视器中和SIM800L直接通讯,这样可以方便的进行命令的测试。需要注意的是在下面一定要选择“BOTHBL&CR”,这样每次发送的数据都是使用回车和换行结尾,这样才能保证 SIM800L 识别。

AT+CSQ 命令查询当前信号强度结果

因为国内的验证码短信大多是包含中文的,所以需要使用 AT+CMGF=0命令设置为 PDU模式(与之对应的另外一种模式是 TEXT, 是纯 ASCII )。切换为 PDU 之后,收到的一个条短信示例如下:
+CMGL: 2,0,””,800891683110304105F22412A10156155931010536300008020130224352233A30104EAC4E1C30119A8C8BC178014E3A003100330038003200390035FF0C8BF757286CE8518C987597624E2D8F9351654EE55B8C62106CE8518C
有兴趣的朋友可以去研究一下 PDU 的格式,这里我们使用一个在线的工具进行解析【参考1】

在线解析 PDU

将收到的短信内容(数字部分),填写到中间,然后使用 Convert 即可完成解析。
了解了基本原理后就可以开始着手代码编写了。代码主要分成两部分:第一部分,短信的接收。通过一个 AT 告诉 SIM800L 之后每次模块收到短信后会自动将其发送到串口,这样简化了操作,也避免因为SIM卡短信满了的情况;第二部分,通过 EMAIL 转发短信内容。这里使用 ESP32_MailClient 这个库来实现。另外因为 PDU 短信格式比较复杂,还涉及到了 Unicode 的编码问题,想 ESP32 直接解析有困难,因此,这里采用将短信内容嵌入一个解析的 HTML文件,然后按照附件发送的方法。用户收到邮件后,下载附件,打开即可看到内容(因为HTML中有需要执行的 JS 代码,邮箱系统预览附件无法看到最终结果)。
完整的代码如下:

#include <Wire.h>
 
#include "ESP32_MailClient.h"
 
#include "parta.h"
 
#include "partc.h"
 
  
 
// WIFI 账号和密码
 
#define WIFI_SSID "WIFINAME"
 
#define WIFI_PASSWORD "WIFIPASSWORD"
 
  
 
// 给 Serial1 赋予别称
 
#define SerialAT  Serial1
 
  
 
// 声明发送用到的对象
 
SMTPData smtpData;
 
  
 
// 设置短信为PDU 模式
 
String PDUModeCMD="AT+CMGF=0\n\r";
 
// 设置收到短信后直接发送给 ESP32, 不存储在 SIM卡上
 
String DirectSMSCMD="AT+CNMI=1,2,0,0,0\n\r";
 
  
 
#define MODEM_RST             5
 
#define MODEM_PWRKEY          4
 
#define MODEM_POWER_ON       23
 
#define MODEM_TX             27
 
#define MODEM_RX             26
 
  
 
#define I2C_SDA              21
 
#define I2C_SCL              22
 
#define LED_GPIO             13
 
#define LED_ON               HIGH
 
#define LED_OFF              LOW
 
  
 
#define IP5306_ADDR          0x75
 
#define IP5306_REG_SYS_CTL0  0x00
 
  
 
  
 
String SMSReady="";
 
char   c;
 
long int Elsp=-1;
 
  
 
void SendMail() {
 
        Serial.println("Sending email...");
 
  
 
        //【登录】服务器(服务器地址,端口,账号,授权码)
 
        smtpData.setLogin("smtp.163.com", 465, "SenderAddress@163.com", "SenderPassword"); 
 
        //启用【TLS】协议支持,在587端口  
 
        //smtpData.setSTARTTLS(true);
 
        //设置【发件人】
 
        smtpData.setSender("YOURNAME", "SenderAddress@163.com");
 
        //设置【优先级】(High, Normal, Low or 1 to 5 (1 is highest))
 
        smtpData.setPriority("Low");
 
        //设置【主题】
 
        smtpData.setSubject("SMS data");
 
        //设置【消息文本】(普通的文本或html)
 
        smtpData.setMessage("<div style=\"color:#ff0000;font-size:20px;\">SMS in attachment</div>", true);
 
        //添加【收件人】,可以添加多个收件人
 
        smtpData.addRecipient("Receiver@qq.com");
 
        //设置【存储类型】读取附加文件
 
        
 
        // put your setup code here, to run once:
 
        char *p;
 
        if ((p = (char *) malloc (PartA_html_size+SMSReady.length()+PartC_html_size)) == 0)
 
          {
 
            //out of memory
 
            Serial.println("Can't allocate memeory");
 
            smtpData.empty();
 
            return ;
 
          }
 
        for (int i=0;i<PartA_html_size;i++) {p[i]=PartA_html[i];}
 
        for (int i=0;i<SMSReady.length();i++) {p[i+PartA_html_size]=SMSReady[i];}
 
        for (int i=0;i<PartC_html_size;i++) {p[i+PartA_html_size+SMSReady.length()]=PartC_html[i];}
 
        smtpData.addAttachData("SMS.html", "image/png", (uint8_t *)p, PartA_html_size+SMSReady.length()+PartC_html_size); //从内存中
 
        //【发送】
 
        if (!MailClient.sendMail(smtpData)) {
 
        Serial.println("Error sending Email, " + MailClient.smtpErrorReason());
 
        }
 
        //【清空】缓存
 
        smtpData.empty();
 
        free(p);
 
}
 
  
 
void setup() {
 
    // USB 串口
 
    Serial.begin(115200);
 
  
 
    // ESP32 对 SIM800 串口
 
    SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
 
  
 
    Serial.print("Connecting to AP");
 
  
 
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
 
    while (WiFi.status() != WL_CONNECTED)
 
    {
 
      Serial.print(".");
 
      delay(200);
 
    }
 
  
 
    Serial.println("");
 
    Serial.println("WiFi connected.");
 
    Serial.println("IP address: ");
 
    Serial.println(WiFi.localIP());
 
    Serial.println();
 
  
 
    #ifdef MODEM_RST
 
      // Reset 模块
 
      pinMode(MODEM_RST, OUTPUT);
 
      digitalWrite(MODEM_RST, HIGH);
 
    #endif
 
  
 
    pinMode(MODEM_PWRKEY, OUTPUT);
 
    pinMode(MODEM_POWER_ON, OUTPUT);
 
  
 
    // Turn on the Modem power first
 
    digitalWrite(MODEM_POWER_ON, HIGH);
 
  
 
    // Pull down PWRKEY for more than 1 second according to manual requirements
 
    digitalWrite(MODEM_PWRKEY, HIGH);
 
    delay(100);
 
    digitalWrite(MODEM_PWRKEY, LOW);
 
    delay(1000);
 
    digitalWrite(MODEM_PWRKEY, HIGH);
 
  
 
    // Initialize the indicator as an output
 
    pinMode(LED_GPIO, OUTPUT);
 
    digitalWrite(LED_GPIO, LED_OFF);
 
    
 
    Serial.println("Power on");
 
    delay(20000);
 
    Serial.println("Start");
 
  
 
    Serial.println("Set to PDU Mode");
 
    SerialAT.print(PDUModeCMD);
 
    delay(200);
 
    Serial.println("Direct show SMS to uart");
 
    SerialAT.print(DirectSMSCMD);
 
    delay(200);   
 
}
 
  
 
  
 
void loop() {
 
  if (Serial.available()) {      // If anything comes in Serial (USB),
 
    SerialAT.write(Serial.read());   // read it and send it out Serial1
 
  }
 
  
 
  if (SerialAT.available()) {     // If anything comes in Serial1
 
    Elsp=millis();
 
    c=SerialAT.read();
 
    SMSReady=SMSReady+c;
 
    Serial.write(c);   // read it and send it out Serial (USB)
 
  }
 
  
 
  if ((Elsp!=-1)&&(millis()-Elsp>500)) {
 
      Serial.print("Buffer [");
 
      Serial.print(SMSReady);
 
      Serial.println("]");
 
      if (SMSReady.indexOf("+CMT:")!=-1) {
 
            // 删除 "+CMT:" 以及他之前的字符(主要是 \n \r)
 
            SMSReady=SMSReady.substring(SMSReady.indexOf("+CMT:")+5);
 
            SMSReady=SMSReady.substring(SMSReady.indexOf('\n')+1);
 
      Serial.print("<");
 
      Serial.print(SMSReady);
 
      Serial.println(">");
 
  
 
            SendMail();
 
            SMSReady="";
 
            Elsp=-1;
 
        }
 
      else {SMSReady="";}
 
      Elsp=-1;
 
    }
 
}

运行结果:

收到的邮件
打下载附件后打开即可看到结果


最后特别注意的事情还有:

1. SIM卡的选择。切勿选择物联网卡,这种卡没有短信收发和电话拨打的功能。另外,我测试了上电的联通和电信卡,前者工作正常,后者虽然在手机上显示有2G网络,但是无法正常工作;

2. 这个开发板背后的 SIM插槽比较松,我拿到第一天就干掉卡片了,好在装上去还能正常工作,所以在使用时无比小心;

3. 如果发现无法接收到短信,请检查信号强度,或者用其他手机拨打号码查看能否接通;

4. SIM800L 提供上网功能的,但是同ESP32_MailClient不兼容,所以最终还是使用 ESP32 的 WIFI 功能进行上网发送邮件;

本文首发在 DFRobot 论坛

https://mc.dfrobot.com.cn/thread-307727-1-1.html


参考:

1. http://www.sendsms.cn/pdu/