在ESP8266(Arduino SDK)上实现接近6Mbps的高速IO

在这里插入图片描述

TL; DR 代码见GitHub Gist: Jamesits/high-frequency-square-wave-generator-esp8266.ino

继在Arduino UNO上实现了高频方波发生器之后,我把魔爪伸向了便宜量足的ESP8266。它能不能产生符合要求的高频波形呢?

初测

分析ESP8266 Arduino SDK的 digitalWrite() 实现后发现,ESP8266的IO分为两组:0-15号,由同一组寄存器控制;16号独立控制。要实现对0-15号IO口的控制,只需要向 GPOS (置1)和 GPOC (置0)寄存器的相应位置写数据即可。

C++
#include <esp8266_peri.h>

void setup() {
pinMode(5, OUTPUT);
while(1) {
// set high
GPOS = (1 << 5);
// set low
GPOC = (1 << 5);
}
}

void loop() {

}
(为了获得尽可能快的结果,代码省去了几乎所有非必要的功能和函数调用。)

测试结果:
在这里插入图片描述

80MHz:约5.88MHz
160MHz CPU:约6.25MHz

看门狗问题

但是这样做还有个问题:ESP8266会以每六秒钟多一点一次的频率重启。(如果你的ESP模块的PIN 4上焊有LED,重启时LED会短暂闪亮一下。)研究发现,ESP8266由于在软件上实现了Wi-Fi和TCP stack,故意设计了双重watchdog:软件watchdog会在各种系统函数里面被调用,用来执行网络操作,SDK提供了一个禁用/启用的函数;硬件watchdog是强制开启的,没有提供禁用方法,喂狗间隔6.7s左右,会在软件watchdog调用时喂。由于大部分人写的代码或多或少会频繁调用一些系统函数,因此不会感知到这两个watchdog的存在;而现在的这份代码由于死循环且循环内不调用任何函数,一开机就会触发watchdog重启。

为了测试喂狗造成的时间问题,我在上面的程序里加入了喂狗函数,然后重新测试。

C++
#include <Esp.h>
#include <esp8266_peri.h>
#define PINOUT 5

void setup() {
// disable software watchdog
ESP.wdtDisable();
pinMode(PINOUT, OUTPUT);
while(1) {
// set high
GPOS = (1 << PINOUT);
// set low
GPOC = (1 << PINOUT);
// feed hardware watchdog
ESP.wdtFeed();
// shift again so we can measure the timing of watchdog feeding
GPOS = (1 << PINOUT);
GPOC = (1 << PINOUT);
}
}

void loop() {

}
测试结果:
在这里插入图片描述

现在的确不重启了。不幸的是,如图所示,喂狗大约需要430ns,这的确是一笔不小的开销。有没有办法绕过硬件watchdog呢?

经过一番研究,我发现Mongoose OS在ESP8266 SDK中禁用了硬件watchdog。他们是怎么实现的呢?其实,硬件watchdog有一个开关,只不过ESP8266 Arduino SDK开发者认为关掉它会影响基础功能,没有实现。Mongoose OS的 esp_hw_wdt_disable() 函数实现了关闭硬件watchdog功能,那么我们把它抄过来。

C++
#include <Esp.h>
#include <esp8266_peri.h>
#include <eagle_soc.h>
#define REG_WDT_BASE 0x60000900
#define WDT_CTL (REG_WDT_BASE + 0x0)
#define WDT_CTL_ENABLE (BIT(0))

#define PINOUT 5

void setup() {
// disable hardware watchdog
CLEAR_PERI_REG_MASK(WDT_CTL, WDT_CTL_ENABLE);
// disable software watchdog
ESP.wdtDisable();
pinMode(PINOUT, OUTPUT);

while(1) {
// set high
GPOS = (1 << PINOUT);
// set low
GPOC = (1 << PINOUT);
}
}

void loop() {

}
测试结果:
在这里插入图片描述

结果是非常完美的。

实现固定频率方波发生器

接下来,我们把上一篇文章实现的高频方波发生器移植过来。

C++
#include <Esp.h>
#include <esp8266_peri.h>
#include <eagle_soc.h>
#define REG_WDT_BASE 0x60000900
#define WDT_CTL (REG_WDT_BASE + 0x0)
#define WDT_CTL_ENABLE (BIT(0))

#define PINOUT 5

double freq; // Hz
double offset; // percent
double width; // percent

// unit: microsecond
unsigned long cycle_time;
unsigned long raising_edge;
unsigned long falling_edge;
unsigned long prev_micros;

// compare 2 unsigned value
// true if X > Y while for all possible (X, Y), X - Y < Z
#define TIME_CMP(X, Y, Z) (((X) - (Y)) < (Z))

inline void setHigh() {
GPOS = (1 << PINOUT);
}

inline void setLow() {
GPOC = (1 << PINOUT);
}

void setup() {
CLEAR_PERI_REG_MASK(WDT_CTL, WDT_CTL_ENABLE);
ESP.wdtDisable();
pinMode(PINOUT, OUTPUT);

// calculate arguments
freq = 1;
width = 0.5;
offset = 0.0;

cycle_time = 1000000 / freq;
raising_edge = (unsigned long)(offset * cycle_time) % cycle_time;
falling_edge = (unsigned long)((offset + width) * cycle_time) % cycle_time;

prev_micros = micros();

// do pinout shifting
while(1) {
if (width + offset < 1) {
// raising edge should appear earlier
while (TIME_CMP(micros(), prev_micros + raising_edge, cycle_time)); setHigh();
while (TIME_CMP(micros(), prev_micros + falling_edge, cycle_time)); setLow();
} else {
// falling edge should appear earlier
while (TIME_CMP(micros(), prev_micros + falling_edge, cycle_time)); setLow();
while (TIME_CMP(micros(), prev_micros + raising_edge, cycle_time)); setHigh();
}
prev_micros += cycle_time;
}
}

void loop() {

}
测试结果:
在这里插入图片描述

设定方波频率 实测方波频率 误差 备注

250KHz 333.27KHz-341.82KHz – 极限频率以上,误差过大无法使用
250KHz 249.95KHz 0.02%
100KHz 99.98KHz 0.02%
50KHz 49.99KHz 0.02%
1000Hz 999.80Hz 0.02%
得益于高速CPU,ESP8266在方波发生器程序上的表现远好于Arduino UNO
CPU频率设置到80MHz或160MHz不影响测试结果
250KHz处有一个间断点;超过此频率即出现较大误差,且脉宽设定也会失效
其余测试数据上的0.02%误差应该是梦源实验室DSView软件浮点数计算导致的系统误差
如果你设定的方波频率过高,ESP8266可能因为固件bug而死机;因为我们禁用了watchdog,它无法自行重启。如果用于生产环境,请务必注意。
参考:

WeMos D1 mini
A 4mbps shiftOut for esp8266/Arduino
ESP8266: Watchdog functions
Reverse engineering of the ESP8266 watchdog timer
本条目发布于2017年10月25日。属于Arduino、C++、ESP8266分类。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值