1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-05-03 20:44:46 +03:00

Merge branch 'master' into master

This commit is contained in:
Earle F. Philhower, III 2020-11-29 12:56:22 -08:00 committed by GitHub
commit 84fdf15fdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1795 additions and 714 deletions

View File

@ -23,6 +23,7 @@ menu.stacksmash=Stack Protection
menu.wipe=Erase Flash
menu.sdk=Espressif FW
menu.ssl=SSL Support
menu.waveform=Waveform Flavour
##############################################################
generic.name=Generic ESP8266 Module
@ -63,6 +64,10 @@ generic.menu.ssl.all=All SSL ciphers (most compatible)
generic.menu.ssl.all.build.sslflags=
generic.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
generic.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
generic.menu.waveform.pwm=Locked PWM
generic.menu.waveform.pwm.build.waveform=
generic.menu.waveform.phase=Locked Phase
generic.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
generic.menu.ResetMethod.nodemcu=dtr (aka nodemcu)
generic.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset
generic.menu.ResetMethod.ck=no dtr (aka ck)
@ -532,6 +537,10 @@ esp8285.menu.ssl.all=All SSL ciphers (most compatible)
esp8285.menu.ssl.all.build.sslflags=
esp8285.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
esp8285.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
esp8285.menu.waveform.pwm=Locked PWM
esp8285.menu.waveform.pwm.build.waveform=
esp8285.menu.waveform.phase=Locked Phase
esp8285.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
esp8285.menu.ResetMethod.nodemcu=dtr (aka nodemcu)
esp8285.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset
esp8285.menu.ResetMethod.ck=no dtr (aka ck)
@ -871,6 +880,10 @@ gen4iod.menu.ssl.all=All SSL ciphers (most compatible)
gen4iod.menu.ssl.all.build.sslflags=
gen4iod.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
gen4iod.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
gen4iod.menu.waveform.pwm=Locked PWM
gen4iod.menu.waveform.pwm.build.waveform=
gen4iod.menu.waveform.phase=Locked Phase
gen4iod.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
gen4iod.upload.resetmethod=--before default_reset --after hard_reset
gen4iod.menu.FlashMode.dout=DOUT (compatible)
gen4iod.menu.FlashMode.dout.build.flash_mode=dout
@ -1125,6 +1138,10 @@ huzzah.menu.ssl.all=All SSL ciphers (most compatible)
huzzah.menu.ssl.all.build.sslflags=
huzzah.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
huzzah.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
huzzah.menu.waveform.pwm=Locked PWM
huzzah.menu.waveform.pwm.build.waveform=
huzzah.menu.waveform.phase=Locked Phase
huzzah.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
huzzah.upload.resetmethod=--before default_reset --after hard_reset
huzzah.build.flash_mode=qio
huzzah.build.flash_flags=-DFLASHMODE_QIO
@ -1312,6 +1329,10 @@ wifi_slot.menu.ssl.all=All SSL ciphers (most compatible)
wifi_slot.menu.ssl.all.build.sslflags=
wifi_slot.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
wifi_slot.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
wifi_slot.menu.waveform.pwm=Locked PWM
wifi_slot.menu.waveform.pwm.build.waveform=
wifi_slot.menu.waveform.phase=Locked Phase
wifi_slot.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
wifi_slot.upload.resetmethod=--before default_reset --after hard_reset
wifi_slot.menu.FlashFreq.40=40MHz
wifi_slot.menu.FlashFreq.40.build.flash_freq=40
@ -1625,6 +1646,10 @@ arduino-esp8266.menu.ssl.all=All SSL ciphers (most compatible)
arduino-esp8266.menu.ssl.all.build.sslflags=
arduino-esp8266.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
arduino-esp8266.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
arduino-esp8266.menu.waveform.pwm=Locked PWM
arduino-esp8266.menu.waveform.pwm.build.waveform=
arduino-esp8266.menu.waveform.phase=Locked Phase
arduino-esp8266.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
arduino-esp8266.upload.resetmethod=--before no_reset --after soft_reset
arduino-esp8266.build.flash_mode=qio
arduino-esp8266.build.flash_flags=-DFLASHMODE_QIO
@ -1813,6 +1838,10 @@ espmxdevkit.menu.ssl.all=All SSL ciphers (most compatible)
espmxdevkit.menu.ssl.all.build.sslflags=
espmxdevkit.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espmxdevkit.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
espmxdevkit.menu.waveform.pwm=Locked PWM
espmxdevkit.menu.waveform.pwm.build.waveform=
espmxdevkit.menu.waveform.phase=Locked Phase
espmxdevkit.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
espmxdevkit.upload.resetmethod=--before default_reset --after hard_reset
espmxdevkit.build.flash_mode=dout
espmxdevkit.build.flash_flags=-DFLASHMODE_DOUT
@ -2041,6 +2070,10 @@ oak.menu.ssl.all=All SSL ciphers (most compatible)
oak.menu.ssl.all.build.sslflags=
oak.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
oak.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
oak.menu.waveform.pwm=Locked PWM
oak.menu.waveform.pwm.build.waveform=
oak.menu.waveform.phase=Locked Phase
oak.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
oak.upload.resetmethod=--before no_reset --after soft_reset
oak.build.flash_mode=dio
oak.build.flash_flags=-DFLASHMODE_DIO
@ -2237,6 +2270,10 @@ espduino.menu.ssl.all=All SSL ciphers (most compatible)
espduino.menu.ssl.all.build.sslflags=
espduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espduino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
espduino.menu.waveform.pwm=Locked PWM
espduino.menu.waveform.pwm.build.waveform=
espduino.menu.waveform.phase=Locked Phase
espduino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
espduino.build.flash_mode=dio
espduino.build.flash_flags=-DFLASHMODE_DIO
espduino.build.flash_freq=40
@ -2423,6 +2460,10 @@ espectro.menu.ssl.all=All SSL ciphers (most compatible)
espectro.menu.ssl.all.build.sslflags=
espectro.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espectro.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
espectro.menu.waveform.pwm=Locked PWM
espectro.menu.waveform.pwm.build.waveform=
espectro.menu.waveform.phase=Locked Phase
espectro.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
espectro.upload.resetmethod=--before default_reset --after hard_reset
espectro.build.flash_mode=dio
espectro.build.flash_flags=-DFLASHMODE_DIO
@ -2610,6 +2651,10 @@ espino.menu.ssl.all=All SSL ciphers (most compatible)
espino.menu.ssl.all.build.sslflags=
espino.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
espino.menu.waveform.pwm=Locked PWM
espino.menu.waveform.pwm.build.waveform=
espino.menu.waveform.phase=Locked Phase
espino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
espino.menu.ResetMethod.nodemcu=dtr (aka nodemcu)
espino.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset
espino.menu.ResetMethod.ck=no dtr (aka ck)
@ -2800,6 +2845,10 @@ espresso_lite_v1.menu.ssl.all=All SSL ciphers (most compatible)
espresso_lite_v1.menu.ssl.all.build.sslflags=
espresso_lite_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espresso_lite_v1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
espresso_lite_v1.menu.waveform.pwm=Locked PWM
espresso_lite_v1.menu.waveform.pwm.build.waveform=
espresso_lite_v1.menu.waveform.phase=Locked Phase
espresso_lite_v1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
espresso_lite_v1.build.flash_mode=dio
espresso_lite_v1.build.flash_flags=-DFLASHMODE_DIO
espresso_lite_v1.build.flash_freq=40
@ -2990,6 +3039,10 @@ espresso_lite_v2.menu.ssl.all=All SSL ciphers (most compatible)
espresso_lite_v2.menu.ssl.all.build.sslflags=
espresso_lite_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espresso_lite_v2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
espresso_lite_v2.menu.waveform.pwm=Locked PWM
espresso_lite_v2.menu.waveform.pwm.build.waveform=
espresso_lite_v2.menu.waveform.phase=Locked Phase
espresso_lite_v2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
espresso_lite_v2.build.flash_mode=dio
espresso_lite_v2.build.flash_flags=-DFLASHMODE_DIO
espresso_lite_v2.build.flash_freq=40
@ -3190,6 +3243,10 @@ sonoff.menu.ssl.all=All SSL ciphers (most compatible)
sonoff.menu.ssl.all.build.sslflags=
sonoff.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
sonoff.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
sonoff.menu.waveform.pwm=Locked PWM
sonoff.menu.waveform.pwm.build.waveform=
sonoff.menu.waveform.phase=Locked Phase
sonoff.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
sonoff.upload.resetmethod=--before no_reset --after soft_reset
sonoff.build.flash_mode=dout
sonoff.build.flash_flags=-DFLASHMODE_DOUT
@ -3417,6 +3474,10 @@ inventone.menu.ssl.all=All SSL ciphers (most compatible)
inventone.menu.ssl.all.build.sslflags=
inventone.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
inventone.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
inventone.menu.waveform.pwm=Locked PWM
inventone.menu.waveform.pwm.build.waveform=
inventone.menu.waveform.phase=Locked Phase
inventone.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
inventone.upload.resetmethod=--before default_reset --after hard_reset
inventone.build.flash_mode=dio
inventone.build.flash_flags=-DFLASHMODE_DIO
@ -3604,6 +3665,10 @@ d1_mini.menu.ssl.all=All SSL ciphers (most compatible)
d1_mini.menu.ssl.all.build.sslflags=
d1_mini.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
d1_mini.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
d1_mini.menu.waveform.pwm=Locked PWM
d1_mini.menu.waveform.pwm.build.waveform=
d1_mini.menu.waveform.phase=Locked Phase
d1_mini.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
d1_mini.upload.resetmethod=--before default_reset --after hard_reset
d1_mini.build.flash_mode=dio
d1_mini.build.flash_flags=-DFLASHMODE_DIO
@ -3791,6 +3856,10 @@ d1_mini_lite.menu.ssl.all=All SSL ciphers (most compatible)
d1_mini_lite.menu.ssl.all.build.sslflags=
d1_mini_lite.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
d1_mini_lite.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
d1_mini_lite.menu.waveform.pwm=Locked PWM
d1_mini_lite.menu.waveform.pwm.build.waveform=
d1_mini_lite.menu.waveform.phase=Locked Phase
d1_mini_lite.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
d1_mini_lite.upload.resetmethod=--before default_reset --after hard_reset
d1_mini_lite.build.flash_mode=dout
d1_mini_lite.build.flash_flags=-DFLASHMODE_DOUT
@ -4018,6 +4087,10 @@ d1_mini_pro.menu.ssl.all=All SSL ciphers (most compatible)
d1_mini_pro.menu.ssl.all.build.sslflags=
d1_mini_pro.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
d1_mini_pro.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
d1_mini_pro.menu.waveform.pwm=Locked PWM
d1_mini_pro.menu.waveform.pwm.build.waveform=
d1_mini_pro.menu.waveform.phase=Locked Phase
d1_mini_pro.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
d1_mini_pro.upload.resetmethod=--before default_reset --after hard_reset
d1_mini_pro.build.flash_mode=dio
d1_mini_pro.build.flash_flags=-DFLASHMODE_DIO
@ -4188,6 +4261,10 @@ d1.menu.ssl.all=All SSL ciphers (most compatible)
d1.menu.ssl.all.build.sslflags=
d1.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
d1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
d1.menu.waveform.pwm=Locked PWM
d1.menu.waveform.pwm.build.waveform=
d1.menu.waveform.phase=Locked Phase
d1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
d1.upload.resetmethod=--before default_reset --after hard_reset
d1.build.flash_mode=dio
d1.build.flash_flags=-DFLASHMODE_DIO
@ -4375,6 +4452,10 @@ nodemcu.menu.ssl.all=All SSL ciphers (most compatible)
nodemcu.menu.ssl.all.build.sslflags=
nodemcu.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
nodemcu.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
nodemcu.menu.waveform.pwm=Locked PWM
nodemcu.menu.waveform.pwm.build.waveform=
nodemcu.menu.waveform.phase=Locked Phase
nodemcu.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
nodemcu.upload.resetmethod=--before default_reset --after hard_reset
nodemcu.build.flash_mode=qio
nodemcu.build.flash_flags=-DFLASHMODE_QIO
@ -4562,6 +4643,10 @@ nodemcuv2.menu.ssl.all=All SSL ciphers (most compatible)
nodemcuv2.menu.ssl.all.build.sslflags=
nodemcuv2.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
nodemcuv2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
nodemcuv2.menu.waveform.pwm=Locked PWM
nodemcuv2.menu.waveform.pwm.build.waveform=
nodemcuv2.menu.waveform.phase=Locked Phase
nodemcuv2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
nodemcuv2.upload.resetmethod=--before default_reset --after hard_reset
nodemcuv2.build.flash_mode=dio
nodemcuv2.build.flash_flags=-DFLASHMODE_DIO
@ -4753,6 +4838,10 @@ modwifi.menu.ssl.all=All SSL ciphers (most compatible)
modwifi.menu.ssl.all.build.sslflags=
modwifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
modwifi.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
modwifi.menu.waveform.pwm=Locked PWM
modwifi.menu.waveform.pwm.build.waveform=
modwifi.menu.waveform.phase=Locked Phase
modwifi.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
modwifi.upload.resetmethod=--before no_reset --after soft_reset
modwifi.build.flash_mode=qio
modwifi.build.flash_flags=-DFLASHMODE_QIO
@ -4960,6 +5049,10 @@ phoenix_v1.menu.ssl.all=All SSL ciphers (most compatible)
phoenix_v1.menu.ssl.all.build.sslflags=
phoenix_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
phoenix_v1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
phoenix_v1.menu.waveform.pwm=Locked PWM
phoenix_v1.menu.waveform.pwm.build.waveform=
phoenix_v1.menu.waveform.phase=Locked Phase
phoenix_v1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
phoenix_v1.build.flash_mode=dio
phoenix_v1.build.flash_flags=-DFLASHMODE_DIO
phoenix_v1.build.flash_freq=40
@ -5150,6 +5243,10 @@ phoenix_v2.menu.ssl.all=All SSL ciphers (most compatible)
phoenix_v2.menu.ssl.all.build.sslflags=
phoenix_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
phoenix_v2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
phoenix_v2.menu.waveform.pwm=Locked PWM
phoenix_v2.menu.waveform.pwm.build.waveform=
phoenix_v2.menu.waveform.phase=Locked Phase
phoenix_v2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
phoenix_v2.build.flash_mode=dio
phoenix_v2.build.flash_flags=-DFLASHMODE_DIO
phoenix_v2.build.flash_freq=40
@ -5340,6 +5437,10 @@ eduinowifi.menu.ssl.all=All SSL ciphers (most compatible)
eduinowifi.menu.ssl.all.build.sslflags=
eduinowifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
eduinowifi.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
eduinowifi.menu.waveform.pwm=Locked PWM
eduinowifi.menu.waveform.pwm.build.waveform=
eduinowifi.menu.waveform.phase=Locked Phase
eduinowifi.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
eduinowifi.upload.resetmethod=--before default_reset --after hard_reset
eduinowifi.build.flash_mode=dio
eduinowifi.build.flash_flags=-DFLASHMODE_DIO
@ -5527,6 +5628,10 @@ wiolink.menu.ssl.all=All SSL ciphers (most compatible)
wiolink.menu.ssl.all.build.sslflags=
wiolink.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
wiolink.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
wiolink.menu.waveform.pwm=Locked PWM
wiolink.menu.waveform.pwm.build.waveform=
wiolink.menu.waveform.phase=Locked Phase
wiolink.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
wiolink.upload.resetmethod=--before default_reset --after hard_reset
wiolink.build.flash_mode=qio
wiolink.build.flash_flags=-DFLASHMODE_QIO
@ -5714,6 +5819,10 @@ blynk.menu.ssl.all=All SSL ciphers (most compatible)
blynk.menu.ssl.all.build.sslflags=
blynk.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
blynk.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
blynk.menu.waveform.pwm=Locked PWM
blynk.menu.waveform.pwm.build.waveform=
blynk.menu.waveform.phase=Locked Phase
blynk.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
blynk.upload.resetmethod=--before default_reset --after hard_reset
blynk.build.flash_mode=qio
blynk.build.flash_flags=-DFLASHMODE_QIO
@ -5901,6 +6010,10 @@ thing.menu.ssl.all=All SSL ciphers (most compatible)
thing.menu.ssl.all.build.sslflags=
thing.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
thing.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
thing.menu.waveform.pwm=Locked PWM
thing.menu.waveform.pwm.build.waveform=
thing.menu.waveform.phase=Locked Phase
thing.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
thing.upload.resetmethod=--before no_reset --after soft_reset
thing.build.flash_mode=qio
thing.build.flash_flags=-DFLASHMODE_QIO
@ -6088,6 +6201,10 @@ thingdev.menu.ssl.all=All SSL ciphers (most compatible)
thingdev.menu.ssl.all.build.sslflags=
thingdev.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
thingdev.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
thingdev.menu.waveform.pwm=Locked PWM
thingdev.menu.waveform.pwm.build.waveform=
thingdev.menu.waveform.phase=Locked Phase
thingdev.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
thingdev.upload.resetmethod=--before default_reset --after hard_reset
thingdev.build.flash_mode=dio
thingdev.build.flash_flags=-DFLASHMODE_DIO
@ -6275,6 +6392,10 @@ esp210.menu.ssl.all=All SSL ciphers (most compatible)
esp210.menu.ssl.all.build.sslflags=
esp210.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
esp210.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
esp210.menu.waveform.pwm=Locked PWM
esp210.menu.waveform.pwm.build.waveform=
esp210.menu.waveform.phase=Locked Phase
esp210.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
esp210.upload.resetmethod=--before no_reset --after soft_reset
esp210.build.flash_mode=qio
esp210.build.flash_flags=-DFLASHMODE_QIO
@ -6462,6 +6583,10 @@ espinotee.menu.ssl.all=All SSL ciphers (most compatible)
espinotee.menu.ssl.all.build.sslflags=
espinotee.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espinotee.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
espinotee.menu.waveform.pwm=Locked PWM
espinotee.menu.waveform.pwm.build.waveform=
espinotee.menu.waveform.phase=Locked Phase
espinotee.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
espinotee.upload.resetmethod=--before default_reset --after hard_reset
espinotee.build.flash_mode=qio
espinotee.build.flash_flags=-DFLASHMODE_QIO
@ -6649,6 +6774,10 @@ wifiduino.menu.ssl.all=All SSL ciphers (most compatible)
wifiduino.menu.ssl.all.build.sslflags=
wifiduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
wifiduino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
wifiduino.menu.waveform.pwm=Locked PWM
wifiduino.menu.waveform.pwm.build.waveform=
wifiduino.menu.waveform.phase=Locked Phase
wifiduino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
wifiduino.upload.resetmethod=--before default_reset --after hard_reset
wifiduino.build.flash_mode=dio
wifiduino.build.flash_flags=-DFLASHMODE_DIO
@ -6853,6 +6982,10 @@ wifinfo.menu.ssl.all=All SSL ciphers (most compatible)
wifinfo.menu.ssl.all.build.sslflags=
wifinfo.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
wifinfo.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
wifinfo.menu.waveform.pwm=Locked PWM
wifinfo.menu.waveform.pwm.build.waveform=
wifinfo.menu.waveform.phase=Locked Phase
wifinfo.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
wifinfo.upload.resetmethod=--before default_reset --after hard_reset
wifinfo.build.flash_mode=qio
wifinfo.build.flash_flags=-DFLASHMODE_QIO
@ -7087,6 +7220,10 @@ cw01.menu.ssl.all=All SSL ciphers (most compatible)
cw01.menu.ssl.all.build.sslflags=
cw01.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
cw01.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC
cw01.menu.waveform.pwm=Locked PWM
cw01.menu.waveform.pwm.build.waveform=
cw01.menu.waveform.phase=Locked Phase
cw01.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE
cw01.upload.resetmethod=--before default_reset --after hard_reset
cw01.menu.CrystalFreq.26=26 MHz
cw01.menu.CrystalFreq.40=40 MHz

View File

@ -102,6 +102,7 @@ bool schedule_function(const std::function<void(void)>& fn)
return true;
}
IRAM_ATTR // (not only) called from ISR
bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
uint32_t repeat_us, const std::function<bool(void)>& alarm)
{

View File

@ -25,15 +25,18 @@
#include "core_esp8266_waveform.h"
#include "user_interface.h"
// Which pins have a tone running on them?
static uint32_t _toneMap = 0;
static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) {
if (_pin > 16) {
return;
}
#ifndef WAVEFORM_LOCKED_PHASE
// Stop any analogWrites (PWM) because they are a different generator
_stopPWM(_pin);
#endif
// If there's another Tone or startWaveform on this pin
// it will be changed on-the-fly (no need to stop it)
pinMode(_pin, OUTPUT);
high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency,
@ -42,9 +45,7 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat
duration = microsecondsToClockCycles(duration * 1000UL);
duration += high + low - 1;
duration -= duration % (high + low);
if (startWaveformClockCycles(_pin, high, low, duration)) {
_toneMap |= 1 << _pin;
}
startWaveformClockCycles(_pin, high, low, duration);
}
@ -86,6 +87,5 @@ void noTone(uint8_t _pin) {
return;
}
stopWaveform(_pin);
_toneMap &= ~(1 << _pin);
digitalWrite(_pin, 0);
}

View File

@ -32,7 +32,7 @@
String::String(const char *cstr) {
init();
if (cstr)
copy(cstr, strlen(cstr));
copy(cstr, strlen_P(cstr));
}
String::String(const String &value) {
@ -55,14 +55,6 @@ String::String(StringSumHelper &&rval) noexcept {
move(rval);
}
String::String(char c) {
init();
char buf[2];
buf[0] = c;
buf[1] = 0;
*this = buf;
}
String::String(unsigned char value, unsigned char base) {
init();
char buf[1 + 8 * sizeof(unsigned char)];
@ -91,7 +83,7 @@ String::String(unsigned int value, unsigned char base) {
String::String(long value, unsigned char base) {
init();
char buf[2 + 8 * sizeof(long)];
if (base==10) {
if (base == 10) {
sprintf(buf, "%ld", value);
} else {
ltoa(value, buf, base);
@ -118,31 +110,21 @@ String::String(double value, unsigned char decimalPlaces) {
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
}
String::~String() {
invalidate();
}
/*********************************************/
/* Memory Management */
/*********************************************/
inline void String::init(void) {
setSSO(true);
setLen(0);
wbuffer()[0] = 0;
}
void String::invalidate(void) {
if(!isSSO() && wbuffer())
if (!isSSO() && wbuffer())
free(wbuffer());
init();
}
unsigned char String::reserve(unsigned int size) {
if(buffer() && capacity() >= size)
if (buffer() && capacity() >= size)
return 1;
if(changeBuffer(size)) {
if(len() == 0)
if (changeBuffer(size)) {
if (len() == 0)
wbuffer()[0] = 0;
return 1;
}
@ -157,35 +139,32 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
uint16_t oldLen = len();
setSSO(true);
setLen(oldLen);
return 1;
} else { // if bufptr && !isSSO()
// Using bufptr, need to shrink into sso.buff
char temp[sizeof(sso.buff)];
memcpy(temp, buffer(), maxStrLen);
free(wbuffer());
const char *temp = buffer();
uint16_t oldLen = len();
setSSO(true);
setLen(oldLen);
memcpy(wbuffer(), temp, maxStrLen);
return 1;
free((void *)temp);
}
return 1;
}
// Fallthrough to normal allocator
size_t newSize = (maxStrLen + 16) & (~0xf);
// Make sure we can fit newsize in the buffer
if (newSize > CAPACITY_MAX) {
return false;
return 0;
}
uint16_t oldLen = len();
char *newbuffer = (char *) realloc(isSSO() ? nullptr : wbuffer(), newSize);
char *newbuffer = (char *)realloc(isSSO() ? nullptr : wbuffer(), newSize);
if (newbuffer) {
size_t oldSize = capacity() + 1; // include NULL.
if (isSSO()) {
// Copy the SSO buffer into allocated space
memmove_P(newbuffer, sso.buff, sizeof(sso.buff));
}
if (newSize > oldSize)
{
if (newSize > oldSize) {
memset(newbuffer + oldSize, 0, newSize - oldSize);
}
setSSO(false);
@ -201,7 +180,7 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
/* Copy and Move */
/*********************************************/
String & String::copy(const char *cstr, unsigned int length) {
String &String::copy(const char *cstr, unsigned int length) {
if (!reserve(length)) {
invalidate();
return *this;
@ -211,7 +190,7 @@ String & String::copy(const char *cstr, unsigned int length) {
return *this;
}
String & String::copy(const __FlashStringHelper *pstr, unsigned int length) {
String &String::copy(const __FlashStringHelper *pstr, unsigned int length) {
if (!reserve(length)) {
invalidate();
return *this;
@ -227,44 +206,35 @@ void String::move(String &rhs) noexcept {
rhs.init();
}
String & String::operator =(const String &rhs) {
String &String::operator =(const String &rhs) {
if (this == &rhs)
return *this;
if (rhs.buffer())
copy(rhs.buffer(), rhs.len());
else
invalidate();
return *this;
}
String & String::operator =(String &&rval) noexcept {
String &String::operator =(String &&rval) noexcept {
if (this != &rval)
move(rval);
return *this;
}
String & String::operator =(StringSumHelper &&rval) noexcept {
if (this != &rval)
move(rval);
return *this;
}
String & String::operator =(const char *cstr) {
String &String::operator =(const char *cstr) {
if (cstr)
copy(cstr, strlen(cstr));
else
invalidate();
return *this;
}
String & String::operator = (const __FlashStringHelper *pstr)
{
if (pstr) copy(pstr, strlen_P((PGM_P)pstr));
else invalidate();
String &String::operator =(const __FlashStringHelper *pstr) {
if (pstr)
copy(pstr, strlen_P((PGM_P)pstr));
else
invalidate();
return *this;
}
@ -285,7 +255,7 @@ unsigned char String::concat(const String &s) {
return 0;
memmove_P(wbuffer() + len(), buffer(), len());
setLen(newlen);
wbuffer()[len()] = 0;
wbuffer()[newlen] = 0;
return 1;
} else {
return concat(s.buffer(), s.len());
@ -313,22 +283,17 @@ unsigned char String::concat(const char *cstr) {
}
unsigned char String::concat(char c) {
char buf[2];
buf[0] = c;
buf[1] = 0;
return concat(buf, 1);
return concat(&c, 1);
}
unsigned char String::concat(unsigned char num) {
char buf[1 + 3 * sizeof(unsigned char)];
sprintf(buf, "%d", num);
return concat(buf, strlen(buf));
return concat(buf, sprintf(buf, "%d", num));
}
unsigned char String::concat(int num) {
char buf[2 + 3 * sizeof(int)];
sprintf(buf, "%d", num);
return concat(buf, strlen(buf));
return concat(buf, sprintf(buf, "%d", num));
}
unsigned char String::concat(unsigned int num) {
@ -339,8 +304,7 @@ unsigned char String::concat(unsigned int num) {
unsigned char String::concat(long num) {
char buf[2 + 3 * sizeof(long)];
sprintf(buf, "%ld", num);
return concat(buf, strlen(buf));
return concat(buf, sprintf(buf, "%ld", num));
}
unsigned char String::concat(unsigned long num) {
@ -351,22 +315,25 @@ unsigned char String::concat(unsigned long num) {
unsigned char String::concat(float num) {
char buf[20];
char* string = dtostrf(num, 4, 2, buf);
char *string = dtostrf(num, 4, 2, buf);
return concat(string, strlen(string));
}
unsigned char String::concat(double num) {
char buf[20];
char* string = dtostrf(num, 4, 2, buf);
char *string = dtostrf(num, 4, 2, buf);
return concat(string, strlen(string));
}
unsigned char String::concat(const __FlashStringHelper * str) {
if (!str) return 0;
unsigned char String::concat(const __FlashStringHelper *str) {
if (!str)
return 0;
int length = strlen_P((PGM_P)str);
if (length == 0) return 1;
if (length == 0)
return 1;
unsigned int newlen = len() + length;
if (!reserve(newlen)) return 0;
if (!reserve(newlen))
return 0;
memcpy_P(wbuffer() + len(), (PGM_P)str, length + 1);
setLen(newlen);
return 1;
@ -376,79 +343,78 @@ unsigned char String::concat(const __FlashStringHelper * str) {
/* Concatenate */
/*********************************************/
StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(rhs.buffer(), rhs.len()))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!cstr || !a.concat(cstr, strlen(cstr)))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, char c) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, char c) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(c))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, int num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, int num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, long num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, float num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, float num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, double num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, double num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs)
{
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(rhs))
a.invalidate();
return a;
@ -459,11 +425,11 @@ StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHel
/*********************************************/
int String::compareTo(const String &s) const {
if(!buffer() || !s.buffer()) {
if(s.buffer() && s.len() > 0)
return 0 - *(unsigned char *) s.buffer();
if(buffer() && len() > 0)
return *(unsigned char *) buffer();
if (!buffer() || !s.buffer()) {
if (s.buffer() && s.len() > 0)
return 0 - *(unsigned char *)s.buffer();
if (buffer() && len() > 0)
return *(unsigned char *)buffer();
return 0;
}
return strcmp(buffer(), s.buffer());
@ -521,7 +487,7 @@ unsigned char String::equalsConstantTime(const String &s2) const {
//at this point lengths are the same
if (len() == 0)
return 1;
//at this point lenghts are the same and non-zero
//at this point lengths are the same and non-zero
const char *p1 = buffer();
const char *p2 = s2.buffer();
unsigned int equalchars = 0;
@ -541,19 +507,19 @@ unsigned char String::equalsConstantTime(const String &s2) const {
}
unsigned char String::startsWith(const String &s2) const {
if(len() < s2.len())
if (len() < s2.len())
return 0;
return startsWith(s2, 0);
}
unsigned char String::startsWith(const String &s2, unsigned int offset) const {
if(offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer())
if (offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer())
return 0;
return strncmp(&buffer()[offset], s2.buffer(), s2.len()) == 0;
}
unsigned char String::endsWith(const String &s2) const {
if(len() < s2.len() || !buffer() || !s2.buffer())
if (len() < s2.len() || !buffer() || !s2.buffer())
return 0;
return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0;
}
@ -562,16 +528,12 @@ unsigned char String::endsWith(const String &s2) const {
/* Character Access */
/*********************************************/
char String::charAt(unsigned int loc) const {
return operator[](loc);
}
void String::setCharAt(unsigned int loc, char c) {
if (loc < len())
wbuffer()[loc] = c;
}
char & String::operator[](unsigned int index) {
char &String::operator[](unsigned int index) {
static char dummy_writable_char;
if (index >= len() || !buffer()) {
dummy_writable_char = 0;
@ -596,7 +558,7 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind
unsigned int n = bufsize - 1;
if (n > len() - index)
n = len() - index;
strncpy((char *) buf, buffer() + index, n);
strncpy((char *)buf, buffer() + index, n);
buf[n] = 0;
}
@ -604,31 +566,15 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind
/* Search */
/*********************************************/
int String::indexOf(char c) const {
return indexOf(c, 0);
}
int String::indexOf(char ch, unsigned int fromIndex) const {
if (fromIndex >= len())
return -1;
const char* temp = strchr(buffer() + fromIndex, ch);
const char *temp = strchr(buffer() + fromIndex, ch);
if (temp == NULL)
return -1;
return temp - buffer();
}
int String::indexOf(const __FlashStringHelper *s2) const {
return indexOf(s2, 0);
}
int String::indexOf(const __FlashStringHelper *s2, unsigned int fromIndex) const {
return indexOf((const char*) s2, fromIndex);
}
int String::indexOf(const char *s2) const {
return indexOf(s2, 0);
}
int String::indexOf(const char *s2, unsigned int fromIndex) const {
if (fromIndex >= len())
return -1;
@ -638,28 +584,25 @@ int String::indexOf(const char *s2, unsigned int fromIndex) const {
return found - buffer();
}
int String::indexOf(const String &s2) const {
return indexOf(s2, 0);
}
int String::indexOf(const String &s2, unsigned int fromIndex) const {
return indexOf(s2.c_str(), fromIndex);
}
int String::lastIndexOf(char theChar) const {
return lastIndexOf(theChar, len() - 1);
int String::lastIndexOf(char ch) const {
return lastIndexOf(ch, len() - 1);
}
int String::lastIndexOf(char ch, unsigned int fromIndex) const {
if (fromIndex >= len())
return -1;
char tempchar = buffer()[fromIndex + 1];
wbuffer()[fromIndex + 1] = '\0';
char* temp = strrchr(wbuffer(), ch);
wbuffer()[fromIndex + 1] = tempchar;
char *writeTo = wbuffer();
char tempchar = writeTo[fromIndex + 1]; // save the replaced character
writeTo[fromIndex + 1] = '\0';
char *temp = strrchr(writeTo, ch);
writeTo[fromIndex + 1] = tempchar; // restore character
if (temp == NULL)
return -1;
return temp - buffer();
return temp - writeTo;
}
int String::lastIndexOf(const String &s2) const {
@ -672,11 +615,11 @@ int String::lastIndexOf(const String &s2, unsigned int fromIndex) const {
if (fromIndex >= len())
fromIndex = len() - 1;
int found = -1;
for (char *p = wbuffer(); p <= wbuffer() + fromIndex; p++) {
for (const char *p = buffer(); p <= buffer() + fromIndex; p++) {
p = strstr(p, s2.buffer());
if (!p)
break;
if ((unsigned int) (p - wbuffer()) <= fromIndex)
if ((unsigned int)(p - buffer()) <= fromIndex)
found = p - buffer();
}
return found;
@ -693,10 +636,11 @@ String String::substring(unsigned int left, unsigned int right) const {
return out;
if (right > len())
right = len();
char temp = buffer()[right]; // save the replaced character
wbuffer()[right] = '\0';
out = wbuffer() + left; // pointer arithmetic
wbuffer()[right] = temp; //restore character
char *writeTo = wbuffer();
char tempchar = writeTo[right]; // save the replaced character
writeTo[right] = '\0';
out = writeTo + left; // pointer arithmetic
writeTo[right] = tempchar; // restore character
return out;
}
@ -713,7 +657,7 @@ void String::replace(char find, char replace) {
}
}
void String::replace(const String& find, const String& replace) {
void String::replace(const String &find, const String &replace) {
if (len() == 0 || find.len() == 0)
return;
int diff = replace.len() - find.len();
@ -735,7 +679,7 @@ void String::replace(const String& find, const String& replace) {
readFrom = foundAt + find.len();
setLen(len() + diff);
}
memmove_P(writeTo, readFrom, strlen(readFrom)+1);
memmove_P(writeTo, readFrom, strlen(readFrom) + 1);
} else {
unsigned int size = len(); // compute size needed for result
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
@ -759,13 +703,6 @@ void String::replace(const String& find, const String& replace) {
}
}
void String::remove(unsigned int index) {
// Pass the biggest integer as the count. The remove method
// below will take care of truncating it at the end of the
// string.
remove(index, (unsigned int) -1);
}
void String::remove(unsigned int index, unsigned int count) {
if (index >= len()) {
return;
@ -828,11 +765,10 @@ long String::toInt(void) const {
float String::toFloat(void) const {
if (buffer())
return atof(buffer());
return 0;
return 0.0F;
}
double String::toDouble(void) const
{
double String::toDouble(void) const {
if (buffer())
return atof(buffer());
return 0.0;

View File

@ -53,7 +53,7 @@ class String {
// if the initial value is null or invalid, or if memory allocation
// fails, the string will be marked as invalid (i.e. "if (s)" will
// be false).
String() {
String() __attribute__((always_inline)) { // See init()
init();
}
String(const char *cstr);
@ -61,7 +61,12 @@ class String {
String(const __FlashStringHelper *str);
String(String &&rval) noexcept;
String(StringSumHelper &&rval) noexcept;
explicit String(char c);
explicit String(char c) {
sso.buff[0] = c;
sso.buff[1] = 0;
sso.len = 1;
sso.isHeap = 0;
}
explicit String(unsigned char, unsigned char base = 10);
explicit String(int, unsigned char base = 10);
explicit String(unsigned int, unsigned char base = 10);
@ -69,35 +74,35 @@ class String {
explicit String(unsigned long, unsigned char base = 10);
explicit String(float, unsigned char decimalPlaces = 2);
explicit String(double, unsigned char decimalPlaces = 2);
~String(void);
~String() {
invalidate();
}
// memory management
// return true on success, false on failure (in which case, the string
// is left unchanged). reserve(0), if successful, will validate an
// invalid string (i.e., "if (s)" will be true afterwards)
unsigned char reserve(unsigned int size);
inline unsigned int length(void) const {
if(buffer()) {
return len();
} else {
return 0;
}
unsigned int length(void) const {
return buffer() ? len() : 0;
}
inline void clear(void) {
void clear(void) {
setLen(0);
}
inline bool isEmpty(void) const {
bool isEmpty(void) const {
return length() == 0;
}
// creates a copy of the assigned value. if the value is null or
// invalid, or if the memory allocation fails, the string will be
// marked as invalid ("if (s)" will be false).
String & operator =(const String &rhs);
String & operator =(const char *cstr);
String & operator = (const __FlashStringHelper *str);
String & operator =(String &&rval) noexcept;
String & operator =(StringSumHelper &&rval) noexcept;
String &operator =(const String &rhs);
String &operator =(const char *cstr);
String &operator =(const __FlashStringHelper *str);
String &operator =(String &&rval) noexcept;
String &operator =(StringSumHelper &&rval) noexcept {
return operator =((String &&)rval);
}
// concatenate (works w/ built-in types)
@ -114,67 +119,67 @@ class String {
unsigned char concat(unsigned long num);
unsigned char concat(float num);
unsigned char concat(double num);
unsigned char concat(const __FlashStringHelper * str);
unsigned char concat(const __FlashStringHelper *str);
unsigned char concat(const char *cstr, unsigned int length);
// if there's not enough memory for the concatenated value, the string
// will be left unchanged (but this isn't signalled in any way)
String & operator +=(const String &rhs) {
String &operator +=(const String &rhs) {
concat(rhs);
return (*this);
return *this;
}
String & operator +=(const char *cstr) {
String &operator +=(const char *cstr) {
concat(cstr);
return (*this);
return *this;
}
String & operator +=(char c) {
String &operator +=(char c) {
concat(c);
return (*this);
return *this;
}
String & operator +=(unsigned char num) {
String &operator +=(unsigned char num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(int num) {
String &operator +=(int num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(unsigned int num) {
String &operator +=(unsigned int num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(long num) {
String &operator +=(long num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(unsigned long num) {
String &operator +=(unsigned long num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(float num) {
String &operator +=(float num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(double num) {
String &operator +=(double num) {
concat(num);
return (*this);
return *this;
}
String & operator += (const __FlashStringHelper *str){
String &operator +=(const __FlashStringHelper *str) {
concat(str);
return (*this);
return *this;
}
friend StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs);
friend StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr);
friend StringSumHelper & operator +(const StringSumHelper &lhs, char c);
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, int num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, long num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, float num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, double num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs);
friend StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr);
friend StringSumHelper &operator +(const StringSumHelper &lhs, char c);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, int num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, float num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, double num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
// comparison (only works w/ Strings and "strings")
operator StringIfHelperType() const {
@ -202,45 +207,45 @@ class String {
unsigned char equalsIgnoreCase(const String &s) const;
unsigned char equalsConstantTime(const String &s) const;
unsigned char startsWith(const String &prefix) const;
unsigned char startsWith(const char * prefix) const {
unsigned char startsWith(const char *prefix) const {
return this->startsWith(String(prefix));
}
unsigned char startsWith(const __FlashStringHelper * prefix) const {
unsigned char startsWith(const __FlashStringHelper *prefix) const {
return this->startsWith(String(prefix));
}
unsigned char startsWith(const String &prefix, unsigned int offset) const;
unsigned char endsWith(const String &suffix) const;
unsigned char endsWith(const char * suffix) const {
unsigned char endsWith(const char *suffix) const {
return this->endsWith(String(suffix));
}
unsigned char endsWith(const __FlashStringHelper * suffix) const {
unsigned char endsWith(const __FlashStringHelper *suffix) const {
return this->endsWith(String(suffix));
}
// character access
char charAt(unsigned int index) const;
char charAt(unsigned int index) const {
return operator [](index);
}
void setCharAt(unsigned int index, char c);
char operator [](unsigned int index) const;
char& operator [](unsigned int index);
char &operator [](unsigned int index);
void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const;
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
getBytes((unsigned char *) buf, bufsize, index);
}
const char* c_str() const { return buffer(); }
char* begin() { return wbuffer(); }
char* end() { return wbuffer() + length(); }
const char* begin() const { return c_str(); }
const char* end() const { return c_str() + length(); }
const char *c_str() const { return buffer(); }
char *begin() { return wbuffer(); }
char *end() { return wbuffer() + length(); }
const char *begin() const { return c_str(); }
const char *end() const { return c_str() + length(); }
// search
int indexOf(char ch) const;
int indexOf(char ch, unsigned int fromIndex) const;
int indexOf(const char *str) const;
int indexOf(const char *str, unsigned int fromIndex) const;
int indexOf(const __FlashStringHelper *str) const;
int indexOf(const __FlashStringHelper *str, unsigned int fromIndex) const;
int indexOf(const String &str) const;
int indexOf(const String &str, unsigned int fromIndex) const;
int indexOf(char ch, unsigned int fromIndex = 0) const;
int indexOf(const char *str, unsigned int fromIndex = 0) const;
int indexOf(const __FlashStringHelper *str, unsigned int fromIndex = 0) const {
return indexOf((const char*)str, fromIndex);
}
int indexOf(const String &str, unsigned int fromIndex = 0) const;
int lastIndexOf(char ch) const;
int lastIndexOf(char ch, unsigned int fromIndex) const;
int lastIndexOf(const String &str) const;
@ -248,29 +253,29 @@ class String {
String substring(unsigned int beginIndex) const {
return substring(beginIndex, len());
}
;
String substring(unsigned int beginIndex, unsigned int endIndex) const;
// modification
void replace(char find, char replace);
void replace(const String& find, const String& replace);
void replace(const char * find, const String& replace) {
void replace(const String &find, const String &replace);
void replace(const char *find, const String &replace) {
this->replace(String(find), replace);
}
void replace(const __FlashStringHelper * find, const String& replace) {
void replace(const __FlashStringHelper *find, const String &replace) {
this->replace(String(find), replace);
}
void replace(const char * find, const char * replace) {
void replace(const char *find, const char *replace) {
this->replace(String(find), String(replace));
}
void replace(const __FlashStringHelper * find, const char * replace) {
void replace(const __FlashStringHelper *find, const char *replace) {
this->replace(String(find), String(replace));
}
void replace(const __FlashStringHelper * find, const __FlashStringHelper * replace) {
void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) {
this->replace(String(find), String(replace));
}
void remove(unsigned int index);
void remove(unsigned int index, unsigned int count);
// Pass the biggest integer if the count is not specified.
// The remove method below will take care of truncating it at the end of the string.
void remove(unsigned int index, unsigned int count = (unsigned int)-1);
void toLowerCase(void);
void toUpperCase(void);
void trim(void);
@ -278,7 +283,7 @@ class String {
// parsing/conversion
long toInt(void) const;
float toFloat(void) const;
double toDouble(void) const;
double toDouble(void) const;
protected:
// Contains the string info when we're not in SSO mode
@ -291,8 +296,8 @@ class String {
enum { SSOSIZE = sizeof(struct _ptr) + 4 - 1 }; // Characters to allocate space for SSO, must be 12 or more
struct _sso {
char buff[SSOSIZE];
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
unsigned char isSSO : 1;
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
unsigned char isHeap : 1;
} __attribute__((packed)); // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues
enum { CAPACITY_MAX = 65535 }; // If typeof(cap) changed from uint16_t, be sure to update this enum to the max value storable in the type
union {
@ -300,25 +305,47 @@ class String {
struct _sso sso;
};
// Accessor functions
inline bool isSSO() const { return sso.isSSO; }
inline unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
inline unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
inline void setSSO(bool set) { sso.isSSO = set; }
inline void setLen(int len) { if (isSSO()) sso.len = len; else ptr.len = len; }
inline void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; }
inline void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; }
bool isSSO() const { return !sso.isHeap; }
unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
void setSSO(bool set) { sso.isHeap = !set; }
void setLen(int len) {
if (isSSO()) {
setSSO(true); // Avoid emitting of bitwise EXTRACT-AND-OR ops (store-merging optimization)
sso.len = len;
} else
ptr.len = len;
}
void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; }
void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; }
// Buffer accessor functions
inline const char *buffer() const { return (const char *)(isSSO() ? sso.buff : ptr.buff); }
inline char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
const char *buffer() const { return wbuffer(); }
char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
protected:
void init(void);
void init(void) __attribute__((always_inline)) {
sso.buff[0] = 0;
sso.len = 0;
sso.isHeap = 0;
// Without the 6 statements shown below, GCC simply emits such as: "MOVI.N aX,0", "S8I aX,a2,0" and "S8I aX,a2,11" (8 bytes in total)
sso.buff[1] = 0;
sso.buff[2] = 0;
sso.buff[3] = 0;
sso.buff[8] = 0;
sso.buff[9] = 0;
sso.buff[10] = 0;
// With the above, thanks to store-merging, GCC can use the narrow form of 32-bit store insn ("S32I.N") and emits:
// "MOVI.N aX,0", "S32I.N aX,a2,0" and "S32I.N aX,a2,8" (6 bytes in total)
// (Literature: Xtensa(R) Instruction Set Reference Manual, "S8I - Store 8-bit" [p.504] and "S32I.N - Narrow Store 32-bit" [p.512])
// Unfortunately, GCC seems not to re-evaluate the cost of inlining after the store-merging optimizer stage,
// `always_inline` attribute is necessary in order to keep inlining.
}
void invalidate(void);
unsigned char changeBuffer(unsigned int maxStrLen);
// copy and move
String & copy(const char *cstr, unsigned int length);
String & copy(const __FlashStringHelper *pstr, unsigned int length);
String &copy(const char *cstr, unsigned int length);
String &copy(const __FlashStringHelper *pstr, unsigned int length);
void move(String &rhs) noexcept;
};
@ -354,6 +381,9 @@ class StringSumHelper: public String {
StringSumHelper(double num) :
String(num) {
}
StringSumHelper(const __FlashStringHelper *s) :
String(s) {
}
};
extern const String emptyString;

View File

@ -1,312 +0,0 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include "ets_sys.h"
#include "core_esp8266_waveform.h"
extern "C" {
// Maximum delay between IRQs
#define MAXIRQUS (10000)
// Set/clear GPIO 0-15 by bitmask
#define SetGPIO(a) do { GPOS = a; } while (0)
#define ClearGPIO(a) do { GPOC = a; } while (0)
// Waveform generator can create tones, PWM, and servos
typedef struct {
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
uint32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform
uint32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform
} Waveform;
static Waveform waveform[17]; // State of all possible pins
static volatile uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
static volatile uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
static uint32_t (*timer1CB)() = NULL;
// Non-speed critical bits
#pragma GCC optimize ("Os")
static inline ICACHE_RAM_ATTR uint32_t GetCycleCount() {
uint32_t ccount;
__asm__ __volatile__("esync; rsr %0,ccount":"=a"(ccount));
return ccount;
}
// Interrupt on/off control
static ICACHE_RAM_ATTR void timer1Interrupt();
static bool timerRunning = false;
static void initTimer() {
timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timerRunning = true;
}
static void ICACHE_RAM_ATTR deinitTimer() {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
timer1_isr_init();
timerRunning = false;
}
// Set a callback. Pass in NULL to stop it
void setTimer1Callback(uint32_t (*fn)()) {
timer1CB = fn;
if (!timerRunning && fn) {
initTimer();
timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste
} else if (timerRunning && !fn && !waveformEnabled) {
deinitTimer();
}
}
// Start up a waveform on a pin, or change the current one. Will change to the new
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) {
return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS));
}
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) {
if ((pin > 16) || isFlashInterfacePin(pin)) {
return false;
}
Waveform *wave = &waveform[pin];
// Adjust to shave off some of the IRQ time, approximately
wave->nextTimeHighCycles = timeHighCycles;
wave->nextTimeLowCycles = timeLowCycles;
wave->expiryCycle = runTimeCycles ? GetCycleCount() + runTimeCycles : 0;
if (runTimeCycles && !wave->expiryCycle) {
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
}
uint32_t mask = 1<<pin;
if (!(waveformEnabled & mask)) {
// Actually set the pin high or low in the IRQ service to guarantee times
wave->nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(1);
waveformToEnable |= mask;
if (!timerRunning) {
initTimer();
timer1_write(microsecondsToClockCycles(10));
} else {
// Ensure timely service....
if (T1L > microsecondsToClockCycles(10)) {
timer1_write(microsecondsToClockCycles(10));
}
}
while (waveformToEnable) {
delay(0); // Wait for waveform to update
}
}
return true;
}
// Speed critical bits
#pragma GCC optimize ("O2")
// Normally would not want two copies like this, but due to different
// optimization levels the inline attribute gets lost if we try the
// other version.
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
return ccount;
}
static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) {
if (a < b) {
return a;
}
return b;
}
// Stops a waveform on a pin
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!timerRunning) {
return false;
}
// If user sends in a pin >16 but <32, this will always point to a 0 bit
// If they send >=32, then the shift will result in 0 and it will also return false
if (waveformEnabled & (1UL << pin)) {
waveformToDisable = 1UL << pin;
// Must not interfere if Timer is due shortly
if (T1L > microsecondsToClockCycles(10)) {
timer1_write(microsecondsToClockCycles(10));
}
while (waveformToDisable) {
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
}
}
if (!waveformEnabled && !timer1CB) {
deinitTimer();
}
return true;
}
// The SDK and hardware take some time to actually get to our NMI code, so
// decrement the next IRQ's timer value by a bit so we can actually catch the
// real CPU cycle counter we want for the waveforms.
#if F_CPU == 80000000
#define DELTAIRQ (microsecondsToClockCycles(3))
#else
#define DELTAIRQ (microsecondsToClockCycles(2))
#endif
static ICACHE_RAM_ATTR void timer1Interrupt() {
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
// are generating. In the common case (1 PWM) these may be the same pin and
// we can avoid looking at the other pins.
static int startPin = 0;
static int endPin = 0;
uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
if (waveformToEnable || waveformToDisable) {
// Handle enable/disable requests from main app.
waveformEnabled = (waveformEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off
waveformState &= ~waveformToEnable; // And clear the state of any just started
waveformToEnable = 0;
waveformToDisable = 0;
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
startPin = __builtin_ffs(waveformEnabled) - 1;
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
endPin = 32 - __builtin_clz(waveformEnabled);
}
bool done = false;
if (waveformEnabled) {
do {
nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
for (int i = startPin; i <= endPin; i++) {
uint32_t mask = 1<<i;
// If it's not on, ignore!
if (!(waveformEnabled & mask)) {
continue;
}
Waveform *wave = &waveform[i];
uint32_t now = GetCycleCountIRQ();
// Disable any waveforms that are done
if (wave->expiryCycle) {
int32_t expiryToGo = wave->expiryCycle - now;
if (expiryToGo < 0) {
// Done, remove!
waveformEnabled &= ~mask;
if (i == 16) {
GP16O &= ~1;
} else {
ClearGPIO(mask);
}
continue;
}
}
// Check for toggles
int32_t cyclesToGo = wave->nextServiceCycle - now;
if (cyclesToGo < 0) {
waveformState ^= mask;
if (waveformState & mask) {
if (i == 16) {
GP16O |= 1; // GPIO16 write slow as it's RMW
} else {
SetGPIO(mask);
}
wave->nextServiceCycle = now + wave->nextTimeHighCycles;
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles);
} else {
if (i == 16) {
GP16O &= ~1; // GPIO16 write slow as it's RMW
} else {
ClearGPIO(mask);
}
wave->nextServiceCycle = now + wave->nextTimeLowCycles;
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles);
}
} else {
uint32_t deltaCycles = wave->nextServiceCycle - now;
nextEventCycles = min_u32(nextEventCycles, deltaCycles);
}
}
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
uint32_t now = GetCycleCountIRQ();
int32_t cycleDeltaNextEvent = timeoutCycle - (now + nextEventCycles);
int32_t cyclesLeftTimeout = timeoutCycle - now;
done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0);
} while (!done);
} // if (waveformEnabled)
if (timer1CB) {
nextEventCycles = min_u32(nextEventCycles, timer1CB());
}
if (nextEventCycles < microsecondsToClockCycles(10)) {
nextEventCycles = microsecondsToClockCycles(10);
}
nextEventCycles -= DELTAIRQ;
// Do it here instead of global function to save time and because we know it's edge-IRQ
#if F_CPU == 160000000
T1L = nextEventCycles >> 1; // Already know we're in range by MAXIRQUS
#else
T1L = nextEventCycles; // Already know we're in range by MAXIRQUS
#endif
TEIE |= TEIE1; // Edge int enable
}
};

View File

@ -1,76 +1,7 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#ifndef __ESP8266_WAVEFORM_H
#define __ESP8266_WAVEFORM_H
#ifdef __cplusplus
extern "C" {
#endif
// Start or change a waveform of the specified high and low times on specific pin.
// If runtimeUS > 0 then automatically stop it after that many usecs.
// Returns true or false on success or failure.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS);
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles.
// Returns true or false on success or failure.
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles);
// Stop a waveform, if any, on the specified pin.
// Returns true or false on success or failure.
int stopWaveform(uint8_t pin);
// Add a callback function to be called on *EVERY* timer1 trigger. The
// callback returns the number of microseconds until the next desired call.
// However, since it is called every timer1 interrupt, it may be called
// again before this period. It should therefore use the ESP Cycle Counter
// to determine whether or not to perform an operation.
// Pass in NULL to disable the callback and, if no other waveforms being
// generated, stop the timer as well.
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
void setTimer1Callback(uint32_t (*fn)());
#ifdef __cplusplus
}
#endif
// Wrapper to include both versions of the waveform generator
#ifdef WAVEFORM_LOCKED_PHASE
#include "core_esp8266_waveform_phase.h"
#else
#include "core_esp8266_waveform_pwm.h"
#endif

View File

@ -0,0 +1,440 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
Copyright (c) 2020 Dirk O. Kaar.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
clock cycle time, or an interval measured in clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef WAVEFORM_LOCKED_PHASE
#include "core_esp8266_waveform_phase.h"
#include <Arduino.h>
#include "ets_sys.h"
#include <atomic>
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
// Maximum servicing time for any single IRQ
constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
// The latency between in-ISR rearming of the timer and the earliest firing
constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
// The SDK and hardware take some time to actually get to our NMI code
constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3};
// Waveform generator can create tones, PWM, and servos
typedef struct {
uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count
uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
int32_t adjDutyCcys; // Temporary correction for next period
int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
WaveformMode mode;
int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
} Waveform;
namespace {
static struct {
Waveform pins[17]; // State of all possible pins
uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
// Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
uint32_t(*timer1CB)() = nullptr;
bool timer1Running = false;
uint32_t nextEventCcy;
} waveform;
}
// Interrupt on/off control
static ICACHE_RAM_ATTR void timer1Interrupt();
// Non-speed critical bits
#pragma GCC optimize ("Os")
static void initTimer() {
timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
waveform.timer1Running = true;
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
}
static void ICACHE_RAM_ATTR deinitTimer() {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
timer1_isr_init();
waveform.timer1Running = false;
}
extern "C" {
// Set a callback. Pass in NULL to stop it
void setTimer1Callback(uint32_t (*fn)()) {
waveform.timer1CB = fn;
std::atomic_thread_fence(std::memory_order_acq_rel);
if (!waveform.timer1Running && fn) {
initTimer();
} else if (waveform.timer1Running && !fn && !waveform.enabled) {
deinitTimer();
}
}
int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS,
uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
return startWaveformClockCycles(pin,
microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS),
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
}
// Start up a waveform on a pin, or change the current one. Will change to the new
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
uint32_t periodCcys = highCcys + lowCcys;
if (periodCcys < MAXIRQTICKSCCYS) {
if (!highCcys) {
periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
}
else if (!lowCcys) {
highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
}
}
// sanity checks, including mixed signed/unsigned arithmetic safety
if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) ||
static_cast<int32_t>(periodCcys) <= 0 ||
static_cast<int32_t>(highCcys) < 0 || static_cast<int32_t>(lowCcys) < 0) {
return false;
}
Waveform& wave = waveform.pins[pin];
wave.dutyCcys = highCcys;
wave.adjDutyCcys = 0;
wave.periodCcys = periodCcys;
wave.autoPwm = autoPwm;
std::atomic_thread_fence(std::memory_order_acquire);
const uint32_t pinBit = 1UL << pin;
if (!(waveform.enabled & pinBit)) {
// wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR
wave.nextPeriodCcy = phaseOffsetCcys;
wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count
wave.mode = WaveformMode::INIT;
wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase;
if (!wave.dutyCcys) {
// If initially at zero duty cycle, force GPIO off
if (pin == 16) {
GP16O = 0;
}
else {
GPOC = pinBit;
}
}
std::atomic_thread_fence(std::memory_order_release);
waveform.toSetBits = 1UL << pin;
std::atomic_thread_fence(std::memory_order_release);
if (!waveform.timer1Running) {
initTimer();
}
else if (T1V > IRQLATENCYCCYS) {
// Must not interfere if Timer is due shortly
timer1_write(IRQLATENCYCCYS);
}
}
else {
wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI
std::atomic_thread_fence(std::memory_order_release);
wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count
if (runTimeCcys) {
wave.mode = WaveformMode::UPDATEEXPIRY;
std::atomic_thread_fence(std::memory_order_release);
waveform.toSetBits = 1UL << pin;
}
}
std::atomic_thread_fence(std::memory_order_acq_rel);
while (waveform.toSetBits) {
delay(0); // Wait for waveform to update
std::atomic_thread_fence(std::memory_order_acquire);
}
return true;
}
// Stops a waveform on a pin
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!waveform.timer1Running) {
return false;
}
// If user sends in a pin >16 but <32, this will always point to a 0 bit
// If they send >=32, then the shift will result in 0 and it will also return false
std::atomic_thread_fence(std::memory_order_acquire);
const uint32_t pinBit = 1UL << pin;
if (waveform.enabled & pinBit) {
waveform.toDisableBits = 1UL << pin;
std::atomic_thread_fence(std::memory_order_release);
// Must not interfere if Timer is due shortly
if (T1V > IRQLATENCYCCYS) {
timer1_write(IRQLATENCYCCYS);
}
while (waveform.toDisableBits) {
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
std::atomic_thread_fence(std::memory_order_acquire);
}
}
if (!waveform.enabled && !waveform.timer1CB) {
deinitTimer();
}
return true;
}
};
// Speed critical bits
#pragma GCC optimize ("O2")
// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
if (ISCPUFREQ160MHZ) {
return isCPU2X ? ccys : (ccys >> 1);
}
else {
return isCPU2X ? (ccys << 1) : ccys;
}
}
static ICACHE_RAM_ATTR void timer1Interrupt() {
const uint32_t isrStartCcy = ESP.getCycleCount();
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
const bool isCPU2X = CPU2X & 1;
if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
// Handle enable/disable requests from main app.
waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
waveform.toDisableBits = 0;
}
if (waveform.toSetBits) {
const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
Waveform& wave = waveform.pins[toSetPin];
switch (wave.mode) {
case WaveformMode::INIT:
waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) {
wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy;
}
else {
wave.nextPeriodCcy = waveform.nextEventCcy;
}
if (!wave.expiryCcy) {
wave.mode = WaveformMode::INFINITE;
break;
}
// fall through
case WaveformMode::UPDATEEXPIRY:
// in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
wave.mode = WaveformMode::EXPIRES;
break;
default:
break;
}
waveform.toSetBits = 0;
}
// Exit the loop if the next event, if any, is sufficiently distant.
const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
uint32_t busyPins = waveform.enabled;
waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
uint32_t now = ESP.getCycleCount();
uint32_t isrNextEventCcy = now;
while (busyPins) {
if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) {
waveform.nextEventCcy = isrNextEventCcy;
break;
}
isrNextEventCcy = waveform.nextEventCcy;
uint32_t loopPins = busyPins;
while (loopPins) {
const int pin = __builtin_ffsl(loopPins) - 1;
const uint32_t pinBit = 1UL << pin;
loopPins ^= pinBit;
Waveform& wave = waveform.pins[pin];
if (clockDrift) {
wave.endDutyCcy += clockDrift;
wave.nextPeriodCcy += clockDrift;
wave.expiryCcy += clockDrift;
}
uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
if (WaveformMode::EXPIRES == wave.mode &&
static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 &&
static_cast<int32_t>(now - wave.expiryCcy) >= 0) {
// Disable any waveforms that are done
waveform.enabled ^= pinBit;
busyPins ^= pinBit;
}
else {
const int32_t overshootCcys = now - waveNextEventCcy;
if (overshootCcys >= 0) {
const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
if (waveform.states & pinBit) {
// active configuration and forward are 100% duty
if (wave.periodCcys == wave.dutyCcys) {
wave.nextPeriodCcy += periodCcys;
wave.endDutyCcy = wave.nextPeriodCcy;
}
else {
if (wave.autoPwm) {
wave.adjDutyCcys += overshootCcys;
}
waveform.states ^= pinBit;
if (16 == pin) {
GP16O = 0;
}
else {
GPOC = pinBit;
}
}
waveNextEventCcy = wave.nextPeriodCcy;
}
else {
wave.nextPeriodCcy += periodCcys;
if (!wave.dutyCcys) {
wave.endDutyCcy = wave.nextPeriodCcy;
}
else {
int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
if (dutyCcys <= wave.adjDutyCcys) {
dutyCcys >>= 1;
wave.adjDutyCcys -= dutyCcys;
}
else if (wave.adjDutyCcys) {
dutyCcys -= wave.adjDutyCcys;
wave.adjDutyCcys = 0;
}
wave.endDutyCcy = now + dutyCcys;
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
wave.endDutyCcy = wave.nextPeriodCcy;
}
waveform.states |= pinBit;
if (16 == pin) {
GP16O = 1;
}
else {
GPOS = pinBit;
}
}
waveNextEventCcy = wave.endDutyCcy;
}
if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) {
waveNextEventCcy = wave.expiryCcy;
}
}
if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) {
busyPins ^= pinBit;
if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) {
waveform.nextEventCcy = waveNextEventCcy;
}
}
else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) {
isrNextEventCcy = waveNextEventCcy;
}
}
now = ESP.getCycleCount();
}
clockDrift = 0;
}
int32_t callbackCcys = 0;
if (waveform.timer1CB) {
callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X);
}
now = ESP.getCycleCount();
int32_t nextEventCcys = waveform.nextEventCcy - now;
// Account for unknown duration of timer1CB().
if (waveform.timer1CB && nextEventCcys > callbackCcys) {
waveform.nextEventCcy = now + callbackCcys;
nextEventCcys = callbackCcys;
}
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
int32_t deltaIrqCcys = DELTAIRQCCYS;
int32_t irqLatencyCcys = IRQLATENCYCCYS;
if (isCPU2X) {
nextEventCcys >>= 1;
deltaIrqCcys >>= 1;
irqLatencyCcys >>= 1;
}
// Firing timer too soon, the NMI occurs before ISR has returned.
if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
nextEventCcys = irqLatencyCcys;
}
else {
nextEventCcys -= deltaIrqCcys;
}
// Register access is fast and edge IRQ was configured before.
T1L = nextEventCcys;
}
#endif // WAVEFORM_LOCKED_PHASE

View File

@ -0,0 +1,93 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
Copyright (c) 2020 Dirk O. Kaar.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef WAVEFORM_LOCKED_PHASE
#include <Arduino.h>
#ifndef __ESP8266_WAVEFORM_H
#define __ESP8266_WAVEFORM_H
#ifdef __cplusplus
extern "C" {
#endif
// Start or change a waveform of the specified high and low times on specific pin.
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
// full period.
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that.
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
// under load, for applications where frequency or duty cycle must not change, leave false.
// Returns true or false on success or failure.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS,
uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false);
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next
// full period.
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that.
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
// under load, for applications where frequency or duty cycle must not change, leave false.
// Returns true or false on success or failure.
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys,
uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false);
// Stop a waveform, if any, on the specified pin.
// Returns true or false on success or failure.
int stopWaveform(uint8_t pin);
// Add a callback function to be called on *EVERY* timer1 trigger. The
// callback must return the number of CPU clock cycles until the next desired call.
// However, since it is called every timer1 interrupt, it may be called
// again before this period. It should therefore use the ESP Cycle Counter
// to determine whether or not to perform an operation.
// Pass in NULL to disable the callback and, if no other waveforms being
// generated, stop the timer as well.
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
void setTimer1Callback(uint32_t (*fn)());
#ifdef __cplusplus
}
#endif
#endif // __ESP8266_WAVEFORM_H
#endif // WAVEFORM_LOCKED_PHASE

View File

@ -0,0 +1,626 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1
is set to 1-shot mode and is always loaded with the time until the next
edge of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not
the timer. This allows for removing interrupt jitter and delay as the
counter always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not
TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef WAVEFORM_LOCKED_PHASE
#include <Arduino.h>
#include "ets_sys.h"
#include "core_esp8266_waveform_pwm.h"
#include "user_interface.h"
extern "C" {
// Maximum delay between IRQs
#define MAXIRQUS (10000)
// Waveform generator can create tones, PWM, and servos
typedef struct {
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles)
uint32_t timeLowCycles; //
uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
uint32_t desiredLowCycles; //
uint32_t lastEdge; // Cycle when this generator last changed
} Waveform;
class WVFState {
public:
Waveform waveform[17]; // State of all possible pins
uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI
uint32_t waveformNewHigh = 0;
uint32_t waveformNewLow = 0;
uint32_t (*timer1CB)() = NULL;
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
// are generating. In the common case (1 PWM) these may be the same pin and
// we can avoid looking at the other pins.
uint16_t startPin = 0;
uint16_t endPin = 0;
};
static WVFState wvfState;
// Ensure everything is read/written to RAM
#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); }
// Non-speed critical bits
#pragma GCC optimize ("Os")
// Interrupt on/off control
static ICACHE_RAM_ATTR void timer1Interrupt();
static bool timerRunning = false;
static __attribute__((noinline)) void initTimer() {
if (!timerRunning) {
timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timerRunning = true;
timer1_write(microsecondsToClockCycles(10));
}
}
static ICACHE_RAM_ATTR void forceTimerInterrupt() {
if (T1L > microsecondsToClockCycles(10)) {
T1L = microsecondsToClockCycles(10);
}
}
// PWM implementation using special purpose state machine
//
// Keep an ordered list of pins with the delta in cycles between each
// element, with a terminal entry making up the remainder of the PWM
// period. With this method sum(all deltas) == PWM period clock cycles.
//
// At t=0 set all pins high and set the timeout for the 1st edge.
// On interrupt, if we're at the last element reset to t=0 state
// Otherwise, clear that pin down and set delay for next element
// and so forth.
constexpr int maxPWMs = 8;
// PWM machine state
typedef struct PWMState {
uint32_t mask; // Bitmask of active pins
uint32_t cnt; // How many entries
uint32_t idx; // Where the state machine is along the list
uint8_t pin[maxPWMs + 1];
uint32_t delta[maxPWMs + 1];
uint32_t nextServiceCycle; // Clock cycle for next step
struct PWMState *pwmUpdate; // Set by main code, cleared by ISR
} PWMState;
static PWMState pwmState;
static uint32_t _pwmFreq = 1000;
static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq;
// If there are no more scheduled activities, shut down Timer 1.
// Otherwise, do nothing.
static ICACHE_RAM_ATTR void disableIdleTimer() {
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
timer1_isr_init();
timerRunning = false;
}
}
// Notify the NMI that a new PWM state is available through the mailbox.
// Wait for mailbox to be emptied (either busy or delay() as needed)
static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
p->pwmUpdate = nullptr;
pwmState.pwmUpdate = p;
MEMBARRIER();
forceTimerInterrupt();
while (pwmState.pwmUpdate) {
if (idle) {
delay(0);
}
MEMBARRIER();
}
}
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range);
// Called when analogWriteFreq() changed to update the PWM total period
void _setPWMFreq(uint32_t freq) {
_pwmFreq = freq;
// Convert frequency into clock cycles
uint32_t cc = microsecondsToClockCycles(1000000UL) / freq;
// Simple static adjustment to bring period closer to requested due to overhead
// Empirically determined as a constant PWM delay and a function of the number of PWMs
#if F_CPU == 80000000
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 13) >> 4) + 110;
#else
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 10) >> 4) + 75;
#endif
if (cc == _pwmPeriod) {
return; // No change
}
_pwmPeriod = cc;
if (pwmState.cnt) {
PWMState p; // The working copy since we can't edit the one in use
p.mask = 0;
p.cnt = 0;
for (uint32_t i = 0; i < pwmState.cnt; i++) {
auto pin = pwmState.pin[i];
_addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles);
}
// Update and wait for mailbox to be emptied
initTimer();
_notifyPWM(&p, true);
disableIdleTimer();
}
}
// Helper routine to remove an entry from the state machine
// and clean up any marked-off entries
static void _cleanAndRemovePWM(PWMState *p, int pin) {
uint32_t leftover = 0;
uint32_t in, out;
for (in = 0, out = 0; in < p->cnt; in++) {
if ((p->pin[in] != pin) && (p->mask & (1<<p->pin[in]))) {
p->pin[out] = p->pin[in];
p->delta[out] = p->delta[in] + leftover;
leftover = 0;
out++;
} else {
leftover += p->delta[in];
p->mask &= ~(1<<p->pin[in]);
}
}
p->cnt = out;
// Final pin is never used: p->pin[out] = 0xff;
p->delta[out] = p->delta[in] + leftover;
}
// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
ICACHE_RAM_ATTR bool _stopPWM(int pin) {
if (!((1<<pin) & pwmState.mask)) {
return false; // Pin not actually active
}
PWMState p; // The working copy since we can't edit the one in use
p = pwmState;
// In _stopPWM we just clear the mask but keep everything else
// untouched to save IRAM. The main startPWM will handle cleanup.
p.mask &= ~(1<<pin);
if (!p.mask) {
// If all have been stopped, then turn PWM off completely
p.cnt = 0;
}
// Update and wait for mailbox to be emptied, no delay (could be in ISR)
_notifyPWM(&p, false);
// Possibly shut down the timer completely if we're done
disableIdleTimer();
return true;
}
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range) {
// Stash the val and range so we can re-evaluate the fraction
// should the user change PWM frequency. This allows us to
// give as great a precision as possible. We know by construction
// that the waveform for this pin will be inactive so we can borrow
// memory from that structure.
wvfState.waveform[pin].desiredHighCycles = val; // Numerator == high
wvfState.waveform[pin].desiredLowCycles = range; // Denominator == low
uint32_t cc = (_pwmPeriod * val) / range;
// Clip to sane values in the case we go from OK to not-OK when adjusting frequencies
if (cc == 0) {
cc = 1;
} else if (cc >= _pwmPeriod) {
cc = _pwmPeriod - 1;
}
if (p.cnt == 0) {
// Starting up from scratch, special case 1st element and PWM period
p.pin[0] = pin;
p.delta[0] = cc;
// Final pin is never used: p.pin[1] = 0xff;
p.delta[1] = _pwmPeriod - cc;
} else {
uint32_t ttl = 0;
uint32_t i;
// Skip along until we're at the spot to insert
for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) {
ttl += p.delta[i];
}
// Shift everything out by one to make space for new edge
for (int32_t j = p.cnt; j >= (int)i; j--) {
p.pin[j + 1] = p.pin[j];
p.delta[j + 1] = p.delta[j];
}
int off = cc - ttl; // The delta from the last edge to the one we're inserting
p.pin[i] = pin;
p.delta[i] = off; // Add the delta to this new pin
p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant
}
p.cnt++;
p.mask |= 1<<pin;
}
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
bool _setPWM(int pin, uint32_t val, uint32_t range) {
stopWaveform(pin);
PWMState p; // Working copy
p = pwmState;
// Get rid of any entries for this pin
_cleanAndRemovePWM(&p, pin);
// And add it to the list, in order
if (p.cnt >= maxPWMs) {
return false; // No space left
}
// Sanity check for all-on/off
uint32_t cc = (_pwmPeriod * val) / range;
if ((cc == 0) || (cc >= _pwmPeriod)) {
digitalWrite(pin, cc ? HIGH : LOW);
return true;
}
_addPWMtoList(p, pin, val, range);
// Set mailbox and wait for ISR to copy it over
initTimer();
_notifyPWM(&p, true);
disableIdleTimer();
// Potentially recalculate the PWM period if we've added another pin
_setPWMFreq(_pwmFreq);
return true;
}
// Start up a waveform on a pin, or change the current one. Will change to the new
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) {
return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS));
}
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) {
if ((pin > 16) || isFlashInterfacePin(pin)) {
return false;
}
Waveform *wave = &wvfState.waveform[pin];
wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0;
if (runTimeCycles && !wave->expiryCycle) {
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
}
_stopPWM(pin); // Make sure there's no PWM live here
uint32_t mask = 1<<pin;
MEMBARRIER();
if (wvfState.waveformEnabled & mask) {
// Make sure no waveform changes are waiting to be applied
while (wvfState.waveformToChange) {
delay(0); // Wait for waveform to update
// No mem barrier here, the call to a global function implies global state updated
}
wvfState.waveformNewHigh = timeHighCycles;
wvfState.waveformNewLow = timeLowCycles;
MEMBARRIER();
wvfState.waveformToChange = mask;
// The waveform will be updated some time in the future on the next period for the signal
} else { // if (!(wvfState.waveformEnabled & mask)) {
wave->timeHighCycles = timeHighCycles;
wave->desiredHighCycles = timeHighCycles;
wave->timeLowCycles = timeLowCycles;
wave->desiredLowCycles = timeLowCycles;
wave->lastEdge = 0;
wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
wvfState.waveformToEnable |= mask;
MEMBARRIER();
initTimer();
forceTimerInterrupt();
while (wvfState.waveformToEnable) {
delay(0); // Wait for waveform to update
// No mem barrier here, the call to a global function implies global state updated
}
}
return true;
}
// Set a callback. Pass in NULL to stop it
void setTimer1Callback(uint32_t (*fn)()) {
wvfState.timer1CB = fn;
if (fn) {
initTimer();
forceTimerInterrupt();
}
disableIdleTimer();
}
// Stops a waveform on a pin
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!timerRunning) {
return false;
}
// If user sends in a pin >16 but <32, this will always point to a 0 bit
// If they send >=32, then the shift will result in 0 and it will also return false
uint32_t mask = 1<<pin;
if (wvfState.waveformEnabled & mask) {
wvfState.waveformToDisable = mask;
// Cancel any pending updates for this waveform, too.
if (wvfState.waveformToChange & mask) {
wvfState.waveformToChange = 0;
}
forceTimerInterrupt();
while (wvfState.waveformToDisable) {
MEMBARRIER(); // If it wasn't written yet, it has to be by now
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
}
}
disableIdleTimer();
return true;
}
// Speed critical bits
#pragma GCC optimize ("O2")
// Normally would not want two copies like this, but due to different
// optimization levels the inline attribute gets lost if we try the
// other version.
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
return ccount;
}
// Find the earliest cycle as compared to right now
static inline ICACHE_RAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) {
uint32_t now = GetCycleCountIRQ();
int32_t da = a - now;
int32_t db = b - now;
return (da < db) ? a : b;
}
// The SDK and hardware take some time to actually get to our NMI code, so
// decrement the next IRQ's timer value by a bit so we can actually catch the
// real CPU cycle counter we want for the waveforms.
// The SDK also sometimes is running at a different speed the the Arduino core
// so the ESP cycle counter is actually running at a variable speed.
// adjust(x) takes care of adjusting a delta clock cycle amount accordingly.
#if F_CPU == 80000000
#define DELTAIRQ (microsecondsToClockCycles(9)/4)
#define adjust(x) ((x) << (turbo ? 1 : 0))
#else
#define DELTAIRQ (microsecondsToClockCycles(9)/8)
#define adjust(x) ((x) >> 0)
#endif
// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage
#define MINIRQTIME microsecondsToClockCycles(4)
static ICACHE_RAM_ATTR void timer1Interrupt() {
// Flag if the core is at 160 MHz, for use by adjust()
bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;
uint32_t nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
if (wvfState.waveformToEnable || wvfState.waveformToDisable) {
// Handle enable/disable requests from main app
wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off
wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started
wvfState.waveformToEnable = 0;
wvfState.waveformToDisable = 0;
// No mem barrier. Globals must be written to RAM on ISR exit.
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1;
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
} else if (!pwmState.cnt && pwmState.pwmUpdate) {
// Start up the PWM generator by copying from the mailbox
pwmState.cnt = 1;
pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0
pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop!
// No need for mem barrier here. Global must be written by IRQ exit
}
bool done = false;
if (wvfState.waveformEnabled || pwmState.cnt) {
do {
nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
// PWM state machine implementation
if (pwmState.cnt) {
int32_t cyclesToGo;
do {
cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ();
if (cyclesToGo < 0) {
if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new
if (pwmState.pwmUpdate) {
// Do the memory copy from temp to global and clear mailbox
pwmState = *(PWMState*)pwmState.pwmUpdate;
}
GPOS = pwmState.mask; // Set all active pins high
if (pwmState.mask & (1<<16)) {
GP16O = 1;
}
pwmState.idx = 0;
} else {
do {
// Drop the pin at this edge
if (pwmState.mask & (1<<pwmState.pin[pwmState.idx])) {
GPOC = 1<<pwmState.pin[pwmState.idx];
if (pwmState.pin[pwmState.idx] == 16) {
GP16O = 0;
}
}
pwmState.idx++;
// Any other pins at this same PWM value will have delta==0, drop them too.
} while (pwmState.delta[pwmState.idx] == 0);
}
// Preserve duty cycle over PWM period by using now+xxx instead of += delta
cyclesToGo = adjust(pwmState.delta[pwmState.idx]);
pwmState.nextServiceCycle = GetCycleCountIRQ() + cyclesToGo;
}
nextEventCycle = earliest(nextEventCycle, pwmState.nextServiceCycle);
} while (pwmState.cnt && (cyclesToGo < 100));
}
for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) {
uint32_t mask = 1<<i;
// If it's not on, ignore!
if (!(wvfState.waveformEnabled & mask)) {
continue;
}
Waveform *wave = &wvfState.waveform[i];
uint32_t now = GetCycleCountIRQ();
// Disable any waveforms that are done
if (wave->expiryCycle) {
int32_t expiryToGo = wave->expiryCycle - now;
if (expiryToGo < 0) {
// Done, remove!
if (i == 16) {
GP16O = 0;
}
GPOC = mask;
wvfState.waveformEnabled &= ~mask;
continue;
}
}
// Check for toggles
int32_t cyclesToGo = wave->nextServiceCycle - now;
if (cyclesToGo < 0) {
uint32_t nextEdgeCycles;
uint32_t desired = 0;
uint32_t *timeToUpdate;
wvfState.waveformState ^= mask;
if (wvfState.waveformState & mask) {
if (i == 16) {
GP16O = 1;
}
GPOS = mask;
if (wvfState.waveformToChange & mask) {
// Copy over next full-cycle timings
wave->timeHighCycles = wvfState.waveformNewHigh;
wave->desiredHighCycles = wvfState.waveformNewHigh;
wave->timeLowCycles = wvfState.waveformNewLow;
wave->desiredLowCycles = wvfState.waveformNewLow;
wave->lastEdge = 0;
wvfState.waveformToChange = 0;
}
if (wave->lastEdge) {
desired = wave->desiredLowCycles;
timeToUpdate = &wave->timeLowCycles;
}
nextEdgeCycles = wave->timeHighCycles;
} else {
if (i == 16) {
GP16O = 0;
}
GPOC = mask;
desired = wave->desiredHighCycles;
timeToUpdate = &wave->timeHighCycles;
nextEdgeCycles = wave->timeLowCycles;
}
if (desired) {
desired = adjust(desired);
int32_t err = desired - (now - wave->lastEdge);
if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal
err /= 2;
*timeToUpdate += err;
}
}
nextEdgeCycles = adjust(nextEdgeCycles);
wave->nextServiceCycle = now + nextEdgeCycles;
wave->lastEdge = now;
}
nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle);
}
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
uint32_t now = GetCycleCountIRQ();
int32_t cycleDeltaNextEvent = nextEventCycle - now;
int32_t cyclesLeftTimeout = timeoutCycle - now;
done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0);
} while (!done);
} // if (wvfState.waveformEnabled)
if (wvfState.timer1CB) {
nextEventCycle = earliest(nextEventCycle, GetCycleCountIRQ() + wvfState.timer1CB());
}
int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ();
if (nextEventCycles < MINIRQTIME) {
nextEventCycles = MINIRQTIME;
}
nextEventCycles -= DELTAIRQ;
// Do it here instead of global function to save time and because we know it's edge-IRQ
T1L = nextEventCycles >> (turbo ? 1 : 0);
}
};
#endif

View File

@ -0,0 +1,87 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef WAVEFORM_LOCKED_PHASE
#include <Arduino.h>
#ifndef __ESP8266_WAVEFORM_H
#define __ESP8266_WAVEFORM_H
#ifdef __cplusplus
extern "C" {
#endif
// Start or change a waveform of the specified high and low times on specific pin.
// If runtimeUS > 0 then automatically stop it after that many usecs.
// Returns true or false on success or failure.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS);
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles.
// Returns true or false on success or failure.
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles);
// Stop a waveform, if any, on the specified pin.
// Returns true or false on success or failure.
int stopWaveform(uint8_t pin);
// Add a callback function to be called on *EVERY* timer1 trigger. The
// callback must return the number of CPU clock cycles until the next desired call.
// However, since it is called every timer1 interrupt, it may be called
// again before this period. It should therefore use the ESP Cycle Counter
// to determine whether or not to perform an operation.
// Pass in NULL to disable the callback and, if no other waveforms being
// generated, stop the timer as well.
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
void setTimer1Callback(uint32_t (*fn)());
// Internal-only calls, not for applications
extern void _setPWMFreq(uint32_t freq);
extern bool _stopPWM(int pin);
extern bool _setPWM(int pin, uint32_t val, uint32_t range);
#ifdef __cplusplus
}
#endif
#endif
#endif

View File

@ -82,7 +82,10 @@ extern void __pinMode(uint8_t pin, uint8_t mode) {
}
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
stopWaveform(pin);
stopWaveform(pin); // Disable any tone
#ifndef WAVEFORM_LOCKED_PHASE
_stopPWM(pin); // ...and any analogWrite
#endif
if(pin < 16){
if(val) GPOS = (1 << pin);
else GPOC = (1 << pin);

View File

@ -26,27 +26,18 @@
extern "C" {
static uint32_t analogMap = 0;
static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x
#ifdef WAVEFORM_LOCKED_PHASE
static uint32_t analogMap = 0;
static uint16_t analogFreq = 1000;
extern void __analogWriteRange(uint32_t range) {
if ((range >= 15) && (range <= 65535)) {
analogScale = range;
}
}
extern void __analogWriteResolution(int res) {
if ((res >= 4) && (res <= 16)) {
analogScale = (1 << res) - 1;
}
}
extern void __analogWriteFreq(uint32_t freq) {
if (freq < 100) {
analogFreq = 100;
} else if (freq > 40000) {
analogFreq = 40000;
} else if (freq > 60000) {
analogFreq = 60000;
} else {
analogFreq = freq;
}
@ -63,22 +54,68 @@ extern void __analogWrite(uint8_t pin, int val) {
val = analogScale;
}
if (analogMap & 1UL << pin) {
// Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
// val: the duty cycle: between 0 (always off) and 255 (always on).
// So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH)
analogMap &= ~(1 << pin);
analogMap &= ~(1 << pin);
}
else {
pinMode(pin, OUTPUT);
}
uint32_t high = (analogPeriod * val) / analogScale;
uint32_t low = analogPeriod - high;
pinMode(pin, OUTPUT);
if (low == 0) {
digitalWrite(pin, HIGH);
} else if (high == 0) {
digitalWrite(pin, LOW);
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
int phaseReference = __builtin_ffs(analogMap) - 1;
if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) {
analogMap |= (1 << pin);
}
}
#else // !WAVEFORM_LOCKED_PHASE
extern void __analogWriteFreq(uint32_t freq) {
if (freq < 100) {
freq = 100;
} else if (freq > 60000) {
freq = 60000;
} else {
if (startWaveformClockCycles(pin, high, low, 0)) {
analogMap |= (1 << pin);
}
freq = freq;
}
_setPWMFreq(freq);
}
extern void __analogWrite(uint8_t pin, int val) {
if (pin > 16) {
return;
}
if (val < 0) {
val = 0;
} else if (val > analogScale) {
val = analogScale;
}
// Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
// val: the duty cycle: between 0 (always off) and 255 (always on).
// So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH)
pinMode(pin, OUTPUT);
_setPWM(pin, val, analogScale);
}
#endif // WAVEFORM_LOCKED_PHASE
extern void __analogWriteRange(uint32_t range) {
if ((range >= 15) && (range <= 65535)) {
analogScale = range;
}
}
extern void __analogWriteResolution(int res) {
if ((res >= 4) && (res <= 16)) {
analogScale = (1 << res) - 1;
}
}

View File

@ -725,7 +725,7 @@ int HTTPClient::writeToStream(Stream * stream)
int ret = 0;
if(_transferEncoding == HTTPC_TE_IDENTITY) {
if(len > 0) {
if(len > 0 || len == -1) {
ret = writeToStreamDataBlock(stream, len);
// have we an error?
@ -1185,6 +1185,12 @@ int HTTPClient::writeToStreamDataBlock(Stream * stream, int size)
readBytes = buff_size;
}
// len == -1 or len > what is available, read only what is available
int av = _client->available();
if (readBytes < 0 || readBytes > av) {
readBytes = av;
}
// read data
int bytesRead = _client->readBytes(buff, readBytes);
if (!bytesRead)

View File

@ -246,7 +246,7 @@ protected:
bool _parseForm(ClientType& client, const String& boundary, uint32_t len);
bool _parseFormUploadAborted();
void _uploadWriteByte(uint8_t b);
uint8_t _uploadReadByte(ClientType& client);
int _uploadReadByte(ClientType& client);
void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
bool _collectHeader(const char* headerName, const char* headerValue);

View File

@ -347,14 +347,14 @@ void ESP8266WebServerTemplate<ServerType>::_uploadWriteByte(uint8_t b){
}
template <typename ServerType>
uint8_t ESP8266WebServerTemplate<ServerType>::_uploadReadByte(ClientType& client){
int ESP8266WebServerTemplate<ServerType>::_uploadReadByte(ClientType& client){
int res = client.read();
if(res == -1){
while(!client.available() && client.connected())
yield();
res = client.read();
}
return (uint8_t)res;
return res;
}
@ -444,45 +444,34 @@ bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->status = UPLOAD_FILE_WRITE;
int bLen = boundary.length();
uint8_t boundBuf[2 + bLen + 1]; // "--" + boundary + null terminator
boundBuf[2 + bLen] = '\0';
uint8_t argByte;
bool first = true;
while (1) {
//attempt to fill up boundary buffer with length of boundary string
int i;
for (i = 0; i < 2 + bLen; i++) {
if (!client.connected()) return _parseFormUploadAborted();
argByte = _uploadReadByte(client);
if (argByte == '\r')
int fastBoundaryLen = 4 /* \r\n-- */ + boundary.length() + 1 /* \0 */;
char fastBoundary[ fastBoundaryLen ];
snprintf(fastBoundary, fastBoundaryLen, "\r\n--%s", boundary.c_str());
int boundaryPtr = 0;
while ( true ) {
int ret = _uploadReadByte(client);
if (ret < 0) {
// Unexpected, we should have had data available per above
return _parseFormUploadAborted();
}
char in = (char) ret;
if (in == fastBoundary[ boundaryPtr ]) {
// The input matched the current expected character, advance and possibly exit this file
boundaryPtr++;
if (boundaryPtr == fastBoundaryLen - 1) {
// We read the whole boundary line, we're done here!
break;
boundBuf[i] = argByte;
}
if ((strncmp((const char*)boundBuf, "--", 2) == 0) && (strcmp((const char*)(boundBuf + 2), boundary.c_str()) == 0))
break; //found the boundary, done parsing this file
if (first) first = false; //only add newline characters after the first line
else {
_uploadWriteByte('\r');
_uploadWriteByte('\n');
}
// current line does not contain boundary, upload all bytes in boundary buffer
for (int j = 0; j < i; j++)
_uploadWriteByte(boundBuf[j]);
// the initial pass (filling up the boundary buffer) did not reach the end of the line. Upload the rest of the line now
if (i >= 2 + bLen) {
if (!client.connected()) return _parseFormUploadAborted();
argByte = _uploadReadByte(client);
while (argByte != '\r') {
if (!client.connected()) return _parseFormUploadAborted();
_uploadWriteByte(argByte);
argByte = _uploadReadByte(client);
}
} else {
// The char doesn't match what we want, so dump whatever matches we had, the read in char, and reset ptr to start
for (int i = 0; i < boundaryPtr; i++) {
_uploadWriteByte( fastBoundary[ i ] );
}
_uploadWriteByte( in );
boundaryPtr = 0;
}
if (!client.connected()) return _parseFormUploadAborted();
_uploadReadByte(client); // '\n'
}
//Found the boundary string, finish processing this file upload
// Found the boundary string, finish processing this file upload
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->totalSize += _currentUpload->currentSize;

View File

@ -69,8 +69,13 @@ uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs)
uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs, int value)
{
if (!_attached) {
#ifdef WAVEFORM_LOCKED_PHASE
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW);
#else
digitalWrite(pin, LOW);
pinMode(pin, OUTPUT);
#endif
_pin = pin;
_attached = true;
}
@ -90,7 +95,11 @@ void Servo::detach()
{
if (_attached) {
_servoMap &= ~(1 << _pin);
#ifdef WAVEFORM_LOCKED_PHASE
startWaveform(_pin, 0, REFRESH_INTERVAL, 1);
#else
// TODO - timeHigh == 0 is illegal in _PWM code branch. Do nothing for now.
#endif
delay(REFRESH_INTERVAL / 1000); // long enough to complete active period under all circumstances.
stopWaveform(_pin);
_attached = false;
@ -115,7 +124,14 @@ void Servo::writeMicroseconds(int value)
_valueUs = value;
if (_attached) {
_servoMap &= ~(1 << _pin);
if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0)) {
#ifdef WAVEFORM_LOCKED_PHASE
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
int phaseReference = __builtin_ffs(_servoMap) - 1;
if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0, phaseReference))
#else
if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0))
#endif
{
_servoMap |= (1 << _pin);
}
}

@ -1 +1 @@
Subproject commit 4c08ee8d2cb7b5b27eb4f86797694cbac94aa5c9
Subproject commit 6d520c259cad4457ccdbee362c16f7fa3b504b06

View File

@ -55,7 +55,7 @@ compiler.libc.path={runtime.platform.path}/tools/sdk/libc/xtensa-lx106-elf
compiler.cpreprocessor.flags=-D__ets__ -DICACHE_FLASH -U__STRICT_ANSI__ "-I{compiler.sdk.path}/include" "-I{compiler.sdk.path}/{build.lwip_include}" "-I{compiler.libc.path}/include" "-I{build.path}/core"
compiler.c.cmd=xtensa-lx106-elf-gcc
compiler.c.flags=-c {compiler.warning_flags} -std=gnu17 {build.stacksmash_flags} -Os -g -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals -falign-functions=4 -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags}
compiler.c.flags=-c {compiler.warning_flags} -std=gnu17 {build.stacksmash_flags} -Os -g -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals -falign-functions=4 -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags} {build.waveform}
compiler.S.cmd=xtensa-lx106-elf-gcc
compiler.S.flags=-c -g -x assembler-with-cpp -MMD -mlongcalls
@ -66,7 +66,7 @@ compiler.c.elf.cmd=xtensa-lx106-elf-gcc
compiler.c.elf.libs=-lhal -lphy -lpp -lnet80211 {build.lwip_lib} -lwpa -lcrypto -lmain -lwps -lbearssl -lespnow -lsmartconfig -lairkiss -lwpa2 {build.stdcpp_lib} -lm -lc -lgcc
compiler.cpp.cmd=xtensa-lx106-elf-g++
compiler.cpp.flags=-c {compiler.warning_flags} {build.stacksmash_flags} -Os -g -mlongcalls -mtext-section-literals -fno-rtti -falign-functions=4 {build.stdcpp_level} -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags}
compiler.cpp.flags=-c {compiler.warning_flags} {build.stacksmash_flags} -Os -g -mlongcalls -mtext-section-literals -fno-rtti -falign-functions=4 {build.stdcpp_level} -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags} {build.waveform}
compiler.as.cmd=xtensa-lx106-elf-as

View File

@ -66,6 +66,8 @@ function build_sketches()
local sketches=$(find $srcpath -name *.ino | sort)
print_size_info >size.log
export ARDUINO_IDE_PATH=$arduino
local pwm_phase=""
[ $(( $build_rem % 2 )) -eq 0 ] && pwm_phase="--waveform_phase"
local testcnt=0
for sketch in $sketches; do
testcnt=$(( ($testcnt + 1) % $build_mod ))
@ -104,7 +106,7 @@ function build_sketches()
fi
echo -e "\n ------------ Building $sketch ------------ \n";
# $arduino --verify $sketch;
if [ "$WINDOWS" == "1" ]; then
if [ "$WINDOWS" == "1" ]; then
sketch=$(echo $sketch | sed 's/^\/c//')
# MINGW will try to be helpful and silently convert args that look like paths to point to a spot inside the MinGW dir. This breaks everything.
# http://www.mingw.org/wiki/Posix_path_conversion
@ -112,8 +114,8 @@ function build_sketches()
export MSYS2_ARG_CONV_EXC="*"
export MSYS_NO_PATHCONV=1
fi
echo "$build_cmd $sketch"
time ($build_cmd $sketch >build.log)
echo "$build_cmd $pwm_phase $sketch"
time ($build_cmd $pwm_phase $sketch >build.log)
local result=$?
if [ $result -ne 0 ]; then
echo "Build failed ($1)"

View File

@ -1466,6 +1466,18 @@ def led (name, default, ledList):
]))
return { name: led }
################################################################
# Waveform flavour
def waveform ():
return { 'waveform': collections.OrderedDict([
('.menu.waveform.pwm', 'Locked PWM'),
('.menu.waveform.pwm.build.waveform', ''),
('.menu.waveform.phase', 'Locked Phase'),
('.menu.waveform.phase.build.waveform', '-DWAVEFORM_LOCKED_PHASE'),
])
}
################################################################
# sdk selection
@ -1517,6 +1529,7 @@ def all_boards ():
macros.update(led('led', led_default, range(0,led_max+1)))
macros.update(led('led216', 2, { 16 }))
macros.update(sdk())
macros.update(waveform())
if boardfilteropt or excludeboards:
print('#')
@ -1561,6 +1574,7 @@ def all_boards ():
print('menu.wipe=Erase Flash')
print('menu.sdk=Espressif FW')
print('menu.ssl=SSL Support')
print('menu.waveform=Waveform Flavour')
print('')
missingboards = []
@ -1581,7 +1595,7 @@ def all_boards ():
print(id + optname + '=' + board['opts'][optname])
# macros
macrolist = [ 'defaults', 'cpufreq_menu', 'vtable_menu', 'exception_menu', 'stacksmash_menu', 'ssl_cipher_menu' ]
macrolist = [ 'defaults', 'cpufreq_menu', 'vtable_menu', 'exception_menu', 'stacksmash_menu', 'ssl_cipher_menu', 'waveform' ]
if 'macro' in board:
macrolist += board['macro']
macrolist += [ 'lwip', 'debug_menu', 'flash_erase_menu' ]

View File

@ -69,6 +69,8 @@ def compile(tmp_dir, sketch, cache, tools_dir, hardware_dir, ide_path, f, args):
'ResetMethod=nodemcu'.format(**vars(args))
if args.debug_port and args.debug_level:
fqbn += 'dbg={debug_port},lvl={debug_level}'.format(**vars(args))
if args.waveform_phase:
fqbn += ',waveform=phase'
cmd += [fqbn]
cmd += ['-built-in-libraries', ide_path + '/libraries']
cmd += ['-ide-version=10607']
@ -115,6 +117,8 @@ def parse_args():
type=int, choices=[40, 80])
parser.add_argument('--debug_port', help='Debug port',
choices=['Serial', 'Serial1'])
parser.add_argument('--waveform_phase', action='store_true',
help='Select waveform locked on phase')
parser.add_argument('--debug_level', help='Debug level')
parser.add_argument('--build_cache', help='Build directory to cache core.a', default='')
parser.add_argument('sketch_path', help='Sketch file path')

View File

@ -141,6 +141,31 @@ def add_crc(out):
with open(out, "wb") as binfile:
binfile.write(raw)
def gzip_bin(mode, out):
import gzip
firmware_path = out
gzip_path = firmware_path + '.gz'
orig_path = firmware_path + '.orig'
if os.path.exists(gzip_path):
os.remove(gzip_path)
print('GZipping firmware ' + firmware_path)
with open(firmware_path, 'rb') as firmware_file, \
gzip.open(gzip_path, 'wb') as dest:
data = firmware_file.read()
dest.write(data)
orig_size = os.stat(firmware_path).st_size
gzip_size = os.stat(gzip_path).st_size
print("New FW size {:d} bytes vs old {:d} bytes".format(
gzip_size, orig_size))
if mode == "PIO":
if os.path.exists(orig_path):
os.remove(orig_path)
print('Moving original firmware to ' + orig_path)
os.rename(firmware_path, orig_path)
os.rename(gzip_path, firmware_path)
def main():
parser = argparse.ArgumentParser(description='Create a BIN file from eboot.elf and Arduino sketch.elf for upload by esptool.py')
parser.add_argument('-e', '--eboot', action='store', required=True, help='Path to the Arduino eboot.elf bootloader')
@ -150,6 +175,7 @@ def main():
parser.add_argument('-s', '--flash_size', action='store', required=True, choices=['256K', '512K', '1M', '2M', '4M', '8M', '16M'], help='SPI flash size')
parser.add_argument('-o', '--out', action='store', required=True, help='Output BIN filename')
parser.add_argument('-p', '--path', action='store', required=True, help='Path to Xtensa toolchain binaries')
parser.add_argument('-g', '--gzip', choices=['PIO', 'Arduino'], help='PIO - generate gzipped BIN file, Arduino - generate BIN and BIN.gz')
args = parser.parse_args()
@ -175,6 +201,9 @@ def main():
# Because the CRC includes both eboot and app, can only calculate it after the entire BIN generated
add_crc(args.out)
if args.gzip:
gzip_bin(args.gzip, args.out)
return 0

View File

@ -45,10 +45,15 @@ Builder.match_splitext = scons_patched_match_splitext
env = DefaultEnvironment()
platform = env.PioPlatform()
board = env.BoardConfig()
gzip_fw = board.get("build.gzip_fw", False)
gzip_switch = []
FRAMEWORK_DIR = platform.get_package_dir("framework-arduinoespressif8266")
assert isdir(FRAMEWORK_DIR)
if gzip_fw:
gzip_switch = ["--gzip", "PIO"]
env.Append(
ASFLAGS=["-x", "assembler-with-cpp"],
@ -145,7 +150,7 @@ env.Append(
"--path", '"%s"' % join(
platform.get_package_dir("toolchain-xtensa"), "bin"),
"--out", "$TARGET"
]), "Building $TARGET"),
] + gzip_switch), "Building $TARGET"),
suffix=".bin"
)
)
@ -237,6 +242,13 @@ else:
LIBS=["lwip2-536-feat"]
)
#
# Waveform
#
if "PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PHASE" in flatten_cppdefines:
env.Append(CPPDEFINES=[("WAVEFORM_LOCKED_PHASE", 1)])
# PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PWM will be used by default
#
# VTables
#